C_字符串

C_字符串

字符串字面值

字符串字面值是用一对双引号括起来的字符序列:

1
"To C or not to C, that's the question.\n"

字符串字面值太长而无法放置在一行内,可以用字符 \ 结尾,然后在下一行的开始处延续字符串字面值:

1
2
printf("When you come to a fork in the road, take it. \
——Yogi Berra");

字符 \ 可以把两行或者更多行连接成一行。但使用 \ 有一个缺陷:字符串字面值必须从下一行的开始位置书写,破环了程序的缩进结构

C 语言中当两个或更多个字符串字面值相邻时 (仅用空白字符分割),编译器会把它们合并成一个:

1
2
printf("When you come to a fork in the road, take it. "
   "——Yogi Berra");

字符串字面值的存储方式

C 语言是把字符串字面值作为字符数组来处理的。C 语言会为长度为 n 的字符串字面值分配长度为 n+1 的内存空间。这块内存空间将用来存储字符串字面值中的字符,以及一个用来标志字符串末尾的标记字符 (空字符 \0 )。

字符串字面量是用数组存储的,当作为参数传递的时候,编译器会把它看成是 char * 类型的指针。例如, printf scanf 的第一个参数都是 char * 类型。当调用 printf 时,会传递 “abc” 的地址。

1
printf("abc");

字符串字面量的操作

字符串字面值在C中被视为指向字符数组的指针,可以赋值给一个指向常量字符的指针(const char *),指针本身也是常量的话,可以使用 const char * const 类型。 例如,字符串字面值可以出现在赋值运算符的右边:

1
2
char *p;
p = "abc";

这个赋值操作不是复制 “abc” 中的字符,而是使 p 指向字符串的第一个字符。

C 语言允许对指针取下标,因此也可以对字符串字面值取下标:

1
2
3
4
5
6
7
char ch; 
ch = "abc"[1];

//01~15转换成16进制
char digit_to_hex_char(int digit) {
return "0123456789ABCDEF"[digit];
}

字符串字面值是不可以修改的,修改字符串字面量会导致未定义的行为:

1
2
char *p = "abc";
*p = 'A';         /*** 错误 ***/

字符串变量

c语言规定:任何一维的字符数组都可以用来存储字符串,只要保证以空字符 \0 结尾即可。处理方式:

  1. 无法确定一个字符数组是否表示字符串。
  2. 需要正确地处理空字符 \0
  3. 确定字符串的长度可以遍历字符串。

初始化字符串变量

字符串变量可以在声明的同时进行初始化:

1
char date1[8] = "June 14";

编译器会把字符串 “June 14” 中的字符复制到数组 date1 中,然后追加一个空字符。

初始化字符串变量_1

C 编译器会把”June 14”看成是数组初始化式的缩写形式。

1
char date1[8] = {'J', 'u', 'n', 'e', ' ', '1', '4', '\0'};

初始化式太短不能填满字符串变量编译器会把后面的字符都初始化为 \0

1
char date2[9] = "June 14";
初始化字符串变量_2

初始化式 (不包括空字符) 和字符数组具有相同的长度

1
char date3[7] = "June 14";
image-20240401165748691

由于没有给空字符留空间,所以编译器不会试图存储空字符。

注意:一定要确保字符数组的长度大于初始化式的长度,否则,编译器将忽略空字符,字符数组无法作为字符串使用。

字符串变量在声明时可以省略它的长度 (推荐使用),编译器会自动计算长度,为其分配对应字符个数的字符空间和一个空字符:

1
char date4[] = "June 14";
初始化字符串变量_4

字符串数组和字符串指针

以下第一个 date 是一个字符数组 ,第二个 date 是一个字符指针,都可以用作字符串。

1
2
char date[] = "June 14";
char *date = "June 14";

字符数组和字符指针的差异:

  • 声明为数组时,可以修改存储在date中的字符。声明为指针时,date 指向字符串字面值,字符串字面值是不能修改。
  • 声明为数组时,date是数组名。声明为指针时,date是指针变量,指针变量可以在程序执行期间指向其它字符串。

把字符指针作为字符串使用之前,需要把字符指针指向一个字符数组,不能使用未初始化的字符指针变量。

1
2
char str[STR_LEN + 1], *p;
p = str;

读写字符串

使用printf和puts写字符串

转换说明 %s 允许 printf 写字符串。

1
2
char str[] = "Are we having fun yet?";
printf("%s\n", str);

printf 函数会逐个写字符串中的字符,直到遇到空字符。(如果字符数组中没有空字符, printf 会一直写,直到在内存的某个位置找到空字符为止)。

转换说明符 %.ps,其中的 p 表示要显示的字符数量,也称为精度(precision)。格式化输出字符串时,显示字符串的前 p 个字符。

1
2
char str[] = "Hello, world!";
printf("%.5s\n", str); // Output: "Hello",只显示字符串的前 5 个字符

转换说明符 %ms,其中的 m 代表最大字段宽度(maximum field width)。格式化输出字符串时,该字段会被限制在最多 m 个字符的宽度内。字符串的长度超过 m,则会显示整个字符串,而不会截断。字符串长度少于 m,则会在字段内右对齐,即空白字符会在字符串左侧填充,以达到总宽度为 m。左对齐,则在 m 前面添加一个减号。

1
2
3
char str[] = "Hello";
printf("%10s\n", str); // Output: " Hello",在宽度为 10 的字段内右对齐
printf("%-10s\n", str); // Output: "Hello ",在宽度为 10 的字段内左对齐

C 函数库还提供了 puts 函数,用来输出字符串,puts 会在后面添加一个额外的换行符。如:

1
puts(str);

使用scanf和gets读字符串

转换说明 %s 允许 scanf 把字符串读入字符数组:

1
scanf("%s", str);

scanf 函数调用中,不需要在str 前添加运算符 &str 是数组名,编译器在把它传递给函数时会把它当作指针来处理。

读字符串时, scanf 会跳过前面的空白字符,然后读入字符并存储到 str 中,直到遇到空白字符为止。 scanf 始终会在字符串末尾存储一个空字符空格符制表符会使 scanf 停止读入。

为了读入一整行输入,可以使用 gets 函数。类似于 scanfgets 会把读入的字符存储到字符数组中,然后存储一个空字符。但两个函数在其它方面有很大的不同:

  • gets 不会跳过前面的空白字符 (scanf 会跳过)。

  • gets 会一直读入直到遇到换行符 \n 才停止 (scanf 会在任意空白字符处停止)。此外,gets 函数会忽略掉换行符,用空字符代替换行符

注意: scanfputs 函数都不会检查数组是否越界。使用这两类函数是不安全的。

C语言字符串库

C 语言提供了丰富的字符串函数。函数的原型都包含在 string.h 头文件中。

strcpy 函数

strcpy 函数用于将一个字符串复制到另一个字符串中,其原型如下:

1
char *strcpy(char *dest, const char *src);

strcpy 函数用于将源字符串 src 复制到目标字符串 dest 中(不会改变 src 指向的字符串),直到遇到源字符串的空字符 \0 (包括空字符 \0 也会复制)。目标字符串必须有足够的空间来容纳源字符串,否则会导致缓冲区溢出。返回指向目标字符串的指针 dest (大部分情况下忽略返回值)。这一过程不会改变 src 指向的字符串,因此将其声明为 const

strcpy 是不安全的,不会检查 dest 指向的数组是否能容纳字符串 src 中的所有字符。

strncpy 函数也是用于字符串复制,能够限制复制的字符数。原型如下:

1
char *strncpy(char *dest, const char *src, size_t n);

dest是目标字符串的指针,src是源字符串的指针,n是要复制的最大字符数。该函数会把 src 指向的字符串的前n个字符复制到dest指向的字符串中。如果源字符串的长度小于n,则会在目标字符串的剩余空间用 \0 来填充。

src 的长度大于 dest 的长度, dest 将不会以空字符结尾。更安全的做法:

1
2
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';

strlen 函数

strlen 函数用于计算字符串的长度,其原型如下:

1
size_t strlen(const char *str);

strlen 函数返回字符串 str 的长度:str 中第一个空字符之前的字符个数(不包括空字符\0)。

strcat 函数

strcat 函数用于将一个字符串追加到另一个字符串的末尾,其原型如下:

1
char *strcat(char *dest, const char *src);

dest 是目标字符串的指针,src 是要追加的源字符串的指针。函数会把 src 指向的字符串追加到 dest 指向的字符串的末尾。返回指向目标字符串的指针 dest (大部分情况下忽略返回值)。这一过程不会改变 src 指向的字符串,因此将其声明为 const

strcat 是不安全的,不会检查 dest 指向的数组是否能容纳所有的字符。

strncat函数也用于连接字符串,允许指定最大连接的字符数。其原型如下:

1
char *strncat(char *dest, const char *src, size_t n);

dest 是目标字符串的指针,src 是要连接的源字符串的指针,n 是要连接的最大字符数。strncat 函数会将 src 指向的字符串中的最多 n 个字符连接到 dest 指向的字符串的末尾,并返回 dest 的值。

strcmp 函数

strcmp函数用于比较两个字符串,其原型如下:

1
int strcmp(const char *str1, const char *str2);

其中,str1str2分别是要比较的两个字符串的指针。该函数返回值为整数,表示比较结果的大小关系:

  • 如果 str1 小于 str2 ,则返回负数;
  • 如果 str1 等于 str2 ,则返回0;
  • 如果 str1 大于 str2 ,则返回正数。

strcmp函数是按照字典顺序逐个比较两个字符串的字符,直到遇到不同的字符或者其中一个字符串的结束符\0为止。 strcmp 函数小于的判定:

  • str1str2 的前 i 个字符一致,但是 str1 的第 i + 1 个字符小于 str2 的第 i + 1个字符。(”abc” 小于 “bcd” ;”abd” 小于 “abe”)
  • str1 的所有字符与 str2 的字符一致,但是 str1str2 短。(”abc” 小于 “abcd”)

忽略大小写进行比较,可以使用strcasecmp函数(Linux/Unix系统)或者_stricmp函数(Windows系统)。

strdup 函数

strdup函数在C语言中用于复制字符串,并返回一个指向新复制的字符串的指针。它的原型如下:

1
char *strdup(const char *str);

str是要复制的字符串的指针。strdup函数会动态分配足够的内存来存储复制的字符串,并将原始字符串的内容复制到新分配的内存中,然后返回新分配内存的指针。使用完后需要手动释放内存,以避免内存泄漏。

其它函数

  • strchr:在字符串中查找指定字符的第一个匹配,并返回指向该字符的指针。
1
char *strchr(const char *str, int c);
  • strrchr:在字符串中查找指定字符的最后一个匹配,并返回指向该字符的指针。
1
char *strrchr(const char *str, int c);
  • strstr:在字符串中查找子串的第一个匹配,并返回指向该子串的指针。
1
char *strstr(const char *haystack, const char *needle);
  • strtok:将字符串分解为一系列子字符串,并返回指向下一个子字符串的指针。
1
char *strtok(char *str, const char *delim);
  • strspn:返回字符串中连续匹配给定字符集的字符的长度。
1
size_t strspn(const char *str, const char *accept);
  • strcspn:返回字符串中连续不匹配给定字符集的字符的长度。
1
size_t strcspn(const char *str, const char *reject);
  • strncpy:复制字符串的一部分到另一个字符串,可以指定要复制的最大字符数。
1
char *strncpy(char *dest, const char *src, size_t n);
  • strncasecmp:忽略大小写比较两个字符串的前n个字符。
1
int strncasecmp(const char *s1, const char *s2, size_t n);

字符串数组

存储一组字符串的两种方法:

  • 二维字符数组: 在二维字符数组中,每一行都是一个字符串,而数组中的每个元素都是一个字符数组,用于存储一个字符串。这种方式在内存中是连续存储的,例如:

    1
    2
    char planets[][8] = { "Mercury", "Venus", "Earth", "Mars",
                        "Jupiter", "Saturn", "Uranus", "Neptune"};
    二维字符数组

    弊端:

    1. 字符串之间长度差异很大,就会浪费内存空间。
    2. 不灵活。对字符串进行排序,就需要复制整个字符串里面的内容。
  • 字符指针数组: 在字符指针数组中,数组的每个元素都是一个指向字符数组的指针,每个指针指向一个字符串。这种方式在内存中不一定是连续存储的,例如:

    1
    2
    char* planets[] = { "Mercury", "Venus", "Earth", "Mars",
                        "Jupiter", "Saturn", "Uranus", "Neptune"};
    字符指针数组

    虽然必须为字符串数组中的指针分配空间,但是字符串中不再有任何浪费的字符。存储方式很灵活,对字符串数组进行排序的时候,只需要交换指针即可,不需要复制整个字符串。

命令行参数

命令行参数是指在运行程序时通过命令行传递给程序的参数。

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

1
int main(int argc, char **argv)

argc(argument count)是命令行参数的数量,argv(argument vector)是一个指向字符指针数组的指针,每个指针指向一个命令行参数字符串。操作系统都是以字符串的形式传递参数的,argv[0]存储的是程序的名称,argv[1]argv[2]等存储的是传递给程序的参数字符串。