C 字符串
我们了解过,以\0结尾的字符数组就是字符串;字符串太常用了,我们不得不细细地聊一聊字符串。本文内容大致包含:指针与字符串、字符指针与字符数组、字符串相关的IO函数、字符串的常用函数等。
细说字符串
字符串的指针形式和数组形式
const char* str="hello";
char arr[]="hello";
上面两种形式有什么不同?
- 字符串字面量赋值给字符指针发生了什么
- 在静态存储区开辟了一段6个字节的空间用于存储字符串hello;
- 将静态存储区的这片空间的首地址交给了 字符指针 str;
- 这期间没有发生字符串拷贝,仅仅是地址拷贝
- 字符串字面量相当于常量;将字符指针指向常量,需要将指针声明为 常量指针。
- 字符串字面量赋值给字符数组发生了什么
- 在静态存储区开辟了一段6个字节的空间用于存储字符串hello;
- 编译器根据字符串推断出需要在栈区开辟6个字节空间用于分配给数组;
- 将静态存储区的字符串逐个字符拷贝至数组内存地址处;
- 此后内存中便有两份字符串hello;一份在栈区、一份在静态存储区;
- 此后编译器会把数组名识别为数组第一个元素的地址的别名;故数组名是一个地址常量;
- 即 以后 不可以改变数组名存储的值;如果改了,就相当于改了数组的存储位置;故,数组名不可以出现在等号左边;自增自减操作都是不合法的;
- 但 数组名+1 这种操作是允许的
下面通过一个示例,可以进一步了解数组形式和字符指针形式的字符串的区别:
#include <stdio.h>
#include <string.h>
#define HELLO "hello"
int main(void) {
char arr[] = HELLO;
char *str = HELLO;
printf("arr address is :%p\n",arr);
printf("str address is :%p\n",str);
printf("\"hello\" address is :%p\n",&"hello");
printf("HELLO address is :%p\n",&HELLO);
return 0;
}
输出:
arr address is :0x16d80f534
str address is :0x1025f3f5c
"hello" address is :0x1025f3f5c
HELLO address is :0x1025f3f5c
小结:可以看到编译器貌似对字符串字面量有优化,会将多次出现的内容相同的字符串字面量在内存只存储一份;。
何时用指针形式何时用数组形式
- 在结构体中,若能够确定字符串的最大长度,此时用字符数组方便,因为数组成员随结构体,赋值时都是值拷贝、销毁时不必考虑内存泄漏
- 在结构体中,若不能确定字符串的长度,此时用字符指针较好,因为可以节省内存
- 想要改变字符串的内容,不要使用指向字符串字面量的字符指针,而是要使用字符数组。比如下面的代码会异常:原因是试图通过指针改变静态存储区的只读字符串。
#include <string.h>
int main(void) {
char * str1="hhh";
char * str2="yyy";
strcpy(str1, str2);
return 0;
}
字符串IO相关的函数
往程序内写入内容,需要自己申请空间,编译器不会帮你申请;常用的手段是,定义一个缓冲区,如:char buffer[512]。
从键盘输入中,读取输入的内容,scanf函数我们十分熟悉;这个函数只能接收一个单词,有一个函数比他更强大:
接收键盘输入 gets
gets接受一个参数,char;读取整行输入,遇到换行符终止,并去掉换行符,最后在末尾加上字符串结束符\0;
与其搭配的还有一个函数,puts:它接受一个char;该函数专用于打印字符串,并且自带换行符。类似:printf("%s\n",str);
对于gets有一个问题:无法判断用于接受一整行字符串的缓冲区是否空间够用。如果不够用呢?操作到非法内存区域,程序会崩溃。
现在我用的编译器,当我写出下面代码后,直接就警告⚠️了:
#include <stdio.h>
#include <string.h>
int main(void) {
char buffer[6];
char *input = gets(buffer);
puts(input);
return 0;
}
warning: 'gets' is deprecated: This function is provided for compatibility reasons only. Due to security concerns inherent in the design of gets(3), it is highly recommended that you use fgets(3) instead. [-Wdeprecated-declarations]
char *input = gets(buffer);
gets已经被弃用了,推荐我们用 fgets。
#include <stdio.h>
#include <string.h>
int main(void) {
char buffer[6];
char *input = fgets(buffer, sizeof(buffer),stdin);
puts(input);
return 0;
}
fgets和fputs,他俩都是为文件的读写设计的;所以可以看到,他们两个都需要传入一个文件指针。输入流、输出流、标准文件流等都是文件指针,只不过前两个是系统帮我们打开的;最后一个是我们自己手动打开的。
可以看到fgets更安全,因为它第二个参数可以指定,读取的长度。
fputs和puts的区别是,fputs不会追加换行符。
字符串常用函数
拷贝字符串 strcpy
将第二个参数的字符串拷贝到第一个参数指定位置中,需要注意的是:
- 第一个参数指定的位置必须是可以修改的,若指向的是只读取程序会崩溃
- 第一个参数指定的位置的空间必须能够容下第二个参数指定的字符串,否则可能操作到非法内存区域,程序崩溃
- 第二个参数指定的字符串必须是一个带有结束符的标准字符串,否则拷贝完成后,第一个参数可能就不是字符串了
- 对于可能存在的溢出问题,标准库还提供了一个 strncpy的函数,其中有一个参数可以指定拷贝字符的个数。
求字符串长度 strlen
这个太常见了,只提一点:这个函数,不会把\0计算在内的。
字符串拼接 strcat
将第二个参数的字符串追加到第一个参数指定的字符串的末尾,需要注意的是:
- 目标字符串空间要足够
- 第二个参数字符串要是个标准的字符串,别是个普通的字符数组
- 对于可能存在的溢出问题,标准库提供了一个strncat函数,可以指定追加的字符的个数。
以上便是对c程序中字符串的一些知识点的总结。

浙公网安备 33010602011771号