文章目录
1.继承
继承:一个类(定义一个新类)自动获得另一个类(已经存在的类)的全部成员。
例如:原有类Animal,则定义一个新类公开继承Animal:
class Bird:public Animal
{
}
公开继承:父类中定义的不同权限变量,在之类和外部的访问权限表:
父类中定义 | 私有private | 保护protected | 公有public |
---|---|---|---|
父类中访问权限 | √ | √ | √ |
之类中访问权限 | × | √ | √ |
外部访问权限 | × | × | √ |
1.1.子类父类同名函数
子类存在于父类中同名函数,子类对象调用同名函数时,子类函数覆盖父类同名函数,与传参数无关。
如果子类同名函数的参数类型或个数与父类不同,子类对象调用时传参依照父类参数表给同名函数传参,父类同名函数同样被子类同名函数覆盖,编译报错,找不到父类函数。
1.同名同参
#include <iostream>
using namespace std;
class System{
private:
int Bit;
public:
void setBit(int Bit)
{
this->Bit = Bit;
}
void show()
{
cout<<"Bit:"<<Bit<<endl;
}
};
class Ubuntu:public System
{
private:
int Version;
public:
void setVer(int Version)
{
this->Version = Version;
}
void show()
{
cout<<"Version:"<<Version<<endl;
}
};
int main()
{
Ubuntu a1;
a1.setVer(2);
a1.setBit(64);
a1.show();
}
执行结果:
root@host:/home/LinuxShare/004.c++/day08# g++ main.cpp
root@host:/home/LinuxShare/004.c++/day08# ./a.out
Version:2
2.同名不同参
#include <iostream>
using namespace std;
class System{
private:
int Bit;
public:
void setBit(int Bit)
{
this->Bit = Bit;
}
void show(bool bOnceAgain)
{
cout<<"Bit:"<<Bit<<endl;
if(bOnceAgain)
{
cout<<"Bit(2):"<<Bit<<endl;
}
}
};
class Ubuntu:public System
{
private:
int Version;
public:
void setVer(int Version)
{
this->Version = Version;
}
void show()
{
cout<<"Version:"<<Version<<endl;
}
};
int main()
{
Ubuntu a1;
a1.setVer(2);
a1.setBit(64);
a1.show();
a1.show(true);
}
编译报错:
root@host:/home/LinuxShare/004.c++/day08# g++ main.cpp
main.cpp: In function ‘int main()’:
main.cpp:43:14: error: no matching function for call to ‘Ubuntu::show(bool)’
a1.show(true);
^
main.cpp:31:7: note: candidate: void Ubuntu::show()
void show()
^~~~
main.cpp:31:7: note: candidate expects 0 arguments, 1 provided
3.同名不同参一般解决办法
在之类中再写一个同名函数,同名同参,去调用父类同名函数。
#include <iostream>
using namespace std;
class System{
private:
int Bit;
public:
void setBit(int Bit)
{
this->Bit = Bit;
}
void show(bool bOnceAgain)
{
cout<<"Bit:"<<Bit<<endl;
if(bOnceAgain)
{
cout<<"Bit(2):"<<Bit<<endl;
}
}
};
class Ubuntu:public System
{
private:
int Version;
public:
void setVer(int Version)
{
this->Version = Version;
}
void show()
{
cout<<"Version:"<<Version<<endl;
}
//写一个与父类相同的函数(参数表也相同)
void show(bool bOnceAgain)
{
//调用父类同名函数
System::show(bOnceAgain);
}
};
int main()
{
Ubuntu a1;
a1.setVer(2);
a1.setBit(64);
a1.show();
a1.show(true);
}
执行结果:
root@host:/home/LinuxShare/004.c++/day08# g++ main.cpp
root@host:/home/LinuxShare/004.c++/day08# ./a.out
Version:2
Bit:64
Bit(2):64
1.2.子类对象是否调用父类构造和析构函数
1.2.1.创建子类对象调用父类构造函数
#include <iostream>
using namespace std;
class Parent{
int m_p;
public:
//父类构造函数
Parent():m_p(0)
{
cout<<"Parent()"<<endl;
}
//父类析构函数
~Parent()
{
cout<<"~Parent()"<<endl;
}
};
class Child:public Parent
{
double m_c;
public:
//子类构造函数
Child():m_c(0.0)
{
cout<<"Child()"<<endl;
}
//子类析构函数
~Child()
{
cout<<"~Child()"<<endl;
}
};
int main()
{
//创建子类对象
Child c1;
return 0;
}
执行结果:
root@host:/home/LinuxShare/004.c++/day09# g++ main.cpp
root@host:/home/LinuxShare/004.c++/day09# ./a.out
Parent()
Child()
~Child()
~Parent()
1.2.2.创建子类对象默认调用父类无参构造函数
#include <iostream>
using namespace std;
class Parent{
int m_p;
public:
Parent():m_p(0)
{
cout<<"Parent()"<<endl;
}
//增加父类有参构造函数
Parent(int d):m_p(0)
{
cout<<"Parent("<<d<<")"<<endl;
}
~Parent()
{
cout<<"~Parent()"<<endl;
}
};
class Child:public Parent
{
double m_c;
public:
Child():m_c(0.0)
{
cout<<"Child()"<<endl;
}
//增加子类有参构造函数
Child(double d):m_c(d)
{
cout<<"Child("<<d<<")"<<endl;
}
~Child()
{
cout<<"~Child()"<<endl;
}
};
int main()
{
Child c1(100);
return 0;
}
执行结果:
root@host:/home/LinuxShare/004.c++/day09# g++ main_bak.cpp
root@host:/home/LinuxShare/004.c++/day09# ./a.out
Parent()
Child(100)
~Child()
~Parent()
1.2.3.创建子类对象并向父类构造函数传递参数方法
在之类构造函数初始化列表中传递,父类类名后跟参数。
#include <iostream>
using namespace std;
class Parent{
int m_p;
public:
Parent():m_p(0)
{
cout<<"Parent()"<<endl;
}
Parent(int d):m_p(0)
{
cout<<"Parent("<<d<<")"<<endl;
}
~Parent()
{
cout<<"~Parent()"<<endl;
}
};
class Child:public Parent
{
double m_c;
public:
Child():m_c(0.0)
{
cout<<"Child()"<<endl;
}
//向父类传参,使用父类类名后跟参数
Child(double d):m_c(d),Parent(50)
{
cout<<"Child("<<d<<")"<<endl;
}
~Child()
{
cout<<"~Child()"<<endl;
}
};
int main()
{
Child c1(100);
return 0;
}
执行结果:
root@host:/home/LinuxShare/004.c++/day09# g++ main_bak.cpp
root@host:/home/LinuxShare/004.c++/day09# ./a.out
Parent()
Child(100)
~Child()
~Parent()
2.无名对象
2.1.无名对象析构调用时机
无名对象,在29行后无法再被用到(没有名字和地址),所以在29行后立即释放。
#include <iostream>
using namespace std;
class A{
int data;
public:
A():data(0)
{
cout<<"A()"<<endl;
}
A(int d):data(d)
{
cout<<"A("<<d<<")"<<endl;
}
~A()
{
cout<<"~A("<<data<<")"<<endl;
}
void show()
{
cout<<"data="<<data<<endl;
}
};
int main()
{
A a1(1);
A(2); //无名对象
cout<<"====="<<endl;
return 0;
}
执行结果:
root@host:/home/LinuxShare/004.c++/day09# g++ main.cpp
root@host:/home/LinuxShare/004.c++/day09# ./a.out
A(1)
A(2)
~A(2)
=====
~A(1)
2.2.无名对象调用成员函数
#include <iostream>
using namespace std;
class A{
int data;
public:
A():data(0)
{
cout<<"A()"<<endl;
}
A(int d):data(d)
{
cout<<"A("<<d<<")"<<endl;
}
~A()
{
cout<<"~A("<<data<<")"<<endl;
}
void show()
{
cout<<"data="<<data<<endl;
}
};
int main()
{
A a1(1);
A(2);
A(3).show();//无名对象调用成员函数
cout<<"====="<<endl;
return 0;
}
执行结果:
root@host:/home/LinuxShare/004.c++/day09# g++ main.cpp
root@host:/home/LinuxShare/004.c++/day09# ./a.out
A(1)
A(2)
~A(2)
A(3)
data=3
~A(3)
=====
~A(1)
2.3.使用无名对象给有名对象赋值
#include <iostream>
using namespace std;
class A{
int data;
public:
A():data(0)
{
cout<<"A()"<<endl;
}
A(int d):data(d)
{
cout<<"A("<<d<<")"<<endl;
}
~A()
{
cout<<"~A("<<data<<")"<<endl;
}
void show()
{
cout<<"data="<<data<<endl;
}
};
int main()
{
A a1(1);
A(2);
A(3).show();
cout<<"====="<<endl;
A a4;
a4 = A(4);//无名对象给有名对象赋值
return 0;
}
执行结果:
root@host:/home/LinuxShare/004.c++/day09# g++ main.cpp
root@host:/home/LinuxShare/004.c++/day09# ./a.out
A(1)
A(2)
~A(2)
A(3)
data=3
~A(3)
=====
A()
A(4)
~A(4)
~A(4)
~A(1)
2.4.无名对象理解为强制类型转换
代码中定义无名对象:
A(4)
形式为:类型(数据)
与强制类型转换的格式相同。
前提:由于数据会传递给构造函数形参,所以要求类型中必须要有能接收一个参数的构造函数。
强制类型转换的另一种形式:(类型)数据
#include <iostream>
using namespace std;
class A{
int data;
public:
A():data(0)
{
cout<<"A()"<<endl;
}
A(int d):data(d)
{
cout<<"A("<<d<<")"<<endl;
}
~A()
{
cout<<"~A("<<data<<")"<<endl;
}
void show()
{
cout<<"data="<<data<<endl;
}
};
int main()
{
A a1(1);
A(2);
A(3).show();
cout<<"====="<<endl;
A a4;
a4 = A(4);
//强制类型转换的另一种形式:(类型)数据
(A)5;
return 0;
}
执行结果:
root@host:/home/LinuxShare/004.c++/day09# g++ main.cpp
root@host:/home/LinuxShare/004.c++/day09# ./a.out
A(1)
A(2)
~A(2)
A(3)
data=3
~A(3)
=====
A()
A(4)
~A(4)
A(5)
~A(5)
~A(4)
~A(1)
2.5.自动类型转换是否可行
#include <iostream>
using namespace std;
class A{
int data;
public:
A():data(0)
{
cout<<"A()"<<endl;
}
A(int d):data(d)
{
cout<<"A("<<d<<")"<<endl;
}
~A()
{
cout<<"~A("<<data<<")"<<endl;
}
void show()
{
cout<<"data="<<data<<endl;
}
};
int main()
{
A a1(1);
A(2);
A(3).show();
cout<<"====="<<endl;
A a4;
a4 = A(4);
(A)5;
//自动类型转换
a4 = 40; //a4 = (A)40;or a4 = A(40)
return 0;
}
执行结果:
root@host:/home/LinuxShare/004.c++/day09# g++ main.cpp
root@host:/home/LinuxShare/004.c++/day09# ./a.out
A(1)
A(2)
~A(2)
A(3)
data=3
~A(3)
=====
A()
A(4)
~A(4)
A(5)
~A(5)
A(40)
~A(40)
~A(40)
~A(1)
3.常量对象
3.1.常量对象不能调用普通成员函数
因为普通成员函数可以修改当前对象的数据成员。
#include <iostream>
using namespace std;
class A{
int data;
public:
A():data(0)
{
cout<<"A()"<<endl;
}
A(int d):data(d)
{
cout<<"A("<<d<<")"<<endl;
}
~A()
{
cout<<"~A("<<data<<")"<<endl;
}
void show()
{
cout<<"data="<<data<<endl;
}
};
int main()
{
A a1(1);
const A a2(2);
a1.show();
//常量对象调用普通常用函数show()
a2.show();
return 0;
}
编译报错:
root@host:/home/LinuxShare/004.c++/day09# g++ main_const.cpp
main_const.cpp: In function ‘int main()’:
main_const.cpp:31:10: error: passing ‘const A’ as ‘this’ argument discards qualifiers [-fpermissiv ]
a2.show();
^
main_const.cpp:19:7: note: in call to ‘void A::show()’
void show()
^~~~
报错原因:普通成员函数可以修改当前对象的数据成员。而常量对象不允许修改。
3.2.常量对象调用成员函数方法
给成员函数添加const
修饰,表示成员函数不去修改数据成员。
#include <iostream>
using namespace std;
class A{
int data;
public:
A():data(0)
{
cout<<"A()"<<endl;
}
A(int d):data(d)
{
cout<<"A("<<d<<")"<<endl;
}
~A()
{
cout<<"~A("<<data<<")"<<endl;
}
//成员函数添加const修饰
void show()const
{
cout<<"data="<<data<<endl;
}
};
int main()
{
A a1(1);
const A a2(2);
a1.show();
a2.show();
return 0;
}
执行结果:
root@host:/home/LinuxShare/004.c++/day09# g++ main_const.cpp
root@host:/home/LinuxShare/004.c++/day09# ./a.out
A(1)
A(2)
data=1
data=2
~A(2)
~A(1)
3.3.const和无const成员函数同时存在(重载的新形式)
如果const修饰的成员函数和无const修饰的成员函数同时存在:
(1)普通对象优先调用无const修饰的成员函数
(2)常量对象调用const修饰的成员函数
重载的新形式。
#include <iostream>
using namespace std;
class A{
int data;
public:
A():data(0)
{
cout<<"A()"<<endl;
}
A(int d):data(d)
{
cout<<"A("<<d<<")"<<endl;
}
~A()
{
cout<<"~A("<<data<<")"<<endl;
}
//无const修饰
void show()
{
cout<<"data="<<data<<endl;
}
//const修饰,使用DATA大写区分
void show()const
{
cout<<"DATA="<<data<<endl;
}
};
int main()
{
A a1(1);
const A a2(2);
a1.show();
a2.show();
return 0;
}
执行结果
root@host:/home/LinuxShare/004.c++/day09# g++ main_const.cpp
root@host:/home/LinuxShare/004.c++/day09# ./a.out
A(1)
A(2)
data=1
DATA=2
~A(2)
~A(1)
4.虚继承(用于菱形继承)
4.1.虚继承解决的问题
虚继承是解决 C++ 多重继承问题的一种手段,从不同途径继承来的同一基类,会在子类中存在多份拷贝。这将存在两个问题:第一,浪费存储空间;第二,存在二义性问题。
针对这种情况,C++ 提供虚基类(virtual base class)的方法,使得在继承间接共同基类时只保留一份成员。
class A // 声明基类A
{
// 代码
};
class B: virtual public A // 声明类 B 是类 A 的公有派生类,A 是 B 的虚基类
{
// 代码
};
class C: virtual public A // 声明类 C 是类 A 的公有派生类,A 是 C 的虚基类
{
// 代码
};
class D: public B, public C // 类 D 中只有一份 A 的数据
{
// 代码
};
【注意】虚基类并不是在声明基类时声明的,而是在声明派生类时,指定继承方式时声明的。因为一个基类可以在生成一个派生类时作为虚基类,而在生成另一个派生类时不作为虚基类。
4.2.虚基类初始化
如果在虚基类中定义了带参数的构造函数,而且没有定义默认构造函数,则在其所有派生类(包括直接派生或间接派生的派生类)中,通过构造函数的初始化列表对虚基类进行初始化。如下:
class A // 声明基类 A
{
A(int i); // 声明一个带有参数的构造函数
};
class B: virtual public A // A 是 B 的虚基类
{
B(int n):A(n){ } // B 类构造函数, 在初始化列表中对虚基类 A 进行初始化
};
class C: virtual public A // A 是 C 的虚基类
{
C(int n):A(n){ } // C 类构造函数, 在初始化列表中对虚基类 A 进行初始化
};
class D: public B, public C
{
D(int n):A(n),B(n),C(n){ } // D 类构造函数, 在初始化列表中对所有基类进行初始化
};
【注意】在定义类 D 的构造函数时,与以往使用的方法有所不同。以往,在派生类的构造函数中只需负责对其直接基类初始化,再由其直接基类负责对间接基类初始化。现在,由于虚基类在派生类中只有一份数据成员,所以这份数据成员的初始化必须由派生类直接给出。如果不由最后的派生类直接对虚基类初始化,而由虚基类的直接派生类(如类 B 和类 C)对虚基类初始化,就有可能由于在类 B 和类 C 的构造函数中对虚基类给出不同的初始化参数而产生矛盾。所以规定:在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。
C++ 编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略虚基类的其他派生类(如类 B 和类 C)对虚基类的构造函数的调用,这就保证了虚基类的数据成员不会被多次初始化。
4.3.虚基类内存分布
本节参考:http://blog.csdn.net/xiejingfa/article/details/48028491
4.3.1.普通多继承子类的内存布局
/**
普通继承(没有使用虚基类)
*/
// 基类A
class A
{
public:
int dataA;
};
class B : public A
{
public:
int dataB;
};
class C : public A
{
public:
int dataC;
};
class D : public B, public C
{
public:
int dataD;
};
从类D的内存布局可以看到A派生出B和C,B和C中分别包含A的成员。再由B和C派生出D,此时D包含了B和C的成员。这样D中就总共出现了2个A成员。大家注意到左边的几个数字,这几个数字表明了D中各成员在D中排列的起始地址,D中的五个成员变量(B::dataA、dataB、C::dataA、dataC、dataD)各占用4个字节,sizeof(D) = 20。
4.3.2.虚继承的内存分布情况
/**
虚继承(虚基类)
*/
#include <iostream>
// 基类A
class A
{
public:
int dataA;
};
class B : virtual public A
{
public:
int dataB;
};
class C : virtual public A
{
public:
int dataC;
};
class D : public B, public C
{
public:
int dataD;
};
我们可以看到,菱形继承体系中的子类在内存布局上和普通多继承体系中的子类类有很大的不一样。对于类B和C,sizeof的值变成了12,除了包含类A的成员变量dataA外还多了一个指针vbptr,类D除了继承B、C各自的成员变量dataB、dataA和自己的成员变量外,还有两个分别属于B、C的指针。
D对象的内存布局就变成如下的样子:
vbptr:继承自父类B中的指针
int dataB:继承自父类B的成员变量
vbptr:继承自父类C的指针
int dataC:继承自父类C的成员变量
int dataD:D自己的成员变量
int A:继承自父类A的成员变量
虚继承之所以能够实现在多重派生子类中只保存一份共有基类的拷贝,关键在于vbptr指针。那vbptr到底指的是什么?又是如何实现虚继承的呢?其实上面的类D内存布局图中已经给出答案:
实际上,vbptr指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚表(virtual table),虚表中记录了vbptr与本类的偏移地址;第二行是vbptr到共有基类元素之间的偏移量。在这个例子中,类B中的vbptr指向了虚表D::$vbtable@B@,虚表表明公共基类A的成员变量dataA距离类B开始处的位移为20,这样就找到了成员变量dataA,而虚继承也不用像普通多继承那样维持着公共基类的两份同样的拷贝,节省了存储空间。
为了进一步确定上面的想法是否正确,我们可以写一个简单的程序加以验证:
int main()
{
D* d = new D;
d->dataA = 10;
d->dataB = 100;
d->dataC = 1000;
d->dataD = 10000;
B* b = d; // 转化为基类B
C* c = d; // 转化为基类C
A* fromB = (B*) d;
A* fromC = (C*) d;
std::cout << "d address : " << d << std::endl;
std::cout << "b address : " << b << std::endl;
std::cout << "c address : " << c << std::endl;
std::cout << "fromB address: " << fromB << std::endl;
std::cout << "fromC address: " << fromC << std::endl;
std::cout << std::endl;
std::cout << "vbptr address: " << (int*)d << std::endl;
std::cout << " [0] => " << *(int*)(*(int*)d) << std::endl;
std::cout << " [1] => " << *(((int*)(*(int*)d)) + 1)<< std::endl; // 偏移量20
std::cout << "dataB value : " << *((int*)d + 1) << std::endl;
std::cout << "vbptr address: " << ((int*)d + 2) << std::endl;
std::cout << " [0] => " << *(int*)(*((int*)d + 2)) << std::endl;
std::cout << " [1] => " << *((int*)(*((int*)d + 2)) + 1) << std::endl; // 偏移量12
std::cout << "dataC value : " << *((int*)d + 3) << std::endl;
std::cout << "dataD value : " << *((int*)d + 4) << std::endl;
std::cout << "dataA value : " << *((int*)d + 5) << std::endl;
}
得到结果: