1,派生类的声明方式
class Student
{
public:
void display()
{
cout << "num:" << num << endl;
cout << "name:" << name << endl;
cout << "sex:" << sex << endl;
}
private:
int num;
char name[10];
char sex;
};
class Student1 : public Student//声明派生类
{
public:
void display_1()
{
cout << "age:" << endl;
cout << "address:" << addr << endl;
}
private:
int age;
char addr[10];
};
2,派生类成员的访问属性
(1)public(公用继承)
class student
{
public:
void getvalue()
{
cin >> num >> name >> sex;
}
void display()
{
cout << "num:" << num << endl;
cout << "name::" << name << endl;
cout << "sex:" << sex << endl;
}
private:
int num;
char name[10];
char sex[5];
};
class student1:public student
{
public:
void getvalue_1()
{
getvalue();
cin >> age>>addr;
}
void display_1()
{
display();//调用基类公有成员函数来访问基类私有成员变量
cout << "num:" << num << endl;//报错,基类私有成员不可访问
cout << "age:" << age << endl;
cout << "addr:" << addr << endl;
}
int age;
char addr[10];
};
int main()
{
Student1 stud;
stud.getvalue_1();
stud.display_1();
system("pause\n");
return 0;
}
下面用一张表来说明公用基类的成员在派生类中的访问属性
(2)private(私有继承)
将上面的程序改为私有继承
class student
{
public:
void getvalue()
{
cin >> num >> name >> sex;
}
void display()
{
cout << "num:" << num << endl;
cout << "name::" << name << endl;
cout << "sex:" << sex << endl;
}
private:
int num;
char name[10];
char sex[5];
};
class Student1 :private student
{
public:
void getvalue_1()
{
getvalue();
cin >> age >> addr;
}
void display_1()
{
display();
//cout << "num:" << num << endl;
cout << "age:" << age << endl;
cout << "addr:" << addr << endl;
}
private:
int age;
char addr[10];
};
int main()
{
Student1 stud;
//stud.getvalue();//报错,私有基类的公用成员函数在外界不可访问
stud.display_1();
//stud.age = 18;//错误,外界无法访问派生类的私有成员
system("pause\n");
return 0;
}
结论:(1)不能通过派生类对象引用从私有基类继承过来的任何成员
(2)派生类的成员函数不能访问私有基类的私有成员,但可以访问私有类的公用成员函数
(3)protected(保护继承)
class student
{
private:
void display();
protected:
int num;
char name[10];
char sex[5];
};
class Student1 :protected student
{
public:
void getvalue_1()
{
cin >> num >> name >> sex;
cin >> age >> addr;
}
void display_1()
{
cout << "num:" << num << endl;
cout << "name:" << num << endl;
cout << "sex:" << num << endl;
cout << "age:" << age << endl;
cout << "addr:" << addr << endl;
}
private:
int age;
char addr[10];
};
int main()
{
Student1 stud;
stud.display_1();
//stud.display();//报错,保护基类的公用成员在外界不可访问
stud.display_1();
//stud.age = 18;//报错,基类的保护成员对派生类的外界来说是不可访问的
system("pause\n");
return 0;
}
保护基类的公有成员和保护成员在派生类中都成了保护成员,其私有成员仍为基类私有,也就是把基类原有的公有成员也保护起来,不让类外任意访问。
3,多级派生时的访问属性
class A
{
public:
int i;
protected:
void f1(){}
int j;
private:
int k;
};
class B :public A
{
public:
void f2(){}
protected:
void f3(){}
private:
int m;
};
class C :protected B
{
public:
void f4();
private:
int n;
};
int main()
{
C c;
B b;
b.i = 10;//可访问
//c.f2();//不可访问
}
各成员在不同类中的访问属性
无论哪一种继承方式,在派生类中是不能访问基类的私有成员的,私有成员只能被本类的成员函数访问。
4,派生类的构造函数和析构函数
先定义一个简单的派生类的构造函数
class Student
{
public:
Student(int n, string nam, char s)//定义基类构造函数
:num(n), name(nam), sex(s)
{
cout << "Student()" << endl;
}
~Student()
{
cout << "~Student()" << endl;
}
protected:
int num;
string name;
char sex;
};
class Student1 :public Student
{
public:
Student1(int n, string nam, char s, int a, string add)//定义派生类构造函数
:Student(n, nam, s), age(a), addr(add)//在初始化列表初始化
{
cout << "Student1()"<<endl;
}
void show()
{
cout << "num:" << num << endl;
cout << "name:" << name << endl;
cout << "sex:" << sex << endl;
cout << "age:" << age << endl;
cout << "add:" << addr << endl;
}
~Student1()
{
cout << "~Student1()" << endl;
}
private:
int age;
string addr;
};
int main()
{
Student1 stud1(1000, "wangli", 'n', 19, "shanxikejidaxue");
Student1 stud2(1001, "liuyulin", 'n', 18, "xibeihzengfadaxue");
stud1.show();
cout << endl;
stud2.show();
//system("pause\n");
return 0;
}
分别在两个构造函数和两个析构函数处大断点,对程序进行调试,打印的结果如图
从结果上看,程序是先调用了基类的构造函数,再调用派生类的构造函数,程序结束后,先析构派生类,再析构基类。
事实上,从程序的调试过程可以看出,程序是先调用派生类的构造函数,此时,执行调用基类构造函数完成对基类的初始化,然后再继续执行派生类构造函数的函数体。此处可上机实践进行验证。
若派生类中有子对象,则在派生类构造函数中对子对象进行初始化。因此,执行派生类构造函数的次序是
(1)调用基类构造函数,对基类数据成员初始化
(2)调用子对象构造函数,对子对象数据成员初始化
(3)再执行派生类构造函数本身,对派生类数据成员初始化
派生类是不能继承基类的析构函数的,也需要通过派生类的析构函数去调用基类的析构函数。
5,多重继承引起的二义性问题及同名覆盖
class A
{
public:
int a;
void display();
};
class B
{
public:
int a;
void display();
};
class C :public A, public B
{
public:
int b;
//void show();
};
int main()
{
C c;
c.a = 10;//报错
c.display();//报错
}
编译系统无法判别要访问的是哪个基类的成员,编译出错,这就是多重继承存在的二义性问题。
如果将C类改为如下形式
class C :public A, public B
{
public:
int a;
void display();
};
int main()
{
C c;
c.a = 10;//编译通过
c.display();//编译通过
}
此时编译通过,那么程序执行时访问的到底是哪一个类中的成员?
此时,访问的是C类的成员,规则:基类的同名成员在派生类中被屏蔽,成为“不可见”的,或者说,派生类新增加的同名成员覆盖了基类中的同名成员。
注意:不同的成员函数,只有在函数名和参数个数相同,类型匹配的情况下才发生同名覆盖
6,菱形继承及虚基类
菱形继承时多重继承中的一种继承方式,也存在二义性问题
这样的继承方式成为菱形继承
class A
{
public:
int data;
void fun(){}
};
class B :public A
{
public:
int data;
void fun(){}
int _b;
};
class C :public A
{
public:
int data;
void fun(){}
int _c;
};
class D :public B, public C
{
public:
int _d;
void fun_d(){}
};
从上图看出,不同类中相同的成员在D中产生了多份拷贝,不仅占用内存,而且对访问这些成员增加困难。
下面我们介绍虚基类
虚基类可使得在继承间接共同基类时只保留一份成员
将上面的代码改为
class A
{};
class B:virtual public A
{};
class C:virtual public A
{};
之前学习继承多态的时候学的是似懂非懂的,所以这篇博客内容也只是一些概念性的东西,并未深入探索,再回过头学习时,以前模棱两可的东西突然就明白了一些,所以决定将这篇博客更新。
菱形继承中含有虚函数:
class A
{
public:
virtual void Fun1()
{
cout << "A::Fun1" << endl;
}
virtual void Fun2()
{
cout << "A::Fun2" << endl;
}
int _a;
};
class B :public A
{
public:
virtual void Fun1()
{
cout << "B::Fun1" << endl;
}
virtual void Fun3()
{
cout << "B::Fun3" << endl;
}
int _b;
};
class C :public A
{
public:
virtual void Fun1()
{
cout << "C::Fun1()" << endl;
}
virtual void Fun4()
{
cout << "C::Fun4" << endl;
}
int _c;
};
class D :public B, public C
{
public:
virtual void Fun1()
{
cout << "D::Fun1" << endl;
}
virtual void Fun5()
{
cout << "D::Fun5" << endl;
}
int _d;
};
//打印虚表
typedef void(*V_FUNC) ();
void PrintVTable(int* vtable)
{
printf("vtable 0x%p\n", vtable);
int** ppVtable = (int**)vtable;
for (size_t i = 0; ppVtable[i] != 0; ++i)
{
printf("vatable[%u]:0x%p->", i, ppVtable[i]);
V_FUNC f = (V_FUNC)ppVtable[i];
f();
}
cout << "——————————————————————————————" << endl;
}
void Test1()
{
D d;
d.B::_a = 1;
d._b = 2;
d.C::_a = 3;
d._c = 4;
d._d = 5;
PrintVTable(*((int**)&d));//第一个虚表B的
PrintVTable(*((int**)((char*)&d + sizeof(B))));//C的虚表
}
int main()
{
Test1();
system("pause");
return 0;
}
我们通过内存窗口来查看D的虚表:
可以证明,多继承时,子进程中新增的虚函数是写入了第一个继承的父类的虚表中。
菱形虚拟继承中含有虚函数:
class A
{
public:
virtual void Fun1()//去掉Func1崩溃,因为BC是同级的。虚继承不明确
{
cout << "A::Fun1" << endl;
}
virtual void Fun2()
{
cout << "A::Fun2" << endl;
}
int _a;
};
class B :virtual public A
{
public:
virtual void Fun1()
{
cout << "B::Fun1" << endl;
}
virtual void Fun3()
{
cout << "B::Fun3" << endl;
}
int _b;
};
class C :virtual public A
{
public:
virtual void Fun1()
{
cout << "C::Fun1()" << endl;
}
virtual void Fun4()
{
cout << "C::Fun4" << endl;
}
int _c;
};
class D :public B, public C
{
public:
virtual void Fun1()
{
cout << "D::Fun1" << endl;
}
virtual void Fun5()
{
cout << "D::Fun5" << endl;
}
int _d;
};
typedef void(*V_FUNC) ();
void PrintVTable(int* vtable)
{
printf("vtable 0x%p\n", vtable);
int** ppVtable = (int**)vtable;
for (size_t i = 0; ppVtable[i] != 0; ++i)
{
printf("vatable[%u]:0x%p->", i, ppVtable[i]);
V_FUNC f = (V_FUNC)ppVtable[i];
f();
}
cout << "——————————————————————————————" << endl;
}
void Test1()
{
D d;
d.B::_a = 1;
d._b = 2;
d.C::_a = 3;
d._c = 4;
d._d = 5;
//(*(int**)&d)解引用二级指针,在32位和64位下均能正确使用
PrintVTable(*((int**)&d));//第一个虚表B的
PrintVTable(*((int**)((char*)&d + sizeof(B)-sizeof(A))));//C的虚表
PrintVTable(*((int**)((char*)&d + sizeof(D)-sizeof(A))));//A的虚表
//B的虚表里其实是含有A的
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
cout << sizeof(C) << endl;
cout << sizeof(D) << endl;
B b;
b._a = 10;
b._b = 11;
}
int main()
{
Test1();
system("pause");
return 0;
}
通过上图可以看出,菱形虚拟继承中,为了避免二义性,公共父类A有单独的虚表,且图中有两张表,一个是虚基表,里面保存的是虚继承中子类的偏移量,而虚表中保存的是虚函数。
公共数据a在D对象的最底层,且B类和C类中均包含A类,所以的它们的大小为虚表指针+虚基表指针+各自数据大小+A类中公共数据大小。