C++有两种处理字符串的方式,除了沿用C风格字符串,另一种方法是借助用string class
库。
1. 字符数组
字符串是一系列保存在连续内存字节中的字符(每个字符对应一个字节),所以可以将字符串保存在字符数组中。以下四种声明方式都是字符数组,但是只有第一个不是字符串,其中无效字符(null character)'\0'
(其在机器字符集ASCII中对应的整数值是0)起了关键作用。字符数组c
使用字符串常量(即双引号引用的字符串)将字符数组初始化为字符串,比字符数组b
的声明更简洁,并且隐含一个结尾无效字符'\0'
。甚至可以省略数组声明中的数组大小,让编译器来统计字符个数,如字符数组d
,得到的数组大小等于字符串长度加一。
char a[3] = {'o', 'd', 'd'}; // not a string
char b[5] = {'o', 'k', '\0'}; // a string
char c[5] = "ok"; // same as b
char d[] = "ok"; // let the compiler count
C和C++的大多数表达式将字符数组名、字符指针和字符串常量都解读为字符串首字符的地址(即为字符串的地址)1,并做相同处理。以C++的cout
为例,将字符地址传递给cout
后,cout
认为该字符地址是字符串的地址,打印出从该地址开始的每个字符,直到遇到第一个无效字符'\0'
。如以下代码示例所示,将字符数组a
传递给cout
后,除了明确给出的3个字符,还打印出来一些乱码,而且使用strlen()
函数得到的字符串长度也大于明确给出的字符数组大小。相比之下,字符数组b
是一个字符串,字符串长度是无效字符前的字符数量,即2,而数组大小是5。
#include <iostream>
#include <cstring> // for strlen() function
using namespace std;
int main(int argc, char const *argv[]) {
char a[3] = {'o', 'd', 'd'}; // not a string
char b[5] = {'o', 'k', '\0'}; // a string
cout << "array of chars a: " << a << '\n';
cout << "array of chars b: " << b << '\n';
cout << "sizeof(a): " << sizeof(a) << '\n';
cout << "sizeof(b): " << sizeof(b) << '\n';
cout << "strlen(a): " << strlen(a) << '\n';
cout << "strlen(b): " << strlen(b) << '\n';
return 0;
}
输出
array of chars a: odd��X��<0x7f>
array of chars b: ok
sizeof(a): 3
sizeof(b): 5
strlen(a): 9
strlen(b): 2
2. 字符串长度
字符串的长度由结尾的无效字符决定。对于C语言风格字符串,可以使用标准库<string.h>
或<cstring>
里的函数strlen
来求字符串长度,代码示例如下:
int strlen(const char * s) {
int i = 0;
while (s[i] != '\0') {
i++;
}
return i;
}
如果使用C++的string class
,可以使用其成员函数std::string::size
或std::string::length
返回字符串长度。
3. 字符指针
由于C和C++的大多数表达式将字符数组、字符指针和字符串常量当作字符串首字符的地址,用字符串常量初始化字符常量指针时const char * ps = "hello"
,实际上将字符串常量"hello"
所代表的字符串地址赋值给ps
。内存中有一块专门的空间,用来存放所有字符串常量。从以下代码示例的输出二可以看出存放字符串常量的内存区和存放字符数组的内存区相差很远。并且由于字符串常量是常量,所以在声明中使用修饰符const
,明确不能对该字符指针所指的整个字符串内存空间的内容进行修改。
#include <iostream>
using namespace std;
int main(int argc, char const *argv[]) {
// char *ps1 = "hello"; // valid in C, deprrcated in C++
// ps1[0] = 'H';
const char *ps2 = "world";
// ps2[0] = 'W';
char s[] = "there";
cout << "address of string constant \"hello\": " << (void *)ps2 << '\n';
cout << "address of character array \"there\": " << (void *)s << '\n';
return 0;
}
输出一(取消main()
函数第1、2和4行注释,编译报错)
string_constant.cpp:6:17: warning: ISO C++11 does not allow conversion from string literal to 'char *'
[-Wwritable-strings]
char *ps1 = "hello";
^
string_constant.cpp:9:12: error: read-only variable is not assignable
ps2[0] = 'W';
~~~~~~ ^
如果没有使用修饰符const
,初始化时会进行隐含转换,将不能修改的字符串常量转化为可以赋值的字符串。C++明确反对用字符串常量初始化字符指针char *
,编译不通过,警告信息如上面的输出一所示。而尝试通过字符常量指针const char *
修改字符串常量所导致的编译错误信息很常见:只读变量不可赋值。
输出二
address of string constant "hello": 0x102e7af0c
address of character array "there": 0x7ffeecd88a82
通常情况下,cout
接收到指针,相应打印出地址。但是当cout
接收到字符指针char *
时,cout
打印出指针所指向的整个字符串。所以如果想显示字符串地址,需要进行类型转换(type cast),比如转换成void *
或int *
。
对于C语言,虽然使用字符串常量初始化字符指针char *
编译成功(编译器Apple clang version 12.0.0
),但是运行出现bus error
,操作系统禁止修改字符串常量区内容。
#include <stdio.h>
#include <string.h>
int main(int argc, char const *argv[]) {
char *ps = "hello, world"; // no compile error or warning
printf("%s\n", ps);
ps[0] = 'H';
return 0;
}
Shell输出
user@Laptop C/C++ % clang string_constant.c
user@Laptop C/C++ % ./a.out
hello, world
zsh: bus error ./a.out
C++ Primer Plus 6th Edition by Stephen Prata ↩︎