《C primer plus》速通(字符串篇)
【速过C primer plus】字符串
一、字符串数组与指针区.
别
字符串在程序运行时,独自存储在静态存储区中,无论是含有字符串数组还是指向字符串的指针的程序中,都必须单独为字符串预留一个静态内存来存储字符串字面量。
但程序一旦编译,字符串数组与指向字符串的指针就会有所不同:
1)字符串数组会被单独开辟出来一段空间,然后将字符串字面量复制进入字符串数组,此时系统中有两个字符串字面量的空间,一个是原本字面量被存储的静态存储区,另一个则是字符串数组内部,他们的存储地址是不同的。
2)指向字符串的指针不会被单独开辟空间,而是直接将指针地址指向字符串字面量的地址,此时系统中只有一个字符串字面量空间。
综上所述,对于单独存储字符串字面量时,指向其的指针应该是属于const
类型,不可修改,如果修改则会造成程序原本存储的字符串字面量会同时修改。而字符串数组是复制副本,则可以用于修改,它的修改不会影响程序原本存储的字符串字面量。
二、字符串数组
指针的地址并不是禁止直接修改的,你可以使用正常的赋值方式来直接修改指针的地址,如果是整型数则会被尝试解释为内存地址,如果是字符串则会被分配一个随机的地址。
1 | int* i = 1; |
但是在处理指针为字符串时,定义发生了一些变化:
1 | const char* i = "123"; |
在这个例子中,i
的地址不会被直接赋值123这个具体的数值,内存分配过程中,是编译器先给字面量123
这个字符串分配一个具体的空间,然后把i
的指针指向这个存储空间。在快速定义字符串时,可以使用这种指针定义法,不过需要注意以下两点:
1)在规则中,允许的定义方式需要在前面加入const
,使指针常量化,这会导致无法单个修改指针中的单个字符(即变为了java意义上的String
变量,或者说const char[]
),如果不加入const
,则赋值结果是未知的,因为规则没有定义这种行为。
2)%s
针对的是char*
变量,无论如何输出都必须对应字符串指针。
通过以上这种指针定义方式,可以得到两种定义字符串数组的方法:
1 | char a[3][3] = { "12","23","34" };//二维字符串数组 |
在定义中,指针构造的字符串数组效率会更高,并且可以不提前确定字面量的个数,但它不能修改字面量。
三、字符串输入与输出
1)注意事项
为什么不能使用如下这种写法定义动态字符串?
1 | char* a; |
在VS中,这种写法甚至不能通过编译(使用了未初始化的变量a)。原因是指针a从一开始就没有被分配地址,在scanf
进行输入后,指针a
可能会指向任何地方(比如指向已经被使用的内存,导致程序数值被修改)。使用动态数组用动态内存分配解决,否则就使用固定长度的字符串数组。
2)gets()与puts()
1 | char a[5]; |
可以使用gets()
函数直接读取一整行(不会像scanf()
读取%s
时只读取一个单词,gets()
会将空格一并读取),再配合puts()
进行输出。但是gets()
有缺陷,它无法确定字符串a
界限在何处,所以如果越界输入,则会导致程序异常。
3)fgets()与fputs()
为了解决这个问题,C语言使用fgets()
与fputs()
代替了gets()
与puts()
。
1 | char a[5]; |
fgets()
需要三个参数:字符串载体、控制输入的宽度N(实际会输入N-1个,最后一个默认为\0
)、输入方式(可以固定为stdin
)。
fputs()
除了原本的字符串载体之外,还有一个输出方式,一般固定为stdout
。
4)scanf与其特殊用法
在之前的文章中说明了,scanf可以通过%s为占位符读取除了空格符、换行符以外的字符:
1 | char a[6]; |
但是如果想读取空格或者其他特殊字符,应该如何使用scanf?
可以使用特殊字符集占位符强制读取空格:
1 | char a[6]; |
scanf有一种特殊的占位符%[]
字符集,它使用的是一种类似正则表达式的语法,按照需求填入字符则scanf只会读取需求中的字符,如果遇到非字符集中的字符则会跳出。常见有以下几个规则:
1 | [^\n]表示非换行符的所有字符,^表示非 |
四、尽量不要使用string.h
在笔者测试时,string.h中大部分函数都失效\不安全了。这些函数在早期定义时就有很多问题,不同编译器中给出了不同的解决方案,建议在需要进行字符串操作时,自定义函数进行处理,不要依赖string.h中的函数。