龙空技术网

正点原子I.MX6U嵌入式Linux C应用编程 第六章 字符串处理

正点原子原子哥 612

前言:

此刻小伙伴们对“ubuntu报错缓冲区溢出”大体比较关切,看官们都需要学习一些“ubuntu报错缓冲区溢出”的相关资讯。那么小编同时在网上搜集了一些对于“ubuntu报错缓冲区溢出””的相关文章,希望你们能喜欢,朋友们一起来了解一下吧!

字符串处理

字符串处理在几乎所有的编程语言中都是一个绕不开的话题,在一些高级语言当中,对字符串的处理支持度更是完善,譬如C++、C#、Python等。若在C语言中想要对字符串进行相关的处理,譬如将两个字符串进行拼接、字符串查找、两个字符串进行比较等操作,几乎是需要程序员自己编写字符串处理相关逻辑代码来实现字符串处理功能。

好在C语言库函数中已经给我们提供了丰富的字符串处理相关函数,基本常见的字符串处理需求都可以直接使用这些库函数来实现,而不需要自己编写代码,使用这些库函数可以大大减轻编程负担。这些库函数大致可以分为字符串的输入、输出、合并、修改、比较、转换、复制、搜索等几类,本章将向大家介绍这些库函数的使用方法。

本章将会讨论如下主题内容。

字符串输入/输出;C库中提供的字符串处理函数;给应用程序传参;正则表达式。字符串输入/输出

在程序当中,经常需要在程序运行过程中打印出一些信息,将其输出显示到标准输出设备stdout(譬如屏幕)或标准错误设备stderr(譬如屏幕),譬如调试信息、报错信息、中间产生的变量的值等等,以实现对程序运行状态的掌控和分析。除了向stdout或stderr输出打印信息之外,有时程序在运行过程中还需要从标准输入设备stdin(譬如键盘)中读取字符串,将读取到的字符串进行解析,以指导程序的下一步动作、控制程序执行流程。

字符串输出

常用的字符串输出函数有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函数测试

示例代码 6.1.1 puts函数使用示例

#include <stdio.h>

#include <stdlib.h>

int main(void)

{

char str[50] = "Linux app puts test";

puts("Hello World!");

puts(str);

exit(0);

}

编译运行结果如下:

图 6.1.1 puts输出字符串

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函数测试

示例代码 6.1.2 putchar函数使用示例

#include <stdio.h>

#include <stdlib.h>

int main(void)

{

putchar('A');

putchar('B');

putchar('C');

putchar('D');

putchar('\n');

exit(0);

}

编译运行结果如下:

图 6.1.2 putchar输出字符

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函数将字符输出到标准输出设备。

示例代码 6.1.3 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);

}

编译运行:

图 6.1.3 fputc测试结果1

(2)使用fputc函数将字符输出到一个普通文件。

示例代码 6.1.4 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);

}

编译运行,结果如下:

图 6.1.4 fputc测试结果2

fputs函数

同理,fputs()与puts()类似,也用于输出一条字符串,与puts()区别在于,puts()只能输出到标准输出设备,而fputs()可把字符串输出到指定的文件中,既可以是标准输出、标准错误设备,也可以是一个普通文件。

函数原型如下所示:

#include <stdio.h>

int fputs(const char *s, FILE *stream);

函数参数和返回值含义如下:

s:需要输出的字符串。

stream:文件指针。

返回值:成功返回非负数;失败将返回EOF。

fputs测试

(1)使用fputs输出字符串到标注输出设备。

示例代码 6.1.5 fputs输出字符串到标准输出设备

#include <stdio.h>

#include <stdlib.h>

int main(void)

{

fputs("Hello World! 1\n", stdout);

fputs("Hello World! 2\n", stdout);

exit(0);

}

编译运行,结果如下:

图 6.1.5 fputs测试结果1

(2)使用fputs输出字符串到一个普通文件。

示例代码 6.1.6 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.6 fputs测试结果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()函数获取用户输入字符串。

示例代码 6.1.7 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系统编译代码时,会出现如下警告信息:

图 6.1.7 编译代码出现警告信息

出现如上警告信息,其实是建议我们不要使用gets()函数,因为程序中使用gets()函数是非常不安全的,可能会出现bug、出现不可靠性,gets()在某些意外情况下会导致程序陷入不可控状态,所以一般建议大家不要使用这个函数,可以使用后面将给大家介绍的fgets()代替。

这里先不管这个警告信息,我们直接运行测试:

图 6.1.8 gets测试结果

由此可知,不管我们输入的是空格、单引号、双引号都会作为gets()获取到的字符串的一部分,直到用户输入回车换行符结束。

gets()与scanf()的区别

gets()除了在功能上不及scanf之外,它们在一些细节上也存在着不同:

gets()函数不仅比scanf简洁,而且,就算输入的字符串中有空格也可以,因为gets()函数允许输入的字符串带有空格、制表符,输入的空格和制表符也是字符串的一部分,仅以回车换行符作为字符串的分割符;而对于scanf以%s格式输入的时候,空格、换行符、TAB制表符等都是作为字符串分割符存在,即分隔符前后是两个字符串,读取字符串时并不会将分隔符读取出来作为字符串的组成部分,一个%s只能读取一个字符串,若要多去多个字符串,则需要使用多个%s、并且需要使用多个字符数组存储。gets()会将回车换行符从输入缓冲区中取出来,然后将其丢弃,所以使用gets()读走缓冲区中的字符串数据之后,缓冲区中将不会遗留下回车换行符;而对于scanf()来说,使用scanf()读走缓冲区中的字符串数据时,并不会将分隔符(空格、TAB制表符、回车换行符等)读走将其丢弃,所以使用scanf()读走缓冲区中的字符串数据之后,缓冲区中依然还存在用户输入的分隔符。

针对上面所提出的两个区别点,下面我们将进行一些列的代码测试。

(1)测试1

示例代码 6.1.8 测试代码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回车时,输出结果如下(_表示空格):

图 6.1.9 测试结果1

代码中我们调用了两次scanf(),而事实上我们只输入了一次,输入“123”之后输入空格、再输入“456”,然后按回车,由打印结果可知,字符串s1等于“123”,字符串s2等于“456”;当输入完成按回车之后,输入缓冲区中此时存在如下字符:

'1'、'2'、'3'、'空格'、'4'、'5'、'6'、'\n'

第一个scanf()读取缓冲区时,将'1'、'2'、'3'读走,读走之后,它们将不存在于缓冲区中了,空格被视为字符串分割符,分割符及后面的字符将不会读取(以%s格式输入情况下)。

第二个scanf()读取缓冲区时,'4'、'5'、'6'会被读走,分割符依然不读取。

再次执行测试程序:

图 6.1.10 测试结果2

当输入“123”回车,之后输出了“123”;之后需要再次输入,接着输入“456”回车,输出“456”。这里输入了两次字符串,原因在于第一次scanf()读走“123”之后,缓冲区中只剩下回车换行字符,第二次scanf()不读取换行符,所以需要用户再次输入字符串。

(2)测试2

示例代码 6.1.9 测试代码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);

}

执行测试:

图 6.1.11 测试结果3

同样这段代码也是调用了两次scanf(),但只是输入了一次字符串,当第一个scanf()读取之后,缓冲区中只剩下回车换行符;从打印信息可以发现,第二次scanf()读取时,把换行符也读取出来了(换行符'\n'对应的ASCII编码值等于10),因为这里scanf用的是%c格式,而不是%s,对于%c读入时,空格、换行符、TAB这些都是正常字符。

(3)测试3

示例代码 6.1.10 测试代码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);

}

执行测试:

图 6.1.12 测试结果4

这段代码先是调用了scanf(),之后调用了gets(),但只输入了一次字符串。scanf()读取之后,缓冲区中只剩下换行符,但是gets()会将换行符读取出来并将其丢弃,所以说字符串便是一个空字符串。

再次执行测试程序:

图 6.1.13 测试结果5

字符串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制表符、换行回车符等。

测试

示例代码 6.1.11 getchar函数使用示例

#include <stdio.h>

#include <stdlib.h>

int main(void)

{

int ch;

ch = getchar();

printf("ch: %c\n", ch);

exit(0);

}

执行测试:

图 6.1.14 测试结果6

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)在一些细节方面的区别。

测试

示例代码 6.1.12 fgets从标准输入设备获取字符串

#include <stdio.h>

#include <stdlib.h>

int main(void)

{

char str[100] = {0};

printf("请输入字符串: ");

fgets(str, sizeof(str), stdin);

printf("%s", str);

exit(0);

}

执行测试:

图 6.1.15 测试结果

此段代码中,使用printf打印字符串str时并没有在%s后面添加'\n',但是结果显示,打印出来的字符串已经换行,也就意味着str字符串本身就包含了换行符'\n'。

示例代码 6.1.13 fgets从普通文件中输入字符串

#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()读取文件中输入的字符串,文件指针会随着读取的字节数向前移动。

执行测试:

图 6.1.16 测试结果

fgetc函数

fgetc()与getchar()一样,用于读取一个输入字符,函数原型如下所示:

#include <stdio.h>

int fgetc(FILE *stream);

使用该函数需要包含头文件<stdio.h>。

函数参数和返回值含义如下:

stream:文件指针。

返回值:该函数以无符号char强制转换为int的形式返回读取的字符,如果到达文件末尾或发生读错误,则返回EOF。

fgetc()与getchar()的区别在于,fgetc可以指定输入字符的文件,既可以从标准输入设备输入字符,也可以从一个普通文件中输入字符,其它方面与getchar函数相同。

测试

示例代码 6.1.14 fgetc从标准输入设备中输入字符

#include <stdio.h>

#include <stdlib.h>

int main(void)

{

int ch;

ch = fgetc(stdin);

printf("%c\n", ch);

exit(0);

}

执行测试:

图 6.1.17 测试结果

示例代码 6.1.15 fgetc从普通文件中输入字符

#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.18 测试结果

总结

本小节给大家介绍了一些字符串输入、输出相关的C库函数,涉及到的函数比较多,在实际的编程当中,需要根据自己的实际需求以及函数的适用情况来进行选择。

字符串长度

C语言函数库中提供了一个用于计算字符串长度的函数strlen(),其函数原型如下所示:

#include <string.h>

size_t strlen(const char *s);

使用该函数需要包含头文件<string.h>。

函数参数和返回值含义如下:

s:需要进行长度计算的字符串,字符串必须包含结束字符' \0 '。

返回值:返回字符串长度(以字节为单位),字符串结束字符' \0 '不计算在内。

测试

示例代码 6.2.1 strlen使用示例

#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);

}

运行结果:

图 6.2.1 strlen计算字符串长度

sizeof和strlen的区别

在程序当中,我们通常也会使用sizeof来计算长度,那strlen和sizeof有什么区别呢?

sizeof是C语言内置的操作符关键字,而strlen是C语言库函数;sizeof仅用于计算数据类型的大小或者变量的大小,而strlen只能以结尾为' \0 '的字符串作为参数;编译器在编译时就计算出了sizeof的结果,而strlen必须在运行时才能计算出来;sizeof计算数据类型或变量会占用内存的大小,strlen计算字符串实际长度。

sizeof和strlen测试

示例代码 6.2.2 strlen和sizeof对比测试

#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);

}

测试结果:

图 6.2.2 strlen和sizeof对比测试结果

从打印信息可知,第一个sizeof计算的是数组变量str的大小,所以等于50;而第二个sizeof计算的是指针变量ptr的大小,这里等于8个字节,因为这里笔者是在Ubuntu 64位系统下进行的测试,所以指针占用的内存大小就等于8个字节;而strlen始终计算的都是字符串的长度。

字符串拼接

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末尾,并将其打印出来。

示例代码 6.3.1 strcat使用示例

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

int main(void)

{

char str1[100] = "Linux app strcat test, ";

char str2[] = "Hello World!";

strcat(str1, str2);

puts(str1);

exit(0);

}

测试结果:

图 6.3.1 strcat测试结果

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测试

示例代码 6.3.2 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.3.2 strncat测试结果

字符串拷贝

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测试

示例代码 6.4.1 strcpy函数使用示例

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

int main(void)

{

char str1[100] = {0};

char str2[] = "Hello World!";

strcpy(str1, str2);

puts(str1);

exit(0);

}

测试结果:

图 6.4.1 strcpy测试结果

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函数测试

示例代码 6.4.2 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);

}

测试结果:

图 6.4.2 strncpy测试结果

memcpy、memmove、bcopy

除了strcpy()和strncpy()之外,其实还可以使用memcpy()、memmove()以及bcopy()这些库函数实现拷贝操作,字符串拷贝本质上也只是内存数据的拷贝,所以这些库函数同样也是适用的,在实际的编程当中,这些库函数也是很常用的,关于这三个库函数,这里不再给大家介绍,用法也非常简单,需要注意的就是目标内存空间与源内存空间是否有重叠的问题。

关于三个库函数的使用方法,大家可以使用man手册进行查询。

内存填充

在编程中,经常需要将某一块内存中的数据全部设置为指定的值,譬如在定义数组、结构体这种类型变量时,通常需要对其进行初始化操作,而初始化操作一般都是将其占用的内存空间全部填充为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。

示例代码 6.5.1 memset函数使用示例

#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。

示例代码 6.5.2 bzero函数使用示例

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

int main(void)

{

char str[100];

bzero(str, sizeof(str));

exit(0);

}

字符串比较

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测试

示例代码 6.6.1 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);

}

测试结果:

图 6.6.1 strcmp测试结果

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测试

示例代码 6.6.2 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.6.2 strncmp测试结果

字符串查找

字符串查找在平时的编程当中也是一种很常见的操作,譬如从一个给定的字符串当中查找某一个字符或者一个字符串,并获取它的位置。C语言函数库中也提供了一些用于字符串查找的函数,包括strchr()、strrchr()、strstr()、strpbrk()、index()以及rindex()等。

strchr函数

使用strchr()函数可以查找到给定字符串当中的某一个字符,函数原型如下所示:

#include <string.h>

char *strchr(const char *s, int c);

函数参数和返回值含义如下:

s:给定的目标字符串。

c:需要查找的字符。

返回值:返回字符c第一次在字符串s中出现的位置,如果未找到字符c,则返回NULL。

字符串结束字符' \0 '也将作为字符串的一部分,因此,如果将参数c指定为' \0 ',则函数将返回指向结束字符的指针。strchr函数在字符串s中从前到后(或者称为从左到右)查找字符c,找到字符c第一次出现的位置就返回,返回值指向这个位置,如果找不到字符c就返回NULL。

strchr测试

从字符串中查找一个字符,并将其在字符串数组中的下标打印出来。

示例代码 6.7.1 strchr函数使用示例

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

int main(void)

{

char *ptr = NULL;

char str[] = "Hello World!";

ptr = strchr(str, 'W');

if (NULL != ptr) {

printf("Character: %c\n", *ptr);

printf("Offset: %ld\n", ptr - str);

}

exit(0);

}

测试结果:

图 6.7.1 strchr测试结果

strrchr函数

strrchr()与strchr()函数一样,它同样表示在字符串中查找某一个字符,返回字符第一次在字符串中出现的位置,如果没找到该字符,则返回值NULL,但两者唯一不同的是,strrchr()函数在字符串中是从后到前(或者称为从右向左)查找字符,找到字符第一次出现的位置就返回,返回值指向这个位置,strrchr()函数原型如下所示:

#include <string.h>

char *strrchr(const char *s, int c);

函数参数和返回值含义与strchr()函数相同。

strrchr测试

编写程序测试strrchr()与strchr()之间的区别。

示例代码 6.7.2 strrchr()与strchr()之间的区别

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

int main(void)

{

char *ptr = NULL;

char str[] = "I love my home";

ptr = strchr(str, 'o');

if (NULL != ptr)

printf("strchr: %ld\n", ptr - str);

ptr = strrchr(str, 'o');

if (NULL != ptr)

printf("strrchr: %ld\n", ptr - str);

exit(0);

}

测试结果:

图 6.7.2 strrchr与strchr对比测试结果

strstr函数

与strchr()函数不同的是,strstr()可在给定的字符串haystack中查找第一次出现子字符串needle的位置,不包含结束字符' \0 ',函数原型如下所示:

#include <string.h>

char *strstr(const char *haystack, const char *needle);

函数参数和返回值含义如下:

haystack:目标字符串。

needle:需要查找的子字符串。

返回值:如果目标字符串haystack中包含了子字符串needle,则返回该字符串首次出现的位置;如果未能找到子字符串needle,则返回NULL。

strstr测试

在给定的字符串"I love my home"中,查找"home"在该字符串中首次出现的位置。

示例代码 6.7.3 strstr函数使用示例

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

int main(void)

{

char *ptr = NULL;

char str[] = "I love my home";

ptr = strstr(str, "home");

if (NULL != ptr) {

printf("String: %s\n", ptr);

printf("Offset: %ld\n", ptr - str);

}

exit(0);

}

测试结果:

图 6.7.3 strstr测试结果

其它函数

除了上面介绍的三个函数之外,C函数库中还提供其它的字符串(或字符)查找函数,譬如strpbrk()、index()以及rindex()等,这里便不再给大家一一介绍了,这些函数的用法都比较简单,大家通过man手册便可以快速了解到它们的使用方法。

字符串与数字互转

在编程中,经常会需要将数字组成的字符串转换为相应的数字、或者将数字转换为字符串,在C函数库中同样也提供了相应的函数,本小节就向大家介绍这些函数的用法。

字符串转整形数据

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()这三个函数将一个数字字符串转为十进制数据。

示例代码 6.8.1 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);

}

测试结果:

图 6.8.1 atoi、atol、atoll测试结果

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”前缀。

测试

示例代码 6.8.2 strtol函数使用示例

#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);

}

测试结果:

图 6.8.2 strtol测试结果

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()一样,这里不再重述!

测试

示例代码 6.8.3 strtoul函数使用示例

#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.3 strtoul函数测试结果

字符串转浮点型数据

C函数库中用于字符串转浮点型数据的函数有atof()、strtod()、strtof()、strtold()。

atof函数

atof()用于将字符串转换为一个double类型的浮点数据,函数原型如下所示:

#include <stdlib.h>

double atof(const char *nptr);

使用该函数需要包含头文件<stdlib.h>。

函数参数和返回值含义如下:

nptr:需要进行转换的字符串。

返回值:返回转换得到的double类型数据。

测试

示例代码 6.8.4 atof函数使用示例

#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);

}

测试结果:

图 6.8.4 atof函数测试结果

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参数。

测试

示例代码 6.8.5 strtof、strtod、strtold使用示例

#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);

}

测试结果:

图 6.8.5 字符串转浮点型数据

数字转字符串

数字转换为字符串推荐大家使用前面介绍的格式化IO相关库函数,譬如使用printf()将数字转字符串、并将其输出到标准输出设备或者使用sprintf()或snprintf()将数字转换为字符串并存储在缓冲区中,具体的使用方法,第四章内容中已经给大家进行了详细介绍,这里不再重述。

示例代码 6.8.6 sprintf数字转字符串

#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.8.6 测试结果

给应用程序传参

一个能够接受外部传参的应用程序往往使用上会比较灵活,根据参入不同的参数实现不同的功能,前面给大家编写的示例代码中,信息都是硬编码在代码中的,譬如open打开的文件路径是固定的,意味着如果需要打开另一个文件则需要修改代码、修改文件路径,然后再重新编译、运行,非常麻烦、不够灵活。其实可以将这些可变的信息通过参数形式传递给应用程序,譬如,当执行应用程序的时候,把需要打开的文件路径作为参数传递给应用程序,就可以在不重新编译源码的情况下,通过传递不同的参数打开不同的文件。当然这里只是举个例子,不同应用程序需根据其需要来设计。

在第一章内容中便给大家介绍了main函数的两种常用写法,如果在执行应用程序时,需要向应用程序传递参数,则写法如下:

int main(int argc, char **argv)

{

/* 代码 */

}

或者写成如下形式:

int main(int argc, char *argv[])

{

/* 代码 */

}

传递进来的参数以字符串的形式存在,字符串的起始地址存储在argv数组中,参数argc表示传递进来的参数个数,包括应用程序自身路径名,多个不同的参数之间使用空格分隔开来,如果参数本身带有空格、则可以使用双引号" "或者单引号' '的形式来表示。

测试

获取执行应用程序时,向应用程序传递的参数。

示例代码 6.9.1 打印传递给应用程序的参数

#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.9.1 给应用程序传参

正则表达式

上面给大家介绍了C语言函数库中提供的用于处理字符串相关的一些函数,这些库函数能够满足基本常见的字符串处理需求,其库函数内部实现并不复杂,无非使用到了for循环进行处理,大家可以尝试自己去实现这些函数的功能。

本小节给大家介绍一个新的内容---正则表达式,在许多的应用程序当中,通常会有这样的需要:给定一个字符串,检查该字符串是否符合某种条件或规则、或者从给定的字符串中找出符合某种条件或规则的子字符串,将匹配到的字符串提取出来。这种需要在很多的应用程序当中是存在的,例如,很多应用程序都有这种校验功能,譬如检验用户输入的账号或密码是否符合它们定义的规则,如果不符合规则通常会提示用户按照正确的规则输入用户名或密码。

譬如给定一个字符串,在程序当中判断该字符串是否是一个IP地址,对于实现这个功能,大家可能首先想到的是,使用万能的for循环,当然,笔者首先肯定的是,使用for循环自然是可以解决这个问题,但是在程序代码处理上会比较麻烦,有兴趣的朋友可以自己试一下。

对于这些需求,其实只需要通过一个正则表达式就可以搞定了,下一小节开始将向大家介绍正则表达式。

初识正则表达式

正则表达式,又称为规则表达式(英语: Regular Expression),正则表达式通常被用来检索、替换那些符合某个模式(规则)的字符串,正则表达式描述了一种字符串的匹配模式(pattern),可以用来检查一个给定的字符串中是否含有某种子字符串、将匹配的字符串替换或者从某个字符串中取出符合某个条件的子字符串。

在Linux系统下运行命令的时候,相信大家都使用过?或*通配符来查找硬盘上的文件或者文本中的某个字符串,?通配符匹配0个或1个字符,而*通配符匹配0个或多个字符,譬如"data?.txt"这样的匹配模式可以将下列文件查找出来:

data.dat

data1.dat

data2.dat

datax.dat

dataN.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_@?^=%&/~+#])?$

示例代码 6.11.1 C应用程序中使用正则表达式

#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(&reg, argv[1], REG_EXTENDED)) {

regerror(ret, &reg, 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(&reg, sptr, 1, &pmatch, 0)) {

regerror(ret, &reg, 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(&reg);

exit(0);

}

标签: #ubuntu报错缓冲区溢出