编译器自动生成的特殊成员函数:
默认构造函数:
当构造函数无参数、或所有参数都有默认值时(二者不能同时存在),则是默认构造函数。
自动生成的默认构造函数,将调用基类的默认构造函数;如果类的数据成员是另一个类的对象,那么这个数据成员在生成的时候,也会调用其默认构造函数。
假如定义了某个构造函数,那么编译器将不会定义默认构造函数。
假如类数据成员有指针,应该在默认构造函数中正确为其分配内存地址。
复制构造函数:
原型:类名(const 类名& );
使用复制构造函数的四种情况(关键是在声明一个类并初始化时):
①将新对象初始化为一个同类对象(如Man a=b; Man是类名,下同);
②按值将对象传递给函数(如void show(Man a));
③函数按值返回对象(如Man show(););
④编译器生成临时对象(临时对象通常在以下情况产生:①类型转换;②按值返回;③按值传递;④对象定义);
具体情况:
①类里没有声明复制构造函数,也没用使用——编译器提供复制构造函数原型,但不提供定义;
②类中没有声明,但是使用了——编译器提供复制构造函数的原型和定义(数据成员按值传递);
③类中声明了,使用类提供的复制构造函数。
赋值运算符:
在声明类的时候,调用构造函数或复制构造函数。
而当类对象被声明之后,将一个类对象赋值给另一个类对象时,才使用赋值运算符。
原型:类名& operator=(const 类名& );
①默认情况下,将使用逐成员按值传递;
②假如数据成员是另一个类对象,则在按值传递的时候,调用该类的赋值运算符(如果该类没有,则使用该类的默认赋值运算符);
③对于指针(由new分配内存)作为数据成员时,由于构造函数需要显式声明,同样,赋值运算符也需要显式声明以处理其情况;
如果要将另一个类型赋值给类对象,方法①是定义该类型作为赋值运算符的参数;方法②是使用转换函数(之构造函数,将其他类型转换为临时类对象,然后通过临时类对象赋值给类对象);
其他类方法:
构造函数:
特点是声明一个新对象时调用(视情况调用默认构造函数、构造函数、或者是复制构造函数)。
构造函数不被派生类继承,但派生类的构造函数会默认调用基类的构造函数。
如果类的数据成员使用动态内存,那么应显式的声明构造函数、复制构造函数、赋值运算符和析构函数。
析构函数:
①基类的析构函数应该定义为虚的,即使没有任何内容,也至少应该有一个虚的析构函数。
②如果构造函数使用new来分配动态内存给数据成员,那么析构函数应该使用对应的delete来释放内存。
③需要显式的使用析构函数的情况很少,使用new定位运算符创建的类对象,是少数几种需要显式的调用析构函数的情况之一(因为不能通过直接delete类对象的方式,来调用析构函数)。
转换:
使用一个参数就可以调用的构造函数,定义了从参数类型到类类型的转换。
例如将一个int类型作为类构造函数的参数,那么这个类对象可以等于int类型(利用构造函数,然后调用赋值运算符)。
如果需要禁止这种情况发生,可以在构造函数前面加上关键字explicit,来禁止隐式转换,于是只有显式的调用对应的构造函数时,才能进行转换。
将类对象转换为其他类型的转换函数,可以是没有参数的类成员函数(例如operator int());也可以是返回类型被声明为目标类型的类成员函数(不懂)。
按值传递、按引用传递,返回对象和返回引用:
使用引用的好处:
①节省内存开支(不需要创建临时对象);
②对于指针,如返回临时对象的话,应显式声明复制构造函数,但返回引用就不用;
缺点:
①不能对函数内部创造的对象返回引用(因为离开函数块时该引用的原型会被销毁);
通常来说,如果是函数内部创造的对象,然后返回它,就应该使用返回对象而非返回引用。否则,应尽量使用返回引用(如果要求其不能成为左值,则加上关键字const);
使用const:
作用:
①可以确保类方法不修改对象的数据成员(在参数列表的括号后加const);
②确保类方法不修改调用的参数(在参数类型名前加const);
③确保类方法的返回值不被修改(在返回值的类型名前加cosnt);
公有继承的考虑因素:
is-a关系:
要遵循is-a关系,如果派生类不是一种特殊的基类,则不要使用公有派生。(不懂)
感觉大概意思是说,一个类作为基类还是作为另一个类的成员对象,要看情况。有些时候适合做基类(例如派生类是基类的一种),有些时候适合做成员对象(例如派生类是基类的一部分)。
可以创建具有纯虚函数的抽象基类,然后派生出其他类。
基类的指针、引用可以指向派生类对象,但不能反过来。
什么不能被继承:
①构造函数:但在派生类构造函数时会自动调用基类的构造函数;
②析构函数:派生类析构函数也会自动调用基类的析构函数;
③赋值运算符:视情况而定,有时候可以使用默认的,有时候需要自定义,并显式调用基类的赋值运算符函数(基类名::operator=(参数对象))。
赋值运算符:
①默认赋值运算符版本,将逐成员按值传递;
②派生类的赋值运算符如果是默认版本,将对基类部分调用基类的赋值运算符,对派生类新增数据成员按值传递;
③如果自定义派生类赋值运算符,那么需要显式的声明基类的赋值运算符以复制基类的部分;
④遇见new来分配内存,应自定义赋值运算符;
⑤默认情况下,派生类对象赋值给基类对象,相当于将派生类对象强制转换为基类并赋值(相反则不行,只能调用参数合适的构造函数,或参数合适的赋值运算符)。
私有成员和保护成员:
对于派生类而言,保护成员类似公有成员;
对于外界而言,保护成员类似私有成员。
虚方法:
格式是函数原型(函数定义不需要)加关键字virtual。
①基类的析构函数需要虚方法;
②对于派生类需要重新定义的方法,基类使用虚方法(派生类可以不用,但用的话可以明确表示基类该方法也是虚的);
③使用虚方法时,受传递的对象所影响(按引用、指针传递,和按值传递的结果是不一样的);
析构函数:
公有继承的析构函数应该是虚的。
友元函数:
派生类不继承友元函数,但可以显式的调用友元函数。方法是 类名::基类友元函数 。或者是使用强制类型转换。
关于使用基类方法的说明:
①某方法如果派生类没有重新定义,则使用基类的;
②派生类构造函数自动调用基类的,默认调用默认构造函数,除非更改声明调用的构造函数(例如原本是调用默认构造函数的,1个参数。改用另一个构造函数,2个参数的);
③一般情况下,派生类复制构造函数会自动使用基类的复制构造函数,除非自己显式的在派生类复制构造函数使用其他构造函数;
④派生类的方法中,可以通过作用域解析运算符(双冒号)显式的调用基类的方法(前提不是私有的);
其他(附表):