《Effective C++》读书笔记一

1、C++中std是什么意思?

摘自:https://blog.csdn.net/calvin_zhou/article/details/78440145

在程序中像vector ,cout,这类东西都是在std内,有时会忽略std::,你得自己认清

2、

3、尽量以Const、enum、inline替换#define

举例:定义常量可以用define方式

#define ASPECT_RATIO 1.653

不好之处:

ASPECT_RATIO无类型,在进行预处理阶段只有ASPECT_RATIO被替换成1.653的过程,不会进行类型安全检查

改成:

const double AspectRatio = 1.653

好处:

AspectRatio有类型double,在编译阶段进行类型安全检查

如果定义常量指针,记得*前后都要加const

const char* const authorName = "Scott Meyers";

用inline函数代替宏函数

#define CALL_WITH_MAX(a,b) f((a)>(b)?(a):(b))

改成(至于原因有点复杂,查看https://blog.csdn.net/u013517637/article/details/51154422

template<typename T>
inline void callWithMax(const T& a,const T& b)
{
	f(a>b?a:b);
}

建议传参采用const 引用形式,提高效率

最后请记住:

1、对于单纯常量,最好以const对象或enums替换#defines

2、对于形似函数的宏,最好改用inline函数替换#define

总结:

有了consts, enums、inlines 我们对预处理器(特别是#define)的需求降低了,但并非完全消除,#include仍然是必需品,#ifdef/#ifndef也继续扮演控制编译的重要角色。

最后了解下预处理器和编译器:

从源代码到获取到可执行程序大致流程如下所示:

Step1:源代码(source code) 

Step2:预处理器(preprocessor)

Step3:编译器(compiler)

Step4:目标代码(object code)

Step5:链接器(Linker)

Step6:可执行文件(executables)

预处理器:

早于 编译器。

工作内容:删除注释、包含(include)其他文件以及宏替换等

常用的预处理有:

#include 功能:包含头文件、#if 功能:条件、#else 功能:否则、#elif 功能:否则如果、#endif 功能:结束条件、#ifdef 功能如果定义了一个符号,就执行操作,等效#if !defined、#define 功能:定义一个符号、#undef 功能:删除一个符号

编译器

工作时间:晚于预处理器

工作任务:语法分析、语义分析等,最后生成目标文件

摘自:https://blog.csdn.net/u012421852/article/details/51167668?utm_source=copy

4、尽可能使用const

(1)const修饰函数(函数内不能修改成员的值且不能调用非const函数)

int get() const
{ 
    //get函数返回i的值,不需要对i进行修改,则可以用const修饰。防止在函数体内对i进行修改。
	return i;
}

将成员函数设为const又分为两个概念:

第一种:bitwise constness,认为成员函数设为const,那么不能改变对象的任何成员变量(static除外)

第二种:logical constness,一个const成员函数可以修改它所处理的对象的某些bits,但要保证在用户使用中侦测不出。

对于第一种bitwise constness,有个特殊情况,虽然将operator[]声明为const成员函数,如下:

但是外部可以通过指针指向的值可以改变,与常量对象矛盾,如下:

解决方法:

可使用mutable关键字解除bitwise constness,例如 mutable char *mPointer;

在程序中:

(2)const修饰函数参数(在函数内不能修改指针所指内容,起到保护作用)

void fun(const char * src, char * des){  //保护源字符串不被修改,若修改src则编译出错。
	strcpy(des,src);
}

(3)如果参数是引用,为了避免函数通过引用修改实参的内部数据成员,加const来保护实参

void h(const A & a){
}

(4)const修饰函数返回值

也是用const来修饰返回的指针或引用,保护指针指向的内容或引用的内容不被修改,也常用于运算符重载。归根究底就是使得函数调用表达式不能作为左值。

#include <iostream>  
using namespace std;    
class A 
{
private:	
    int i;
public:	
    A(){i=0;}	
    int & get()
    {		
        return i;	
    }
}; 

void main()
{	
    A a;	
    cout<<a.get()<<endl; //数据成员值为0	
    a.get()=1; //尝试修改a对象的数据成员为1,而且是用函数调用表达式作为左值。	
    cout<<a.get()<<endl; //数据成员真的被改为1了,返回指针的情况也可以修改成员i的值,所以为了安全起见最好在返回值加上const,使得函数调用表达式不能作为左值
}

摘自:https://blog.csdn.net/my_mao/article/details/22872149

最后请记住:

5、确定对象被使用前已被初始化

(1)对于变量初始化

一定在声明的时候先初始化

(2)对于构造函数初始化

不好:

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

好:

好的地方:通过初始化列表,效率比赋值更高,且注意,初始化列表的成员变量排列次序和他们在类中声明的次序相同

5、知道c++默认调用了哪些函数

如果写一个空类,c++默认会声明一个默认构造函数、拷贝构造函数和一个析构函数,这些函数都是public且inline

因此你写下:

class Empty{};

等同于写下:

只有当这些函数被调用,才会被编译器创建出来,下面代码造成每一个函数被编译器产出:

对于拷贝构造函数:

类NamedObject的构造函数这样:

(1)如果调用了拷贝构造函数:

在将第一个参数no1的string传给no2时,会调用string的拷贝构造函数并以"Smallest Prime Number"为实参,第二个参数类型int,所以会拷贝no1中value的每一个bits完成初始化

(2)如果调用赋值操作:

编译器拒绝执行,因为引用没法赋值,无法改变,编译器拒绝编译

NamedObject<int> p("Persephone", 2);
NamedObject<int> s("Satch", 36);
p=s;

6、为多态基类的析构函数声明为virtual

父类析构函数如果不设为virtual,那么下列程序

    CEmployee *oper = new COperator() ;   //CEmployee是父类,COperator是子类
    delete oper;  

执行顺序为:

父类构造->子类构造->父类析构

后果:子类析构没有调用,内存泄露

如果父类析构加virtual,子类析构不加virtual,执行顺序为:

父类构造->子类构造->子类析构->父类析构

注意点:

1、既不能将所有类析构函数都声明为virtual,因为Point class内含virtual函数,对象体积会增加百分之50-100,也不能所有类析构都不声明为virtual

2、给基类一个virtual析构函数只适用于带多态性质的基类

3、如果类带有任何virtual函数,析构函数就应该设为virtual

4、如果该类设计不是作为基类使用,或不是为了具备多态性,析构函数不该设为virtual

7、别让异常逃离析构函数

8、在构造和析构期间不要调用virtual函数,因为调用从不下降至子类

9、在赋值操作符operator= 返回一个*this,类型为对象本身

10、在赋值操作符operator= 中处理“自我赋值”

11、复制对象勿忘其每一个成分

不对做法:

正确做法:

12、以对象管理资源

1、创建了一个资源后立即交给管理manager类管理

2、管理对象运用析构函数确保资源被释放

13、成对使用new和delete采用相同的形式

猜你喜欢

转载自blog.csdn.net/zhangxiaofan666/article/details/82964728