字符串、字符和字节
字符串基础
C语言中没有显示的字符串数据类型,字符串以字符串常量或者字符数组的形式出现,字符串常量适用于那些程序不会对它们进行修改的字符串。所有其它字符串都必须存储于字符数组或动态分配的内存中。
字符串是一串零个或多个字符,并且以一个位模式为全0的NULL字节结尾。
- 字符串所包含的的字符内部不能出现NULL字节。
- NULL字节是字符串的终结符,但它并不是字符串的一部分,所以字符串的长度并不包含NULL字节。
- 字符串函数的声明都在
string.h
头文件中。
字符串的长度
字符串的长度就是它所包含的字符个数,通常使用函数strlen
来计算字符串的长度。
sizet_t strlen(char const *string);
size_t
定义在 stddef.h
中,它是一个无符号整数类型。
在表达式中使用无符号数可能导致不必要的结果,如下:
if(strlen(x) >= strlen(y))...
if(strlen(x) - strlen(y) >= 0)...
第二条语句的结果永远为真,因为strlen(x)、strlen(y)的返回值都是无符号数,所以>=左边是无符号数,无符号数不可能小于零。
不受限制的字符串函数
最常用的字符串函数都是不受限制的,就是说它们只是通过寻找字符串参数结尾的NULL来判断它的长度。在使用这些函数时,必须要保证结果字符串不会溢出内存。
复制字符串
char *strcpy(char *dst,char *src);
-
函数strcpy把参数src字符串复制到dst。
-
如果src和dest的内存空间重合,其结果是未定义的。
-
由于dest参数将进行修改所以它必须是一个字符数组或者指向一个动态分配内存的指针,不能是字符串常量。
char *s1 = "hello.world"; strcpy(s1,"hi"); // 错误,目的字符串不是字符数组也不是动态分配的内存
-
目标参数以前的内容将会被覆盖并丢失,即使新的字符串比dst原先的内存更短,因为新的字符串是以NULL结尾,所以原字符串最后剩余的几个字符也会被有效删除。
char s[] = "Original message"; strcpy(s,"Different"); // s 将变成"Different"
char s[] = "Original message";
strcpy(s,"A different message");
因为src字符串比dst字符串占用的内存空间大,程序将会发生错误。
## 连接字符串
char *strcat(char *des,char *src);
- strcat函数要求des参数已经包含一个字符串(可以是空字符串)。
- 函数strcat找到字符串des的末尾,并把src字符串拷贝到这个位置。
- 如果src和dst的位置发生重叠,结果是未定义的。
## 函数的返回值
strcpy和strcat都**返回它们的第一个参数的一份拷贝**,就是一个指向目标字符数组的指针,由于它们返回这种类型的值,所以可以嵌套的调用这些函数,例:
strcat(strcpy(dst, a), b);
## 字符串比较
int strcmp(const char* s1, const char* s2);
srtcmp函数是从两个字符串的首字符开始一个一个比较两个字符串的字符,如果碰见两个字符不匹配,不匹配中的字符中 比较“小”的那个字符所在的字符串小于另一个字符串:
- 如果s1小于s2,函数返回一个小于0的值。
- 如果s1大于s2,函数返回一个大于0的值。
- 如果两个字符串相等函数就返回0。
# 长度受限的字符串函数
标准库还定义了一些函数以不同的方式处理字符串,这些函数接受一个显示的长度参数,用于限定的复制或比较的字符数,这些函数提供了一种方便的机制以防止目标数组溢出。
char* strncpy(char* dst, const char* src, size_t len);
char* strncat(char* dst, const char* src, size_t len);
char* strncmp(const char* s1, const char* s2, size_t len);
## strncpy
strncpy把源字符串的字符复制到目标数组,然而它正好向dst写入len个字符:
- 如果src字符串的字符数量小于len个,那么dst数组就用额外的NULL字节填充到len长度
- 如果大于或者等于len,那么就只有len个字符被复制到dst中,注意,**它的结果将不会以NULL字节结尾。**
**注意:**
如果在一个需要字符串的地方如strlen函数的参数,使用了一个不是以NULL结尾的字符串,因为strlen无法找到NULL字节,那么它将继续查找,一个字符接一个字符,直到找到NULL为止,由此strlen返回的将会是一个随机数,如果函数试图访问程序以外的内存范围,程序就会崩溃。
## strncat
strncat函数从src中复制最多len个字符到目标数组后面,但是strncat会在结果字符串后面添加一个NULL字节,而且它不会对目标数组用NULL进行填充。
## strncmp
strncmp最多比较len个字节,如果两个字符串的前len个字符串相等,就返回0,如果在len个字节之前有不相等的,函数就像strcmp一样停止比较并返回结果。
# 字符串查找基础
## 查找一个字符
在一个字符串中查找一个字符最容易的方法就是strchr和strrchr函数:
char* strchr(const char* str, int ch);
char* strrchr(const char* str, int ch);
strchr函数在字符串str中查找到字符ch第一次出现的位置,找到后函数返回一个指向该指针位置的指针,如果该字符并不存在于字符串中,则返回一个NULL指针。
strrchr的功能和strchr基本一致,只是它返回的是字符ch最后一次在该字符串中出现的位置。
## 查找任何几个字符
strpbrk函数查找任何一组字符第一次在字符串中出现的位置:
char* strpbrk(const char* str, const char* group);
这个函数返回一个指向str中第一个匹配group中任何一个字符的字符位置,如果未找到匹配即返回NULL。
char s[] = "hello,world";
char *ans = strpbrk(s, "aeiou"); // ello,world
## 查找一个子串
在字符串中查找一个字串:
char* strstr(const char* s1, const char* s2);
这个函数在s1中查找整个s2第一次出现的起始位置并返回一个指向该位置的指针。
- 如果s2并没有完整的出现在s1的任何地方,函数将返回一个NULL指针。
- 如果s2是一个空字符串函数就返回s1。
# 高级字符串查找
## 查找一个字符串前缀
strspn和strcspn函数用于在函数的起始位置对字符计数:
size_t strspn(const char* str, const chat* group);
size_t strcspn(const char* str, const char* group);
group字符串指定一个或多个字符。
strspn函数返回str起始部分匹配group中任意字符的字符数。
char str[] = "1234k56";
char accept[] = "1234567890";
int i = strspn(str, accept); //4
strcspn函数对str字符串起始部分中不与group中任何字符匹配的字符进行计数。
char str[] = "abcd1234k56";
char accept[] = "1234567890";
int j = strcspn(str, accept); // 4
## 查找标记
strtok函数从字符串中隔离各个单独的称为标记的部分,并丢弃分隔符:
char* strtok(char* str, const char* sep);
第一个参数指定一个字符串,它包含0个或多个由sep字符串中一个或者多个分隔符分隔的标记。
sep参数是个字符串,定义了用作分隔符的字符集合。
- strtok找到str的下一个标记并将其用NULL结尾然后返回一个指向这个标记的指针。
- strtok函数执行时会修改它所处理的字符串,如果原字符串不能被修改,那么就需要先复制一份,然后将副本传递给strtok。
- 如果strtok的第一个参数不是NULL,函数将找到字符串中的第一个标记,同时保存其在字符串中的位置。
- 如果strtok的第一个参数是NULL,函数就在同一个字符串中从被保存的位置开始继续查找下一个标记。
- 如果字符串中不存在标记,strtok函数将会返回NULL。
一般使用的模式为:第一次调用strtok函数时,向它传递一个指向字符串的指针,然后这个函数被反复调用(第一个参数设置为NULL),直到它返回NULL:
char sep = ",?!";
char s[] = "hello!where,are,you?";
char token;
for (token = strtok(s, sep); token != NULL; token = strtok(NULL, sep))
{
printf("%s\n",token);
}
输出:
![](https://img2018.cnblogs.com/blog/819930/201909/819930-20190915103117341-1028458737.png)
一个指向用于描述错误的字符串的指针,函数原型如下:
char* strerror( int error_number );
FILE *fp = fopen("1.txt", "r");
if (fp == NULL)
{
printf("%s\n",strerror(errno));
}
# 字符操作
标准库包含了两组函数用于操作单独的字符,它们的原型位于头文件`ctype.h`,第一组函数用于对字符进行分类,第二组函数用于转换字符。
## 字符分类
| 函数 | 如果参数符合下列条件就返回非0,否则返回0 |
| -------- | ------------------------------------------------------------ |
| iscntrl | 任何控制字符 |
| isspace | 空白字符:空格 ' ',换页'\f',换行'\n',回车'\r',制表符'\t',垂直制表符 '\v' |
| isdigit | 数字0-9 |
| isxdigit | 十六进制数字,数字0-9,小写字母a-f,大写字母A-F |
| islower | 小写字母a-z |
| isupper | 大写字母A-Z |
| isalpha | 字母a-z,A-Z |
| isalnum | 字母或数字,a-z,A-Z或0-9 |
| ispunct | 标点符号,任何不属于字母或数字的图形字符(可打印符号) |
| isgraph | 任何图形字符 |
| isprint | 任何可打印字符,包括图形字符或空白字符 |
## 字符转换
转化函数把大写字母转换成小写字母或小写字母转换成大写字母:
int tolower(int ch);
int toupper(int ch);
- 函数tolower返回字符ch 对应的小写形式的整型值。
- 函数toupper返回字符ch 对应的大写形式的整型值。
- ch并不是处于一个适当的大小写状态,函数将不修改参数直接返回。
# 内存操作
当字符串内部包含NULL字符,此时将不能在使用字符串操作函数,内存操作函数可以操作任意的字符串序列,包含字符串中含有NULL字符的字符串。
void *memcpy(void *dst,void const *src,size_t length);
void *memmove(void *dst,void const *src,size_t length);
void *memcmp(void const *a,void const *b,size_t length);
void *memchr(void const *a,int ch ,size_t length);
void *memset(void *a,int ch,size_t length );
- 以上函数与字符串函数不同之处在于它们遇到**NULL**不会停止操作。
- memmove函数的src和dst的内存空间可以重合。
- memcmp函数对两端内存的内容进行比较,这些值将按照无符号字符诸字节比较,若用该函数比较非单字节的数据如整数或浮点数将出现不可预料的结果。
- memchr函数从a的起始位置开始查找字符ch第一次出现的位置,最多查找length个字节,如果在这length个字节中未找到,函数返回一个NULL指针。
- memset函数把a开始length个字节全部设置为字符值ch,如把buffer的前SIZE个字节都初始化为0。