C语言字符串详解:从基础操作到高级应用

C语言字符串详解:从基础操作到高级应用

字符串是C语言编程中最常用的数据类型之一,理解字符串的处理对于编写高效、安全的C程序至关重要。C语言中的字符串实际上是字符数组,以空字符\0结尾。本篇博客将深入探讨C语言字符串的各个方面,包括字符串的声明、常用字符串函数的使用、字符串数组以及安全编程的最佳实践。

一、字符串简介:字符数组的特殊形式

在C语言中,字符串并不是一个独立的数据类型,而是字符数组的一种特殊形式。字符串以空字符\0(ASCII值为0)作为结束标志。

字符串的声明和初始化

C
1
2
3
4
5
6
7
8
9
10
11
// 方式1:字符数组初始化
char str1[6] = {'H', 'e', 'l', 'l', 'o', '\0'};

// 方式2:字符串字面量(推荐)
char str2[6] = "Hello";

// 方式3:自动计算大小
char str3[] = "Hello";

// 方式4:字符指针
char *str4 = "Hello";

重要特性

  • 字符串必须以\0结尾
  • 字符串长度不包括结尾的\0
  • 字符数组的大小必须至少比字符串长度多1(用于存放\0

二、字符串长度:strlen()函数

strlen()函数用于计算字符串的长度(不包括结尾的\0)。它的原型定义在string.h头文件中。

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <string.h>

int main() {
char str[] = "Hello World";
int len = strlen(str);
printf("字符串长度:%d\n", len); // 输出:11

// 注意:strlen计算的是字符数,不是字节数
char chinese[] = "你好";
printf("中文字符串长度:%d\n", strlen(chinese)); // 输出:6(UTF-8编码)

return 0;
}

strlen()的特点

  • 时间复杂度为O(n),需要遍历整个字符串直到遇到\0
  • 不计算结尾的\0
  • 对于空字符串返回0

三、字符串复制:strcpy()和strncpy()

1. strcpy()函数

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

strcpy()将源字符串src复制到目标字符串dest,包括结尾的\0

C
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <string.h>

int main() {
char src[] = "Hello World";
char dest[20];

strcpy(dest, src);
printf("复制后的字符串:%s\n", dest); // 输出:Hello World

return 0;
}

安全风险strcpy()不检查目标缓冲区的大小,如果源字符串长度超过目标缓冲区,会导致缓冲区溢出。

2. strncpy()函数(安全版本)

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

strncpy()最多复制n个字符到目标字符串。如果源字符串长度小于n,剩余部分用\0填充。

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <string.h>

int main() {
char src[] = "Hello World";
char dest[10];

strncpy(dest, src, sizeof(dest) - 1); // 保留一个位置给\0
dest[sizeof(dest) - 1] = '\0'; // 手动添加结尾符

printf("安全复制:%s\n", dest);

return 0;
}

四、字符串连接:strcat()和strncat()

1. strcat()函数

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

strcat()将源字符串src连接到目标字符串dest的末尾。

C
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <string.h>

int main() {
char str1[20] = "Hello";
char str2[] = " World";

strcat(str1, str2);
printf("连接后的字符串:%s\n", str1); // 输出:Hello World

return 0;
}

2. strncat()函数(安全版本)

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

strncat()最多连接n个字符到目标字符串末尾,并自动添加\0

C
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <string.h>

int main() {
char str1[20] = "Hello";
char str2[] = " World";

strncat(str1, str2, 5); // 只连接前5个字符
printf("安全连接:%s\n", str1); // 输出:Hello Worl

return 0;
}

五、字符串比较:strcmp()和strncmp()

1. strcmp()函数

C
1
int strcmp(const char* s1, const char* s2);

strcmp()比较两个字符串的字典顺序:

  • 如果s1等于s2,返回0
  • 如果s1小于s2,返回负数
  • 如果s1大于s2,返回正数
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <string.h>

int main() {
char str1[] = "apple";
char str2[] = "banana";
char str3[] = "apple";

printf("比较结果:%d\n", strcmp(str1, str2)); // 负数
printf("比较结果:%d\n", strcmp(str2, str1)); // 正数
printf("比较结果:%d\n", strcmp(str1, str3)); // 0

return 0;
}

2. strncmp()函数

C
1
int strncmp(const char* s1, const char* s2, size_t n);

strncmp()只比较前n个字符。

C
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <string.h>

int main() {
char s1[12] = "hello world";
char s2[12] = "hello C";

if (strncmp(s1, s2, 5) == 0) {
printf("它们都有hello.\n");
}

return 0;
}

六、格式化字符串:sprintf()和snprintf()

1. sprintf()函数

C
1
int sprintf(char* s, const char* format, ...);

sprintf()将格式化数据写入字符串而不是输出到显示器。

C
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <string.h>

int main() {
char first[6] = "hello";
char last[6] = "world";
char s[40];

sprintf(s, "%s %s", first, last);
printf("%s\n", s); // 输出:hello world

return 0;
}

安全风险sprintf()有严重的安全风险,如果写入的字符串过长会导致缓冲区溢出。

2. snprintf()函数(安全版本)

C
1
int snprintf(char* s, size_t n, const char* format, ...);

snprintf()最多写入n-1个字符,最后一个位置写入\0

C
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <string.h>

int main() {
char buffer[12];
int written = snprintf(buffer, sizeof(buffer), "%s %s", "hello", "world");

printf("写入的字符串:%s\n", buffer);
printf("尝试写入的字符数:%d\n", written);

return 0;
}

七、字符串数组

字符串数组可以通过二维字符数组或字符指针数组实现。

1. 二维字符数组方式

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <string.h>

int main() {
char weekdays[7][10] = {
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
};

// 可以省略第一维大小
char weekdays2[][10] = {
"Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday"
};

for (int i = 0; i < 7; i++) {
printf("%s\n", weekdays[i]);
}

return 0;
}

2. 字符指针数组方式(更高效)

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <string.h>

int main() {
char* weekdays[] = {
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
};

for (int i = 0; i < 7; i++) {
printf("%s\n", weekdays[i]);
}

return 0;
}

两种方式的区别

  • 二维数组:内存连续,每个字符串占用固定空间
  • 指针数组:更灵活,每个字符串可以有不同的长度

八、安全编程最佳实践

1. 总是检查缓冲区大小

C
1
2
3
4
5
6
7
8
// 不安全
char buffer[10];
strcpy(buffer, "这是一个很长的字符串"); // 缓冲区溢出!

// 安全
char buffer[10];
strncpy(buffer, "这是一个很长的字符串", sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';

2. 使用安全的字符串函数

C
1
2
3
4
// 优先使用安全版本
strncpy() 代替 strcpy()
strncat() 代替 strcat()
snprintf() 代替 sprintf()

3. 验证字符串长度

C
1
2
3
4
5
6
7
8
9
10
char input[100];
if (fgets(input, sizeof(input), stdin)) {
// 移除换行符
input[strcspn(input, "\n")] = '\0';

// 检查长度
if (strlen(input) < sizeof(input) - 1) {
// 安全处理
}
}

九、总结

C语言字符串处理是编程中的基础但重要的技能。关键要点:

  1. 理解字符串本质:字符串是以\0结尾的字符数组
  2. 掌握核心函数strlen, strcpy, strcat, strcmp
  3. 安全第一:总是使用安全版本的函数并检查缓冲区大小
  4. 灵活运用:根据需求选择二维数组或指针数组实现字符串数组
  5. 性能考虑:了解各函数的时间复杂度,避免不必要的字符串操作

通过熟练掌握这些字符串处理技术,你将能够编写出更安全、高效的C语言程序。


C语言字符串详解:从基础操作到高级应用
https://www.edenzeng.online/2015/10/23/0.技术栈/01.开发语言/01.C语言/09-字符串详解/
作者
Edenzeng
发布于
2015年10月23日
许可协议