Effective C++学习笔记

简介: 导读本书的最佳用途:彻底了解C++如何行为,为什么那样行为,以及如何运用其行为形成优势。size_t是一个typedef,是某种不带正负号的unsigned类型。

导读

本书的最佳用途:彻底了解C++如何行为,为什么那样行为,以及如何运用其行为形成优势。


size_t是一个typedef,是某种不带正负号的unsigned类型。

签名(signature):函数的参数和返回值。即std::size_t num(int number) 的签名是 std::size_t  ( int )

explicit可组织他被用来隐式转换(implicit type conversions),但他仍可以被进行显示转换(explicit type conversions)。

如图:

拷贝构造---以同型对象初始化自我对象。

拷贝赋值---从另一个同性对象中拷贝其值到自我对象。




1、让自己习惯C++

条款一:视C++为一个语言联邦

c++是四种语言的联邦


每种语言都有自己的规约。C++高效编程守则视状况而变化,取决于你使用C++的哪一部分。


条款2:尽量以const、enum、inline替换#define

也就是“宁可以编译器替换预处理”。因为define或许不被视为语言的一部分。



class专属常量:为了将常量的作用域(scope)限制于class内,必须让它成为class的一个成员(member);而为了确保此常量最多只有一份实体,必须让它成为static成员。

我们无法用#define创建一个专属常量。



menu hack:

1、menu hack的行为更像#define,而不是const。例如取const地址是合法的,而取menu hack或者#define的地址是不合法的。

2、如果不希望别人活得一个pointer或reference指向你的某个常量,enum可以帮你实现这个约束。

3、enum和#define绝不会导致没必要的内存分配,因为有的编译器会为const对象设定另外的存储空间。

4、enum hack是模板元编程的技术基础。


对于单纯的常量,最好以const对象或enum替换#define;对于形似函数的宏,最好用inline函数替换#define。



条款3:尽可能使用const



令函数的返回值为const,可以降低因客户错误而造成的意外。如下图:

class R{.....}
const R poerator *(const R& lns, const R& rhs)


两个成员函数如果只是常量性(constness)方面不同,可以被重载,如下图:


上图中non-const operator[]的返回值类型是char &,而不是char,因为如果函数的返回类型是一个内置类型,那么改动函数返回值就不合法(what?我不懂。。。吴老师:这个说法有点令人困惑。关键不是内置类型与否,而是值类型与否。需要修改内容,函数需要返回一个非const的引用。。。还是不太懂。。。)




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

int x;

c++语言中,上述语句在某些语境下,x被初始化为0,某些情况下x不被初始化。读取未初始化的值会导致不明确的行为。在某些平台上,仅仅只是读取未初始化的值,就可以让你的程序终止运行。


不要混淆赋值和初始化,如下图:


c++规定:对象成员变量的初始化动作发生在进入构造函数本体之前。

使用初始列进行初始化的效率更高,因为比起先调用default构造函数然后再调用copy assignment操作符,单只调用一次copy构造函数比较高效,有时甚至高效得多。

(1) 在初始化列表中,成员的初始化顺序和成员的声明顺序相同,与初始化列表的位置顺序无关。 
(2) 初始化列表先于构造函数执行。 

(3) 类中可以定义const成员变量,它并非真正意义的常量,而是只读变量,其初始化方式只能依靠初始化列表。

const只读变量虽然不可出现在赋值符号的左边,但是可以通过指针去修改其值的。(what???)

当你想要default构造一个成员变量,你都可以使用初值列,只要指定无物(nothing)作为初始化实参即可。如下图:


规定总是在初值列中列出所有成员变量,以免去记忆哪些成员变量可以无需初值。

如果成员变量是const或references,它就需要初值,而不能被赋值。具体原因详见链接:

https://blog.csdn.net/qq_29344757/article/details/76093216

在class 中,变量不能直接初始话,需要通过构造函数(或拷贝构造函数)来初始话,如果程序员没有定义构造函数系统会有一个默认构造函数。

除了静态数据成员外,数据成员不能在类体内显式的初始化




不同编译单元内定义之non-local static 对象







条款5:了解C++默默编写并调用哪些函数

当你写一个空类没有声明的时候,编译器会为他声明一个copy构造函数、一个copy assignment函数和一个析构函数。如果你没有声明任何构造函数,编译器会为你声明一个default构造函数。如下图:


当这些函数被调用的时候,编译器才会把他们创建出来。

注意:编译器产生的析构函数是一个non-virtual,除非这个class的base class自身声明有virtual析构函数(不明觉厉,先记下来)

如果类中你声明了一个带实参的构造函数,编译器便不会创建default构造函数,如此,你写的构造函数就不会被创建出来的无实参的default构造函数覆盖掉。






条款6:若不想使用编译器自动生成的函数,就应该明确的拒绝




驳回编译器自动提供的功能,可将相应的成员函数声明为private并且不予实现。或者使用上图所示的手法也可以(上图所示的手法感觉更好一点)



条款7:为多态基类声明virtual析构函数



如果一个类里面不含虚函数,通常意味着他并不意图被用做一个base class(基类)。当类不企图被当做base class,灵其析构函数为virtual往往是个馊主意。C++标准库中很多都是没有虚函数的class,比如string、vector、list等,所以不要把标准库中的类作为基类。

虚指针vptr(virtual table pointer)用于在运行期决定哪一个虚函数被调用。vptr指向一个由函数指针构成的数组,该数组称为虚表vtbl(virtual table):每个带有虚函数的类都有对应的虚表。

纯虚函数导致抽象类(不能被实例化(instantiated)的类)

虚析构函数的作用为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。(用C++开发的时候,用来做基类的类的析构函数是虚函数。

Class ClxBase
{
public:
    ClxBase() {};
    //虚虚构函数
    virtual ~ClxBase() {};

    virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};


条款8:别让异常逃离析构函数

析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能会抛出异常,析构函数应该捕捉任何异常,然后吞下他们(不传播)或结束程序。

如果客户需要对某一操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。


条款9:绝不在构造和析构过程中调用虚函数

class Transation {
public:
	Transation();
	virtual void logTransaction() const = 0;
	...
};

Transation::Transation() {
	...
	logTransaction();//构造函数调用虚函数
}

class BuyTransation :public Transation {
public:
	virtual void logTransation() const;
	...
};
上述代码执行:
BuyTransation b;

在该调用logTransaction时,调用的虚函数是base class中的,而不是BuyTransaction中的。

因为:base class(基类)构造期间,虚函数不会下降到derived classes(子类)阶层。由于base classes构造函数的执行更早于的derived classes构造函数,当base class 构造函数执行时derived class的成员变量尚未初始化,如果器件调用的virtual函数下降至derived classes阶层,而derived classes的函数机会必然去用local成员变量,而那些成员变量尚未初始化,因此无法调用derived classes中的虚函数,而是调用base class的虚函数。

更根本原因:在derived class对象的base class 构造期间,对象的类型是base class而不是derived class。不止虚函数会被编译器解析至base class,若使用运行期类型信息,也会把对象是为base class。

logTransaction是纯虚函数,除非他被定义,否则程序无法连接,因为连接器找不到必要的Transaction::logTransaction实现代码。

弥补措施:在构造期间,可以藉由“令derived classes将必要的构造信息向上传递至base class构造函数”。



条款10:令operator= 返回一个 reference to *this

赋值采用右结合定律,因此x=y=z=15等价于(x=(y=(z=15)))。为了实现“连锁赋值”,赋值操作符必须返回一个reference指向操作符的左侧实参。




条款11:在operator= 中处理“自我赋值”

“自我赋值”发生在对象被赋值给自己时。拷贝构造函数里面应该先判断一下传入的参数是不是自己。



条款12:复制对象时勿忘其每一个成分

如果你自己写拷贝构造函数,而不用编译器默认构造的拷贝构造函数,当你为class添加一个成员变量后,你必须同时修改拷贝构造函数,如果你忘记,编译器也不太可能会提醒你。一旦你给这个class添加了子类,而忘记修改基类的拷贝构造函数,子类就无法对从父类那里继承下来的变量进行赋值。

拷贝构造函数应该确保复制“对象内的所有成员变量”及“所有base class成分”。


不要试图使拷贝赋值函数调用拷贝构造函数,同时也不要试图让拷贝构造函数调用拷贝赋值函数。如果上述两个函数有重复代码,可以在private里面写一个函数供上述两个函数调用,该函数常备命名为init



条款13:对象管理资源


相关文章
|
2月前
|
算法 API 计算机视觉
[opencv学习笔记] jiazhigang 30讲源码C++版本(含Makefile)
[opencv学习笔记] jiazhigang 30讲源码C++版本(含Makefile)
26 0
|
3月前
|
缓存 网络协议 Linux
Linux C/C++ 开发(学习笔记十三):百万并发的服务器实现
Linux C/C++ 开发(学习笔记十三):百万并发的服务器实现
54 0
|
3月前
|
存储 关系型数据库 MySQL
Linux C/C++ 开发(学习笔记八):Mysql数据库图片存储
Linux C/C++ 开发(学习笔记八):Mysql数据库图片存储
50 0
|
3月前
|
关系型数据库 MySQL 数据库
Linux C/C++ 开发(学习笔记七):Mysql数据库C/C++编程实现 插入/读取/删除
Linux C/C++ 开发(学习笔记七):Mysql数据库C/C++编程实现 插入/读取/删除
49 0
|
3月前
|
网络协议 Linux C++
Linux C/C++ 开发(学习笔记十二 ):TCP服务器(并发网络编程io多路复用epoll)
Linux C/C++ 开发(学习笔记十二 ):TCP服务器(并发网络编程io多路复用epoll)
56 0
|
3月前
|
网络协议 Linux C++
Linux C/C++ 开发(学习笔记十一 ):TCP服务器(并发网络网络编程 一请求一线程)
Linux C/C++ 开发(学习笔记十一 ):TCP服务器(并发网络网络编程 一请求一线程)
39 0
|
2月前
|
算法 安全 C++
C++ Effective Modern Pointer (智能指针模块)
C++ Effective Modern Pointer (智能指针模块)
|
3月前
|
Web App开发 网络协议 Linux
Linux C/C++ 开发(学习笔记十 ):实现http请求器(TCP客户端)
Linux C/C++ 开发(学习笔记十 ):实现http请求器(TCP客户端)
49 0
|
3月前
|
存储 网络协议 Linux
Linux C/C++ 开发(学习笔记九 ):DNS协议与请求的实现
Linux C/C++ 开发(学习笔记九 ):DNS协议与请求的实现
55 0
|
3月前
|
关系型数据库 MySQL Linux
Linux C/C++ 开发(学习笔记六):MySQL安装与远程连接
Linux C/C++ 开发(学习笔记六):MySQL安装与远程连接
47 0