正文

第二课 C++经典知识回顾(三)2007-09-22 12:37:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/lym51/29594.html

分享到:

this指针:

如果成员函数Output被调用,一定是产生了一个对象实例,在这假设对象名称为a,并以a.Output形式调用的,Output的操作一定是针对对象a的。有时,成员函数需要访问它所依赖的那个对象,而不仅仅是这个对象中的其他成员。在类的成员函数中,可以用this关键字代表成员函数所依赖的那个对象的地址,所以,在成员函数中可以用this->成员的方式访问其它的成员,如CPoint(int x2,int y2)函数中用this->x2访问成员变量x2。在成员函数中,我们通常可以省略this->,直接访问类中的成员变量。在CPoint(int x2,int y2)函数中,由于函数参数变量x2,y2与成员CPoint中的成员变量x2,y2同名,要在该函数中访问成员变量x2,y2,可用this->x2this->y2与参数变量x2,y2区分。小技巧:在以后的MFC编程中,如果在成员函数中想调用同类中的某个成员,可以使用VC++提供的自动列出成员函数功能,使用this->,VC++将列出该类中的所有成员,我们可以从列表中选择我们想调用的成员。自动列出成员函数功能,可以提高编写速度,减少拼写错误。特别是我们不能完全记住某个函数的完整拼写,但却能够从列表中辨别出该函数时,自动列出成员函数功能更是有用。事实上,在各种IDE编程环境中,我们通常都没有完全记住某些函数的完整拼写,只是记住其大概写法和功能,要调用该函数时都是从自动列出成员函数中选取的。这样能够大大节省我们的学习时间,我们没有花大量的时间去死记硬背许多函数,利用自动列出成员函数功能和帮助系统,却也能够在编程使顺利使用这些函数,等用的次数多了,也就在不知不觉中完全掌握了这些函数。

 

注意比较Output全局函数与Output成员函数的差别。对Output全局函数的调用,可以理解成“输出某个pt点的坐标”,是一种谓宾关系,是面向过程(或函数)Output的。对Output成员函数的调用,可以理解成“pt这个点对象执行输出动作”,是面向对象pt的。希望通过这样的比较,能够有助于读者理解c++中关于面向对象的概念。

 

四、类的继承与protected访问修饰符:

类是可以继承的,如果类B继承了类A,我们称A为基类(也叫父类),B为派生类(也叫子类)。派生类不但拥有自己新的成员变量和成员函数,还可以拥有基类的成员变量和成员函数。派生类的定义方法是:

class 派生类名:访问权限 基类名称

{

       .....

};

要实现类B与类A的继承关系,我们在定义类B之前必须已定义了类A,并用如下的格式定义类B

class B:publicprivate A

{

       ....

};

讲到类的继承后,我们再讲解另一种成员访问权限修饰符,protectedpublic,protected,private三种访问权限的比较:

public定义的成员可以被在任何地方访问。

protected定义的成员只能在该类及其子类中访问。

private定义的成员只能在该类自身中访问。

派生类可以用publicprivate两种访问权限继承基类中的成员,如果在定义派生类时没有指定如何继承访问权限,则默认为private。如果派生类以private继承基类的访问权限,基类中的成员在派生类中都变成private类型的访问权限。如果派生类以public继承基类的访问权限,基类中的成员在派生类中仍以原来的访问权限在派生类中出现。注意:基类中的private成员不能被子类访问,所以private成员不能被子类所继承。

我们分析如下代码:

class CAnimal

{

       public:

              void eat();

              void breathe();

}

void CAnimal::eat()

{

       cout<<"eating"<<endl;

}

void CAnimal::breathe()

{

       cout<<"breathing"<<endl;

}

class CFish:public CAnimal

{

       public:

              void swim();

              void breathe();      

}

void CFish::swim()

{

       cout<<"swimming"<<endl;

}

void CFish::breathe()

{

       CAnimal::breathe();

       cout<<"breathing"<<endl;

}

void main()

{

       CFish f;

       f.eat();

       f.swim();

       f.breathe();

       //下面的代码演示虚拟函数的多态性

       CAnimal *pA;

       pA=&f;

       pA->breathe();

}

关于类的继承及类的访问特性可以参照如下表:

基类的访问特性

类的继承特性

子类的访问特性

Public
Protected
Private

Public

Public
Protected
No access1

Public
Protected
Private

Protected

Protected
Protected
No access1

Public
Protected
Private

Private

Private
Private
No access1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

由于CFish继承了CAnimal,所以在main函数中用CFish定义的对象f可以将CAnimal中定义的eat()成员函数当作自己的成员函数调用。f还调用了CFish中新定义的成员函数swim()

对象f还调用了breathe()函数,大家发现在基类CAnimal和派生类CFish中都定义了breathe函数,在这种情况下调用的到底是哪个类中定义的函数呢?在这里,调用的是子类CFish中定义的函数。如果在子类与父类中都定义了同样的函数,当用子类定义的对象调用这个函数时,调用的是子类定义的函数,这就是函数的覆盖。函数的覆盖,我们可以用生活中的例子来比喻,儿子继承了父亲的许多方法,包括“结婚”这一行为,但父亲“结婚”用的是花轿,而儿子“结婚”用的却是汽车,儿子不能使用父亲“结婚”的方式。如果儿子结婚时,即要花轿,也要汽车,也就是在子类的成员函数定义中,要调用父类中定义的那个被覆盖的成员函数,其语法为,父类名::函数名(参数)。如CFish定义的breathe函数中使用的CAnimal::breathe()语句,就是调用CAnimal中的breathe函数。

 

在程序中main函数的结尾处的代码:

CAnimal *pA;

pA=&f;

pA->breathe();

上述代码定义了一个CAnimal类型的指针pApA指向CFish定义的对象f的地址,用指针pA去调用breathe函数,在这种情况下调用的到底是哪个类中定义的函数呢?简单的死记硬背只能管一时,不能管一世。我们还是从类型转换的原理上寻找答案。将鱼CFish对象的首地址直接赋值给动物CAnimal类型的指针变量,是不用强制类型转换的,编译器能够自动完成这种转换,子类对象指针能够隐式转换成父类指针。这个过程好比现实生活中将一条鱼当作一个动物是没有什么问题的,但要将一个动物当作鱼来对待是存在问题的。如果某一动物确实是一条鱼,我们就可以将这个动物强制类型转换成鱼。也就是说,要将父类类型的对象转换成子类对象,在程序中必须强制类型转换,编译才能通过,但要保证内存中的对象确实是那种被转换成的类型,程序在运行时才不会有问题。我们可以这样想象类型转换,用目标类型的内存布局,去套取要类型转换的对象的首地址开始的那一段内存块(大小为目标类型的大小),套取的内容即为转换后的结果。见图x&f转换成pA后,转换完后的内容包含的breatheCAnimal中定义的那个。

 

五、虚函数与多态性。

如果我们在CAnimal中定义的void breathe()函数前增加virtual关键字,即改为如下定义:

class CAnimal

{

       public:

              void eat();

              virtual void breathe();

};

则上面的代码

CAnimal *pA;

pA=&f;

pA->breathe();breathe调用的是CFish中定义的那个,这就是编译器对虚函数调用的编译方式,这就是虚拟函数的多态性。如果在某个类的成员函数定义前加了virtual,这个函数就是虚函数,如果子类中有对该函数的覆盖定义,无论该覆盖定义是否有virtual关键字,都是虚拟函数。

 

五、类的书写规范与如何解决头文件重复引用问题。

操作符重载,匈牙利命名法。类继承中的构造函数调用顺序与指定父类中的构造函数。

关于完整的c++语法讲解,需要厚厚的一大本书,如果读者需要深入了解,请参看相关书籍。但只要掌握了本课中介绍的关于C++的知识,基本上就能够顺利学习以后的章节了,如有特殊需求,我们将在以后章节中用到时专门讲解。我们认为抱着问题学习的效果要比泛泛而学的效果好得多,并且学到一个新知识后马上便能看到其应用更能令人记忆深刻,举一反三。

 

实验步骤:

观察构造函数与析构函数的调用时机。

阅读(4280) | 评论(0)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

暂无评论
您需要登录后才能评论,请 登录 或者 注册