【C++基础】第十八课:C风格字符串

C风格字符串

Posted by x-jeff on July 21, 2020

【C++基础】系列博客为参考《C++ Primer中文版(第5版)》C++11标准)一书,自己所做的读书笔记。
本文为原创文章,未经本人允许,禁止转载。转载请注明出处。

1.C风格字符串

尽管C++支持C风格字符串,但在C++程序中最好还是不要使用它们。这是因为C风格字符串不仅使用起来不太方便,而且极易引发程序漏洞,是诸多安全问题的根本原因。

字符串字面值是C++由C继承而来的C风格字符串

‼️C风格字符串存放在【字符数组】中并以空字符结束。以空字符结束的意思是在字符串最后一个字符后面跟着一个空字符(\0)。

1.1.C标准库String函数

下表列举了C语言标准库提供的一组函数,这些函数可用于操作C风格字符串,它们定义在cstring头文件中,cstring是C语言头文件string.h的C++版本。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <string>
#include <cstring>

using namespace std;

int main() {
    string str1="Hello";
    string str2="World";
    char str3[6]="Hello";//加上末尾的空字符
    char str4[6]="World";//加上末尾的空字符
    //C标准库String函数
    cout<<strlen(str1.c_str())<<endl;//输出为:5
    cout<<strcmp(str1.c_str(),str2.c_str())<<endl;//输出为:-15
    cout<<strcat(str3,str4)<<endl;//输出为:HelloWorld
    cout<<strcpy(str3,str4)<<endl;//输出为:World
    return 0;
}

⚠️传入此类函数的指针必须指向以空字符作为结束的数组:

1
2
char ca[]={'C','+','+','\0'};
cout<<strlen(ca)<<endl;//输出为:3

1.2.比较字符串

比较两个C风格字符串的方法和之前学习过的比较标准库string对象的方法完全不同。

比较两个string对象:

1
2
3
string s1="A string example";
string s2="A different string";
if (s1<s2) //返回false,因为s2小于s1

如果用同样的方法比较两个C风格字符串:

1
2
3
const char ca1[]="A string example";
const char ca2[]="A different string";
if (ca1<ca2) //未定义的:试图比较两个无关地址

当使用数组的时候其实真正用的是指向数组首元素的指针。

要想比较两个C风格字符串需要调用strcmp函数,见1.1部分的讲解。

1.3.目标字符串的大小由调用者指定

连接或拷贝C风格字符串也与标准库string对象的同类操作差别很大。例如:

1
2
//接着1.2部分的例子
string largeStr=s1+" "+s2;

同样的操作如果放到ca1ca2这两个数组身上就会产生错误了。正确的方法是使用1.1部分介绍的strcat函数和strcpy函数,例如:

1
2
3
4
//如果我们计算错了largeStr的大小将引发严重错误
strcpy(largeStr,ca1);
strcat(largeStr," ");
strcat(largeStr,ca2);

一个潜在的问题是,我们在估算largeStr所需的空间时不容易估准,而且largeStr所存的内容一旦改变,就必须重新检查其空间是否足够。

对大多数应用来说,使用标准库string要比使用C风格字符串更安全、更高效。

2.与旧代码的接口

现代的C++程序不得不与那些充满了数组和/或C风格字符串的代码衔接,为了使这一工作简单易行,C++专门提供了一组功能。

2.1.混用string对象和C风格字符串

任何出现字符串字面值的地方都可以用以空字符结束的字符数组来替代:

  • 允许使用以空字符结束的字符数组来初始化string对象或为string对象赋值。
  • 在string对象的加法运算中允许使用以空字符结束的字符数组作为其中一个运算对象(不能两个运算对象都是);在string对象的复合赋值运算中允许使用以空字符结束的字符数组作为右侧的运算对象。

例如接着1.1部分的例子:

1
2
3
string str5;
str5=str1+str4;
cout<<str5<<endl;//输出为:HelloWorld

⚠️但是上述性质反过来就不成立了:如果程序的某处需要一个C风格字符串,无法直接用string对象来代替它。为了解决这个问题,string专门提供了一个名为c_str的成员函数,用以返回一个C风格的字符串。例如:

1
2
3
string s("Hello World");
char *str=s;//错误:不能用string对象初始化char*
const char *str=s.c_str();//正确

2.2.使用数组初始化vector对象

【C++基础】第十七课:数组一文中介绍过不允许使用一个数组为另一个内置类型的数组赋初值,也不允许使用vector对象初始化数组。相反的,允许使用数组来初始化vector对象。要实现这一目的,只需指明要拷贝区域的首元素地址和尾后地址就可以了:

1
2
3
int int_arr[]={0,1,2,3,4,5};
//ivec有6个元素,分别是int_arr中对应元素的副本
vector<int> ivec(begin(int_arr),end(int_arr));//ivec内的元素为{0,1,2,3,4,5}

👉begin()和end()的使用

用于初始化vector对象的值也可能仅是数组的一部分:

1
vector<int> subVec(int_arr+1,int_arr+4);//subVec内的元素为{1,2,3}