网站 展示板,永久免费国外php空间,四川百度推广和seo优化,惠民网站建设阿尼亚全程陪伴大家学习~
前言
每个程序员在开发新系统时#xff0c;都希望能够利用已有的软件资源#xff0c;以缩短开发周期#xff0c;提高开发效率。 为了提高软件的可重用性(reusability)#xff0c;C提供了类的继承机制。
1.继承的概念
继承#xff1a; 指在现有…
阿尼亚全程陪伴大家学习~
前言
每个程序员在开发新系统时都希望能够利用已有的软件资源以缩短开发周期提高开发效率。 为了提高软件的可重用性(reusability)C提供了类的继承机制。
1.继承的概念
继承 指在现有类的基础上建立一个新的类。现有类称为基类或父类新建类称为派生类或子类。对于父类与子类人们也常说子类继承了父类或者父类派生了子类。
语法class 子类派生类继承方式 父类基类
现在我们一起来看一下具体的实现
#includeiostream
using namespace std;
//基础界面
class BasePage
{void left(){cout Java,C,Python,C.... endl;}void right(){cout 右界面 endl;}void head(){cout 头部界面 endl;}void bottom(){cout 底部界面 endl;}
};
//C语言界面
class C
{
public:void left(){cout Java,C,Python,C.... endl;}void right(){cout 右界面 endl;}void head(){cout 头部界面 endl;}void bottom(){cout 底部界面 endl;}void Linux(){cout Linux endl;}
};
//C语言界面
class C :public BasePage
{
public:void Linux(){cout Linux endl;}
};
//Java界面
class Java :public BasePage
{
public:void JavaSE(){cout JavaSE endl;}
};这可以看做一个编程语言学习的主界面左界面是各种不同语言的分类这些不同编程语言界面都有主界面的部分但是他们也有自己独特的部分主界面是基类而具体的编程语言界面则是派生类派生类都继承了父类基类所有的成员函数。
2.继承的三种方式
公共public继承、保护protected继承、私有private继承 下面我们一一介绍
2.1公共继承
#includeiostream
using namespace std;
class A
{
private:int _a 1;
public:int _A 10;void A_print(){cout _a _a endl;cout _A _A endl;}
};
class B:public A
{
private:int _b 2;
public:int _B 20;void B_print(){cout _b _b endl;}
};
int main()
{B b;b.B_print();b.A_print();return 0;
} 我们发现通过公共继承的方式来继承A类A类中的打印函数也被继承了所以B类的对象可以调用A类的成员函数接下来我们修改一下代码在B类中访问A类的成员变量 编译器告诉我们_a不可以被访问因为他是A类中的私有成员而_A可以被访问因为他是A类中的公有成员那有没有什么方法能让_a也能被访问呢
有。一种是把_a设置为public成员另外一种是把_a设置为protected成员最好的做法这三种又有什么区别呢重要 *公有public成员 公有成员可以从任何地方被访问包括类的内部、类的派生类以及类的外部。 把_a设置为公有成员意味着任何地方的代码都可以直接访问它这通常不是一个好的做法因为它破坏了封装性封装性意味着隐藏对象的内部状态以防止它们被外部代码直接访问。 *私有private成员 私有成员只能在类的内部被访问。 编译器告诉我们_a不可以被访问因为它被声明为私有成员。这意味着你不能从类的外部或派生类中直接访问它。 *保护protected成员 保护成员可以在类的内部和派生类中被访问但不能在类的外部被访问。 把_a设置为保护成员意味着你可以在其派生类中访问它但不能在类的外部直接访问它。这提供了一种在派生类中重用和扩展基类功能的方式同时保持对外部世界的封装性。 能明白三者的差异也就很容易理解三种继承方式的差别了
#includeiostream
using namespace std;
class A
{
private:int _a 1;
protected:int a 100;
public:int _A 10;void A_print()//A类打印函数{cout _a _a endl;cout _A _A endl;cout a a endl;}
};
class B:public A
{
private:int _b 2;
public:void B_print()//B类打印函数{cout _b _b endl;//cout _a _a endl;//A类私有成员不能被访问cout _A _A endl;//A类公有成员可以被访问cout a a endl;//A类保护成员可以被访问A_print();//A类公有成员可以被访问}
};
int main()
{B b;b.B_print();cout endl;cout b._A endl;//public继承方式在类的外部能访问类public成员b.A_print();//public继承方式在类的外部能访问类public成员//cout b.a endl;//在类的外部不能访问类protected成员return 0;
}
2.2保护继承
#includeiostream
using namespace std;
class A
{
private:int _a 1;
protected:int a 100;
public:int _A 10;void A_print()//A类打印函数{cout _a _a endl;cout _A _A endl;cout a a endl;}
};
class B:protected A
{
private:int _b 2;
public:void B_print()//B类打印函数{cout _b _b endl;//cout _a _a endl;//A类私有成员不能被访问cout _A _A endl;//A类公有成员可以被访问cout a a endl;//A类保护成员可以被访问A_print();//A类公有成员可以被访问}
};
int main()
{B b;b.B_print();//b.A_print();//protected继承方式在类的外部不能访问类成员return 0;
} 2.3私有继承
#includeiostream
using namespace std;
class A
{
private:int _a 1;
protected:int a 100;
public:int _A 10;void A_print()//A类打印函数{cout _a _a endl;cout _A _A endl;cout a a endl;}
};
class B:private A
{
private:int _b 2;
public:void B_print()//B类打印函数{cout _b _b endl;//cout _a _a endl;//A类私有成员不能被访问cout _A _A endl;//A类公有成员可以被访问cout a a endl;//A类保护成员可以被访问//A_print();//A类公有成员可以被访问}
};
int main()
{B b;b.B_print();cout endl;//cout b._A endl;//private继承方式在类的外部不能访问类public成员//cout b.a endl;private继承方式在类的外部不能访问类protected成员//b.A_print();//private继承方式在类的外部不能访问类public成员return 0;
} 总结
在类的内部派生类无论是以哪种方式继承基类都不能访问基类的private成员而基类的public成员、protected成员可以被派生类访问
在类的外部首先需要明确的是无论是基类还是派生类的private成员和protected成员都是不能直接被访问的。而public继承的方式可以通过派生类的对象访问基类的public成员而private继承的方式和protected继承的方式中却不可以通过派生类的对象访问基类的public成员可以这么理解此时基类的public成员分别成了派生类的私有成员和保护成员。
下图辅助理解 3.继承的对象模型 我们发现B类的大小是16个字节但是我们之前继承方式当中不是说基类的私有成员派生类不能访问吗
其实父类中所有非静态成员都会被子类继承父类中的私有成员属性其实是被编译器隐藏了因此访问不到但是确实被继承了下来下面我们来验证一下
首先我们先打开这个工具开发人员命令提示符 指令输入步骤 最终呈现结果 经过验证是不是更可靠了呢
4.派生类的构造函数和析构函数
4.1构造函数
派生类不继承基类的构造函数在声明派生类时一般应定义自己的构造函数
注意
派生类构造函数的总参数表中的参数应当包括调用基类构造函数所需的参数 派生类构造函数的执行过程是先调用基类构造函数初始化基类成员然后对新增成员初始化
#includeiostream
#includestring
using namespace std;class Person//基类
{
protected:string _name;//姓名char _sex;//性别int _age;//年龄
public://Person构造函数Person(string name,char sex,int age):_name(name),_sex(sex),_age(age){}void PersonPrint(){cout name: _name endl;cout sex: _sex endl;cout age: _age endl;}
};
//派生类
class Student :public Person
{
private:int _num;//学号
public://Student构造函数Student(string name,char sex,int age,int num):Person(name,sex,age),_num(num){}void StudentPrint(){PersonPrint();cout num: _num endl;}
};
int main()
{Student s(liming, M, 18, 1001);s.StudentPrint();return 0;
} 4.1.1有子对象的派生类的构造函数
在学习结构体时我们讲到一个结构体的成员还可以是个结构体变量。 派生类也可以有子对象类类型的成员变量 #includeiostream
#includestring
using namespace std;class Person//基类
{
protected:string _name;//姓名char _sex;//性别int _age;//年龄
public://Person构造函数Person(string name,char sex,int age):_name(name),_sex(sex),_age(age){}void PersonPrint(){cout name: _name endl;cout sex: _sex endl;cout age: _age endl;}
};
//派生类
class Student :public Person
{
private:Person _teacher;//班主任子对象int _num;//学号
public://Student构造函数Student(string name,char sex,int age,int num,const Person teacher):Person(name,sex,age),_num(num),_teacher(teacher){}void StudentPrint(){PersonPrint();//打印学生的信息cout num: _num endl;_teacher.PersonPrint();//打印老师的信息}
};
int main()
{Person teacher(zhaoli, F, 38);//班主任Student s(liming, M, 18, 1001, teacher);//学生s.StudentPrint();return 0;
} 基类构造函数和子对象的书写顺序可以任意
这里有子对象的派生类的构造函数还有另外两种写法
写法一
这种不如引用传参更安全效率高 写法二
这种写法相对比较麻烦写的形参更多了 4.1.2派生类构造函数的执行顺序
结论 先调用基类构造函数对基类数据成员初始化 再调用子对象类的构造函数对子对象的数据成员初始化 最后执行派生类构造函数体中的语句对派生类新增数据成员初始化 验证 4.2析构函数 1.派生类不继承基类的析构函数在声明派生类时应当定义自己的析构函数 2.派生类的析构函数只对新增成员进行清理工作基类、子对象的清理工作仍由它们各自的析构函数负责。 3.在执行派生类的析构函数时系统会自动调用基类的析构函数和子对象的析构函数分别对基类和子对象进行清理 4.析构函数的执行顺序与构造函数正好相反 先执行派生类自己的析构函数对派生类新增成员进行清理 然后调用子对象的析构函数对子对象进行清理 最后调用基类的析构函数对基类进行清理。 析构顺序验证 我们发现Person类只构造了两次居然析构了三次这是为什么呢
其实是因为默认的Person类拷贝构造函数现在我们显示写一下他的拷贝构造函数来验证一下
#includeiostream
#includestring
using namespace std;
class Person//基类
{
protected:string _name;//姓名char _sex;//性别int _age;//年龄
public:void PersonPrint(){cout name: _name endl;cout sex: _sex endl;cout age: _age endl;}//Person构造函数Person(string name,char sex,int age) :_name(name), _sex(sex), _age(age){cout Person构造 endl;}//Person拷贝构造Person(const Person p){_name p._name;_sex p._sex;_age p._age;cout Person拷贝构造 endl;}~Person(){cout ~Person析构 endl;}
};
class Student :public Person
{
private:Person _teacher;//班主任子对象int _num;//学号
public://Student构造函数Student(string name, char sex, int age, int num, const Person teacher):Person(name, sex, age), _num(num), _teacher(teacher){cout Student构造 endl;}void StudentPrint(){PersonPrint();//打印学生的信息cout num: _num endl;_teacher.PersonPrint();//打印老师的信息}~Student(){cout ~Student析构 endl;}
};
int main()
{Person teacher(zhaoli, F, 38);//班主任cout ******************************* endl;Student s(liming, M, 18, 1001, teacher);//学生//s.StudentPrint();return 0;
} 那么谁是拷贝构造的呢
第一个构造的是Person类的班主任对象第二个则是调用基类的构造第三个构造的是子对象是拷贝构造
5.同名成员的处理
假如在A类基类和B类派生类中有同名数据成员m同名函数print(),那在类外面访问他们的时候会如果我们想调用A类中的函数print()和访问A类数据成员m可以通过创建一个A类的对象通过对象来访问。
但是既然A类被B类通过公共继承的方式继承了那么A类的公有数据成员m和函数print()也被继承了但是B类派生类中有同名数据成员m同名函数print()。此时如果我们建一个B类的对象访问数据成员m和调用函数print()会产生二义性吗冲突
一起来看一下 证明了同名成员也被继承 我们发现B类的对象访问的都是B类中的数据成员m、函数print()A类的数据成员m、函数print()并没有被访问也没有产生二义性其实派生类会隐藏基类的同名成员那我们怎么样才能通过派生类的对象访问基类成员和成员函数呢
其实很简单只需要加类名即可 总结: 1.子类对象可以直接访问到子类中同名成员 2.子类对象加作用域可以访问到父类同名成员 3.当子类与父类拥有同名的成员函数子类会隐藏父类中同名成员函数加作用域可以访问到父类中同名函数
补充 继承同名静态成员处理方式 问:继承中同名的静态成员在子类对象上如何进行访问 静态成员和非静态成员出现同名处理方式一致 ·访问子类同名成员直接访问即可 ·访问父类同名成员需要加作用域 6.多继承
6.1语法
C允许一个类继承多个类 语法:class子类:继承方式 父类1继承方式 父类2...... 多继承可能会引发父类中有同名成员出现需要加作用域区分 6.2多继承派生类的构造函数 注意
⑴初始化表中基类构造函数的排列顺序任意
⑵派生类D的构造函数的执行顺序是先调用基类的构造函数再执行派生类构造函数的函数体
⑶调用基类的构造函数的顺序是按照声明派生类时基类出现的顺序。
示例 7.菱形继承
如图先声明A类然后由它派生出B1类、B2类D类同时继承了B1类、B2类
当菱形继承两个父类拥有相同数据需要加以作用域区分 A类中的数据成员a这份数据我们知道只有有一份就可以菱形继承导致数据有两份造成了资源浪费该如何解决呢
利用虚继承可以解决在继承方式之前 加上关键字virtual即可此时的基类称为虚基类