【Effective C++ 学习笔记】

本文详细讲解了如何通过const、enum、inline避免宏定义的问题,强调了const的使用、对象初始化的重要性,以及如何处理构造、析构函数的细节。还讨论了inline函数、异常安全、资源管理、模板和继承的最佳实践,帮助读者写出更高效、安全的C++代码。
摘要由CSDN通过智能技术生成

条款02:尽量以const,enum,inline替换 #define

#define定义的常量也许从未被编译器看见,也许在编译器开始处理源码之前它就被预处理器移走了;

#define不重视作用域,不能利用define创建一个类常量,故不能提供封装性;

#取一个const的地址是合法的,但取一个enum和define的地址不合法

#define定义函数可能招致误用,最好用inline函数替换

条款03:尽可能使用const

常量指针:const int *p=&a; 此处const修饰*p,指针指向的值不可改变,指针指向可变。

指针常量(引用):int* const p=&a;此处const修饰p,指针指向不可变,指针指向的值可变

将某些东西声明为const可帮助编译器侦测出错误用法(不小心对const常量进行更改后编译器会报错)

条款04:确认对象被使用前已先被初始化

#为内置型对象进行手工初始化;内置类型以外,构造函数负责初始化责任

#构造函数最好使用初始化列表 ,而不使用赋值操作C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前)

初始化列表 A::A(int age):m_age(age){}   这样效率较高

赋值操作 A::A(int age){m_age=age;}       这种赋值版本会首先调用默认构造函数,然后再对变量赋新值,默认构造的一切作为都浪费了。

#使用初始化列表进行初始化时,其变量初始化排列次序应该与他们在类中声明的次序相同

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

#如果自己不声明,编译器会自动给类创建默认构造函数,默认拷贝构造函数,默认拷贝赋值函数,默认析构函数。这些函数都是public且inline的。

#父类如果把拷贝构造函数或拷贝赋值函数设置为private,子类将拒绝生成拷贝构造函数和拷贝赋值函数

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

为了不使用默认拷贝构造、默认拷贝赋值函数和阻止其他人调用构造函数和赋值函数,你可以自己写拷贝构造函数和赋值构造函数,并将其声明为private并不定义。或者继承Uncopyable类。(条款05)

条款07:为多态父类声明virtual析构函数

#发生多态时,父类中的析构函数应该设置为虚析构函数或纯虚析构函数。(使用多态时,若子类有属性开辟在堆区,父类指针在释放时无法调用到子类的析构代码,也就无法利用父类指针去释放子类中开辟在堆区的数据,造成局部释放不干净)

#若不发生多态(子类无堆区数据),父类不需要虚析构函数。因为虚函数使类体积变大,虚函数内含虚函数指针指向虚函数表。

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

#析构函数绝对不能抛出异常。若析构函数可能发生异常,析构函数应该捕捉异常并吞下异常或者直接调用abort()结束程序来阻止异常传播防止发生不明确行为。

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

#构造子类对象时,首先调用父类构造函数初始化对象的父类部分。此时,对象的派生类部分是未初始化的。析构子类对象时,首先析构其派生类部分,然后析构其基类部分。因此,在运行构造函数或者析构函数时,对象都是不完整的。  如果在构造函数或者析构函数中调用虚函数,这种情况下的虚函数调用不会调用到外层子类的虚函数。

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

#为了实现连续赋值    eg).cout<<a<<b<<c<<endl;

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

#自我赋值:class A; A a; a=a;

#处理自我赋值的方法:在operator=中加“证同测试”检验是否为自我赋值(if this=&this) return *this;

#在复制一个指针p指向的东西之前别删除p,留作备份。复制成功后可删除。

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

#当自己写拷贝构造函数和拷贝赋值函数时,请确保复制对象的所有成员变量和父类的成员变量

#不要在拷贝构造函数里面调用拷贝赋值构造函数,不要在拷贝赋值构造函数里面调用拷贝构造函数(eg.构造函数用来初始化新对象,而拷贝赋值函数只能作用于已经初始化的对象上,此时还没初始化好呢)

条款13:以对象管理资源

#智能指针:shared_ptr(类对象,内部追踪共有多少个对象指向某资源。若某对象不指向资源了,智能指针内部引用计数减一,当某个对象内部的引用计数为0时会释放这个资源,在其析构函数内delete)

条款14:在资源管理类小心copy行为

条款15:在资源管理类中提供对原始资源的访问

#shared_ptr提供get成员函数,返回智能指针内部的原始指针,同时也重载了指针取值操作符(*ptr和ptr->)

条款16:成对使用new和delete要采用相同的格式

#new/delete   new[]/delete[]

条款17:以独立的语句将newd对象置入智能指针

条款18:让接口容易被正确使用,不易被误用

#unique_ptr比auto_ptr好。

        一、auto_ptr<string>p1(new string("hyb"));  auto_ptr<string> p2=p1;//合法的。p2接管了string对象的所有权,p1的所有权被剥夺,这样可以防止重复释放堆区内存。但是若程序随后试图使用p1会报错,因为p1不再指向有效的数据。

        unique_ptr<string>p1(new string("hyb")); unique_ptr<string> p2=p1;//不合法的。避免了p1不再指向有效数据的问题。编译阶段错误比潜在的程序运行阶段崩溃更安全。

        二、使用new/new[]分配内存时,能用unique_ptr。使用new分配内存时只能用auto_ptr和shared_ptr。

#shared_ptr提供的某个构造函数接收两个参数,一个是被管理的指针,另一个是引用次数变成0时将被调用的删除器。

#shared_ptr基于”引用计数”模型实现,多个shared_ptr可指向同一内存,并维护一个共享的引用计数器,记录了指向同一内存的shared_ptr数量。当最后一个指向内存的shared_ptr销毁时,会自动销毁其所指对象(通过delete操作符)。

#要注意循环引用带来的内存泄漏问题。如下面A与B循环引用,导致内存泄漏。解决办法为weak_ptr。

eg).shared_ptr<Node> p1(new Node(1));   shared_ptr<Node> p2(new Node(2));(引用计数为1)

      p1->_next = p2;   p2->_prev = p1;(双向链表,此时引用计数为2)

      释放p2时,引用计数变为1(!=0),不能释放。释放p1时,引用计数变为1(!=0),不能释放。

    通俗点讲:就是p2要释放,那么必须等p1释放了,而p1要释放,必须等p2释放,所以,最终,它们两个都没有释放空间。造成内存泄漏

条款19:设计class犹如设计type

条款20:宁以pass-by-refrence-to-const替换pass-by-value

#按值传递参数实际上是传递实际参数的副本,获得的函数返回值实际上也是一个副本,这带来了额外的时间消耗。

#按值传递类对象时会额外调用对象的构造函数,当函数返回时临时对象被销毁,调用了析构函数。尤其是有继承关系时,父类构造函数析构函数也会被调用。多做了很多无用功。

#因为引用传递实际上是传递指针,若传递的对象为内置数据类型、STL迭代器、函数对象,按值传递的效率会高一些

条款21:必须返回对象时,别妄想返回其reference

条款22:将成员变量申明为private

#将成员变量声明为private,提供公共接口(set,get)给外部使用。好处在于能赋予客户访问数据的一致性,若不声明为private,就会有无限量的函数可以访问到数据,也就毫无封装性可言。

条款23:宁以non-member,non-friend替换member函数

#愈多的函数可访问数据,数据的封装性就愈低,因此成员函数的封装性比非成员函数的封装性差,拿非成员函数替换成员函数可以增加类的封装性。因为非成员函数访问不到类的私有变量,而成员函数能访问私有变量。少一个成员函数也就少一个函数能访问私有数据,封装性也就更好。

条款24:若所有参数都需要类型转换,请为此采用non-member函数

条款25:考虑写一个不抛出异常的swap函数

#C++只允许类模板偏特化,不允许函数模板偏特化

#当全局交换函数std::swap()对我需要交换的类型来说效率很低时,应该在类型中提供一个自身的swap()成员函数,并确定这个函数不会抛出异常

条款26:尽可能延后变量定义式出现的时间

#在需要变量的时候才定义它,防止出现未被使用的变量,造成额外成本(例如避免多余的构造函数和析构函数)

条款27:尽量少做转型动作

#C语言类型转换: (T)expression 和 expression(T) ----> 将expression转换为类型T

#C++类型转换:转换后应该用变量接收,原变量数据类型不变,接收的变量为希望转变成的类型。且转换后的数据类型空间必须要能够存储原数据类型所需空间大小。

        const_cast<T>(expression):  expression原本被const修饰,现在利用const_cast删除const,使之可被修改。expression和T的类型必须相同。PS:const_cast可修改被const修饰的指针,但如果这个指针指向的为const常量,就不能删除const,即不能修改。

        dynamic_cast<T>(expression): 只用在类层次结构中进行向上转换,子类指针转换为父类指针。向下转换安全,有类型检查。

        static_cast<T>(expresssion):只能在相互有联系的类型中进行转换。比如可将子类对象指针转换为父类对象指针(安全),也可将父类对象指针转换为子类对象指针(无类型检查,不能确保转换后的子类指针真的指向子类对象,不安全)。主要用于隐式转换,将Int转为double,将任何类型的表达式转为void

        reinterpret_cast<T>(expression):用于天生危险的类型转换。例如将int指针转为类指针

条款28:避免返回引用、指针、迭代器指向对象内部成分

#如果你返回引用或者指针或者迭代器指向类对象内部数据,这样外界会通过你返回的引用、指针、迭代器来修改类对象内部数据,破坏了类对象的封装性。若类对象内部数据为const,这样就矛盾了。

条款29:为“异常安全”而努力是值得的

#异常安全函数保证发生异常时,也不会有资源泄漏和数据结构破坏。

条款30:透彻了解inline函数的里里外外

#inline函数:看起来像函数且动作像函数,可调用他们又不需要负担函数调用所造成的额外开销。对inline函数的每一个调用都以函数本体替换之,这样会增加程序体积,故而inline函数本体代码需要比较小。即使拥有虚内存,inline函数也会导致额外的换页行为,降低缓存命中率。

#inline函数适用于小型且被频繁调用的函数身上,不能用在循环和递归函数里。inline函数一般放在头文件中定义,方便编译器认识它

#定义在类声明之中的成员函数将自动地成为内联函数。如果inline函数代码很长,编译器也会自动去掉inline

条款31:将文件的编译依存关系降到最低

条款32:确定你的public继承塑模出is-a模型

#public继承意味着is-a。适用于父类身上的每一件事情也一定适用于子类身上,因为每一个子类对象也都是一个父类对象

条款33:避免遮掩继承而来的名称

#子类里的同名数据会遮掩父类中同名数据,为了让被遮掩的数据重见天日,可使用using(例如:在子类中使用using Base::f())

条款34:区分接口继承和实现继承

#包含纯虚函数的类称为抽象类,无法实例化对象,而且其子类必须重写抽象类中的纯虚函数,否则子类也无法实例化对象

#子类重写父类虚函数时发生多态,多态优点:代码组织结构清晰,可读性强,利于前后期的扩展以及维护。

条款35:考虑virtual函数以外的其他选择

#虚函数的替代:策略模式

条款36:绝不重新定义继承而来的non-virtual函数

#虚函数动态绑定,非虚函数静态绑定。eg).

class Base{void:f();};   class Son: public Base{void f()};//不建议重新定义父类中的非虚函数

Base* b=new Son;    b->f();调用的是父类中的f()

Son* s=new Son;      s->f();调用的是子类中的f()

PS:当父类中的f()为虚函数时,b->f()和s->f()都调用的是子类中的f();

条款37:绝不重新定义继承而来的缺省参数值

#父类中静态绑定的在子类中不能被重新定义(非虚函数),父类中动态绑定的在子类中能重新定义(虚函数)

条款38:通过复合塑模出has-a或者”根据某物实现出”

#is-a:子类public继承父类。has-a:类中嵌套其他类

条款39:明智而审慎地使用private继承

#私有继承时,父类所有数据在子类中都变成私有数据

条款40:明智而审慎地使用多重继承

#多重继承会导致钻石继承(菱形继承):羊类和驼类继承于动物类,羊驼类又继承自羊类又继承于驼类。问题1:动物类有属性age,则羊类和驼类都会有age,需要加以作用域来区分;问题2:羊驼类继承了羊类的age,又继承了驼类的age,实际只需要一个age,导致资源浪费。

#解决钻石继承的方法:1.虚继承(羊类和驼类虚继承自动物类),但是虚继承会导致类对象体积变大,访问成员变量速度慢;2.拒绝使用多重继承。

条款41:了解隐式接口和编译期多态

条款42:了解typename的双重意义

#template<class T>等同于template<typename T>

# typename被用来验证嵌套从属类型名称   

     template<class T>

     void f(const T& container){T::const_iterator it=container.begiin();}//不合法

it的类型取决于模板参数T,it被称为嵌套从属名称。it只有在T::const_iterator是一个类型时才合理,而我们没有告诉C++,编译器会默认它不是一个类型。解决办法为在代码前加关键字typename(typename T::const_iterator)

条款43:学习处理模板化基类内的名称

条款44:将无关参数抽离template

#template<class T,int a>  其中T为类型模板参数,a为非类型模板参数。非类型模板参数造成的代码膨胀,以函数参数或者类成员变量替换template参数

条款45:运用成员函数模版接收所有兼容类型

#类里可以使用成员函数模板(成员函数也是一个模板函数)。当类中有了拷贝构造函数模板和拷贝赋值函数模板,仍然需要正常的拷贝构造函数和拷贝赋值函数。

条款46:需要类型转换时请为模版定义非成员函数

条款47:请使用traits classes表现类型信息

#iterator_traits 可判断迭代器的类型。is_fundamental<T>判断T是否为内置类型。is_array<T>判断T是否为数组类型。

条款48:认识模板元编程:template metaprogramming   TMP

#TMP执行于C++编译期,因此可将工作从运行期转移到编译期。好处在于:(1)某些错误原本在运行期才能侦测到,现在可在编译期找出来;(2)使用TMP的C++程序在各方面都更加高效:较小的可执行文件,较短的运行期,较少的内存需求。坏处在于:编译时间大幅度增加。

#TMP解决阶乘问题的实例:TMP(模板元编程)实例_小王子殿下是个大帅哥的博客-CSDN博客_tmp编程

条款49:了解new-handler的行为

#当申请内存不够时,会不断调用new-handler函数,要么释放一部分无关紧要内存直到找到内存,要么abort()或exit()。

#set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用

条款50:了解new和delete的合理替换时机

#new内为operator new(),可重载。delete内为operator delete(),可重载。自己写new/delete的好处:改善效能(malloc次数太多会增加额外cookie开销)、收集heap使用信息、对heap运用错误进行调试。

#为啥要内存对齐:合理使用内存对齐,可节省内存;减少CPU访问数据出错次数(有些CPU要求必须内存对齐,否则访问数据会报错);减少CPU访问数据次数,可更快读取数据,提高访问速度。

条款51:编写符合常规的new和delete

#operator new()实际上不止一次尝试分配内存,其内部有一个死循环,若分配内存成功,返回一个指针指向那个内存,若失败就会调用new handle尝试释放一部分无关紧要的内存,如果还不行,就抛出异常(std::bad_alloc)。

#如果客户申请0字节内存,operator new()也应该要有能力来处理0字节内存的申请,处理方法为内部将0字节视为1字节。

条款52:写了placement new也要写相应的placement delete

#Person* p=new person;//先调用operator new,再调用person的默认构造函数。

#  string* p=new string[100];   //p指向提前分配好的内存

    string*q=new(p) string; //不需要再分配内存了,而是放进已经分配好了的内存中(p指向的那块)

    delete[] p; 

#当你写一个placement operator new,请确定也写出了对应的placement operator delete。如果没有这样做,程序可能会发生内存泄漏

条款53:不要轻忽编译器的警告

条款54:让自己熟悉包括TR1在内的标准程序库

#shared_ptr循环引用:std::tr1::shared_ptr会记录有多少个shared_ptr共同指向同一个对象(引用计数),当某对象的引用次数为0时,对象调用析构函数自动删除。这在非环形数据结构中防止资源泄漏很有帮助。但如果两个或多个对象内含shared_ptr并形成环状(p1->next=p2;p2->next=p1),会造成每个对象的引用次数都超过0,无法释放对象。

#解决shared_ptr:std::tr1::weak_ptr。weak_ptr并不参与引用计数的计算,当最后一个指向对象的shared_ptr被销毁,即使还有weak_ptr继续指向同一对象,该对象仍然会被删除。

条款55:让自己熟悉Boost

#Boost是一个C++程序库,里面很多代码被加入C++标准库。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值