C风格字符串

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

字符串字面值是一种通用结构的实例。这种结构即是C++由C继承而来的C风格字符串。C风格字符串不是一种类型,而是为了表达和使用字符串而形成的一种约定俗成的写法。

 

C标准库String函数

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

C风格字符串的函数
strlen(p)返回p的长度,空字符不计算在内
strcmp(p1,p2)比较p1和p2的相等性。如果p1==p2,返回0;如果p1>p2,返回一个正值;如果p1<p2,返回一个负值
strcat(p1,p2)将p2附加到p1之后,返回p1
strcpy(p1,p2)将p2拷贝给p1,返回p1

 注意:

(1)表中的4个函数都不负责验证其字符串参数;

(2)传入此类函数的指针,必须是指向以空字符作为结束的数组。

char ca[] = {'C', '+', '+'};     // 不以空字符结尾的数组

cout << strlen(ca) << endl;    // 错误,因为strlen遇到空字符才停下来,所以strlen函数将有可能沿着ca在内存中的位置不断向前寻找。

 

比较字符串

比较两个C风格字符串的方法和之前学习过的比较标准库string对象的方法大相径庭。比较标准库string对象的时候,用的是普通的关系运算符和相等性运算法。

string s1 = "A string example";

string s2 = "A different string";

if (s1 < s2)    // false :s2小于s1

如果把这些运算符用在两个C风格字符串,实际比较的将是指针而非字符串本身:

const char ca1[] = "A string example";

const char ca2[] = "A different string";

if (ca1 < ca2)    // 未定义的:试图比较两个无关地址

当使用数组的时候其实真正用的是指向数组首元素的指针。因此,上面的if条件实际上比较的是两个const char*的值。这两个指针指向的并非同一对象,所以的得到未定义的结果。

要想比较两个C风格字符串需要调用strcmp函数。

if (strcmp(ca1, ca2) < 0)   //和两个string对象比较s1 < s2效果一样

 

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

连接或拷贝C风格字符串也与string差别很大。

string largeStr = s1 + " " + s2;  // 将largeStr初始化s1,一个空格和s2的连接(当然这里string和字符串相加有条件的)

同样的操作放到ca1和ca2这两个数组身上就会产生错误了。表达式ca1 + ca2视图将两个指针相加,非法无意义的。

 

正确的方法是使用strcat和strcpy函数。不过要使用这两个函数,还必须提供一个用于存放结果字符串的数组,该数组必须足够大以便容纳下结果字符串及末尾的空字符。下面的代码虽然常见,但充满了危险,极易引发严重错误:

strcpy(largeStr, ca1);    // 把ca1拷贝给largeStr

strcat(largeStr, " ");   // 在largeStr的末尾加一个空格

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

 

C++与旧代码的接口

很多时候写C++的时候,不得不与那些充满了数组和C风格字符串的代码衔接。为了使这一项工作简单易行,C++专门提供了一组功能。

 

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

string a("Hello World");    // s的内容时Hello World (注意这里可以是())

更一般的情况是,任何出现字符串字面值的地方都可以用空字符结束的字符数组来替代:

(1)允许使用以空字符结束的字符数组来初始化string对象或为string对象赋值

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

 

但是,上述性质反过来就不成立了:如果程序的某处需要一个C风格字符串,无法直接用string对象来代替它。例如:不能用string对象直接初始化指向字符的指针。为了完成该功能,string专门提供了一个名为c_str的成员函数:

char *str = s;   //错误:不能用string对象初始化char*

const char *str = s.c_str();   //正确

顾名思义:c_str函数返回的是一个C风格的字符串。也就是说,函数的返回结果是一个指针,该指针指向一个以空字符结束的字符数组,而这个字符数组所存的数据恰好与那个string对象一样。结果指针的类型是const char*,从而确保我们不会改变字符数组的内容。

无法保证c_str函数返回的数组一直有效,事实上,如果后续的操作改变了s的值就可能让之前返回的数组失去效用。

如果执行完c_str()函数后程序想一直都能使用其返回的数组,最好将该数组重新拷贝一份。

 

使用数组初始化vector对象

不允许使用一个数组为另一个内置类型的数组赋初值,也不允许使用vector对象初始化数组。

相反允许使用数组来初始化vector对象。要实现这一目的,只需指明要拷贝区域的首元素地址和尾后地址就可以了:

int int_arr = {0,1,2,3,4,5};

vector<int> ivec(begin(int_arr), end(int_arr));  //ivec有6个元素,分别是int_arr中对应元素的副本

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

vector<int> subVec(int_arr + 1, int_arr + 4);    // 拷贝三个元素,int_arr[1]、int_arr[2]、int_arr[3]

 

 

这里有个知识点,指针其实也是迭代器。

vector和string的迭代器支持的运算,数组的指针全部支持。就像使用迭代器遍历vector对象中的元素一样,使用指针也能遍历数组中的元素。当然这样做的前提是先得获取到指向数组第一个元素的指针和指向数组尾元素的下一位置的指针。可以通过数组的名字或者数组首元素的地址来来得到指向首元素的指针,我们可以设法获取数组的尾元素之后那个并不存在的元素的地址:

int *e = &arr[10];    //arr为10个整型元素的数组,e指向arr尾元素的下一位置的指针。

这里的e不能递增和解引用。

输出arr的全部元素:

for (int *b = arr; b != e; ++b)
    cout << *b << endl;

 

标准库函数begin和end

尽管能计算到尾后指针,但这种做法极易出错,为了让这个指针使用更简单,更安全,C++11新标准引入了两个名为begin和end的函数,这两个函数通用容器中的同名成员类似,不过数组毕竟不是类类型(而容器是类模板),因此这两个函数不是成员函数,正确的使用形式是将数组作为它们的参数:

int ia{} = {0,1,2,3,4,5,6,7,8,9};    // ia是一个含有10个整数的数组

int *beg = begin(ia);   // 指向ia首元素的指针

int *last = end(ia);      // 指向ia尾元素的下一位置的指针

这两个函数定义在iterator中。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值