文章目录
在进入正题之前,先上一波 思维导图,帮助你了解下面的知识体系。
字符串表示
概念
字符串:字符串是以空字符(’\0’)结尾的char数组。
创建
- 数组:
char name[] = "Elizabeth";
- 指针:
char *str = "Hello!";
看到这里,聪明的你也许会发现,既然有两种创建方式,那么它们之间有什么不同之处呢?
数组和指针创建的区别
-
相同点
-
使用数组符号
for (int i = 0; i < 10; i++) printf("%c", name[i]); printf(""); for (int i = 0; i < 6; i++) printf("%c", str[i]);
-
使用指针加法
for (int i = 0; i < 10; i++) printf("%c", *(name+i)); printf(""); for (int i = 0; i < 6; i++) printf("%c", *(str+i));
-
-
不同点
只有指针可以使用自增运算符,WTF?难道数组名不是一个指针吗?
不急,先耐心的看一下,指针的自增运算符的使用:
while (*(str))
putchar(*str++);// 编译,运行正常
再来看一下,数组名能不能使用自增运算符?
while (*name)
putchar(*name++);// 编译报错,
结果:
从错误结果来看,难道说,数组名是一个常量?
在《C和指针》这本书中“第八章 数组”中,作者从底层论述了:数组名是一个指针常量。
存储方式
字符串常量属于静态存储 static storage类。
静态存储:指如果在一个函数中使用字符串常量,即多次调用了这个函数,该字符串在程序的整个运行过程中只存储一份。
字符串的输入和输出
输入
scanf:
emmm…带有格式化输入的scanf函数更倾向于读入一个单词而不是字符串。
scanf函数主要用于以某种标准形式输入的混合类型数据的读取和转换。(感觉这个跟占位符有很大的关系)
例如:
char name[20];
scanf("%s", name);
printf("%s\n", name);
运行输入结果如下:
WTF?为什么只输出了一项?难道说后面的被计算机吃了?还是被抛弃了?
这也是scanf函数读取字符串时存在的一个小问题。
解释一下:如果输入了空格会认为字符串结束,空格后的字符将作为下一个输入项处理。
注:当使用vs的时候,请在第一行添加 #define _CRT_SECURE_NO_WARNINGS// 强行除去安全检查
gets:
函数声明:
char *gets(char *str)
先来说一下这个函数的特点,然后在分析一下其中的奥秘。
特点:它从系统的标准输入设备(通常是键盘)获得一个字符串,当遇到换行符时,表示这个字符串接收完毕。在接受完的字符串的后面自动添加一个空字符(’\0’),不接收换行符。
致命缺陷:它不检查预留存储区是否能够容纳实际输入的数据。多出来的字符有可能会溢出到相邻的内存空间,造成缓冲区溢出(buffer overflow)。
例如:
char name[10];
gets(name);
输入:
(巧了,我这边出现了不幸。):警告,变量name周围的栈堆已损坏。
嗯哼,这是为啥呢?(简单来说,发明者没有添加这种机制。)
从上面的函数声明中可得知:返回值是一个char*
(返回一个指向接收字符串的指针)。
嗯哼?如果gets函数遇到了错误或者是到了文件的结尾,它就返回一个空地址(空指针 NULL)。这不就使我们可以方便的添加一些错误检查(适用于读取文件)。
whlie(get(file) != NULL)
综上所述:当你用gets函数的时候,一定,一定,一定要注意,你的buffer空间的大小。不然,可能你就要遭遇不幸了。
fgets:
gets函数的升级版,改进了gets函数溢出问题。
函数声明:
char *fgets(char *str, int n, FILE *stream)
注:这里的返回值是跟gets函数的无差别。
参数解释:
char *str
:这是指向一个字符数组的指针,该数组存储了要读取的字符串
int n
:这是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度。FILE *stream
:这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。从键盘录入数据时,可以使用stdin作为该参数。
例如:
char name[10];// 这里是不规范的写法,规范的数组长度应该是使用宏定义
printf("What's your name?\n");
fgets(name, 10, stdin);
printf("%s,Hello.", name);
输入:
从上面的两种不同输入来看:
- 当输入的长度超出数组的存储空间的时候,超出部分就扔掉了。
- fgets函数读取换行符
输出
printf:
emmm… 这个函数啊,万能,当然在输出字符串的时候还是得用下面的函数。
puts:
函数声明:
int puts(const char *string)
返回值:如果成功,该函数返回一个非负值,如果发生错误则返回 EOF。
参数解释:
const char *string
:这是要被写入的 C 字符串。
特点:在要显示的字符串后面,自动添加一个换行符。
例如:
char *str = "Hello";
int res = puts(str);
print("res = %d",res);
输出:
致命缺点:puts函数遇到空字符时它就会停下来。
例如:
char name[] = {'J','o','e'};// 创建一个没有空字符的字符串
puts(name);
输出:
WTFFFFFF?这是?FFFFFF,因为没有遇到空字符,puts函数不知道在哪里停止,它会往输出的字符后面找(在内存中),直到找到空字符位置。(是不是发现,这哥们一点都不可靠。)
fputs:
puts函数的面向文件版本,进化版。
函数声明:
int fputs(const char *str, FILE *stream)
返回值:该函数返回一个非负值,如果发生错误则返回 EOF(-1)。
参数解释:
- str:这是一个数组,包含了要写入的以空字符终止的字符序列。
- fp:指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符串的流。
特点:不会自动添加换行符。
例如:
FILE *fp;
fp = fopen("file.txt", "w+");
fputs("文件操作函数。", fp);
fclose(fp);
输出:它会在当然文件目录下,创建一个名为:file,后缀是:.txt的文件,里面内容为:文件操作函数。
怎么将文件中的内容输出到控制台中,在这里就不再演示了,等到文件操作那一章节好好深入研究一波。
sprintf:
printf函数的升级版。
函数声明:
int sprintf(char *str, const char *format, ...)
参数解释:
char *str
:这是指向一个字符数组的指针,该数组存储了 C 字符串。const char *format
:这是字符串,包含了要被写入到字符串 str 的文本。它可以包含嵌入的 format 标签,format 标签可被随后的附加参数中指定的值替换,并按需求进行格式化。
特点:将后面的内容存储到前面指定的字符串中。
例如:
char str[80];
sprintf(str, "Pi 的值 = %f", 3.1415926);
puts(str);
输出:
总结
- gets:读取字符串的时候,自动除去换行符。读取会发生溢出
- puts:读取字符串的时候,自动添加换行符。如果字符串中没有空字符,输出会发生隐患
- fgets:存储换行符
- fputs:不自动添加换行符
字符串常用函数
strlen
strlen:计算字符串的长度,直到空结束字符,但不包括空结束字符。
函数声明:
size_t strlen(const char *str)
参数解释:
const char *str
:要计算长度的字符串。
返回值:该函数返回字符串的长度。
例如:
char str[] = "Hello!"; // 末尾包含一个空字符,加上空字符,len = 7
int len = strlen(str);
printf("len = %d\n", len);
输出:
strcmp、strncmp
strcmp:
strcmp:把 str1 所指向的字符串和 str2 所指向的字符串进行比较。
函数声明:
int strcmp(const char *str1, const char *str2)
参数解释:
const char *str1
:要进行比较的第一个字符串。const char *str2
:要进行比较的第二个字符串。
返回值:
- 如果返回值 < 0,则表示 str1 小于 str2。
- 如果返回值 > 0,则表示 str2 小于 str1。
- 如果返回值 = 0,则表示 str1 等于 str2。
比较规则:比较两个字符串的ASCII值。
例如:
char str1[] = "Hello!"; // o : 111
char str2[] = "Helle!"; // e : 101
int res = strcmp(str1, str2);
printf("res = %d\n", res);
输出:
strncmp:
strncmp:把 str1 和 str2 进行比较,最多比较前 n 个字节。
函数声明:
int strncmp(const char *str1, const char *str2, size_t n)
参数解释:
const char *str1
: 要进行比较的第一个字符串。const char *str2
:要进行比较的第二个字符串。size_t n
:要比较的最大字符数。
返回值:同上。
注:在这里就不做这个函数的演示了,简单来说,这个函数主要作用就是比较指定的字节数目。
strcpy、strncpy
strcpy:
strcpy:把 src 所指向的字符串复制到 dest。
函数声明:
char *strcpy(char *dest, const char *src)
参数解释:
char *dest
:指向用于存储复制内容的目标数组。const char *src
:要复制的字符串。
返回值:该函数返回一个指向最终的目标字符串 dest 的指针。
例如:
char dest[20];
char src[20] = "Hello!";
strcpy(dest, src);
printf("Dest = %s\nSrc = %s\n", dest,src);
输出:
注意!注意!注意!倘若,目标数组的空间 不够装下 源字符串时,会发生缓冲溢出。切记!切记!切记!
strncpy:
strncpy:把 src 所指向的字符串复制到 dest,最多复制 n 个字符。当 src 的长度小于 n 时,dest 的剩余部分将用空字节填充。
函数声明:
char *strncpy(char *dest, const char *src, size_t n)
返回值:同上。
注:在这里就不做这个函数的演示了,我相信你懂得。
strcat、strncat
strcat:
strcat:把 src 所指向的字符串追加到 dest 所指向的字符串的结尾。
函数声明:
char *strcat(char *dest, const char *src)
参数解释:
char *dest
:指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串。const char *src
:指向要追加的字符串,该字符串不会覆盖目标字符串。
返回值:该函数返回一个指向最终的目标字符串 dest 的指针。
例如:
char dest[20] = "Hello!";
char src[20] = "你好!";
strcat(dest, src);
printf("Dest = %s\nSrc = %s\n", dest,src);
输出:
从上面文字中,你可能会发现一个WWTFFFF的问题。那就是如果Dest的空间不够大,可能会的发生跟gets、strcpy函数等同样的问题。所以说:三思而后行。
strncat:
strncat:把 src 所指向的字符串追加到 dest 所指向的字符串的结尾,直到 n 字符长度为止。
函数声明:
char *strncat(char *dest, const char *src, size_t n)
参数解释:
char *dest
:指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串,包括额外的空字符。const char *src
:要追加的字符串。size_t n
:要追加的最大字符数。
返回值:同上。
注:在这里就不做这个函数的演示了,简单来说,这个函数主要作用就是追加指定大小的字节数目。
strstr
strstr:在字符串 haystack 中查找第一次出现字符串 needle 的位置,不包含终止符 ‘\0’。
函数声明:
char *strstr(const char *haystack, const char *needle)
参数解释:
const char *haystack
:要被检索的 C 字符串。const char *needle
:在 haystack 字符串内要搜索的字符串。
返回值:该函数返回在 haystack 中第一次出现 needle 字符串的位置,如果未找到则返回 null。
例如:
const char haystack[40] = "I am a super man!";
const char needle[10] = "super";
char *res = strstr(haystack, needle); // 在haystack中,没有,则返回null
printf("输出结果是: %s\n", res);
输出:
最后说一下,嘿嘿,不喜勿喷,小生菜鸟一只,如有不足,还需各位兄长们多多指教。