@zhengyuhong
2014-10-09T02:24:32.000000Z
字数 5586
阅读 1032
读书笔记 C++
C++是一个由相关语言组成的联邦,它支持过程、面向对象、泛型
const作用
1. 在类外部修饰全局变量或者命名空间中的变量
2. 修饰函数参数、返回值类型,区块作用域的static对象
3. 修饰类内部的成员变量,成员函数
const成员函数
将const实施于成员函数的目的,是为了该成员函数可以作用于const对象,承诺不修改const对象、不修改const指针
const、non-const成员函数避免重复,后者调用前者,并采用const_cast去常量性。(关于类型转换,安全的都可以隐式转换,不安全的需要显示,譬如去常量性const_cast,dynamic_cast向下转型)
在构造函数中对成员变量进行赋值,那不是初始化,那仅仅是伪初始化,初始化需要使用构造函数中的初始化列表,C++规定对象的成员变量的初始化动作发生在进入构造函数本体之前,初始化顺序与声明顺序一样。
成员变量是const或者reference时,必须使用初始化列表
跨编译单元的初始化顺序不能确定,运用单例模式以local static替换non-local static对象,具体详看@p31,这种手法是基于:C++保证,函数内中的local static 对象会在该函数第一次被调用时进行了初始化,并且保持记忆性,下一次调用直接返回,不必再初始化。
class Empty{};就好像写了如下:class Empty{public:Empty(){..};Empty(const Empty& rhs){...};~Empty(){...}Empty& operator=(const Empty& rhs){...}};
唯有当这些函数被需要时才会调用,如果整个源文件中都没有使用过拷贝函数或者赋值运算符,那么这两个函数C++编程器是不会实现的,因为它实现了,但是用户也没有用,浪费。
当成员变量当中含有const、reference时,由于reference不能修改绑定对象,所以默认的赋值运算符是不可取的,C++编译器会禁止生成的,所以必须自行定义一个赋值运算符函数。
为了驳回编译器自动提供的四个默认函数,可以将对象的成员函数声明为private并且不予以实现即可。继承Uncopyable这样的基类也是一种做法。
有一个观点是,在构造函数体中,仅仅是完成了基类的初始化(基类的初始化在初始化列表,进入构造函数体前已经完成了),派生类的初始化还没完成,所以当前对象只能算是基类的对象,调用的虚函数也只是对应的基类的函数,不均被多态性。同理,在析构函数中,派生类已经开始析构了,这也是已经不完全的派生类对象了,所以也是无法调用调用的虚函数的,只能算基类对象
如果用户自定义了自己的拷贝函数,意思告诉编译器不喜欢它提供的函数,编译器就会以儒学方式回敬:当自定义拷贝函数中有出错的情况,它却不会告诉你,譬如漏了拷贝某些成员变量
派生类拷贝函数需要调用基类对应的拷贝函数对基类的成员变量进行初始化,这时候也是在初始化列表当中Base(rhs),rhs是派生类对象,这时候会自动向上类型转换,这是安全的转换,简称切割。
同理,在赋值运算符时也需要调用基类的赋值运算符对基类部分成员变量进行赋值
void foo(){int* p = new int[100];...//假如在此处出现了异常,那么已经提早返回,导致后面的的delete p无法执行,造成内存泄露delete p;}
解决方法是:
1. 获得资源后立刻放入管理对象内譬如:
std::auto_ptr<int> p(new int[100]);
当离开当前作用域时,管理对象(RAII)自动释放资源,这样子就不用担心异常中断了delete p
2. 管理对象是运用在离开作用域时调用析构函数的策略来释放资源的。
除了auto_ptr,还有share_ptr,后者是一个引用计数型智慧指针,记录由多少个对象指向某笔资源,并在无对象指向资源时释放资源。
当一个RAII对象被复制时,会发生什么事?
为了顾及原来的接口,所以必须提供管理对象内中的原始指针或者提供类型转换函数譬如get**(),或者隐式转换函数,这是一个成员函数
operator 目标类型(){return 目标类型对象}
我个人比较认同显式转换,就是使用一个get函数获得指向资源的指针,误用概率更低。
譬如工厂函数不要返回原始指针,应该返回一个智能指针,如此一来资源释放的任务由智能指针完成,不必担心用delete还是free或者忘记使用delete或者free。
以引用方式传递基类接口时可以避免切割
void f(base& b){...}derived d;f(d);//不会产生切割对象
为了防止对象被修改,可以使用const引用
void f(const base& b){...}
提高封装性,用户访问数据的一致性
成员变量应该是private,否则有无限函数可以访问它,无封装性可言
以non-member、non-friend替换member函数提高封装性、包裹弹性、机能扩展性
譬如一个有理数类:
class Rational{public:Rational(int numerator = 0, int denominator = 1);int getNumerator()const;int getDenominator()const;const Rational operator+(const Rational& rhs)const;private:int _numerator,_denominator;}Rational oneHalf(1,2);Rational one(2*oneHalf);//错误
解决方案:在Rational的命名空间下定义一个non-member函数
Rational operator=(int lhs, const Rational& rhs);
如此以来Rational one(2*oneHalf);就可以查找到相应的函数了
如此可以增加源代码的清晰度并且改善效率
像智能指针为了兼容旧接口,有必要返回指针内部资源的接口,其他尽量不能返回内部接口,因为如此就是破坏了封装性
记住,inline只是对编译器的一个申请,不是强制命令
在类内部定义的函数体也只是一个隐喻的inline申请,不属于强制命令
在类外部定义的函数体有inline声明就是一个显式inline申请。
虚函数意味着接口必须被继承,(函数声明就是接口,实现就是函数定义),当然虚函数的实现也可以被继承,但是不强制
非虚函数意味着接口与实现必须被继承
纯虚函数只指定了具体的接口(函数声明),但是没有具体实现(函数定义)
class Airplane{public:vidtual void fly(const Airport& destination) = 0;}
简朴的纯虚函数还提供了一个实现
class Airplane{public:virtual void fly(const Airport& destination) = 0;}void Airplane::fly(){...;}Airplane plane;//错误,有纯虚函数,不能实例化
派生类使用缺省的fly
class Aplane:public Airplane{public:virtual void fly(const Airport& destination){Airplane::fly(destination);//显式使用缺省行为}}//纯虚函数仅仅继承接口,即便有实现也不会继承的。
在前面也说到了,在派生类重新定义域基类同名成员函数会遮掩基类的成员函数,这是public继承所不希望看到的,尽量不能重新定义非虚函数。
如果需要重新定义一个函数,那么证明了派生类的行为与基类的行为不一样,所以需要重新定义,那么使用一个虚函数也是一个不错的选择,还可以使用多态性。当然如果不需要使用多态性的话那么考虑重新定义一个非虚函数还是可以考虑一下,没有太绝对的必然。
虚函数是动态绑定的,而缺省函数时静态绑定的,那么就在编译时已经根据指针或者引用的类型绑定了缺省参数了,所以多态调用时的默认参数就是跟着指针或者引用的,那就是根据基类的参数了,那么跟实际不相符,所以不应该重新定义缺省参数值。
复合的关系还是比较容易理解,就是类中包含了一个成员变量
至于根据某物实现出这个通常是通过private继承,然后在类内部调用了private基类的接口(就是public函数)
尽量使用复合,必要时才使用private继承
个人认为多重继承就是在继承接口方面有很大作用,在其他方面,我没有太多深入理解,特别涉及虚继承的时候,比较麻烦,这些内容在《深入探索C++对象模型》中有详细讲解。