c++学习笔记第六天
继承与派生(Inherit&&Derive)
7.1.引入
在 C++中可重用性(software reusability)是通过继承(inheritance)这一机制来实现的。
如果没有掌握继承性,就没有掌握类与对象的精华。
引例:
7.1.1.归类
对于学生和老师进行归类。
7.1.2.抽取
7.1.3.继承
7.1.4.重用
#include
using namespace std;
class Person
{
public:
void eat(string food)
{
cout<<"i am eating "<
}
};
class Student:public Person
{
public:
void study(string course)
{
cout<<"i am a student i study "<
}
};
class Teacher:public Person
{
public:
void teach(string course)
{
cout<<"i am a teacher i teach "<
}
};
int main()
{
Student s;
s.study("C++");
s.eat("
黄焖鸡");
Teacher t;
t.teach("Java");
t.eat("
驴肉火烧");
return 0;
}
7.2.定义
类的继承,是新的类从已有类那里得到已有的特性。或从已有类产生新类的过程就是类
的派生。原有的类称为基类或父类,产生的新类称为派生类或子类。
派生与继承,是同一种意义两种称谓。
7.3.继承
7.3.1.关系定性is-a/has-a
is-a 是一种属于关系,如:狗属于一种动物,车属于一种交通工具(Dog is an Animal. Car
is a Vehicle.)在面向对象中表现为一种继承关系。可以设计一个 Animal 类,Dog 类作为
Animal 类(基类)的派生类;设计一个 Vehicle 类,Car 类作为 Vehicle 类(基类)的派生
类。
has-a 是一种包含、组合关系。如:车包含方向盘、轮胎、发动机(Car has steering-wheel,
wheels, engine),但不能说方向盘/轮胎/发动机是一种车;狗包含腿、尾巴,但不能说腿/
尾巴是一种狗。正确的应该说车聚合(包含)了方向盘、轮胎、发动机。
is-a 这种关系可以完成代码复用,是继承。把比较抽象的类(如例子中的动物、交通工具)
定义为基类,把比较具体的定义为子类(派生类),比如狗、兔子、马都以动物做为基类而做
出派生类。基类都可以有跑、吃、睡觉等共同方法,高度、体重等共同属性;具体到狗、兔子、
马则有自己特别的属性如食物,特别的方法如叫声等。
因此,如果 A 是 B,则 B 是 A 的基类,A 是 B 的派生类。为继承关系。
has-a 这种关系可以把一个复杂的类处理为一个个相对简单的类,是聚合。比如创建方向
盘类、轮胎类、发动机类,最后创建车类。车类调用 4 个轮胎类实例(假如该车有 4 个轮胎)
和 1 个方向盘类实例(对象)和一个发动机类实例(对象)。每个类本身相对简单,通过聚
合(组合)成为一个复杂的类。
因此,如果 A 包含 B,则 B 是 A 的组成部分。为聚合关系,可以由组成部分聚合成为一
个类。
7.3.2.语法
派生类的声明:
class 派生类名:[继承方式] 基类名
{
派生类成员声明;
};
一个派生类可以同时有多个基类,这种情况称为多重继承,派生类只有一个基类,称为
单继承。下面从单继承讲起。
7.3.3.继承方式
继承方式规定了如何访问基类继承的成员。继承方式有 public, private, protected。继
承方式不影响派生类的访问权限,影响了
从基类继承来的成员的访问权限,包括派生类内的
访问权限和派生类对象
。
简单讲:
公有继承:基类的公有成员和保护成员在派生类中保持原有访问属性,其私有成员仍
为基类的私有成员。
私有继承:基类的公有成员和保护成员在派生类中成了私有成员,其私有成员仍为基类
的私有成员。
保护继承:基类的公有成员和保护成员在派生类中成了保护成员,其私有成员仍为基类
的私有成员。
pretected 对于外界访问属性来说,等同于私有,但可以派生类中可见。
#include <iostream>
using namespace std;
class Base
{
public:
int pub;
protected:
int pro;
private:
int pri;
};
class Drive:public Base
{
public:
void func()
{
pub = 10;
pro = 100;
// pri = 1000;
public;
int a;
protected:
int b;
private:
int c
};
//
int main()
{
Base b;
b.pub = 10;
// b.pro = 100;
// b.pri = 1000;
return 0;
}
7.3.4.派生类的组成
派生类中的成员,包含两大部分,一类是从基类继承过来的,一类是自己增加的成员。
从基类继承过过来的表现其共性,而新增的成员体现了其个性。
几点说明:
1,全盘接收,除了构造器与析构器。基类有可能会造成派生类的成员冗余,所以说基
类是需设计的。
2,派生类有了自己的个性,使派生类有了意义。
#include
#include
using namespace std;
class A
{
public:
A()
{
cout<
cout<
}
int a;
};
class B:public A
{
public:
B()
{
cout<
cout<
}
int b;
};
class C:public B
{
public:
C()
{
cout<
cout<
}
void func()
{
cout<<&a<
cout<<&b<
cout<<&c<
}
int c;
};
int main()
{
C c;
cout<<"&c "<<&c<
cout<<"*************"<
c.func();
return 0;
}
7.4.派生类的构造
派生类中由基类继承而来的成员的初始化工作还是由基类的构造函数完成,然后派生类
中新增的成员在派生类的构造函数中初始化。
7.4.1.派生类构造函数的语法:
派生类名::派生类名(参数总表)
:基类名(参数表)
,内嵌子对象(参数表)
{
派生类新增成员的初始化语句; //也可出现地参数列表中
}
注:
构造函数的初始化顺序并不以上面的顺序进行,而是根据声明的顺序初始化。
如果基类中没有默认构造函数(无参),那么在派生类的构造函数中必须显示调用基类构
造函数,以初始化基类成员。
派生类构造函数执行的次序:
基类
-->成员-->子类
a 调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左到右);
b 调用内嵌成员对象的构造函数,调用顺序按照它们在类中声明的顺序;
c 派生类的构造函数体中的内容。
7.4.2.代码实现
祖父类
student.h
class Student
{
public:
Student(string sn,int n,char s);
~Student();
void dis();
private:
string name;
int num;
char sex;
};
student.cpp
Student::Student(string sn, int n, char s)
:name(sn),num(n),sex(s)
{ }
Student::~Student()
{ }
void Student:: dis()
{
cout<
cout<
cout<
}
父类
graduate.h
class Graduate:public Student
{
public:
Graduate(string sn,int in,char cs,float fs);
~Graduate();
void dump()
{
dis();
cout<
}
private:
float salary;
};
graduate.cpp
Graduate::Graduate(string sn, int in, char cs, float fs)
:
Student(sn,in,cs),salary(fs)
{ }
Graduate::~Graduate()
{ }
类成员
birthday.h
class Birthday
{
public:
Birthday(int y,int m,int d);
~Birthday();
void print();
private:
int year;
int month;
int day;
};
birthday.cpp
Birthday::Birthday(int y, int m, int d)
:year(y),month(m),day(d)
{ }
Birthday::~Birthday()
{}
void Birthday::print()
{
cout<
}
子类
doctor.h
class Doctor:public Graduate
{
public:
Doctor(string sn,int in,char cs,float fs,string st,int iy,int im,in
t id);
~Doctor();
void disdump();
private:
string title; //
调用的默认构造器,初始化为””
Birthday birth; //类中声明的类对象
};
doctor.cpp
Doctor::Doctor(string sn, int in, char cs, float fs, string st, int iy,
int im, int id)
:
Graduate(sn,in,cs,fs),birth(iy,im,id),title(st)
{}
Doctor::~Doctor()
{ }
void Doctor::disdump()
{
dump();
cout<
birth.print();
}
测试代码
int main()
{
Student s("zhaosi",2001,'m');
s.dis();
cout<<"----------------"<
Graduate g("liuneng",2001,'x',2000);
g.dump();
cout<<"----------------"<
Doctor d("qiuxiang",2001,'y',3000,"doctor",2001,8,16);
d.disdump();
return 0;
}
7.4.3.结论
子类构造器中,要么显示的调用父类的构造器(传参),要么隐式的调用。发生隐式调用时,
父类要有无参构造器或是可以包含无参构造器的默认参数函数。子类对象亦然。
7.5.派生类的拷贝构造
7.5.1.格式
派生类::派生类(const 派生类& another)
:基类(another),派生类新成员(another.新成员)
{ }
7.5.2.代码
父类
student.h
class Student
{
public:
Student(string sn,int n,char s);
Student(const Student & another);
~Student();
void dis();
private:
string name;
int num;
char sex;
};
student.cpp
Student::Student(string sn, int n, char s)
:name(sn),num(n),sex(s)
{ }
Student::~Student()
{ }
void Student:: dis()
{
cout<
cout<
cout<
}
Student::Student(const Student & another)
{
name = another.name;
num = another.num;
sex = another.sex;
}
子类
graduate.h
class Graduate:public Student
{
public:
Graduate(string sn,int in,char cs,float fs);
~Graduate();
Graduate(const Graduate & another);
void dump()
{
dis();
cout<
}
private:
float salary;
};
graduate.cpp
Graduate::Graduate(string sn, int in, char cs, float fs)
:Student(sn,in,cs),salary(fs)
{ }
Graduate::~Graduate()
{ }
Graduate::Graduate(const Graduate & another)
:Student(another),salary(another.salary)
{ }
测试代码
int main()
{
Graduate g("liuneng",2001,'x',2000);
g.dump();
Graduate gg = g;
gg.dump();
return 0;
}
7.5.3.结论:
派生类中的默认拷贝构造器会调用父类中默认或自实现拷贝构造器,若派生类中自实现拷
贝构造器,
则必须显示的调用父类的拷贝构造器。
7.6.派生类的赋值运算符重载
赋值运算符函数不是构造器,所以可以继承,语法上就没有构造器的严格一些。
7.6.1.格式
子类& 子类::operator=(const 子类& another)
{
if(this == &another)
return *this; //
防止自赋值
父类::operator =(another); // 调用父类的赋值运算符重载
this->salary = another.salary;//子类成员初始化
return * this;
}
7.6.2.代码
基类
student.h
Student & operator=(const Student & another);
student.cpp
Student & Student::operator=(const Student & another)
{
this->name = another.name;
this->num = another.num;
this->sex = another.sex;
return * this;
}
派生类
graduate.h
Graduate & operator=(const Graduate & another);
graduate.cpp
Graduate & Graduate::operator=(const Graduate & another)
{
if(this == &another)
return *this;
Student::operator =(another);
this->salary = another.salary;
return * this;
}
测试代码
int main()
{
Graduate g("liuneng",2001,'x',2000);
g.dump();
Graduate gg = g;
gg.dump();
cout<<"-----------"<
Graduate ggg("gege",2001,'x',4000);
ggg.dump();
ggg = g;
ggg.dump();
return 0;
}
7.6.3.结论:
派生类的默认赋值运算符重载函数,会调用父类的默认或自实现函数。派生类若自实现,
则不会发生调用行为,也不报错(区别拷贝),赋值错误,若要正确,需要显示的调用父类的构
造器。
7.7.派生类友元函数
由于友元函数并非类成员,因引不能被继承,在某种需求下,可能希望派生类的友元函数能
够使用基类中的友元函数。为此可以通过强制类型转换,将
派生类的指针或是引用强转为其类的
引用或是指针
,然后使用转换后的引用或是指针来调用基类中的友元函数。
#include
using namespace std;
class Student
{
friend ostream &operator<<(ostream & out, Student & stu);
private:
int a;
int b;
};
ostream &operator<<(ostream & out, Student & stu)
{
out<
}
class Graduate:public Student
{
friend ostream &operator<<(ostream & out, Graduate & gra);
private:
int c;
int d;
};
ostream &operator<<(ostream & out, Graduate & gra)
{
out<<(Student&)gra<<endl;
out<
}
int main()
{
// Student a;
// cout<
Graduate g;
cout<
return 0;
}
7.8.派生类析构函数的语法
派生类的析构函数的功能是在该对象消亡之前进行一些必要的清理工作,析构函数没有类
型,也没有参数。析构函数的执行顺序与构造函数
相反。
析构顺序
子类
->成员->基类
无需指明析构关系。why? 析构函数只有一种,无重载,无默