• C与C++风格的字符串辨析


    C++中实际上存在两类字符串,一类是常说的标准库中的string,另一类是C风格的字符串。

    正如C++ primer中所述:

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

    确实C风格的字符串及其难用,而且非常容易发生问题。

    但是,我们不能完全不了解C风格的字符串,因为很多程序和相关的API在标准库出现前就已经完成。而且其中很多涉及重要的系统调用,例如网络编程中就有大量的C风格字符串的使用。我们可以嫌弃它,但是掌握它也是必须的。

    下面就C与C++风格的字符串中一些使用上极易混淆的地方做一些辨析。

    1. 关于头文件

    1. C风格的字符串是string.h

    2. cstring是C风格的string.h在C++下的对应头文件。

    3. C++风格的字符串是string(STL标准库)。

    实际是C++是兼容上述三个头文件的。

    • string.h和cstring都提供了对于C风格字符串的相关操作。

    • 而string是STL标准库提供的模板类容器。

    他们是完全不一样的,如需详细了解,可以查阅cppreference.com文档。

    2. 关于C风格字符串的初始化与赋值操作

    2.1 不同方式的初始化与其存储位置

    C风格的字符串有两大类初始化方式:

    // 第一类:字符数组形式
    // 指定大小
    char c[6] = "abcde";
    // 由编译器计算大小
    char c[] = "abcde";
    
    // 第二类:字符指针形式
    char *c = "abcde";
    

    第一类方法用来指定大小时,必须至少比初始化的内容多1个空间,用来存放null-terminated,即''。否则会有undefined错误发生。

    必须要明确知道,这两类初始化完全不同。使用字符数组所创建的字符串是可读可写的,存储位置为栈区。而用字符指针所得到的字符串是只读的,因为存储位置位于常量区。这是使用前必须明确的。

    2.2 字符数组的名字和字符指针的关系

    由于上述两种关系,我们常常会觉得,字符数组的名字和字符指针是一样的。实际上在很多使用到数组名字的地方,确实编译器会帮我们自动将其替换成「一个指向数组首元素的指针」。这句话的另一层意思就是,二者其实是不一样的,只是“好像在一些操作中一样”。有两个点需要注意:

    • 使用auto变量推断一个C风格字符串时得到的是字符指针,而非字符数组。
    • 使用decltype推断时,得到的是一个字符数组而不是字符指针(也就是上述的转换没有发生)

    即:

    char c[] = "abcde";
    auto auto_c = c;
    decltype(c) decl_c = "abc";
    

    观察其类型:

    注意:decltype连大小都继承下来了,因此不注意的话非常容易出现错误(不过大部分IDE能检测到)。

    2.3 赋值问题

    在知道了字符数组和字符指针的区别后,必须要指出,不建议使用2.1中所述的字符指针方法构建字符串(即第二类)。这并不是一种标准的用法,而且十分危险。它的内存存放在文本常量区,可读不可写。如果试图写入,程序会直接崩溃,甚至编译器不会警告(GCC应该会)。因此,一个常规的做法是用第一类方法初始化,而字符指针可以用来指向已经初始化好的字符串。接下来讨论赋值的时候,都是存储在栈中的字符串。

    当我们以为辨清了存储问题就可以随意赋值,那就大错特错了,因为在C风格的字符串中,不能将数组的内容拷贝给其他数组作为其初始值

    也就是说,不能进行赋值操作。具体来说是不能使用「等号」进行赋值。我们应该是用cstring或string.h中提供的字符串拷贝函数来进行操作:

    char* strcpy( char* dest, const char* src );
    char *strncpy( char *dest, const char *src, std::size_t count );
    

    这里通常就会出现及其危险的操作了,必须足够注意:

    • 使用strcpy的时候必须保证dest是由足够空间的,否则结果为undefined。
    • 使用strncpy时,不会保证拷贝 null-terminated,即''。

    一个常用的方法时把dest开得大一些,然后用0作为初始值,这样能以一种相对优雅的方式防止大多数问题,但是使用起来仍需注意。

    char src[] = "abcde";
    char dest[100] = {0};
    // 拷贝下标2开始的总共2个字符
    strncpy(dest, src + 2, 2);
    cout << dest << endl;
    // 结果输出:cd
    

    3. 关于混用

    前面也提到了,我们无法抛弃C风格的字符串。当我们使用STL的string时,难免遇到C风格与STL容器类混用的问题。幸运的是STL已经为我们考虑了足够多,二者的转换可以非常方便。

    3.1 C风格字符串转STL string

    STL对于C风格是非常宽容的,我们可以直接使用构造函数将C风格转换成容器类。

    char cs[] = "unix";
    string s(cs);
    cout << s << endl;
    //输出:unix
    

    也可以直接用C风格给STL string赋值。

    string s2;
    s2 = cs;
    cout << s2 << endl;
    //输出:unix
    

    进行STL string的加法运算时,C风格的字符串可以作为其中一个对象。

    string scs = s + cs;
    cout << scs << endl;
    //输出:unixunix
    

    需要再次警告:我们必须保证C风格的字符串是正确的,如果它没有争取地以''结尾,那结果也是undefined。

    3.2 STL string转C风格

    当我们的一些API只能接收C风格的字符串时,就必须考虑STL string向C风格转换。这里STL再一次为我们考虑了,提供了对应的转换函数:

    它的使用方法非常简单,如下:

    string s = "unix";
    // 以C格式的字符指针传进去
    func(s.c_str());
    

    参考

  • 相关阅读:
    浮动与浮动的清除
    【最全】经典排序算法(C语言)
    Python类中的self到底是干啥的
    浅析Python3中的bytes和str类型
    Shell十三问[转]
    VMware下对虚拟机Ubuntu14系统所在分区sda1进行磁盘扩容
    C语言运算符优先级
    mysql主要性能监控指标
    sql 优化
    npm install安装时忘记--save解决方法
  • 原文地址:https://www.cnblogs.com/molinchn/p/14771979.html
Copyright © 2020-2023  润新知