这里也是C++中扩展和修改类最最有用的方法——类的继承或者派生
派生类的基本操作
首先我们应该创建一个基类
这里我们创建Fruit_shop类,里面的私有成员包括该家店的位置,水果的种类和总的营业额,然后我们由它派生一个Fruit_branch_shop类,代表这是一家分店,然后我们这里添加一个特有的数据:分店的员工数量
创建一个基类
首先我们创建一家水果店:
# include <iostream>
# include <cstring>
using namespace std;
class Fruit_shop
{
private:
char location[100];
int fruit_types;
int total_turnover;
public:
Fruit_shop(const char*fn="nowhere", const int ft=0, const int tt=0);
~Fruit_shop();
void show();
};
Fruit_shop::Fruit_shop(const char*fn, const int ft, const int tt)
{
strncpy(location, fn, 99);
location[99]='\0';
fruit_types=ft;
total_turnover=tt;
}
void Fruit_shop::show()
{
cout << "The basic information:\n";
cout << "Location : " << location << "\n";
cout << "Fruit types : "<<fruit_types<<"\n";
cout << "Total turnover : "<<total_turnover<<"\n";
}
Fruit_shop::~Fruit_shop()
{
}
int main()
{
Fruit_shop Star("Beijing", 10, 200000);
Star.show();
return 0;
}
输出结果:
The basic information:
Location : Beijing
Fruit types : 10
Total turnover : 200000
这里我们只是简单的显示该店的基本信息,之后我们会创建一个由它派生的分店
创建一个派生类
我们可以这么声明:
class Fruit_branch_shop_:public Fruit_shop
{
};
这样我们完成了两件事:
- 使分店存储了总店的数据信息
- 分店可以使用总店提供的方法
那么我们需要给派生类添加些什么呢?
1.需要自己的构造函数
2.根据自身的需要添加额外的数据成员和成员函数
那么我们开始完善这个派生类的构建:
首先是声明部分
class Fruit_branch_shop:public Fruit_shop
{
private:
int number; //这个是分店特有的属性
public:
Fruit_branch_shop(const int num=0, const char*fn="nowhere", const int ft=0,const int tt=0);
Fruit_branch_shop(const int num, const Fruit_shop &tp);
friend ostream&operator<<(ostream &os, const Fruit_branch_shop &t);
//这里使用友元重载了<<操作符
};
给出方法的代码
Fruit_branch_shop::Fruit_branch_shop(const int num, const char*fn, const int ft, const int tt):Fruit_shop(fn, ft, tt)
{
number = num;
}
Fruit_branch_shop::Fruit_branch_shop(const int num, const Fruit_shop&tp):Fruit_shop(tp), number(num)
{
}
ostream &operator<<(ostream &os, const Fruit_branch_shop&t)
{
os <<"Numbers : "<<t.number<<"\n";
return os;
}
这里的编写有的神奇,后面再讲解
(继承类这边有很多很多需要注意的地方)
最后是main函数的执行
int main()
{
cout<<"input the branch shop's location:\n";
char location[100];
gets(location);
cout <<"input the branch shop's fruit types, turnover, numbers:\n";
int ft,tt,n;
cin >> ft;
cin >> tt;
cin >> n;
cout << "\n";
cout <<"The main shop:\n";
Fruit_shop Star("Beijing", 10, 200000);
Star.show();
cout <<"\n";
Fruit_branch_shop Alice(n, location, ft, tt);
Fruit_branch_shop Matter(5, Star);
cout << "\nAlice:\n";
Alice.show();
cout << "\nThe number of Alice's staff: "<<Alice<<"\n";
cout << "Matter:\n";
Matter.show();
cout << "\nThe number of Matter's staff: "<<Matter<<"\n";
return 0;
}
我们可以得到以下输出结果
input the branch shop's location:
Shanghai
input the branch shop's fruit types, turnover, numbers:
12 120000 15
The main shop:
The basic information:
Location : Beijing
Fruit types : 10
Total turnover : 200000
Alice:
The basic information:
Location : Shanghai
Fruit types : 12
Total turnover : 120000
The number of Alice's staff: Numbers : 15
Matter:
The basic information:
Location : Beijing
Fruit types : 10
Total turnover : 200000
The number of Matter's staff: Numbers : 5
Perfect !
需要注意的几点(非常重要!!!)
访问权限
派生类不能直接访问基类的私有成员!!!
派生类不能直接访问基类的私有成员!!!
派生类不能直接访问基类的私有成员!!!
重要的事情说三遍
我们必须通过基类的方法进行访问
所以派生类构造函数必须使用基类构造函数
我们创建派生类之前,应该先创建基类对象
所以C++使用成员初始化列表句法来完成创建派生类对象
这里给出三种派生类的构造函数作为参考:
Fruit_branch_shop::Fruit_branch_shop(const int num, const char*fn,
const int ft, const int tt):Fruit_shop(fn, ft, tt)
// 后面的Fruit_shop(fn,ft,tt)就是成员初始化列表
{
number = num;
}
Fruit_branch_shop::Fruit_branch_shop(const int num,
const Fruit_shop&tp):Fruit_shop(tp), number(num)
{
}
Fruit_branch_shop::Fruit_branch_shop(const int num,
const Fruit_shop&tp):Fruit_shop(tp)
{
number = num;
}
对于第一个构造函数:
编译器的具体操作步骤:
先将实参赋给Fruit_shop的形参,然后传入其构造函数中,创建一个嵌套的Fruit_shop对象,将这些实参存储在该对象中,然后再调用Fruit_branch_shop的构造函数,将参数num的值赋给number
也就是子类对象内嵌套了一个基类对象来存储属于基类的数据成员
对于第二个构造函数:
依然使用的是成员初始化列表句法,在这种情况下,我们应该在列表中使用成员名
对于第三个构造函数:
这里的tp是Fruit_shop&类型的,所以调用基类的复制构造函数,由于我们没有显式的定义复制构造函数,所以系统会自动创建一个默认的复制构造函数,因为不涉及new操作符分配空间,所以这样是可以的
所以我们为派生类写构造函数时需要注意:
- 基类对象需要先被创建
- 成员初始化列表句法
- 派生类的额外数据成员需要额外初始化
派生类对象过期时,程序先调用派生类析构函数,在调用其嵌套的基类的析构函数
派生类与基类的关系
- 派生类对象可以使用基类的公有方法(public),也就是说不能使用其私有方法
- 基类指针可以直接指向派生类对象;基类引用也可以直接引用派生类对象
- 基类指针或者引用只能用于调用基类方法,不能调用派生类的方法
- 不能将基类对象或者地址赋给派生类引用或者指针
继承方式
C++提供三种继承方式:
- 公有继承
- 保护继承
- 私有继承
公有继承的多态
我们希望同一个方法在基类和派生类中的行为是不同的,这种复杂的行为就称为多态,也就是说一个方法的行为将随上下文的环境不同而不同我们一般有两种方式来实现多态公有继承:
两种多态定义方法
1. 派生类中重新定义基类的方法
2. 虚方法(virtual关键字)
重点:
两种方法的区别:
如果我们在派生类写的方法不是虚方法,那么程序将根据引用类型或指针的类型选择方法;
如果是虚方法,程序根据引用或指针指向的对象的类型来选择方法
有的绕,所以我们还是写一个小程序来理解一下
这里我们还是以上面的水果店为例,我们将show()方法声明为virtual,然后添加一个add函数,不声明为virtual。
然后我们在基类和派生类分别都创建这两种方法(多态),然后我们在main函数中使用引用来说明加virtual和不加virtual的区别
先上代码:(有些函数的具体代码这里没有贴上面有给出)
类声明:
class Fruit_shop
{
private:
char location[100];
int fruit_types;
int total_turnover;
public:
Fruit_shop(const char*fn="nowhere", const int ft=0, const int tt=0);
virtual ~Fruit_shop(){} //这里使用了虚析构函数,后面再讲为什么
virtual void show(); //虚函数
void add(const int ft=0); //非虚函数
};
class Fruit_branch_shop:public Fruit_shop
{
private:
int number; //这个使特有的属性
public:
Fruit_branch_shop(const int num=0, const char*fn="nowhere", const int ft=0,const int tt=0);
Fruit_branch_shop(const int num, const Fruit_shop &tp);
friend ostream&operator<<(ostream &os, const Fruit_branch_shop &t);
virtual void show(); //派生类中的show()
void add(const int ft=0,const int num=0); //派生类中的add函数
};
给出新的show函数和add函数代码:
void Fruit_shop::show() //这个没有改动
{
cout << "The basic information:\n";
cout << "Location : " << location << "\n";
cout << "Fruit types : "<<fruit_types<<"\n";
cout << "Total turnover : "<<total_turnover;
}
void Fruit_shop::add(const int ft)
{
fruit_types += ft; //我们更新了水果的种类
}
//省略了其他函数……
void Fruit_branch_shop::show()
{
//给分店添加的信息展示方法
Fruit_shop::show(); //这里我们调用基类方法来展示基本信息
cout << "\nthe number of staff : "<<number<<"\n";//添加新信息展示
}
void Fruit_branch_shop::add(const int ft, const int num)
{
Fruit_shop::add(ft);
//同样调用基类添加函数(因为派生类函数无法访问基类的私有成员)
number += num; // 更新员工数量
}
main函数:
int main()
{
//这里创建三个对象
Fruit_shop Star("Beijing", 10, 200000);
Fruit_branch_shop Alice(5, "Lanzhou",8, 120000);
Fruit_branch_shop Matter(7, Star);
Fruit_shop & f1=Star;
Fruit_shop & f2=Alice;
Fruit_branch_shop & f3=Matter;
f1.show();
cout << "\n\n";
f2.show();
f1.add(2);
f2.add(5);
cout << "\n";
f1.show();
cout << "\n\n";
f2.show();
f3.add(2, 5);
cout << "\n";
f3.show();
return 0;
}
输出结果:
The basic information:
Location : Beijing
Fruit types : 10
Total turnover : 200000
The basic information:
Location : Lanzhou
Fruit types : 8
Total turnover : 120000
the number of staff : 5
The basic information:
Location : Beijing
Fruit types : 12
Total turnover : 200000
The basic information:
Location : Lanzhou
Fruit types : 13
Total turnover : 120000
the number of staff : 5
The basic information:
Location : Beijing
Fruit types : 12
Total turnover : 200000
the number of staff : 12
我们分开来看main函数中的几行代码的作用
Fruit_shop & f1=Star;
Fruit_shop & f2=Alice;
Fruit_branch_shop & f3=Matter;
这三行创建三个引用对象f1 f2 f3
f1.show();
cout << "\n\n";
f2.show();
这里我们从输出内容可以看到,它分别输出了两个指针指向的对象的信息,也就是说
编译器识别virtual show()方法的对象是那个类是基于引用所指的对象的类型
f1.add(2);
f2.add(5);
f3.add(2, 5);
我们知道add()方法不是虚方法(virtual),所以即使f2指向的是Fruit_branch_shop 对象,它还是只能调用基类的add方法——
因为这时编译器识别add方法是属于哪个类的方法是根据引用的类型而定的
所以f1 f2 都只能调用基类的add方法,而f3能够调用派生类的add方法
虚拟析构函数
如果析构函数非虚拟,那么我们用一个基类的指针指向派生类时,在程序结束时,由于指针是基类,所以只有基类的析构函数被调用,即使它指向的对象是派生类
使用虚拟析构函数,则会调用相应对象类型的析构函数,所以一个基类指针指向派生类时,会先调用派生类析构函数,再调用基类析构函数,保证了正确的析构函数序列被调用
特别是当类中使用了new/delete时,虚拟析构函数的正确使用就变得非常重要
类继承的东西很复杂繁琐,所以大家一定要多动手,多实践哟!
看到这里了顺手点个赞呗