1、类中的成员函数和成员变量有三种属性:
private, //私有成员,只能由类内部成员访问 //(默认属性) //也是类封装性的体现
public, //共有成员,类内成员或类对象、派生类都能访问
protected //保护成员,类内成员或者派生类访问。
一般使用方式:类成员变量使用private;类成员函数使用public;而protected是在类继承中才会使用。(不继承的话,protected相当于private,也就是说,有些私有成员我们平时是当成私有成员,当有子类的时候,我们想让子类继承。)
2、类继承的方式有三种:
类可以是单继承(继承于一个父类)和多继承 (继承于多个父类)
三种继承方式与成员属性相同,
public,//公有继承
private//私有继承
protected//保护继承
子类 | 继承方式 | 父类成员属性 | 继承结果及父类成员 在子类中属性 | 备注 |
public | public | 子类可以访问, 属性仍为public | ||
public | private | 子类不可以访问 属性仍为private | ||
public | protected | 子类可以访问, 属性仍为protected | 这个就是protected 属性的意义 | |
private | public | 子类可以访问 属性变为private | ||
private | private | 子类不可以访问 属性仍为private | ||
private | protected | 子类可以访问 属性变为private | ||
protected | public | 子类可以访问 属性变为protected | ||
protected | private | 子类不可以访问 属性仍为private | ||
protected | protected | 子类可以访问 属性仍为protected |
说明:
1)我们一般使用的是public继承,其他两种继承方式可以暂时不管。
2)父类成员属性为private的,子类无论以何种方式继承,都无法访问的。
3)父类成员被子类继承后的属性,此时的属性可用性就可以参照1中的情况来分析了。
3、语法形式
类声明中如下形式
class A : public class B
{
};
4、继承性特点
1)子类继承了除父类的构造函数、析构函数外的所有成员变量和成员函数;
2)子类可以拥有新的成员变量和成员函数。
3)父类成员函数为virtual时,子类可以重写该函数
4)父类成员函数不为virtual时,子类重新定义该函数时,会覆盖父类函数。
5、子类重写父类函数//父类成员函数声明的前面 添加virtual
父类成员函数为virtual时,子类可以重写该函数,注意函数名,参数必须完全一样。否则即便是父类成员函数前有virtual也没用,不是重写,变成重定义了。
虚方法只有在使用指针或引用来调用函数的时候才会用到(指针/引用是基类指针/引用)
用对象来调用函数的时候,不会用到虚方法。
基类方法的声明中使用virtual可使该方法在基类及所有派生类(包括从派生类派生出的类)中是虚的。
重写函数的访问修饰符可以不同。即,假使virtual是private的,派生类中重写改写为public,protected也是可以的
假使方法是通过引用类型或指针类型选择方法:
1)当父类成员函数不是virtual时,程序会根据引用或指针类型来选择方法版本。
2)当父类成员函数前面有vitual, 程序将根据引用或指针指向的对象的类型来选择方法。
class CBase {
private:
virtual void width() { //do sth.}
public:
void length(string str) { //do sth. }
void length(int num) { //do sth.}
};
class CDerive:public CBase{
public:
void width() { //do sth. } //重写,因基类中width()为虚函数
void lenth(int num){//do sth.} //重定义,lenth函数在父类中不为虚函数
void length(int a,int b) { //do sth. } //重定义,lenth函数在父类中不为虚函数
};
int main(){
//非虚函数看指针/引用
CBase fa;
CDerive so;
CBase &b1_ref = fa;
CBase &b2_ref = so;
b1_ref.length(1); //use CBase::length();
b2_ref.length(1); //use CBase::length();
//虚函数看指针/引用对象
CBase *b3_ref = new CBase;
CBase *b4_ref = new CDerive;
b3_ref->width(); //use CBase::width();
b4_ref->width(); //use CDerive::width();
}
6、子类重定义父类函数//一般不这么用//可以忽略不看
重定义 (redefining)也叫做隐藏:子类重新定义父类中有相同名称的非虚函数 ( 参数列表可以不同 ) 。
如果一个类,存在和父类相同的函数,那么,这个类将会覆盖其父类的方法,除非你在调用的时候,强制转换为父类类型,否则试图对子类和父类做类似重载的调用是不能成功的。
7、父类指针/引用 指向子类对象
该指针只能访问父类定义的函数,(静态联编)(与父类指针,指向父类对象一样的效果)
这是我们经常用到的,这也是虚函数的作用所在。即多态性的体现。同一个类中,函数名相同,函数参数不同,构成多态;基类继承类中,函数名、函数参数完全相同,通过基类指针指向的对象不同,也实现了多态性。
//同时将不同的数据类型放在一个数组是不可能的(比如将CBase和CDerive对象放在一起是不行的,
但是,可以将CBase*指针放在同一数组内,)
int main()
{
CBase fa;
CDerive so;
CBase *pt[2]={&fa,&so};
for(int i =0;i<2;i++)
{
pt[i]->width();
}
}
下图中展示了为什么基类指针/引用可以指向派生类对象的情况,而反之不行(除非显示强制转换)
8、子类指针指向父类对象//一般不这么定义//可以忽略不看
需要先进行强制类型转换(explicit cast);
9、析构函数为虚函数
10、小结:
1)将派生类引用或指针转换为基类引用或指针称为向上强制转换,这个不需要强制转换;派生类对象都是基类对象,继承了基类的所有数据成员和成员函数,所以对基类对象的任何操作,都适用于派生类对象。
2)相反的过程,将基类指针或医用转换为派生类指针或引用称为向下强制转换,如果不使用显示类型转换,向下强制转换是不允许的。
3)按值进行传递,只能将派生类中的基类部分传到函数中
11、总结
一般用法:
1)基类中需要继承类重写的函数前面应添加virtual
2)基类中析构函数前添加virtual
3)使用基类指针或引用指向基类对象或派生类对象实现动态连编
4)派生类直接值传递给基类,仍然调用的是基类的函数,是静态连编。