本课程学习目标 培养编程技能 开启智慧之门
3.4 多态性和虚函数 何谓多态性? 多态性也是面向对象程序设计方法的一个重要特征,它主要表现在函数调用时实现“一种接口、多种方法”。 3.4 多态性和虚函数 何谓多态性? 多态性也是面向对象程序设计方法的一个重要特征,它主要表现在函数调用时实现“一种接口、多种方法”。 两种多态性:编译时多态性和运行时多态性。 编译时多态性:在函数名或运算符相同的情况下,编译器在编译阶段就能够根据函数参数类型的不同来确定要调用的函数 —— 通过重载实现。 运行时多态性:在函数名、函数参数和返回类型都相同的情况下,只能在程序运行时才能确定要调用的函数 —— 通过虚函数实现。 下节讲授的内容
3.4.1 用基类指针指向派生类对象 声明一个派生类的对象的同时也自动声明了一个基类的对象。 —— 3.3小节内容 3.4.1 用基类指针指向派生类对象 声明一个派生类的对象的同时也自动声明了一个基类的对象。 —— 3.3小节内容 派生类的对象可以认为是其基类的对象。C++允许一个基类对象的指针指向其派生类的对象 —— 这是实现虚函数的关键 不允许派生类对象的指针指向其基类的对象。 即使将一个基类对象的指针指向其派生类的对象,通过该指针也只能访问派生类中从基类继承的公有成员,不能访问派生类自定义的成员,除非通过强制类型转换将基类指针转换为派生类指针。 例
例 基类指针与派生类指针之间的相互转换。 class B : public A class A { { private: private: 例 基类指针与派生类指针之间的相互转换。 class B : public A { private: int b; public: void setB(int i) { b=i; }; void showB() { cout<<"b="<<b<<'\n'; }; } ; class A { private: int a; public: void setA(int i) { a=i; }; void showA() { cout<<"a="<<a<<'\n'; }; };
pa=&b; // 基类指针pa指向派生类对象b // 通过基类指针pa访问B中从基类A继承的公有成员 pa->setA(100); void main() { A a, *pa; // pa为基类对象的指针 B b, *pb; // pb为派生类对象的指针 pa=&b; // 基类指针pa指向派生类对象b // 通过基类指针pa访问B中从基类A继承的公有成员 pa->setA(100); pa->showA(); pb=(B*)pa; // 将基类指针强制转化为派生类指针 // 不能通过基类指针pa访问派生类自己定义的成员 pb->setB(200); pb->showB(); } pb=&a pa->setB() pa->showB() 程序运行结果为: a=100 b=200
? 3.4.2 虚函数 1. 为什么要引入虚函数 class A { public: void Show( ) 3.4.2 虚函数 1. 为什么要引入虚函数 class A { public: void Show( ) { cout<<"A::Show\n"; }; }; class B : public A { public: void Show( ) { cout<<"B::Show\n"; }; }; void main() { A *pa; B b; pa=&b; pa->Show(); } 调用哪一个Show() ? 如果想通过基类指针调用派生类中覆盖的成员函数,只有使用虚函数。
要将一个成员函数声明为虚函数,只需在定义基类时在成员函数声明的开始位置加上关键字virtual。 2. 虚函数的声明 要将一个成员函数声明为虚函数,只需在定义基类时在成员函数声明的开始位置加上关键字virtual。 class A { public: virtual void Show() { cout<<"A::show\n"; }; }; class B : public A void Show() { cout<<"B::show\n"; };
程序运行结果: A::Show B::Show void main() { A a, *pa; B b; pa=&a; pa->Show(); // 调用函数A::Show() pa=&b; pa->Show(); // 调用函数B::Show() } 总结:利用虚函数可以在基类和派生类中使用相同的函数名和参数类型,但定义不同的操作。这样,就为同一个类体系中所有派生类的同一类行为(其实现方法可以不同)提供了一个统一的接口。 例如,在一个图形类继承结构中,设类CShape是所有具体图形类(如矩形、三角形或圆等)的基类,则函数调用语句“pShape->Draw()”可能是绘制矩形,也可能是绘制三角形或圆。具体绘制什么图形,取决于pShape所指的对象。
3. 联编的概念 即将函数调用语句与函数代码相关联。 两种联编方式:静态联编和动态联编。静态联编是指编译器在编译阶段就确定了要调用的函数,即早期绑定。动态联编是指在程序执行过程中根据具体情况再确定要调用的函数,即后期绑定。 重载采用静态联编方式:虽然函数名相同,但编译器能够根据函数参数类型的不同确定要调用的函数。重载体现出一种静态多态性或编译时多态性。 当通过基类指针调用虚函数时,C++采用动态联编方式。虚函数体现出一种动态多态性或运行时多态性。
基于构造函数的特点,不能将构造函数定义为虚函数。 4. 构造函数、析构函数与虚函数 基于构造函数的特点,不能将构造函数定义为虚函数。 声明派生类对象时自动调用基类的构造函数 当撤消派生类的对象时,先调用派生类析构函数,然后自动调用基类析构函数,如此看来析构函数没必要定义为虚函数。但是,假如使用基类指针指向其派生类的对象,而这个派生类对象是用new运算创建的。当程序使用delete运算撤消派生类对象时,这时只调用了基类的析构函数,而没有调用派生类的析构函数。 如果使用虚析构函数,无论指针所指的对象是基类对象还是派生类对象,程序执行时都会调用对应的析构函数。 例
例 虚析构函数的使用。 class A { public: A() { }; // 构造函数不能是虚函数 例 虚析构函数的使用。 class A { public: A() { }; // 构造函数不能是虚函数 virtual ~A() { cout<<"A::destructor\n"; }; // 析构函数是虚函数 }; class B : public A B() { }; ~B() { cout<<"B::destructor\n"; }; // 虚析构函数
? 程序运行结果: B::destructor A::destructor void main() { A *pA=new B; // . . . . . . delete pA; // 先调用派生类B的构造函数,再调用基类A的构造函数 } 如果析构函数不是虚函数,则得不到下面的运行结果。请读者思考会是什么结果 ? 程序运行结果: B::destructor A::destructor 总结:由于使用了虚析构函数,当撤消pA所指派生类B的对象时,首先调用派生类B的析构函数,然后再调用基类A的析构函数。
3.4.3 抽象类和纯虚函数 1. 何谓抽象类 抽象类是类的一些行为(成员函数)没有给出具体定义的类,即纯粹的一种抽象。 3.4.3 抽象类和纯虚函数 1. 何谓抽象类 抽象类是类的一些行为(成员函数)没有给出具体定义的类,即纯粹的一种抽象。 抽象类只能用于类的继承,其本身不能用来创建对象,抽象类又称为抽象基类。 抽象基类只提供了一个框架,仅仅起着一个统一接口的作用,而很多具体的功能由派生出来的类去实现。 虽然不能声明抽象类的对象,但可以声明指向抽象类的指针。 在一般的类库中都使用了抽象基类,如类CObject就是微软基础类库MFC的抽象基类。
一个类如果满足以下两个条件之一就是抽象类: 至少有一个成员函数不定义具体的实现; 定义了一个protected属性的构造函数或析构函数。 2. 抽象类的定义 一个类如果满足以下两个条件之一就是抽象类: 至少有一个成员函数不定义具体的实现; 定义了一个protected属性的构造函数或析构函数。 纯虚函数 3. 纯虚函数 不定义具体实现的成员函数称为纯虚函数。纯虚函数不能被调用,仅起提供一个统一接口的作用。 纯虚函数的声明: virtual <数据类型> <成员函数名>(<形参表>)= 0 ; 当基类是抽象类时,只有在派生类中重新定义基类中的所有纯虚函数,该派生类才不会再成为抽象类。
例 纯虚函数和抽象类的使用。 // 定义具体的派生类 // 定义抽象基类 class CCircle : public CShape 例 纯虚函数和抽象类的使用。 // 定义具体的派生类 class CCircle : public CShape { public: CCircle(double x):CShape(x) { }; // 重新定义虚函数 void Area() { s=3.14159*r*r; }; }; // 定义抽象基类 class CShape { public: double r ; double s ; CShape(double x) { r=x; } // 声明纯虚函数 virtual void Area()=0; };
作业: P111-114 3-38(1)、(2),3- 49,3-52(上机) main() { CCircle circle(48.52); circle.Area(); cout<<"Area="<<circle.s<<endl; } 作业: P111-114 3-38(1)、(2),3- 49,3-52(上机)
3.5 重载 重载是C++提供的一个新特性。C++重载分为函数重载和运算符重载,这两种重载的实质是一样的,因为进行运算可以理解为是调用一个函数。 Add(x, y) Add(x, y, z) x + y X + Y 通过使用重载机制,可以对一个函数名(或运算符)定义多个函数(或运算功能),只不过要求这些函数的参数(或参加运算的操作数)的类型有所不同。 重载使C++程序具有更好的可扩充性。
3.5.1 函数重载 函数重载:指一组功能类似但函数参数类型(个数)不同的函数可以共用一个函数名。 3.5.1 函数重载 函数重载:指一组功能类似但函数参数类型(个数)不同的函数可以共用一个函数名。 当C++编译器遇到重载函数的调用语句时,它能够根据不同的参数类型或不同的参数个数选择一个合适的函数。 例 通过函数参数类型的不同实现函数重载。 int abs(int val) { return val<0 ? –val : val; } float abs(float val) { return (val<0) ? –val : val; }
不能利用函数返回类型的不同进行函数重载。因为在没有确定调用的是哪个函数之前,不知道函数的返回类型。 main() { int i=100; cout<<abs(i)<<endl; // int型 float f=-125.78F; cout<<abs(f)<<endl; // float型 } 在程序中,求绝对值函数的名称相同,但参数类型不同,这时C++编译器自动按参数表的不同来分别联编不同的求绝对值函数。 不能利用函数返回类型的不同进行函数重载。因为在没有确定调用的是哪个函数之前,不知道函数的返回类型。 long abc(int); float abc(int);
同样,不能利用引用进行函数重载: void fun(int&); void fun(int); 因为对于下面的调用语句,编译器无法决定调用哪一个函数: fun(i); // i是一个整型变量 从上面可以看出,一般函数的重载使C++程序具有更好的可扩充性。此外,类的成员函数也可以重载,特别是构造函数的重载给C++程序设计带来很大的灵活性。
例 构造函数的重载。 class Box { private: int height, width, depth; public: Box() { height=0; width=0; depth=0; } // 避免给成员变量赋不安全的值 Box(int ht, int wd, int dp) // 重载构造函数 { height=ht; width=wd; depth=dp; } int Volume() { return height*width*depth; } };
cout<<"Volume1="<<box1.Volume() void main() { Box box1; Box box2(10, 15, 20); cout<<"Volume1="<<box1.Volume() <<", Volume2="<<box2.Volume()<<endl; } 程序运行结果: Volume1=0,Volume2=3000
3.5.2 运算符重载 运算符重载:指对于不同数据类型的操作数,同一个运算符所代表的运算功能可以不同。 3.5.2 运算符重载 运算符重载:指对于不同数据类型的操作数,同一个运算符所代表的运算功能可以不同。 一个运算符定义了一种操作,一个函数也定义了一种操作,其本质是相同的,当程序遇到运算符时会自动调用相应的运算符函数。 虽然重载运算符完成的功能都能够用一个真正的成员函数来实现,但使用运算符重载使程序更易于理解。 与函数重载类似,编译器是根据参加运算的操作数的类型来识别不同的运算。
我们可以将字符串operator+看成一个运算符函数名,这些同名的运算符函数根据不同类型的操作数完成不同的加法运算。 例: 对于表达式:10+20 编译器把它看成如下函数调用: int operator+(10, 20); 对于表达式:10.0+20.0 float operator+(10.0, 20.0); 参加运算的数是整数 参加运算的数是单精度实型数 我们可以将字符串operator+看成一个运算符函数名,这些同名的运算符函数根据不同类型的操作数完成不同的加法运算。
重载一个运算符,就是编写一个运算符函数,重载运算符(函数)的原型为: 重载运算符的形式: 重载一个运算符,就是编写一个运算符函数,重载运算符(函数)的原型为: <数据类型> operator<运算符>(<形参表>); 运算结果的类型 参加运算的操作数 要重载的运算符 例 定义复数类型,重载运算符“+”。 例如:c3=c1+c2
class Complex { public: // 公有成员,以便运算符函数(非成员函数)访问 float r; // 实部 float i; // 虚部 public: Complex(float x=0, float y=0) { r=x; i=y; } }; Complex operator+(Complex c1 , Complex c2) Complex temp; temp.r=c1.r+c2.r; temp.i=c1.i+c2.i; return temp; } 利用普通函数重载运算符
void main() { Complex complex1(3. 34f, 4. 8f), complex2(12. 8f, 5 void main() { Complex complex1(3.34f, 4.8f), complex2(12.8f, 5.2f); Complex complex; complex=complex1+complex2; // 进行两个复数的相加运算 cout<<complex.r<<'+'<<complex.i<<'i'<<endl; } 说明: 本例采用普通函数的形式重载运算符。 可以采用成员函数的形式重载运算符。并且如果运算符函数要求直接访问类的非公有成员时,运算符函数不能定义为非成员函数,除非将它声明为该类的友元函数。
例 利用成员函数进行运算符重载。 class Complex { private: // 私有成员能够在成员函数(运算符函数)中访问 例 利用成员函数进行运算符重载。 class Complex { private: // 私有成员能够在成员函数(运算符函数)中访问 float r; // 实部 float i; // 虚部 public: Complex(float x=0, float y=0) { r=x; i=y; }; Complex operator+(Complex); void Display() { cout<<r<<'+'<<i<<'i'<<endl; }; // 输出实部和虚部 };
Complex Complex::operator+(Complex other) { Complex temp; temp.r=this->r+other.r; temp.i=this->i+other.i; // 可以省略this指针 return temp; } 利用成员函数重载运算符 void main() { Complex complex1(3.34f, 4.8f), complex2(12.8f, 5.2f); Complex complex; complex=complex1+complex2; complex.Display(); }
说明: 当利用非成员函数重载双目运算符时,运算符函数的第一个参数代表运算符左边的操作数,运算符函数第二个参数代表运算符右边的操作数。 当利用成员函数重载双目运算符时,运算符左边的操作数就是对象本身,不能再将它作为运算符函数的参数,运算符函数只需要一个函数参数。 运算符重载与函数重载的区别: 同一个重载运算符的参数个数是相同的。 不能定义新的运算符,只能重载现有的运算符。 运算符重载后仍然保持原来的优先级和结合性。
作业: P111-114 3-38(3)、(4),3-55,3-57(上机)
3.6 C++模板 什么是模板? 模板是一个将数据类型参数化的工具,它把“一般性的算法”和其“对数据类型的实现”区分开来。 模板分为函数模板和类模板两种。 采用模板方式定义函数或类时不确定某些函数参数或数据成员的类型,而将它们的数据类型作为模板的参数。在使用模板时根据实参的数据类型确定模板参数(数据类型)的数据类型。 模板提高了软件的重用性。当函数参数或数据成员可以是多种类型而函数或类所实现的功能又相同时,使用C++模板在很大程度上简化了编程。
3.6.1 函数模板 1. 函数重载与函数摸板 函数模板扩展了函数重载:利用函数重载可以让多个函数共享一个函数名,只要所重载的函数的参数类型必须有所不同。但是,由于参数的类型不一样,虽然这些函数所完成的功能完全一样,也必须为每一个重载函数编写代码。 一个函数模板可用来生成多个功能相同但参数和返回值的类型不同的函数。 函数工厂 2. 什么是函数模板 函数模板是一种不指定某些参数的数据类型的函数,在函数模板被调用时根据实际参数的类型决定这些函数模板参数的类型。
以下定义了一个可对任何类型变量进行操作(求绝对值)的函数模板: 3. 函数模板的定义举例 以下定义了一个可对任何类型变量进行操作(求绝对值)的函数模板: template < class T > T abs( T val ) { return val<0 ? -val : val; } 类型参数T作用: 定义函数的参数和返回值; 在函数体中用来声明变量。 模板定义以关键字template开头; 关键字class后面的标识符T由用户自定义,称为类型参数,是函数模板abs()中没有确定数据类型的参数val的类型。 模板定义的下面是模板函数abs()的定义。
4. 含义多个类型参数的函数模板 定义函数模板时可以使用多个类型参数,每个类型参数前面只需加上关键字class,用逗号分隔: template <class T1,class T2,class T3> 例如: template <class T1,class T2> T1 Max( T1 x, T2 y) { return x>=y ? x : (T1)y; }
5. 函数模板的实例化 函数模板将数据类型参数化,这使得在程序中能够用不同类型的参数调用同一个函数(模板函数)。在调用模板函数时即创建函数模板的一个实例,这个过程称为函数模板的实例化。 函数模板的实例化由编译器完成:编译时函数模板本身并不产生可执行代码,只有在函数模板被实例化时,编译器才按照实参的数据类型进行类型参数的替代,生成新的函数。 编译器 函数模板 函 数
例 函数模板的定义和使用。 #include <iostream 例 函数模板的定义和使用。 #include <iostream.h> template <class T> // 定义模板 T abs(T val) // 定义模板函数 { return val<0 ? -val : val; } void main() int i=100; cout<<abs(i)<<endl; long l=-12345L; cout<<abs(l)<<endl; float f=-125.78F; cout<<abs(f)<<endl; 类型参数T 替换为int 类型参数T 替换为long 类型参数T 替换为float
3.6.2 类模板 1. 类模板与函数模板 函数模板只能用于定义非成员函数,它是模板的一个特例。类模板实际上是函数模板的推广,它是一种不确定类的某些数据成员的类型或成员函数的参数及返回值的类型的类。 2. 类模板与类 类是对问题的抽象,而类模板是对类的抽象,即更高层次上的抽象。 类模板称为带参数(或参数化)的类,也称为类工厂,它可用来生成多个功能相同而某些数据成员的类型不同或成员函数的参数及返回值的类型不同的类。
3. 类模板的定义 为了起到模板的作用,与函数模板一样,定义一个类模板时必须将某些数据类型作为类模板的类型参数。 模板类的实现代码与普通类没有本质上的区别,只是在定义其成员时要用到类模板的类型参数。 定义举例
例如,以下定义了含有一个类型参数的类模板: template < class T > class MyTemClass { private: T x; // 类型参数T用于声明数据成员 public: void SetX( T a ) { x=a; }; // 类型参数T用于声明成员函数的参数 T GetX( ) { return x; }; // 类型参数T用于声明成员函数的返回值 };
注意: 如果在模板类的外部定义模板类的成员函数,必须采用如下形式: template < class T > // 不能省略模板声明 void MyTemClass < T > :: SetX( T a ) { x=a; } 编译时由编译器完成 4. 类模板的实例化 与函数模板不同,类模板不是通过调用函数时实参的数据类型来确定类型参数具体所代表的类型,而是通过在使用模板类声明对象时所给出的实际数据类型确定类型参数。
例如,以下使用类模板声明了一个类型参数为int的模板类的对象: MyTemClass < int > intObject; 对于上面的对象声明: 编译器首先用int替代模板类定义中的类型参数T,生成一个所有数据类型已确定的类class; 然后再利用这个类创建对象intObject 。 5. 含有多个参数类模板的定义 template < class T1,int i,class T2 > class MyTemClass { . . . } 使用
例如,声明模板类的对象应采用如下形式: MyTemClass < int, 100, float > MyObject ; 例 使用多个类型参数的类模板。 template <class T1, class T2> // 使用2个类型参数 class MyTemClass // 定义模板类 { private: T1 x; T2 y; public: MyTemClass(T1 a, T2 b) { x=a; y=b; }; void ShowMax() { cout<<"MaxMember="<<(x>=y?x:y)<<endl; }; };
作业: P115 3-63,3-65 void main() { int a=100; float b=123.45F; MyTemClass< int, float > mt(a, b); // 声明模板类的对象 mt.ShowMax(); } 类模板 的实例化 作业: P115 3-63,3-65
3.7 Microsoft Visual C++的语法扩充 经过多年的发展,C++有很多版本,微软公司就推出了不少C++编译器。微软公司最早推出的C++编译器是Microsoft C++(1.0版到8.0版)。1993年,微软推出了第一个可视化编译器即Visual C++ 1.0,以后不断推出它的新版本,2001年推出了Visual C++ 7.0。1998年,美国国家标准化协会ANSI和国际标准组织ISO联合正式制定了C++国际标准。Visual C++编译器除了遵循一般的C++标准,还结合自己的开发环境、工具和MFC类对C++语法进行了一些扩充。
3.7.1 Visual C++自定义数据类型 数据类型 意义 FAR 对应于far NEAR 对应于near CONST 对应于const BOOL 布尔类型,值为TRUE(真)或FALSE(假) UINT 32位无符号整形,对应于unsigned int BYTE 8位无符号整形,对应于unsigned char WORD 16位无符号整形,对应于unsigned short int DWORD 32位无符号长整形,对应于unsigned long int SHORT 短整形 LONG 32位长整形,对应于long LONGLONG 64位长整形 FLOAT 浮点型,对应于float CHAR Windows字符 VOID 任意类型
LPCSTR 32位字符串指针,指向一个常数字符串 LPSTR 32位字符串指针 LPVOID 32位指针,指向一个未定义类型的数据 LPARAM 32位消息参数,作为窗口函数或回调函数的参数 LPRESULT 32位数值,作为窗口函数或回调函数的返回值 LPCRECT 32位指针,指向一个RECT结构的常量 PROC 指向回调函数的指针 WNDPROC 32位指针,指向一个窗口函数 WPARAM 16位或32位数值,作为窗口函数或回调函数的 参数 HANDLE 对象句柄,其它还有HPEN、HWND、 HCURSOR、HDC等 CONST 常量 COLORREF 32位数值,代表一个颜色值
3.7.2 Visual C++运行库 运行库(Run-Time Library)是存放一些常用函数执行代码的文件库,它由LIB文件组成,进行链接时将需要的LIB文件与程序链接在一起。 Visual C++ 6 可以使用的运行库包括C运行库、标准C++库(存放新的iostream函数和其它标准函数)和旧的iostream库,但标准C++库和旧的iostream库是不兼容的,因此,链接时除了链接C运行库,只能链接标准C++库或旧的iostream库中的一个库。
根据程序中使用的头文件就可以确定是链接标准C++库还是旧的iostream库。 以下文件包含指令包含一个标准C++库头文件,在编译时Visual C++将自动链接一个标准C++库: #include < iostream> 以下文件包含指令包含一个旧的iostream库头文件(本章的例子都是使用该库),在编译时Visual C++将自动链接一个旧的iostream库: #include < iostream .h > 使用标准C++库(新的isotherm函数)的例子: #include <iostream> std::cout<< “AAAAAA\n ”; // 输出
3.7.3 运行时类型识别RTTI 运行时类型识别RTTI(Run-Time Type Information)是这样一种机制:在程序运行时可以确定对象的类型。RTTI主要有以下两种应用: (1)使用dynamic_cast运算符检查一个基类指针是否指向其派生类对象; (2)使用typeid运算符识别指针所指类型。
1. dynamic_cast运算符语法结构如下: dynamic_cast < Type-ID > ( EXP ) 该运算符的功能是将EXP转换成Type-ID类型,要求Type-ID必须是类的指针、引用或void*类型,EXP必须是一个具体的指针或引用。如果EXP是Type-ID类型的基类指针,程序运行时该运算符检查EXP是否指向Type-ID类型(派生类)的对象,如果是,运算结果是该Type-ID类的对象的指针,否则为 NULL(空)。 例 使用dynamic_cast运算符检查一个基类指针是否指向其派生类对象。
#include <iostream #include <iostream.h> class A { public: // 多态性类(使用虚函数) // 才能使用dynamic_cast运算符 virtual void f1() { }; }; class B : public A void f1() { };
void main() { A. pAA=new A; // 基类指针pAA指向基类类对象 A void main() { A *pAA=new A; // 基类指针pAA指向基类类对象 A *pAB=new B; // 基类指针pAB指向派生类对象 B *pB1=dynamic_cast<B*>(pAA); // pB1==NULL B *pB2=dynamic_cast<B*>(pAB); // pB2!=NULL cout<<"pB1="<<pB1<<", pB2="<<pB2<<endl; } 要使用RTTI,应对Visual C++ IDE进行如下设置:执行菜单命令 “Project→Settings→C/C++ →Category→C++ Language”,选择Enable Run-Time Type Information项。
2. typeid 运算: 利用typeid运算符不仅可以确定一个对象是否属于类继承层次中的某个类,还可以识别程序运行时一个对象的真实类型。 CMyClass my; cout<<“Class Name of my: ”<<typeid(my).name()<<endl; int i=12345; if(typeid(i)!=typeid(my)) cout<<"The type of i is not CMyClass !"<<endl; if(typeid(i)!=typeid(float)) cout<<"The type of i is not float !"<<endl; if(typeid(i) = = typeid(123)) cout<<"The type of i is int !"<<endl;
运行时类型识别RTTI机制在较先进的编译器如Visual C++和Borland C++中才得到支持,但微软基础类MFC并未使用Visual C++所支持的RTTI,它有自己的一套办法。MFC提供了有关运行时类型识别的宏,其详细内容请参看第6章的6.5和6.6节。 作业: P115 3-69,3-70
3.7.4 编程规范 为了阅读理解源程序,Visual C++源程序中变量的取名一般采用匈牙利表示法则。该法则要求每一个变量名都有一个前缀,用于表示变量的类型,后面是代表变量含义的一串字符。 例如:前缀n表示整形变量,前缀sz表示以0结束的字符串变量,前缀lp表示指针变量。这些前缀还可以组合起来使用。前缀一般是小写字母,前缀后的第一个字符要大写。如:nWidth表示一个整形变量,lpszMyname表示一个字符串的指针。 在给类和成员变量取名时也使用特定的前缀,如CView是一个类(视图类),m_xStart是一个类的整形成员变量(起点的X坐标)。
Visual C++中的前缀及说明 前缀 表示的类型 例 子 a 数组变量 aScore[50] b 布尔变量 bFlag,bIsEnd c 字符变量 cSex n,i 整形变量 nWidth,iNum x、y 无符号整形变量(X、Y坐标) xStart,yPos s 字符串变量(不常使用) sMyName sz 以0结束的字符串变量 szMyName p 指针变量 pszString,pMyDlg lp 长指针变量 lpszMyname h 句柄 hWnd,hPen,hDlg fn 函数 FnCallBack() m_ 类的成员变量 m_xStart C 类和结构 CDialog,CView,CMysdiApp,CRuntimeClass Afx,afx,AFX 应用程序框架 AfxGetApp(),afx_msg ID*_ 资源标识 ID_,IDD_,IDC_,IDB_,IDI_