c++的继承 《进阶一》 《继承的概念》《继承的定义》《继承基类成员访问方式的变化》《继承的中的作用域》《派生类的默认成员函数》《继承和友元》

我相信,如果当你点开这篇博客,说明你已经进入了c++的部分了,如果是刚刚开始学习继承的同学,我建议还是先看一下,我写的类和对象,先要了解一下,这里是进阶部分,害怕直接看同学有一点看懂。这节内容有一点多,所以我准备分两次写 ,我会把链接放在下面
c++的继承 《进阶二》:https://blog.csdn.net/dpfxaca6/article/details/88784387
类和对象:https://blog.csdn.net/dpfxaca6/article/details/86511762
好了,还是直接进入我的学习继承进阶的部分
一. 《继承的概念及定义》
(1) 继承的概念;
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用
下面看一下代码的实现:

#include<iostream>
#include<string>   //再写的时候,一定要使用这个接口,因为调用了String关键字。
using namespace std;
//父类
class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
protected:
	string _name = "peter"; // 姓名
	int _age = 18; // 年龄
};
//子类
class Student : public Person
{
protected:
	int _stuid; // 学号
};

class Teacher : public Person
{
protected:
	int _jobid; // 工号
};
 //继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分
// Student和Teacher复用了Person的成员。下面我们使用监视窗口查看Student和Teacher对象,
//可以看到变量的复用。调用Print可以看到成员函数的复用。
int main()
{
	Student s;
	Teacher t;
	s.Print();
	t.Print();
	return 0;
}

下面是执行代码:
在这里插入图片描述
这样我们就可以清楚的看出了我们所写的程序,在子类中,可以调用父类的函数复用,这就是我们上面提到过的继承是类设计层次的复用,所以,我们应该还好理解其中的意思。
(2) 继承的定义
1-定义格式
下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类。
在这里插入图片描述
由上面的图片:我相信我们了解开始写的**《基类》父类和《派生类》子类**了吧
2- 继承关系和访问限定符

我们都知道继承和访问限定符都是有三种关系;
在这里插入图片描述
在这里插入图片描述
3-继承基类成员访问方式的变化
在这里插入图片描述如果有什么问题,可以先了解以下,我写的类和对象:
https://mp.csdn.net/mdeditor/86511762#

为什么会是这样;我们用代码来实现以下,先了解以下,后面我会总结一下。

#include<iostream>
#include<string>

using namespace std;

class Person
{
public:
	void Print()
	{
		cout << _name << "peter"<< endl;
	}
protected:
	string _name; // 姓名
private:
	int _age; // 年龄
};

//class Student : protected Person    //夫类公共,子类保护,子类就不能直接访问父类就访问不了;父类可以访问自己
//class Student : public Person    //公共和公共就可以直接 子在夫中直接调用;
class Student : private Person    //夫类公共,子类私有,在调用的时候也是不能直接被访问的。
{
protected:
	int _stunum; // 学号
};

int main()
{
	Person p;
	p.Print();
	Student s;
	s.Print();
	return 0;
}

根据上面的代码,我们可以了解掉,在父类和子类使用不同的访问限定符的时候,其调用的结果也是不一样。
在这里插入图片描述当你调用使用在子类不同的调用方式的时候,结果就是这个样子。
**总结:

  1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。

  2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。

  3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected > private。

  4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。

  5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用
    protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中
    扩展维护性不强**。
    所以建议大家不要是用保护《protected》或者私有《private》,大部分直接使用的就是共有《public》
    二,基类和派生类的对象的赋值转换

    class Person
    {
    protected:
    string _name; // 姓名
    string _sex; // 性别
    int _age; // 年龄
    };

    class Student : public Person
    {
    public:
    int _No; // 学号
    };

    int main()
    {
    Student s; // 1.子类对象可以赋值给父类对象/指针/引用
    Person p = s;
    Person* pp = &p; // 在这里需要将取地址符加上,要不然无法有 person 转化成 person*;
    //s = p; //2.无法赋值由基类直接到派生类
    // 3.基类的指针可以通过强制类型转换赋值给派生类的指针
    pp = &s;
    Student* s1 = (Student*)pp;
    s1->_No = 10;
    //这种情况下面是可以转换,并且可以通过编译,而且程序可以执行。在这里插入图片描述
    在这里插入图片描述
    pp = &p;
    Student* s2 = (Student*)pp;
    s2->_No = 10;
    //这种情况下是可以转换,而且编译可以通过,但是程序不可以执行,直接崩掉,访问越界;在这里插入图片描述
    在这里插入图片描述
    return 0;
    }

以上是因为要看图片比较多,所以看起来有一点乱,所以希望有什么意见的同学,提一下意见。

三,继承的中的作用域

#include<iostream>
#include<string>

using namespace std;

class Person
{
protected:
	string _name = "小李子"; // 姓名
	int _num = 111; // 身份证号
};
class Student : public Person
{
public:
	void Print()
	{
		cout << " 姓名:" << _name << endl;
		//cout << " 身份证号:" << _num << endl;     //1.当子类与父类的里面有同名成员,调用子类,将父类直接隐藏。
		cout << " 身份证号:" <<Preson::_num << endl;  修改方法;在——num前面加上父类的名;
		cout << " 学号:" << _num << endl;
	}
protected:
	int _num = 999; // 学号  
};

int main()
{
	Student s1;
	s1.Print();
	return 0;
}

// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。

class A
{
public:
void fun()
{
cout << “func()” << endl;
}
};
class B : public A
{
public:
void fun(int i)
{
A::fun();
cout << “func(int i)->” <<i<<endl;
}
};
int main()
{
B b; //1. 调用的第一次
b.fun(10);
A a; //2. 调用的第二次
a.fun();
B b;
b.fun(10);
}

调用这两次的结果是不一样,看一下下面的图片:
在这里插入图片描述第一次调用。

在这里插入图片描述第二次调用。

总结以下:

  1. 在继承体系中基类和派生类都有独立的作用域。
  2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
  3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。

注意在实际中在继承体系里面最好不要定义同名的成员。

四, 派生类的默认成员函数

6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个
成员函数是如何生成的呢?
1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
3. 派生类的operator=必须要调用基类的operator=完成基类的复制。
4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
5. 派生类对象初始化先调用基类构造再调派生类构造。
6. 派生类对象析构清理先调用派生类析构再调基类的析构。

下面有详细的介绍,请看下面的代码;

 #include<iostream>
    using namespace std;
    
     class Person
    {
    public:
    	Person(const char* name = "peter")
    		: _name(name)
    	{
    		cout << "Person()" << endl;
    	}
    	Person(const Person& p)
    		: _name(p._name)
    	{
    		cout << "Person(const Person& p)" << endl;
    	}
    	Person& operator=(const Person& p)
    	{
    		cout << "Person operator=(const Person& p)" << endl;
    		if (this != &p)
    			_name = p._name;
    		return *this;
    	}
    	~Person()     //派生类的析构必须调用基类的析构
    	{
    		cout << "~Person()" << endl;
    	}
    protected:
    	string _name; // 姓名
    };




class Student : public Person
    {
    public:
    	Student(const char* name, int num)
    		: Person(name)
    		, _num(num)
    	{
    		cout << "Student()" << endl;
    	}
    	Student(const Student& s)
    		: Person(s)
    		, _num(s._num)
    	{
    		cout << "Student(const Student& s)" << endl;
    	}
    	Student& operator = (const Student& s)
    	{
    		cout << "Student& operator= (const Student& s)" << endl;
    		if (this != &s)
    		{
    			Person::operator =(s);
    			_num = s._num;
    		}
    		return *this;
    	}
    	~Student()
    	{
    		cout << "~Student()" << endl;
    	}
    protected:
    	int _num; //学号
    };
    void Test()
    {
    	Student s1("jack",18);
    	Student s2(s1);
    	//Student s3("rose", 17);
    	//s1 = s3;
    }

    
    int main()
    {
    	Test();
    	return 0;
    }

<不知道怎么回事,我们上面是代码,不可以弄成代码块>。来看下面的图篇让我们分析一下上面的话:
在这里插入图片描述这是当全部打开的时候,基类和派生类都没有关闭,全部调用,
在这里插入图片描述
这个是将基类的构造函数屏蔽掉,导致程序崩溃,这就明白了,派生类调用的构造函数是由基类提供的。
这里差两个图片,回去补上。

那么,这里又有一个问题,怎么实现一个不被继承的类???

这里就要说一个关键字 “ final ”,它是如何使用的呢?
这里我就不举例子了,就拿上面的例子说明一下:
在:class Person 后面加上 final

在这里插入图片描述
这样就可以实现一个不被继承的类,虽然我们使用类的时候,肯定是要被继承的,但不排除特殊情况。

五。继承和友元
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员 看下面的代码:

  **#include<iostream>
    #include<string.h>
    using namespace std;**


class Person
{
public:
	friend void Display(const Person& p, const Student& s);
public:
	string _name; // 姓名
};

class Student : public Person
{
public:
       int _stuNum; // 学号
};

void Display(const Person& p, const Student& s)
{
	cout <<  p._name << endl;
	cout <<  s._stuNum << endl;
}

void Test()
{
	Person p;
	Student s;
	Display(p, s);
}

int main()
{
	Test();
	return 0;
}

直接调用这给函数会有一点问题。
实际因为是因为;基类的友元不能访问子类私有和保护成员

c++的继承 《进阶二》https://mp.csdn.net/mdeditor/88784387#
类和对象https://mp.csdn.net/mdeditor/86511762#

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值