本文是 你必须知道的495个C语言问题 系列的第五篇,主要还是来了解下字符和字符串相关的东西,同时也会涉及内存相关的知识。
一般来说,字符串以及字符的处理都是编程语言中的常客,在C语言中,我们被给予了很好的灵活性,但是同时字符也成了最诡秘的处理单元。
在指针的内容里,我基本上讲解了字符串和字符数组的差异,所以这里就不再赘述了。
我们知道字符的英语翻译是character,这也是为什么字符类型为char的原因,我们使用单引号‘‘来标示一个字符而使用双引号“”来标示字符串。实际上,在C语言中没有内建的字符串类型,所以传统上使用以‘\0‘结束的字符数组来表示字符串,或者以字符串常量的形式出现。而再退一步讲,C语言也没有真正的字符类型,字符是用它在机器字符集中的整数值来表示的,
字符操作
标准库提供了两组函数,用于操作单独的字符,原形位于ctype.h中,第一组用于字符分类,第二组用于转换字符。
字符分类
以is开头,用于判断该字符属于何种类型:是则返回真
iscntrl //任何控制字符 isspace // 空白字符 :‘ ‘ 换页‘\f‘换行‘\n‘回车‘\r‘制表符‘\t‘或者垂直制表符‘\v‘ isdigit //10十进制任何数字 isxdigit // 16进制任何数字 islower // 小写字母 isupper // 大写字母 isalpha // 所有字母 isalnum // 所有字母和数字 isputct // 标点符号 isgraph // 图形符号 isprint // 任何可以打印的字符
字符转换
int tolower(int ch);//转变为小写字母 int toupper(int ch);//转变为大写字母
字符串操作
为了在C语言中操作字符串,标准库在头文件string.h中声明了一些函数。比如字符串的拼接,拷贝,查找,等。比如总有面试会考察strcpy的写法,这的确是很讨厌的一件事。既然C风格的字符串是一个由字符组成的char类型的数组,那么,我们就可以通过指针以及数组下标来操作字符串中一个个的字符。
字符串长度
可能你觉得字符串长度计算不值得一提,strlen是你写过的最简单的字符串处理函数
如果你觉得以下写法是你的作品,请注意下:
int mystrlen(char *str) { int lenght =0; while(*str++!=‘\0‘) lenght++; return lenght; }
乍一看,程序运行正确,也没有出现bug,但是,如果总分是100,这个函数只能得50分。老手就不要看了,心知肚明,新手可以从以下方面更正下:
- 程序的返回值类型
- 程序的入参类型限定
- 入参非空判断
- 不要试图写一个mystrlen而是直接调用标准strlen函数
可以做如下优化:
#include<stddef.h> size_t myStrlen(char const*str) { int lenght =0; while(*str++!=‘\0‘) lenght++; return lenght; }
备注,size_t 与环境相关,一般为unsigned int类型,入参数为NULL未作检查,是为了说明,此类函数应该在调用前,就让调用者显示判断入参不为空。
字符串拷贝
char *strcpy(char *dst ,char const * src); char *strncpy(char *dst,char const * src ,size_t len);
很清楚,strcpy将src字符串内容拷贝到dst处,而strncpy则只拷贝len个字节,二者有以下注意点:
- 调用者必须保证dst内存区间大小是合理的,以避免拷贝src时溢出,strcpy本身并不作检查。
- 正常调用后,strcpy能在拷贝最后自动加上‘\0‘,而不需要手动添加,但是strncpy则不然,如果,src的长度比参数len长,需要调用者手动添加‘\0‘。这一点需要注意。
字符串的拼接
char *strcat(char *dst,char const *src); char *strncat(char *dst,char const *src,size_t len);
strcat 将src的内容拼接到dst的后面,是一个 字符串的连接动作,而strncat则是向dst连接src的前len个字符。但是与strncpy不同的是,它会保证在完成后向新的字符串末尾添加‘\0‘.
字符串拷贝和拼接函数的返回值 都是char* ,目的是为了嵌套调用这些函数。而实际上,返回值经常性被忽略。
字符串的比较
int strcmp(cha const *s1,char const* s2); int strncmp(char const *s1,char const* s2,size_t len);
两个字符串的比较完全一致则 返回0,前者大于后者 返回一个大于零的值,否则返回一个小于零的值。
strcmp比较全部,而strncmp比较二者的前len个字符。
字符串查找
- 查找一个字符,
char *strchr(char const *src,int ch); char *strrchr(char const *src,int ch);
strchr从src找到ch字符第一次出现的位置,返回的就是该指针
而strrchr则是反向查找,或者说是最后一次出现的位置。
- 查找任何几个字符
char *strpbrk(char const *str,char const* group);
该函数查找group中的任何一个在str中第一次出现的位置、
- 查找一个子串
char *strstr(char const * s1,char const*s2);
这个函数在s1中查找整个s2第一次出现的起始位置,并返回一个指向该位置的指针。
备注:标准库中 并没有提供strrpbrk和strrstr的原形,但是,我们可以自己实现一个,
- 查找一个字符串的前缀
size_t strspn(char const* str,char const*group); size_t strcspn(char const* str,char const*group);
strspn返回str起始匹配group中任意字符的字符数,数量之和
而strcspn则是 与group中任意字符都不同的字符数量之和,有求补的意思。
直接看代码,更好理解:
#include<string.h> char* str ="123123,hello world"; printf("%d\n",(int)strspn(str,"123,")); printf("%d\n",(int)strcspn(str," "));
输出结果
7
12
strspn查找前面所有123和,的数量之和,为7;而strcspn查找前缀中不为空格的字符的个数,为12,如此,这两个函数的用法,你应该清楚了吧
- 查找标记
一个字符串常常包含几个单独的部分,它们彼此被分隔开来,每次为了处理各个部分,你必须将它们从字符串中抽取出来,这个任务是strtok所实现的功能。
char *strtok(char *str,char const* sep);
sep参数是一个字符串,定义了用作分隔符的字符集合。第一个参数时一个字符串,它包含零个或者多个由sep字符分隔的标记,strtok找到str的下一个标记,并将其用NUL结尾,然后返回一个指向这个标记的指针。警告:strtok会改变它所查找的字符串,所以你懂的怎么做的。
如果strtok函数的第一个参数不是NULL,函数将找到字符串的第一个标记,strtok同时将保存它在字符串中的位置,如果strtok函数的第一个参数是NULL,函数就在同一个字符串中从这个位置开始像前面一样查找下一个标记,如果字符串中不再有更多这样的标记则返回一个NULL指针,在典型情况下,在第一次调用strtok时,向它传递一个指向字符串的指针。然后这个函数被重复调用(第一个参数为NULL),直到它返回一个NULL为止。
记住:strtok有“记忆”功能,所以不要试图同时解析两个字符串。同理,因为strtok会改变传入的字符串本身,所以我们不应该传入字符串常量,所以请复制一份来使用。
看代码:
char str[]="123,45,67;6,23;2;31";//这里换成char *str就会崩溃 static char*sep =",;"; char* token = NULL; for(token = strtok(str,sep); token != NULL; token = strtok(NULL,sep)) printf("%s ",token);
输出结果如你所想;
内存操作
我们知道,C字符串必须以‘\0‘结尾,所以非字符串处理时,含有零值得情况并不罕见,所以我们需要直接操作内存来保证‘\0‘也得到处理。
void* memcpy(void *dst,void const*src,size_t lenght); void* memmove(void *dst,void const*src,size_t lenght); void* memcmp(void const*a,void const*b,size_t lenght); void* memchr(void const*a,int ch,size_t lenght); void* memset(void* a ,int ch,size_t lenght);
因为 内存操作并没有结束标志,所以必须在最后一个参数中注明长度(以字节为单位)。这些函数的含义与字符串相同后缀的函数一样,所以不再赘述。