CPP-04-string类

要使用string类,必须在程序中包含头文件stringstring类位于名称空间std

1
2
3
4
5
6
string name1 = "hello";
char name2[6] = "hello";
string name3 = "hello";
//char name4[5] = "hello"; //"const char [6]" 类型的值不能用于初始化 "char [5]" 类型的实体
if(name1 == name3) cout << "name1 == name3 " << endl;
if(name2 == name3) cout << "name2 == name3" << endl;

这里有两个注意点

  1. 字符串可以通过 == 比较,且 name1 == name3
  2. 字符串可以通过 char[] 表示,且相等
  3. 字符数组后面有一个 ‘\0’

C++11也允许将列表初始化用于C-风格字符串和string对象:

1
2
3
4
5
6
7
8
9
10
11
char charr1[20];
char charr2[20] = "jaguar";
string str1;
string str2 = "panther";

cout << "Enter a kind of feline: ";
cin >> charr1;
cout << "Enter another kind of feline: ";
cin >> str1;
cout << charr1 << " " << charr2 << " " << str1 << " " << str2 << endl;
cout << charr2[2] << " " << str2[2] << endl;

输出结果

1
2
3
4
Enter a kind of feline: hello
Enter another kind of feline: world
hello jaguar world panther
g n

所以

  • 可以使用C-风格字符串来初始化string对象。
  • 可以使用cin来将键盘输入存储到string对象中。
  • 可以使用cout来显示string对象。
  • 可以使用数组表示法来访问存储在string对象中的字符。

对于一个数组,返回这个数组占的总空间,所以 sizeof(name2) 取的额是字符串name2占的总空间

头文件cstring(以前为string.h)提供了字符串操作函数

1
2
char	*strcpy(char *__dst, const char *__src); //copy __src to __dst
char *strcat(char *__s1, const char *__s2); //append contents of charr2 to char1

接着看看实际操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
string str1 = "hello";
string str2 = str1;
cout << str1 << " " << str2 << endl;
str2[0] = '0';
cout << str1 << " " << str2 << endl;

char name1[6] = "world";
char name2[5];
strcpy(name2, name1);
cout << name1 << " " << name2 << endl;
name2[0] = '0';
cout << name1 << " " << name2 << endl;
//strcpy(name1, str1); //不存在从 "std::__1::string" 到 "const char *" 的适当转换函数

char name3[6] = "xboom";
char name4[6];
strcpy(name4, name3);
cout << name3 << " " << name3 << endl;
name3[0] = '0';
cout << name4 << " " << name3 << endl;

运行结果

1
2
3
4
5
6
hello hello
hello 0ello
world
0orld
xboom xboom
xboom 0boom

可以得出几点:

  1. strcpy 是复制,不会影响原来的字符串
  2. string 类可以直接使用 = 复制,并且也不会影响原来的
  3. string 转换为 const char *,因为 const char * 是一个指向字符常量的指针,类型不匹配
  4. 使用 strcpy 注意目标空间大于等于源目标,否则导致目标缓冲区异常,导致崩溃或未知错误

计算字符长度也是不一样的

1
2
3
4
char name1[6] = "world";
char name2[6] = {'h','e','l','l','o','\0'};
string name3 = "hello";
cout << strlen(name1) << " " << strlen(name2) <<" "<< name3.size() << endl;

输出结果

1
5 5 5
1
2
3
4
5
6
7
char name[20];
string str;
string str2;
cin.getline(name, 20);
getline(cin, str);
cin >> str2;
cout << name << " " << str << " " << str2 << endl;

运行结果

1
2
3
4
hello world		#输入
hello world #输入
hello world #输入
hello world hello world hello
  • getline() 是 C++ 标准库中的一个自由函数(不是成员函数),用于从标准输入流 cin 中读取一行文本,并将其存储到 std::string 对象 str
  • cin.getline()istream 类的成员函数,用于从标准输入流 cin 中读取一行文本,并将其存储到字符数组 name

C++11 新增了原始字符串,字符表示的就是自己

1
2
cout << R"(Jim "King" Tutt users "\n" instead of endl.)" << '\n';
cout << R"+*("(Who wouldn't?)", she whispered.)+*" << endl;

输入结果

1
2
Jim "King" Tutt users "\n" instead of endl.
"(Who wouldn't?)", she whispered.

原始字符串将 用作定界符,并使用前缀 R 来标识原始字符串

也可以使用 R"+*( 表示原始字符串的开头的时候,必须使用 )+*" 标识原始字符串的结尾

最后看一波 strcpy 的源码实现

1
2
3
4
5
6
7
8
9
10
11
12
char* strcpy(char* dest, const char* src) {
char* tmp = dest; // 保存目标字符串的起始地址,用于返回复制后的目标字符串的指针

while (*src != '\0') {
*dest = *src; // 复制源字符串的字符到目标字符串
++dest;
++src;
}

*dest = '\0'; // 在目标字符串的末尾添加 null 终止符
return tmp; // 返回复制后的目标字符串的指针
}
  • src 为指针常量不会被修改
  • 定义一个指针 tmp 并将其指向目标字符串 dest 的起始地址。这样在复制结束后,可以通过 tmp 指针找到复制后的目标字符串的起始地址,并返回该指针

实际的使用中会发现 strcpy 几乎不是用,原因是 strcpy当一直遍历到 src 结束才会停止写入 dest,当 src 长度大于 dest 的时候,会导致溢出

就提到了另外函数 strncpy

1
2
3
4
5
6
7
8
9
10
11
12
char *strncpy(char *dest, const char *src, size_t count)
{
char *tmp = dest;

while (count) { //仅复制指定大小的长度
if ((*tmp = *src) != 0) //如果没有移动到字符串末尾‘\0’
src++;
tmp++;
count--;
}
return dest;
}

这里有几个关键的问题

  1. *temp != 0 进行判断是否结束,不是 \0 吗?

    '0'代表ASCII值为48的数字零字符,而'\0'代表空字符(null terminator),其ASCII值为0

  2. 如果 src_len >= count 会怎样?

    src 的全部内容都回被copy 到 dest 中,但是 dest 末尾并不是\0

  3. 如果 src_len < counttmp++ 直到 count == 0,也就是说超过 count 的部分不变

    1
    2
    3
    4
    char arr[6] = "world";
    char dest[12] = "hello world";
    strncpy(dest, arr, 8);
    cout << dest << endl;

    输出结果 world, 是因为将'\0' 也复制进去了,那是不是当超过 src_len < count 就可以结束了

在使用 strncpy 又会有一个新的提示

This function or variable may be unsafe. Consider using strncpy_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details

下面是 strcpy_s 的实现

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
27
28
_FUNC_PROLOGUE
errno_t __cdecl _FUNC_NAME(_CHAR *_DEST, size_t _SIZE, const _CHAR *_SRC)
{
_CHAR *p;
size_t available;
_VALIDATE_STRING(_DEST, _SIZE); //验证目标字符串 _DEST 的合法性
//验证源字符串 _SRC 的合法性。它可能会检查 _SRC 是否为有效的指针,并确保 _DEST 和 _SIZE 参数在一定条件下都是合法的
_VALIDATE_POINTER_RESET_STRING(_SRC, _DEST, _SIZE);
p = _DEST;
available = _SIZE;
//_SRC 到头或者数量达到目标就停止复制
while ((*p++ = *_SRC++) != 0 && --available > 0)
{
}
//达到复制长度,说明 _DEST 还没存入'\0', 即 src_len >= cout 的情况
if (available == 0)
{

//将_DEST 全部重置为 '\0'
_RESET_STRING(_DEST, _SIZE);
//返回_DEST 长度不够存储_SIZE 的错误
_RETURN_BUFFER_TOO_SMALL(_DEST, _SIZE);
}
//src_len < count 的情况
//否则天车工剩下的部分为 '\0'
_FILL_STRING(_DEST, _SIZE, _SIZE - available + 1);
_RETURN_NO_ERROR;
}

也就是 strcpy_s 除了字符校验还解决了当 dest_len < cout 时候的溢出

参考链接

  1. 《C++ Primer Plus》