1.继承和派生 public:private:protected
看了那么多图片,有点乱吧?简单来说就为了提高类的可重用性,具有相同属性,相同功能的函数不用再重新写到新的一个类里面,只需使用继承与派生, 基类也称父类,派生类也称子类。
看第一张图,person里的姓名性别年龄籍贯这些和新的类student的部分属性重复了,不需要在student中重复写。把person作为基类,student继承积累的属性。 student类也同样获得person的属性。
而在继承和派生过程中同时出现了3种方式,分别是 public:private:protected
我们首先来看看他们的定义
(非常重要,看不懂时重复理解)
public:可以被该类中的函数、子类的函数、友元函数访问,也可以由该类的对象访问;
protected:可以被该类中的函数、子类的函数、友元函数访问,但不可以由该类的对象访问;
private:可以被该类中的函数、友元函数访问,但不可以由子类的函数、该类的对象、访问。
以上这张图片大致是本节内容的精髓
记住:基类中的私有成员不允许派生
比方说有一个基类(父类)A,子类B,B是A的private类,则A中所有的private:类内的数据成员和成员函数都不能被直接访问(不允许继承)。
在C++类中,我们通常使用 指针 .(点的方式) 成员函数 这三种方式直接访问数据成员和成员函数
比如
#include<iostream>
#include<cstring>
using namespace std;
struct A
{
string name;
string getname()
{
return name;
}
};
int main()
{
A a,c;
A *b;//b是一个 自定义类形的指针
b=new A; //开辟一个新的A类空间,并使指针b指向这个新开辟的A类的空间
string name_1,name_2,name_3;
cin>>name_1;
a.name=name_1;//1.通过 点 的方式(访问)给对象的名字属性赋值
cin>>name_2;
b->name=name_2;//2.通过 指针 的方式(访问)给对象的名字属性赋值
cin>>name_3;
c.name=name_3;
// 以下是输出
cout<<a.name<<endl;//1.通过 点 的方式(访问)显示对象的属性--名字
cout<<b->name<<endl;//2.通过 指针 的方式(访问)显示对象的属性--名字
cout<<c.getname()<<endl;//3.先通过点的方式访问从而间接利用 成员函数 的方式(访问)显示对象的属性--名字
}
我们暂且不讨论指针在继承中的使用,访问通过 1 对象+点.的方式 和 2 通过 对象+点.访问子类的成员函数从而间接访问基类的数据成员或者成员函数
4.14那个图我教大家我自己的一个记忆方法:
1.private的继承方式 :基类(包括所有继承方式)中的private都是不允许访问(继承), 而在private的继承方式中其他的(public和protected)都变成private 。
2.public的继承方式 :尊重原则,除了privated原来是怎么样就怎么样,不改变。public->public. protected->protected
3.protected的继承方式:除了privated,强行保护,public->protected . protected->protected
然后结合定义自己理解一下
(非常重要,看不懂时重复理解)
public:可以被该类中的函数、子类的函数、友元函数访问,也可以由该类的对象访问;
protected:可以被该类中的函数、子类的函数、友元函数访问,但不可以由该类的对象访问;
private:可以被该类中的函数、友元函数访问,但不可以由子类的函数、该类的对象、访问。
举个例子
第一个报错 不能显示出x,因为x是私有成员,不可以被继承,第二个报错是因为y是基类中是protected成员,通过public继承到子类仍然为protected类,但是protected类不可以由该类的对象访问;
如图第一个报错改成Showxy( )后,通过子类成员函数去访问基类的成员函数,这是允许的。
第二个报错若想修改,可以通过对 子类增加一个给Y赋值的成员函数,对obj.成员函数 调用重而实现赋值。
2.派生类的构造函数
这个总参数表包含了给基类构造函数传参用的,也包含了派生类中新增的成员的传参
我们来看下面这个例子,把3个参传给基类,剩下1个参留给自己
3.类的组合
继承练习1
①声明并实现一个名为Person的基类,Person类有保护数据成员name(姓名,string类型)、sex(性别,char类型,'M'表示男,'F'表示女)。以及有参构造函数,将姓名、性别设置为给定的参数;成员函数print,输出姓名和性别。
②从Person类派生出Student类,Student类有私有数据成员status(状态,string类型),表示年级(FRESHMAN、SOPHOMORE、JUNIOR、SENIOR),表示大一、大二、大三、大四学生。以及有参构造函数,将姓名、性别、状态设置为给定的参数;成员函数print,print函数输出姓名、性别和状态。
③定义MyDate类,它包含私有数据成员year、month和day以及带默认参数的有参构造函数,年、月、日的默认参数值分别为1900、1、1;成员函数print,输出年、月、日。
④从Person类派生出Employee类,Employee类有保护数据成员salary(薪水,int类型)、dateHired(雇佣日期),dataHired的类型是MyDate。以及有参构造函数,将姓名、性别、薪水和雇佣日期设置为给定的参数;成员函数print,输出姓名、性别、薪水和雇佣日期。
⑤从Employee类派生出Faculty类,Faculty类有私有数据成员rank(级别,string类型),有(PROFESSOR、ASSOCIATE_PROFESSOR、LECTURER),表示教授、副教授、讲师。以及有参构造函数,将姓名、性别、薪水、雇佣日期和级别设置为给定的参数;成员函数print,输出姓名、性别、薪水、雇佣日期和级别。
⑥从Employee类派生出Staff类,Staff类有私有数据成员headship(职务,string类型),有(PRESIDENT、DEAN、DEPARTMENT_CHAIRMAN),表示校长、院长、系主任。以及有参构造函数,将姓名、性别、薪水、雇佣日期和职务设置为给定的参数;成员函数print,输出姓名、性别、薪水、雇佣日期和职务。
输入输出示例
逐个输入:
Person对象的构造信息
Student的构造信息
Data的构造信息
Employee的构造信息
Faculty的构造信息
Staff的构造信息
其中雇佣日期都使用相同的日期,所以构造一个对象即可
输入 | 输出 | |
示例 1 | | |
我们来看一道值得深度思考的题目
继承练习1
①声明并实现一个名为Person的基类,Person类有保护数据成员name(姓名,string类型)、sex(性别,char类型,'M'表示男,'F'表示女)。以及有参构造函数,将姓名、性别设置为给定的参数;成员函数print,输出姓名和性别。
②从Person类派生出Student类,Student类有私有数据成员status(状态,string类型),表示年级(FRESHMAN、SOPHOMORE、JUNIOR、SENIOR),表示大一、大二、大三、大四学生。以及有参构造函数,将姓名、性别、状态设置为给定的参数;成员函数print,print函数输出姓名、性别和状态。
③定义MyDate类,它包含私有数据成员year、month和day以及带默认参数的有参构造函数,年、月、日的默认参数值分别为1900、1、1;成员函数print,输出年、月、日。
④从Person类派生出Employee类,Employee类有保护数据成员salary(薪水,int类型)、dateHired(雇佣日期),dataHired的类型是MyDate。以及有参构造函数,将姓名、性别、薪水和雇佣日期设置为给定的参数;成员函数print,输出姓名、性别、薪水和雇佣日期。
⑤从Employee类派生出Faculty类,Faculty类有私有数据成员rank(级别,string类型),有(PROFESSOR、ASSOCIATE_PROFESSOR、LECTURER),表示教授、副教授、讲师。以及有参构造函数,将姓名、性别、薪水、雇佣日期和级别设置为给定的参数;成员函数print,输出姓名、性别、薪水、雇佣日期和级别。
⑥从Employee类派生出Staff类,Staff类有私有数据成员headship(职务,string类型),有(PRESIDENT、DEAN、DEPARTMENT_CHAIRMAN),表示校长、院长、系主任。以及有参构造函数,将姓名、性别、薪水、雇佣日期和职务设置为给定的参数;成员函数print,输出姓名、性别、薪水、雇佣日期和职务。
输入输出示例
逐个输入:
Person对象的构造信息
Student的构造信息
Data的构造信息
Employee的构造信息
Faculty的构造信息
Staff的构造信息
其中雇佣日期都使用相同的日期,所以构造一个对象即可
输入 | 输出 | |
示例 1 | | |
#include<iostream>
using namespace std;
class Person
{
public:
void print1()
{
cout<<"Name:"<<Name<<", Sex:"<<sex<<endl;
}
Person(string Name_,char sex_)
{
Name=Name_;
sex=sex_;
}
protected:
string Name;
char sex;
};
class Student:public Person
{
public:
Student(string Name_,char sex_,string status_):Person(Name_,sex_)
{
status=status_;
}
void print2()
{
print1();
cout<<status<<endl;
}
private:
string status;
};
class MyDate
{
public:
void print3()
{
cout<<year<<"-"<<month<<"-"<<day<<endl;
}
MyDate(int year_,int month_,int day_)
{ year=year_;
month=month_;
day=day_;
}
private:
int year;
int month;
int day;
};
class Employee:public Person
{
public:
void print4()
{
print1();
cout<<"Salary:"<<salary;
cout<<", Hire date:";dateHired.print3();
}
Employee(string Name_,char sex_,int salary_,int year_,int month_,int day_):dateHired( year_, month_,day_),Person(Name_,sex_)
{
salary=salary_;
}
protected:
int salary;
MyDate dateHired;
};
class Faculty:public Employee
{
public:
Faculty(string Name_,char sex_,int salary_,string rank_,int year_,int month_,int day_):
Employee(Name_,sex_,salary_,year_,month_,day_)
{
rank=rank_;
}
void print5()
{
print4();cout<<rank<<endl;
}
private:
string rank;
string Name;
};
class Staff:public Employee
{
public:
Staff(string Name_,char sex_,int salary_,string headship_,int year_,int month_,int day_):Employee(Name_,sex_,salary_,year_,month_,day_)
{
headship=headship_;
}
void print6()
{
print4();cout<<headship<<endl;
}
private:
string headship;
};
int main()
{
string name1,name2,status2,name3,rank1,headship1,name4;
char sex1,sex2,sex3,sex4;
int year,month,day,salary1,salary2;
cin>>name1>>sex1;
Person a1(name1,sex1);
cin>>name2>>sex2>>status2;
Student a2(name2,sex2,status2);
cin>>year>>month>>day>>name3>>sex3>>salary1;
Employee a3(name3,sex3,salary1,year,month,day);
cin>>name3>>sex3>>salary1>>rank1;
Faculty a4(name3,sex3,salary1,rank1,year,month,day);
cin>>name4>>sex4>>salary2>>headship1;
Staff a5(name4,sex4,salary2,headship1,year,month,day);
a1.print1();a2.print2();a3.print4(); a4.print5(); a5.print6();
}
这道题目涉及到类的组合,派生的构造函数,继承方式等多个知识点糅合在一起,有一定难道,建议读者认真花时间思考,是一道非常不错的题目,自己读代码。
值得提的是类的组合的格式如下图
4.继承中的同名函数
在派生函数中的函数如果和基类中的函数同名,派生(子)函数中同名函数优先级更高一些,若非要表示基类中的函数则 基类名::成员名
5.在派生函数中更改访问声明(不改变基类的权限),
数据成员也可以用访问声明
6.连续继承
我们仔细看上面一张图,这是一个连续继承的图示
首先基类中的 a和 两个成员函数都被私有继承到派生类Devided1中,变成私有的,然后再连续私有继承到Devided2中,因为私有成员不可被继承,所以在Devided2中a和 基类中的两个成员函数就消失了。紧接着的是Devided1中的protected型的b和 public型 的两个成员函数都被私有继承到
Devided2中。
这便是连续继承。
7.多重继承
解决多重继承中的二义性问题
继承中派生类的构造函数,析构函数也具有先进后出,后进先出的
接下来请重点看一看这个
在声明定义类 Derived的对象 obj之前,要先调用这个对象的基类Base1,Base2,但是这两个基类它又是Base类的派生类,所以,在调用这个Base1或者Base2之前,又要调用2次他们的基类Base,因此顺序就是Base->Base1->Base->Base2->Derived
8.虚基类
因为有相同基类,在构造时会出现同名函数但却代表不同含义,我们称之为二义性,它也会影响最终结果,为了解决这个问题,我们提出了一个新的概念————虚基类,这个类能实现共同基类中相同的函数名或者属性只会出现1次 因此顺序就是Base->Base1->Base2->Derived
顺序就是Base->Base1->Base2->Derived
虚基类和普通基类的主要区别在于 两个直接基类Base1,Base2继承方式前面+ virtual,在 Derived构造函数定义是要带上虚基类的形参列表并且赋值 例如 ":Base(sa)"
1.所有直接或间接派生类都要调用虚基类构造函数。
2.由最远派生类调用虚基类构造函数,其他基类调用被忽略。
Base->Base1->Base2->Derived遵循以上规则
先在主函数中找到obj对象的传参,找到 Derived类的构造函数,优先调用虚基类的构造函数
Base-> Derived
其次到Base1->Base2 声明顺序的这个两个构造函数,并且在这两个构造函数中忽略Base虚基类的调用 在Base与 Derived中间插入Base1->Base2->
即: Base->Base1->Base2->Derived
#include<iostream>
using namespace std;
class base
{
public:
string name;
int age;
void show1()
{
cout<<"name:"<<name<<endl;
cout<<"age:"<<age<<endl;
}
};
class derived:public base
{
public:
string sex;
void show2()
{
cout<<"name:"<<name<<endl;
cout<<"age:"<<age<<endl;
cout<<"sex:"<<sex<<endl;
}
};
int main()
{
base person1;
derived person2;
person2.age=18;
person2.name="xiaoming";
person2.sex="man";
person2.show2();
person1=person2;//这个是全文代码的核心,公有继承派生类的属性比基类的属性多,
//赋值的时候可以把派生类的对象直接赋给基类的对象,但基类的对象属性仅仅是基类本身的属性
//派生类新增的属性在基类中不显示。(因为基类没有这些属性)
person1.show1();
}
公有继承派生类的属性比基类的属性多,赋值的时候可以把派生类的对象直接赋给基类的对象,但基类的对象属性仅仅是基类本身的属性,派生类新增的属性在基类中不显示。(因为基类没有这些属性) 除了直接赋值,还有引用,指针等赋值方式,赋值结果相同
以下是我选择题出现的错误,以下均为正确答案,可以自行理解
1.构造函数和析构函数不能被派生类继承。
2.派生类中包含了基类中的成员,它是对基类定义的扩充和延续,是对基类定义的进一步具体化,而不是基类的子集。
3.友元函数能访问类的私有成员
4.基类的构造函数和析构函数不能被派生类继承.
5.类与类之间的友元关系不可以继承
6.友元函数破坏了类的封装性和隐藏性
7.友元函数不是类的成员函数
这题选择D,在建立一个派生类的对象时,先调用 基类->成员对象类(其他类作为成员函数,类的组合)->派生类自身