c++_09_继承

1  继承

        C++的继承是弱继承 

        继承的语法:

                class  子类 : 继承方式1  基类1,  继承方式2  基类2,  ...  { ... };

        继承方式:

                共有继承  public

                保护继承  protected

                私有继承  private  

2  继承的基本属性(3种继承方式均有)

        继承所要达到的目的:

                子类对象包含基类子对象 

                子类内部可以直接访问基类的所有非私有成员

// derived.cpp 继承最基本的特点:
// (1) 子类对象 内部包含 基类(子)对象
// (2) 子类内部可以访问 基类的 非私有(公有/保护)成员(变量/函数)
#include <iostream>
using namespace std;
class Base {
public:int m_a;void foo() { cout << "Base::foo" << endl; }
protected:int m_b;void bar() { cout << "Base::bar" << endl; }
private:int m_c;void hum() { cout << "Base::hum" << endl; }
};
//class Derived : public Base {
//class Derived : protected Base {
class Derived : private Base {
public:void fun() {m_a = 100; // okfoo();     // okm_b = 200; // okbar();     // ok, 以上四行代码证明子类内部可以直接访问基类的公有和保护成员
//      m_c = 300; // error
//      hum();     // error,以上两行代码证明子类内部不可以直接访问基类的私有成员}
private:int m_d;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {Base b; // 基类对象-->|m_a m_b m_c|cout << "基类对象b的大小:" << sizeof(b) << endl; // 12Derived d; // 子类对象--> |基类子对象|m_d| --> |m_a m_b m_c|m_d|cout << "子类对象d的大小:" << sizeof(d) << endl; // 16return 0;
}

        继承的本质:

                基类的非私有成员在子类中仅为可见,而非子类拥有。 (可见表)(软继承)

        注意:关于继承,切记不要理解为基类的成员变为子类的成员。继承不会改变类成员的作用域,基类的成员永远都是基类的成员,并不会因为继承而变成子类的成员。

        尽管基类的公有和保护成员在子类中直接可见,但仍然可以在子类中重新定义这些名字,子类中的名字会隐藏所有基类中的同名定义(定义表隐藏可见表)。

        如果需要在子类内部访问一个基类中定义却被子类标识符所隐藏的名字,可以借助作用域限定操作符::实现。

        因为作用域的不同,分别在子类和基类中定义的同名成员函数(包括静态成员函数),并不构成重载关系,相反是一种隐藏关系。

// hide.cpp 基类和子类内部的同名定义 相互之间为隐藏关系 
#include <iostream>
using namespace std;
class Base {
public:int m_a;void foo() { cout << "Base::foo" << endl; }
protected:int m_b;void bar() { cout << "Base::bar" << endl; }
private:int m_c;void hum() { cout << "Base::hum" << endl; }
};
class Derived : public Base {
//class Derived : protected Base {
//class Derived : private Base {
public:void fun() {Base::foo(); // 子类的foo将基类的foo隐藏,但可以利用作用域限定符强制调用基类的foobar(); // 子类的bar将基类的bar隐藏}
private:int m_d;void foo() { cout << "Derived::foo" << endl; }void bar() { cout << "Derived::bar" << endl; }
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {Derived d;d.fun();return 0;
}

3  三种继承方式的差别(不是重点)

        基类中的公有、保护、私有成员,在子类中将对这些基类成员的访问控制限定进行重新标记:

                          

       工作中主要使用public继承,其他极少使用。

        在类的外部,通过 子类对象   访问   基类的成员时,需要考虑因继承方式对访问控制限定的影响:

// public.cpp 公有继承
#include <iostream>
using namespace std;
class Base {
public: // 原始标记int m_a;void foo() { cout << "Base::foo" << endl; }
protected: // 原始标记int m_b;void bar() { cout << "Base::bar" << endl; }
private: // 原始标记int m_c;void hum() { cout << "Base::hum" << endl; }
};
class Derived : public Base {// 子类将对基类的成员重新标记访控限定 m_a/foo是public m_b/bar是protected // m_c/hum是private
public:void fun() { // 子类内部访问基类的成员,编译器需要查看这些基类成员在基类中的原始标记m_a = 100; // okfoo();     // okm_b = 200; // okbar();     // ok, 以上四行代码证明子类内部可以直接访问基类的公有和保护成员
//      m_c = 300; // error
//      hum();     // error,以上两行代码证明子类内部不可以直接访问基类的私有成员}
private:int m_d;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {Derived d; // 利用子类对象在类外访问基类的成员,编译器需要查看这些成员在子类中的重新标记d.m_a = 1000; // okd.foo();      // ok
//    d.m_b = 2000; // error
//    d.bar();      // error
//    d.m_c = 3000; // error
//    d.hum();      // errorreturn 0;
}
// protected.cpp 保护继承
#include <iostream>
using namespace std;
class Base {
public: // 原始标记int m_a;void foo() { cout << "Base::foo" << endl; }
protected: // 原始标记int m_b;void bar() { cout << "Base::bar" << endl; }
private: // 原始标记int m_c;void hum() { cout << "Base::hum" << endl; }
};
class Derived : protected Base {// 子类将对基类的成员重新标记访控限定 m_a/foo是protected   m_b/bar是protected//m_c/hum是private
public:void fun() { // 子类内部访问基类的成员,编译器需要查看这些基类成员在基类中的原始标记m_a = 100; // okfoo();     // okm_b = 200; // okbar();     // ok, 以上四行代码证明子类内部可以直接访问基类的公有和保护成员
//      m_c = 300; // error
//      hum();     // error,以上两行代码证明子类内部不可以直接访问基类的私有成员}
private:int m_d;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {Derived d; // 利用子类对象在类外访问基类的成员,编译器需要查看这些成员在子类中的重新标记
//    d.m_a = 1000; // error
//    d.foo();      // error
//    d.m_b = 2000; // error
//    d.bar();      // error
//    d.m_c = 3000; // error
//    d.hum();      // errorreturn 0;
}
// private.cpp 私有继承
#include <iostream>
using namespace std;
class Base {
public: // 原始标记int m_a;void foo() { cout << "Base::foo" << endl; }
protected: // 原始标记int m_b;void bar() { cout << "Base::bar" << endl; }
private: // 原始标记int m_c;void hum() { cout << "Base::hum" << endl; }
};
class Derived : private Base {// 子类将对基类的成员重新标记访控限定 m_a/foo是private   m_b/bar是private   //m_c/hum是private
public:void fun() { // 子类内部访问基类的成员,编译器需要查看这些基类成员在基类中的原始标记m_a = 100; // okfoo();     // okm_b = 200; // okbar();     // ok, 以上四行代码证明子类内部可以直接访问基类的公有和保护成员
//      m_c = 300; // error
//      hum();     // error,以上两行代码证明子类内部不可以直接访问基类的私有成员}
private:int m_d;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {Derived d; // 利用子类对象在类外访问基类的成员,编译器需要查看这些成员在子类中的重新标记
//    d.m_a = 1000; // error
//    d.foo();      // error
//    d.m_b = 2000; // error
//    d.bar();      // error
//    d.m_c = 3000; // error
//    d.hum();      // errorreturn 0;
}

 

 

 

4  公有继承独有的特点

4.1  子类对象在类外可以访问基类公有成员

                如果被子类同名标识符隐藏,可借助作用域限定符::指定访问基类的公有成员。

// phs.cpp 公有继承独有特点:
// (1)只有在公有继承下,子类对象在类外可以访问基类的公有成员(其他继承不可以)
#include <iostream>
using namespace std;
class Base {
public: int m_a;void foo() { cout << "Base::foo" << endl; }
protected: int m_b;void bar() { cout << "Base::bar" << endl; }
private: int m_c;void hum() { cout << "Base::hum" << endl; }
};
class Derived : public Base {
public:void foo() { cout << "Derived::foo" << endl; }
private:int m_d;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {Derived d; d.m_a = 1000; // okd.foo();      // ok,只有在公有继承下,子类对象在类外可以访问基类的公有成员d.Base::foo();// okreturn 0;
}

 

4.2  子类类型的指针(引用) 和 基类类型的指针(引用)可以进行转换

        子类类型的指针 能 隐式转换为基类类型的指针 

        子类类型的引用 能 隐式转换为基类类型的引用

         (编译器认为访问范围缩小是安全的):

                                        

                                        class  Human { ... };

                                        class  Student : public Human { ... };

                                        Student s;

                                        Human*  ph = &s;  // 指针  访问范围缩小 

                                        Human&  rh = s;   //  引用  访问范围缩小

        基类类型的指针 能 隐式转换为子类类型的指针

        基类类型的引用 能 隐式转换为子类类型的引用

        (编译器认为访问范围扩大是危险的):

                                        class  Human { ... };

                                        class  Student : public Human { ... };

                                        Human h;

                                        Student*  ps = static_cast<Student*>(&h); // 指针  访问范围扩大 

                                        Student&  rs = static_cast<Student&>(h);  // 引用  访问范围扩大 

                                        

        编译器对类型安全的检测仅仅基于指针/引用本身

        基类指针/引用的实际目标,究竟是不是子类对象,完全由程序员自己判断:

                                        class  Human { ... };

                                        class  Student : public Human { ... };

                                        Student  s;

                                        Human*  ph = &s;

                                        Human&  rh = s;

                                        Student  ps = static_cast<Student*> (ph); // 访问范围扩大,但安全合理

                                        Student  rs = static_cast<Student&> (rh); // 访问范围扩大,但安全合理

                                        

#include <iostream>
using namespace std;
#pragma pack(1)
class Human {
public:int m_age;string m_name;
};
class Student : public Human {
public:int m_no;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {Human h; // |m_age m_name|cout << "基类对象h的大小:" << sizeof(h) << endl; // 36Student s; // |基类子对象|m_no| --> |m_age m_name|m_no|cout << "子类对象s的大小:" << sizeof(s) << endl; // 40Human* ph = &s; // Student*-->Human* (子类型指针-->基类型指针)Human& rh = s;  // 以上两种转换编译器认为访问范围缩小,是安全的//    Student* ps = static_cast<Student*>(&h); // Human*-->Student* // (基类型指针-->子类型指针)
//    Student& rs = static_cast<Student&>(h);  // 以上两种转换编译器认为访问范围扩大,是危险的,通过强转虽然可以成功,但风险依然存在// (极其不建议大家这么做)Student* ps = static_cast<Student*>(ph);//Human*-->Student*(基类型指针-->子类型指针)Student& rs = static_cast<Student&>(rh);//Human&-->Student&(基类型引用-->子类型应用)// 以人类高智商判断以上两个转换毫无风险(极其建议大家这么做) // 编译器只是简单粗暴的根据类型来判断是否存在风险return 0;
}

5  子类的构造和析构

5.1  子类的构造函数

        1)子类没有定义构造函数时:

                        编译器会为子类提供默认的无参构造函数 

                        定义完基类子对象后,调用基类无参构造函数 

        2)子类定义构造函数,但未在初始化表中指明基类部分的构造方式时:

                        定义完基类子对象后,调用基类无参构造函数 

        3)子类定义构造函数,并且在初始化表中指明基类部分的构造方式时:

                        定义完基类子对象后,调用指明的基类的构造函数

        子类对象的构造过程:

        1)构造基类子对象   2)构造子类的成员变量   3)执行自己在子类构造函数中书写的代码

        

        阻断继承:

                子类的构造函数  无论如何  都会调用基类的构造函数。

                若把基类的构造函数定为私有,则该类的子类就永远不能被实例化为对象。

                在C++中可以用这种方法阻断一个类被扩展。

5.2  子类的析构函数

        1)子类没有定义析构函数时:

                编译器将提供一个默认的析构函数,析构完子类所有的成员变量后,

                会自动调用其基类的析构函数

        2)子类定义了析构函数时:

                子类的析构函数在执行完自身析构代码,并析构完所有的成员变量后,

                会自动调用其基类的析构函数

        

        子类对象的析构过程:

        1)执行自己在子类析构函数中书写的代码    2)析构子类的成员变量    3)析构基类子对象

         

5.3  子类的拷贝构造函数

        1)子类并没有定义拷贝构造函数时:

                编译器会为子类提供默认的拷贝构造函数,

                定义完基类子对象后,调用基类的拷贝数构造函 。

        2)子类定义了拷贝构造函数,但没有在初始化表中指明其基类部分的构造方式时:

                定义完基类子对象后,调用基类的无参构造函数。

        3)子类定义了拷贝构造函数,且初始化表中指明了其基类部分以拷贝方式构造时:

                定义完基类子对象后,调用基类的拷贝构造函数。

5.4  子类的拷贝赋值函数

        1)子类没有定义拷贝赋值函数时:

                编译器为子类提供的缺省拷贝赋值函数内部,会自动调用基类的拷贝赋值函数

                复制该子类对象中的基类子对象。

        2)子类定义了拷贝赋值函数,但没有  显示调用  基类的拷贝赋值函数时:

                编译器不会塞任何操作,子类对象中的基类子对象得不到复制

        3)子类定义了拷贝赋值函数,同时  显示调用  了基类的拷贝赋值函数时:

                子类对象中的基类子对象得到复制

// ccons.cpp 子类的构造函数 和 析构函数
#include <iostream>
using namespace std;class Human {
public:Human( int age=0, const char* name="无名" ) : m_age(age),m_name(name) {//【int m_age=age;】定义m_age,初值为age//【string m_name(name);】定义m_name,利用m_name.string(name)cout << "Human类缺省构造函数被调用" << endl;}Human( const Human& that ) : m_age(that.m_age), m_name(that.m_name) { //【int m_age=that.m_age;】定义m_age,初值为that.m_age//【string m_name(that.m_name);】定义m_name,利用m_name.string(that.m_name)cout << "Human类拷贝构造函数被调用" << endl;}Human& operator=( const Human& that ) {// 编译器不会再拷贝赋值函数中塞任何操作cout << "Human类的拷贝赋值函数被调用" << endl;this->m_age = that.m_age;this->m_name = that.m_name; // this->m_name.operator=(that.m_name)-->string类的拷贝赋值函数return *this;}~Human() {cout << "Human类的析构函数被调用" << endl;// 对于基本类型成员变量m_age,什么都不做// 对于类类型成员变量m_name,利用 m_name.~string()// 释放 m_age/m_name 本身所占内存空间}void getinfo( ) {cout << "姓名: " << m_name << ", 年龄: " << m_age;}
private:int m_age; // 基本类型的成员变量string m_name; // 类类型的成员变量
};
class Student : public Human {
public:void getinfo( ) {Human::getinfo();cout << ", 成绩:" << m_score << ", 评语:" << m_remark << endl;}
//  如果子类没有提供任何构造函数,编译器将提供一个无参的构造函数
/*  Student() {【Human();】定义基类子对象,利用 基类子对象.Human()【float m_score;】【string m_remark;】}*/Student( int age=0, const char* name="无名", float score=0.0, const char* remark="没有"): Human(age,name), m_score(score),m_remark(remark) {//【Human(age,name);】定义基类子对象,利用 基类子对象.Human(age,name)//【float m_score=score;】//【string m_remark(remark);】cout << "Student类的缺省构造函数被调用" << endl;}
//  如果子类没有提供析构函数,编译器将提供一个默认的析构函数
/*  ~Student() {对于类类型m_remark,利用m_remark.~string()对于基类子对象,利用基类子对象.~Human()释放 m_score/m_remark/基类子对象 本身所占内存空间}*/~Student() {cout << "Student类的析构函数被调用" << endl;// 对于类类型m_remark,利用m_remark.~string()// 对于基类子对象,利用基类子对象.~Human()// 释放 m_score/m_remark/基类子对象 本身所占内存空间}
//  如果子类没有提供拷贝构造函数,编译器将提供一个默认的拷贝构造函数
/*  Student( const Student& that ) {【Human(that);】定义基类子对象,利用 基类子对象.Human(that)-->Human类的拷贝构造函数【float m_score=that.m_score;】【string m_remark=that.m_remark;】}*/Student( const Student& that ) : Human(that), m_score(that.m_score), m_remark(that.m_remark) {//【Human(that);】定义基类子对象,利用 基类子对象.Human(that)-->Human类的拷贝构造函数//【float m_score=that.m_score;】//【string m_remark=that.m_remark;】cout << "Student类的拷贝构造函数被调用" << endl;}
//  如果子类没有提供拷贝赋值函数,编译器将提供一个默认的拷贝赋值函数
/*  Student& operator=( const Student& that ) {Human& rh = *this;rh = that; // rh.operator=(that)-->Human类的拷贝赋值函数this->m_score = that.m_score;this->m_remark = that.m_remark;return *this;}*/Student& operator=( const Student& that ) {// 编译器不会再拷贝赋值函数中塞任何操作cout << "Student类的拷贝赋值函数被调用" << endl;Human& rh = *this;rh = that; // rh.operator=(that)-->Human类的拷贝赋值函数this->m_score = that.m_score;this->m_remark = that.m_remark;return *this;}
private:float m_score;string m_remark;
};
// 模拟类的设计者(类库、别人设计的类、自己设计的类)
// --------------------------------
// 模拟用户(使用类的人)
int main( void ) {cout << "-----------------s1对象的创建信息-----------------------" << endl;Student s1(22,"张飞",88.5,"良好"); // 定义s1,利用s1.Student(22,"张飞",88.5,"良好")s1.getinfo();cout << "-----------------s2对象的创建信息-----------------------" << endl;Student s2 = s1; //(s1); 定义s2,利用s2.Student(s1)s2.getinfo();cout << "-----------------s3对象的创建信息-----------------------" << endl;Student s3;cout << "s3被赋值前--";s3.getinfo();s3 = s2; // s3.operator=(s2)cout << "s3被赋值后--";s3.getinfo();cout << "-----------------main will be over----------------------" << endl;return 0;
} // s1.~Student()   释放s1本身所占内存空间

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/313892.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TypeScript 之 interface 和 type 的区别

结论&#xff1a; 1、可以声明的数据类型 type 可以修饰任何类型 &#xff08;值类型和引用数据类型&#xff09; interface 只能修饰引用类型 &#xff08;对象、数组、函数&#xff09; //interface 声明对象属性 interface ins {a: string;b?: number; //可选项 }// int…

spring创建与使用

spring创建与使用 创建 Spring 项⽬创建⼀个 Maven 项⽬添加 Spring 框架⽀持添加启动类 存储 Bean 对象创建 Bean将 Bean 注册到容器 获取并使⽤ Bean 对象创建 Spring 上下⽂获取指定的 Bean 对象获取bean对象的方法 使⽤ Bean 总结 创建 Spring 项⽬ 接下来使⽤ Maven ⽅式…

简写英语单词

题目&#xff1a; 思路&#xff1a; 这段代码的主要思路是读取一个字符串&#xff0c;然后将其中每个单词的首字母大写输出。具体来说&#xff0c;程序首先使用 fgets 函数读取一个字符串&#xff0c;然后遍历该字符串中的每个字符。当程序遇到一个字母时&#xff0c;如果此时…

Django开发3

Django开发3 Django开发编辑用户9.靓号管理9.1 表结构9.2 靓号列表9.3 新建靓号9.4 编辑靓号9.5 搜索手机号9.6 分页 10.时间插件11.ModelForm和BootStrap操作 各位小伙伴想要博客相关资料的话关注公众号&#xff1a;chuanyeTry即可领取相关资料&#xff01; Django开发 部门管…

系统功能测试的最好方法

我的新书《Android App开发入门与实战》已于2020年8月由人民邮电出版社出版&#xff0c;欢迎购买。点击进入详情 测试系统功能是软件开发和工程过程中的关键步骤。 它确保系统或软件应用程序按预期运行、满足用户要求并可靠运行。 在这里&#xff0c;我们深入探讨最佳方法&a…

软件测试/测试开发丨Linux进程与线程学习笔记

1、进程 可执行程序的运行态操作系统调度的基本单位线程容器进程本身包含指令、数据等资源 2、 线程 进程中被执行的最小单元cpu 调度的基本单位线程带有指令、数据等资源 3、 进程的生命周期 如执行sleep 100或者执行一个python脚本 创建&#xff1a;created sleep 100 ./…

JavaScript的三种引入的方式

目录 (一).什么是JS1.1JS的特点1.2JS的组成 (二).JS引用的三种方式2.1标签引用&#xff08;或嵌入式)2.2文件引用&#xff08;外链式&#xff09;2.3行内式 (三).JS三种引用方式的优缺点1.行内方式&#xff1a;2.标签引用&#xff08;或嵌入式&#xff09;&#xff1a;3.文件引…

48、激活函数 - 梯度消失和梯度爆炸

简单介绍下梯度消失和梯度爆炸,这个不是重点,但是我觉得有必要再深入了解这个概念,以及很多激活函数为什么是可以防止梯度消失的。 梯度消失和梯度爆炸实际上是在神经网络训练过程中经常会遇到的两类问题,这两类问题都与梯度有关。 什么是梯度 在神经网络训练中,梯度是指…

将学习自动化测试时的医药管理信息系统项目用idea运行

将学习自动化测试时的医药管理信息系统项目用idea运行 背景 学习自动化测试的时候老师的运行方式是把医药管理信息系统项目打包成war包后再放到tomcat的webapp中去运行&#xff0c;于是我想着用idea运行会方便点&#xff0c;现在记录下步骤方便以后查找最开始没有查阅资料&am…

vue脚手架安装

1、安装&#xff1a; npm i vue/cli -g(-g全局安装,全名global) vue --version 查看版本号 2、使用 vue create 项目名称 3、安装选择项 最后一个选N

笔记本安装Centos Stream9系统的详细安装教程-第二版

笔记本安装Centos Stream9系统的详细安装教程-第一版&#xff1a; 笔记本安装Centos Stream9系统的详细安装教程_u盘安装centos 9-CSDN博客文章浏览阅读298次。笔记本安装Centos Stream9系统的详细安装教程 U盘刻录工具_u盘安装centos 9https://blog.csdn.net/wtl1992/article…

UnityShader(四)一个最简单的顶点/片元着色器

目录 顶点/片元着色器的基本结构&#xff1a; 简单的例子 增加模型数据 顶点着色器和片元着色器之间的通信 顶点/片元着色器的基本结构&#xff1a; Shader "MyShaderName"{Properties{//属性}SubShader{//针对显卡A的SubShaderPass{//设置渲染状态和标签//开始C…