Effective C++阅读笔记

首先着重推荐这篇bolg,大神级总结https://blog.csdn.net/thefutureisour/article/category/1230423

条款1.C++和C,它是一个语言联邦,有很多东西一起存储

 C++ 是由C、面向对象、模板和STL组成,我们是站在C这个巨人的肩膀上进行的,C++ 引入类和对象的概念,进行了封装和抽象,非常重要的一点。模板由编译器自动执行,减少了代码的
冗长,提供复用性,不用提供所有的类型函数的重载版本。STL提供了优秀的C++模板库,以便程序员进行使用,特别棒。

条款2. 尽量以const、enum、inline替换#define,即少用全局变量

一、const和enum替换#define

1. 编译器处理方式不同
  • define宏是在预处理阶段展开。
  • const常量是编译运行阶段使用。
2. 类型和安全检查不同
  • define没有类型,不进行类型检查,仅仅是展开
  • const有具体的类型,在编译阶段会执行类型检查
3.存储方式不同
  • define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存
  • const会在内存中分配(堆或者栈)
4.const可以节省空间,避免不必要的内存分配

const定义常量从汇编的角度来看,是给出了对应的内存地址,而不是和define一样是个立即数,所以cosnt定义的常量在程序运行过程中只有一份拷贝,而define定义的常量在内存中有若干个拷贝

  例: 
  #define PI 3.14159 //常量宏  
  const doulbe Pi=3.14159; //此时并未将Pi放入ROM中 ......  
  double i=Pi; //此时为Pi分配内存,以后不再分配!  
  double I=PI; //编译期间进行宏替换,分配内存  
  double j=Pi; //没有内存分配  
  double J=PI; //再进行宏替换,又一次分配内存!
5.const提高了效率
6.有些集成IDE的环境可以对const进行调试,而define是不能进行调试的
二、用inline替换带参的宏

带参的宏函数只是简单的进行替换,用起来四处危机,尤其是展开的时候,特别容易出错,bug不断

条款3.尽可能的使用const,const大法好呀

1. 修饰变量,不改表
2. 修饰指针
const int* p = &a;/* p为指向常量的指针,即p指向的对象是常量,不可以通过*p = 3 来修改a的值,但这时p = &b换个指向还是可以的 */

int* const p = &a; /* p为常量指针,即p的指向不可更改,不可以通过p = &b来修改p的指向,但这时*p = 3改变a的值还是可以的 */

const int* const p = &a; /* p为指向常量的常量指针,p的指向以及指向的对象都不可以更改,无论是*p = 3,还是p = &b都是错误的 */
3. 修饰函数的形参或者函数的返回值,保证传入的实参不在函数内发生改变,保证了安全
4. 修饰迭代器

const vector::iterator表示这个迭代器的指向不可以更改,即表示的是常量迭代器

5. 在类中修饰成员函数,一般放在成员函数的后面,保证这个函数不会改变类的成员变量
  • 重要:有无const是可以构成成员函数的重载的!,但是这种重载的相似度一般是非常高的,改动一个函数体内的内容,另一个就要同步更新,万一更新,会产生错误。
这种方式是可取的:
char& operator[] (size_t position)
{
   return const_cast<char&>(
     static_cast<const TestBlock&>(*this)[postion]);
}

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

-(1) 为内置型对象进行手工初始化,因为C++不保证初始化它们;

-(2) 构造函数最好使用成员初始化列表(实际初始化顺序不与列表的排列顺序有关,只取决于类中的声明顺序),而不要在构造函数体内使用赋值操作;

-(3) 未避免“跨编译单元的初始化次序”问题,请用local static代替non-local static对象。 不要使变量有全局的作用域,用局部static

1.内置类型

c++的内置类型在定义时,会给你随机分配一个值,初值都是垃圾值,那么最佳的处理的方法在使用对象之前都将之初始化

2.STL,容器中写好了构造函数,自动初始化

3.自定义了类

尽可能使用初始化列表,

初始化成员变量的顺序与列表排列的顺序没有关系,只是取决于声明这些成员变量的顺序,下面的代码中就是先a,后b,然后text,因此一定要注意写顺序。

建议初始化类别中最好总是以声明的次序进行初始化
class A
{
private:
         int a;
         double b;
         string text;
public:
         A():a(0), b(0), text("hello world"){} //构造函数
};

槽的方法,如下:

class A
{
private:
         int a;
         double b;
         string text;
public:
         A(){
            a = 0;b = 0;text = "hello world";
         }
};
为什么很糟呢?

因为在进入构造函数的函数体时,这些成员变量已经被初始化了,a和b初始化成垃圾值,string因为是STL,调用默认的构造函数初始化为空字符串,在函数体内进行的操作实为“赋值”,也就是用新值覆盖旧值。这也正是说它的执行效率不高的原因,既进行了初始化,又在之后进行了赋值,

条款5. C++默默调用的函数

  • 构造函数、析构函数、拷贝构造函数、赋值构造函数
为什么赋值构造函数里的形参是加引用的?
  1. 引用修饰形参时,可以避免实参对形参的拷贝,一方面可以节省空间和时间资源,更为重要的是若实参对形参拷贝了,又会调用一次拷贝构造函数,这样拷贝构造函数就会一遍又一遍的被调用,造成无穷递归。
  2. 引用修饰返回值时,可以使返回的对象原地修改
EmptyClass a(b); // 调用的是拷贝构造函数
EmptyClass a = b; // 调用的是拷贝构造函数

EmptyClass a;
a = b; // 调用的是赋值运算符
特殊情况,成员变量是const或者是引用,编译会错,是拒绝生成默认的构造函数和赋值构造函数的

这是由于const和引用会在需要声明的时候进行初始化,而编译器提供的默认构造函数是无法做到这一点的,只能显示的调用构造函数进行。

条款6. 可以拒绝编译器为类自动生成函数,不让外部类来构造对应类的对象

  • 将不想被编译器的生成的函数写成private或者是protected,只能在类内部被访问,还有友元,如下:
class HomeForSale
{
private:
         HomeForSale(const HomeForSale& house){
  ...}
         HomeForSale& operator= (const HomeForSale& house){
  ...}
};
  • 如果是友元,是可以访问到private下的成员函数的,如果不想那么将定义变为声明就可以了(但是这种方法还是不太好,因为函数声明编译会过,但是链接需要函数进行实现,链接就会报错,毕竟错误越早发现越好)
class HomeForSale
{
private:
         HomeForSale(const HomeForSale&);
         HomeForSale& operator= (const HomeForSale&);
};
  • 可以继承的方法,限制拷贝,其实也是通过继承将访问修饰符变化
class Uncopyable {
public:
    Uncopyable() { }
    ~Uncopyable() { }
private:
    Uncopyable(const Uncopyable&);
    Uncopyable& operator=(const Uncopyable&);
};

class HomeForSale : private Uncopyable {
    ...     //这个时候该class就不要再声明copy构造函数或copy assignment操作符了!
}

这样,任何人,甚至是member函数跟friend函数尝试拷贝HomeForSal对象的时候。编译器就试着生成HomeForSale的拷贝构造函数跟赋值操作符。但是这个时候要先调用基类的拷贝构造函数和赋值操作符。这时候,编译器就会拒绝这样调用。因为基类的拷贝构造函数式private。

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

  • 1)带有多态性质的基类应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数

    如果没有virtual,在多态中,会出现内存泄漏,主要出现的原因是简单地析构了基类的部分,然后派生类被架空,delete后只释放了基类的内存,而派生类的内存没有释放。


有了virtual后,析构时会检查指针实际指向的对象,先定位到相应的派生类,完成派生类的析构之后,再析构基类。那么如何检查指针指向的对象呢,c++会为每个virtual关键字的类中创建一个vptr,指向一个由函数指针构成的数组,为虚表,运行时维护类中的虚表进行析构。
  • 2)若一个类不作为基类,或者不具备多态性(比如Uncopyable),就不该声明virtual析构函数。

    加上virtual确实是可以防止多态过程中析构造成的内存泄露,但是一旦出现virtual,编译器就会出现vptr,这是需要占用空间的,因此出现多态继承时一定要加上virtual,非基类或者不用与多态的基类就不要加

  • 3)纯虚函数,不能定义对象,不通过编译,就可以使得不想成为对象的类成为抽象类。但是有时难以找到一个纯虚函数,为了自然,可以将析构函数变为纯虚函数,如下:

class Shape
{
private:
         int color;
public:
         virtual ~Shape() = 0;
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值