zoukankan      html  css  js  c++  java
  • Effective C++ 学习笔记

    一、从c转向c++
    1.尽量用const和inline而不用#define
     尽量用编译器而不用预处理
     const定义常量,当编译出错时能在提示信息中看到符号名
     inline定义内联函数,避免类似#define max(a,b) ((a)>(b)?(a):(b)) 在调用max(++a,b)时出现的不确定性
     
    2.尽量用<iostream>而不用<stdio.h>
     类型安全,扩展性,避免变量和控制读写格式信息分开
     #include<iostream>得到置于std下的库元素;#include<iostream.h>得到置于全局空间的同样的元素,可能导致命名冲突
     
    3.尽量用new和delete而不用malloc和free
     区别在于会不会调用构造和析构函数。
     
    4.尽量使用c++风格的注释//
     /* */不允许嵌套
     
    二、内存管理
    5.new和delete要采用相同的形式
     new[]对应delete[]。使用typedef定义数组类型别名,当用delete去释放事实上为new[]的new分配的内存时内存泄漏
     
    6.析构函数里对指针成员调用delete
     类的每个指针成员要么指向有效内存,要么指向空,构造函数必须确保这一点,析构函数才能直接delete掉。

    7.预先准备好内存不够的情况
     使用set_new_handler处理new失败情况
     
    8.写operator new 和operator delete 时要遵循常规
     分配程序支持new-handler并正确地处理了零内存请求;释放程序处理空指针。

    9.避免隐藏标准形式的new
     类里定义了一个称为“operator new”的函数后,会阻止对标准new的访问。
     一个办法是在类里写一个支持标准 new 调用方式的operator new;另一个方法为每一个增加到 operator new 的参数提供缺省值。
     
    10.如果写了operator new 就要同时写operator delete

    11.为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符
     未定义拷贝构造函数的以处理赋值操作符的方式进行处理(对指针成员进行逐位复制),会导致指针混乱内存泄漏。

    12.尽量使用初始化而不要在构造函数里赋值
     一步动作替代两步动作;const成员不能被赋值。大量固定类型数据成员的情况可能例外。
     推广之,尽量使用一步初始化替代初始化后再赋值。

    13.初始化列表中的成员列出的顺序和它们在类中声明的顺序相同
     因为要保证析构函数被调用的顺序与构造函数相反。静态成员例外;基类总是在派生类之前被初始化,跟继承顺序一致。

    14.确保基类有虚析构函数
     原因:通过基类指针删除派生类对象,如果基类没有虚析构函数,结果不可确定。
     虚析构函数工作方式:最底层的派生类的析构函数最先被调用,然后各个基类的析构函数。
     技巧:使用纯虚析构函数可以使类成为抽象类(无法被实例化),但仍需提供纯虚析构函数的定义,考虑定义为内联函数。
     
    15.让operator=返回*this的引用
     兼容固定类型的常规做法“(a=b)=c ”。定义:String& String::operator=(const String& rhs){ return *this; }
     对于没有声明相应参数为const的函数来说,传递一个const是非法的。

    16.在operator=中对所有数据成员赋值
     为类增加数据成员时容易遗漏。
     对基类调用operator=,使用“static_cast<Base&>(*this)=rhs;”,因为当operator=为编译器生成时“Base::operator=(rhs)”写法无法通过编译)
     拷贝构造函数中有同样的问题,使用“Derived(const Derived& rhs):Base(rhs),data(rhs.data){}”
     
    17.在operator=中检查给自己赋值的情况
     当类有指针成员时尤其注意,否则导致指针混乱。 C& C::operator=(const C& rhs){ if(this==&rhs) return *this; ... }
     
    三、类和函数:设计与声明
    18.争取使类的接口完整并且最小

    19.分清成员函数,非成员函数和友元函数
     虚函数必须是成员函数;若函数需对左边的参数进行类型转换,则使用非成员函数;需要访问非公有成员的非成员函数只能是类的友员函数,尽量避免友员。

    20.避免public接口出现数据成员
     接口一致性,全是函数;读写控制;具备灵活性。
     
    21.尽可能使用const
     编译器实施只读约束。
     const MyClass* p; 或 int const* p; //常量的指针
     MyClass* const p;//常指针
     const MyClass* const p;//常量的常指针
     const MyClass& c;//常量的引用
     const_cast<MyClass*>(p);//将p强制转换为常量的指针
     const成员函数在声明后面加const,表示const对象的成员函数版本。
     const成员函数不能修改对象中的任何一个比特,但能修改指针指向的数据。若要修改,可以使用自定义的非const版this指针。
     
    22.尽量用“传引用”而不是“传值”
     引用是通过指针来实现的,本质上一样。
     避免多次调用拷贝构造函数和析构函数;避免切割问题(丧失多态特性)。
     
    23.必须返回一个对象时不要试图返回一个引用
     当需要在返回引用和返回对象间做决定时,选择完成正确功能的那个。
     
    24.在函数重载和设定参数缺省值间慎重选择
     如果可以选择一个合适的缺省值并且只是用到一种算法,则使用缺省参数。
     在重载函数中调用一个为重载函数完成某些功能的公共的底层函数。
     
    25.避免对指针和数字类型重载
     因为传入NULL或0时不会使用指针版本,编译器也不认为这种调用具有多义性。
     
    26.当心潜在的二义性
     c++允许有潜在的二义性,调用到时才会编译出错。
     单参数构造函数与类型转换运算符重载可能出现二义性;
     调用重载函数时隐式类型转换可能出现二义性,可通过显式转换解决;
     多重继承基类成员函数同名(无论是否公有),派生类中显式调用解决。

    27.如果不想使用隐式生成的函数就要显式地禁止它
     把函数定义为私有的而且不去实现它,当成员函数或友元函数想去调时可以让程序在链接时出错。
     适用与条款45中的所有自动生成的函数。
     
    28.划分全局命名空间
     用命名空间替代命名前缀。
     
    四、类和函数:实现
    29.避免返回内部数据的句柄
     防止调用者通过句柄直接修改内部数据
     
    30.避免返回指向成员的非const指针或引用,但成员的访问级比这个函数低
     这样等于提升了内部成员的访问级别。可以通过返回const指针或引用解决。
     
    31.千万不要返回局部对象的引用,也不要返回函数内部用new初始化的指针或引用
     返回局部对象的句柄在函数返回时局部对象被销毁导致句柄失效;返回new出来的对象极可能导致内存泄漏。此时应该返回对象。
     
    32.尽可能地推迟变量的定义
     不同于c,没必要在函数开始处声明变量,在用时声明有助理解。
     尽量推迟变量的声明到可以为它提供一个初始化参数为止,可以避免不必要的初始化或析构开销。
     
    33.明智地使用内联
     编译器可能拒绝内联复杂的函数,编译时会发出警告。
     在类定义内实现的成员函数自动为内联(隐式内联)。
     内联函数的定义在头文件里面,如果编译器当外联处理,则当头文件被不同cpp包含时,链接出现重复定义(部分编译器在这种情况下只编译一份,防止链接出错)。
     构造函数和析构函数通常不适合内联,因为构造基类、数据成员的代码会自动加到构造函数中,导致体积变大不适合当内联。
     虚函数也不能内联,因为需要动态绑定。
     调试版的不会使用内联,当使用内联时无法进入函数调试。

    34.将文件间的编译依赖性降至最低
     区别声明、定义、实现,
     尽可能使用类的声明,而不使用类的定义。类声明“class MyClass;”替代类定义“#include <MyClass.h>”。
     可以使用句柄类(代理类)和协议类(接口类)降低编译依赖性。依赖接口,不依赖实现。
     
    五、继承和面向对象设计
    35.使公有继承体现“是一个”的含义
     D从B公有继承,表示任何可以使用B对象的地方都可以用D对象。

    36.区分接口继承和实现继承
     纯虚函数控制派生类接口继承;非虚函数实现继承;
     如果想提供默认实现,最好利用纯虚函数同时提供实现,强制派生类实现接口,但允许用Base::VFunc之类的调用默认实现。

    37.决不要重定义继承而来的非虚函数
     
    38.决不要重新定义继承而来的缺省参数值
     虚函数是动态绑定,但缺省参数值是静态绑定(编译期确定,提高运行效率)。
     通过基类句柄调用派生类函数将使用基类的缺省参数值,导致混乱。
     
    39.避免“向下转换”继承层次
     向下转换指从基类指针转换为派生类指针。
     万不得意时,可以用dynamic_cast做安全转换(dynamic_cast通过RTTI检查类型,区别static_cast的编译器静态转换)。

    40.通过分层来体现“有一个”或“用...来实现”
        分层:较高抽象层次的类依赖较低抽象层次的类(人依赖于脑、胃,可以理解为组合)
        继承:具体的类依赖抽象的类

    41.区分继承和模版
     当对象的类型不影响类中函数的行为时,使用模版来生成这样一组类。
     
    42.明智地使用私有继承
     私有继承意味着“用...来实现”,继承实现而忽略接口。
     与分层的区别是,派生类能利用基类的保护成员和通过虚函数动态绑定。

    43.明智地使用多继承
     可能导致二义性,可用显式调用或更改成员函数名解决;
     可能导致菱形继承,可用虚基类解决,但虚基类影响效率,而且很难在设计时决定是否使用。
     构造函数的调用顺序:虚基类的构造函数、非虚基类、对象成员、派生类的构造函数。
     A是B和C的虚基类,D同时继承自B和C,则D的构造函数初始化列表中必须包含对A的初始化,而B和C构造函数中对A的初始化将被忽略。
     虚基类应该禁止包含数据,即使用纯接口,这样可以避免D构造函数初始化列表中对A的初始化。
     若D继承自B而虚继承自C,则首先仍然初始化A,不过之后C比B先被初始化。

    44.说你想说的;理解你所说的
     
    六、杂项
    45.弄清c++在幕后为你所写、所调用的函数
     编译器可能会自动生成类的一个拷贝构造函数、一个赋值运算符、一个析构函数、一对取址运算符。
     如果没定义任何构造函数(包括拷贝构造),编译器将默认生成一个。
     拷贝构造和赋值运算符规则是:逐一拷贝(调用拷贝构造函数)非静态数据成员。
     包含引用类型的数据成员,c++不会自动生成拷贝构造和赋值运算符。
     生成的都是public函数,如果想禁用必须显示声明为private
     
    46.宁可编译和链接时出错,也不要运行时出错
     
    47.确保非局部静态对象在使用前被初始化
     由于无法控制不同被编译单元中非局部变量的初始化顺序,故不同非局部静态变量之间的依赖将导致混沌。
     使用Singleton模式。
     
    48.重视编译器警告
     
    49.熟悉标准库
     为兼容c,支持<stdio.h>、<string.h>之类的c库。
     具有c库功能的c++标准库,<cstdio>、<cstring>,c库的std命名空间版本。
     c++模板库STL,<iostream>、<string>、<vector>等,处于std命名空间下。
     
    50.提高对c++的认识
     任何奇怪的特性都是有原因的,都是经过深思熟虑权衡利弊的结果。
     

  • 相关阅读:
    第十五篇 Django Rest Framework
    第十四篇 Mongodb数据库
    Redis相关操作
    celery
    vscode
    VScode-HTML
    第十三篇 Scrapy框架
    第十二篇 Flask 【进阶篇】 插件-SQLAlchmey等
    附录:1装饰器-functools使用
    第十二篇 Flask 基础篇
  • 原文地址:https://www.cnblogs.com/chenwx/p/2240910.html
Copyright © 2011-2022 走看看