C++继承
类之间的关系
has-A,uses-A 和 is-A
has-A 包含关系,用以描述一个类由多个“部件类”构成。实现has-A关系用类成员表示,即一个类中的数据成员是另一种已经定义的类。
uses-A 一个类部分地使用另一个类。通过类之间成员函数的相互联系,定义友员或对象参数传递实现。
is-A 机制称为“继承”。关系具有传递性,不具有对称性。
基类 & 子类
一个类A继承于B,或称从类B派生类A
则类B称为基类(父类),类A称为派生类(子类)
继承的重要说明
- 子类拥有父类的所有成员变量和成员函数
- 子类可以拥有父类没有的方法和属性,即子类自己的方法和属性
- 子类就是一种特殊的父类
- 子类对象可以当作父类对象使用
基本语法
语法格式
class 派生类名 : 访问控制符 基类名
其中访问控制符包括:public 、private、protected三种,如果继承时未使用访问控制符,默认为private
实例1
#include<iostream>
using namespace std;
class Parent
{
public:
void print()
{
a=0;
b=0;
cout<<"a="<<a<<endl;
cout<<"b="<<b<<endl;
}
private:
};
class Child : public Parent
{
public:
private:
int c;
};
int main()
{
Child c1;
c1.a=2;
c1.b=3;
c1.print();
system("pause");
return 0;
}
上述代码编译执行结果为:
a=0
b=0
访问控制和继承
类的访问控制有三种: public 、private 、protected
派生类可以访问基类中所有的非私有成员
public: 修饰的成员变量和成员方法,在类内和类外都能使用
protected: 修饰的成员变量和成员方法, 在类的内部可以使用,在继承的子类中也可以使用,其他地方不可以使用
private: 修饰的成员变量和成员方法,只能在类的内部使用,不能在类的外部使用
实例2
基类:
class Parent
{
public:
int a;
protected:
int b;
private:
int c;
public:
void printT()
{
cout<<"printT"<<endl;
}
};
公有继承(public)
class Child1 :public Parent
{
public:
void useVar()
{
a=0;
b=0;
//c=0;//error 报错:"Parent::c": 无法访问privatec成员(在"Parent"类中声明)
}
protected:
private:
};
在子类Child1中对c赋值时,报错的原因:
子类Child公有继承于基类Parent,Child1能使用继承于基类的所有非私有成员,然而因为c为Parent类的私有成员,故子类Child1不可以访问c
私有继承(private)
class Child2 : private Parent
{
public:
void useVar()
{
a=0;
b=0;
//c=0;//error
}
};
int main()
{
Child2 c2;
//c2.a = 10;//err
//c2.b = 20;//err
//c2.c = 30;//err
system("pause");
return 0;
}
在上述代码中,子类Child2中对c赋值报错的原因:
c为基类Parent中的私有继承,在子类中虽然依然存在从基类中继承过来的私有成员,但是是无法访问到的,故Parent类外无法访问c
main()函数中,对a,b,c赋值报错的原因
因为Child2私有继承于基类Parent,所以从基类继承过来的成员在子类中都会变成私有的,故a,b,c无法在类外访问
保护继承(protected)
class Child3 : protected Parent
{
public:
protected:
private:
public:
void useVar()
{
a=0;
b=0;
//c=0;//error
}
};
int main()
{
Child3 c3;
//c3.a=10;//error
//c3.b=20;//error
//c3.c=30;//error
system("pause");
return 0;
}
在上述代码中,main()函数中,不能对c3.a,c3.b,c3.c赋值的原因
因为子类Child3是保护继承于基类Parent的,所以从基类Parent中继承过来的a在子类中变成了protected成员,故a不能在类外使用
因为b和c分别是类中的protected成员和private成员,故不能在类外使用
不同的继承方式会改变继承成员的访问属性
从上述实例2中,可以总结出下面几点
C++中的继承方式会影响子类的对外访问属性
- public继承:父类成员在子类中保持原有访问级别
- private继承:父类成员在子类中全部变为private成员
- protected继承:
父类中的public成员会变成protected
父类中的protected成员仍然是protected成员
父类中的private成员仍然是private成员
private成员在子类中依然存在,但是是无法访问到的,无论何种方式继承基类,派生类都不能直接使用基类的私有成员
C++中子类对外访问属性表
访问级别 | public | protected | private | |
---|---|---|---|---|
继承方式 | ||||
public | public | protected | private | |
protected | protected | protected | private | |
private | private | private | private |
基类中的私有成员是否会被继承? 会
https://cloud.tencent.com/developer/article/1394316
类型兼容性原则
类型兼容性原则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代,通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员。这样,公有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决。类型兼容规则中所指的替代包括以下情况:
子类对象可以当作父类对象使用
子类对象可以直接赋值给父类对象
子类对象可以直接初始化父类对象
父类指针可以直接指向子类对象
父类引用可以直接引用子类对象
#include<iostream>
using namespace std;
class Parent_dm04
{
public:
void printP()
{
cout<<"father"<<endl;
}
Parent_dm04()
{
cout<<"parent 无参构造函数"<<endl;
}
Parent_dm04(const Parent_dm04 &obj)
{
cout<<"parent 拷贝构造函数"<<endl;
}
};
class child_dm04 : public Parent_dm04
{
public:
void printC()
{
cout<<"son"<<endl;
}
protected:
private:
int c;
};
//C++编译器是不会报错的
void howToPrint(Parent_dm04 *base)
{
base->printP();//父类的成员函数
}
void howToPrint2(Parent_dm04 &base)
{
base.printP();//父类的成员函数
}
/*
兼容规则中所指的替代包括以下情况:
子类对象可以当作父类对象使用
子类对象可以直接赋值给父类对象
子类对象可以直接初始化父类对象
父类对象可以直接指向子类对象
父类引用可以直接引用子类对象
*/
int main()
{
Parent_dm04 p1;
p1.printP();
child_dm04 c1;
c1.printC();
c1.printP();
//类型(赋值)兼容性原则
//1-1 基类指针(引用)指向子类对象
Parent_dm04 *p=NULL;
p = &c1;
p->printP();//相当于执行子类从基类继承过来的函数
//1-2 指针做函数参数
howToPrint(&p1);
howToPrint(&c1);//将指向子类的指针传给函数参数为父类指针的函数,c++编译器是不会报错的
//1-3 引用做函数参数
howToPrint2(p1);
howToPrint2(c1);
//第二层含义
//可以让父类对象 初始化子类对象
//子类就是一种特殊的父类
Parent_dm04 p3 = c1;
system("pause");
return 0;
}
继承中的构造和析构
在子类对象构造时,需要调用父类构造函数对其继承过来的成员进行初始化
在子类对象析构时,需要调用父类析构函数对其继承过来的成员进行清理
class Parent_dm05
{
public:
Parent_dm05(int a , int b)
{
this->a = a;
this->b = b;
cout<<"Parent_dm05 父类的构造函数"<<endl;
}
~Parent_dm05()
{
cout<<"Parent_dm05的析构函数"<<endl;
}
void printP(int a,int b)
{
this->a = a;
this->b = b;
cout<<"parent_dm05"<<endl;
}
private:
int a;
int b;
}
//如果基类中有构造函数但没有默认构造函数,子类中的构造函数必须指定调用基类中的某个构造函数
class child_dm05 : public Parent_dm05
{
public:
child_dm05(int a, int b,int c) :Parent_dm05(a,b)
{
this->c = c;
cout<<"子类的构造函数"<<endl;
}
~child_dm05()
{
cout<<"子类的析构函数"<<endl;
}
void printC()
{
cout<<"son"<<endl;
}
protected:
private:
int c;
}
void playObj()
{
child_dm05 c1(1,2,3)
}
int main()
{
//Parent_dm05(1,2);
playObj();
system("pause");
return 0;
}
继承中的构造和析构调用原则
结论
先 调用父类构造函数 再调用子类构造函数
析构函数的调用顺序和构造函数相反
1、子类对象在创建时会首先调用父类的构造函数
2、父类构造函数执行结束后,执行子类的构造函数
3、当父类的构造函数有参数时,需要在子类的初始化列表中显式调用
4、析构函数调用的先后顺序与析构函数相反
继承与组合混搭下的构造和析构
#include<iostream>
using namespace std;
//https://m.imooc.com/article/30475 字符指针和字符数组
/*继承与组合对象混搭情况下,构造和析构调用原则:
先构造父类,再执行成员变量,最后执行自己
先析构自己,再析构成员变量,最后析构父类*/
class Object
{
public:
Object(int a,int b)
{
this->a = a;
this->b = b;
cout << "object构造函数执行" << "a" << a << "b" << b << endl;
}
~Object()
{
cout << "object析构函数执行" << endl;
}
protected:
int a;
int b;
};
class Parent : public Object
{
public:
Parent(const char *p) :Object(1, 2)
{
this->p = p;
cout << "parent类构造函数" << endl;
}
~Parent()
{
cout << "Parent析构函数" << p<<endl;
}
void printP(int a, int b)
{
cout << "爹 --- Parent类printP成员函数调用" << endl;
}
protected:
const char *p;
};
class child :public Parent
{
public:
child(const char *p) :Parent(p), obj1(3, 4), obj2(5, 6)
{
this->myp = p;
cout << "子类的构造函数" << myp << endl;
}
~child()
{
cout << "子类的析构" << myp << endl;
}
void printC()
{
cout << "崽 ---child的成员函数printC调用 " << endl;
}
protected:
const char *myp;
Object obj1;
Object obj2;
};
void objPlay()
{
child c1("ccc");
}
int main_dm06()
{
objPlay();
system("pause");
return 0;
}
上述代码,编译运行结果如下:
由运行结果可得出以下结论:
继承与组合对象混搭情况下,构造和析构调用原则:
先构造父类,再执行类成员变量,最后执行自己
先析构自己,再析构类成员变量,最后析构父类