IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++继承 -> 正文阅读

[C++知识库]C++继承

引言

这篇博客本来在上个月就应该发出来,但是中间由于各种原因,而一拖再拖,今天也终于完成了这篇博客,
各位看官请好好欣赏吧

继承的概念与定义

继承是对代码进行复用,是类设计层次的复用,让我们的子类可以使用父类的代码,减少代码的冗余提高效率

class strudent
{
string name;
string id;
string address;
//...
//独自有的
//学院,专业,宿舍楼
}

class teacher
{
string name;
string id;
string address;
//...
//独自有的
//职称,科目
}

我们会发现这虽然这是两种不同的类,但是有很多信息是重复冗余的
所以我们完全可以定义个父类,来保存共有的信息

如:


class person
{
string name;
string id;
string address;
}
继承person的
class student:public person
{
private:
//学院,专业,宿舍楼
}
class teacher:public person
{
private:
//职称,科目
}

#include<iostream>
using namespace std;
class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
protected:
	string _name = "peter"; // 姓名
	int _age = 18; // 年龄
};
// 继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了Student和
//Teacher复用了Person的成员。下面我们使用监视窗口查看Student和Teacher对象,可以看到变量的复用。
//调用Print可以看到成员函数的复用。
//Person里的成员变量和成员函数都被包含了进去
class Student : public Person
{
protected:
		int _stuid; // 学号
};
class Teacher : public Person
{
protected:
	int _jobid; // 工号
};
int main()
{
	Student s;
	Teacher t;
	s.Print();//因为没有二义性,所以这里的print就是父类的
	t.Print();//如果有二义性,只会是用子类的,否则要显示调用父类
	return 0;
}

继承的模板

class student :public person

继承的定义

class默认是公有继承(但是我们最好把继承方式写上)

  1. 格式
    class student :public person
    student是派生类,public是继承方式,person是基类
  2. 继承关系与访问限定符
    继承方式
    :public继承,protect继承,private继承
    访问限定符
    :public访问,protect访问,private访问

在这里插入图片描述

  1. 基类的private成员在派生类中无论以什么方式继承都是不可以见到的

数据继承下来了,在那个地方但是用不上,不可以见的意思是
:继承下来之后,不管是在类的里面还是在类的外面都不可以访问他
继承:简单粗暴,直接全部继承下来,但是无论如何都永不上这个成员的

不想给子类用就定义成private,想给子类用就定义成public或者protected

  1. 取访问限定符和访问方式中的小的那一个
    public>protect>private

  2. public和protect和private的区别
    public想让别人可以直接使用
    protect不想要让别人直接使用,但是想要让子类能够使用
    private不想让任何人使用,包括子类

保护和私有在父类中没有区别

4.在实际使用的过程中,一般都是public继承,几乎很少private和protect,不提倡private和protect继承,
在类里面基本都使用protect和public,几乎不使用private

常见的继承
父类成员:公有和保护
子类继承方式:公有继承

基类和派生类对象赋值转换

(赋值兼容规则)
我们以前学过,如果是同一种类型就可以直接进行赋值,如果不是同一种类型可以实现显示类型访问,

如果我们想要子类给父类

class person
{
protected:
	string _sex;
	string _name;
	int _age;
};

class student : public person
{
public:
	int _no;
};

int main()
{
	person p;
	student s;
	//父类=子类赋值兼任-》切割,切片
	//只有public继承可以
	//把子类里面父类的那一部分切割过去,给父类
	//父类的指针和父类的引用
	//这里不存在类型转换,是语法天然支持的行为
	p=s;
	//父类不能给子类,因为
	person*ptr=&s;//指针只能得到其中的父类的部分
	person& ref=s;//变成子类对象当中父类对象的别名
	return 0;
}

继承中的作用域

  1. 在继承体系中基类和派生类偶有独立的作用域

子类和父类出现同名成员:隐藏/重定义,子类会隐藏父类的同名成员
如果有同一名字的话,并不是构成函数重载(重载是在同一个作用域里面才可以称之为重载)
只要函数名相同就构成隐藏,参数的相同与否无所谓,不影响
最好不用定义同名的成员函数和同名成员


class student : public person
{
public:
	int _no = 1; //ton
	void Print()
	{
		cout << _no; //默认访问自己的
		//现在想要访问父类的
		cout << person::_no << endl; //访问了父亲的
	}
};

int main()
{
	person p;
	student s;
	s.Print();		   //同名变量就近原则,会打印出Student
	s.person::Print(); //指定调用person类域的,这样就可以用了

return 0;
}

派生类的默认成员函数


#include <string>
#include<iostream>
using namespace std;

class Person //父类
{
public:
    Person(const char *name /*="peter"*/) //构造函数
        : _name(name)                     //初始化列表,对值进行初始化
    {
        cout << "person()" << endl;
    }
    Person(const Person &s) //拷贝构造
        : _name(s._name)
    {
        cout << "person(const person& s)" << endl;
    }
    Person &operator=(const Person &s) //赋值相等
    {
        cout << "Person=" << endl;
        if (this != &s)
        {
            _name = s._name;
        }
        return *this;
    }
    ~Person()
    {
        cout << "~Person()" << endl;
    }

protected:
    string _name;
};

class Student : public Person //公有继承,子类
{
public:
    //父类没有默认构造函数,我们自己写
    Student(const char *s = "zhans", int num = 1)//给一个缺省值
        : Person(s) //调用默认构造,父类也是我们调用,不同于析构
          ,
          _num(num)
    {}

    Student(const Student &s)
        //把s当中父类的那一部分取出来
        : Person(s) //我们可以直接传,他会切片,所以不用担心,这是一个天然的部分
                    //这个之后会变成父类当中的别名
          ,
          _num(s._num)
    {
    }

    // s2=s1;
    Student &operator=(Student &s)
    {
        //赋值和拷贝构造类似,只不过他们是已经存在的对象进行赋值
        if (this != &s)
        {
            _num = s._num;

            // operator=(s);//调用父类的赋值运算,所以这里父类切的就是父类的那一部分,没有什么问题
            //上面的操作会无限递归下去,死循环
            Person::operator=(s); //我们指定一下就可以了
        }
    }

    //析构函数的名字,会被统一处理成destructor()(至于为什么会这样,多态的时候我们就会讲解)
    //所以就会被构成隐藏
    #if 0
    //这段代码在vscode里面无法通过
    ~Student()
    {
        //Person::~Person();//这里要指定作用域才可以调用,,
        //我们指定了父类
        //调用父类的
        //delete[] p;
        //我们这里会发现调用两次析构,
        //我们子类先执行
    }
    //子类的析构函数不需要我们去显示调用,因为会在子类析构的时候自动调用父类的析构
    //我们初始化的时候,父类先构造,子类再构造,析构的时候子类先析构,父类再析构
    //所以我们实现子类析构函数的时候,不需要显示调用父类的析构函数,



    #endif

    
private:
    int _num = 1; //这里不是初始化,只是给了一个缺省值
    // string _s="dasd";
    // int *p=new int[10];
};

//派生类的重点4个默认成员函数,我们不写,编译器会默认生成会干些什么呢
//如果我们要写,要做些什么呢

// 我们不写,默认生成的派生类的构造和析构
// a。父类继承下来的 (调用父类的默认构造和析构处理) b。自己的(内置类型和自定义类型成员)(跟普通类是一样的)

//我们不写默认生成的拷贝构造和operator =
// a。父类继承下来的(调用父类的拷贝构造和operator=),完成值拷贝和浅拷贝    b。自己的(内置类型和自定义类型成员)(跟普通类是一样的)
/*

总结:
继承下来的调用父类处理,自己的按普通类基本规则
如果要我们自己处理呢,该如何去写,什么情况要自己写

父类的成员调用父类的对应构造,拷贝构造,operator=和析构处理
自己的成员按需求处理(普通类处理)

1.父类没有默认构造,我们要自己写
2.如果自己的子类有资源要进行释放,就需要我们自己写析构
3.如果子类有存在深拷贝问题,就需要我们自己实现拷贝构造和赋值



4.友元关系不能继承,父类的朋友,不一定是子类的朋友,所以不能访问他的私有成员,
5.继承与静态成员,静态成员无论继承怎么样,静态成员都是一样的,static count,继承都是一样样的,地址也是一样的,只有一份,


*/
int main()
{
    //一个类如果我们不写,我们会不会自动生成一个
    Student s;
    // Student s1(s); //拷贝构造
    // Student s3("jack", 18);
    // s1 = s3;
    return 0;
}

复杂的菱形继承与

多继承就是一个坑,

单继承:一个类只有一个直接父类

多继承,一个类有多个直接父类
在这里插入图片描述
菱形继承是多继承的一种特殊情况,多继承没问题,只不过要避免菱形继承
在这里插入图片描述
存在数据冗余和二义性
里面有两份person

先继承前面的,再继承后面的

在这里插入图片描述
A一般叫做虚基类
再D里面,A放到一个公共的位置,那么有时候B需要找A,C 需要找A,就需要通过虚基表中的偏移量进行计算

class A
{
public:
    int _a;
};

class B :virtual public A
{
public:
    int _b;
};

class C :virtual public A
{
public:
    int _c;
};

class D : public B, public C
{
public:
    int _d;
};
int main()
{
    D d;
    d.B::_a = 1;//虚继承就弄成一起的地方了,
    d.C::_a = 2;
    d._b = 4;
    d._c = 5;
    d._d = 6;

    return 0;
}

我们尽量不要定义出菱形继承,虚继承

继承的总结和反思

c++的缺陷

  1. 没有垃圾回收器
  2. 多继承

继承和组合

//继承
//继承是为了复用代码
class a
{}
class b:public a
{}
//组合

class c
{
	int_c 
}

class d 
{
C _obj;//这样也是一种复用,开一个C类型的变量
int _d 
}
  1. public继承是一种is_a 的关系,也就是说每个派生类的对象都是一种基类对象,b就是一个a

Student 和Person的关系就适合用继承 ,Student is Person

  1. 组合是一种has_a的关系,b组合了A,那么就是说b里面有一个a对象

眼睛和头的关系,就适合用组合,头上有眼睛
车和轮胎的关系,车上有轮胎

  1. 如果它既是is_a 又可以是has_a,那么优先使用has_a(组合)

继承是白箱服用(能看到它的实现细节),组合是黑箱服用(看不见它的实现细节),所以黑盒测试
除了父类的私有成员,其他子类都是可以进行使用的,这样会破坏基类的封装,子类可能会调用父类的公有成员和保护成员,D只能用C 的公有,不能

类和类之间:低耦合,高内聚(跟我没关系的东西,不要设计进来),
类和类之间 的依赖程序低,方便我们维护,所以组合的耦合程度低,
组合下来,保护的成员也是无法使用的

组合关系之间依赖关系小,关联程度低,修改很方便,可维护性强

切割和切片就是继承的好处
而多态就是建立在继承的基础之上,

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-08 07:51:17  更:2022-05-08 07:52:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/21 1:24:57-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码