电子说
本文为程序设计基础,本文为1.8.2 字符串常量第三点:字符串函数。
1. 字符串函数
标准C提供了一个操作字符串的接口string.h,其中的很多函数是有特殊用途的,下面将详细介绍通过string.h导出的最重要的函数原型。
strlen()函数用于统计字符串的长度,其函数原型如下:
size_t strlen(const char *s);
// 前置条件:s是一个以null结尾的字符串
// 后置条件:返回值为s中的字符个数(不包括null字符)
下面的函数可以缩短字符串的长度,其中用到了strlen()。即:
1 void fit(char *string, unsigned int size)
2 {
3 if(strlen(string) > size)
4 strlen[size] = '\0';
5 }
由于该函数要改变字符串,因此函数在声明形参string时没有使用const限定符。当然,也可以调用strlen(s)确定字符串s的长度,或用if语句比较两个字符串是否相等。即:
if(strcmp(s1, s2) == 0) …
用于拼接字符串的strcat()函数接受两个字符串作为参数,其函数原型如下:
char *strcat(char *s1, char const *s2);
// 前置条件:s2是一个以null结尾的字符串,s1数组的末尾还有足够的空间容纳s2的一个副本
// 后置条件:s2被连接到s1中,且返回值是一个指针,该指针指向s1数组的第一个字符
该函数将第2个字符串的备份附加在第1个字符串末尾,s2字符串的第1个字符将覆盖s1字符串末尾的空字符,并将拼接后的新字符串作为第1个字符串返回,第2个字符串不变。strcat()函数的类型是char *(即指向char的指针),strcat()函数返回第1个参数,即拼接第2个字符串后的第1个字符串的地址。
由于strcat()函数无法检查第1个数组是否能够容纳第2个字符串,如果分配给第1个数组的空间不够大,那么多出来的字符溢出到相邻存储单元时就会出问题,当然也可以用strlen()查看第1个数组的长度。注意,要给拼接后的字符串长度加1才够空间存放末尾的空字符。或者用strncat(),该函数的第3个参数指定了添加的最大字符数,其函数原型如下:
char *strncat(char *s1, char const *s2, size_t n);
该函数将s2字符串中的n个字符拷贝到s1字符串末尾,s2字符串中的第1个字符将覆盖s1字符串末尾的空字符。不会拷贝s2字符串中的空字符和其后的字符,并在拷贝字符的末尾添加一个空字符,该函数返回s1。
strcmp()函数要比较的是字符串的内容,不是字符串的地址,其函数原型如下:
int strcmp(char const *s1, char const *s2);
// 前置条件:s1和s2都是以null结尾的字符串
// 后置条件:返回值为0,表示s1=s2;
// 返回值小于0,表示s1在词典顺序上位于s2之前;
// 返回值大于0,表示s1在词典顺序上位于s2之后
如果s1字符串在机器排序序列中位于s2字符串的后面,该函数返回一个正数;如果两个字符串相等,则返回0;如果s1字符串在机器排序序列中位于s2字符串的前面,则返回一个负数。strcmp()函数比较的是字符串("hello")不是字符('q'),所以其参数应该是字符串。由于char的类型实际上是整数类型,因此可以使用关系运算符比较字符。如果两个字符串开始的几个字符都相同会怎样?strcmp()会依次比较每个字符,直到发现第1对不同的字符为止,然后返回相应的值。比如,"apples"和"apple"只有最后一对字符不一样("apples"的s和"apple"的空字符),由于空字符在ASCII中排第1,字符s一定在它后面,所以strcmp()返回一个正数。
最后一个例子,strcmp()比较所有的字符,不只是字母。与其说该函数按字母顺序进行比较,还不如说是按机器排序序列进行比较,即根据字符的数值(ASCII值)进行比较,在ASCII中,大写字母在小写字母前面,因此strcmp("Z", "a")返回的是负值。
在大多数情况下,strcmp()返回的具体值并不重要,只在意该值是0还是非0,即比较两个值是否相等,或按字母排序字符串,此时需要知道比较的结果是正、负或0。假设word是存储在char类型数组中的字符串,ch是char类型的变量。即:
if(strcmp(word, "hello") == 0) puts("bye")
if(ch == 'q' ) puts("bye")
尽管如此,不要使用ch或'q'作为strcmp()的参数。
strcmp()比较两个字符串中的字符,直到发现不同的字符为止,这一过程可能会持续到字符串的末尾。而strcmp()在比较两个字符串时,可以比较字符不同的地方,也可以只比较第3个参数指定的字符数。其函数原型如下:
int strncmp(char const *s1, char const *s2, size_t n);
strcpy()函数有两个属性,第一,strcpy()的返回值类型为char *,该函数返回的是第1个参数的值,即一个字符的地址。第二,第1个参数不必指向数组的开始,这个属性可用于拷贝数组的一部分。注意,strcpy()将源字符串中的空字符也拷贝在内。
如果pts1和pts2都是指向字符串的指针,那么下面语句拷贝的是字符串的地址,而不是字符串本身。即:
pts2 = pts1;
如果希望拷贝整个字符串,可以使用strcpy()函数。其函数原型如下:
char *strcpy(char *s1, char const *s2);
// 前置条件:s2是一个以null结尾的字符串,s1数组有足够的空间容纳s2的一个副本
// 后置条件:s2被复制到s1,且返回值是一个指针,该指针指向s1数组的第一个字符
该函数将s2指向的字符串(包括空字符)拷贝至s1指向的位置,返回s1。即strcpy()接受2个字符串指针作为参数,可以将指向源字符串的第2个指针声明为指针、数组名或字符串常量,而指向源字符串副本的第1个指针应指向一个数据对象,比如,数组,且对象要有足够的空间存储字符串的副本,通常将拷贝出来的字符串称为目标字符串。注意,声明数组将分配存储数据的空间,而声明指针只分配存储一个地址的空间。
strcpy()和strcat()都有同样的问题,它们不能检查目标空间是否能容纳源字符串的副本,因此拷贝字符串使用strncpy()更安全,该函数的第3个参数指明可拷贝的最大字符数。其函数原型如下:
char *strncpy(char *s1, char const *s2, size_t n);
该函数将s2指向的字符串拷贝至s1指向的位置,拷贝的字符不超过n,其返回值为s1。该函数不会拷贝空字符后面的字符,如果源字符串中的字符数少于n,则目标字符串就以拷贝的空字符结尾。如果源字符串有n个或超过n个字符,就不拷贝空字符,所以拷贝的副本中不一定有空字符。基于此,一般会将n设置比目标数组大小少1,然后将数组最后一个元素设置为空字符。
这样做的目的将确保存储的是一个字符串,如果目标空间能够容纳源字符串的副本,那么从源字符串拷贝的空字符便是该副本的结尾;如果目标空间装不下副本,则将副本最后一个元素设置为空字符。
尽管C语言允许将字符串作为一个字符数组或一个指向字符的指针,但是从更抽象的角度理解字符串将会更有意义。如果你想访问字符串中的单个字符,则需要注意它的表现形式。如果将字符串当作一个整体来看待的话,那么就可以忽略其表现的细节,而写出更容易理解的程序。比如:
typedef char *striing;
其目的是强调字符串是一个在概念上完全不同的类型,虽然string与char *类型完全相同,但它们传递的信息却是不同的。如果将一个变量定义为char *类型,其底层的表示方法是指针;如果将一个变量定义为string类型,就会将该字符串作为整体看待。这样一来,在声明函数的形参时,无论是将字符串作为数组、指针或抽象数据类型,它们都是可以互换的,其声明方法如下:
int strlen(string cStr);
int strlen(char cStr[]);
int strlen(char *cStr);
虽然标准C提供的string.h接口提供了一系列的字符串操作函数,它允许在函数调用时将字符串作为一个整体对待,但这些函数同样要求我们了解底层的表示,即函数将分配内存的任务留给了用户,特别是检测缓冲区溢出的条件。当使用这个接口时,用户要为每个字符串的存储负责。这种分配方式不仅增加了程序员的负担,也间接使得编码中的错误增多了。
使用gets()函数从标准输入读入字符串容易导致缓冲区溢出,而误用strcpy()和strcat()同样如此。因为使用某些函数可能造成攻击者用格式化字符串攻击的方法访问内存,甚至能够注入代码,所以C11版本加入了strcat_s()和strcpy_s()函数,如果发生缓冲区溢出,它们会返回错误。printf()、fprintf()和snprintf()这些函数都接受格式化字符串作为参数,避免这类攻击的一种简单方法是不要将用户提供的格式化字符串传给这些函数。
全部0条评论
快来发表一下你的评论吧 !