1. string 类
在 C 中,对于字符串的实现采用的是字符数组的方式,并非真正意义上的字符串。因此,C++ 标准库提供了 string 类来实现真正意义上的字符串。
string 具备以下特性:
- 支持字符串直接连接,即具备 strcat 函数功能。
- 支持字符串的大小直接比较,即具备 strcmp 函数功能。
- 支持子串直接查找和提取,即具备 strstr 函数功能。
- 支持字符串的插入和替换。
- 支持直接获取字符串长度,即具备 strlen 函数功能。
- 实验:
int main(int argc, char* argv[])
{
string str_A = "abc";
string str_B = "def";
cout << "str_A = " << "\"" << str_A << "\"" << endl; // str_A = "abc"
cout << "str_B = " << "\"" << str_B << "\"" << endl; // str_B = "def"
/* 字符串比较 */
cout << "(str_A == str_B) = " << (str_A == str_B) << endl; // (str_A == str_B) = 0
/* 字符串拼接 */
string str_C = str_A + str_B;
cout << "str_C = " << "\"" << str_C << "\"" << endl; // str_C = "abcdef"
str_C += str_A;
cout << "str_C = " << "\"" << str_C << "\"" << endl; // str_C = "abcdefabc"
/* 计算字符串长度 */
cout << "str_C.lenght() = " << str_C.length () << endl; // str_C.lenght() = 9
/* 提取子串 */
cout << "str_C.substr(3) = " << "\"" << str_C.substr(3) << "\"" << endl; // str_C.substr(3) = "defabc"
cout << "str_C.substr(1,4) = " << "\"" << str_C.substr(1,4) << "\"" << endl; // str_C.substr(1,4) = "bcde"
/* 查找子串 */
cout << "str_C.find(\"b\") = " << str_C.find("b") << endl; // str_C.find("b") = 1
return 0;
}
2. 字符串与数字相互转换
C++ 标准库提供了相关类用于字符串与数字的相互转换。istringstream 表示字符串输入流,ostringstream 表示字符串输出流。使用字符串流类需要包含 #include <sstream> 头文件。
- 实验:
int main(int argc, char* argv[])
{
/* 字符串转换为数字 */
istringstream iss("3.14");
float flt_pi = 0;
if (iss >> flt_pi)
{
cout << "flt_pi = " << flt_pi<< endl; // flt_pi = 3.14
}
/* 数字转换为字符串 */
ostringstream oss;
if (oss << flt_pi)
{
string str_pi = oss.str();
cout << "str_pi = " << "\"" << str_pi << "\"" << endl; // str_pi = "3.14"
}
return 0;
}
3. 字符串的循环移动
对于 C++ 的 string 字符串对象,可以重载运算符 “<<” 和 “>>” ,通过 string 类提供的功能组合模拟类似于二进制的位移,实现字符串的循环移动。
- 实验:
string operator >> (const string& s, int n)
{
string ret;
unsigned int pos = 0;
// 右移位数计算
n = n % s.length();
// 右移的开始位置
pos = s.length() - n;
// 提取右移部分的子串作为起始
ret = s.substr(pos);
// 拼接子串
ret += s.substr(0, pos);
return ret;
}
string operator << (const string& s, int n)
{
string ret;
unsigned int pos = n;
// 左移位数计算
pos = pos % s.length();
// 提取左移部分的子串作为结尾
ret = s.substr(0, pos);
// 拼接子串
ret = s.substr(pos) + ret;
return ret;
}
int main(int argc, char *argv[])
{
string str_A = "abcdef";
cout << "str_A = " << "\"" << str_A << "\"" << endl; // str_A = "abcdef"
cout << "str_A >> 1 = " << "\"" << (str_A >> 1) << "\"" << endl; // str_A >> 1 = "fabcde"
string str_B = "abcdef";
cout << "str_B = " << "\"" << str_B << "\"" << endl; // str_B = "abcdef"
cout << "str_B << 1 = " << "\"" << (str_B << 1) << "\"" << endl; // str_B << 1 = "bcdefa"
return 0;
}
4. C++ 方式打印字符型指针的内容
在 C 中,用 printf 函数打印字符型指针内容即指针指向的地址时,使用 %p 便可以对指向字符型指针内容进行打印。在 C++ 中,直接用输出流 cout 输出指向字符型指针时,会发现打印的是指针指向的字符串,并非指针本身的内容。
这是由于 C++ 标准库 中 I/O 类对运算符 << 进行重载,因此在遇到字符型指针时会将其当作字符串名来处理,输出指针所指的字符串。想要打印字符型指针的内容,需要通过强制类型转换,可以用 static_cast 把字符型指针转换成无类型指针。
- 实验:
int main()
{
char* p = "123456";
/* C 方式打印 */
printf("%p\n", p); // 0x400a74
printf("%s\n", p); // 123456
/* C++ 方式打印 */
cout << p << endl; // 123456
cout << static_cast<const void*>(p) << endl; // 0x400a74
return 0;
}
5. string 类的底层实现
string 类通过重载运算符 [] 支持以数组的方式访问字符串,
- 示例:
string str = "123456";
cout << "str[1] = " << "\"" << str[1] << "\"" << endl; // str[1] = "2"
那么 string 类的底层是怎么维护字符串对象的呢?其实在创建 string 对象时,会申请至少初始字符串长度的内存空间,并用一个成员变量记录字符串长度,这个成员变量用 length() 获得。
但假如如果使用数组的方式把字符添加到超出原字符串的内容空间,此时实际并没有真正地添加到有效的字符串后,记录字符串长度的成员变量并没有更新,因此切勿使用数组的方式进行字符串的拼接,对字符串的操作也应尽量调用 string 类提供的功能函数。
使用运算符 + 或调用函数 append() 进行字符串的拼接时,会查看拼接进来的字符串的长度是否大于已申请的内存空间,如果小于,则直接把该字符串存放到原字符串的结尾并更新字符串长度;否则,会申请一段大于拼接后的字符串的长度的内存空间,然后分别把原字符串与拼接字符串拼接存放到新的内存空间去,原内存空间会被释放。
下面通过使用 c_str() 获取 string 对象的字符串首地址来看看这一更换内存的过程。
- 实验:
int main(int argc, char *argv[])
{
string str = "12";
const char* p_A = str.c_str();
cout << "*p_A = " << "\"" << p_A << "\"" << endl; // *p_A = "12"
cout << "p_A = " << static_cast<const void*>(p_A) << endl; // p_A = 0x7ffebe9d8390
str += "3456789987654321";
const char* p_B = str.c_str();
cout << "*p_B = " << "\"" << p_B << "\"" << endl; // *p_B = "123456789987654321"
cout << "p_B = " << static_cast<const void*>(p_B) << endl; // p_B = 0x1713030
str += "123456789987654321";
const char* p_C = str.c_str();
cout << "*p_C = " << "\"" << p_C << "\"" << endl; // *p_C = "123456789987654321123456789987654321"
cout << "p_C = " << static_cast<const void*>(p_C) << endl; // p_C = 0x1713060
cout << "*p_A = " << "\"" << p_A << "\"" << endl; // *p_A = "<"
cout << "*p_B = " << "\"" << p_B << "\"" << endl; // *p_B = ""
return 0;
}