我们在使用类库进行开发时候,如果需要对类库进行修改和扩展,我们就需要在类库的源代码中修改他(如果他是公开的源代码),但是C 提供了更为简单和易操作的方法,叫做类继承,它可以从已有的类派生新的类,而派生类继承了原有类(称为基类) 的特征,包括方法。
可以在已有类的基础上添加功能
可以给类添加数据
可以修改类方法的行为 派生可以通过复制原始类代码 并对其进行修改来完成上述工作,但继承机制只需提供新特性,甚至不需要访问代码就可以派生出类,而且可以在不公开实现的情况下将自己的类分发给其他人,同时允许他们在类中添加新特性
基类
从一个类派生出另一个类时,原始类称为基类,继承类称为派生类,我们现在需要创建一个基类来说明这些情况
代码语言:javascript复制#pragma once
#include <string>
#include<iostream>
class Student
{
std::string name;
int Total_source;
char sex;
int age;
public:
static int number;
Student(std::string x = "none", int n = 0, char s = 'n', int a = 0) :name(x)
, Total_source(n), sex(s), age(a)
{
number ;
}
void reset(const std::string& t);
void show()const;//展示函数
};
以及包括对这个类声明的实现
代码语言:javascript复制#include "Student.h"
void Student::reset(const std::string& t)
{
name = t;
}
void Student::show() const
{
using std::cout;
using std::endl;
cout << "name = " << this->name << endl;
cout << "sex = " << this->sex << endl;
cout << "age = " << this->age << endl;
cout << "Total_source = " << this->Total_source << endl;
}
我们这个学生只是广义上的学生,学生还可以分为大学生 中学生和小学生,这时候我们就可以派生基类Student类
代码语言:javascript复制#include "Student.h"
class CXiaoStudent : public CStudent
{
public:
int yuwen_score;
int shuxue_score;
int english_score;
};
class CZhongStudent : public CXiaoStudent
{
public:
int wuli_score;
int huaxue_score;
};
冒号指出xiaostudent zhongstudent类的基类是Student类 public表示这是公有派生,基类的公有成员将称为派生类的公有成员,基类的私有部分也将称为派生类的一部分,但只能通过基类的公有和保护方法访问 现在我们派生出来的两个类已经具有
派生类对象存储了基类的数据成员
派生类对象可以使用基类的方法
因此,CZhongStudent CXiaoStudent对象可以存储学生的姓名 年龄 性别 以及成绩总和 另外还可以使用基类的void Student::reset(const std::string& t)
void Student::show() const
如果要访问而不经过基类的成员函数 会出现以下错误
那么我们在派生类中应该添加什么呢
自己的构造函数
可以添加额外的数据成员和成员函数 比如我们这个派生类中就有单科成绩的数据成员 现在我们为他加上构造函数和成员函数 派生类构造函数不能直接访问基类的数据,那要怎么设置基类的数据呢,那就是调用基类的方法,也就是说我们要写派生类的构造函数必须使用基类构造函数 需要明确的一点是创建派生类对象时,首先会创建基类对象,C 将会使用初始化列表来写派生类的构造函数
代码语言:javascript复制CXiaoStudent::CXiaoStudent(int yuwen,int shuxue,int yingyu,const std::string&n,int source,char s,int a):Student(n,source,s,a)
{
yuwen = yuwen_score;
shuxue = shuxue_score;
yingyu = english_score;
}
其中Student(n,source,s,a)
是成员初始化列表,调用Student构造函数
我们现在创建一个派生类对象并且初始化他 xiaostudent x1(20,30,40,"雷泽",15,'m',60);
CXiaoStudent构造函数把实参赋给形参 然后将这些参数传递给Student的构造函数,同时创建一个Student对象并嵌套在xiaostudent对象中,完成对xiaostudent的对象创建
当然基类的构造函数也是可以不用显式调用的,则程序会调用默认构造函数 ,也就是说除非使用默认构造函数否则需要调用正确的基类构造函数 当然也可以用以下的方式来传递基类成员的数据
代码语言:javascript复制CXiaoStudent:CXiaoStudent(int yuwen,int shuxue,int yingyu,const Student&st):Student(st);
上述例子调用了基类的拷贝构造函数,关于什么时候调用拷贝构造函数,我们在前面的文章已经详细地介绍过。 总结以下要点
首先会创建基类对象
派生类构造函数应通过成员初始化列表将基类信息传递给基类构造函数
派生类构造函数应初始化派生类新增的数据成员 释放对象的顺序与创建对象的顺序相反,首先执行派生类的析构函数 然后自动滴哦用基类的析构函数
使用派生类
我们现在可以将头文件和定义整理以下,既然都是学生相关的,我们可以把它们放到一起
代码语言:javascript复制#pragma once
#include <string>
#include<iostream>
class Student
{
std::string name;
int Total_source;
char sex;
int age;
public:
static int number;
Student(std::string x = "none", int Total = 0,char s = 'n', int a = 0) :name(x)
,sex(s), age(a), Total_source(Total)
{
number ;
}
void reset(const std::string& t);
void show()const;//展示函数
void Name()const;
int columntotal(int yuwen, int shuxue, int yingyu) ;
};
class xiaostudent :public Student
{
int yuwen_score;
int shuxue_score;
int english_score;
public:
xiaostudent(int yuwen = 0, int shuxue = 0, int yingyu = 0,
const std::string& n = "none", char s = 'w', int a = 0);
xiaostudent(int yuwen, int shuxue, int yingyu,
const Student& st);
void showsource();
int retshuxue() { return shuxue_score; }
int retyuwen() { return yuwen_score; }
int retyingyu() { return english_score; }
void resetsource(int yuwen,int shuxue,int yingyu);
};
因为只是做笔记,所以我派生了一个小学生的类。然后在Student.cpp中实现了声明的定义
代码语言:javascript复制#include "Student.h"
void Student::reset(const std::string& t)
{
name = t;
}
void Student::show() const
{
using std::cout;
using std::endl;
cout << "sex = " << this->sex << endl;
cout << "age = " << this->age << endl;
}
void Student::Name() const
{
std::cout << this->name << std::endl;
}
int Student::columntotal(int yuwen,int shuxue,int yingyu)
{
Total_source = yuwen shuxue yingyu;
return Total_source;
}
//Cxiao
xiaostudent::xiaostudent(int yuwen, int shuxue, int yingyu,
const std::string& n, char s, int a):Student(n,s,a)
{
yuwen_score = yuwen;
shuxue_score = shuxue;
english_score = yingyu;
}
xiaostudent::xiaostudent(int yuwen, int shuxue, int yingyu, const Student& st)
:Student(st),yuwen_score(yuwen),shuxue_score(shuxue),english_score(yingyu)
{
}
void xiaostudent::showsource()
{
using std::cout;
using std::endl;
cout << "Chinese :" << yuwen_score << endl;
cout << "Math :" << shuxue_score << endl;
cout << "English :" << english_score << endl;
int total = columntotal(yuwen_score, shuxue_score, english_score);
cout << "Totoal_Source: " << total << endl;
}
void xiaostudent::resetsource(int yuwen, int shuxue, int yingyu)
{
this->yuwen_score = yuwen;
this->shuxue_score = shuxue;
this->english_score = yingyu;
}
reset
是基类重新设置名字的函数
show
是基类输出数据成员的函数
Name
会输出数据成员name
columntotal
因为要访问基类的数据必须要用基类的成员函数访问,这个函数是将派生类的三科成绩相加赋值给基类的Totol_source数据成员
//Cxiao
xiaostudent
我们知道派生类需要初始化自身和基类的数据,这里我显式调用了基类的构造函数Student
进行初始化 对应参数赋值 在创建完成基类之后 我们需要初始化子类的数据成员 这也在小学生派生类的构造函数给出
xiaostudent
也是一个构造函数,但他不通过初始化列表赋值,他调用了基类的拷贝构造函数,将我们传递进来的基类对象赋值给 我们派生类内嵌套的基类对象
如果我们省略初始化列表 则会自动调用默认的基类构造函数
showsource
派生类的成员函数,用来展示派生类的三科成绩以及通过函数columntotal访问到了基类的数据成员Total_source 并且输出了他
resetsource
提供给派生类修改三科成绩的接口
现在我们来调用看看效果
#include"Student.h"
int Student::number = 0;
int main()
{
using std::cout;
using std::endl;
Student S1("Marry",'m',60);//基类
xiaostudent x1(20, 30, 40, "lucy",'w', 15);//派生类
xiaostudent x2(60, 60, 60, S1);
cout << "Student information" <<endl;
x1.Name();
x1.show();//派生类的成员函数
cout << "Report Card" << endl;
x1.showsource();//基类的成员函数
cout << "Student information"<<endl;
x2.Name();
x2.show();//派生类的成员函数
cout << "Report Card" << endl;
x2.showsource();//基类的成员函数
}
派生类与基类的特殊关系
派生类对象可以使用基类的方法,条件是基类的方法不是私有的
基类指针可以在不进行显式转换的情况下指向派生类对象,基类引用可以在不进行显式类型转换的情况下引用派生类对象
代码语言:javascript复制 Student st1("mike", 'm', 30);
xiaostudent xst1(60, 60, 60, "kitty", 'w', 20);
Student* pst = &xst1;
Student& rst = xst1;
pst->show();
rst.show();
但是基类指针,或者引用只能调用基类的方法,因此不能使用st或者pst来调用派生类的方法,不可以将基类对象和地址赋值给派生类引用和指针。 我们可以根据这个特性可以总结以下几个使用方法 比如举个例子
代码语言:javascript复制void show(const Student&st)
{
using std::cout;
cout<<"name: ";
st.Name;
}
形参st是一个基类引用,结合我们说的基类和派生类可以互相转换,因此它可以指向基类或者派生类对象
代码语言:javascript复制 Student st1("mike", 'm', 30);
xiaostudent xst1(60, 60, 60, "kitty", 'w', 20);
st1.showt(st1);
st1.showt(xst1);
指针也是如此 ; 我们再看一种情况:引用兼容性属性可以将基类对象初始化为派生类对象
代码语言:javascript复制xiaostudent xst(60,60,60,"Kitty",'w',20);
Student st(xst);
熟悉的朋友应该想到了我们前面的拷贝构造函数 尽管我们没有定义这样的构造函数 但是会有隐式拷贝构造函数Student(const Student& st)
形参是基类,但是可以引用派生类 这样把基类初始化为派生类时,会使用基类的构造函数将基类对象初始化为嵌套在派生类中的基类对象.俗称大材小用 同样也可以将派生对象赋值给基类对象
xiaostudent xst(60,60,60,"kitty",'w',20); Student st; st = xst; 这种情况下 程序会隐式重载赋值运算符 Student & operator=(const Student &) const; 基类引用指向的也是派生类对象,因此xst的基类部分将被赋值给st。(由子类对象给父类对象赋值是可以的,俗称大材小用。在赋值的时候会舍弃子类的新增成员)
父子类对象转换的实际用途
我们来写一个统计全县初中生 高中生和小学生平均年龄的例子 思路 我们前面介绍到基类的指针和引用可以指向派生类,然后我们就可以根据传入对象的type判断这是一个什么对象,然后用转换的指针访问嵌套在派生类中的基类age数据 就可以求得总和 切记可以大转小,不能小转大,因为基类是没有派生类数据的,这种情况是单向的。 Student.h
代码语言:javascript复制#pragma once
#include<iostream>
class Student
{
public:
int age;
unsigned int type;
Student(int agec = 0, int typec = 0) :age(agec), type(typec)
{
}
};
class xiaostudent :public Student
{
public:
xiaostudent(int age = 0, int type = 1) :Student(age, type)
{
}
};
class chuxtudent :public Student
{
public:
chuxtudent(int age = 0, int type = 2) :Student(age, type)
{
}
};
class Bigxtudent :public Student
{
public:
Bigxtudent(int age = 0, int type = 3) :Student(age, type)
{
}
};
main.cpp
代码语言:javascript复制#include"Student.h"
void Average(Student* st, int n_size);
int main()
{
chuxtudent cst[10];
xiaostudent xst[10];
Bigxtudent bst[10];
for (size_t i = 0; i < 10; i )
{
cst[i].age = 12;
xst[i].age = 7;
bst[i].age = 18;
}
Average(cst, 10);
Average(xst, 10);
Average(bst, 10);
}
void Average(Student* st, int n_size)
{
if (!st||n_size<=0)
{
return;
}
int Total_age = 0;
int type = st[0].type;
for (size_t i = 0; i < n_size; i )
{
switch (type)
{
case 1:
Total_age = ((xiaostudent*)st)[i].age;
break;
case 2:
Total_age = ((chuxtudent*)st)[i].age;
break;
case 3:
Total_age = ((Bigxtudent*)st)[i].age;
break;
default:
break;
}
}
int aver_age = Total_age / n_size;
switch (type)
{
case 1:
std::cout << "小学生的平均年龄是:" << aver_age << std::endl;
break;
case 2:
std::cout << "中学生的平均年龄是:" << aver_age << std::endl;
break;
case 3:
std::cout << "大学生的平均年龄是:" << aver_age << std::endl;
break;
default:
break;
}
}