目录
本文参考正点原子教程,仅作为个人笔记使用。
字符串处理在几乎所有的编程语言中都是一个绕不开的话题,在一些高级语言当中,对字符串的处理支持度更是完善,譬如 C++、C#、Python 等。若在 C 语言中想要对字符串进行相关的处理,譬如将两个字符串进行拼接、字符串查找、两个字符串进行比较等操作,几乎是需要程序员自己编写字符串处理相关逻辑代码来实现字符串处理功能。
好在 C
语言库函数中已经给我们提供了丰富的字符串处理相关函数,基本常见的字符串处理需求都可以直接使用这些库函数来实现,而不需要自己编写代码,使用这些库函数可以大大减轻编程负担。这些库函数大致可以分为字符串的输入、输出、合并、修改、比较、转换、复制、搜索等几类,本章将向大家介绍这些库函数的使用方法。
6.1 字符串输入/输出
在程序当中,经常需要在程序运行过程中打印出一些信息,将其输出显示到标准输出设备 stdout
(譬如屏幕)或标准错误设备 stderr
(譬如屏幕),譬如调试信息、报错信息、中间产生的变量的值等等,以实现对程序运行状态的掌控和分析。除了向 stdout
或
stderr
输出打印信息之外,有时程序在运行过程中还需要从标准输入设备 stdin
(譬如键盘)中读取字符串,将读取到的字符串进行解析,以指导程序的下一步动作、控制程序执行流程。
6.1.1 字符串输出
常用的字符串输出函数有 putchar()
、
puts()
、
fputc()
、
fputs()
,前面我们经常使用
printf()
函数来输出字符串信息,而并没有使用到 putchar()
、
puts()
、
fputc()
、
fputs()
这些函数,原因在于
printf()
可以按照自己规定的格式输出字符串信息,一般称为格式化输出;而 putchar()
、
puts()
、
fputc()
、
fputs()
这些函数只能输出字符串,不能进行格式转换。所以由此可知,printf()
在功能上要比
putchar()
、
puts()
、
fputc()
、
fputs()
这些函数更加强大,往往在实际编程中,printf()
用的也会更多,但是
putchar()、puts()、fputc()、fputs()这些库函数相比与 printf,在使用上方便、简单。
与 printf()
一样,
putchar()
、
puts()
、
fputc()
、
fputs()
这些函数也是标准
I/O
函数,属于标准
C
库函数,所以需要包含头文件<stdio.h>
,并且它们也使用
stdio
缓冲。
puts 函数
puts()函数用来向标准输出设备(屏幕、显示器)输出字符串并自行换行。把字符串输出到标准输出设备,将' \0 '
转换为换行符
' \n '
。
puts
函数原型如下所示(可通过
"man 3 puts"
命令查看):
#include <stdio.h>
int puts(const char *s);
使用该函数需要包含头文件<stdio.h>
。
函数参数和返回值含义如下:
s:
需要进行输出的字符串。
返回值:成功返回一个非负数;失败将返回
EOF
,
EOF
其实就是
-1
。
使用 puts()
函数连换行符
' \n '
都省了,函数内部会自动在其后添加一个换行符。所以,如果只是单纯输出字符串到标准输出设备,而不包含数字格式化转换操作,那么使用 puts()
会更加方便、简洁;
puts()
虽然方便、简单,但也仅限于输出字符串,功能还是没有 printf()
强大。
puts
函数测试
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char str[50] = "Linux app puts test";
puts("Hello World!");
puts(str);
exit(0);
}
putchar 函数
putchar()函数可以把参数
c
指定的字符(一个无符号字符)输出到标准输出设备,其输出可以是一个字符,可以是介于 0~127
之间的一个十进制整型数(包含
0
和
127
,输出其对应的
ASCII
码字符),也可以是用 char
类型定义好的一个字符型变量。
putchar
函数原型如下所示(可通过
"man 3 putchar"
命令查看):
#include <stdio.h>
int putchar(int c);
使用该函数需要包含头文件<stdio.h>
。
函数参数和返回值含义如下:
c:
需要进行输出的字符。
返回值:出错将返回
EOF
。
putchar 函数测试
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
putchar('A');
putchar('B');
putchar('C');
putchar('D');
putchar('\n');
exit(0);
}
fputc
函数
fputc()与
putchar()
类似,也用于输出参数
c
指定的字符(一个无符号字符),与
putchar()
区别在于,putchar()只能输出到标准输出设备,而
fputc()
可把字符输出到指定的文件中,既可以是标准输出、标准错误设备,也可以是一个普通文件。
fputc 函数原型如下所示:
#include <stdio.h>
int fputc(int c, FILE *stream);
使用该函数需要包含头文件<stdio.h>
。
函数参数和返回值含义如下:
c:
需要进行输出的字符。
stream:
文件指针。
返回值:成功时返回输出的字符;出错将返回
EOF
。
fputc 测试
(1)使用
fputc
函数将字符输出到标准输出设备。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
fputc('A', stdout);
fputc('B', stdout);
fputc('C', stdout);
fputc('D', stdout);
fputc('\n', stdout);
exit(0);
}
(2)使用
fputc
函数将字符输出到一个普通文件。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp = NULL;
/* 创建一个文件 */
fp = fopen("./new_file", "a");
if (NULL == fp) {
perror("fopen error");
exit(-1);
}
/* 输入字符到文件 */
fputc('A', fp);
fputc('B', fp);
fputc('C', fp);
fputc('D', fp);
fputc('\n', fp);
/* 关闭文件 */
fclose(fp);
exit(0);
}
fputs 函数
同理,fputs()
与
puts()
类似,也用于输出一条字符串,与
puts()
区别在于,
puts()
只能输出到标准输出设备,而 fputs()
可把字符串输出到指定的文件中,既可以是标准输出、标准错误设备,也可以是一个普通文件。
函数原型如下所示:
#include <stdio.h>
int fputs(const char *s, FILE *stream);
函数参数和返回值含义如下:
s:
需要输出的字符串。
stream:
文件指针。
返回值:成功返回非负数;失败将返回
EOF
。
fputs 测试
(1)使用
fputs
输出字符串到标注输出设备。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
fputs("Hello World! 1\n", stdout);
fputs("Hello World! 2\n", stdout);
exit(0);
}
(2)使用
fputs
输出字符串到一个普通文件。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
FILE *fp = NULL;
/* 创建一个文件 */
fp = fopen("./new_file", "a");
if (NULL == fp) {
perror("fopen error");
exit(-1);
}
fputs("Hello World! 1\n", fp);
fputs("Hello World! 2\n", fp);
/* 关闭文件 */
fclose(fp);
exit(0);
}
6.1.2 字符串输入
常用的字符串输入函数有 gets()
、
getchar()
、
fgetc()
、
fgets()
。与
printf()
对应,在
C
库函数中同样也提供了格式化输入函数 scanf()
。
scanf()
与
gets()
、
getchar()
、
fgetc()
、
fgets()
这些函数相比,在功能上确实有它的优势,但是在使用上不如它们方便、简单、更易于使用。
与 scanf()
一样,
gets()
、
getchar()
、
fgetc()
、
fgets()
这些函数也是标准
I/O
函数,属于标准
C
库函数,所以需要包含头文件<stdio.h>
,并且它们也使用
stdio
缓冲。
gets 函数
gets()函数用于从标准输入设备(譬如键盘)中获取用户输入的字符串,
gets()
函数原型如下所示:
#include <stdio.h>
char *gets(char *s);
使用该函数需要包含头文件<stdio.h>
。
函数参数和返回值含义如下:
s:
指向字符数组的指针,用于存储字符串。
返回值:如果成功,该函数返回指向
s
的指针;如果发生错误或者到达末尾时还未读取任何字符,则返回 NULL
。
用户从键盘输入的字符串数据首先会存放在一个输入缓冲区中,gets()
函数会从输入缓冲区中读取字符串存储到字符指针变量 s
所指向的内存空间,当从输入缓冲区中读走字符后,相应的字符便不存在于缓冲区了。
输入的字符串中就算是有空格也可以直接输入,字符串输入完成之后按回车即可,gets()
函数不检查缓冲区溢出。
使用示例
使用 gets()
函数获取用户输入字符串。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char str[100] = {0};
char *ptr = NULL;
ptr = gets(str);
if (NULL == ptr)
exit(-1);
puts(str);
exit(0);
}
当在 Ubuntu
系统编译代码时,会出现如下警告信息:
![](https://i-blog.csdnimg.cn/blog_migrate/e1000782e42b9eec0dd7eac6557df4a9.png)
出现如上警告信息,其实是建议我们不要使用 gets()
函数,因为程序中使用
gets()
函数是非常不安全的,可能会出现 bug
、出现不可靠性,
gets()
在某些意外情况下会导致程序陷入不可控状态,所以一般建议大家不要使用这个函数,可以使用后面将给大家介绍的 fgets()
代替。
这里先不管这个警告信息,我们直接运行测试:
![](https://i-blog.csdnimg.cn/blog_migrate/4364c34b0d55e82e83c0654edc02a89d.png)
由此可知,不管我们输入的是空格、单引号、双引号都会作为 gets()
获取到的字符串的一部分,直到用户输入回车换行符结束。
gets()与
scanf()
的区别
gets()除了在功能上不及
scanf
之外,它们在一些细节上也存在着不同:
gets()
函数不仅比
scanf
简洁,而且,就算输入的字符串中有空格也可以,因为
gets()
函数允许输入
的字符串带有空格、制表符,输入的空格和制表符也是字符串的一部分,仅以回车换行符作为字符
串的分割符;而对于
scanf
以
%s
格式输入的时候,空格、换行符、
TAB
制表符等都是作为字符串
分割符存在,即分隔符前后是两个字符串,读取字符串时并不会将分隔符读取出来作为字符串的组
成部分,一个
%s
只能读取一个字符串,若要多去多个字符串,则需要使用多个
%s
、并且需要使用
多个字符数组存储。
|
gets()
会将回车换行符从输入缓冲区中取出来,然后将其丢弃,所以使用
gets()
读走缓冲区中的字符
串数据之后,缓冲区中将不会遗留下回车换行符;而对于
scanf()
来说,使用
scanf()
读走缓冲区中
的字符串数据时,并不会将分隔符(空格、
TAB
制表符、回车换行符等)读走将其丢弃,所以使
用
scanf()
读走缓冲区中的字符串数据之后,缓冲区中依然还存在用户输入的分隔符。
|
针对上面所提出的两个区别点,下面我们将进行一些列的代码测试。
(1)测试
1
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char s1[100] = {0};
char s2[100] = {0};
scanf("%s", s1);
printf("s1: %s\n", s1);
scanf("%s", s2);
printf("s2: %s\n", s2);
exit(0);
}
当输入 123_456
回车时,输出结果如下(_表示空格):
![](https://i-blog.csdnimg.cn/blog_migrate/4b0b53f120d1783c5fa31264215e3fcb.png)
代码中我们调用了两次 scanf()
,而事实上我们只输入了一次,输入“
123
”之后输入空格、再输入“456”,然后按回车,由打印结果可知,字符串 s1
等于“
123
”,字符串
s2
等于“456”;当输入完成按回车之后,输入缓冲区中此时存在如下字符:
'1' 、 '2' 、 '3' 、 ' 空格 ' 、 '4' 、 '5' 、 '6' 、 '\n'
第一个 scanf()
读取缓冲区时,将
'1'
、
'2'
、
'3'
读走,读走之后,它们将不存在于缓冲区中了,空格被视为字符串分割符,分割符及后面的字符将不会读取(以%s
格式输入情况下)。
第二个 scanf()
读取缓冲区时,
'4'
、
'5'
、
'6'
会被读走,分割符依然不读取。
再次执行测试程序:
![](https://i-blog.csdnimg.cn/blog_migrate/c5e6befdc9cc45011c725c18fdfe61a4.png)
当输入“123
”回车,之后输出了“
123
”;之后需要再次输入,接着输入“456”回车,输出“456”。
这里输入了两次字符串,原因在于第一次 scanf()
读走“
123
”之后,缓冲区中只剩下回车换行字符,第二次 scanf()不读取换行符,所以需要用户再次输入字符串。
(2)测试
2
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char s[100] = {0};
char c;
scanf("%s", s);
printf("s: %s\n", s);
scanf("%c", &c);
printf("c: %d\n", c);
exit(0);
}
同样这段代码也是调用了两次 scanf()
,但只是输入了一次字符串,当第一个
scanf()
读取之后,缓冲区中只剩下回车换行符;从打印信息可以发现,第二次 scanf()
读取时,把换行符也读取出来了(换行符
'\n'
对应的 ASCII
编码值等于
10
),因为这里
scanf
用的是
%c
格式,而不是
%s
,对于
%c
读入时,空格、换行符、TAB 这些都是正常字符。
(3)
测试
3
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char s1[100] = {0};
char s2[100] = {0};
scanf("%s", s1);
printf("s1: %s\n", s1);
gets(s2);
printf("s2: %s\n", s2);
exit(0);
}
这段代码先是调用了 scanf()
,之后调用了
gets()
,但只输入了一次字符串。
scanf()
读取之后,缓冲区中只剩下换行符,但是 gets()
会将换行符读取出来并将其丢弃,所以说字符串便是一个空字符串。
再次执行测试程序:
![](https://i-blog.csdnimg.cn/blog_migrate/e6bd3f72b1830074d81aefde0e1d0000.png)
字符串 s1 依然是“
123
”,
scanf
读取完之后,缓冲区此时剩下如下字符串:
' 空格 ' 、 ' 空格 ' 、 '4' 、 '5' 、 '6' 、 '\n'
gets()读取时将两个空格以及“456”、换行符全部读取出来,其中换行符会被丢弃、不作为字符串的组成字符,所以字符串 s2
前面就会存在两个空格。
getchar
函数
getchar()
函数用于从标准输入设备中读取一个字符(一个无符号字符),函数原型如下所示:
#include <stdio.h>
int getchar(void);
使用该函数需要包含头文件<stdio.h>
。
函数参数和返回值含义如下:
无需传参。
返回值:该函数以无符号
char
强制转换为
int
的形式返回读取的字符,如果到达文件末尾或发生读错误,则返回 EOF
。
同样 getchar()
函数也是从输入缓冲区读取字符数据,但只读取一个字符,包括空格、
TAB
制表符、换行回车符等。
测试
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int ch;
ch = getchar();
printf("ch: %c\n", ch);
exit(0);
}
![](https://i-blog.csdnimg.cn/blog_migrate/37bbebc90089ae67c4cff4b9284f9afc.png)
getchar()只从输入缓冲区中读取一个字符,与
scanf
以
%c
格式读取一样,空格、
TAB
制表符、回车符都将是正常的字符。即使输入了多个字符,但 getchar()
仅读取一个字符。
fgets 函数
fgets()与
gets()
一样用于获取输入的字符串,
fgets()
函数原型如下所示
#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
使用该函数需要包含头文件<stdio.h>
。
函数参数和返回值含义如下:
s:
指向字符数组的指针,用于存储字符串。
size:
这是要读取的最大字符数。
stream:
文件指针。
fgets()与
gets()
的区别主要是三点:
gets()
只能从标准输入设备中获取输入字符串,而
fgets()
既可以从标准输入设备获取字符串、也可
以从一个普通文件中获取输入字符串。
|
fgets()
可以设置获取字符串的最大字符数。
|
gets()
会将缓冲区中的换行符
'\n'
读取出来、将其丢弃、将
'\n'
替换为字符串结束符
'\0'
;
fgets()也会将
缓冲区中的换行符读取出来,但并不丢弃,而是作为字符串组成字符存在,读取完成之后自动在最
后添加字符串结束字符
'\0'
。
|
其它方面与 gets()
函数一样,包括前面给大家所介绍的与
scanf(%s)
在一些细节方面的区别。
测试
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char str[100] = {0};
printf("请输入字符串: ");
fgets(str, sizeof(str), stdin);
printf("%s", str);
exit(0);
}
此段代码中,使用 printf
打印字符串
str
时并没有在
%s
后面添加
'\n'
,但是结果显示,打印出来的字符串已经换行,也就意味着 str
字符串本身就包含了换行符
'\n'
。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char str[100] = {0};
FILE *fp = NULL;
/* 打开文件 */
fp = fopen("./test_file", "r");
if (NULL == fp) {
perror("fopen error");
exit(-1);
}
/* 从文件中输入字符串 */
fgets(str, sizeof(str), fp);
printf("%s", str);
/* 关闭文件 */
fclose(fp);
exit(0);
}
使用 fgets()
读取文件中输入的字符串,文件指针会随着读取的字节数向前移动。
fgetc 函数
fgetc()与
getchar()
一样,用于读取一个输入字符,函数原型如下所示:
#include <stdio.h>
int fgetc(FILE *stream);
使用该函数需要包含头文件<stdio.h>
。
函数参数和返回值含义如下:
stream:
文件指针。
返回值:该函数以无符号
char
强制转换为
int
的形式返回读取的字符,如果到达文件末尾或发生读错误,则返回 EOF
。
fgetc()与
getchar()
的区别在于,
fgetc
可以指定输入字符的文件,既可以从标准输入设备输入字符,也可以从一个普通文件中输入字符,其它方面与 getchar
函数相同。
测试
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int ch;
ch = fgetc(stdin);
printf("%c\n", ch);
exit(0);
}
![](https://i-blog.csdnimg.cn/blog_migrate/b7818e0ac6f224ba2378e140677732a0.png)
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int ch;
FILE *fp = NULL;
/* 打开文件 */
fp = fopen("./test_file", "r");
if (NULL == fp) {
perror("fopen error");
exit(-1);
}
/* 从文件中输入一个字符 */
ch = fgetc(fp);
printf("%c\n", ch);
/* 关闭文件 */
fclose(fp);
exit(0);
}
6.1.3 总结
本小节给大家介绍了一些字符串输入、输出相关的 C
库函数,涉及到的函数比较多,在实际的编程当中,需要根据自己的实际需求以及函数的适用情况来进行选择。
6.2 字符串长度
C 语言函数库中提供了一个用于计算字符串长度的函数
strlen()
,其函数原型如下所示:
#include <string.h>
size_t strlen(const char *s);
使用该函数需要包含头文件<string.h>
。
函数参数和返回值含义如下:
s:
需要进行长度计算的字符串,字符串必须包含结束字符
' \0 '
。
返回值:返回字符串长度(以字节为单位),字符串结束字符
' \0 '
不计算在内。
测试
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char str[] = "Linux app strlen test!";
printf("String: \"%s\"\n", str);
printf("Length: %ld\n", strlen(str));
exit(0);
}
sizeof 和
strlen
的区别
在程序当中,我们通常也会使用
sizeof
来计算长度,那
strlen
和
sizeof
有什么区别呢?
sizeof
是
C
语言内置的操作符关键字,而
strlen
是
C
语言库函数;
|
sizeof
仅用于计算数据类型的大小或者变量的大小,而
strlen
只能以结尾为
' \0 '
的字符串作为参数;
|
编译器在编译时就计算出了
sizeof
的结果,而
strlen
必须在运行时才能计算出来;
|
sizeof
计算数据类型或变量会占用内存的大小,
strlen
计算字符串实际长度。
|
sizeof 和
strlen
测试
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char str[50] = "Linux app strlen test!";
char *ptr = str;
printf("sizeof: %ld\n", sizeof(str));
printf("strlen: %ld\n", strlen(str));
puts("~~~~~~~~~~");
printf("sizeof: %ld\n", sizeof(ptr));
printf("strlen: %ld\n", strlen(ptr));
exit(0);
}
![](https://i-blog.csdnimg.cn/blog_migrate/c8dfe38a529b07cccfe55538f97c9853.png)
从打印信息可知,第一个 sizeof
计算的是数组变量
str
的大小,所以等于
50
;而第二个
sizeof
计算的是指针变量 ptr
的大小,这里等于
8
个字节,因为这里笔者是在
Ubuntu 64
位系统下进行的测试,所以指针占用的内存大小就等于 8
个字节;而
strlen
始终计算的都是字符串的长度。
6.3 字符串拼接
C 语言函数库中提供了
strcat()
函数或
strncat()
函数用于将两个字符串连接(拼接)起来,
strcat
函数原型如下所示:
#include <string.h>
char *strcat(char *dest, const char *src);
用该函数需要包含头文件<string.h>
。
函数参数和返回值含义如下:
dest:
目标字符串。
src:
源字符串。
返回值:返回指向目标字符串
dest
的指针。
strcat()函数会把
src
所指向的字符串追加到
dest
所指向的字符串末尾,所以必须要保证
dest
有足够的存储空间来容纳两个字符串,否则会导致溢出错误;dest
末尾的
' \0 '
结束字符会被覆盖,
src
末尾的结束字符
' \0 '会一起被复制过去,最终的字符串只有一个
' \0 '
。
strcat 测试
使用 strcat
函数将字符串
str2
连接到字符串
str1
末尾,并将其打印出来。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char str1[100] = "蔡徐坤 ";
char str2[] = "练习时长两年半";
puts(str1);
puts(str2);
strcat(str1, str2);
puts(str1);
exit(0);
}
strncat 函数
strncat()与
strcat()
的区别在于,
strncat
可以指定源字符串追加到目标字符串的字符数量,
strncat
函数原型如下所示:
#include <string.h>
char *strncat(char *dest, const char *src, size_t n);
函数参数和返回值含义如下:
dest:
目标字符串。
src:
源字符串。
n:
要追加的最大字符数。
返回值:返回指向目标字符串
dest
的指针。
如果源字符串 src
包含
n
个或更多个字符,则
strncat()
将
n+1
个字节追加到
dest
目标字符串(
src
中的
n个字符加上结束字符' \0 '
)。
strncat 测试
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char str1[100] = "Linux app strcat test, ";
char str2[] = "Hello World!";
strncat(str1, str2, 5);
puts(str1);
exit(0);
}
6.4 字符串拷贝
C 语言函数库中提供了
strcpy()
函数和
strncpy()
函数用于实现字符串拷贝,
strcpy
函数原型如下所示:
#include <string.h>
char *strcpy(char *dest, const char *src);
函数参数和返回值含义如下:
dest:
目标字符串。
src:
源字符串。
返回值:返回指向目标字符串
dest
的指针。
strcpy()会把
src
(必须包含结束字符
' \0 '
)指向的字符串复制(包括字符串结束字符
' \0 '
)到
dest
,所以必须保证 dest
指向的内存空间足够大,能够容纳下
src
字符串,否则会导致溢出错误。
strcpy 测试
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char str1[100] = {0};
char str2[] = "练习时长两年半";
strcpy(str1, str2);
puts(str1);
exit(0);
}
![](https://i-blog.csdnimg.cn/blog_migrate/8fa41d3b9955832cd93e1c7c4295dded.png)
strncpy 函数
strncpy()与
strcpy()
的区别在于,
strncpy()
可以指定从源字符串
src
复制到目标字符串
dest
的字符数量,strncpy 函数原型如下所示:
#include <string.h>
char *strncpy(char *dest, const char *src, size_t n);
函数参数和返回值含义如下:
dest:
目标字符串。
src:
源字符串。
n:
从
src
中复制的最大字符数。
返回值:返回指向目标字符串
dest
的指针。
把 src
所指向的字符串复制到
dest
,最多复制
n
个字符。当
n
小于或等于
src
字符串长度(不包括结束字符的长度)时,则复制过去的字符串中没有包含结束字符' \0 '
;当
n
大于
src
字符串长度时,则会将
src 字符串的结束字符' \0 '也一并拷贝过去,必须保证 dest 指向的内存空间足够大,能够容纳下拷贝过来的字符串,否则会导致溢出错误。
strncpy
函数测试
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char str1[100] = "AAAAAAAAAAAAAAAAAAAAAAAA";
char str2[] = "Hello World!";
strncpy(str1, str2, 5);
puts(str1);
puts("~~~~~~~~~~~~~~~");
strncpy(str1, str2, 20);
puts(str1);
exit(0);
}
memcpy、
memmove
、
bcopy
除了 strcpy()
和
strncpy()
之外,其实还可以使用
memcpy()
、
memmove()
以及
bcopy()
这些库函数实现拷贝操作,字符串拷贝本质上也只是内存数据的拷贝,所以这些库函数同样也是适用的,在实际的编程当中,这些库函数也是很常用的,关于这三个库函数,这里不再给大家介绍,用法也非常简单,需要注意的就是目标内存空间与源内存空间是否有重叠的问题。
关于三个库函数的使用方法,大家可以使用 man
手册进行查询。
6.5 内存填充
在编程中,经常需要将某一块内存中的数据全部设置为指定的值,譬如在定义数组、结构体这种类型变量时,通常需要对其进行初始化操作,而初始化操作一般都是将其占用的内存空间全部填充为 0。
memset 函数
memset()
函数用于将某一块内存的数据全部设置为指定的值,其函数原型如下所示:
#include <string.h>
void *memset(void *s, int c, size_t n);
使用该函数需要包含头文件<string.h>
。
函数参数和返回值含义如下:
s:
需要进行数据填充的内存空间起始地址。
c:
要被设置的值,该值以
int
类型传递。
n:
填充的字节数。
返回值:返回指向内存空间
s
的指针。
参数 c
虽然是以
int
类型传递,但
memset()
函数在填充内存块时是使用该值的无符号字符形式,也就是函数内部会将该值转换为 unsigned char
类型的数据,以字节为单位进行数据填充。
memset 测试
对数组 str
进行初始化操作,将其存储的数据全部设置为
0
。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char str[100];
memset(str, 0x0, sizeof(str));
exit(0);
}
bzero 函数
bzero()函数用于将一段内存空间中的数据全部设置为
0
,函数原型如下所示:
#include <strings.h>
void bzero(void *s, size_t n);
函数参数和返回值含义如下:
s:
内存空间的起始地址。
n:
填充的字节数。
返回值:无返回值。
bzero 测试
对数组 str
进行初始化操作,将其存储的数据全部设置为
0
。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char str[100];
bzero(str, sizeof(str));
exit(0);
}
6.6 字符串比较
C 语言函数库提供了用于字符串比较的函数
strcmp()
和
strncmp()
,
strcmp()
函数原型如下所示:
#include <string.h>
int strcmp(const char *s1, const char *s2);
函数参数和返回值含义如下:
s1:
进行比较的字符串
1
。
s2:
进行比较的字符串
2
。
返回值:
如果返回值小于
0
,则表示
str1
小于
str2
|
如果返回值大于
0
,则表示
str1
大于
str2
|
如果返回值等于
0
,则表示字符串
str1
等于字符串
str2
|
strcmp 进行字符串比较,主要是通过比较字符串中的字符对应的
ASCII
码值,
strcmp
会根据
ASCII
编码依次比较 str1
和
str2
的每一个字符,直到出现了不同的字符,或者某一字符串已经到达末尾(遇见了字符串结束字符' \0 '
)。
strcmp 测试
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
printf("%d\n", strcmp("ABC", "ABC"));
printf("%d\n", strcmp("ABC", "a"));
printf("%d\n", strcmp("a", "ABC"));
exit(0);
}
strncmp 函数
strncmp()与
strcmp()
函数一样,也用于对字符串进行比较操作,但最多比较前
n
个字符,
strncmp()
函数
原型如下所示:
#include <string.h>
int strncmp(const char *s1, const char *s2, size_t n);
函数参数和返回值含义如下:
s1:
参与比较的第一个字符串。
s2:
参与比较的第二个字符串。
n:
最多比较前
n
个字符。
返回值:返回值含义与
strcmp()函数相同。
strncmp 测试
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
printf("%d\n", strncmp("ABC", "ABC", 3));
printf("%d\n", strncmp("ABC", "ABCD", 3));
printf("%d\n", strncmp("ABC", "ABCD", 4));
exit(0);
}
6.7 字符串查找
字符串查找在平时的编程当中也是一种很常见的操作,譬如从一个给定的字符串当中查找某一个字符或者一个字符串,并获取它的位置。C 语言函数库中也提供了一些用于字符串查找的函数,包括
strchr()
、strrchr()、
strstr()
、
strpbrk()
、
index()
以及
rindex()
等。(先不看了,用到了再来学习)
strchr
函数
strrchr
函数
strstr
函数
其它函数
6.8 字符串与数字互转
在编程中,经常会需要将数字组成的字符串转换为相应的数字、或者将数字转换为字符串,在 C
函数库中同样也提供了相应的函数,本小节就向大家介绍这些函数的用法。
6.8.1 字符串转整形数据
C 函数库中提供了一系列函数用于实现将一个字符串转为整形数据,主要包括
atoi()
、
atol()
、
atoll()
以及 strtol()、
strtoll()
、
strtoul()
、
strtoull()
等,它们之间的区别主要包括以下两个方面:
- 数据类型(int、long int、unsigned long 等)。
- 不同进制方式表示的数字字符串(八进制、十六进制、十进制)。
atoi
、
atol
、
atoll
函数
atoi()、
atol()
、
atoll()
三个函数可用于将字符串分别转换为
int
、
long int
以及
long long
类型的数据,它们的函数原型如下:
#include <stdlib.h>
int atoi(const char *nptr);
long atol(const char *nptr);
long long atoll(const char *nptr);
使用这些函数需要包含头文件<stdlib.h>
。
函数参数和返回值含义如下:
nptr:
需要进行转换的字符串。
返回值:分别返回转换之后得到的 int
类型数据、
long int
类型数据以及
long long
类型数据。目标字符串 nptr
中可以包含非数字字符,转换时跳过前面的空格字符(如果目标字符串开头存在空格字符),直到遇上数字字符或正负符号才开始做转换,而再遇到非数字或字符串结束时(' /0 ')
才结束转换,并将结果返回。
使用 atoi()
、
atol()
、
atoll()
函数只能转换十进制表示的数字字符串,即
0~9
。
测试
使用 atoi()
、
atol()
、
atoll()
这三个函数将一个数字字符串转为十进制数据。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
printf("atoi: %d\n", atoi("500"));
printf("atol: %ld\n", atol("500"));
printf("atoll: %lld\n", atoll("500"));
exit(0);
}
strtol、
strtoll
函数
strtol()、
strtoll()
两个函数可分别将字符串转为
long int
类型数据和
long long ing
类型数据,与
atol()
、atoll()之间的区别在于,
strtol()
、
strtoll()
可以实现将多种不同进制数(譬如二进制表示的数字字符串、八进制表示的数字字符串、十六进制表示的数数字符串)表示的字符串转换为整形数据,其函数原型如下所示:
#include <stdlib.h>
long int strtol(const char *nptr, char **endptr, int base);
long long int strtoll(const char *nptr, char **endptr, int base);
使用这两个函数需要包含头文件<stdlib.h>
。
函数参数和返回值含义如下:
nptr:
需要进行转换的目标字符串。
endptr:
char **
类型的指针,如果
endptr
不为
NULL
,则
strtol()
或
strtoll()
会将字符串中第一个无效字符的地址存储在*endptr
中。如果根本没有数字,
strtol()
或
strtoll()
会将
nptr
的原始值存储在
*endptr
中(并返回 0
)。也可将参数
endptr
设置为
NULL
,表示不接收相应信息。
base:
数字基数,参数
base
必须介于
2
和
36
(包含)之间,或者是特殊值
0
。参数
base
决定了字符串转换为整数时合法字符的取值范围,譬如,当 base=2
时,合法字符为
' 0 '
、
' 1 '
(表示是一个二进制表示的数字字符串);当 base=8
时,合法字符为
' 0 '
、
' 1 '
、
' 2 '
、
' 3 '……' 7 '
(表示是一个八进制表示的数字字符串);当 base=16
时,合法字符为
' 0 '
、
' 1 '
、
' 2 '
、
' 3 '……' 9 '
、
' a '……' f '
(表示是一个十六进制表示的数字字符串);当 base
大于
10
的时候,
' a '
代表
10
、
' b '
代表
11
、
' c '
代表
12
,依次类推,
' z '
代表
35
(不区分大小写)。
返回值:分别返回转换之后得到的
long int
类型数据以及
long long int
类型数据。
需要进行转换的目标字符串可以以任意数量的空格或者 0
开头,转换时跳过前面的空格字符,直到遇上数字字符或正负符号(' + '或
' - '
)才开始做转换,而再遇到非数字或字符串结束时
(' /0 ')
才结束转换,并将结果返回。
在 base=0
的情况下,如果字符串包含一个了“
0x
”前缀,表示该数字将以
16
为基数;如果包含的是 “0
”前缀,表示该数字将以
8
为基数。
当 base=16
时,字符串可以使用“
0x
”前缀。
测试
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
printf("strtol: %ld\n", strtol("0x500", NULL, 16));
printf("strtol: %ld\n", strtol("0x500", NULL, 0));
printf("strtol: %ld\n", strtol("500", NULL, 16));
printf("strtol: %ld\n", strtol("0777", NULL, 8));
printf("strtol: %ld\n", strtol("0777", NULL, 0));
printf("strtol: %ld\n", strtol("1111", NULL, 2));
printf("strtol: %ld\n", strtol("-1111", NULL, 2));
exit(0);
}
strtoul、
strtoull
函数
这两个函数使用方法与 strtol()
、
strtoll()
一样,区别在于返回值的类型不同,
strtoul()
返回值类型是
unsigned long int,
strtoull()
返回值类型是
unsigned long long int
,函数原型如下所示:
#include <stdlib.h>
unsigned long int strtoul(const char *nptr, char **endptr, int base);
unsigned long long int strtoull(const char *nptr, char **endptr, int base);
函数参数与 strtol()
、
strtoll()
一样,这里不再重述!
测试
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
printf("strtoul: %lu\n", strtoul("0x500", NULL, 16));
printf("strtoul: %lu\n", strtoul("0x500", NULL, 0));
printf("strtoul: %lu\n", strtoul("500", NULL, 16));
printf("strtoul: %lu\n", strtoul("0777", NULL, 8));
printf("strtoul: %lu\n", strtoul("0777", NULL, 0));
printf("strtoul: %lu\n", strtoul("1111", NULL, 2));
exit(0);
}
6.8.2 字符串转浮点型数据
C 函数库中用于字符串转浮点型数据的函数有
atof()
、
strtod()
、
strtof()
、
strtold()
。
atof 函数
atof()用于将字符串转换为一个
double
类型的浮点数据,函数原型如下所示:
#include <stdlib.h>
double atof(const char *nptr);
使用该函数需要包含头文件<stdlib.h>
。
函数参数和返回值含义如下:
nptr:
需要进行转换的字符串。
返回值:返回转换得到的
double
类型数据。
测试
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
printf("atof: %lf\n", atof("0.123"));
printf("atof: %lf\n", atof("-1.1185"));
printf("atof: %lf\n", atof("100.0123"));
exit(0);
}
![](https://i-blog.csdnimg.cn/blog_migrate/5b8a94244f95f30d49f29d5f6b3711ed.png)
strtod、
strtof
、
strtold
函数
strtof()、
strtod()
以及
strtold()
三个库函数可分别将字符串转换为
float
类型数据、
double
类型数据、
long double 类型数据,函数原型如下所示:
#include <stdlib.h>
double strtod(const char *nptr, char **endptr);
float strtof(const char *nptr, char **endptr);
long double strtold(const char *nptr, char **endptr);
使用这些函数需要包含头文件<stdlib.h>
。
函数参数与 strtol()
含义相同,但是少了
base
参数。
测试
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
printf("strtof: %f\n", strtof("0.123", NULL));
printf("strtod: %lf\n", strtod("-1.1185", NULL));
printf("strtold: %Lf\n", strtold("100.0123", NULL));
exit(0);
}
![](https://i-blog.csdnimg.cn/blog_migrate/e651984ee2d63492f23b7cbf8fa825c6.png)
6.8.3 数字转字符串
数字转换为字符串推荐大家使用前面介绍的格式化 IO
相关库函数,譬如使用
printf()
将数字转字符串、并将其输出到标准输出设备或者使用 sprintf()
或
snprintf()
将数字转换为字符串并存储在缓冲区中,具体的使用方法,3.11
内容中已经给大家进行了详细介绍,这里不再重述。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char str[20] = {0};
sprintf(str, "%d", 500);
puts(str);
memset(str, 0x0, sizeof(str));
sprintf(str, "%f", 500.111);
puts(str);
memset(str, 0x0, sizeof(str));
sprintf(str, "%u", 500);
puts(str);
exit(0);
}
6.9 给应用程序传参
一个能够接受外部传参的应用程序往往使用上会比较灵活,根据参入不同的参数实现不同的功能,前面给大家编写的示例代码中,信息都是硬编码在代码中的,譬如 open 打开的文件路径是固定的,意味着如果需要打开另一个文件则需要修改代码、修改文件路径,然后再重新编译、运行,非常麻烦、不够灵活。其实可以将这些可变的信息通过参数形式传递给应用程序,譬如,当执行应用程序的时候,把需要打开的文件路径作为参数传递给应用程序,就可以在不重新编译源码的情况下,通过传递不同的参数打开不同的文件。当然这里只是举个例子,不同应用程序需根据其需要来设计。
在第一章内容中便给大家介绍了 main
函数的两种常用写法,如果在执行应用程序时,需要向应用程序传递参数,则写法如下:
int main(int argc, char **argv)
{
/* 代码 */
}
或者写成如下形式:
int main(int argc, char *argv[])
{
/* 代码 */
}
传递进来的参数以字符串的形式存在,字符串的起始地址存储在 argv
数组中,参数
argc
表示传递进来的参数个数,包括应用程序自身路径名,多个不同的参数之间使用空格分隔开来,如果参数本身带有空格、则可以使用双引号" "
或者单引号
' '
的形式来表示。
测试
获取执行应用程序时,向应用程序传递的参数。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int i = 0;
printf("Number of parameters: %d\n", argc);
for (i = 0; i < argc; i++)
printf(" %s\n", argv[i]);
exit(0);
}
6.10 正则表达式
上面给大家介绍了 C
语言函数库中提供的用于处理字符串相关的一些函数,这些库函数能够满足基本常见的字符串处理需求,其库函数内部实现并不复杂,无非使用到了 for
循环进行处理,大家可以尝试自己去实现这些函数的功能。
本小节给大家介绍一个新的内容---
正则表达式,在许多的应用程序当中,通常会有这样的需要:给定一个字符串,检查该字符串是否符合某种条件或规则、或者从给定的字符串中找出符合某种条件或规则的子字符串,将匹配到的字符串提取出来。这种需要在很多的应用程序当中是存在的,例如,很多应用程序都有这种校验功能,譬如检验用户输入的账号或密码是否符合它们定义的规则,如果不符合规则通常会提示用户按照正确的规则输入用户名或密码。
譬如给定一个字符串,在程序当中判断该字符串是否是一个 IP
地址,对于实现这个功能,大家可能首先想到的是,使用万能的 for
循环,当然,笔者首先肯定的是,使用
for
循环自然是可以解决这个问题,但是在程序代码处理上会比较麻烦,有兴趣的朋友可以自己试一下。
对于这些需求,其实只需要通过一个正则表达式就可以搞定了,下一小节开始将向大家介绍正则表达式。
6.10.1
初识正则表达式
正则表达式,又称为规则表达式(英语: Regular Expression
),正则表达式通常被用来检索、替换那些符合某个模式(规则)的字符串,正则表达式描述了一种字符串的匹配模式(pattern
),可以用来检查一个给定的字符串中是否含有某种子字符串、将匹配的字符串替换或者从某个字符串中取出符合某个条件的子字符串。
在 Linux
系统下运行命令的时候,相信大家都使用过
?
或
*
通配符来查找硬盘上的文件或者文本中的某个字符串,?
通配符匹配
0
个或
1
个字符,而
*
通配符匹配
0
个或多个字符,譬如
"data?.txt"
这样的匹配模式可以将下列文件查找出来:
data.datdata1.datdata2.datdatax.datdataN.dat
尽管使用通配符的方法很有用,但它还是很有限,正则表达式则更加强大、更加灵活。
正则表达式其实也是一个字符串,该字符串由普通字符(譬如,数字 0~9
、大小写字母以及其它字符)和特殊字符(称为“元字符”)所组成,由这些字符组成一个“规则字符串”,这个“规则字符串”用来表达对给定字符串的一种查找、匹配逻辑。
许多程序设计语言都支持正则表达式。譬如,在 Perl
中就内建了一个功能强大的正则表达式引擎、
Python提供了内置模块 re
用于处理正则表达式,正则表达式这个概念最初是由
Unix
中的工具软件(例如
sed
和 grep)普及开的,使用过
sed
命令的朋友想必对正则表达式并不陌生。同样,在
C
语言函数库中也提供了用于处理正则表达式的接口供程序员使用。
6.11 C 语言中使用正则表达式
编译正则表达式
匹配正则表达式
释放正则表达式
匹配 URL
的正则表达式:
^((ht|f)tps?)://[-A-Za-z0-9_]+(\.[-A-Za-z0-9_]+)+([-A-Za-z0-9_.,@?^=%&:/~+#]*[-A-Za-z0-9_@?^=%&/~+#])?$
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <regex.h>
#include <string.h>
int main(int argc, char *argv[])
{
regmatch_t pmatch = {0};
regex_t reg;
char errbuf[64];
int ret;
char *sptr;
int length;
int nmatch; //最多匹配出的结果
if (4 != argc) {
/**********************************
* 执行程序时需要传入两个参数:
* arg1: 正则表达式
* arg2: 待测试的字符串
* arg3: 最多匹配出多少个结果
**********************************/
fprintf(stderr, "usage: %s <regex> <string> <nmatch>\n", argv[0]);
exit(0);
}
/* 编译正则表达式 */
if(ret = regcomp(®, argv[1], REG_EXTENDED)) {
regerror(ret, ®, errbuf, sizeof(errbuf));
fprintf(stderr, "regcomp error: %s\n", errbuf);
exit(0);
}
/* 赋值操作 */
sptr = argv[2]; //待测试的字符串
length = strlen(argv[2]);//获取字符串长度
nmatch = atoi(argv[3]); //获取最大匹配数
/* 匹配正则表达式 */
for (int j = 0; j < nmatch; j++) {
char temp_str[100];
/* 调用 regexec 匹配正则表达式 */
if(ret = regexec(®, sptr, 1, &pmatch, 0)) {
regerror(ret, ®, errbuf, sizeof(errbuf));
fprintf(stderr, "regexec error: %s\n", errbuf);
goto out;
}
if(-1 != pmatch.rm_so) {
if (pmatch.rm_so == pmatch.rm_eo) {//空字符串
sptr += 1;
length -= 1;
printf("\n"); //打印出空字符串
if (0 >= length)//如果已经移动到字符串末尾、则退出
break;
continue; //从 for 循环开始执行
}
memset(temp_str, 0x00, sizeof(temp_str));//清零缓冲区
memcpy(temp_str, sptr + pmatch.rm_so,
pmatch.rm_eo - pmatch.rm_so);//将匹配出来的子字符串拷贝到缓冲区
printf("%s\n", temp_str); //打印字符串
sptr += pmatch.rm_eo;
length -= pmatch.rm_eo;
if (0 >= length)
break;
}
}
/* 释放正则表达式 */
out:
regfree(®);
exit(0);
}
正则表达式不会用
![](https://i-blog.csdnimg.cn/blog_migrate/6ee11fe13f331d68fd805c2612eb246f.png)