学习继承之前,我们先回顾下protected(保护)成员
protected 成员的特点与作用:
-对建立其所在类对象的模块来说,它与private成员的性质相同
-对于其派生类来说,它与public成员的性质相同
-即实现了数据隐藏,又方便继承,实现代码重用
例1:
class A
{
protected:
int x;
};
int main()
{
A.a;
a.x=5;//错误,不能访问
}
但是继承的派生类却可以访问保护成员protected,
我们现在正式来学习继承。
类的继承和派生的概述
一.继承和派生是同一过程从不同的角度看
1.继承:保持已有类的特性而 构造新类的过程称为继承
2.派生:在已有类的基础上新增自己的特性而产生新类的过程称为派生
二.被继承的已有类称为基类(或父类)
三.派生出的新类称为派生类(或子类)
四.直接参与派生出某类的基类称为直接基类
五.基类的基类甚至更高层的基类称为间接基类
继承与派生类的目的
一.继承的目的
实现设计与代码的重用
二.派生的目的
当新的问题出现,原有的程序无法解决(或不能完全解决时),需要对原有的程序进行改造。
派生类的定义
1.单继承
就是只有一个父类的情况
2.多继承
例子
class A
{
public:
void setA(int);
void showA()const;
private:
int a;
};
class B
{
public:
void setB(int);
void showB()const;
private:
int b;
};
class C : public A,private B
{
pubic:
void setC(int,int,int);
void showC()const;
private:
int c;
};
void A::setA(int x)
{
a=x;
}
void B::setB(int x)
{
b=x;
}
void C::setC(int x,int y,int z)
{
//派生类成员直接访问基类 公有成员
set A(x);
setB(y);
c=z;
}
int main()
{
C obj;
obj.setA(5);
obj.showA();
obj.setC(6,7,9);
//obj,setB(6); 错误
return 0;
}
派生类的构成
一.吸收基类成员
吸收基类成员之后,派生类实际上就包含了它的全部基类中除构造和析构函数之外的所有成员。
二.改造基类成员
如果派生类声明了一个和某基类同名的新成员,派生的新成员就隐藏或覆盖了外层同名成员
三.添加新的成员
派生类增加新成员使派生类在功能上有所发展。
不同继承方式以及类成员的访问控制
一.不同继承方式的影响主要体现在:
(1)派生类成员对基类成员的访问权限
(2)通过派生类对象对基类成员的访问权限
二.三种继承方式
(1)公有继承
(2)私有继承
(3)保护继承
1.公有继承(public)
-基类的public和protected成员的访问属性在派生类中保持不变,但基类的private成员不可直接访问。
-派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员。
-通过派生类的对象访问从基类继承的成员,只能访问public成员。
回到开头的地方,我们说基类的对象不能访问protected成员,但是公有继承的函数成员却可以访问
例:
class A
{
protected:
int x;
};
class B:public A
{
public:
void function();
};
void B::function()
{
x=5; //正确
}
2.私有继承(private)
-基类的public和protected成员都以private身份出现在派生类中,但基类的private成员不可直接访问
-派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员
-通过派生类的对象不能直接访问从基类继承的任何成员
3.保护继承(protected)
-基类的public和protected成员都以protected身份出现在派生类中,但基类的private成员不可直接访问
-派生类中的成员函数可以直接访问基类在的public和protected成员,但不能直接访问基类的private成员
-通过派生类的对象不能直接访问从基类继承的任何成员
归纳一小点:
什么时候选择私有成员和保护成员?
这个肯定要从我们想拿它来干什么出发:
1.如果你看中基类中很很多好用的函数,你想拿到派生类中使用,但是仅限派生类的成员函数使用,不想暴露出去基类的接口,这个时候可以用私有继承。
2.如果你发现基类的内容对你没用,但是你觉得对你的子孙有用,可以使用保护继承,以后你的子孙新的派生类还可以使用。
总之一句话,精通使用公有成员,私有成员,公有继承,学会私有继承,保护继承。
向上转型
1.一个公有的派生类的对象在使用上可以被当作基类的对象,反之则不可以。具体表现在:
-派生类的对象可以隐含转换为基类对象
-派生类的对象可以初始化基类的引用
-派生类的指针可以隐含转换为基类的指针
2.通过基类对象名,指针只能使用从基类继承成员
派生类的构造和析构
继承时的构造函数
-默认情况下基类的构造函数不被继承,派生类需要自己定义构造函数
-定义构造函数时,只需要对本类中新增成员进行初始化,对继承来的基类成员的初始化,是自动调用基类构造函数完成的
-派生类的构造函数需要给基类的构造函数传递参数
-c++ 规定可以使用using语句继承基类构造函数,使之成为派生类的构造函数,但是只能初始化从基类继承的成员
语法形式:
using Base::Base;
单一继承时构造函数的定义
派生类名::派生类名(基类所需的形参,本类成员所需的形参):
基类名(参数表),本类成员初始化列表
{
//其他初始化
};
#include <iostream>
using namespace std;
class B
{
public:
B();
B(int i);
~B();
void print()const;
private:
int b;
};
B::B()
{
b=0;
cout<<"B's default constructor called"<<endl;
}
B::B(int i){
b=i;
cout<<"B's constructor called"<<endl;
}
B::~B(){
cout<<"B's destructor called"<<endl;
}
void B::print()const{
cout<<b<<endl;
}
class C: public B
{
public:
C();
C(int i,int j);
~c();
void print()const;
private:
int c;
};
C::C()
{
c=0;
cout<<"error"<<endl;
}
C::C(int i,int j):B(i),C(j){
cout<<"C's constructor"<<endl;
}
C::~C()
{
COUT<<"C's called"<<endl;
}
void print()const{
B::print();
cout<<c<<endl;
}
int main()
{
C obj(5,6);
obj.print();
return 0;
}
多继承时构造函数的定义 顺便说一下名字地二义性怎么解决
#include<iostream>
using namespace std;
#define pri() cout<<__func__<<" line: "<<__LINE__<<endl;
class man{
public:
man(){ pri(); }
~man(){ pri(); }
int x = 12;
};
class wolf{
public:
wolf(){ pri(); }
~wolf(){ pri(); }
int x = 13; //C++11标准后,可以直接在类体中定义初始化 成员变量
};
/*
* 多重继承: 派生类有多个直接继承的基类
* 多重继承构造顺序:
* 先左起第一个基类构造,再左起第二个基类构造.... 最后派生类构造
* 多重基类析构顺序:
* 先派生类析构,再右起第一个基类析构....
* */
class wolfman : public wolf, public man{
public:
wolfman(){ pri(); }
~wolfman(){ pri(); }
};
int main(int argc, char *argv[])
{
wolfman obj;
// 错误,有歧义,又称为 名字的二义性
//cout << obj.x << endl;
//使用 作用域访问符,解决多重继承 名字二义性
cout << obj.man::x << endl;
cout << obj.wolf::x << endl;
return 0;
}
派生类与基类的构造函数
-当基类中声明由默认构造函数或未声明构造函数时,派生类构造函数可以不向基类构造函数传递参数,也可以不声明构造函数。构造派生类的对象时,基类的默认构造函数将被调用。
-当需要执行基类中带形参的构造函数来初始化基类数据时候,派生类地构造函数应在初始化例表中为基类构造函数提供参数。
构造和析构的顺序
#include<iostream>
using namespace std;
/************************************************************************
* 文件说明
************************************************************************/
#define pri() cout<<__func__<<" line: "<<__LINE__<<endl;
/*
* 单继承:派生类只继承一个基类。
* 构造和析构执行顺序:
* 先基类构造,再派生类构造
* 先派生类析构,再基类析构
* */
class A{
public:
A(){ pri(); }
~A(){ pri(); }
};
class B : public A{
public:
B(){ pri(); }
~B(){ pri(); }
};
int main(int argc, char *argv[])
{
B obj;
B *p = new B;
delete p;
}
is-a关系
公有继承,单一继承 水果是基类 苹果是派生类
has-a关系
聚合的情况,组合关系 午餐是基类 苹果是派生类
虚基类的语法和用途
1.需要解决地问题
-当派生类从多个基类派生,而这些基类有共同地基类成员,则访问此共同基类中的成员时,将产生冗余,并有可能因冗余带来不一致性
2.虚基类声明
-以virtual说明基类继承方式
例:class B1:virtual public B
3.作用
-主要来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题
-为最远的派生类提供唯一的基类成员,而不是重复产生多次复制
4.注意
-在第一级继承时就要将共同基类设计为虚基类
代码例子
#define pri() cout<<__func__<<" line: "<<__LINE__<<endl;
class animal{
public:
animal(){ pri(); }
~animal(){ pri(); }
int x = 100;
};
class man : virtual public animal{
public:
man(){ pri(); }
~man(){ pri(); }
};
class wolf : virtual public animal{
public:
wolf(){ pri(); }
~wolf(){ pri(); }
};
// 多重继承时,虚拟继承的基类 优先被构造
class wolfman : public wolf, virtual public man{
public:
wolfman(){ pri(); }
~wolfman(){ pri(); }
};
int main(int argc, char *argv[])
{
wolfman obj;
// 错误:有歧义,路径二义性: 因为animal基类被构造 2次,内存基类部分有两份不同的空间,也就是 x存在于两个地址空间
// cout << obj.x << endl;
// 路径二义性解决方式 1:作用域访问符
//cout << obj.wolf::x << endl;
//cout << obj.man::x << endl;
// 路径二义性解决方式 2:设置虚基类(虚继承的共同基类)
cout << obj.x << endl;
return 0;
}