【C语言】字符串:一串数据类型为字符(char)的数组(字符数组),结束符是'\0'。
一个字符占一个字节的内存,结束符'\0'也占一个字节的内存。
结束符 '\0' :缩写NUL,空字符。表示字符串的结束。
NULL:空指针。定义在标准库中的值为零的常量。
字符串名就是内存地址,也是第一个字符的内存地址。也可以理解为字符串是指针,指向字符数组。
单个字符:用单引号,例如:'a', '\0'。
空字符串或多个字符组成的字符串:用双引号,例如:"good"。
操作字符串(字符数组)的标准库为string.h。
#include <string.h>
0、字符串变量
数组:char 变量名[字符数量] = 字符串; 【字符数量可省略,字符数量包括结束符'\0'】
指针:char *指针变量名;
// 字符数组
char s[6] = "hello"; // 5个字符 + 1个结束符'\0'
char s[6] = {'h','e','l','l','o','\0'}; // 单个字符,用单引号// 指针变量
char *s;
s = "hello";
1、strlen, sizeof
strlen:字符串共有多少个字符(不包括结束符'\0')。
sizeof:字符串共占多少字节内存(包括结束符'\0')。
#include <stdio.h>
#include <string.h>int main(void)
{char strings[] = "hello";int number = strlen(strings);int size = sizeof(strings);printf("strings: \"%s\" has %d elements, memory size is %d bytes",strings, number, size);return 0;
}// 结果为:
strings: "hello" has 5 elements, memory size is 6 bytes
2、memchr,strchar,strrchr 搜索字符串中的字符
memchr:void *memchr(const void *str, int c, size_t n)
参数:str是指定内存区域,c为要搜索的字符(单个字符,需用单引号),n为要搜索的字节数(指定搜索的内存大小)。
返回:指针,指向第一次出现的字符c的内存地址。若没有,返回NULL。
strchar:char *strchr(const char *str, int c)
参数:str是字符串,c为要搜索的字符(单个字符,需用单引号)。
返回:指针,指向第一次出现的字符c的内存地址。若没有,返回NULL。
strrchr:char *strrchr(const char *str, int c)
参数:str是字符串,c为要搜索的字符(单个字符,需用单引号)。
返回:指针,指向最后一次出现的字符c的内存地址。若没有,返回NULL。
memchr, strchar,strrchr基本用法相同:
1、都可以在字符串中搜索字符。
2、都返回指针,指向搜索到的字符的内存地址。若没有搜索到,返回NULL。
memchr, strchar,strrchr的区别是:
1、memchr有3个参数:指定内存区域,搜索的字符,搜索的字节数。
strchar和strrchr只有2个参数:字符串,搜索的字符。
2、memchr和strchr返回的指针都指向第一次出现字符c的内存地址。
strrchr返回的指针是指向最后一次出现字符c的内存地址。
3、memchr指定搜索的内存大小。
strchar和strrchr都是字符串结束(结束符'\0')就停止搜索。
4、memchr可以搜索任意内存区域的字节。
strchar和strrchr专门搜索字符串中的字符。
// 以 memchr 为例
#include <stdio.h>
#include <string.h>int main(void)
{const char strings[] = "hello";char *s;s = (char *)memchr(strings, 'l', strlen(strings));printf("strings=\"%s\", the first \"l\" location is %d\n",strings, s-strings+1);printf("strings=\"%s\", from the first \"l\" to the end are \"%s\"",strings, s);return 0;
}// 结果为:
strings="hello", the first "l" location is 3
strings="hello", from the first "l" to the end are "llo"
补充:strstr (字符串中查找子字符串)
strstr:char *strstr(const char *haystack, const char *needle)
在字符串haystack查找子字符串needle。
返回:若找到,返回指针,指向haystack中第一次出现needle的内存地址。若没有,返回NULL。
#include <stdio.h>
#include <string.h>int main(void)
{char strings[] = "hello world wow";char *s;s = strstr(strings, "world"); // 找到子字符串printf("s point to \"%s\"\n",s); return 0;
}// 结果为:
s point to "world wow"#include <stdio.h>
#include <string.h>int main(void)
{char strings[] = "hello world wow";char *s;s = strstr(strings, "good"); // 没有找到子字符串printf("s point to \"%s\"\n",s); return 0;
}
// 结果为:
s point to "(null)"
3、memcpy,memmove,strcpy,strncpy 复制字符串
memcpy:void *memcpy(void *dest, const void *src, size_t n)
参数:dest是存储复制内容的内存区域(目标字符串),src是要复制的数据源(内存区域),n是复制的字节数。
返回:指针,指向目标字符串。
注意:
memcpy是内存复制,可复制任意类型的数据源。
memcpy指定复制内存大小。不会自动添加结束符'\0'。注意目标字符串中是否有结束符。若没有,需一个字节的内存空间手动添加结束符'\0'。
若目标字符串的内存不够大,可能缓冲区溢出。注意边界检查。
memmove: void *memmove(void *dest, const void *src, size_t n)
参数:dest是存储复制内容的内存区域(目标字符串),src是要复制的字符串,n是要复制的字节数。
返回:指针,指向目标字符串。
注意:
没有重叠的内存区域时,与memcpy功能相同。
有重叠的内存区域时,复制前会将重叠部分拷贝到目标区域,复制后,源区域的内容会被修改。
// 以 memcpy 为例
#include <stdio.h>
#include <string.h>int main(void)
{char s[] = "hello"; // 数据源“hello”(5个字符+1个结束符'\0')char strings[8];memcpy(strings,s,strlen(s)+1); // 复制字符串(包括结束符'\0')printf("strings = \"%s\",strings[5] = %p\n",strings, strings[5]); return 0;
}// 结果为:
strings = "hello",strings[5] = 0000000000000000
strcpy:char *strcpy(char *dest, const char *src)
参数:dest是存储复制内容的内存区域(目标字符串),src是要复制的字符串。
返回:指针,指向目标字符串。
注意:
strcpy专门复制字符串,包括结束符'\0'。依赖结束符'\0'来确定复制的终点,注意要复制的字符串需有正确的结束符。
若没有正确的结束符或目标字符串的内存不够,可能缓冲区溢出。注意边界检查。
#include <stdio.h>
#include <string.h>int main(void)
{char strings[6]; // char strings[5]; // warning: '__builtin_memcpy' writing 6 bytes into a region of size 5 overflows the destination [-Wstringop-overflow=]strcpy(strings,"hello"); // 复制字符串printf("strings = \"%s\"",strings); return 0;
}// 结果为:
strings = "hello"
strncpy:char *strncpy(char *dest, const char *src, size_t n)
参数:dest是存储复制内容的内存区域(目标字符串),src是要复制的字符串,n是复制的字节数。
返回:指针,指向目标字符串。
注意:
strncpy指定复制内存大小。
要复制的字符串若小于指定复制的内存大小,则自动填充结束符'\0'。
要复制的字符串若大于等于指定复制的内存大小,则可能需要一个字节的内存空间手动添加结束符'\0',若复制的内容中有结束符,则无需手动添加。
若目标字符串的内存不够大,可能缓冲区溢出。注意边界检查。
#include <stdio.h>
#include <string.h>int main(void)
{char strings[8];strncpy(strings,"hello",8); // strings 内存存储为:h e l l o \0 \0 \0printf("strings = \"%s\"",strings); return 0;
}// 结果为:
strings = "hello"
#include <stdio.h>
#include <string.h>int main(void)
{char strings[6];strncpy(strings,"hello",6); // 无需手动添加结束符,复制内容中已包含printf("strings = \"%s\"",strings); return 0;
}// 结果为:
strings = "hello"
#include <stdio.h>
#include <string.h>int main(void)
{char strings[6];strncpy(strings,"helloworld",5); // 复制原字符串的一部分内容(不包括结束符)strings[5] = '\0'; // 手动添加结束符printf("strings = \"%s\"",strings); return 0;
}// 结果为:
strings = "hello"
memcpy | strcpy | strncpy |
---|---|---|
3个参数:目标字符串,要复制的数据源,复制内存大小 | 2个参数:目标字符串,要复制的字符串 | 3个参数:目标字符串,要复制的字符串,复制内存大小 |
复制内存区域,可复制任意类型的数据源(包括字符串) | 专门复制字符串 | 专门复制字符串 |
指定复制的内存大小 | 从要复制的字符串开始到结束符(包括结束符\0) | 指定复制的内存大小 |
目标字符串中可能手动添加结束符'\0' | 注意要复制的字符串中需有正确的结束符'\0' | 目标字符串中可能手动添加结束符'\0' |
确保目标字符串的内存足够大 | 确保目标字符串的内存足够大 | 确保目标字符串的内存足够大 |
补充:memset (将某内存区域设置为指定值)
memset:void *memset(void *str, int c, size_t n)
参数:str是存储填充内容的内存区域(目标字符串),c是指定要填充的内容(单个字符,需单引号),n是填充的字节数。
返回:指针,指向目标字符串。
注意:
memset是将指定字符填充到指定内存区域的前n个字节。
用于快速初始化大块内存,或清空内存,或为内存赋值。
memset不做边界检查,需确保目标字符串的内存区域足够大。
#include <stdio.h>
#include <string.h>int main(void)
{char strings[8] = "hihello";printf("Before: strings = %s\n", strings);memset(strings, '#', 2); // 将字符数组前2个字符设为'#'printf("After: strings = %s\n", strings);memset(strings, '\0', sizeof(strings)); // 将字符数组清零,使用'\0'代替0printf("End: strings = %s\n", strings);return 0;
}// 结果为:
Before: strings = hihello
After: strings = ##hello
End: strings =
4、strcat,strncat 字符串结尾追加内容
注意:确保目标字符串的内存区域必须足够大,能够容纳追加的内容。
从目标字符串的结束符'\0'开始追加,'\0'被第一个追加的字符替换,追加完会加上结束符'\0'。
strcat:char *strcat(char *dest, const char *src)
参数:dest是目标字符串,src是要追加的内容。
返回:指针,指向目标字符串。
#include <stdio.h>
#include <string.h>int main(void)
{char strings[16] = "hello";printf("Before: strings = %s\n", strings);strcat(strings, "world"); // 追加内容printf("After: strings = %s\n", strings);return 0;
}// 结果为:
Before: strings = hello
After: strings = helloworld
strncat:char *strncat(char *dest, const char *src, size_t n)
参数:dest是目标字符串,src是要追加的内容,n是追加的字节数。
返回:指针,指向目标字符串。
#include <stdio.h>
#include <string.h>int main(void)
{char strings[16] = "hello";printf("Before: strings = %s\n", strings);strncat(strings, "world", sizeof("world")); // 追加内容(指定字节数)printf("After: strings = %s\n", strings);return 0;
}// 结果为:
Before: strings = hello
After: strings = helloworld
5、memcmp,strcmp,strncmp 比较字符串
memcmp: int memcmp(const void *str1, const void *str2, size_t n)
将两个内存区域str1和str2的前n个字节数进行比较。
返回:若str1>str2,返回值>0;若str1=str2,返回值=0;若str1<str2,返回值<0。
strcmp:int strcmp(const char *str1, const char *str2)
将两个字符串str1和str2进行比较。
返回:若str1>str2,返回值>0;若str1=str2,返回值=0;若str1<str2,返回值<0。
strncmp:int strncmp(const char *str1, const char *str2, size_t n)
将两个字符串str1和str2的前n个字节数进行比较。
返回:若str1>str2,返回值>0;若str1=str2,返回值=0;若str1<str2,返回值<0。
memcmp | strcmp | strncmp |
---|---|---|
比较两个内存区域(可以字符串,但通常用于比较结构体或数组等) | 比较两个字符串,整个字符串比较 | 比较两个字符串,指定比较长度 |
指定比较的字节数 | / | 指定比较的字节数 |
碰到不同的字符或比较完限定的字节数就停止 | 碰到不同的字符或结束符'\0'就停止 | 碰到不同的字符或结束符'\0'或比较完限定的字节数就停止 |
// 以 strcmp 为例
#include <stdio.h>
#include <string.h>int main(void)
{char s[] = "Hello";if(strcmp(s, "hello") == 0) // 比较字符串{printf("the same string");}else{printf("\"%s\" and \"hello\" are different", s);}return 0;
}// 结果为:
"Hello" and "hello" are different
6、匹配字符串中的字符是否在另一个字符串中出现
strspn(str1,str2):str1中第一个不在str2的字符下标。
strcspn(str1,str2):str1中从开头连续多少字符不在str2中。
strpbrk(str1,str2):str1中第一个在str2中的字符,返回指针,指向str1中匹配到的字符。
strspn:size_t strspn ( const char *str1, const char *str2) |
strcspn:size_t strcspn ( const char *str1, const char *str2) |
strpbrk:char *strpbrk ( const char *str1, const char *str2) |
#include <stdio.h>
#include <string.h>int main(void)
{char s1[] = "hellogoodworld";char s2[] = "good";int j = strspn(s1,s2); // s1中第一个不在s2中的字符下标printf("\"%s\" , the first char not in \"%s\" is index %d\n", s1, s2, j);int k = strcspn(s1,s2); // s1中从开头连续多少字符不在s2中printf("\"%s\" , %d char not in \"%s\"\n", s1, k, s2);char *x = strpbrk(s1,s2); // s1中第一个在s2中的字符printf("\"%s\" , the first char in \"%s\" is %c\n", s1, s2, *x);return 0;
}// 结果为:
"hellogoodworld" , the first char not in "good" is index 0
"hellogoodworld" , 4 char not in "good"
"hellogoodworld" , the first char in "good" is o
7、与LC_COLLATE设置相关
strxfrm(目标字符串, 源字符串, 转换的字节数n):将源字符串根据当前LC_COLLATE转换n个字节,再放置到目标字符串指向的内存区域中。返回被转换的字符串的长度(不包括结束符)。
strxfrm 转换字符串格式,便于后续字符串比较。需确保有足够的内存空间存放转换后的字符串。
strcoll(str1,str2):比较两个字符串。返回int类型的值(str1>str2,返回值>0;str1=str2,返回值=0;str1<str2,返回值<0)。
strcoll 根据LC_COLLATE环境变量设置的字符排列次序来比较,若LC_COLLATE设置为"POSIX"或者"C",strcoll 函数与strcmp 函数完全相同。
strxfrm:转换字符串格式。size_t strxfrm ( char *dest, const char *src, size_t n) |
strcoll:比较字符串。int strcoll ( const char *str1, const char *str2) |
通过 标准库locale.h 中的 setlocale 来设置 LC_COLLATE 。
获取或设置地域化信息:char *setlocale(int category, const char *locale)
8、strerror(错误号):通过错误号返回对应的错误信息。
- 当使用系统调用或其它库函数时,如果发生错误,通常由整数变量errno(标准库errno.h中通过系统调用设置的宏) 记录错误号。
- 可使用strerror将错误号转换为对应的错误信息字符串。
- char *strerror(int errnum) 【参数:错误号。返回指针,指向对应的错误信息字符串】
- 具体错误信息取决于开发平台和编译器。
- strerror不应在多线程环境中使用。
9、strtok 将字符串分割
strtok:char *strtok(char *str, const char *delim)
参数:str是要分割的字符串,delim是分割符。
返回:指针,指向被分割的第一个子字符串。若没有可检索的字符串,返回空指针。
获取下一个子字符串:继续调用strtok函数。strtok(NULL,分割符);
注意:strtok函数会改变被操作的字符串,最好拷贝。
#include <stdio.h>
#include <string.h>int main(void)
{char s[] = "hello world wow";printf("Before: s = \"%s\"\n",s);char *x;x = strtok(s, " "); // 使用空格" "来分割字符串while(x != NULL){printf("%s\n",x);x = strtok(NULL, " "); // 获取下一个子字符串}printf("After: s = \"%s\"\n",s);return 0;
}// 结果为:
Before: s = "hello world wow"
hello
world
wow
After: s = "hello"
补充:
1、单引号、双引号
单引号:只能一个字符(占用一个字节)。不能用于空字符串和多个字符组成的字符串。
双引号:0到多个字符组成的字符串,可用于空字符串。若双引号中只有一个字符,则占用两个字节(一个字符和结束符'\0')。
例如:
char ch = 'A'; // 一个字符'A', 占用一个字节char s[] = "A"; // 一个字符"A"和结束符'\0', 占用两个字节
char s[] = "hello"; // 五个字符"hello"和结束符'\0', 占用六个字节
char s[] = ""; // 空字符串, 空字符(结束符)'\0',占用一个字节
输出单引号和双引号:使用转义字符 \' 和 \" 。
#include <stdio.h>int main(void)
{printf("\"hello\",the first char is \'h\'"); return 0;
}// 结果为:
"hello",the first char is 'h'
2、const、define
const:定义常量,告诉编译器某变量的值在初始化后不能修改。
可以修饰变量、数组、指针、函数参数、函数返回值等。
const int a = 10; // 变量a的值不能被修改const char s[] = "hello"; // 数组只读,不能被修改const int *ptr = &a; // 常量指针,指向a的内存地址。指针可修改,指针指向的内存地址的内容不能修改int *const ptr = &a; // 指针常量,指向a的内存地址。指针不可修改,指针指向的内存地址的内容可修改。int fun1(const int m) // 函数中传入的参数m不能被修改
{...
}const fun1(void) // 函数的返回值不能被修改
{return "hello";
}
define:预处理指令。#define 又称宏定义。定义一个标识符来表示常量。在预处理阶段就将标识符出现的地方文本替换为常量值。
#define 常量名(大写字母) 常量值
#include <stdio.h>
#define NUMBER 10 // 定义常量NUMBERint main(void)
{const int k = 8; // 定义常量k// k = 5; // 常量不能修改。error: assignment of read-only variable 'k'printf("[ const ] k = %d\n", k);// NUMBER = 2; // 预处理阶段已经文本替换。不能作为变量修改。error: lvalue required as left operand of assignmentprintf("[ #define ] NUMBER = %d\n", NUMBER);return 0;
}// 结果为:
[ const ] k = 8
[ #define ] NUMBER = 10
const和define的区别:
1、const在编译和运行时都有效。define只在预处理阶段有效。
2、const占用存储空间。define不占用存储空间。
3、const有明确的类型信息,需要类型检测。define是宏定义,没有类型,只是文本替换。
4、const可在调试时查看和修改。define在预处理阶段已通过文本替换的方式插入常量的值,在调试时很难追踪。
5、const遵循作用域,可以是局部也可以是全局。define默认是全局。
6、const定义后不能取消,可通过extern在其它地方重新定义。define可以通过#undef预处理器指令取消之前的宏定义。
3、常量指针、指针常量
常量指针:指针指向的内存地址中的内容不能修改,但指针指向的内存地址可以改变。
常量指针:指向可修改,内容不能改
const 基类型 *常量指针名; // 或者 基类型 const *常量指针名;
常量指针名 = 内存地址; // 修改指针的指向
#include <stdio.h>int main(void)
{int x = 10;int y = 8;const int *n = &x; // 常量指针,初始化指向x的内存地址printf("Before: n point to %p, content is %d\n", n, *n);n = &y; // 修改指针的指向,指向y的内存地址// *n = 5; // 不能修改指向的内存地址中的内容。报错:error: assignment of read-only location '*n'printf("After: n point to %p, content is %d\n", n, *n);y = 6; // 通过引用修改指向的内存地址中的内容。指针指向y的内存地址,不能通过*n修改值,但可修改y的值printf("End: n point to %p, content is %d\n", n, *n);return 0;
}// 结果为:
Before: n point to 000000000061FE14, content is 10
After: n point to 000000000061FE10, content is 8
End: n point to 000000000061FE10, content is 6
指针常量:指针指向的内存地址不能修改,但指向的内存地址的内容可以改变。
指针常量必须初始化,一旦初始化,指针的指向便不能修改。
指针常量:指向初始化后不能改,内容可修改
基类型 *const 指针常量名 = 初始化值; // 指针常量必须初始化
*指针常量名 = 内存地址中的内容; // 修改指向的内存地址的内容
#include <stdio.h>int main(void)
{int x = 10;int y = 8;int *const n = &x; // 指针常量,必须初始化,一旦初始化,不能修改指针的指向printf("Before: n point to %p, content is %d\n", n, *n);// n = &y; // 不能修改指针的指向。报错:error: assignment of read-only variable 'n'*n = 5; // 修改指向的内存地址中的内容printf("After: n point to %p, content is %d\n", n, *n);return 0;
}// 结果为:
Before: n point to 000000000061FE0C, content is 10
After: n point to 000000000061FE0C, content is 5