2024年6月6日发(作者:)
本文作者:黄邦勇帅
本文是属于学习C++的附加内容,本文主要讲解了sizeof操作符,预处理器,#define,RTTI与typid的作用与使用方法,
对于需要了解这方面内容的读者可以参考之。
本文内容完全属于个人见解与参考文现的作者无关,其中难免有误解之处,望指出更正。
声明:禁止抄袭本文,若需要转载本文请注明转载的网址,或者注明转载自“黄邦勇帅”。
主要参考文献:
1、C++..第五版.中文版[美]Stephen Prata著孙建春韦强译人民邮电出版社2005年5月
2、C++..第四版.中文版Stanley n、Barbara 著李师贤等译人民邮电出版社2006年3月
3、C++..第三版.中文版Stanley n等著潘爱民张丽译中国电力出版社2002年5月
4、C++入门经典第三版[美]Ivor Horton著李予敏译清华大学出版社2006年1月
5、C++参考大全第四版[美]Herbert Schidt著周志荣朱德芳于秀山等译电子工业出版社2003年9月
6、21天学通第四版C++ [美]Jesse Liberty著康博创作室译人民邮电出版社2002年3月
第21章其他关键字
1、sizeof操作符:作用是反回一个对象或类型名的长度,反回值的类型为size_t,长度的单位是字节,sizeof表达式的
结果是一个编译时常量,也就是说可以用于常量可以使用的地方,比如用作函数的参数等。有以下3种语法形式。
sizeof(typename);sizeof(expr);sizeof expr;当sizeof操作符用于内置类型名时必须要加括号,比如sizeof(int)而sizeof
int就是错误的。对于char类型或char类型的值执行sizeof操作将恒为1。对引用类型执行sizeof操作将反回存放该
引用类型对象所需内存空间的大小。对指针做sizeof操作将反回存放指针所需内存的大小。要反回指针所指对象的
大小,则必须对指针作解引用操作。对数组名作sizeof操作将反回其元素类型的大小乘以数组元素的个数,即反回
整个数组在内存中的字节长度,它不是数组中第一个元素的长度,也不是数组包含的元素个数。所以对数组名作sizeof
操作除以数组类型的sizeof操作就可求出数组的实际大小。对于string类型的对象使用sizeof操作符时,长度是固定
的,不因字符串的多少而不同。
2、typedef操作符:typedef用于为类型名重命名一个别名,比如要为unsigned short int重命名一个名字为ushort则可以
这样使用typedef unsigned short int ushort;这里把unsigned short int重命名为ushort,在typedef后面为要重命名的类型
名后面是要重命名的名字,最后以分号结束。下面举一个容易范错的例子typedef int * hy; const hy a; 在这里a的类
型应为int *const a;而不是const int *a;因为const修饰a的类型,而a是一个指针,所以最后a的类型为int* const a;
3、预处理器:预处理器是编译器把C++代码编译为机器指令之前执行的一个过程,所有的预处理器都是#开头,以便与
C++语句区分开来,#include预处理器指令在前面已经用过不少了
3.1、#define指令:该指令用于符号置换,其格式为#define 标识符字符序列。注意该语句不以分号结束。比如#define
PI 3.1416就表示把PI置换为3.1416,这里要注意的是虽然PI看起来和变量一样,但PI和变量没有任何关系,PI
只是一个符号或标志,在程序代码编译前该符号会用一组指定的字符来代替。还要注意的是3.1416并不是一个数
值,而是一个字符串,因此不会进行类型检查。在C++中最好是用const来声名常量,比如const long double PI=3.1416;
这样的话PI将会始终保持为long double类型。语句中的字符序列可以是任意的字符序列,而不仅仅是数字,比如
#define PI HYONG这样的话在使用PI使就会用HYONG来替换掉PI,当然HYONG这里会是一个未定义的标识
符。
3.2、从程序串删除#define定义的标志:在#define语句中,如果没有为标识符指定置换字符串,标识符就会被一个空
的字符串来代替,也就是说标识符被删除了,比如#define PI表示在程序中该语句后面删除所有的PI标识符。
3.3、取消#define的定义:可以使用#undef来取消#define定义的标识符,比如#undef PI表示在#undef后面的语句中标
识符PI的定义被取消了。
3.4、带参数的#define:其格式为#define 标识符(参数列表) 置换字符串。比如#define f(v) cout<<(v)< 用后面的字符串替换,其中的参数v也可以进行替换,比如在程序中可以这样调用f(3);就会把程序转换为 cout<<(3)< 开即可,比如#define f(m,n) cout<<(m)<<(n)< 3.5、#define可以引起的错误:比如#define f(m,n) m*n 如果有调用f(4+2, 3);则该语句会被转换为4+2*3这与我们所 希望的(4+2)*3不一致,要解决这个问题就是给参数加上符号,比如#define f(m,n) (m)*(n) 3.6、怎样把预处理指令放在多行上:其方法为使用””续行符符号,该符号应在上一行的最后一个字符。 比如#define m kkielfml 3.7、#define使用字符串作为参数:比如#define m “kdi”如果有语句cout< 这样做#define m dki cout<<”m”;不能在标志符前加上双引号以试图输出字符串dki,这样只会输出字符串m, 因为程序会把”m”解释为一个字符串,而不会把它解释为cout<<”dki”。 3.8、#define把参数指定为字符串:其方法是在参数前加上符号”#”,比如#define f(m) cout<<#m 如果这时有f(dikl); 则程序将会转换为cout<<”dikl”,最后输出字符串dikl。这里要注意的是该方法只能用于参数,而不能用于其 他地方,比如#define m #kidkl这样就是错误的,这里试图用m来代替字符串”kidkl”,这是不成功的,正确方 法为#define m “kidkl” 4、逻辑预处理器指令: 4.1、逻辑#if指令:该指令原理与条件语句if相同,如果测试为真就执行后面的语句,如果为假则跳过后面的语句。 该指令有两种用法,其一可以用#if指令测试某个符号以前是否用#define指令定义过,这是最常用的用法,其二 可以用来测试某个条件表达式是否为真。 4.2、#if指令用法一:测试某个符号是否以前用#define定义过,该用法的指令如下#if defined 标识符…. #endif 其缩 写形式为#ifdef 标识符….#endif表示如果指定的标识符已被#define定义,则中间的语句就包含在源文件中,如果 该标识符还未被#define定义,则跳过#if和#endif之间的语句,该语句以#endif结束,还要注意的是标识符前的关 见字是defined比define多一个字母d。 4.3、测试标识符是否不存在:其语法为#if!defined 标识符…..#endif缩写形式为#ifnedf 标识符…..#endif表示如果指定 的标识符没有定义,则把#if和#endif之间的语句包含在源文件中,如果标识符已定义则跳过#if和#endif之间的 代码,实标上#ifndef语句比#ifdef语句使用得更频繁,因为系统使用该语句防止头文件被多次包含, 4.4、防止头文件被包含多次的方:其方法为#ifndef HY #define HY语句#endif程序在开始遇到标识符HY时没有被定 义,这时执行后面的语句,再第二次被使用时则标识符HY已经被定义,这时不会执行后面的语句,从而防止了 同一头文件被包含多次的情况。这里要注意使用#define后面定义的标识符不需要值。 4.5、#if语句还可以使用逻辑运算符以测试多个值,比如#if defined HY1&&HY2….#endif当HY1与HY2都为真时才执 行,同样还可以使用其他逻辑运算符。 4.6、#if指令用法二:测试某个表达式的值是否为真,其语法格式为:#if 常量表达式….#endif,注意常量表达式的求 值结果应是整数常量表达式,比如#if a=2 …. #endif测试a的值是否为2,如果为2则执行#if与#endif之间的语 句。 4.7、多个#if选择块:和常规的if语句一样#if也有对应的#else和#elif语句,比如#if a=3 …. #else …. #endif表示如果 a=3则执行if后面且在#else前面的语句,如果为假则执行#else与#endif间的语句。#elif用来实现多个选择,该 语句和常规语句的else if相似,比如#if a=1 …. #elif a=2 …. #elif a=3…. #else …. #endif表示,如果a=1则执行#if 后的语句,如果a=2则执行该条件后的语句。 5、RTTI运行时类型识别typeid,type_info,dynamic_cast关建字:在C++中存在虚函数,也就存在了多态性,对于多 态性的对象,在程序编译时可能会出现无法确定对象的类型的情况。当类中含有虚函数时,其基类的指针就可以指向 任何派生类的对象,这时就有可能不知道基类指针到底指向的是哪个对象的情况,类型的确定要在运行时利用运行时 类型标识做出。为了获得一个对象的类型可以使用typeid函数,该函数反回一个对type_info类对象的引用,要使用 typeid必须使用头文件 type_info类 5.1、typid函数:该函数的主要作用就是让用户知道当前的变量是什么类型的,比如使用typid(a).name()就能知道变量 a是什么类型的。因为typid()函数是一个反回类型为typid_info类型的函数,所以下面先对type_info类作下介绍 5.2、type_info类:该类的具体实现方式依编译器而定,但一般都有如下的成员定义 class type_info {private: type_info(const type_info &); type_info& operator =(const type_info&); //type_info类的复制构造函数和赋值运算符是私有的。 public: virtual ~type_info(); //析构函数 bool operator = =(const type_info&) const; //在type_info类中重载了= =运算符,该运算符可以比较两个对象的类型 是否相等。 bool operator !=(const type_info&)const; //重载的!=运算符,以比较两个对象的类型是否不相等 const char * name() const; //使用得较多的成员函数name,该函数反回对象的类型的名字。前面使用的 typeid(a).name()就调用了该成员函数 bool before(const type_info&);}; 因为type_info类的复制构造函数和赋值运算符都是私有的,所以不允许用户自已创建type_info的类,比如type_info A;错误,没有默认的构造函数。唯一要使用type_info类的方法就是使用typeid函数。 5.3、typeid函数怎样创建type_info类的对象:该函数反回type_info类对象的引用,即形式为type_info& typid();因此 也可以说typid函数是type_info类的一个引用对象,可以访问type_info类的成员。但因为不能创建type_info类的 对象,而typeid又必须反回一个类型为type_info类型的对象的引用,所以怎样在typeid函数中创建一个type_info 类的对象以便让函数反回type_info类对象的引用就成了问题。这可能是把typid函数声明为了type_info类的友元 函数来实现的,默认构造函数是私有的并不能阻止该类的友元函数创建该类的对象。所以typeid函数如果是友元 的话就可以访问type_info类的私有成员,从而可以创建type_info类的对象,从而可以创建反回类型为type_info 类的引用。举个例子class A{private:A(){} A(const A&){} A& operator =(const A&){} friend A& f();};这里把类A 的默认构造函数,复制构造函数和赋值操作符定为私有从而防止创建类A的对象,但函数f()是类A的友元,所以 在函数f()中可以创建类A的对象。同时为了实现函数f()反回的对象类型是A的引用,就必须在函数f中创建一 个类A的对象以作为函数f的反回值,比如函数f可以这样定义A& f(){A ma; cout<<”f”< 5.4、因为typeid函数是type_info类的对象,也就是说可以用该函数访问type_info类的成员,即type_info类中重载的 = =和!=运算符,name()和before()成员函数,比如typid(a).naem()和typid(a)= =typid(b)等等。 5.5、typeid函数的使用原理:该函数的形式为type_info& typeid(object)其中object是任何类型的对象,可以是内置类 型和用户创建的类类型。可以看出typeid即是一个函数,同时他也是type_info类的对象,即typeid可以访问类 type_info类的成员,也可以做为一个单独的函数来使用。做个简单的例子,比如 class A{private: A(){b=3;cout<<”A”< public: void name(){cout<<”NA”< friend A f();}; //函数f()是类A的友元,因此在f中可以创建类A的对象。 A f() //函数f()在这里即是类A的一个对象,也是一个单独的函数。 { A m; //创建类A的对象,因为函数f是类A的友元,因此可以创建类A的对象 cout<<”F”< main() { f().name(); //函数f()作为类A的对象使用,这里要注意程序的执行顺序,首先执行函数f()中的语句A m,因此 调用类A的默认构造函数输出A,然后执行A m;后面的语句,输出F,再然后调用类A中的 成员函数name输出NA. f(); } //函数f()单独作为函数使用。 我们创建一个类A,其中A的默认构造函数是私有的,也就是说不能用默认构造函数创建类A的对象。 函数f()是类A的友元,且反回一个类A的对象,因为f()函数是类A的友元,所以在函数f中可以用默认构造函 数创建类A的对象,这时函数f()同时是一个函数,也是类A的对象,因此也可以访问类A中的成员。 5.6、typeid函数使用方式一:使用type_info类中的name()成员函数反回对象的类型的名称。其方法为: typeid(object).name()其中object是要显示的对象的类型名,该函数反回的名字因编译器而定。这里要注意的就是 使用方式一中提到的虚函数类型的问题,即如果有类A,且有虚函数,类B,C,D都是从类A派生的,且都重 定义了类A中的虚函数,这时有类A的指针p,再把对象类B的对象的地址赋给指针p,则typeid(p).name()将反 回的类型将是A*,因为这里的p表示的是一个指针,该指针是类型为A的指针,所以反回A*,而typeid(*p).name() 将反回B,因为指针p是指向类B的对象的,而*p就表示的是类B的对象的类型,所以反回B。 5.7、typeid函数使用方式二:使用type_info类中重载的= =与!=比较两个对象的类型是否相等。使用该方法需要调用 类type_info中重载的= =和!=操作符,其使用方法为typid(object1)= =typid(object2);如果两个对象的类型相等则反 回1,如果不相等则为0。这种使用方法通常用于比较两个带有虚函数的类的对象是否相等,比如有类A,其中 定义有虚函数,而类B,类C,类D,都是从类A派生而来的且重定义了该虚函数,这时有个类A的指针p和 p1,按照虚函数的原理,基类的指针可以指向任何派生类的对象,在这时就有可能需要比较两个指针是否指向同 一个对象,这时就可以这样使用typeid了,typeid(*p)= =typeid(*p1);这里要注意的是typeid(*p)与typeid(p)是指的 不同的对象类型,typeid(p)表示的是p的类型,在这里p是一个指针,这个指针指向的是类A的对象,所以p的 类型是A*,而typeid(*p)则不一样,*p表示的是指针p实际所指的对象的类型,比如这里的指针p指向派生类B, 则typeid(*p)的类型为B。所以在测试两个指针的类型是否是相等时应使用*p,即typeid(*p)= =typeid(*p1)。如果 是typeid(p)= =typeid(p1)的话,则无论指针p和p1指向的什么派生类对象,他们都是相等的,因为都是A *的类 型。 6、强制类型转换运算符:C++有四种强制类型转换符,分别是dynamic_cast,const_cast,static_cast,reinterpret_cast。其 中dynamic_cast与运行时类型转换密切相关,在这里我们先介绍dynamic_cast,其他三种在后面介绍。 6.1、dynamic_cast强制转换运算符:该转换符用于将一个指向派生类的基类指针或引用转换为派生类的指针或引用, 注意dynamic_cast转换符只能用于含有虚函数的类,其表达式为dynamic_cast<类型>(表达式),其中的类型是指 把表达式要转换成的目标类型,比如含有虚函数的基类B和从基类B派生出的派生类D,则B *pb; D *pd, md; pb=&md; pd=dynamic 将这个指针赋给派生类D的指针pd,有人可能会觉得这样做没有意义,既然指针pd要指向派生类为什么不 pd=&md;这样做更直接呢?有些时候我们需要强制转换,比如如果指向派生类的基类指针B想访问派生类D中 的除虚函数之外的成员时就需要把该指针转换为指向派生类D的指针,以达到访问派生类D中特有的成员的目 的,比如派生类D中含有特有的成员函数g(),这时可以这样来访问该成员dynamic_cast dynamic_cast转换后的结果是一个指向派生类的指针,所以可以这样访问派生类中特有的成员。但是该语句不影 响原来的指针的类型,即基类指针pb仍然是指向基类B的。如果单独使用该指针仍然不能访问派生类中特有的 成员。一般情况下不推见这样使用dynamic_cast转换符,因为dynamic_cast的转换并不会总是成功的,具体情况 在后面介绍。 6.2、dynamic_cast的注意事项:dynamic_cast转换符只能用于指针或者引用。dynamic_cast转换符只能用于含有虚函 数的类。dynamic_cast转换操作符在执行类型转换时首先将检查能否成功转换,如果能成功转换则转换之,如果 转换失败,如果是指针则反回一个0值,如果是转换的是引用,则抛出一个bad_cast异常,所以在使用dynamic_cast 转换之间应使用if语句对其转换成功与否进行测试,比如pd=dynamic_cast 样测试if(dynamic_cast 6.3、const_cast操作符:其表达式为const_cast<类型>(表达式),其中类型指要把表达式转换为的目标类型。该操作符 用于改变const和volatile,const_cast最常用的用途就是删除const属性,如果某个变量在大多数时候是常量,而 在某个时候又是需要修改的,这时就可以使用const_cast操作符了。const_cast操作符不能改变类型的其他方面, 他只能改变const或volatile,即const_cast不能把int改变为double,但可以把const int改变为int。const_cast只 能用于指针或引用。const_cast的用法举例比如:int a=3; const int *b=&a; int* c; c=const_cast


发布评论