C++面向对象核心-继承
1、继承
1.1?概念
继承是面向对象的三大特性之一,体现了代码复用的思想。
继承就是在一个已存在的类的基础上建立一个新的类,并拥有其特性。
- 已存在的类被称为“基类”或者“父类”
- 新建立的类被称为“派生类”或者“子类”
- 对象间没有继承关系
#include?<iostream>
using namespace std;
//?基类
class Father
{
private:
string?name?= "孙";
public:
void set_name(string?name)
{
this->name?=?name;
}
string get_name()
{
return?name;
}
void work()
{
????????cout?<< "我的工作是厨师,我负责炒菜" <<?endl;
}
};
//?派生类
class Son:public?Father
{
};
int main()
{
Son?son;
????cout?<<?son.get_name() <<?endl;
????son.work();
return 0;
}
派生类差异化
上面的代码,Son类的功能几乎与Father类重叠,在实际的使用过程中,派生类会做出一些与基类的差异化。
- 修改继承过来的基类内容
属性:1、公有的属性可以直接更改。2、私有的属性,需要使用基类提供的公有函数,进行更改。
成员函数:函数隐藏,通过派生类实现一个同名同参的函数,来隐藏基类的函数。
调用隐藏函数,需要指定作用域
- 新增派生类的内容
#include?<iostream>
#include?<array>
#include?<vector>
#include?<list>
#include?<deque>
#include?<map>?//?头文件
using namespace std;
class Father
{
private:
string?name?= "孙";
public:
void set_name(string?name)
{
this->name?=?name;
}
string get_name()
{
return?name;
}
void work()
{
????????cout<<"我的工作是厨师,我负责炒菜?"<<endl;
}
};
//派生类
class Son:public?Father
{
public:
void init()
{
set_name("王");
}
//函数隐藏,隐藏父类同名函数
void work()
{
????????cout<<"我的工作是学生"<<endl;
}
void game()
{
????????cout<<"我还玩游戏"<<endl;
}
};
int main()
{
Son?son;//无参构造函数
????cout?<<?son.get_name()<<endl;//孙
????son.work();//我的工作是学生
????son.game();//我还玩游戏
????son.init();
????cout?<<?son.get_name()<<endl;//王
????son.Father::work();//”我的工作是厨师,我负责炒菜“;调用隐藏函数,需要指定作用域
return 0;
}
派生类往往是类的具象化,基类则是派生类的抽象。
基类和派生类是相对的,一个类可能又存在基类,又存在派生的情况,取决于那两个类进行比较。
1.2?构造函数
1.2.1?派生类与基类构造函数的关系
构造函数和析构函数不能被继承。
#include?<iostream>
using namespace std;
//?基类
class Father
{
private:
string?name;
public:
//?有参构造函数
Father(string?name):name(name){}
string get_name()
{
return?name;
}
};
//?派生类
class Son:public?Father
{
public:
//?编译器自动添加的构造函数,若没有该构造函数,编译器自动添加
Son():Father(){}
};
int main()
{
Son?son;//创建对象时发现该类由基类,则找基类构造对象
????cout?<<?son.get_name() <<?endl;
return 0;
}
派生类的任意一个构造函数,都必须直接或者间接调用基类的任意构造函数。
1.2.2?解决方案
1.2.2.1?补充基类的无参构造函数
#include?<iostream>
using namespace std;
//?基类
class Father
{
private:
string?name;
public:
Father()
{
????????name??= "王";
}
//?有参构造函数
Father(string?name):name(name){}
string get_name()
{
return?name;
}
};
//?派生类
class Son:public?Father
{
public:
//?编译器自动添加的构造函数
Son():Father(){}
};
int main()
{
Son?son;
????cout?<<?son.get_name() <<?endl;
return 0;
}
1.2.2.2?手动在派生类中调用基类构造函数
1.2.2.2.1?透传构造
在派生类的构造函数中,调用基类的构造函数,实际上编译器自动添加派生类的构造函数,调用基类无参构造时,采用的就是这种方式。
#include?<iostream>
using namespace std;
//?基类
class Father
{
private:
string?name;
public:
//????Father()
//????{
//????????name??=?"王";
//????}
//?有参构造函数
Father(string?name):name(name){}
string get_name()
{
return?name;
}
};
//?派生类
class Son:public?Father
{
public:
//?编译器自动添加的构造函数,透传构造
//?Son():Father(){}
//?手动添加的构造函数,透传构造
Son():Father("张"){}
//?手动添加派生类的有参构造函数
Son(string?name):Father(name){}
};
int main()
{
Son son("王");
????cout?<<?son.get_name() <<?endl;
return 0;
}
1.2.2.2.2?委托构造
一个类的构造函数可以调用这个类中的另一个构造函数。要避免循环委托。
委托构造的性能低于透传构造,但是代码的维护性更好。因为通常一个类中构造函数都会委托给能力最强(参数最多)的构造函数。(效率低)代码重构时,只需要更改这个能力最强的构造函数即可。
#include?<iostream>
using namespace std;
//?基类
class Father
{
private:
string?name;
public:
//????Father()
//????{
//????????name??=?"王";
//????}
//?有参构造函数
Father(string?name):name(name){}
string get_name()
{
return?name;
}
};
//?派生类
class Son:public?Father
{
public:
//?编译器自动添加的构造函数,透传构造
//?Son():Father(){}
//?手动添加的构造函数,透传构造
Son():Son("张"){}
//?手动添加派生类的有参构造函数
Son(string?name):Father(name){}
};
int main()
{
Son son("王");
????cout?<<?son.get_name() <<?endl;
return 0;
}
#include?<iostream>
using namespace std;
//?基类
class Father
{
private:
string?name;
int?i;
public:
//?有参构造函数
Father(string?name,int?i):name(name),i(i){}
string get_name()
{
return?name;
}
int get_i()
{
return?i;
}
};
//?派生类继承基类??公有继承
class Son:public?Father
{
public:
//?编译器自动添加的构造函数,透传构造
//?Son():Father(){}
//?手动添加构造函数,委托构造
Son():Son("张"){}
Son(string?name,int?i?= 1):Father(name,i){}
};
int main()
{
Son son("hello",2);
????cout?<<?son.get_name() <<?endl;
????cout?<<?son.get_i() <<?endl;
return 0;
}
1.2.2.2.3?继承构造
只需要加这一句话,编译器就会添加上面两种构造函数(using?Father::Father;)
C++11新增的写法,只需要一句话,就可以自动给派生类添加n个(n为基类构造函数的个数)构造函数。并且每个派生类的构造函数与基类的构造函数格式相同,每个 派生类的构造函数都通过透传构造调用对应格式的基类构造函数。
#include?<iostream>
using?namespace?std;
//基类
class?Father
{
private:
????string?name;
public:
????//委托构造
????Father():Father("王"){}
????//有参函数构造
????Father(string?name):name(name){}
????string?get_name()
????{
????????return?name;
????}
};
//派生类
class?Son:public?Father
{
public:
????//????//编译器自动添加的构造函数,透传构造
????//????//Son():Father(){}
????//????//手动添加的构造函数,透传构造
????//????Son():Father("张"){}
????//????//手动添加派生类的有参构造函数
????//????Son(string?name):Father(name){}
????//只需要加这一句话,编译器就会添加上面两种构造函数
????using?Father::Father;
};
int?main()
{
????Son?son;//无参构造函数
????Son?son1("张");
????cout?<<?son.get_name()<<endl;
????cout?<<?son1.get_name()<<endl;
????return?0;
}
1.3?对象的创建与销毁流程
在继承中,构造函数与析构函数的调用。
#include?<iostream>
using namespace std;
class Value
{
private:
string?str;
public:
Value(string?str):str(str)
{
????????cout?<<?str?<<"构造函数" <<?endl;
}
~Value()
{
????????cout?<<?str?<< "析构函数" <<?endl;
}
};
class Father
{
public:
static Value?s_value;
Value?val?= Value("Father?成员变量");
Father()
{
????????cout?<< "Father?构造函数被调用了" <<?endl;
}
~Father()
{
????????cout?<< "Father?析构函数被调用了" <<?endl;
}
};
Value?Father::s_value?= Value("静态FatherValue创建了");
class Son:public?Father
{
public:
static Value?s_value;
Value?val?= Value("Son?成员变量");
Son()
{
????????cout?<< "Son构造函数被调用了" <<?endl;
}
~Son()
{
????????cout?<< "Son析构函数被调用" <<?endl;
}
};
Value?Son::s_value?= Value("静态SonValue被创建了");
int main()
{
????cout?<< "主程序开始执行" <<?endl;
//?局部代码块
{
Son?s;
????????cout?<< "对象执行中" <<?endl;
}
????cout?<< "主程序结束了" <<?endl;
return 0;
}
上面的执行结果中,可以得到下面的规律:
- 以“对象执行中”为轴,上下对称,先创建静态后创建非静态,先创建基类,后创建派生类,先创建成员变量,后创建对象。先析构成员变量后析构函数,先析构派生类,后析构基类,先析构非静态,后析构静态。
- 在创建的过程中,同类型的内存区域,基类先开辟。
- 静态的创建早于非静态
- 对象的创建,晚于类中成员变量的创建。
1.4?多重继承
1.4.1?概念
C++支持多重继承,即一个派生类可以有多个基类。派生类对于每个基类的关系仍然可以看作是一个单继承。
#include?<iostream>
using namespace std;
class Sofa
{
public:
void sit()
{
????????cout?<< "沙发可以坐着" <<?endl;
}
};
class Bed
{
public:
void lay()
{
????????cout?<< "床可以躺着" <<?endl;
}
};
class SofaBed:public?Sofa,public?Bed
{
public:
};
int main()
{
SofaBed?sb;
????sb.sit();
????sb.lay();
return 0;
}
1.4.2?可能出现的问题
1.4.2.1?问题1-重名问题
当多个基类具有重名成员时,编译器在调用的过程中会出现二义性。
解决方式:使用基类的类名::方式调用。
#include?<iostream>
using namespace std;
class Sofa
{
public:
void sit()
{
????????cout?<< "沙发可以坐着" <<?endl;
}
void test()
{
????????cout?<< "打扫沙发" <<?endl;
}
};
class Bed
{
public:
void lay()
{
????????cout?<< "床可以躺着" <<?endl;
}
void test()
{
????????cout?<< "打扫床" <<?endl;
}
};
class SofaBed:public?Sofa,public?Bed
{
public:
};
int main()
{
SofaBed?sb;
????sb.sit();
????sb.lay();
//????sb.test();?//?错误二义性,两个类中都继承到相同的成员函数名
????sb.Bed::test();//作用域指定
????sb.Sofa::test();
return 0;
}
1.4.2.2?问题2-菱形继承
当一个派生类有多个基类,且这些基类又有同一个基类,就会出现二义性问题,这种情况也被称为菱形继承(钻石)继承。
1)作用域加类名解决
#include?<iostream>
using namespace?std;
class Furniture
{
public:
void func()
{
????????cout?<< "家具厂里有家具" <<?endl;
}
};
class Sofa:public?Furniture
{
public:
};
class Bed:public?Furniture
{
public:
};
class SofaBed:public?Sofa,public?Bed
{
public:
};
int main()
{
????SofaBed?sb;
//????sb.func();
//作用域加类名完成
????sb.Sofa::func();
????sb.Bed::func();
return 0;
}
2)使用虚继承virtual
虚继承解决二义性是通过虚基类指针和虚基类表实现的。在下面代码中Sofa和Bed类会创建一个虚基类指针和虚基类表。所有同类型对象共用一张虚基类表,每个对象内部增加一个隐藏的虚基类指针成员变量,这个虚基类指针指向虚基类表。当Sofa和Bed类作为基类有了派生类SofaBed时,SofaBed对象中也会有隐藏的虚基类指针,但是SofaBed没有自己的虚基类表。在调用Furniture的成员时,SofaBed对象会通过虚基类指针找到对应的虚基类表,通过查表避免二义性。
#include?<iostream>
using namespace?std;
class Furniture
{
public:
void func()
{
????????cout?<< "家具厂里有家具" <<?endl;
}
};
//?虚继承
class Sofa:virtual?public?Furniture
{
public:
};
class Bed:virtual?public?Furniture
{
public:
};
class SofaBed:public?Sofa,public?Bed
{
public:
};
int main()
{
????SofaBed?sb;
????sb.func();
return 0;
}
练习多重继承:
定义学生类,有姓名,学号,性别,年龄等私有成员变量,有构造函数,有打印信息的成员函数。
要求通过构造函数可以给属性赋予初始值。
定义大学生类,继承自学生类,大学生有专业名、成绩的私有成员变量,还有是否获得奖学金的成员函数(成绩为判断依据)。隐藏基类打印信息的成员函数,新的打印信息的成员函数也要能打印姓名、学号、性别、年龄信息。
要求通过构造函数可以给属性赋予初始值。
再定义研究生类,继承自大学生类,有导师姓名和工资的私有成员变量,有打印工资这个成员函数。
要求通过构造函数可以给属性赋予初始值。
#include?<iostream>
using namespace?std;
class Student
{
private:
????string?name;
int?stdnum;
????string?sex;
int?age;
public:
//有参构造
Student(string?name,int?stdnum,string?sex,int?age):name(name),stdnum(stdnum),sex(sex),age(age){}
void show_student()
{
????????cout<<"姓名:"<<name<<endl<<"学号:"<<stdnum<<endl<<"性别:"<<sex<<endl<<"年龄?"<<age<<endl;
}
????string?get_name()
{
return?name;
}
};
class Undergraduate:public?Student
{
private:
????string?major;
float?grade;
public:
//透传构造
Undergraduate(string?name,int?stdnum,string?sex,int?age,string?major,float?grade):Student(name,stdnum,sex,age),major(major),grade(grade){}
//奖学金判断
void jodge_scholarship()
{
if(this->grade>=95)
{
????????????cout<<Student::get_name()<<"获得奖学金"<<endl;
}
else
{
????????????cout<<Student::get_name()<<"未获得奖学金"<<endl;
}
}
//打印大学生信息
void show_undergraduate()
{
Student::show_student();
????????cout<<"专业:"<<major<<endl<<"成绩:"<<grade<<endl;
}
};
class Graduate:public?Undergraduate
{
private:
????string?totor;//导师
int?pay;
public:
//构造
Graduate(string?name,int?stdnum,string?sex,int?age,string?major,float?grade,string?totor,int?pay):Undergraduate(name,stdnum,sex,age,major,grade),totor(totor),pay(pay){}
void get_pay()
{
????????cout<<"薪资待遇:"<<pay<<endl;
}
void show_graduate()
{
????????cout<<"导师:"<<totor<<endl;
????????cout<<"薪资:"<<pay<<endl;
}
};
int main()
{
????cout<<"学生类"<<endl;
????Student?std1("张三",123,"男",18);
????std1.show_student();
????cout<<"大学生类"<<endl;
????Undergraduate?std2("张三",123,"男",18,"嵌入式",99);
????std2.show_undergraduate();
????std2.jodge_scholarship();
????cout<<"研究生类"<<endl;
????Graduate?std3("张三",123,"男",18,"嵌入式",99,"沈子",9999);
????std3.show_student();
????std3.show_graduate();
????std3.get_pay();
return 0;
}
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!