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->x2,this->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:public或private A
{
....
};
讲到类的继承后,我们再讲解另一种成员访问权限修饰符,protected。public,protected,private三种访问权限的比较:
public定义的成员可以被在任何地方访问。
protected定义的成员只能在该类及其子类中访问。
private定义的成员只能在该类自身中访问。
派生类可以用public和private两种访问权限继承基类中的成员,如果在定义派生类时没有指定如何继承访问权限,则默认为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 |
Public |
Public |
Public |
Protected |
Protected |
Public |
Private |
Private |
由于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类型的指针pA,pA指向CFish定义的对象f的地址,用指针pA去调用breathe函数,在这种情况下调用的到底是哪个类中定义的函数呢?简单的死记硬背只能管一时,不能管一世。我们还是从类型转换的原理上寻找答案。将鱼CFish对象的首地址直接赋值给动物CAnimal类型的指针变量,是不用强制类型转换的,编译器能够自动完成这种转换,子类对象指针能够隐式转换成父类指针。这个过程好比现实生活中将一条鱼当作一个动物是没有什么问题的,但要将一个动物当作鱼来对待是存在问题的。如果某一动物确实是一条鱼,我们就可以将这个动物强制类型转换成鱼。也就是说,要将父类类型的对象转换成子类对象,在程序中必须强制类型转换,编译才能通过,但要保证内存中的对象确实是那种被转换成的类型,程序在运行时才不会有问题。我们可以这样想象类型转换,用目标类型的内存布局,去套取要类型转换的对象的首地址开始的那一段内存块(大小为目标类型的大小),套取的内容即为转换后的结果。见图x,&f转换成pA后,转换完后的内容包含的breathe是CAnimal中定义的那个。
五、虚函数与多态性。
如果我们在CAnimal中定义的void breathe()函数前增加virtual关键字,即改为如下定义:
class CAnimal
{
public:
void eat();
virtual void breathe();
};
则上面的代码
CAnimal *pA;
pA=&f;
pA->breathe();中breathe调用的是CFish中定义的那个,这就是编译器对虚函数调用的编译方式,这就是虚拟函数的多态性。如果在某个类的成员函数定义前加了virtual,这个函数就是虚函数,如果子类中有对该函数的覆盖定义,无论该覆盖定义是否有virtual关键字,都是虚拟函数。
五、类的书写规范与如何解决头文件重复引用问题。
操作符重载,匈牙利命名法。类继承中的构造函数调用顺序与指定父类中的构造函数。
关于完整的c++语法讲解,需要厚厚的一大本书,如果读者需要深入了解,请参看相关书籍。但只要掌握了本课中介绍的关于C++的知识,基本上就能够顺利学习以后的章节了,如有特殊需求,我们将在以后章节中用到时专门讲解。我们认为抱着问题学习的效果要比泛泛而学的效果好得多,并且学到一个新知识后马上便能看到其应用更能令人记忆深刻,举一反三。
实验步骤:
观察构造函数与析构函数的调用时机。
评论