【C++】类的继承(protected,virtual)

目录

类的继承

虚拟继承和虚拟函数

函数重写

父类指针指向子类对象

虚拟继承

继承:构造和析构

virtual析构函数

类的大小和virtual关键字的影响

多重继承


类的继承

类的继承的出现,是由于实际问题的需要。比如定义了一个男人类和女人类,这2个类之间有属性或者函数的重叠,那代码就有点冗余,所以为了解决这个问题,就将这些共有的属性和函数抽取出来,形成一个新类(比如这2类都属于人,就可以抽取人的共有属性),而这个类的属性和函数能够被男人类和女人类继承,也就是能直接使用这些属性和函数。

我们称这个新类为父类,父类中的所有public成员和函数都可以被子类所使用,private成员和函数不能被继承。

#include "pch.h"
#include <iostream>
#include "string.h"

class Tutorial
{
public:
	char name[16];
	char author[16];
public:
	void ShowInfo()
	{
		printf("name:%s, author:%s \n", name, author);
	}
private:
	int my_private; //这个成员是不能被子类所使用的
};

class VideoTutorial : public Tutorial
{
public:
	void Play();//播放
public:
	char url[128]; //在线观看的url地址
	int visits; //播放量
};

int main()
{
	VideoTutorial test_inherit;
	strcpy(test_inherit.name, "test_cpp"); //访问父类的成员和函数
	strcpy(test_inherit.author, "nick");
	test_inherit.ShowInfo();

	strcpy(test_inherit.url, "http://1123.com"); //访问自己类中的成员


	std::cout << "Hello World!\n"; 
	return 0;
}

看了上面的例子,有没有发现public和private的功能有一种情况没有涵盖到,那就是变量能被继承,但不能被外部访问这种情况。那么为了能满足这种需求,就新增了一种访问修饰符protected,制定protected规则如下:

(1)该成员不能被外部访问,同private;

(2)该成员可以被子类继承,同public。

#include "pch.h"
#include <iostream>
#include "string.h"

class Tutorial
{
public:
	char name[16];
	char author[16];
public:
	void ShowInfo()
	{
		printf("name:%s, author:%s \n", name, author);
	}
private:
	int my_private; //这个成员是不能被子类所使用的
protected:
	int my_protected = 9; 
};

class VideoTutorial : public Tutorial
{
public:
	void Play()
	{
		printf("my_protected:%d\n", my_protected); //protected能被继承,但不能被外部访问
	}//播放
public:
	char url[128]; //在线观看的url地址
	int visits; //播放量
};

int main()
{
	VideoTutorial test_inherit;
	strcpy(test_inherit.name, "test_cpp"); //访问父类的成员和函数
	strcpy(test_inherit.author, "nick");
	test_inherit.ShowInfo();

	strcpy(test_inherit.url, "http://1123.com"); //访问自己类中的成员

	test_inherit.Play();

	std::cout << "Hello World!\n"; 
	return 0;
}

既然public的成员被继承了,那么就会在编译的时候直接编译,那private既然没有被继承,是否在编译时就不会被编译呢?

其实private也是会被编译的,也能出现在内存中,只不过编译器限制了程序不能访问private成员。

虚拟继承和虚拟函数

函数重写

Child类中重写了父类的Test函数,最终的打印结果就是This is Child.

#include "pch.h"
#include <iostream>
#include "string.h"

class Parent
{
public:
	void Test()
	{
		printf("This is Parent.\n");
	}
};

class Child : public Parent
{
public:
	void Test()
	{
		printf("This is Child.\n");
	}
};

int main()
{
	Child a;
	a.Test();
	std::cout << "Hello World!\n"; 
	return 0;
}

如果我想在父类某个函数的基础上再增加些功能,该怎么做呢?

在调用父函数的时候前面加上作用域就行了,也就是告诉这个函数你是Parent的。具体情况请看下面的例子。

#include "pch.h"
#include <iostream>
#include "string.h"

class Parent
{
public:
	void Test()
	{
		printf("This is Parent.\n");
	}
};

class Child : public Parent
{
public:
	void Test()
	{
		Parent::Test();
		printf("This is Child.\n");
	}
};

int main()
{
	Child a;
	a.Test();
	std::cout << "Hello World!\n"; 
	return 0;
}

父类指针指向子类对象

可以将一个父类指针指向一个子类的对象,这是完全允许的。例如Tree* p = new AppleTree();

其实在创建子类对象时,是先编译父类的成员,然后再编译子类的成员。

#include "pch.h"
#include <iostream>
#include "string.h"

class Parent
{
public:
	void Test()
	{
		printf("This is Parent.\n");
	}
public:
	int a = 1;
	int b = 2;
};

class Child : public Parent
{
public:
	void Test()
	{
		Parent::Test();
		printf("This is Child.\n");
	}
public:
	int x = 3;
	int y = 4;
};

int main()
{
	Child a;
	a.Test();

	Parent* b = &a;
	b->a; //这里b只能访问到父类中的成员,子类中的成员他是访问不到的
	b->Test(); //还是一样的只能访问到父类的成员

	std::cout << "Hello World!\n"; 
	return 0;
}

小结:正如上面代码所示,Parent* b = &a;该代码通过父类指针指向一个子类对象,该父类指针只能访问父类自己的成员,而子类的成员b是访问不了的。

从b->Test();中可以看出,它访问的是父类的Test函数,变相也表明了子类中重写的Test函数并没有覆盖覆盖的Test函数,只不过在子类中重写过后,就只能访问子类的Test函数而已。

虚拟继承

当一个成员函数需要子类重写,那么在父类应该将其申明为virtual。这样你就可以调用子类中的重写的函数了。

注意:

(1)只需要在父类中将函数声明为virtual,子类自动地就是virtual了。

(2)即将被重写的函数添加virtual,是一条应该遵守的编码习惯。

#include "pch.h"
#include <iostream>
#include "string.h"

class Parent
{
public:
	virtual void Test()
	{
		printf("This is Parent.\n");
	}
public:
	int a = 1;
	int b = 2;
};

class Child : public Parent
{
public:
	void Test()
	{
		printf("This is Child.\n");
	}
public:
	int x = 3;
	int y = 4;
};

int main()
{
	Child a;
	a.Test();

	Parent* b = &a;
	b->a;
	b->Test();

	return 0;
}

结果为:

This is Child.

This is Child.

小结:如果你想被重写,而且其实是想调用重写的函数,那么就加virtual。因为加了virtual就只能调用子类中重写的那个函数了。

继承:构造和析构

(1)总是现有父亲,再有儿子。

(2)构造和析构总是相反的。

#include "pch.h"
#include <iostream>
#include "string.h"

class Parent
{
public:
	Parent()
	{
		printf("Parent:创建\n");
	}
	~Parent()
	{
		printf("Parent:销毁\n");
	}
};

class Child : public Parent
{
public:
	Child()
	{
		printf("Child:创建\n");
	}
	~Child()
	{
		printf("Child:销毁\n");
	}
};

int main()
{
	{
		Child a;
	}
	return 0;
}

结果为:

当父类有多个构造函数时,可以显示的调用某个构造函数(一个类在被创建对象时,只能选择一个构造函数和一个析构函数)。比如下面的例子

#include "pch.h"
#include <iostream>
#include "string.h"

class Parent
{
public:
	Parent()
	{
		printf("Parent:创建\n");
	}
	Parent(int x, int y)
	{
		printf("Parent:有参数\n");
		this->x = x;
		this->y = y;
	}
	~Parent()
	{
		printf("Parent:销毁\n");
	}
private:
	int x, y;
};

class Child : public Parent
{
public:
	Child():Parent(1,2)
	{
		printf("Child:创建\n");
	}
	~Child()
	{
		printf("Child:销毁\n");
	}
};

int main()
{
	{
		Child a;
	}
	return 0;
}

 结果为:

virtual析构函数

为什么析构函数需要加virtual呢?

比如Parent* p = new Child(); delete p; //这里到底调用的是谁的析构函数?

#include "pch.h"
#include <iostream>
#include "string.h"

class Parent
{
public:
	Parent()
	{
		printf("Parent:创建\n");
	}
	Parent(int x, int y)
	{
		printf("Parent:有参数\n");
		this->x = x;
		this->y = y;
	}
	virtual ~Parent()
	{
		printf("Parent:销毁\n");
	}
private:
	int x, y;
};

class Child : public Parent
{
public:
	Child():Parent(1,2)
	{
		printf("Child:创建\n");
	}
	~Child()
	{
		printf("Child:销毁\n");
	}
};

int main()
{
	Parent* p = new Child();
	delete p;

	return 0;
}

结果为:

小结:如果不加virtual,那么程序不会报错。但真实情况是,我真正想调用子类中的析构函数,不然程序析构的对象有问题,以后面程序会出现不可预知的错误。

类的大小和virtual关键字的影响

(1)类的大小由成员变量决定。

类的大小成员函数的个数无关,即使一个类有10000个成员函数,对它所占的内存空间是没有影响的。

(2)但是,如果一个成员函数被声明virtual,那类的大小会有些微的变化。(这个变化由编译器决定,一般是增加4个字节)

(3)构造函数不能加virtual。

多重继承

#include "pch.h"
#include <iostream>
#include "string.h"

class Father
{
public:
	int a, b;
	void Test()
	{
		printf("Father");
	}
};

class Mother
{
public:
	int c, d;
	void Test()
	{
		printf("Mother");
	}
};

class Child : public Father, public Mother
{
public:
	int e;
};

int main()
{
	Child my_child;
	my_child.a = 1;
	my_child.b = 2;
	my_child.c = 3;
	my_child.d = 4;
	my_child.e = 5;

	//my_child.Test();// 这里会直接报错,因为编译器不知道该调用哪个Test

	return 0;
}

小结:正如上面例子所示,我在Father和Mother类中都写了Test函数,在主函数中调用Test时,编译器就傻了,它不知道该调用谁,所以直接给你报错。

因此可以得出结论,在实际使用中,尽量不要使用多重继承,万一成员名重复就很麻烦了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值