字符数组是C++中用于存储字符序列的基础数据结构,其本质是元素类型为char的数组。与普通数组相比,字符数组具有以下特殊属性:
在实际工程中,字符数组常用于处理文本数据、配置参数存储等场景。例如日志系统常使用字符数组存储日志信息:
cpp复制char logMessage[256]; // 声明256字节的日志缓冲区
字符数组的初始化有多种形式,每种形式都有其特定的应用场景和内存布局:
cpp复制char s1[5] = {'a', 'b', 'c', 'd', 'e'};
内存布局:
| 索引 | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 值 | 'a' | 'b' | 'c' | 'd' | 'e' |
特点:
cpp复制char s2[5] = {'a', 'b', 'c'};
内存布局:
| 索引 | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 值 | 'a' | 'b' | 'c' | '\0' | '\0' |
特点:
cpp复制char s3[5] = "abcd";
内存布局:
| 索引 | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 值 | 'a' | 'b' | 'c' | 'd' | '\0' |
特点:
重要提示:当使用字符串初始化时,数组长度必须至少比字符串长度多1,以容纳'\0'。例如"hello"需要至少6个元素的数组。
二维字符数组实际上可以理解为字符串数组:
cpp复制char names[3][10] = {"Alice", "Bob", "Charlie"};
内存布局:
code复制names[0]: 'A' 'l' 'i' 'c' 'e' '\0' ...
names[1]: 'B' 'o' 'b' '\0' ...
names[2]: 'C' 'h' 'a' 'r' 'l' 'i' 'e' '\0' ...
每个子数组都是一个独立的字符串,有自己的终止符。这种结构常用于存储固定数量的字符串。
cpp复制char str[100];
// 输入
for(int i=0; i<5; ++i) {
cin >> str[i];
}
// 输出
for(int i=0; i<5; ++i) {
cout << str[i];
}
适用场景:
cpp复制char name[50];
cin >> name; // 读取到空白符停止
cin.getline(name, 50); // 读取整行
cout << name; // 输出到'\0'停止
注意事项:
cin >>遇到空格/tab/换行会停止cin.getline()更安全,可指定最大读取长度不安全的做法:
cpp复制char buffer[10];
cin >> buffer; // 用户输入超过9字符会导致溢出
安全做法:
cpp复制char buffer[10];
cin.get(buffer, sizeof(buffer)); // 限制最大读取长度
混合使用>>和getline时容易出错:
cpp复制int age;
char name[50];
cin >> age;
cin.getline(name, 50); // 会立即读取之前留下的换行符
解决方案:
cpp复制cin >> age;
cin.ignore(); // 清除输入缓冲区中的换行符
cin.getline(name, 50);
cpp复制size_t strlen(const char* str);
实现原理:
cpp复制size_t my_strlen(const char* str) {
size_t len = 0;
while(*str++ != '\0') len++;
return len;
}
注意事项:
cpp复制int strcmp(const char* str1, const char* str2);
返回值含义:
0: str1大于str2
比较规则:
典型实现:
cpp复制int my_strcmp(const char* s1, const char* s2) {
while(*s1 && (*s1 == *s2)) {
s1++;
s2++;
}
return *(const unsigned char*)s1 - *(const unsigned char*)s2;
}
cpp复制char* strcpy(char* dest, const char* src);
安全版本:
cpp复制char* strncpy(char* dest, const char* src, size_t n);
cpp复制char* strcat(char* dest, const char* src);
注意事项:
字符数组优势:
string类优势:
选择建议:
传统方式:
cpp复制for(int i=0; i<strlen(s); i++) {
// 每次循环都调用strlen,效率低下
}
优化方案:
cpp复制for(int i=0; s[i]!='\0'; i++) {
// 直接检测终止符
}
或者:
cpp复制char* p = s;
while(*p) {
// 使用指针运算
p++;
}
对于频繁操作的字符数组,可以考虑缓存对齐:
cpp复制alignas(64) char buffer[256]; // 64字节对齐
这种优化在SIMD指令处理时能显著提升性能。
症状:
解决方案:
症状:
预防措施:
问题:
解决方案:
虽然C++推荐使用string类,但在以下场景仍需字符数组:
C++17后可以结合string_view使用:
cpp复制char oldBuffer[100];
std::string_view sv(oldBuffer);
// 使用sv的各种方法而不复制数据
在实际项目中,我通常会根据以下原则选择: