• C++的优秀特性1:引用


    (转载请注明原创于潘多拉盒子)

    一本典型的C语言教科书的厚度大约是200页左右,而一本典型的C++教科书的厚度至少要500页。比如K&R的《The C Programming Language》的厚度是272页,而权威性于此大致相当的Stroustrup的C++教科书《The C++ Programming Language》的厚度是1019页,后者是前者的3.75倍。这给C++工作者带来了沉重的负担,纯记忆这些内容就已非易事,能深入理解则消耗更多的实践,如果想把C++的每一个特性熟练恰当的运用,则近乎天方夜谭。

    这源于C++的特性之复杂。为了在底层上兼容C并保持较高的运行效率,C++尽管号称对语言本身的特性进行了大大的限制,然而实际的结果并不理想。这里有大量的特性是程序员无法准确把握的,或者会造成误解,容易引起错误的。比如C++的默认参数特性,这个特性设计的本意是为了让C++的接口调用者使用起来更简单,或者对接口保持向下兼容,然而实际的效果并不理想。需要考虑的情况实在是太多了。对于一个含有默认参数的调用者来说,他需要搞清楚默认的参数是哪个,默认的情况下参数取值是什么,这些取值很多情况下并不是显而易见的,每到这样的一个地方就需要仔细的检查,看看是不是与接口设计的一致。非常容易发生错误。默认参数的个数还可能超过一个,这种情况下还夹杂类型隐式转换,实在是让人头疼不已。

    因此不难看出,C++并非每一个特性都适合在实际中使用,而其中大量的特性,其实是实际中不适合使用的。因此很有必要有一个详细的介绍,能将C++中的优秀特性圈出来,让程序员在实际开发时优先选择这些优秀特性,而对其它C++特性的使用则采取谨慎的态度。

    如果你之前用过别的高级语言,如Java/Python之类的,那么C++的引用特性可能有一部分是容易理解的,而有一部分则是有所不同的。

    int a = 0;
    int& b = a;    // b是a的引用,对b的所有操作等同于作用到a上,包括作为左值和右值
    b = 10;    // 此时a = 10,b = 10
    int* c = &b;     // c持有的是a的地址,也是b的地址
    

    这里的int& b定义了一个引用,需要立刻将被引用的对象作为右值赋值。

    这段程序至少可以得到以下几个结论:

    1. 引用实际上是一个别名,相当于对变量换了个名字,其余不变(包括变量的地址)。
    2. 引用一旦指向一个对象,则这种指向关系不能变更。
    3. 对引用的操作等同于对它引用的对象进行操作。
    4. 引用的生命期包含于被引用对象的生命期(引用生命期开始晚于被引用对象生命期开始,结束则更早)。
    5. 引用不同于指针之处在于,引用关系是不能变更的;而指针的指向关系是可以变更的。实际上,一个引用大致相当于定义了一个const指针(int* const b = &a;)。
    6. 引用不能指向一个空对象(null)。

    这里的引用和Python相同的点是#1,#3,#4;不同之处是#2,#5,#6。

    引用出了可以定义变量之外,还可以作为形参。当引用作为形参时,传入的对象不会被拷贝,二是直接拷贝了地址。如:

    int createFile(const std::string& filePath)
    {
        // 以filePath作为文件名创建文件
      return 0; }

    这里的filePath是一个std::string类型的对象,这个对象作为行参传入时,不会被复制,从而提高了效率。实际上,往往比这一点更重要的是,有些对象是无法复制的,比如锁、单例对象等。

    当使用引用时,一个const修饰符往往是必要的。只要对象不需要在后续的代码中修改(mutate),那么就可以给该对象的引用加上const修饰符。关于const修饰符使用的场景,后面会介绍。

    了解了一个特性之后,就有一个很重要的问题:什么场景下使用这个特性?总结下来看,对引用的使用,可以归结为以下几点:

      1. 对复杂表达式创建别名,提高可读性,降低思维成本。

    std::vector<std::string> fields;
    // 创建fields,填充值,这往往来自于一个parse操作,或者是一个split操作
    const std::string& username = fields[0];    // 这里定义了一个别名,相比记忆fields[0]这种带脚标的表达式,实在是容易多了。
    const std::string& age = fields[1];    // 还可以定义更多的别名
    // 使用username和age,显著降低了思维成本,提高了代码的可读性
    

      2. 在形参中引用复杂的对象,避免对象拷贝。

      3. 返回一个对象的引用,避免拷贝。根据结论#4,返回的对象的生命期需要包含调用者取得引用的生命期。因此返回一个局部变量是不允许的。但可以返回一个成员变量、static对象、全局对象。

      4. 作为形参传入,用于保存函数对该形参的修改。这通常适合需要多返回值的情况,或者返回值是复杂对象,切不满足上述第3条,不是成员变量、static对象、全局对象。

    然而,好的特性并不是可以被滥用的,如果定义一个下述的形参:

    int nextNumber(const int& n)
    {
        return n + 1;
    }
    

     则是完全不必要的,因为int型是一个基本类型,不是复杂类型,这样做就是“画蛇添足”。

    为了让读者能专注在C++的特性上,这里的例子都是尽可能简单的。

  • 相关阅读:
    Jessica's Reading Problem POJ
    FatMouse and Cheese HDU
    How many ways HDU
    Humble Numbers HDU
    Doing Homework again
    Stacks of Flapjacks UVA
    Party Games UVA
    24. 两两交换链表中的节点
    面试题 03.04. 化栈为队
    999. 可以被一步捕获的棋子数
  • 原文地址:https://www.cnblogs.com/bqzhao/p/3537533.html
Copyright © 2020-2023  润新知