构造函数:与类同名,无返回值
this指针,指向类对象地址
析构函数:与类同名,加代字号~,无参数,无返回值
当对象超出他的作用域时,自动调用析构函数。
switch 、goto语句都可能挑个语句,因此不允许对象定义
栈——局部变量
堆——动态变量
静态存储区——全局变量、静态变量
require.h中的require()函数,可以代替assert的作用
union也可以带有构造函数、析构函数、成员函数和访问控制,但是union不能作为继承时的基类
union没有类型名和标识符,叫做匿名联系,为这个union创建空间,不需要标志符的方式和以点操作符方式访问这个union的元素。
函数的默认参数规则:
第一,只有参数列表后部的参数可以是默认的,即不可以在一个默认参数后面又跟一个非默认参数
第二,一旦在一个函数调用中开始使用默认参数,那么这个参数后面所有的参数必须是默认的
默认参数只能放在函数声明中,通常在头文件里
第8章 常量
#define BUFSIZe 100
不占用存储空间,预编译时,替换
const
变量,仅在const变量定义的文件中是可见的,其他文件不可见;通常情况下,C++编译器不为const创建存储空间,可以使用extern进行强制说明extern
const int bufsize;extern意味着有外部文件需要连接,因此必须分配空间。
const可以用于集合,但不能将const集合保存在符号表中,所以必须分配内存,在这种情况下,const意味着不能改变的一块存储空间,因此,不能在编译期间使用它的值,因为编译器在编译期间不需要知道存储的内容。
const int i[]={1,2,3,4};
float f[i[3]];//非法的;因为编译期间不知道i[3]的值。
在C编译器中,const意味着不能改变内容的普通变量:const int bufsize=100; char
buf[bufsize];非法的;
C编译器中可以写const int
bufsize,可以看做是bufsize的声明,是允许外部链接的。C++默认const是内部连接的,C++里面需要使用extern
*与标识符结合而不是与类型结合
为了保证一个类对象为常量,引入了const成员函数:const成员函数只能对于const对象调用
构造函数初始化列表就是用来初始化const成员,初始化列表的发生在构造函数任何代码执行之前
enum不占存储空间,在编译期间生成
const类对象,当类对象声明为const时,只能保证public成员变量在生命周期内是不可更改的,为了保证const类对象不可更改内部成员,对象只能调用const成员函数,const成员函数声明例如:
int f() const;
在f() const函数中任何更改类对象成员的语句,编译器都能检测出错误;
需要注意的是:int f() const; int f();在编译器看来是两个不同的函数;
const成员函数调用const和非const对象都是安全的;
析构函数与构造函数都不能是const成员函数;
const函数内部还是可以将成员变量更改的,方法一:将this强制转化为非const类对象,即可;
方法二,可以使用mutable关键字、,通常为了表明在const成员函数中可以对象mutable成员发生了更新
volatile关键字,防止编译器对变量的优化
第8章小结:const能将对象、函数参数、返回值和成员函数定义为常量
第9章 内联函数
C中保持效率的方法是使用宏,宏是预编译处理而不是编译器,但是宏容易引入难以发现的错误
内联函数在适当的地方像宏一样展开,但是不需要函数调用的开销
任何在雷总定义的函数自动地成为内联函数;
非类的函数可以在前面加上inline关键字使之成为内联函数;
内联函数必须使函数体和声明结合在一起,否则,编译器将它作为普通函数对待,即,
inline int plusOne (int x){ return ++x;}
一般应该将内联放在头文件中,当编译器看见这个定义时,它把函数类型(函数名+返回值)和函数体放入符号表里;
当使用函数时,编译器检查以确保调用是正确的切返回值被正确使用
不适合内联的情况:任何种类的循环
static
1. 在特殊的静态数据区创建
2. 可见范围是局部的
静态对象和静态变量只会被初始化一次,当main函数退出或者exit函数被调用时,静态对象的析构函数才会被调用
因此在析构函数使用exit是非常危险的,可能导致无穷的递归调用。
标准C库函数abort()退出程序,静态对象的析构函数不会被调用
析构的过程与构造函数调用的过程刚刚好相反
全局对象的创建是在main函数之前
extern和static对于全局变量,只是改变变量的可见性,全局变量也是存储在静态存储区
extern和static用于函数,只是改变函数的可见性
static用于局部变量,改变变量的存储性质,存储与静态存储区,而不是堆栈中
auto关键字,存储类型说明符,表明变量是局部变量,通常编译器能够判断出变量是否是局部变量,因此auto是多余的。
register也是局部变量,但是它告诉编译器,这个变量经常用到,应该保存在寄存器中,主要用于代码优化
C++名字空间,关键字namespace,定义例如
namespace myLib{
}
注:全局范围内定义,可嵌套,不用跟分号结尾
namspace a = myLib;//myLib命名空间的别名a
未命名空间只在编译单元内有效,保证每个编译单元只有一个未命名空间;
10.3 C++的静态成员
对象的静态成员变量,只能在类外进行定义初始化;
类的嵌套类中可以有静态成员变量,但是在函数内部嵌套的类,不能有静态成员变量
类中也可以定义静态的数组包括const数组和非const数组
类中的静态常量可以在类内部进行定义,但是其他的静态变量包括静态数组常量或者变量都需要在类外定义,以及静态类成员变量。
静态成员函数,可以使用类名::函数名的方式进行调用;也是像其他成员函数一样使用对象进行调用。
静态成员函数只能访问静态成员变量,不能访问非静态数据成员,和调用其他的静态成员函数,也不能访问非静态成员函数,静态成员函数是没有this指针的.对于静态函数的所有调用来说,一个局部变量只有一份拷贝
10.4 静态初始化的相依性
静态对象的初始化顺序严格按照对象在该单元中定义出现的顺序,析构则刚好相反
第11章 引用和拷贝构造函数
引用使用的规则:
1. 引用创建会死必须初始化
2. 一旦一个引用被初始化为指向一个对象,就不能改变为另一个对象的引用
3. 不能有NULL的引用,必须保证引用是与一块合法的存储单元关联的。
int & h(){
int q;
//! return q; //ERROR
static int x;
return x;//safe,x lives outside this scope
}
C/C++中,参数是由右向左进栈的,然后调用函数
第12章运算符重载(没看)
第13章 动态对象创建()
13.1 对象创建
创建C++对象的过程:
1) 为对象分配内存
2)调用构造函数初始化对象内存
第14章 组合和继承(关键细看)
组合——即将一个类的对象作为另外一个类的成员
继承——即派生类根据继承权限具有基类所有的成员变量和成员函数
基类和类成员在本书中均被称为子对象,因为他们都是作为新类的一部分
继承,如果继承默认是私有继承即private
派生类中与基类相同的成员变量和成员函数被重定义后,派生类对象直接调用将会调用派生类的直接定义,若想要是有基类定义,需要使用类区域操作符::显示表明基类名。例如,
构造函数:由于通常新类的构造函数没有权利访问子对象想的私有成员函数(构造函数是私有的),因此不能直接对它们初始化。
因此对于继承和组合,必须使用初始化表达式的方式对子对象进程初始化:
1. 继承
MyTyape::MyType(int i):Bar(int i){}//Bar是MyType的基类
2. 组合
MyType::MyType(int i):Bar(int
i),m(i+1){}//m是myType的一个子对象,即其他类的对象,初始化时,自动调用m的对应构造函数
a. 构造函数初始化执行顺序为:初始化列表->构造函数结构体(保证子对象先执行构造函数)。
b. 基类构造函数->成员对象构造函数;析构过程与构造过程完全相反次序
c. 对于成员对象的构造函数对象调用与初始化列表无关,而是在类中声明的次序决定的。
14.5 名字隐藏
总结:
1. 与基类的定义完全相同,对普通成员函数的重定义redifining
2. 与基类虚函数定义相同,成为重写overriding
重载——函数名相同,参数列表不同
派生类的重定义函数,会将基类所有函数名相同的函数覆盖调度,即不会调用父类的相同函数名的函数
这样做的好处是,基类的接口发生变化,而派生类相同的接口并不会因为基类的接口的变化而发生影响,因为派生类已经按照自身的需求重新定义了接口,保证了类的多态性
非自动继承的函数——不是所有的函数都能被自动继承,包括:构造函数、析构函数和operator=也不能被继承
继承和静态成员函数,静态成员函数与非静态成员函数共同点:
1. 均可被继承到派生类中
2. 如果重新定义了一个静态成员函数,所有在基类中的其他重载函数会被隐藏
3. 如果改变了基类中一个函数的特征,所有使用该函数名字的基类版本都会被隐藏
然而,静态成员函数不可以是虚函数
向上类型转换——组合和继承最直接的区别
Wind w;//继承自instrument
instrument * ip =w;//向上类型转换
ip->play();//调用instrument的play函数,而不是wind的play函数
若想要ip调用wind的play函数,就要依靠virtual实现的多态性,能够根据对象的真实类型,调用正确的函数
第15章 多态性和虚函数(关键细看)
多态性——提供接口与具体实现直接的隔离
普通函数编译器知道所要调用的函数地址;
虚函数在运行时动态判断需要调用的函数地址通过virtual关键字说明;
如果一个函数在基类中是virtual的,那么在所有的派生类中都是virtual,在派生类中virtual函数的重定义成为overriding(重写)
对每个包含虚函数的类创建一个表(VTABLE),在VTABLE中,编译器放置特定类的虚函数地址,每个带有虚函数的类中,编译秘密放置一个支持成为Vpointer(VPTR),指向这个VTABLE。当通过基类指针做虚函数调用时(即多态调用),编译器静态地插入能取得这个VPTR并在VTABLE表中查找函数地址的代码,这样就能正确调用函数并引起晚捆绑的发生。
为每个类设置VTABLE、初始化VPTR、为虚函数调用插入代码,所有这些都是自动发生的。
带有虚函数的类,除了成员变量还有一个指针的空间大小
对于没有数据成员的类,C++编译器会强制对象是非零长度,会把一个“哑”成员插入对象,当类型信息存在virtual关键字时,这个“哑”成员位置被占用
如果派生类没有对基类的virtual函数重写,那么派生类默认使用基类的VTABLE
每个对象只有一个VPTR,VTPR在初始化阶段指向响应的VTABLE的起始地址,一点VPTR被初始化为指向相应的VTABLE时,对象就知道它自己是什么类型,但只有当虚函数被调用时才有这种自我认知。
VPTR位于对象的首部,VTABLE的顺序相同
向上类型转换,其实仅仅处理地址
抽象基类和纯虚函数
在基类中加入至少一个纯虚函数,使得基类成为抽象类,如果所有的成员函数都是虚函数,那么基类为纯抽象类。
纯虚函数,使用关键字virtual,并且在其后面加上=0
如果尝试生成一个抽象类对象时,编译器会制止他。
继承抽象类的派生类必须实现所有的纯虚函数,否则继承出的类也是一个抽象类
抽象类的目的,所有它派生的类创建公共接口
纯虚函数的目的,告诉编译器在VTABLE中为函数保留一个位置
纯虚函数禁止对抽象类的函数以传值方式调用,通过使用抽象类,可以保证在向上类型转换期间总是使用指针或引用。
对纯虚函数提供定义时可能的,将纯虚函数的定义放在类定义之外。派生类使用时,需要通过类型作用域操作符调用。仍然不允许抽象类生成对象。
15.8 继承与VTABLE
派生类在重定义虚函数时,不能改变函数的返回值,单非虚函数是可以的。
同样也会覆盖掉基类的同名函数
构造函数中调用虚函数,是调用本地版本,即虚机制在构造函数中不工作。
但是析构函数却常常必须使虚的
纯虚析构函数是合法的,但是使用时有一个额外限制:必须为纯虚析构函数提供一个函数体。纯虚析构函数的目的是防止类被实例化。
析构函数中调用虚函数,也是调用本地版本,而不是晚绑定的方法。虚机制不工作
dynamic_cast显示类型转换,仅当转化正确时是成功的,返回值是一个指向所需类型的指针,否则返回0表示转换失败。