C语言学习笔记——字符串操作
char heart[] = "I am the heart";
char *head = "I am the head";
若想让heart和head统一,可以执行如下操作:
head = heart; // 指针head指向了数组heart
但以下操作是非法的:
heart = head; // heart为地址常量,不能作为左值
part2 以下未使用const限定的指针初始化:
char *word = "frame";
是否可以使用该指针来修改该字符串?
word[1] = 'l'; // 是否允许?
《C Plus》中是这样描述的:编译器可能允许这样做,但是对于当前的C标准而言,这样的行为是未定义的。列如,这样的语句可能导致内存访问错误。原因:编译器可以使用内存中的一个副本来表示所有完全相同的字符串字面量。参见如下示例(错误示例):
/** BUGGY: Segmentation fault (core dumped)* str_constant_pt.c -- 使用指针修改字符串常量 */
#include int main(void) {char *p1 = "Klingon";p1[0] = 'F';printf("Klingon");printf(": Beware the %ss!\n", "Klingon");return 0;
}
原书中显示的结果是:
Flingon: Beware the Flingons!
而我自己的自行结果如下:
[root@gavinpan p2]# !g
gcc -o str_constant_pt str_constant_pt.c
[root@gavinpan p2]# !.
./str_constant_pt
Segmentation fault (core dumped)
fault (core )多为内存不当操作造成。空指针、野指针的读写操作,数组越界访问,破坏常量等1。
基于上述问题,建议在把指针初始化为字符串常量时使用const限定符:
const char *pt = "Klingon";
在把非const数组初始化为字符串常量时,不会导致类似问题,因为数组获得的是原始字符串的副本。
因此,如果打算修改字符串,就不要用指针指向字符串常量。
字符串数组 指向字符串的指针数组
const char *pointer_arr[3] = {"alpha", "beta", "theta"};
是一个内含3个指针的数组,在当前操作系统 8中,占用24个字节:
printf("size of pointer_arr: %d\n", sizeof(pointer_arr));
输出:
size of pointer_arr: 24
char类型数组的数组
char arrays[3][10] = {"alpha", "beta", "theta"};
是内含3个数组的数组,每个数组内含10个char类型的值,共占用30个字节:
printf("size fo arrays: %d\n", sizeof(arrays));
输出:
size fo arrays: 30
中的指针指向初始化时所用字符串常量的位置;而中的数组则存储了字符串常量的副本,故每个字符串都被存储了两次。
基于上述两种声明方式,可以把想象为不规则数组,而则为矩形二维数组:
综上,如果要用数组表示一些列带显示的字符串,请使用指针数组;如果要改变字符串或为字符串输入预留空间,不要使用指向字符串字面量的指针。
指针和字符串
一个例子:
/* p_and_s,c --指针和字符串 */
#include int main(void) {const char *msg = "Don't be a fool!";const char *copy;copy = msg;printf("%s\n", copy);printf("msg = %s; &msg = %p; value = %p\n", msg, &msg, msg);printf("copy = %s; © = %p; copy = %p\n", copy, ©, copy);return 0;
}
输出结果如下:
[root@gavinpan p2]# ./p_and_s
Don't be a fool!
msg = Don't be a fool!; &msg = 0x7ffcb7cb1348; value = 0x4006d8
copy = Don't be a fool!; © = 0x7ffcb7cb1340; copy = 0x4006d8
其中,&msg = 和© = 说明指针msg和copy分别存储在地址为和的内存中;最后一项value = ,说明两个指针指向了同一个位置,因此,程序并未拷贝字符串。
字符串输入 不安全的gets()函数
gets()函数读取整行输入,直到遇到换行符,然后丢弃其余字符,存储其余字符;并在这些字符的末尾添加一个空字符\0。
使用示例:
char words[10];
gets(words);
puts(words);
gets()函数唯一的参数是words,它无法坚持该数组是否装得下输入行。因此,gets()函数只知道数组的开始位置,但不知都数组中有多少个元素。
一旦输入的字符过长,就会导致缓冲区溢出( ),即多余的字符超出了指定的目标空间。如果这些多余的字符只是占用了尚未使用的内存,就不会立即出现问题;但如果它们擦写掉了程序中的其他数据,就会导致程序异常终止;或者其他情况。
gets()的替代品 fgets()函数
该函数专门设计用于处理文件输入。`使用示例如下:
char words[10];
fgets(words, 10, stdin);
fputs(words, stdout);
fgets()与gets()的区别如下:
1 第2个参数指明了读入字符的最大数量。如果该参数为n,那么fgets()将读入n-1个字符,或者遇到第一个换行符为止。
2 如果fgets()读到一个换行符,会把它存储咋字符串中。而gets()则会丢弃换行符。
3 第3个参数,指明要读入的文件。若要从键盘读取输入,则以stdin(标准输入)作为参数。stdin定义在stdio.h中。
fgets()通常与fputs()配对使用。fputs()的第二个参数,指明它要写入的文件,如果要输出到屏幕,则使用(标准输出)作为参数。
fgets()的返回值是指向char的指针。如果一切顺利,返回的地址与第1个参数相同;如果读到文件结尾,则会返回空指针(null )。代码示例如下:
/* fgets2.c -- 使用fgets()和fputs() */
#include #define STLEN 10int main(void) {char words[STLEN];puts("Enter strings (empty line to quit):");while (fgets(words, STLEN, stdin) != NULL && words[0] != '\n') {fputs(words, stdout);}puts("Done.");return 0;
}
输出:
Enter strings (empty line to quit):
hello
hello
c primer plus
c primer plusDone.
系统使用缓冲的I/O。意味着在按下键之前,输入都被存储在缓冲区。按下键会在输入中增加一个换行符\n,并把整行输入发送给fgets()。对于输出,fputs()把字符发送给另一个缓冲区,当发送换行符时,缓冲区的内容被发送至屏幕上。
fgets()会存储输入中的换行符\n,那么如何处理换行符呢?参见如下示例:
/* * fgets3.c* 处理通过fgets()获取的字符串中的换行符,* 若无换行符,则丢弃多余字符。**/
#include #define STLEN 10int main(void) {char words[STLEN];puts("Enter strings (empty line to quit):");while (fgets(words, STLEN, stdin) != NULL && words[0] != '\n') {int i = 0;while (words[i] != '\n' && words[i] != '\0') {i++;}if (words[i] == '\n') { // 处理换行符words[i] = '\0';} else {while (getchar() != '\n') { // 丢弃输入中的多余字符continue;}}puts(words);}puts("Done.");return 0;
}
输出:
[root@gavinpan p2]# !.
./fgets3
Enter strings (empty line to quit):
hello
hello
c primer plus
c primer Done.
相比于.c,.c中丢弃了多余的输入,相关代码为:
while (getchar() != '\n') { continue;
}
自定义的()函数
示例代码:
/*** char *s_gets(char *, int);** 获取字符串输入*/
char *s_gets(char *st, int len) {char *ret; // 接收fgets()函数的返回值int i = 0;ret = fgets(st, len, stdin);if (ret) { // ret != NULLwhile (st[i] != '\n' && st[i] != '\0') {i++;}if (st[i] == '\n') { // 处理换行符\nst[i] = '\0';} else {while (getchar() != '\n') { // 丢弃多余字符continue;}}}return ret;
}
那么为什么要丢弃输入行中的多余字符呢?因为,输入行中的多余字符会被留在缓冲区,成为下一次读取语句的输入。
scanf()函数
相比于gets()和fgets(),scanf()更像是“获取单词”的函数。scanf()函数有两种方法确定输入结束:
其一,如果使用%s转换说明,则以下一个空白字符(空行、空格、制表符或换行符)作为字符串的结束(不包括空白字符)。
其二,如果指定了字段宽度,如s,那么scanf()将读取10个字符或读到第1个空字符停止。
见如下示例:
/*** scanf1.c* scanf()函数输入结束示例*/
#include #define STLEN 10void clear_buf(void);int main(void) {char name[STLEN];puts("Enter some lines");scanf("%s", name);printf("result of \"%%s\": %s\n", name);clear_buf(); // 清除缓冲区多余字符scanf("%5s", name);printf("result of \"%%5s\": %s\n", name);clear_buf(); scanf("%5s", name);printf("result of \"%%5s\": %s\n", name);clear_buf(); return 0;
}void clear_buf(void) {while (getchar() != '\n') {continue;}
}
运行结果示例:
[root@gavinpan p2]# ./scanf1
Enter some lines
Heroes come and go
result of "%s": Heroes // 空格结束
Heroes come and go
result of "%5s": Heroe // 5个字符长度结束
Kobe Bryant
result of "%5s": Kobe // 空格结束
在看scanf()的使用示例:
/*** scanf()函数的使用示例*/
#include #define STRLEN 12int main(void) {char name1[STRLEN], name2[STRLEN];int count;printf("Please enter 2 names:\n");count = scanf("%5s s", name1, name2);printf("I read the %d names %s and %s\n", count, name1, name2);return 0;
}
以下为3个输出示例:
[root@gavinpan p2]# !.
./scanf_str1
Please enter 2 names:
Terry Lampard
I read the 2 names Terry and Lampard[root@gavinpan p2]# !.
./scanf_str1
Please enter 2 names:
Cech Drogba
I read the 2 names Cech and Drogba[root@gavinpan p2]# !.
./scanf_str1
Please enter 2 names:
Lampard Kante
I read the 2 names Lampa and rd
如果输入行的内容过长,scanf()也会导致数据溢出。不过,在%s转换说明中使用字段宽度,可防止溢出。
字符串输出 puts()函数
puts()函数的入参为字符串的地址,在显示字符串时,会自动在末尾添加一个换行符。其使用示例如下:
/*** put_out1.c* puts()函数的使用*/
#include #define STRLEN 50
#define DEF "I am a #defined string."int main(void) {char str1[STRLEN] = "An array was initialized to me.";const char *pt1 = "A pointer was initilized to me.";puts("I am an argument to \"puts()\".");puts(DEF);puts(str1);puts(pt1);puts(&str1[3]); // 从第4个字符开始puts(pt1 + 3); // 从第4个字符开始return 0;
}
输出如下:
[root@gavinpan p2]# ./put_out1
I am an argument to "puts()".
I am a #defined string.
An array was initialized to me.
A pointer was initilized to me.
array was initialized to me.
ointer was initilized to me.
puts()函数在遇到空字符\0时就会停止输出,因此必须确保有空字符。一下为一个错误示例:
/*** nono_puts.c* puts()的错误示例** BUGGY*/
#include int main(void) {char side_a[] = "Side A";char dont[] = {'O', 'h', ',', 'n', 'o', '!'};char side_b[] = "SIde B";puts(dont); /* WARNING: dont并不是一个字符串! */return 0;
}
输出:
[root@gavinpan p2]# !.
./nono_puts
Oh,no!Side A
fputs()函数
fputs()显示字符串时,不会在末尾添加换行符。fputs()常与fgets()配对使用。
使用示例:
/*** fputs()的使用*/
#include int main(void) {fputs("A \"fputs()\" string\n", stdout);return 0;
}
输出:
[root@gavinpan p2]# !.
./fputs1
A "fputs()" string
()函数
示例:
/* printf1.c --printf()函数使用 */
#include int main(void) {printf("%d strings \"%s\" and \"%s\"\n", 2, "red", "blue");return 0;
}
输出:
[root@gavinpan p2]# !.
./printf1
2 strings "red" and "blue"
自定义输入/输出函数
可以基于()和()自定义所需的函数。
自定义输入的简单示例:
/* 自定义输出 */
#include #define STLEN 10void get1(char *st, int len);int main(void) {char words[STLEN];printf("Enter your string:\n");get1(words, STLEN);printf("Your string is: %s\n", words);return 0;
}void get1(char *st, int len) {int i;char c;for (i = 0; (c = getchar()) != '\n' && i < len - 1; i++) {st[i] = c;}st[i] = '\0'; // 最后一位存储空字符if (c != '\n') {while (getchar() != '\n') { // 丢弃输入中的多余字符continue;}}
输出结果:
[root@gavinpan p2]# !.
./get1
Enter your string:
Welcome
Your string is: Welcome
[root@gavinpan p2]# !.
./get1
Enter your string:
Your string is good.
Your string is: Your stri
自定义输出简单示例:
/* put1.c -- 打印字符串, 不添加\n */
#include void put1(const char *str1);int main(void) {char *str1 = "hello, world!";put1(str1);return 0;
}void put1(const char *str1) {while (*str1) {putchar(*str1++);}
}
输出结果:
[root@gavinpan p2]# !.
./put1
hello, world![root@gavinpan p2]# // 不添加换行符
Mark Done.
↩︎