类的继承和派生
1.1 继承的基本语法
class 派生类名:[继承方式] 基类名
{
派生类新增加的成员
};
1.2 三种继承方式 public/protected/private
public成员可以通过对象来访问
protected成员和private成员类似,也不能通过对象访问
在类的内部,private和protected都可以用来限制成员的访问权限,但它们有以下区别:
1. private成员只能在当前类的内部访问,而不能被派生类或其他类访问。
2. protected成员可以在当前类和派生类的内部访问,但不能被其他类访问。
3. private成员和protected成员都不能被外部访问,只能通过类的公共接口访问。
因此,private成员主要用于封装类的实现细节,防止外部直接访问和修改类的内部状态;而protected成员主要用于派生类继承和重载基类的成员函数。
三种继承方式
1.2 三种继承方式 public/protected/private
public、protected、private 修饰类的成员:
public 成员可以通过对象来访问
protected 成员和 private 成员类似,也不能通过对象访问
但是当存在继承关系时,protected 和 private 就不一样了:基类中的 protected 成员可以在派生类中使用,而基类中的 private 成员不能在派生类中使用
public、protected、private 指定继承方式:
不同的继承方式会影响基类成员在派生类中的访问权限,
1 public继承方式
基类中所有 public 成员在派生类中为 public 属性;
基类中所有 protected 成员在派生类中为 protected 属性;
基类中所有 private 成员在派生类中不能使用。
2 protected继承方式
基类中的所有 public 成员在派生类中为 protected 属性;
基类中的所有 protected 成员在派生类中为 protected 属性;
基类中的所有 private 成员在派生类中不能使用。
3 private继承方式
基类中的所有 public 成员在派生类中均为 private 属性;
基类中的所有 protected 成员在派生类中均为 private 属性;
基类中的所有 private 成员在派生类中不能使用
/*===============================================
* 文件名称:inherit_protected.cpp
* 创 建 者: memories
* 创建日期:2023年06月01日
* 描 述:have a nice day
================================================*/
#include <iostream>
using namespace std;
class Base
{
public:
int b1;
void show();
private:
int b3;
protected:
int b2;
};
void Base::show()
{
cout << "b1=" << b1 << endl;
}
//派生类//通常采用保护继承//父类
class Derived: protected Base
{
public:
int d1;
void display();
private:
int d2;
};
void Derived::display()
{
//采用保护继承//子类访问父类,保护的可以访问,私有的不能访问
show();
cout << "b2=" << b2 << endl;
// cout << "b3=" << b3 << endl;
cout << "d1=" << d1 << endl;
}
void debug()
{
cout << "--------------------" << endl;
}
int main()
{
Derived d;
// d.b1 = 100;//派生类可以任意调用父类的属性和行为
d.d1 = 200;
// d.show();
d.display();
//d.b2 = 1;
return 0;
}
继承时的名字遮盖问题
1.3 继承时的名字遮盖问题
如果派生类中的成员(包括成员变量和成员函数)和基类中的成员重名,那么就会遮蔽从基类继承过来的成员。
所谓遮蔽,就是在派生类中使用该成员(包括在定义派生类时使用,也包括通过派生类对象访问该成员)时,实际上使用的是派生类新增的成员,而不是从基类继承来的
怎么解决:
//成员变量
derived_object.aa = 1000;
derived_object.Base::aa = 100;
//成员函数
derived_object.show();
derived_object.Base::show();
/*===============================================
* 文件名称:name_hidden.cpp
* 创 建 者: memories
* 创建日期:2023年06月01日
* 描 述:have a nice day
================================================*/
#include <iostream>
using namespace std;
class Base
{
public:
int aa;
int bb;
void show()
{
cout << "aa=" << aa << " bb=" << bb << endl;
}
};
class Derived:public Base
{
public:
int aa;
int cc;
void show()
{
cout << "aa=" << aa << " cc=" << cc << endl;
}
};
int main()
{
Derived d;
d.aa = 100;
d.bb = 200;
d.cc = 300;
d.show();
d.Base::aa = 400;
d.Base::show();
return 0;
}
基类和派生类的构造函数
1.4 基类和派生类的构造函数
1 类的构造函数不能被继承
2 在设计派生类时,对继承过来的成员变量的初始化工作也要由派生类的构造函数完成,但是大部分基类都有 private 属性的成员变量,
它们在派生类中无法访问,更不能使用派生类的构造函数来初始化。
解决这个问题的思路是:在派生类的构造函数中调用基类的构造函数
3 构造函数的调用顺序
构造函数的调用顺序是按照继承的层次自顶向下、从基类再到派生类的
基类和派生类的析构函数
1.5 基类和派生类的析构函数
1 构造函数类似,析构函数也不能被继承。
2 与构造函数不同的是,在派生类的析构函数中不用显式地调用基类的析构函数,因为每个类只有一个析构函数,编译器知道如何选择,无需程序员干涉。
3 另外析构函数的执行顺序和构造函数的执行顺序也刚好相反:
a 创建派生类对象时,构造函数的执行顺序和继承顺序相同,即先执行基类构造函数,再执行派生类构造函数。
b 而销毁派生类对象时,析构函数的执行顺序和继承顺序相反,即先执行派生类析构函数,再执行基类析构函数。
多继承
1.6 多继承
class D: public A, private B, protected C{
//类D新增加的成员
}
1 多继承下的构造函数:
基类构造函数的调用顺序和和它们在派生类构造函数中出现的顺序无关,而是和声明派生类时基类出现的顺序相同。
仍然以上面的 A、B、C、D 类为例,即使将 D 类构造函数写作下面的形式:
D(形参列表): B(实参列表), C(实参列表), A(实参列表){
//其他操作
}
那么也是先调用 A 类的构造函数,再调用 B 类构造函数,最后调用 C 类构造函数。
2 命名冲突:
当两个或多个基类中有同名的成员时,如果直接访问该成员,就会产生命名冲突,编译器不知道使用哪个基类的成员。
这个时候需要在成员名字前面加上类名和域解析符::,以显式地指明到底使用哪个类的成员
在多层级的类的继承关系,A<--B<--C 派生类只负责它的直接基类的构造,不能够去做非直接基类的构造
/*===============================================
* 文件名称:multiple_inherit.cpp
* 创 建 者: memories
* 创建日期:2023年06月01日
* 描 述:have a nice day
================================================*/
#include <iostream>
using namespace std;
class Base1
{
public:
Base1(int a):b1(a)
{
cout << "Base1::Base1()" << endl;
}
~Base1()
{
cout << "Base1::~Base1()" << endl;
}
int b1;
void show()
{
cout << "b1=" << b1 << endl;
}
};
class Base2
{
public:
Base2(int a):b2(a)
{
cout << "Base2::Base2()" << endl;
}
~Base2()
{
cout << "Base2::~Base2()" << endl;
}
int b2;
void show()
{
cout << "b2=" << b2 << endl;
}
};
class Base3
{
public:
Base3(int a):b3(a)
{
cout << "Base3::Base3()" << endl;
}
~Base3()
{
cout << "Base3::~Base3()" << endl;
}
int b3;
void show()
{
cout << "b3=" << b3 << endl;
}
};
///继承///
class Derived: public Base1,protected Base2,private Base3
{
public:
Derived(int a,int b,int c,int d):d1(d),Base1(a),Base2(b),Base3(c)
{
cout << "Derived::Derived()" << endl;
}
~Derived()
{
cout << "Derived::~Derived" << endl;
}
int d1;
void show()
{
Base2::show();
Base3::show();
cout << "d1=" << d1 << endl;
}
};
int main()
{
// Derived d;
// d.d1 = 100;
// d.show();
Derived d1(1,2,3,4);
d1.Base1::show();
d1.show();
return 0;
}
/*===============================================
* 文件名称:multiple_inherit3.cpp
* 创 建 者: memories
* 创建日期:2023年06月01日
* 描 述:have a nice day
================================================*/
#include <iostream>
using namespace std;
class A
{
public:
A(int aa):a(aa)
{
cout << "A::A(int aa)" << endl;
}
~A()
{
cout << "A::~A()" << endl;
}
void show()
{
cout << "a=" << a << endl;
}
protected:
// private:
int a;
};
class B:public A
{
public:
B(int bb,int aa):b(bb),A(aa)
{
cout << "B::B(int bb,int aa)" << endl;
}
~B()
{
cout << "B::~B()" << endl;
}
void show()
{
//A::show();
cout << "b=" << b << endl;
}
protected:
// private:
int b;
};
class C:public B
{
public:
C(int cc,int bb,int aa):c(cc),B(bb,aa)
{
cout << "C::C(int cc,int bb,int aa)" << endl;
}
~C()
{
cout << "C::~C()" << endl;
}
void show()
{
// A::show();
// B::show();
cout << "a=" << a << endl;
cout << "b=" << b << endl;
cout << "c=" << c << endl;
}
private:
int c;
};
int main()
{
C c1(1,2,3);
c1.show();
return 0;
}
虚继承和虚基类
1.7 虚继承和虚基类
class B: virtual public A{
//类B新增加的成员
}
1 类 A 派生出类 B1 和类 B2,类 C 继承自类 B1 和类 B2,这个时候类 A 中的成员变量和成员函数继承到类 C 中变成了两份,
一份来自 A-->B1-->C 这条路径,另一份来自 A-->B2-->C 这条路径 (菱形继承)
因为保留多份成员变量不仅占用较多的存储空间,还容易产生命名冲突
为了解决多继承时的命名冲突和冗余数据问题,C++ 提出了虚继承,使得在派生类中只保留一份间接基类的成员
2 虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类(Virtual Base Class),
A就是一个虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员
3 在虚继承中,虚基类是由最终的派生类初始化的,换句话说,最终派生类的构造函数必须要调用虚基类的构造函数。
对最终的派生类来说,虚基类是间接基类,而不是直接基类。这跟普通继承不同,在普通继承中,派生类构造函数中只能调用直接基类的构造函数,不能调用间接基类的
/*===============================================
* 文件名称:diamond_inherit.cpp
* 创 建 者: memories
* 创建日期:2023年06月01日
* 描 述:have a nice day
================================================*/
#include <iostream>
using namespace std;
class A
{
public:
A(int aa):a(aa)
{
cout << "A::A()" << endl;
}
~A()
{
cout << "A::~A()" << endl;
}
void show()
{
cout << "a=" << a << endl;
}
protected:
int a;
};
class B1:virtual public A
{
public:
B1(int bb1,int aa):b1(bb1),A(aa)
{
cout << "B1::B1()" << endl;
}
~B1()
{
cout << "B1::~B1()" << endl;
}
void show()
{
cout << "b1=" << b1 << endl;
}
protected:
int b1;
};
class B2: virtual public A
{
public:
B2(int bb2,int aa):b2(bb2),A(aa)
{
cout << "B2::B2()" << endl;
}
~B2()
{
cout << "B2::~B2()" << endl;
}
void show()
{
cout << "b2=" << b2 << endl;
}
protected:
int b2;
};
class C: public B1,public B2
{
public:
C(int cc,int bb2,int bb1,int aa):c(cc),B2(bb2,-1),B1(bb1,-2),A(aa)//普通继承和虚继承的区别
{
cout << "C::C()" << endl;
}
~C()
{
cout << "C::~C()" << endl;
}
void show()
{
cout << "c=" << c << endl;
cout << "b2=" << b2 << endl;
cout << "b1=" << b1 << endl;
cout << "a=" << a << endl;
}
protected:
int c;
};
int main()
{
C c1(4,3,2,1);
c1.show();
return 0;
}
向上转型
1.8 向上转型
1 类其实也是一种数据类型,也可以发生数据类型转换,不过这种转换只有在基类和派生类之间才有意义,
并且只能将派生类赋值给基类,包括将派生类对象赋值给基类对象、将派生类指针赋值给基类指针、将派生类引用赋值给基类引用,这在 C++ 中称为向上转型(Upcasting)。
相应地,将基类赋值给派生类称为向下转型(Downcasting)
2 派生类对象赋值给基类对象
派生类对象的数据已经赋给了基类对象,基类调用的成员函数还是基类的成员函数
3 派生类指针赋值给基类指针
基类的指针指向了派生类的对象的首地址,但是基类指针调用成员函数还是基类的成员函数
4 将派生类对象赋值给基类引用
数据用的是派生的数据,当时调用的基类的成员函数
结论: 编译器虽然通过指针的指向来访问成员变量,但是却不通过指针的指向来访问成员函数:编译器通过指针的类型来访问成员函数
/*===============================================
* 文件名称:upcast.cpp
* 创 建 者: memories
* 创建日期:2023年06月01日
* 描 述:have a nice day
================================================*/
#include <iostream>
using namespace std;
//向上转型
class A
{
public:
/*
A(int aa):a(aa)
{
cout << "A::A()" << endl;
}
~A()
{
cout << "A::~A()" << endl;
}
*/
void show()
{
cout << "A.a1=" << a1 << endl;
}
int a1;
};
class B:public A
{
public:
void show()
{
cout << "B.b1=" << b1 << endl;
cout << "A.a1=" << a1 << endl;
}
int b1;
};
class C:public B
{
public:
void show()
{
cout << "C.c1=" << c1 << endl;
cout << "B.b1=" << b1 << endl;
cout << "A.a1=" << a1 << endl;
}
int c1;
};
void debug()
{
cout << "---------------------" << endl;
}
int main()
{
debug();
A a;
a.a1 = 1;
B b;
b.a1 = 10;
b.b1 = 20;
C c;
c.a1 = 100;
c.b1 = 200;
c.c1 = 300;
a.show();
b.show();
c.show();
debug();
a = b;//向上转型
a.show();
b.show();
debug();
a = c;
a.show();
c.show();
// c = b;向下转型是错误的
return 0;
}
继承和组合
1.9 继承和组合
继承 is-a 具有电话功能的车还是可以看成是一个电话
组合 has-a 我的车拥有了一部电话,车就有了电话的功能
/*===============================================
* 文件名称:Car_with_phone.cpp
* 创 建 者: memories
* 创建日期:2023年06月01日
* 描 述:have a nice day
================================================*/
#include <iostream>
#include <string>
using namespace std;
class phone
{
public:
void call(string name)
{
cout << "calling " << name << endl;
}
};
class Car:public phone
{
public:
void drive()
{
cout << ownername << "is driving" << endl;
}
string ownername;
};
int main()
{
Car car;
car.ownername = "deng";
car.call("deng");
return 0;
}
/*===============================================
* 文件名称:Car_has_phone.cpp
* 创 建 者: memories
* 创建日期:2023年06月01日
* 描 述:have a nice day
================================================*/
#include <iostream>
#include <string>
using namespace std;
class Phone
{
public:
void call(string name)
{
cout << "calling " << name << endl;
}
};
class Car
{
public:
void drive()
{
cout << ownername << "is driving" << endl;
}
void buyPhone(Phone *p)
{
phone = p;
}
void call(string name)
{
if(phone != NULL)
phone->call(name);
}
string ownername;
Phone *phone;
};
int main()
{
Car car;
Phone phone;
car.ownername = "dengshilin";
car.buyPhone(&phone);
car.drive();
car.call("xiaoming");
return 0;
}
鸟作为基类-----人派生-----飞
飞行器+人--------飞