甘肃省建设厅注册中心网站首页,企业策划案例,网络营销中心,免费企业建站源代码P. S.#xff1a;以下代码均在VS2019环境下测试#xff0c;不代表所有编译器均可通过。 P. S.#xff1a;测试代码均未展示头文件stdio.h的声明#xff0c;使用时请自行添加。 博主主页#xff1a;Yan. yan. … P. S.以下代码均在VS2019环境下测试不代表所有编译器均可通过。 P. S.测试代码均未展示头文件stdio.h的声明使用时请自行添加。 博主主页Yan. yan. C语言专栏 数据结构专栏 力扣牛客经典题目专栏 C专栏 文章目录 继承的概念与定义1、继承的概念2、继承的定义2.1、继承的语法形式2.2、继承中类的叫法2.3、继承后的子类成员访问权限 基类与派生类的赋值转换1、派生类对象赋值给基类对象2、派生类对象的引用赋值给基类对象3、派生类对象的指针赋值给基类对象4、基类指针赋值给派生类指针 继承的作用域1、同名变量2、同名函数 派生类中的默认成员1、对象的构造和析构遵循特定的顺序2、派生类构造函数调用基类构造函数3、析构函数的特殊处理4、拷贝构造与赋值重载必须调用基类的拷贝构造与赋值重载完成对基类的初始化 继承的概念与定义
1、继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段它允许程序员在保持原有类特性的基础上进行扩展增加功能这样产生新的类称派生类。继承呈现了面向对象程序设计的层次结构体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用继承是类设计层次的复用。 举一个简单的例子 一个人他具有年龄姓名等个人信息然后我们可以将这些信息整合为一个Person类如果说我们还想要定义一个Student的类这些学生当然也是人因此我们可以复用Person这个类然后再添加一些其他的信息例如学号之类的。 2、继承的定义
2.1、继承的语法形式 继承的定义时是通过在子类的声明中使用基类 并加上冒号“” 和继承方式(public \ protected \ private)来实现的。
class Person
{//.....基类
};// 子 继承方式 父
class Student : public Person
{//....派生类
};例
class Person
{
public:void print(){cout _name endl;cout _address endl;cout _age endl;}
private:string _name 张三 ; // 姓名string _address 河北 ;// 地址int _age 18 ;// 年龄
};// 子 继承方式 父
class Student : public Person
{
private:string _tel 189 ;// 电话int _id 123321;
};int main()
{Person p;Student s;return 0;
}通过监视窗口可以看出Student类继承了Person类的成员与函数。
2.2、继承中类的叫法
1子类或派生类 这是指继承其他类即父类的类。子类可以使用父类的所有非私有属性和方法同时也可以添加自己的属性和方法或重写父类的方法。 2父类或基类这是指被其他类即子类继承的类。父类提供了通用的属性和方法这些可以被子类继承和使用。
2.3、继承后的子类成员访问权限
不同的继承方式产生的继承效果自然也不一样。 如图 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中但是语法上限制派生类对象不管在类里面还是类外面 都不能去访问它。基类private成员在派生类中是不能被访问如果基类成员不想在类外直接被访问但需要在派生类中能访问就定义为protected。可以看出保护成员限定符是因继承才出现的。实际上面的表格我们进行一下总结会发现基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 Min(成员在基类的访问限定符继承方式)public protected private。使用关键字class时默认的继承方式是private使用struct时默认的继承方式是public不过 最好显示的写出继承方式。在实际运用中一般使用都是public继承几乎很少使用protetced/private继承也不提倡使用protetced/private继承因为protetced/private继承下来的成员都只能在派生类的类里面使用实际中扩展维护性不强。 基类与派生类的赋值转换 在面向对象编程中基类和派生类之间的赋值转换涉及到对象的类型兼容性和多态性。
1、派生类对象赋值给基类对象 派生类对象可以赋值给基类的对象。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。 相反基类成员无法赋值给派生类成员因为有些成员派生类有而基类没有。
class Person
{
public:Person(string name Kana, int age 18, double height 1.50) :_name(name),_age(age),_height(height){// ...}void Print() const{cout _name endl;cout _age endl;cout _height endl;}private:string _name; // 姓名 int _age; // 年龄 double _height; // 身高
};class Student : public Person
{
public:Student(string name Kana, int age 18, double height 1.50, int id 233333, int grade 10): Person(name, age, height), _id(id), _grade(grade) {}void Print() const{// 首先调用基类的 Print 方法 Person::Print();// 然后打印学生特有的属性 cout ID: _id endl;cout Grade: _grade endl;}private:int _id; // 学号 int _grade; // 年级
};int main()
{Student s;s.Print();// 尝试将 Student 对象切片为 Person 对象不推荐因为会丢失信息 Person p s;p.Print(); // 仅打印 Person 的信息姓名、年龄、身高 return 0;
}2、派生类对象的引用赋值给基类对象 我们可以将一个派生类对象的引用赋值给一个基类类型的引用而不需要const修饰符。
class Person
{
public:void Print(){cout Person Print() endl;}
};class Student :public Person
{
public:void Print(){cout Student Print() endl;}
};int main()
{Student s;Person p s;p.Print();return 0;
}3、派生类对象的指针赋值给基类对象 派生类对象的指针可以赋值给基类对象的指针。
class Person
{
public:void Print() const{cout Person: Name _name , Age _age endl;}Person(string name, int age) : _name(name), _age(age) {}private:string _name;int _age;
};class Student : public Person
{
public:void Print() const{cout Student: ID _id , Grade _grade endl;}Student(string name, int age, int id, int grade) : Person(name, age), _id(id), _grade(grade) {}private:int _id; // 学号 int _grade; // 年级
};int main()
{Student student(Kana, 18, 12345, 10);// 将Student对象的指针赋值给Person对象的指针 Person* p student;p-Print(); // 如果要访问Student特有的成员需要使用Student类型的指针或引用 Student* s student;s-Print(); return 0;
}4、基类指针赋值给派生类指针 在C中将基类指针直接强制转换为派生类指针是一种危险的做法通常是不被推荐的因为它违反了类型安全的原则并且可能导致未定义行为包括越界访问或访问无效内存。 Person p;Student *s (Student*) p; // right1.派生类对象可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。 2.基类对象不能赋值给派生类对象。 3.基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。 继承的作用域 继承的作用域决定了从基类继承到派生类的成员包括变量和方法的访问权限。 在C的继承体系中 派生类和基类都各自拥有独立的作用域当派生类和基类中定义了相同的变量包括成员和函数时派生类的成员会“隐藏”或者“重定义”基类中的同名成员这意味着在派生类的作用域内直接访问该同名成员将引用派生类的成员而不是基类的成员。
1、同名变量
class Person
{
protected:int _id 123;int _age 18;
};class Student : public Person
{
public:void print(){cout _id endl;cout _age endl;cout _num endl;}
private:int _age 20;int _num 3;
};int main()
{Student s;s.print();return 0;
}派生类和基类中都含有名为_age的成员变量打印结果如下 如果想要打印基类中的_age则需要使用 :: 限定符
2、同名函数
class A
{
public:void fun(){cout fun() endl;}
};class B : public A
{
public:void fun(int i){A::fun();cout fun(int i)- i endl;}
};int main()
{B b;b.fun(1);
};B 中的 fun 和 A 中的 fun 构成隐藏成员函数满足函数名相同就构成隐藏。
由于函数重载针对的是同一个作用域的函数而基类与派生类直接作用域不同。因此不是函数重载。
同样的如果需要访问其他作用域的函数我们需要使用 :: 操作符
派生类中的默认成员
我们知道在类中有6个默认成员函数如果不显示定义编译会自动生成。
那么在派生类中这些成员函数如何生成
1、对象的构造和析构遵循特定的顺序
对象的构造和析构遵循特定的顺序以确保对象的正确初始化和清理。
构造函数调用顺序 (1)创建派生类对象时从最顶层的基类开始逐层向下调用构造函数直到派生类。 (2)接着按照派生类中成员变量的声明顺序初始化成员变量若成员是对象则调用其构造函数。 (3)最后执行派生类构造函数体中的代码。 析构函数调用顺序 (1)销毁派生类对象时首先调用派生类的析构函数。 (2)然后按照成员变量声明的逆序调用成员变量的析构函数若成员是对象。 (3)最后从最顶层的基类开始逐层向上调用析构函数直到派生类的基类。 class Person
{
public:Person(string name Kana): _name(name){cout Person() endl;}~Person(){cout ~Person() endl;}
private:string _name; // 姓名
};
class Student : public Person
{
public:Student(){cout Student() endl;}~Student(){cout ~Student() endl;}
private:int _id;
};int main()
{Student s;return 0;
}2、派生类构造函数调用基类构造函数 (1)派生类的构造函数必须调用基类的构造函数来初始化基类的成员。 (2)如果基类没有默认的构造函数则必须在派生类构造函数的初始化列表中显式调用基类的一个构造函数。 class Person
{
public:// Person类的构造函数用于初始化名字Person(const char* name) : _name(name) {// ...}// Person类的拷贝构造函数Person(const Person p) : _name(p._name) {}
private:string _name;
};class Student : public Person
{
public:// Student类的构造函数接收学号和名字Student(int id, const char* name) : _id(id), Person(name) {// ...}// Student类的默认构造函数Student() : Person(Default Student Name), _id(0) {// ...}
private:int _id;
};3、析构函数的特殊处理
因为后续一些场景析构函数需要构成重写重写的条件之一是函数名相同。那么编译器会对析构函数名进行特殊处理处理成destructor()。
4、拷贝构造与赋值重载必须调用基类的拷贝构造与赋值重载完成对基类的初始化
class Person
{
public:// 默认构造函数 Person(const string name) : _name(name) {// ...}// 拷贝构造函数 Person(const Person p) : _name(p._name) {cout Copy Person( _name ) endl;}// 赋值操作符重载 Person operator(const Person p) {if (this ! p) {_name p._name;cout Assign Person( _name ) endl;}return *this;}// 析构函数 ~Person() {cout ~Person( _name ) endl;}string _name;
};class Student : public Person
{
public:// 构造函数 Student(int num, const string name) : Person(name), _num(num) {cout Student( _num , _name ) endl;}// 拷贝构造函数 Student(const Student s) : Person(s), _num(s._num) {cout Copy Student( _num , _name ) endl;}// 赋值操作符重载 Student operator(const Student s) {if (this ! s) {Person::operator(s); // 调用基类的赋值操作符 _num s._num;}return *this;}// 析构函数 ~Student() {cout ~Student( _num , _name ) endl;}int _num;
};