浅谈继承与多态

学习C++的人都知道面向对象程序设计有4个主要特点:抽象、封装、继承和多态性。可见继承和多态在C++中占据的分量不一般。

本篇文章分别用阐述概念和举栗说明两种方式研究继承与多态,其中会穿插图片说明,望达到轻松理解C++中继承与多态机制的效果。

继承的概念

所谓继承就是在一个已有类的基础上建立一个新的类。以存在的类称为“基类”或“父类”,新建的类称为“派生类“或”子类”。继承机制可提高程序的可重用性,减少重复的工作量。

声明派生类的一般形式:

class 派生类名:[继承方式] 基类名

{

       派生类新增成员

};

三种继承方式

(1)公有继承(public)

基类的公有成员和保护成员在派生类中保持原有访问属性,其私有成员仍为基类私有。

class Person
{
public:
	int _age = 23;
protected:
	string _name = "xiaoxu";
private:
	int _id = 11;
};

class Student : public Person
{
public:
	void Display()
	{
		cout << _age << endl;  //引用基类的公有成员,可以
		cout << _name << endl; //引用基类的保护成员,可以
		cout << _id << endl;   //引用基类的私有成员,出错
		cout << _num << endl;  //引用自己的私有成员,可以
	}

private:
	int _num;
};

(2)私有继承(private)

基类的公有成员和保护成员在派生类中的访问属性变为私有,其私有成员仍为基类私有。

class Student : private Person
{
private:
	int _num;
};

int main()
{
	Student s;
	//基类所有成员访问属性在派生类中全部变为私有,外界不能引用
	s._age = 26;
	s._name = jiyong;
	s._id = 22;
	system("pause");
	return 0;
}

(3)保护继承(protected)

基类的公有成员和保护成员在派生类中的访问属性变为保护,其私有成员仍为基类私有。保护的意思是:不能被外界引用,但可以被派生类的成员引用。

class Student : protected Person
{
public:
	void Display()
	{
		cout << _age << endl;
		cout << _name << endl;
	}

private:
	int _num;
};

int main()
{
	Student s;
	s.Display(); //通过派生类对象的公有成员函数可以访问基类的公有、保护成员
	//不能通过派生类对象直接去访问基类中的成员
	s._age = 26;
	s._name = jiyong;
	s._id = 22;
	system("pause");
	return 0;
}

继承与转换——赋值兼容规则

1.子类对象可以赋值给父类对象(切割/切片

2.父类对象不能赋值给子类对象

3.父类的指针/引用可以指向子类对象

4.子类的指针/引用不能指向父类对象(除非通过强制类型转换完成)

class Person
{
public:
	void Display()
	{
		cout << _name << endl;
	}

protected:
	string _name = "xiaoxu";
};

class Student : public Person
{
public:
	int _num;
};

void Test()
{
	Person p;
	Student s;

	p = s;            //可以
	s = p;            //出错

	Person* p1 = &s;  //可以
	Person& r1 = s;   //可以

	Student* p2 = &p; //出错
	Student& r2 = p;  //出错

	Student* p2 = (Student*)&p; //可以
	Student& r2 = (Student&)p;  //可以
}

派生类的默认成员函数

1.在继承关系中,如果派生类没有显示定义默认的六个成员函数,编译系统会默认合成这六个默认的成员函数。

2.子类中的构造函数是由父类和子类合成的,初始化时只需初始化子类中的变量,父类的变量调用父类的构造函数即可,无需在子类中初始化。

3.子类的拷贝构造会发生切割/切片行为,因为是把子类对象传给父类拷贝构造的引用参数。

4.不需在子类中显式调用父类的析构函数,编译器会自动调用。

5.析构时先析构子类,再析构父类,因为遵循栈中有高地址向低地址生长的原则。

class Person
{
public:
	Person()  //无参构造
	{
		cout << "Person()" << endl;
		_name = "xiaoxu";
	}

	Person(const char* name)
		:_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()
	{
		cout << "Student()" << endl;
		_num = 1;
	}

	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;
	}

private:
	int _num;
};

void Test()
{
	Student s1("weiwei", 9);
	Student s2(s1);
	Student s3;
	s3 = s1;
}

int main()
{
	Test();

	system("pause");
	return 0;
}

输出结果:


继承的层次机构

1.单继承

一个派生类只有一个直接父类


2.多继承

一个子类有两个或两个以上个直接父类


3.菱形继承


class Person
{
public:
	string _name;
};

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

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

class Assistant : public Student, public Teacher
{
protected:
	int _num;
};

void Test()
{
	Person p;
	Student s;
	Teacher t;
	Assistant a;
	a._name = "xiaoxu"; //_name的指向不明确,出现二义性
}

菱形继承的对象模型


4.菱形虚拟继承

为解决菱形继承的二义性和数据冗余,引入虚继承,在继承方式前加关键字virtual即可。

虚继承是将Assistant间接继承的基类Person声明为虚基类,使得在Assistant类中只保留一份Person类成员。

注意:虚基类并不是在声明基类时声明的,而是在声明派生类时,指定继承方式时声明的。因为一个基类可以在生成一个派生类时作为虚基类,而在生成另一个派生类时不作为虚基类。

class Student : virtual public Person
{
protected:
	int _credit;
};

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


菱形虚拟继承的对象模型


虚基类的初始化

如果在虚基类中定义了带参数的构造函数,而没有定义默认构造函数,则在其所有派生类(包括直接派生或间接派生的派生类)中,通过构造函数的初始化列表对虚基类进行初始化。也就是说,在最后的派生类中不仅要负责 对其直接基类进行初始化,还要负责对虚基类初始化。因为虚基类在派生类中只有一份数据成员,如果由虚基类的直接派生类(如类B和类C)对虚基类初始化,就有可能由于初始化参数不同而产生矛盾。

class A
{
	A(int i)   //基类构造函数,有一个参数
	{}
};

class B : virtual public A
{
	B(int n)
	:A(n)
	{}
};

class C : virtual public A
{
	C(int n)
	:A(n)
	{}
};

class D : public B, public C
{
	D(int n)    //D类构造函数,在初始化表中对所有基类初始化
	:A(n)
	, B(n)
	, C(n)
	{}
};

虚函数

虚函数:类的成员函数前加virtual关键字,则这个成员函数称为虚函数。

在同一个类中不能定义两个名字相同、参数个数和类型都相同的函数。但在类的继承中允许在派生类中定义与基类完全相同的虚函数,称这个派生类中的函数重写(覆盖)了基类的虚函数。可以通过基类指针或引用来访问基类和派生类中的同名函数。

class Person
{
public:
	virtual void Display()
	{
		cout << _name << endl;
	}
protected:
	string _name = "xiaoxu";
};

class Student : public Person
{
public:
	void Display()   //重写
	{
		cout << _num << endl;
	}
protected:
	int _num = 11;
};

void Test()
{
	Person p;
	Student s;
	Person* pt = &p;
	pt->Display();  //输出基类对象中的数据"xiaoxu"
	pt = &s;
	pt->Display();  //输出派生类对象中的数据"11"
}
纯虚函数:在成员函数的形参后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义后,派生类才能实例化出对象。

class Person
{
public:
	virtual void Display() = 0;  //纯虚函数
protected:
	string _name;
};

class Student : public Person
{
public:
	void Display()   //重新定义
	{
		cout << _num << endl;
	}
protected:
	int _num;
};

多态

所谓多态,就是多种“形态”,其具体表述为:向不同的对象发送同一个调用函数,不同的对象会有不同的实现。

从系统实现的角度来看,多态性分为两类:静态动态性和动态多态性。

静态多态性是通过函数重载实现的。函数重载、运算符重载都属于静态多态性,要求在程序编译时就知道调用函数的全部信息,因此,在程序编译时系统就能决定要调用的是哪个函数。静态多态性又称编译时的多态性。静态多态性的函数调用速度快、效率高,但缺乏灵活性,在程序运行前就已决定了执行的函数和方法。

动态多态性是通过虚函数实现的。又称为运行时的多态性。动态多态性不在编译时确定调用的是哪个函数,而是在程序运行过程中才动态地确定操作所针对的对象。

class Base
{
public:
	virtual void Fun1()
	{
		cout << "Base::Fun1" << endl;
	}

	virtual void Fun2()
	{
		cout << "Base::Fun2" << endl;
	}

	void Display()
	{
		cout << "Display()" << endl;
	}

	void Display(int i)
	{
		cout << "Display(int i)->" << i << endl;
	}

private:
	int a;
};

class Derive : public Base
{
public:
	virtual void Fun1()
	{
		cout << "Derive::Fun1" << endl;
	}

private:
	int b;
};

void Fun(Base& b)
{
	b.Fun1();
	b.Fun2();
	b.Display();
	b.Display(10);
}
void Test()
{
	Base b1;
	Derive d1;
	Fun(b1);
	Fun(d1);
}

int main()
{
	Test();
	system("pause");
	return 0;
}
输出结果:




继承体系同名成员函数的关系

1.重载

a.在同一作用域

b.函数名相同,参数不同

c.返回值可以不同

2.重定义(隐藏)

a.在不同作用域(基类和派生类中)

b.函数名相同

c.在基类和派生类中只要不构成重写就是重定义

3.重写(覆盖)

a.在不同作用域(基类和派生类中)

b.函数名相同,参数相同,返回值相同(协变例外)

c.基类函数必须有virtual关键字

d.访问修饰符可以不同






  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值