C++继承和多态



一、继承


C++继承的3种关系


1、public继承

      public继承是一种接口继承,子类可以代替父类完成接口所声明的行为。此时,子类可以自动转换成父类的接口,完成接口的转换。

从语法的角度上说,public继承会保留父类中成员(成员函数和成员变量)的可见性,也就是说,如果父类中的某个函数是public,那么被子类继承后仍然是public。


2、private继承

     private继承是一种实现继承,子类不能代替父类完成接口所声明的行为,此时子类不能自动转换成父类的接口。

     从语法的角度上说,private继承将会将父类中的public和protected可以性的成员修改成为private可见性,这样在子类中同样可以调用父类的protected和public成员,子类的子类也可以调用被protected继承的父类的protected和public成员。


3、protected继承

     protected继承是一种实现继承,子类不能代替父类完成接口所声明的行为,此时子类不能自动转换成父类的接口。

从语法的角度上说,protected继承会将父类中的public可见性的成员修改成为protected可见性,这样在子类中同样可以调用父类的protected和public成员,子类的子类也可以调用被protected继承的父类的protected和public成员。
#include <iostream>
using namespace std;


class Base
{
protected:
	void printProtected()
	{
		cout<<"Print Protected"<<endl;
	}
public:
	void printPublic()
	{
		cout<<"Print Public"<<endl;
	}
};

class Derived1 : protected Base
{
};


class Derived2 : private Base
{
};


class A : public Derived1
{
public:
	void Print()
	{
		printProtected();
		printPublic();
	}
};


class B : public Derived2
{
public:	
	void Print()
	{
		//因为Derived2是private继承自Base,所以Derived2继承自Base的成员不能被继承到class B
		//所以下面两行代码会出现调用错误
		printProtected();
		printPublic();
	}
}; 

int main()
{
	A a;
	B b;
	a.Print();
	b.Print();
	return 0;
}


4、私有继承

#include <iostream>
using namespace std;


class Person
{
public:
	void eat()
	{
		cout<< "Person eat" <<endl;
	}
};


class Student : private Person
{
public:
	void study()
	{
		cout<< "Student study" <<endl;
	}
};


int main()
{
	Person p;
	Student s;
	p.eat();
	s.study();
	s.eat(); //erro
	return 0;
}


二、多态


当不同的对象调用相同名称的成员函数时,可能引起不同的行为(执行不同的代码)。这种现象称为多态性。将函数调用链接相应函数体的代码的过程称为函数联编。在C++中,根据联编时刻的不同,分为静态联编和动态联编。


1、静态联编


不同的类可以有相同名称的成员函数(甚至还可以有相同的参数,编译器在编译时就对他们进项函数联编,这种在编译时刻进行的联编称为静态联编)静态联编所支持的多态性称为编译时的多态性,函数重载就属于编译时的多态性。


2、动态联编


在动态联编中,程序在运行的时候后才能确定调用哪个函数。这种在运行时的函数联编称为动态联编。动态联编所支持的多态性称为运行时多态性。在C++中,只有虚函数才可能是动态联编的。可以通过定义类的虚函数和创建派生类,然后在派生类中重新实现虚函数,实现具有运行时的多态性。



#include <iostream>
using namespace std;

class Person
{
public:
	virtual void print()
	{
		cout << "I'm a Person" << endl;
	}
};

class Chinese : public Person
{
public:
	virtual void print()
	{
		cout<< "I'm from China" <<endl;
	}
};

class American : public Person
{
public:
	virtual void print()
	{
		cout<<"I'm from USA"<<endl;
	}
};

void printPerson(Person &person)
{
	person.print();
}

int main()
{
	Person p;
	Chinese c;
	American a;
	printPerson(p);
	printPerson(c);
	printPerson(a);
	return 0;
}


简单的说,虚函数是通过虚函数表实现的。事实上,如果一个雷中含有虚函数,则系统会为这个类分配一个指针成员指向一张虚函数表(vtbl),表中每一项指向一个虚函数的地址,实现上就是一个函数指针的数组。


class Parent
{
public:
	virtual void foo1(){}
	void foo2(){}
};


class Child1 : Parent
{
public:
	virtual void foo1(){}
	void foo3(){}
};


class Child2 : Child1
{
public:
	void foo1(){}
	void foo2(){}
	void foo3();
}

Parent类中的vtbl: Parent::foo1()
Child1类中的vtbl:child1::foo1(), Parent::foo1()
Child2类中的vtbl: Child1::foo1(),Parent::foo1();


下面来几个例子:

#include <stdio.h>


class A
{
public:
	A()
	{
		doSth();
	}
	virtual doSth()
	{
		printf("I am A");
	}
	virtual doSth2()
	{
		printf("In A");
	}
};


class B : public A
{
public:
	virtual void doSth()
	{
		printf("I am B");
	}
	virtual doSth2()
	{
		printf("In B");
	}
};


int main()
{
	//在构造函数中,虚拟机制不会发生作用,因为基类的构造函数在
	//派生类构造函数之前执行,当基类构造函数运行时,派生类数据
	//成员还没有被初始化。如果基类构造期间调用的虚函数向下匹配
	//到派生类,派生类的函数理所当然会实际本地数据成员,但是数
	//据成员还没有初始化,而调用涉及对象还没有被初始化的部分自
	//然是危险的,所以C++会提示编译错误。因此虚函数不会向下匹配
	//到派生类,而是直接执行基类的函数。
	B b; 		//I am A
	//在构造完成后就会向下匹配
	A *p = &b;
	p->doSth();	//I am B
	return 0;
}

再来一个例子

#include <iostream>
using namespace std;


class A
{
public:
	virtual void print()
	{
		cout<< "A::print()" << endl;
	}
};


class B : public A
{
public:
	virtual void print()
	{
		cout <<B::print()" << endl;
	}
};


class C : public A
{
public:
	void print(void)
	{
		cout<<"C::print()"<<endl;
	}
};


void print(A a)
{
	//存在对象的类型转换,对象拷贝
	a.print();
}


void print(A* a)
{
	a->print();
}


int main()
{
	A a,*pa,*pb,*pc;
	B b;
	C c;
	pa = &a;
	pb = &b;
	pc = &c;
	
	a.print();
	b.print();
	c.print();
	
	pa->print();
	pb->print();
	pc->print();


	print(a);
	print(b);
	print(c);
	
	print(&a);
	print(&b);
	print(&c);
	
	return 0;
}

输出:




#include <iostream>
#include <string>
using namespace std;
void println(const std::string &msg)
{
	cout<< msg << endl;
}


class Base
{
public:
	Base()
	{
		println("Base::Base()");
		virt();
	}
	void f()
	{
		println("Base::f()");
		virt();
	}
	virtual void virt()
	{
		println("Base::virt()");
	}
};


class Derived : public Base
{
public:
	Derived()
	{
		println("Derived::Derived()");
		virt();
	}
	virtual void virt()
	{
		println("Derived::virt()");
	}
};


int main()
{
	Derived d;
	Base *pB = &d;
	pB->f();
	return 0;
}



三、纯虚函数和抽象基类

纯虚函数就是基类只定义了函数体,没有实现过程。如果基类含有一个或多个纯虚函数,那么它就属于抽象基类,不能被实例化。
引入抽象基类和纯虚函数的原因有一下两点:
1、为了方便使用多态特性;
2、在很多情况下,基类本身生成对象是不合情理的。

纯虚函数和虚函数有以下区别:
1、
类里声明虚函数的作用是为了能让这个函数在它的子类里面覆盖,这样编译器就可以使用后期绑定来达到多态了。纯虚函数只是一个接口,只是函数的声明而已,它要留到子类里面去实现。
2、虚函数在子类里面也可以不重载,但是纯虚函数必须在子类里面去实现。通常,很多函数加上virtual修辞,虽然牺牲掉一些性能,但是增加了面向对象的多态性,可以阻止父类里面的这个函数在子类面被修改实现。
3、虚函数的类用于"实作继承",也就是说继承接口的同时也继承了父类的实现。当然大家也可以完成自己的实现。纯虚函数的类用于"介面继承",即纯虚函数关注的是接口的统一性,实现由子类完成。
4、纯虚函数的类叫做虚基类。这种基类不能直接生成对象,只能被继承,并重写其虚函数后才能使用,这样的类也叫做抽象类。




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

空空的司马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值