Effective C++学习笔记(条款1-34)

博主分享了阅读《Effective C++》的体会,强调了理解C++的联邦特性、使用const、正确初始化对象、避免#define、理解编译器默认生成的函数、智能指针的使用、接口设计和继承策略的重要性。文章详细探讨了每个条款的关键点,并提醒读者在资源管理和异常安全方面要格外小心。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 开场白

《Effective C++》这本书一直觉得有些难度,已经反复看了好几次了,每次看都能发现一些以前没有注意到的知识点。建议在看这本书前先看看《C++编程思想》或者是《C++ Primer》,另外,如果看过一些《设计模式》或者《敏捷设计开发》的会稍微好些,书中一些条款涉及了设计模式,虽然并不是在讲设计模式,但是一些设计模式中的思维模式影响了这些条款的内容。

最后,我不知道是我个人语文水平不行的原因,还是说侯捷大大翻译的原因。我总觉得这本书翻译的有些拗口,不像我以前读《STL源码剖析》时候那么有感觉,我后面也读了《More Effective C++》,也是侯捷大大翻译的,感觉理解起来也是很吃力,一些语句得停下来一点点的抠才知道他说的是什么意思。额,不提这些了,接下来就整理一些条款的心得体会。


2. 记录

条款1. 视C++为联邦语言

大意就是C++很难,但是很通用,学会了C++学其他语言都不吃力,要好好学C++。

条款2. 尽量以const,enum,line替换#define

首先说明#define,其作用就是做一些宏定义,这些宏定义在编译过程(更确切的说是预处理过程)中会进行宏展开,而所谓的宏展开就是“文本替换”,在linux下可以使用:gcc -c input.c -o output  查看一个宏展开后的结果,更详细可以翻阅《链接、装载与库》这本书,书中详细介绍了预处理过程。
既然#define定义的内容在编译期间会被宏展开,它好处很明显,可以让代码便于阅读,但是又不会带来函数调用的开销。那它有什么缺点:
  • #define没有作用域的概念,也就是说namespace和class的作用域限定对于#define定义的宏是不起作用的,可以看下面的这个代码。
namespace Foo {
#define TEMP "hello world"
}

class Base {
#define NAME "Base"
};

int main() {
    cout << NAME << TEMP << endl;
    return 1;
}
  • #define在预处理过程中处理,不利于调试。#define一旦宏展开,宏名称再也找不到了,那调试的时候并不会告诉我们是宏定义出现了错误。例如下面的这个例子,:
#define MAX(a, b) (a > b ? a : b)

int main() {
    int a = 1, b = 1;
    cout << MAX(++a, b) << endl;
    return 1;
}
这里的++a被替换了两次,所以宏展开以后的结果是:(++a > b ? ++a : b),这样一看就知道有什么问题了,但是没有调试的时候却很不容易发现这种问题。

这样就很容易理解为什么希望用const、enum和inline替换#define了吧。
const和enum可以定义常量而且还可以先定这些常量的作用域范围。
用inline则可以定义函数式的宏,却不会引起上述这样的问题。

条款3. 尽可能使用const

首先需要熟悉这里的const会出现在哪些地方,const虽然总体来说是常量(不愿意被修改),但是在不同的位置中语义又存在一些细微的区别,下面列举了一些const出现的位置,在不同的位置出现的位置,其中关注几点:
  • 它修饰的对象是谁?
  • 它的作用域范围?
  • 它的语义是什么?
在《C++编程思想》一书中对const进行了详细的描述,具体内容可以翻阅这本书,这里就不赘述了。
const int a = 1;
const static int b = 2;
class Base {
public:
    const string &getName(const string &index) const {
        const int tmp = 10;
    }
    const static string name = "Base";
    const int idx;
    const int * const foo;
};
使用const的理由有:
  • 避免作为等式左值,虽然稍微熟悉点的程序员都会避免这么做,但是不排除在写条件语句的时候不小心将“==”错误的输入成了“=”,当然我也见过初学者直接将这个作为函数左值的情况出现。
  • 告诉调用者以及自己,使用const修饰的这个对象希望被保护起来,我们不愿意其被修改。虽然在这些情况下不使用const也是可以的,但是问题就在于总有那么些人和自己捣乱,与其一次次的和他们说这里不能动,那里不能改,不如事先用const修饰一下,然后其用的时候就知道“哦,这里用const修饰了,我不应该去改它”。
但是const的保护并不“完美”,它没办法避免一些人别有用心的去修改它,感觉它就和互斥锁一样,仅仅只是大家见的“君子协议”,如果真的有人不遵守,那就没办法了。所以,在后面的条款28就告诉我们:既然我们没办法阻止一些人总希望绕过const来修改内部的值,我们就尽量不要将对象内部数据的引用返回给调用者,这样用private保护起来的对象属性,外部就无法获取到了。

条款4. 确定对象使用前已被初始化

在C++中,有几个术语的定义总容易被搞混,比如“声明”、“定义”、“初始化”,这些术语的定义有些精细,如果没有弄清楚这些词具体说的是什么,那看《C++编程思想》《Effective C++》这种书的时候就有些吃力,因为有许多地方作者精确描述了它们,但是我们却没有理解作者在说什么。
“初始化”这个词在不同的场景下的语义有些细微的区别:
  • 如果对一个类对象说初始化,意思主要是指这个类已经“准备完毕”,可以正常工作,那这里的准备完毕就有很多含义了,比如tcp客户端的套接字的“准备完毕”--可能指的就是已经与服务器端完成链接,也已经完成服务器端的地址信息配置,也可能是指已经套接字已经申请完成,这里的初始化语义就很丰富了,总体来说,只要这个对象在后面的代码中可以正常调用了,那就是已经初始化完成了;
  • 但是对一个对象内部的成员属性的初始化定义就十分精细:在成员初始化列表中完成初始化过程。
作者在条款4中,作者前后讨论的其实就是两类情况下的初始化:类对象实例内部成员属性的初始化;相互依赖的类对象间的初始化。
  • 对于类对象内部的成员属性的初始化,第一个需要注意的就是构造函数内部的过程并不是”初始化“,初始化这时候已经完成,确切的初始化应该是在初始化成员列表中的初始化;第二个需要注意的就是,内置对象(int、char、long、double等)的初始化值是不明确的,根据具体的编译器相关,所以最好在初始化列表中进行初始化,对于引用,const变量只能在初始化列表中进行初始化,对于是一个类对象的成员属性,如果不希望调用默认构造函数,那也需要在初始化列表中初始化(为了保证效率);第三个需要注意的就是初始化的顺序问题,基类首先被初始化(如果存在的话,如果是多重继承,那先初始化虚继承,然后再依次初始化),然后就是依据声明的次序依次初始化,如果初始化列表中存在定义,则调用初始化列表中的方法进行初始化,如果没有在初始化列表中初始化,那对于基本类型就根据编译器而定,类对象则
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值