• C++ 的那些坑 (Day 0)


    C++ 的那些坑 (Day 0)

    永远的for循环

    其实这里要说的并不是for循环本身还是其中的计数变量的类型的选择。

    std::string s = "abcd"
    for (string::size_type i=0; i<s.size(); i++) {
        std::cout<<s[i]<<" ";
    }
    // a b c d
    

    对于以上代码估计很多人是不会这样装逼的使用size_t或者string::size_type类型的,而是直接使用int类型,虽然string.size()以及其他一些容器如vector的size()方法返回的都是size_t类型的值。再来看下面这段代码

    std::string s = "abcd"
    for (string::size_type i=0; i<s.size() - 10; i++) {
        std::cout<<s[i]<<" ";
    }
    
    

    功能上来说这个for循环是负责输出字符串中除去最后10个字符的前面部分。当运行编译运行时,会发现程序胡乱输出,并最终可能给出一个Segmentation fault。这个非常出乎意料,按照原先的剧情,这个for应该根本无法进入才对。当你使用debug工具去debug时你会惊奇的发现真的会进入循环内部。

    原因在于size_t或者string::size_type其实是一个unsigned的整数,因此情景中s.size() - 10的结果值并不是一个负数,而是一个非常大的整数。

    再来看另一个场景,这次是逆序输出字符串中的字符:

    std::string s = "abcd"
    for (size_t i=s.size() - 1; i >= 0; i--) {
        std::cout<<s[i]<<" ";
    }
    

    此时我们还是装逼的使用了size_t类型作为迭代变量,编译运行会发现程序又是一阵抽风或者根本停不下来。原因还是一样的,size_t作为无符号类型无论如何在i >= 0这个条件判断中总是会通过的。其实编译器在编译时已经给出了警告:

    sizet.cpp:6:31: warning: comparison of unsigned expression >= 0 is always true [-Wtautological-compare]
    for (size_t i=s.size() - 1; i>=0; i--) {
    ~^ ~
    1 warning generated.

    所以一般情况下我们还是直接使用int作为迭代变量或者作为终止条件变量:

    int end = s.size() - 10;
    for (int i=0; i<end; i++) {
        std::cout<<s[i]<<" ";
    }
    

    当然这样会有编译警告说类型转换可能会有精度丢失。但是我们要想一下有符号的int能表示2GB的空间,也就是大约20亿的数据项,如果这个数据项是整数的话在多于8GB的空间时才会溢出。对于一般的应用程序级别的容器根本无法达到这种量级。

    当然的,对于广大装逼爱好者,还是要掌握容器迭代的正确姿势的:

    // C++11 foreach (string s = "abcd")
    for (char ch : s) {
        std::cout<<ch<<" ";
    }
    
    // using iterator
    for (auto iter = s.begin(); iter != s.end(); i++) {
    
    }
    
    // using reverse iterator
    for (auto iter = s.rbegin(); iter != s.rend(); i++) {
    
    }
    

    有偏见的引用

    下面是一个再寻常不过的例子

    int value = 10086;
    int &valref = value;
    

    当我们稍作修改改为如下语句时,编译就无法通过

    double value = 10086.233;
    int &valref = value;
    

    type.cpp:8:7: error: non-const lvalue reference to type 'int' cannot bind to a value of unrelated type 'double'
    int &valref = value;
    ^ ~~~~~
    1 error generated.

    看来引用必须是同类型的,然而看了下面这段又不能如此过早下结论

    double value = 10086.233;
    const int &valref = value;
    
    cout<<valref<<endl;
    

    上述代码会编译通过并输出10086。C++ Primer 5th给出的解释如下:

    double value = 10086.233;
    const int tmp = value;
    const int &valref = tmp;
    

    在使用常量引用时因为只需要读取原变量的值,所以中间创建了一个用于隐式类型转换的临时变量,常量引用最终使用的值就是这个临时变量的值。那么这里就有个问题,下面两段代码的输出会是什么:

    int value = 10086;
    const &valref = value;
    value = value + 1;
    
    cout<<valref<<endl;
    

    输出:10087

    double value = 10086.233;
    const &valref = value;
    value = value + 1;
    
    cout<<valref<<endl;
    
    

    输出:10086

    得知这个消息我还是稍稍有点震惊的,不过一想这种代码估计除了这里其他地方就不会出现吧!类型隐式转换还是挺危险的。上述的都是基本类型的变量,下面来试一试复杂类型看看有没有真的产生临时变量:

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    class Double {
    private:
        double val;
    public:
        Double(double v = 0.0) : val(v) {
            cout<<"Double constructor with double value"<<endl;
        }   
        double value() const {
            return val;
        }   
    
        void change(double delta) {
            val += delta;
        }   
    };
    
    class Integer {
    private:
        int val;
    public:
        Integer(int v = 0) : val(v) {
            cout<<"Integer constructor with integer value"<<endl;
        }   
        Integer(Double& v) : val(v.value()) {
            cout<<"Integer constructor with Double"<<endl;
        }   
    
        int value() const {
            return val;
        }   
    };
    
    int main() {
        Double dnum(10086.233);
        const Integer &inum = dnum;
        dnum.change(1);
        cout<<inum.value()<<endl;
        return 0;
    }
    
    

    由于Integer类的构造函数中可以接受Double对象并且没有声明为explicit所以可以将Double类型隐式转换为Integer类型。当使用复杂类型再来看这个规则时,会更明白的发现为什么中间要产生一个用于类型转换的临时对象。因为类型不同使用不同的引用类型必然引起混乱,就像一个类型的指针指向不同的类型的存储区域一样(引用在汇编就是用的指针,机器并没有引用对应的专门指令)。而临时对象的产生使得引用变量和最初类型的变量脱离了关系(如果是基本类型,或者用于类型转换的构造函数中没有对内部结构进行共享)。

    这也是为什么const引用可以指向不同类型而一般引用不行,因为const引用只能读取对象数据,信息是单向流动(类型转换生成临时对象即可解决),而非const引用还可以修改原对象数据,是一个双向的过程,仅仅靠类型转换生成临时对象是无法解决的。其实觉得能够实现const引用不同类型已经是非常不错了,也没比较在推广下去,因为这种几乎钻牛角尖的语言特性反而使得C++变得难用易错。引用最一般的解释就是说是对象的别名,现在因为不同类型的转换而使得与原有对象可能脱离了联系,也就没有了变量“别名”的语义。

  • 相关阅读:
    利用FUSE编写自定义的文件系统
    ubuntu16.04 overlay 不支持redirect_dir开关
    ip rule实现源IP路由,实现一个主机多IP(或多网段)同时通(外部看是完全两个独立IP)
    段地址机制以及段地址转换触发segmentation falt
    sshfs+overlayfs实现一个共享只读资源被多个主机挂载成可写目录
    解析prototxt文件的python库 prototxt-parser(使用parsy自定义文件格式解析)
    arris1750 pandorabox安装bandwidthd之后带宽监控(nlbwmon)报资源不足
    工作中的C++问题汇总
    CMake相关代码片段
    编写合格的C代码(1):通过编译选项将特定警告视为错误
  • 原文地址:https://www.cnblogs.com/lailailai/p/4675831.html
Copyright © 2020-2023  润新知