【速过C primer plus】字符串

一、字符串数组与指针区.

​ 字符串在程序运行时,独自存储在静态存储区中,无论是含有字符串数组还是指向字符串的指针的程序中,都必须单独为字符串预留一个静态内存来存储字符串字面量

​ 但程序一旦编译,字符串数组与指向字符串的指针就会有所不同:

​ 1)字符串数组会被单独开辟出来一段空间,然后将字符串字面量复制进入字符串数组,此时系统中有两个字符串字面量的空间,一个是原本字面量被存储的静态存储区,另一个则是字符串数组内部,他们的存储地址是不同的。

​ 2)指向字符串的指针不会被单独开辟空间,而是直接将指针地址指向字符串字面量的地址,此时系统中只有一个字符串字面量空间。

​ 综上所述,对于单独存储字符串字面量时,指向其的指针应该是属于const类型,不可修改,如果修改则会造成程序原本存储的字符串字面量会同时修改。而字符串数组是复制副本,则可以用于修改,它的修改不会影响程序原本存储的字符串字面量。

二、字符串数组

​ 指针的地址并不是禁止直接修改的,你可以使用正常的赋值方式来直接修改指针的地址,如果是整型数则会被尝试解释为内存地址,如果是字符串则会被分配一个随机的地址。

1
2
3
int* i = 1;
printf("%d", i);
//这样写会造成给指针*i直接指向内存000001的系统位置,是直接给指针地址赋值了,而地址内部数据实际上是未知的。

​ 但是在处理指针为字符串时,定义发生了一些变化:

1
2
3
const char* i = "123";
printf("%s", i);
//这样定义字符串变量是被允许的

​ 在这个例子中,i的地址不会被直接赋值123这个具体的数值,内存分配过程中,是编译器先给字面量123这个字符串分配一个具体的空间,然后把i的指针指向这个存储空间。在快速定义字符串时,可以使用这种指针定义法,不过需要注意以下两点:

​ 1)在规则中,允许的定义方式需要在前面加入const,使指针常量化,这会导致无法单个修改指针中的单个字符(即变为了java意义上的String变量,或者说const char[]),如果不加入const,则赋值结果是未知的,因为规则没有定义这种行为。

​ 2)%s针对的是char*变量,无论如何输出都必须对应字符串指针。


​ 通过以上这种指针定义方式,可以得到两种定义字符串数组的方法:

1
2
3
4
	char a[3][3] = { "12","23","34" };//二维字符串数组
const char* b[3] = { "12","23","34" };//指针数组
printf("%s与%s", a[1], b[1]);
//输出:23与23

​ 在定义中,指针构造的字符串数组效率会更高,并且可以不提前确定字面量的个数,但它不能修改字面量。

三、字符串输入与输出

1)注意事项

​ 为什么不能使用如下这种写法定义动态字符串?

1
2
3
char* a;
scanf("%s", a);
printf("%s", a);

​ 在VS中,这种写法甚至不能通过编译(使用了未初始化的变量a)。原因是指针a从一开始就没有被分配地址,在scanf进行输入后,指针a可能会指向任何地方(比如指向已经被使用的内存,导致程序数值被修改)。使用动态数组用动态内存分配解决,否则就使用固定长度的字符串数组。

2)gets()与puts()

1
2
3
char a[5];
gets(a);
puts(a);

​ 可以使用gets()函数直接读取一整行(不会像scanf()读取%s时只读取一个单词,gets()会将空格一并读取),再配合puts()进行输出。但是gets()有缺陷,它无法确定字符串a界限在何处,所以如果越界输入,则会导致程序异常。

3)fgets()与fputs()

​ 为了解决这个问题,C语言使用fgets()fputs()代替了gets()puts()

1
2
3
4
5
	char a[5];
fgets(a,5,stdin);
fputs(a,stdout);
//输入:123456
//输出:1234

fgets()需要三个参数:字符串载体、控制输入的宽度N(实际会输入N-1个,最后一个默认为\0)、输入方式(可以固定为stdin)。

fputs()除了原本的字符串载体之外,还有一个输出方式,一般固定为stdout

4)scanf与其特殊用法

​ 在之前的文章中说明了,scanf可以通过%s为占位符读取除了空格符、换行符以外的字符:

1
2
3
4
5
char a[6];
scanf("%s", a);
printf("%s", a);
//输入:A B C
//输出:A

​ 但是如果想读取空格或者其他特殊字符,应该如何使用scanf?

​ 可以使用特殊字符集占位符强制读取空格:

1
2
3
4
5
char a[6];
scanf("%[^\n]", a);
printf("%s", a);
//输入:A B C
//输出:A B C

​ scanf有一种特殊的占位符%[]字符集,它使用的是一种类似正则表达式的语法,按照需求填入字符则scanf只会读取需求中的字符,如果遇到非字符集中的字符则会跳出。常见有以下几个规则:

1
2
3
4
5
6
7
[^\n]表示非换行符的所有字符,^表示非
[0-9]表示读取0-9之间的数字
[a-zA-Z]表示只读入字母
[abc][bca][acb][c-a]都表示只读取a,b,c三种字符

以上的所有情况中,读取到了中括号中以外的其他字符则停止读取
可以在前中括号外前缀n来控制输入字符个数

四、尽量不要使用string.h

​ 在笔者测试时,string.h中大部分函数都失效\不安全了。这些函数在早期定义时就有很多问题,不同编译器中给出了不同的解决方案,建议在需要进行字符串操作时,自定义函数进行处理,不要依赖string.h中的函数。