前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深度剖析C++继承

深度剖析C++继承

作者头像
小灵蛇
发布2024-06-06 21:30:02
810
发布2024-06-06 21:30:02
举报
文章被收录于专栏:文章部文章部

一.继承的概念与定义

1.1概念

说得简单一点继承就是用已有的类来定义新的类,所定义的新类不仅拥有已有的类的成员,还可以自己附加一些成员上去,实现了扩展原有功能的作用。

这种机制是面向对象编程使代码可以复用的重要手段,呈现了C++面向对象编程的思想。

举个栗子吧!

代码语言:javascript
复制
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可以看到成员函数的复用。

class Student : public Person

{

protected:
 int _stuid; // 学号

};

class Teacher : public Person

{

protected:
 int _jobid; // 工号

};

int main()
{
 Student s;
 Teacher t;
 s.Print();
 t.Print();
 return 0;
}

监视窗口结果:

可以看见Student对象s不止有着从Person继承的_name和_age,还有自己的_stuid,同理可以看见Teacher对象t不止有着从Person继承的_name和_age,还有自己的_jobid。

2.继承的定义

2.1定义格式

下面我们可以看到Person是父类,Student是子类,这就是继承的基本格式!

2.2继承关系和访问限定符
  1. 继承关系符指的是在派生类的类名加冒号后面的字符
  2. 访问限定符指的是对成员的声明

用一张图说明:

需要特别注意的是:

(1)基类private成员还是继承到派生类中,但是无论什么方式都访问不了 (2)class默认继承方式是private,struct默认继承方式是public

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

  1. 派生类对象可以赋值给基类对象/基类引用/基类指针,这样的操作有个形象的说法叫切片或者切割。
  2. 基类对象不能赋值给派生类对象。
  3. 基类指针或引用可以通过强制类型转换赋值给派生类指针或引用,但是只有在基类的指针是指向派生类的时候才是安全的。但是建议尽量不要这样操作。
  4. 将派生类给基类,发生了类型转换,但是并没有产生临时变量(特殊处理)

三.继承中的作用域

3.1概念

  1. 在继承体系中基类和派生类都有自己独立的作用域
  2. 当基类和派生类中存在同名成员,则派生类中的成员会对基类中的同名成员的直接访问进行屏蔽,这就叫隐藏也叫重定义。
  3. 对于成员函数来讲,只需要基类和派生类函数名相同就构成隐藏。

所以小蛇蛇们要注意了,在实际编程中,尽量不要在基类和派生类中定义同名的成员 。

3.2成员同名

代码语言:javascript
复制
// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆

class Person

{

protected :
 string _name = "小李子"; // 姓名

 int _num = 111;   // 身份证号

};

class Student : public Person

{

public:
 void Print()
 {
 cout<<" 姓名:"<<_name<< endl;
 cout<<" 身份证号:"<<Person::_num<< endl;
 cout<<" 学号:"<<_num<<endl;
 }

protected:
 int _num = 999; // 学号

};

void Test()
{
 Student s1;
 s1.Print();
};

3.3函数同名

代码语言:javascript
复制
// 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;
 }
};

void Test()

{
 B b;
 b.fun(10);
};

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

4.1概念

  1. 派生类构造函数初始化列表处必须调用基类的构造函数来初始化基类对象的那部分成员,如果基类没有默认构造函数,派生类必须在构造函数初始化列表处显示调用。
  2. 派生类的拷贝构造函数必须调用基类的拷贝构造函数实现基类的构造。
  3. 派生类的赋值重载operator=必须调用基类的赋值重载实现基类的复制。
  4. 派生类对象初始化先调用基类构造函数,派生类对象清理空间时先调用父类对象析构函数再调用派生类析构函数。

4.2总结

其实在继承中的派生类的这些默认成员函数跟以前普通类一样,只是在构造/赋值/析构时,多了父类的那一部分。

4.3代码实现举例

公主王子请看代码!

代码语言:javascript
复制
class Person
{
public:
	Person(const char* name)
		:_name(name)
	{
		cout << "Person(const char* name)" << endl;
	}
	Person(const Person& p)
	{
		_name = p._name;
		cout << "Person(const Person& p)" << endl;
	}
	Person& operator=(const Person& p)
	{
		if(this!=&p)
		{
			_name = p._name;
		}
		cout << "Person operator=(const Person& p)" << endl;
		return *this;
	}
	~Person()
	{
		cout << "~Person" << endl;
	}
protected:
	string _name;
};
class Student:public Person
{
public:
	Student(const char* name,int id)
		:Person(name)
		,_id(id)
	{
		cout << "Student(const char* name,int id)" << endl;
	}
	Student(const Student& s)
		:Person(s)
	{
		cout << "Student(const Student& s)" << endl;
		_id = s._id;
	}
	Student& operator=(const Student& s)
	{
		cout << "Student& operator=(const Student& s)" << endl;
		if (this != &s)
		{
			Person::operator=(s);
			_id = s._id;
		}
		return *this;
	}
	~Student()
	{
		cout << "~Student()" << endl;
	}
private:
	int _id;
};


int main()
{
	Student s1("张三", 12);
	Student s2(s1);

	Student s3("李四", 11);
	s1 = s3;
	return 0;
}

五.继承和友元

在继承体系中,友元是不能被继承的,也就是说基类的友元不能访问派生类的私有和保护成员。

代码语言:javascript
复制
class Student;
class Person
{
public:
	friend void Display(const Person& p, const Student& s);
protected:
	string _name;
};

class Student :public Person
{
public:
	friend void Display(const Person& p, const Student& s);
protected:
	int _stuNum;
};

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

int main()
{
	Person p;
	Student s;
	Display(p, s);
	return 0;
}

可以看见程序是报错了的!

六.继承与静态成员

当基类定义了一个静态成员时,则整个继承体系中只能有一个静态成员,无论派生多少个类,都只有一个静态实例。

代码语言:javascript
复制
class Person
{
public:
	Person()
	{
		++_count;
	}
private:
	string _name;
public:
	static int _count;
};
int Person::_count = 0;

class Student :public Person
{
protected:
	int _stuNum;
};

int main()
{
	Person p1;
	Person p2;

	Student s1;
	cout << Person::_count << endl;
	cout << Student::_count << endl;

	cout << &Person::_count << endl;
	cout << &Student::_count << endl;
	return 0;
}

七.菱形继承和菱形虚拟继承

7.1菱形继承

在讲菱形继承之前先讲讲最基础的单继承和多继承。

单继承:一个派生类只有一个基类的继承

多继承:一个派生类有两个及以上的直接基类的继承

而咱们得菱形继承就是多继承的一种特殊情况。

那么菱形继承有啥问题呢?

先看一段代码:

代码语言:javascript
复制
class Person
{
public:
	string _name;
	int _age;
	int _tel;
	string _address;
};

class Student:public Person
{
protected:
	int _stuNum;
};

class Teacher :public Person
{
protected:
	int _id;
};

class Assitant :public Student, public Teacher
{
protected:
	string _majorCourse;
};

int main()
{
	Assitant at;
    at.Student::_name = "张三";
	at.Teacher::_name = "李四";
	at._name = "老张";
	return 0;
}

运行一下发现:

这样我们可以看出菱形继承有着数据冗余和二义性的问题 。

而咱们的大哥----》菱形虚拟继承就能解决这样的问题。

我们再来看看他的内存结构:

代码语言:javascript
复制
class A
{
public:
    int _a;
};
class B:public A
{
public:
    int _b;
};
class C :public A
{
public:
    int _c;
};

class D :public B, public C
{
public:
    int _d;
};

int main()
{
    D dd;
    dd.B::_a = 1;
    dd.C::_a = 2;

    dd._b = 3;
    dd._c = 4;

    dd._d = 5;
    return 0;
}

调试一下看见:

7.2菱形虚拟继承

直接上代码:

代码语言:javascript
复制
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 dd;
	dd.B::_a = 1;
	dd.C::_a = 2;
	dd._b = 3;
	dd._c = 4;
	dd._d = 5;

	B bb;
	bb._a = 11;
	bb._b = 12;
	return 0;
}

那他为什么能解决上面菱形继承的问题呢?

我们还是先来看看他的内存结构:

代码语言:javascript
复制
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 dd;
    dd.B::_a = 1;
    dd.C::_a = 2;

    dd._b = 3;
    dd._c = 4;

    dd._d = 5;
    return 0;
}

可以看见图中多了个虚基表的概念,虚基表是啥呢?

我们先来看前面一张图,这就是D对象的组成,那么在B和C对象各自的前面都有一个指针,而这两个指针指向的一段区域就叫做虚基表,用官方的话来讲虚基表就是一段存放虚基类偏移量的空间。

这里的偏移量指的是到A的距离,可以通过左边的监视窗口的地址看出。

那我们可以看见:

此时D从B和C继承来的a就是唯一的,可以直接D::a访问。

总结

好了,到这里今天的知识就讲完了,大家有错误一点要在评论指出,我怕我一人搁这瞎bb,没人告诉我错误就寄了。

祝大家越来越好,不用关注我(疯狂暗示)

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-06-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一.继承的概念与定义
    • 1.1概念
      • 2.继承的定义
        • 2.1定义格式
        • 2.2继承关系和访问限定符
    • 二.基类和派生类对象赋值转换
    • 三.继承中的作用域
      • 3.1概念
        • 3.2成员同名
          • 3.3函数同名
          • 四.派生类的默认成员函数
            • 4.1概念
              • 4.3代码实现举例
              • 五.继承和友元
              • 六.继承与静态成员
              • 七.菱形继承和菱形虚拟继承
                • 7.1菱形继承
                  • 7.2菱形虚拟继承
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档