c++学习笔记(12.继承与多态)(一)

2014-11-24 07:38:50 · 作者: · 浏览: 2

本节知识点:

1.函数重写:

a.通过上篇文章 我们可以知道, 子类与父类可以定义同名成员变量子类依然继承了父类中的同名变量并且在默认情况下子类中的同名成员变量隐藏了父类的同名成员变量如果想访问父类的同名成员变量,就要使用作用域分别符号。 b. 子类和父类中的同名成员函数: 第一, 函数参数相同的情况,这样就在子类和父类之间出现了,除了函数体以外,原型完全相同的函数。这种情况叫做 函数的重写对于重写,子类中的同名成员函数会隐藏父类中的同名成员函数,如果想调用父类中的同名成员函数,就要使用作用域分别符号。注意:函数重写只发生在父类与子类之间 第二, 函数参数不相同的情况切记此时即不发生函数的重写,也不发生函数的重载(子类与父类之间是不发生重载的),但是因为函数名字相同,子类中的函数会隐藏父类中的同名成员函数 总结下:子类与父类中,只要存在同名成员,子类中的成员就会隐藏父类中的成员。同名成员函数和同名成员变量都一样。函数参数相同的同名成员函数还会发生函数的重写 示例代码:
#include 
   
    

using namespace std;

class parent
{
public:
	void fun(int a) //这两个函数发生了重载 
	{
		cout << "parent fun() " << a << endl;
	}
	void fun()
	{
		cout << "parent fun() " << endl;
	}
};
class child : public parent
{
public:
	void fun() //与上面父类中的函数发生了重写 
	{
		cout << "child fun() " << endl;
	}
};
int main()
{
	child c;
	c.parent::fun(4); //这里发生了函数的重载 
	c.parent::fun();
	c.fun();    //发生了函数的重写  隐藏了父类中的同名函数 
	//c.fun(4); //这里会报错 因为没有发生函数重载  被子类的函数隐藏了 
	return 0;
}

   
c.父类中被重写的函数依然会继承给子类,仅仅是被子类中的函数隐藏了。但是可以通过作用域分辨符:: 来访问父类中被隐藏的函数

2.函数重写与赋值兼容性:

a.对于类的赋值兼容性,父类的指针或引用,指向子类的对象,如:parent *p = &c 或 parent &p = c 此时的p它不能访问子类中独特的成员,它只能访问父类继承给子类的那些成员。其实在赋值的过程中,应该是 隐藏了一个类似强制类型转换的过程就像把一个int的变量赋值给一个char的变量一样要进行切割的如果父类和子类之间存在同名成员,能访问的应该都是父类的同名成员(前提是没有虚函数设置)当p进行访问的时候,先去判断访问成员,是不是父类继承给子类的,不是,直接就会报错,说成员不存在(因为p的类型是parent类型) 。然后再去判断父类子类直接有没有同名的,不同名的,子类会赋值给父类。同名的,会直接调用父类的同名成员,忽略子类的成员。 示例代码:
#include 
   
    

using namespace std;

class parent 
{
public:
	int a;
	int b;
	void fun()
	{
		cout << "parent fun " << endl;
	}
};

class child : public parent
{
public:
	int a;
	void fun()
	{
		cout << "child fun " << endl;
	} 
};
int main()
{
	child  c;
	c.a = 10;
	c.b = 100;
	parent *p = &c;
	parent &p1 = c;
	cout << p->a << endl;
	cout << p1.a << endl;
	cout << p1.b << endl;
	cout << p->b << endl;
	
	p->fun();
	p1.fun();
	return 0;
} 

   
注意: 如果子类与父类存在同名成员,child c子类对象去访问同名成员,访问到的一定是子类中的同名成员,把父类继承过来的同名成员隐藏(此时是子类的类型)。如果父类的指针或引用指向子类对象的时候,存在同名成员,通过指针或引用访问到的一定是父类的同名成员,子类的同名成员不是被隐藏了,而是根本就没有赋值过来(此时是父类的类型)。这里就说明一个问题,变量的类型决定变量的行为!!!(但是前提一定是没有设置虚函数)
b. c语言和c++语言都是 静态编译型语言所谓静态编译型语言就是在编译前清楚变量函数的类型,根据确定下来的变量函数的类型进行编译。所以说,在默认的情况下,由于程序没有运行,不可能知道父类指针指向的具体是父类对象还是子类对象,一律认为父类指针指向的是父类对象,因此编译的结果为调用父类的成员函数。

3.多态的本质:

a. 如何根据不同对象的类型来判断重写函数的调用,这是面向对象中多态的概念。如果父类指针指向的是父类对象,则调用父类中定义的函数。如果父类指针指向的是子类对象,则调用子类中定义的重写函数。 b.多态, 根据实际的对象类型决定函数调用语句的具体调用目标,打破了c++静态编译的弊端。 使得同样的调用语句有多种不同的表现形式。 \
c.使用虚函数,让重写后的函数具有多态的特性。 c++中通过virtual关键字对多态进行支持,使用virtual声明的函数被重写后即可展现多态特性 示例代码:
#include 
   
    

using namespace std;

class parent
{
public:
	virtual void fun()
	{
		cout << "parent fun() " << endl;
	}	
};
class child : public parent
{
public:
  	virtual void fun()
	{ 
		cout << "child fun() " << endl;
	}
};
int main()
{
	child c;
	parent *p = &c;
	p->fun(); //同样的语句  有不同的表现形式
	 
	parent d;
	p = &d;
	p->fun();//次条语句 具有多态的特性 
	return 0;
}

   
注意:对于虚函数的声明,其实仅仅在父类中声明就可以了,不用再在子类中进行声明了。也可以即在父类中声明,也在子类中声明。

4.重写与重载:

a.在阐述重写与重载之前,要先说明一些概念。 在一个类里面,肯定是不允许存在同名函数或变量的。但是父类和子类对象之间,虽然说子类是特殊的父类,但是毕竟他俩是相对独立的, 这样就允许子类与父类之间存在同名成员了同时子类还会默认隐藏父类的同名成员子类中依然继承了父类的同名成员可以通过作用域分别符进行访问对于同名成员函数,就存在两种情况,参数相同的就发生了函数的重写。对于参数不相同的,切记,这里没有发生重载(因为子类和父类之间是不可以发生重载的),虽然说参数不相同就是两个完全不相同的函数,但是函数名字相同就发生了隐藏。两者的区别就在,函数的重写是允许父类和子类之间存在完全相同的函数结构。总之,c++允许子类与父类之间,存在完全相同的同名成员函数(即函数的重写)和同名成员变量,也允许存在参数不同的同名成员函数(其实允许是合理的),但不发生函数重载,对于同名成员一律进行对父类的隐藏,并可以通过作用域分别符进行访问。 b. 比较重载与重写: 函数重载: 必须在同一个作用域。子类无法重载父类函数,父类同名函数将被隐藏。重载是在编译期间静态的根据参数类型和个数决定调用哪个函数的。 函数重写: 必须发生在父类与子类之间。并且父类与子类之间的函数必须有完全相同的函数原型。使用virtual关键字声明后能够产生多态。多态是在运行期间动态的根据具体对象的类型决定调用函数的。

5.深入理解虚函数:

a. c++中多态的实现原理:当类中声明虚函数时,编译器会在类中生成一个虚函数表。虚函数表是一个存储类成员虚函数的 函数指针的数据结构(实际上是一个 链表)。 虚函数表是由编译器自动生成与维护的。virtual成员函数会被编译器放入虚函数表中。存在虚函数时,每个对象都有一个指向虚函数表的指针(即VPTR指针)
b. 多态实现的过程:首先得有两个以上具有继承关系的类(一个父类与多个子类是个经典的例子)然后类之间存在着函数的重写。再把重写的函数定义为虚函数。因为定义了virtual成员函数,编译器就会在这些类创建对象时分别产生各自的虚函数表。当然,各自的虚函数表需要各自的虚函数表的指针(VPTR指针是类的一个成员,且还是第一个成员)。最重要的是,在调用多态的这些函数时,编译器首先判断函数是否为虚函数。如果是,就利用此时的对象的VPTR指针所指向的虚函数表中查找这个函数,并调用,查找和调用是在程序运行时完成的(注意对象是如何找到VPTR指针的,这个指针首先是不可以外界调用的,当类中有函数声明为virtual属性时,类中就会多一个成员变量,即VPTR指针,且这个指针放在了类的第一个成