继承
继承的基本语法
class 子类 : 继承方式1 基类1, 继承方式2 基类2, ...
{
…
};
继承方式有以下三种:
公有继承:public
保护继承:protected
私有继承:private
三种继承方式的共同特点
继承所要达到的目的:
(1)子类对象包含基类子对象
(2)子类内部可以直接访问基类的所有非私有成员
继承的本质:
基类的非私有成员在子类中仅仅为可见,而非拥有
注意:对于继承切忌不要理解为基类的成员变为子类的成员,继承不会改变类成员的作用域,基类的成员永远都是基类的成员,并不会因为继承而变成子类的成员
#include <iostream>
using namespace std;
class Base//基类
{
public:
int m_a;
void foo() { cout << "Base::foo" << endl; }
protected:
int m_b;
void bar() { cout << "Base::bar" << endl; }
private:
int m_c;
void hum() { cout << "Base::hum" << endl; }
};
class Derived : public Base//派生类
{
public:
void fun()
{
/*子类内部可以直接访问基类的多有非私有成员*/
m_a = 100;// ok
foo(); // ok
m_b = 200;// ok
bar(); // ok
/*
基类内部的私有成员不能在子类中访问
m_c=300;//error
hum();//error
*/
}
private:
int m_d;
};
int main(void)
{
Base b;
cout << "基类对象b的大小:" << sizeof(b) << endl; // 12
Derived d;//子类对象包含基类子对象
cout << "子类对象d的大小:" << sizeof(d) << endl; // 16
return 0;
}
运行结果:
尽管基类的公有和保护成员在子类中直接可见,但仍然可以在子类中重新定义这些名字,子类中的名字会隐藏所有基类中的同名定义。
如果需要在子类内部访问一个在基类中定义却被子类标识符所隐藏的名字,可以借助作用域限定操作符 :: 实现
因为作用域的不同,分别在子类和基类中定义的同名成员函数(包括静态成员函数),并不构成重载关系,相反是一种隐藏关系.
任何时候,在子类的内部,总可以通过作用域限定操作符 :: ,显式地调用那些在基类中定义却被子类所隐藏的成员
#include <iostream>
using namespace std;
class Base
{
public:
int m_a;
void foo() { cout << "Base::foo" << endl; }
protected:
int m_b;
void bar() { cout << "Base::bar" << endl; }
private:
int m_c;
void hum() { cout << "Base::hum" << endl; }
};
class Derived : public Base {
public:
void fun()
{
m_a = 100;
foo();//出现同名foo()函数,基类中的foo 被 子类的foo 隐藏
m_b = 200;
Base::bar();//基类中的bar 虽 被 子类中的bar隐藏,但可以通过作用域限定符强行调用基类bar
}
private:
int foo() {cout << "Derived::foo" << endl;}
int bar() {cout << "Derived::bar" << endl;}
private:
int m_d;
};
int main(void)
{
Derived d;
d.fun();
return 0;
}
运行结果
三种继承的差别
基类中的公有、保护和私有成员,在子类中将对这些基类成员的访问控制限定进行重新标记
当“通过”子类访问其所继承的基类的成员时,需要考虑因继承方式对访问控制限定的影响。
#include <iostream>
using namespace std;
class Base
{
public:
int m_a;//原始标记为public
void foo() { cout << "Base::foo" << endl; }
protected:
int m_b;//原始标记为protected
void bar() { cout << "Base::bar" << endl; }
private:
int m_c;//原始标记为private
void hum() { cout << "Base::hum" << endl; }
};
class Derived : public Base
{
/*
子类对基类的成员重新标记防控限定
m_a/foo重新标记防控限定是public
m_b/bar重新标记防控限定是protected
m_c/hum重新标记防控限定是private
*/
public:
void fun()
{
m_a = 100;
foo();
m_b = 200;
bar();
}
private:
int m_d;
};
运行结果:
class Derived : protected Base
{
/*
子类对基类的成员重新标记防控限定
m_a/foo重新标记防控限定是protected
m_b/bar重新标记防控限定是protected
m_c/hum重新标记防控限定是private
*/
public:
void fun()
{
m_a = 100;
foo();
m_b = 200;
bar();
}
private:
int m_d;
};
class Derived : private Base
{
/*
子类对基类的成员重新标记防控限定
m_a/foo重新标记防控限定是private
m_b/bar重新标记防控限定是private
m_c/hum重新标记防控限定是private
*/
public:
void fun()
{
m_a = 100;
foo();
m_b = 200;
bar();
}
private:
int m_d;
};
公有成员独有特点
子类对象在类外可以访问基类公有成员(其他继承方式不可以)
如果被子类同名标识符隐藏也可以借助作用域限定符 :: 指定访问基类的公有成员
子类类型的指针或引用 和 基类类型的指针或引用可以进行转换(其他继承方式不可以)
#include <iostream>
#pragma pack(1)
using namespace std;
class Human
{
public:
int m_age;
string m_name;
};
class Student : public Human
{
public:
int m_no;
};
int main(void)
{
Human h;
cout << "Human类对象h的大小:" << sizeof(h) << endl;
Student s;
cout << "Student类对象s的大小:" << sizeof(s) << endl;
//以下两种访问范围缩小是安全的
Human* ph = &s;//Student* --> Human*
Human& rh = s;
//以下两种,访问范围扩大有风险,虽然通过强转可以成功,但风险依然存在
Student* ps = static_cast<Student*>(&h);
Student& rs = static_cast<Student&>(h);
//以下两种转换毫无风险
Student* pps = static_cast<Student*>(ph);//Human* --> Student*
Student& rrs = static_cast<Student&>(h);
return 0;
}
运行结果
子类的构造、析构
构造
子类没有定义构造函数,编译器为子类提供的默认无参构造函数
定义基类子对象,并调用其基类的无参构造函数,构造该子类对象中的基类子对象。
子类定义构造函数但没有在初始化表中指明基类部分构造方式
定义基类子对象,并调用其基类的无参构造函数,构造该子类对象中的基类子对象。
子类定义构造函数并在初始化表中指明基类部分构造方式
定义基类子对象并调用指明的其基类的构造函数。
子类对象的构造过程:构造基类子对象->构造成员变量->执行构造代码
阻断继承:
子类的构造函数无论如何都会调用基类的构造函数,构造子类对象中的基类子对象
如果把基类的构造函数定义为私有,那么该类的子类就永远无法被实例化为对象,在C++中可以用这种方法阻断一个类被扩展
析构
子类没有定义析构函数编译器将提供一个默认析构函数
析构完所有的成员变量以后,会自动调用其基类的析构函数.
子类定义析构函数
子类的析构函数在执行完自身析构代码,并析构完所有的成员变量以后,会自动调用其基类的析构函数.
子类对象的析构过程:执行析构代码->析构成员变量->析构基类子对象
拷贝构造
子类没有定义拷贝构造函数,编译器为子类提供的默认拷贝构造函数
定义基类子对象,并调用其基类的拷贝构造函数,构造该子类对象中的基类子对象
子类定义了拷贝构造函数,但没有在初始化表指明其基类部分的构造方式
定义基类子对象,并调用其基类的无参构造函数,构造该子类对象中的基类子对象
子类定义了拷贝构造函数,同时初始化表中指明了其基类部分以拷贝方式构造
定义基类子类对象,并调用其基类的拷贝构造函数,
构造该子类对象中的基类子对象
拷贝赋值
子类没有定义拷贝赋值函数编译器为子类提供的缺省拷贝赋值函数
会自动调用其基类的拷贝赋值函数,复制该子类对象中的基类子对象
子类定义了拷贝赋值函数,但没有显式调用其基类的拷贝赋值函数
子类对象中的基类子对象将得不到复制
子类定义了拷贝赋值函数,同时显式调用了其基类的拷贝赋值函数
子类对象中的基类子对象将得到复制
代码
#include <iostream>
using namespace std;
class Human
{
public:
Human(int age=0,const char* name="wr"):m_age(age),m_name(name)
{
cout << "Human类的缺省构造函数被调用" << endl;
}
Human(const Human& that):m_age(that.m_age),m_name(that.m_name)
{
cout << "Human类拷贝构造函数被调用" << endl;
}
Human& operator=(const Human& that)
{
cout << "Human类的拷贝赋值函数被调用" << endl;
this->m_age=that.m_age;
this->m_name=that.m_name;
return *this;
}
void getinfo()
{
cout << "姓名:" << this->m_name << ",年龄:" <<this->m_age;
}
~Human()
{
cout << "Human类的析构函数被调用" << endl;
}
private:
int m_age;
string m_name;
};
class Student:public Human
{
public:
Student(int age=0,const char* name="wr",float score=0.0,const char* remark="w")
:Human(age,name),m_score(score),m_remark(remark)
{
cout << "Student类的缺省构造函数被调用" << endl;
}
Student(const Student& that):Human(that),m_score(that.m_score),m_remark(that.m_remark)
{
cout << "Student类的拷贝构造函数被调用" << endl;
}
Student& operator=(const Student& that)
{
cout << "Student类的拷贝赋值函数被调用" << endl;
Human& rh = *this;
rh=that;
this->m_score=that.m_score;
this->m_remark=that.m_remark;
return *this;
}
void getinfo()
{
Human::getinfo();
cout << ",成绩:" << m_score << ",评语:" << m_remark << endl;
}
~Student()
{
cout << "Student类的析构函数被调用" << endl;
}
private:
float m_score;
string m_remark;
};
int main(void)
{
cout << "---------------s1的创建信息---------------" << endl;
Student s1(23,"Tom",88.8,"良好");
s1.getinfo();
cout << "---------------s2的创建信息---------------" << endl;
Student s2(s1);
s2.getinfo();
cout << "---------------s3的创建信息---------------" << endl;
Student s3;
cout << "s3被赋值前---";
s3.getinfo();
s3=s2;
cout << "s3被赋值后---";
s3.getinfo();
cout << "---------------main will be over---------------" << endl;
return 0;
}
运行结果
多重继承
多重继承的内存布局
子类对象中的多个基类子对象,按照继承表的顺序依次被构造,析构的顺序则与构造严格相反,各个基类子对象按照从低地址到高地址排列
#include <iostream>
using namespace std;
class A
{
public:
int m_a;
A() {cout << "A()" << endl;}
~A() {cout << "~A()" << endl;}
};
class B
{
public:
int m_b;
B() {cout << "B()" << endl;}
~B() {cout << "~B()" << endl;}
};
class C
{
public:
int m_c;
C() {cout << "C()" << endl;}
~C() {cout << "~C()" << endl;}
};
class D:public A, public B, public C//汇聚子类
{
public:
D() {}
~D() {}
int m_d;
};
int main(void)
{
D d;
cout << "汇聚子对象d的大小:" << sizeof(d) << endl;
D* pd=&d;
cout << "整个汇聚子类对象的首地址D* pd:" << pd << endl;
cout << "A基类子对象的首地址:" << &d.m_a << endl;
cout << "B基类子对象的首地址:" << &d.m_b << endl;
cout << "C基类子对象的首地址:" << &d.m_c << endl;
cout << "D基类子对象的首地址:" << &d.m_d << endl;
return 0;
}
运行结果:
多重继承的类型转换
1.将多重继承的子类对象的指针,隐式转换为它的基类类型,编译器会根据各个基类子对象在子类对象中的内存位置,进行适当的偏移计算。以保证指针的类型与其所指向目标对象的类型一致
2.反之,将任何一个基类类型的指针静态转换为子类类型,编译器同样会进行适当的偏移计算
3.无论在哪个方向上,重解释类型转换(reinterpret_cast)都不进行任何偏移计算
4.引用的情况与指针类似,因为引用的本质就是指针
#include <iostream>
using namespace std;
class A
{
public:
int m_a;
};
class B
{
public:
int m_b;
};
class C
{
public:
int m_c;
};
class D : public A, public B, public C//汇聚子类
{
public:
int m_d;
};
int main( void ) {
D d;
cout << "汇聚子类对象d的大小:" << sizeof(d) << endl; // 16
D* pd = &d;
cout << "整个汇聚子类对象的首地址 D* pd:" << pd << endl;
cout << "A基类子对象的首地址:" << &d.m_a << endl;
cout << "B基类子对象的首地址:" << &d.m_b << endl;
cout << "C基类子对象的首地址:" << &d.m_c << endl;
cout << "D类自己的成员 &m_d:" << &d.m_d << endl;
cout << "-------------隐式转换-------------------" << endl;
A* pa = pd;
cout << "D* pd--->A* pa:" << pa << endl;
B* pb = pd;
cout << "D* pd--->B* pb:" << pb << endl;
C* pc = pd;
cout << "D* pd--->C* pc:" << pc << endl;
cout << "-------------静态转换-------------------" << endl;
D* p1 = static_cast<D*>(pa);
cout << "A* pa--->D* p1:" << p1 << endl;
D* p2 = static_cast<D*>(pb);
cout << "B* pb--->D* p2:" << p2 << endl;
D* p3 = static_cast<D*>(pc);
cout << "C* pc--->D* p3:" << p3 << endl;
cout << "-------------重解释类型转换-------------------" << endl;
pa = reinterpret_cast<A*>(pd);
cout << "D* pd--->A* pa:" << pa << endl;
pb = reinterpret_cast<B*>(pd);
cout << "D* pd--->B* pb:" << pb << endl;
pc = reinterpret_cast<C*>(pd);
cout << "D* pd--->C* pc:" << pc << endl;
return 0;
}
运行结果:
多重继承的名字冲突
如果在子类的多个基类中,存在同名的标识符,那么任何试图通过子类对象,或在子类内部访问该名字的操作,都将引发歧义。
名字冲突问题解决方法:
(1)子类隐藏该标识符
(2)通过作用域限定操作符“::”显式指明所属基类
#include <iostream>
using namespace std;
class A
{
public:
int m_a;
int m_c;
};
class B
{
public:
int m_b;
int m_c;
};
class D : public A, public B
{
public:
int m_d;
int m_c;
void foo()
{
A::m_c = 300;
}
};
int main( void )
{
D d;
cout << "汇聚子类对象d的大小:" << sizeof(d) << endl;
d.B::m_c = 100;
d.foo();
cout << "d.A::m_c=" << d.A::m_c << endl;
cout << "d.B::m_c=" << d.B::m_c << endl;
d.m_c=200;
cout << "d.D::m_c=" << d.m_c << endl;
return 0;
}
运行结果:
钻石继承
概念:一个子类继承自多个基类,而这些基类又源自共同的祖先,这样的继承结构称为钻石继承(菱形继承)
钻石继承的问题:
1.公共基类子对象,在汇聚子类对象中,存在多个实例
2.在汇聚子类内部,或通过汇聚子类对象,访问公共基类的成员,会因继承路径的不同而导致匹配歧义
#include <iostream>
#pragma pack(1)
using namespace std;
class A//公共基类
{
public:
int m_a;
};
class X:public A//中间子类
{
public:
int m_x;
};
class Y:public A//中间子类
{
public:
int m_y;
};
class Z:public X,public Y//汇聚子类
{
public:
int m_z;
void foo()
{
X::m_a=20;//没有访问限定符,将会引发歧义
}
};
int main(void)
{
Z z;// | X中间子类子对象 | Y中间子类子对象 |m_z| --->
// |A公共基类子对象 m_x|A公共基类子对象 m_y|m_z| --->
// | m_a m_x| m_a m_y|m_z|
cout << "汇聚子类对象z的大小:" << sizeof(z) << endl;
z.Y::m_a=20;//没有访问限定符,将会引发歧义
return 0;
}
运行结果
虚继承与虚函数
虚继承
虚继承可以用来解决钻石继承带来的问题,方法如下:
(1)在继承表中使用virtual关键字
(2)虚继承可以保证:
①公共虚基类子对象在汇聚子类对象中仅存一份实例
②公共虚基类子对象被多个中间子类子对象所共享
虚继承实现原理:汇聚子类对象中的每个中间子类子对象都持有一个指针,通过该指针可以获取 中间子类子对象的首地址 到 公共虚基类子对象的首地址的 偏移量
#include <iostream>
#pragma pack(1)
using namespace std;
class A//公共基类
{
public:
int m_a;
};
class X:virtual public A//中间子类
{
public:
int m_x;
void SetAge(/*X* this*/int age)
{
this->m_a=age;//
}
};
class Y:virtual public A//中间子类
{
public:
int m_y;
int GetAge(/*Y* this*/)
{
return this->m_a;
}
};
class Z:public X,public Y//汇聚子类
{
public:
int m_z;
void foo()
{
m_a=20;
}
};
int main(void)
{
Z z;// | X中间子类子对象 | Y中间子类子对象 |m_z|A公共基类子对象| --->
// | 指针1 m_x | 指针2 m_y |m_z| m_a |
cout << "汇聚子类对象z的大小:" << sizeof(z) << endl;//32
z.m_a=20;
z.SetAge(28);//SetAge(&z,28)
cout << z.GetAge() << endl;
return 0;
}
运行结果:
虚函数和覆盖
(1)虚函数
形如
class 类名
{
virtual 返回类型 函数名 (形参表) { … }
};的成员函数,称为虚函数或方法
(2)覆盖:如果子类的成员函数和基类的虚函数具有相同的函数签名,那么该成员函数就也是虚函数,无论其是否带有virtual关键字,且与基类的虚函数构成覆盖关系
(3)通过基类类型指针调用虚函数
①如果基类型指针指向基类对象,调用基类的原始版本虚函数。
②如果基类型指针指向子类对象,调用子类的覆盖版本虚函数。
#include <iostream>
using namespace std;
class Shape
{
public:
virtual void Draw() { cout << "Shape::Draw" << endl; }//虚函数 原始版本
int m_x;
int m_y;
};
class Rect:public Shape
{
public:
void Draw() { cout << "Rect::Draw" << endl; }//虚函数(编译器补virtual)覆盖版本
int m_rx;
int m_ry;
};
class Circle:public Shape
{
public:
virtual void Draw() { cout << "Circle::Draw" << endl; }//虚函数(编译器不补virtual) 覆盖版本
int m_radius;
};
int main(void)
{
cout << "------利用对象 调用 虚成员函数------" << endl;
// 哪个类对象 就调用 哪个类 虚成员函数--对象的自洽性
Shape s;
s.Draw();
Rect r;
r.Draw();
Circle c;
c.Draw();
cout << "------利用指针 调用 虚成员函数--------" << endl;
// 根据 指针指向的对象的类型 来确定 调用哪个类 虚 成员函数
Shape* ps = &s;
ps->Draw();
ps=&r;
ps->Draw();
ps = &c;
ps->Draw();
return 0;
}
运行结果: