继承
方式:
- private 基类的公有成员与保护成员成为派生类的私有成员
- public 基类的成员访问权限不变
- protected 基类的公有成员与保护成员将成为派生类的保护成员
三种继承方式的对比:
特征 公有继承 保护继承 私有继承
公有成员继成 派生类公有成员 派生类保护成员 派生类私有成员
私有成员继成 只能通过基类接口访问 只能通过基类接口访问 只能通过基类接口访问
保护成员继成 派生类保护成员 派生类保护成员 派生类私有成员
能否隐式转换 是 是(但只能在此派生类中) 否
在没有虚函数的情况下,类指针调用函数是注意:
- 如果以一个基类指针指向派生类对象,那么经由该指针只能调用基类所定义的函数
- 如果你以一个派生类的指针指向一个基类对象,你必须先做明显的强制转换,但是这样做很危险
- 如果基类和派生类都定义了相同名的成员函数,那么通过对象指针调用成员函数时候,调用的函数是由指针的原始类型而定,而不是看指针指向的对象的类型而定
虚函数表:
定义:
为了达到动态绑定(后期绑定)的目的,C++编译器通过某个表格,在执行期"间接"调用实际上欲绑定的函数(注意"间接"字眼).这样的表格称为虚函数表(常被称为vtable).每一个"内含虚函数的类",编译器都会为它做一个虚函数表,表中的每一个元素都指向一个虚函数的地址.此外,编译器当然会为这个类加上一项成员变量,是一个指向这个虚函数表的指针(常被称为vptr),且每个由此派生出来的对象,都会有这个一个vptr.
介绍:
当我们通过这个对象调用虚函数时,事实上是通过vptr找到虚函数,再找出真实的地址,虚函数表用这种间接的的方式,虚函数表的内容是依据类中的虚函数声明次序,意义填入函数表(以及所有其他可以继承的成员),当我们在派生类中改写虚函数时,虚函数表就受了影响:表中元素所指的函数地址将不再是基类的函数地址,而是派生类的地址
例子:
三个继承权限的区别:
class ex0
{
private:
void showPrivate(){cout<<"this is private function!";}
public:
void showPublic(){cout<<"this is public function!";}
protected:
void showProtected(){cout<<"this is protected function!";}
};
class ex1:public ex0
{
public:
void func()
{
showPrivate();
//错误因为此函数访问权限只有基类 ex0 自己有
showPublic();
showProtected();
}
};
class ex2:protected ex0
{
public:
void func()
{
showPrivate();
//错误因为此函数访问权限只有基类 ex0 自己有
showPublic();
//正确, 但是此函数由于 ex2 的保护继承这个函数的访问权限已经变成了 protected,
//也就是说对于外部类来说已经不具备访问这个函数的权限了
showProtected();
}
};
class ex3:private ex0
{
public:
void func()
{
showPrivate();
//错误因为此函数访问权限只有基类 ex0 自己有
showPublic();
//正确, 但是此函数由于 ex3 的私有继承这个函数的访问权限已经变成了 private,
//也就是说对于外部类和派生类来说已经不具备访问这个函数的权限了
showProtected();
//正确, 但是此函数由于 ex3 的私有继承这个函数的访问权限已经变成了 private,
//也就是说对于外部类和派生类来说已经不具备访问这个函数的权限了
}
};
有没有虚函数的情况下,类指针调用函数的区别:
单继承
声明格式:
class 派生类名:继承方式(若不具体指出默认为private) 基类名
{
…
};
特性:
继承了基类所有属性与行为,包括私有成员,但不允许派生类直接访问基类私有成员
构造函数:
格式: 派生类构造函数名(形参表):基类构造函数名(形参表){…}
要点:
● 创建派生类对象时,程序首先创建基类对象,即基类对象应在进入派生类构造函数前被创建完成
(即先调用基类构造函数,后调用派生类构造函数)
● 派生类构造函数应通过成员初始化表将基类信息传递给基类构造函数
● 派生类构造函数应初始化派生类新增的数据成员
析构函数:
特性: 派生类对象过期时,程序将首先调用派生类析构函数,然后调用基类的
虚析构函数:
作用: 和虚函数一样类似,在用基类指针释放派生类对象时候,为了能调用正确的析构函数.
注意: 当一个类有虚函数功能,它经常作为一个基类使用,并且它的派生类经常使用new来分配,那么它最好也使用虚析构函数,因为这样才能保证在释放实例对象的时候调用正确的析构函数
注意:
- 构造函数的调用次序为 基类 -> 派生类
- 析构函数的调用次序为 派生类 -> 基类
例子:
构造函数和析构函数的调用顺序:
#include <iostream>
#include <string>
using namespace std;
class A{
public:
A(){cout<<"A::A() ";}
~A(){cout<<"A::~A()"<<endl;}
};
class B:virtual public A{
public:
B(){cout<<"B::B() "<<endl;}
~B(){cout<<"B::~B() ";}
};
void func()
{
cout<<"this is class D:"<<endl;
cout<<"the order of constructor: ";
B b;
cout<<"the order of destructor: ";
}
void main()
{
func();
system("pause");
}
/********************************
输出结果:
this is class D:
the order of constructor: A::A() B::B()
the order of destructor: B::~B() A::~A()
请按任意键继续. . .
********************************/
虚函数使用:
无虚析构函数:
class A{
public:
A(){cout<<"A::A() ";}
~A(){cout<<"A::~A()"<<endl;}
};
class B:public A{
public:
B(){cout<<"B::B() "<<endl;}
~B(){cout<<"B::~B() ";}
};
void func()
{
A* pb = new B();
delete pb;
}
/******************************************
调用 func 函数的输出结果:
A::A() B::B()
A::~A()
******************************************/
有虚析构函数:
class A{
public:
A(){cout<<"A::A() ";}
virtual ~A(){cout<<"A::~A()"<<endl;}
};
class B:public A{
public:
B(){cout<<"B::B() "<<endl;}
~B(){cout<<"B::~B() ";}
};
void func()
{
A* pb = new B();
delete pb;
}
/*****************************************
调用 func 函数的输出结果:
A::A() B::B()
B::~B() A::~A()
*****************************************/
基类构造函数的调用:
class A{
public:
A(){cout<<"this is A::A()"<<endl;}
A(int b){cout<<"this is A::A(int b): the value of b is: "<<b<<endl;}
A(double b){cout<<"this is A::A(double b) the value of b is: "<<b<<endl;}
};
class B:public A{
public:
B():A(){} //调用基类 A 默认构造函数
B(int b):A(b){} //调用基类 A 的 A::A(int b) 构造函数
B(double b):A(b){} //调用基类 A 的 A::A(double b) 构造函数
};
void main()
{
B b0,b1(5),b2(10.5);//产生三个实例,分别调用不同的基类构造函数
system("pause");
}
/***********************************
调用 func 函数的输出结果:
this is A::A()
this is A::A(int b): the value of b is: 5
this is A::A(double b) the value of b is: 10.5
***********************************/
多继承
声明格式:
class 派生类名:继承方式 基类名,继承方式 基类名… {….};
构造函数:
- 形式:
派生类名::派生类名(形参表):基类名(形参表),基类名(形参表)…{…}
- 特点:
处理同一层的基类构造函数的执行顺序取决于定义派生类对各基类的排列顺序,与定义派生类的构造函数时基类的排列顺序无关
- 二定义性:
原因:
- 由于多层次的交叉派生类关系,造成一个派生类对象包含了基类成员的多个副体
- 多个基类中某个成员名相同
方法:
- 用虚基类来解决由于多层次的交叉派生类关系(原理:采用虚基类定义方式定义派生类,在创建派生类时,类层次结构中某个虚基类的成员只保留一个,即虚基类成员的一个副本被所有派生类共享)
- 用基类中定义成员的访问修改方法来解决多个基类中某个成员名相同
虚基类:
声明格式: class 派生类名:virtual 继承方式 基类名,… { … };
注意:
- virtual与继承方式之间的次序无关且将基类作为虚基类,计算机为此完成一些额外计算量慎用
- 一个类可以在一个类族中用作虚基类,也可以用作非虚基类.
- 在派生类的对象中,同名的虚基类只产生一个虚基类子对象,而某个非虚基类产生各自的对象.
- 虚基类子对象是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的.
- 最派生类是指在继承结构中建立对象时所指定的类.
- 在派生类的构造函数的成员初始化列表中,必须列出对虚基类构造函数的调用,如果没有列出,则表示使用该虚基类的缺省构造函数.
- 在虚基类直接或间接派生的派生类中的构造函数的成员初始化列表中,都要列出对虚基类构造函数的调用.但只有用于建立对象的最派生类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类构造函数的调用在执行中被忽略,从而保证对虚基类子对象只初始化一次.
- 在一个成员初始化列表中,同时出现对虚基类和非虚基类构造函数的调用时,基类的构造函数先于非虚基类的构造函数执行
有虚基类和无虚基类的区别:
- 有虚基类:
- 无虚基类:
例子:
没有虚基类继承:
#include <iostream>
#include <string>
using namespace std;
class A{
protected:
int a;
public:
A():a(0){}; //对成员变量 a 进行初始化
};
class B:public A{
public:
void B_changeValue(int b) //在派生类 B 中修改基类 A 的成员变量 a 并输出其前后的值
{
cout<<"in the class B"<<endl;
cout<<"the original value of a is: "<<a<<endl;
a = b;
cout<<"the value after change is: "<<a<<endl<<endl;
}
};
class C:public A{
public:
void C_changeValue(int c) //在派生类 C 中修改基类 A 的成员变量 a 并输出其前后的值
{
cout<<"in the class C"<<endl;
cout<<"the original value of a is: "<<a<<endl;
a = c;
cout<<"the value after change is: "<<a<<endl<<endl;
}
};
class D:public B,public C{
};
void main()
{
D d;
d.B_changeValue(10);
d.C_changeValue(20);
d.B_changeValue(11);
d.C_changeValue(21);
system("pause");
}
输出结果:
由此可以看出,其实派生类 D 从基类 B 与基类 C 中继承了两份 A 的数据变量备份
有虚基类的继承:
#include <iostream>
#include <string>
using namespace std;
class A{
protected:
int a;
public:
A():a(0){}; //初始化成员变量 a
};
class B:virtual public A{
public:
void B_changeValue(int b) // 在派生 B 中修改基类 A 的成员变量 a 并输出其修改前后的值
{
cout<<"in the class B"<<endl;
cout<<"the original value of a is: "<<a<<endl;
a = b;
cout<<"the value after change is: "<<a<<endl<<endl;
}
};
class C:virtual public A{
public:
void C_changeValue(int c) // 在派生 C 中修改基类 A 的成员变量 a 并输出其修改前后的值
{
cout<<"in the class C"<<endl;
cout<<"the original value of a is: "<<a<<endl;
a = c;
cout<<"the value after change is: "<<a<<endl<<endl;
}
};
class D:public B,public C{
};
void main()
{
D d;
d.B_changeValue(10);
d.C_changeValue(20);
d.B_changeValue(11);
d.C_changeValue(21);
system("pause");
}
输出结果:
由此可以看出,这次派生类 D 从基类 B 与基类 C 中继承了只有一份 A 的数据变量备份
构造函数与析构函数的顺序:
#include <iostream>
#include <string>
using namespace std;
class A{
public:
A(){cout<<"A::A() ";}
~A(){cout<<"A::~A()"<<endl;}
};
class B:virtual public A{
public:
B(){cout<<"B::B() ";}
~B(){cout<<"B::~B() ";}
};
class C:virtual public A{
public:
C(){cout<<"C::C() ";}
~C(){cout<<"C::~C() ";}
};
class D:public B,public C{
public:
D():C(),B(){cout<<"D::D()"<<endl;}
~D(){cout<<"D::~D() ";}
};
class E:public C,public B
{
public:
E():C(),B(){cout<<"E::E()"<<endl;}
~E(){cout<<"E::~E() ";}
};
void funcD()
{
cout<<"this is class D:"<<endl;
cout<<"the order of constructor: ";
D d;
cout<<"the order of destructor: ";
}
void funcE()
{
cout<<"this is class E:"<<endl;
cout<<"the order of constructor: ";
E e;
cout<<"the order of destructor: ";
}
void main()
{
funcD();
cout<<endl;
funcE();
cout<<endl;
system("pause");
}
输出结构:
由此可以看出,构造函数的顺序与继承顺序有关,而不是基类构造函数的调用有关
基类构造函数调用:
#include <iostream>
#include <string>
using namespace std;
class A{
public:
A(){cout<<"this is A::A()"<<endl;}
A(int b){cout<<"this is A::A(int b): the value of b is: "<<b<<endl;}
A(double b){cout<<"this is A::A(double b) the value of b is: "<<b<<endl;}
};
class B
{
public:
B(){cout<<"this is B::B()"<<endl;}
B(int b){cout<<"this is B::B(int b): the value of b is: "<<b<<endl;}
B(double b){cout<<"this is B::B(double b) the value of b is: "<<b<<endl;}
};
class C:public A,public B{
public:
C():A(),B(){cout<<endl;}
C(int b,double c):A(b),B(c){cout<<endl;}
C(double b,int c):A(b),B(c){cout<<endl;}
};
void main()
{
C c0,c1(5,15.123),c2(10.5,1235);
system("pause");
}
输出结果: