类型转换运算符C类型转换运算符虽然类型转换有缺点但也不能抛弃类型转换的概念。在很多情况下类型转换是合理的需求可解决重要的兼容性问题。C提供了一种新的类型转换运算符专门用于基于继承的情形这种情形在 C 语言编程中并不存在。4 个 C类型转换运算符如下static_castdynamic_castreinterpret_castconst_cast这 4 个类型转换运算符的使用语法相同destination_type resultcast_operatordestination_type(object_to_cast);1.static_caststatic_cast用于在相关类型的指针之间进行转换还可显式地执行标准数据类型的类型转换—这种转换原本将自动或隐式地进行。用于指针时static_cast实现了基本的编译阶段检查确保指针被转换为相关类型。这改进了 C 风格类型转换在 C 语言中可将指向一个对象的指针转换为完全不相关的类型而编译器不会报错。使用 static_cast 可将指针向上转换为基类类型也可向下转换为派生类型如下面的示例代码所示Base*objBasenewDerived();Derived*objDerstatic_castDerived*(objBase);// ok!将 Derived转换为 Base被称为向上转换无需使用任何显式类型转换运算符就能进行这种转换Derived objDerived;Base*objBaseobjDerived;// ok!将 Base转换为 Derived被称为向下转换如果不使用显式类型转换运算符就无法进行这种转换Derived objDerived;Base*objBaseobjDerived;// Upcast - ok!Derived*objDerobjBase;// Error: Downcast needs explicit cast然而static_cast只验证指针类型是否相关而不会执行任何运行阶段检查。因此程序员可使用static_cast编写如下代码而编译器不会报错Base*objBasenewBase();Derived*objDerstatic_castDerived*(objBase);// Still no errors!其中 objDer 实际上指向一个不完整的 Derived 对象因为它指向的对象实际上是 Base()类型。由于 static_cast 只在编译阶段检查转换类型是否相关而不执行运行阶段检查因此objDer - DerivedFunction()能够通过编译但在运行阶段可能导致意外结果。除用于向上转换和向下转换外static_cast 还可在很多情况下将隐式类型转换为显式类型以引起程序员或代码阅读者的注意doublePi3.14159265;intnumstatic_castint(Pi);// Making an otherwise implicit cast, explicit在上述代码中使用 num Pi 将获得同样的效果但使用 static_cast 可让代码阅读者注意到这里使用了类型转换并指出对知道 static_cast 的人而言编译器根据编译阶段可用的信息进行了必要的调整以便执行所需的类型转换。对于使用关键字 explicit 声明的转换运算符和构造函数要使用它们也必须通过 static_cast。2.dynamic_cast顾名思义与静态类型转换相反动态类型转换在运行阶段即应用程序运行时执行类型转换。可检查 dynamic_cast 操作的结果以判断类型转换是否成功。使用 dynamic_cast 运算符的典型语法如下destination_type*Destdynamic_castclass_type*(Source);if(Dest)// Check for success of the casting operationDest-CallFunc();例如Base*objBasenewDerived();// Perform a downcastDerived*objDerdynamic_castDerived*(objBase);if(objDer)// Check for success of the castobjDer-CallDerivedFunction();如上述代码所示给定一个指向基类对象的指针程序员可使用dynamic_cast进行类型转换并在使用指针前检查指针指向的目标对象的类型。在上述示例代码中目标对象的类型显然是Derived因此这些代码只有演示价值。然而情况并非总是如此例如将Derived*传递给接受Base*参数的函数时。该函数可使用dynamic_cast判断基类指针指向的对象的类型再执行该类型特有的操作。总之可使用 dynamic_cast 在运行阶段判断类型并在安全时使用转换后的指针。这种在运行阶段识别对象类型的机制称为运行阶段类型识别runtime type identificationRTTI。使用动态转换判断 Fish 指针指向的是否是 Tuna 对象或 Carp 对象0:#includeiostream1:usingnamespacestd;2:3:classFish4:{5:public:6:virtualvoidSwim()7:{8:coutFish swims in waterendl;9:}10:11:// base class should always have virtual destructor12:virtual~Fish(){}13:};14:15:classTuna:publicFish16:{17:public:18:voidSwim()19:{20:coutTuna swims real fast in the seaendl;21:}22:23:voidBecomeDinner()24:{25:coutTuna became dinner in Sushiendl;26:}27:};28:29:classCarp:publicFish30:{31:public:32:voidSwim()33:{34:coutCarp swims real slow in the lakeendl;35:}36:37:voidTalk()38:{39:coutCarp talked Carp!endl;40:}41:;42:43:voidDetectFishType(Fish*objFish)44:{45:Tuna*objTunadynamic_castTuna*(objFish);46:if(objTuna)// check success of cast47:{48:coutDetected Tuna. Making Tuna dinner: endl;49:objTuna-BecomeDinner();50:}51:52:Carp*objCarpdynamic_castCarp*(objFish);53:if(objCarp)54:{55:coutDetected Carp. Making carp talk: endl;56:objCarp-Talk();57:}58:59:coutVerifying type using virtual Fish::Swim: endl;60:objFish-Swim();// calling virtual function Swim61:}62:63:intmain()64:{65:Carp myLunch;66:Tuna myDinner;67:68:DetectFishType(myDinner);69:coutendl;70:DetectFishType(myLunch);71:72:return0;73:}务必检查 dynamic_cast 的返回值看它是否有效。如果返回值为 NULL说明转换失败。3.reinterpret_castreinterpret_cast 是 C中与 C 风格类型转换最接近的类型转换运算符。它让程序员能够将一种对象类型转换为另一种不管它们是否相关也就是说它使用如下所示的语法强制重新解释类型Base*objBasenewBase();Unrelated*notRelatedreinterpret_castUnrelated*(objBase);// The code above compiles, but is not good programming!这种类型转换实际上是强制编译器接受 static_cast 通常不允许的类型转换通常用于低级程序如驱动程序在这种程序中需要将数据转换为 API应用程序编程接口能够接受的简单类型例如有些 OS 级 API 要求提供的数据为 BYTE 数组即 unsigned char*SomeClass*objectnewSomeClass();// Need to send the object as a byte-stream...unsignedchar*bytesFoAPIreinterpret_castunsignedchar*(object);上述代码使用的类型转换并没有改变源对象的二进制表示但让编译器允许程序员访问 SomeClass对象包含的各个字节。由于其他 C类型转换运算符都不允许执行这种有悖类型安全的转换因此除非万不得已否则不要使用reinterpret_cast来执行不安全不可移植的转换。应尽量避免在应用程序中使用reinterpret_cast因为它让编译器将类型 X 视为不相关的类型Y这看起来不像是优秀的设计或实现。4.const_castconst_cast 让程序员能够关闭对象的访问修饰符 const。您可能会问为何要进行这种转换在理想情况下程序员将经常在正确的地方使用关键字 const。不幸的是现实世界并非如此像下面这样的代码随处可见classSomeClass{public:// ...voidDisplayMembers();//problem - display function isnt const};在下面的函数中以 const 引用的方式传递 object 显然是正确的。毕竟显示函数应该是只读的不应调用非 const 成员函数即不应调用能够修改对象状态的函数。然而DisplayMembers()本应为 const的但却没有这样定义。如果 SomeClass 归您所有且源代码受您控制则可对DisplayMembers()进行修改。然而在很多情况下它可能属于第三方库无法对其进行修改。在这种情况下const_cast将是您的救星。voidDisplayAllData(constSomeClassobject){object.DisplayMembers();// Compile failure// reason: call to a non-const member using a const reference}在这种情况下调用 DisplayMembers()的语法如下voidDisplayAllData(constSomeClassobject){SomeClassrefDataconst_castSomeClass(object);refData.DisplayMembers();// Allowed!}除非万不得已否则不要使用 const_cast 来调用非 const 函数。一般而言使用 const_cast 来修改const 对象可能导致不可预料的行为。另外const_cast 也可用于指针voidDisplayAllData(constSomeClass*data){//>*pCastedDataconst_castSomeClass*(data);pCastedData-DisplayMembers();// Allowed!}宏和模板宏也进行文本替换。预处理器只是就地将标识符替换为指定的文本。使用 assert 宏验证表达式编写程序后立即单步执行以测试每条代码路径很不错但对大型应用程序来说可能不现实。比较现实的做法是插入检查语句对表达式或变量的值进行验证。assert 宏让您能够完成这项任务。要使用 assert 宏需要包含assert.h其语法如下assert(expression that evaluates totrueorfalse);下面是一个示例它使用 assert( )来验证指针的值#includeassert.hintmain(){char*sayHellonewchar[25];assert(sayHello!NULL);// throws a message if pointer is NULL// other codedelete[]sayHello;return0;}在大多数开发环境中assert( )通常在发布模式下被禁用因此它仅在调试模式下显示错误消息。另外在有些开发环境中assert()被实现为函数而不是宏。由于断言在发布模式下不可用对于对应用程序正确运行至关重要的检查如检查dynamic_cast 的返回值为了确保它们在发布模式下也会执行应使用 if 语句这很重要。断言可帮助您找出问题但不能因此不在代码中对指针做必要的检查。模板简介模板无疑是 C语言中最强大却最少被使用的特性之一。在 C中模板让程序员能够定义一种适用于不同类型对象的行为。这听起来有点像宏参见前面用于判断两个数中哪个更大的简单宏 MAX但宏不是类型安全的而模板是类型安全的。模板声明语法所有模板必须以template关键字开头尖括号内声明模板形参有两种基础修饰符class/typename代表类型参数接收任意数据类型int、string、自定义类等二者在模板参数中等价普通类型代表非类型参数仅接收编译期常量。基础语法格式templatetypenameT// 后续函数/类定义各种类型的模板声明类型参数模板最常用templateclassTvoidswap(Ta,Tb);非类型参数模板常量参数templateintNintgetSize(){returnN;}模板的模板参数参数本身是模板templatetemplatetypenameclassContainervoidtest(){}模板函数也称泛型函数将函数的参数、返回值类型抽象为模板参数一套函数逻辑兼容所有匹配类型。#includeiostreamusingnamespacestd;templatetypenameTTmaxVal(T a,T b){returnab?a:b;}intmain(){coutmaxVal(3,9);coutmaxVal(2.5,1.8);return0;}编译器会根据调用时传入的实参类型自动生成对应类型的函数实体。模板与类型安全模板是编译期展开不存在运行时类型转换开销同时具备强类型校验实例化时会对传入类型完整语法检查类型不匹配直接编译报错不会像void*那样丢失类型信息全程保留完整类型信息不支持隐式非法类型转换从根源规避类型错乱问题。模板类将整个类抽象为模板类内成员变量、成员函数均可使用模板参数标准容器vectorT、mapK,V均为模板类。templatetypenameTclassStack{private:T data[100];inttop0;public:voidpush(T val){data[top]val;}Tpop(){returndata[--top];}};intmain(){StackintintStk;StackstringstrStk;return0;}声明包含多个参数的模板模板尖括号内可逗号分隔定义多个模板形参支持同时定义类型参数、常量参数。// 两个类型参数templatetypenameT,typenameUautoadd(T a,U b){returnab;}// 类型常量混合多参数templatetypenameT,intMAXclassArray{};声明包含默认参数的模板模板参数可在声明时赋予默认值使用时若不传入对应实参自动采用默认类型/常量。// 默认类型为inttemplatetypenameTintclassNum{};// 默认常量为100templateintN100intgetLen(){returnN;}Numa;// 等价于 Numint规则带默认值的参数必须写在参数列表尾部。一个模板示例完整综合示例多参数、默认参数的模板类#includeiostream#includestringusingnamespacestd;templatetypenameTstring,intCap50classBuffer{private:T buf[Cap];public:voidset(intidx,T val){buf[idx]val;}Tget(intidx){returnbuf[idx];}intcapacity(){returnCap;}};intmain(){BufferstrBuf;Bufferint,20intBuf;strBuf.set(0,demo);coutstrBuf.get(0);return0;}模板的实例化和具体化隐式实例化仅使用模板、不手动指定类型编译器根据上下文自动生成对应代码templatetypenameTvoidfunc(T){}func(10);// 隐式实例化 funcint显式实例化手动告知编译器生成指定类型模板实体常用于分离编译场景templatevoidfuncdouble(double);模板特化具体化针对某一种特定类型重写专属实现覆盖通用模板逻辑templatetypenameTvoidprint(T x){coutx;}// 字符串类型专属特化版本templatevoidprintstring(string s){cout字符串s;}模板类和静态成员模板类的静态成员每个实例化类型独立拥有一份不同类型的模板对象不共享静态变量templatetypenameTclassTest{public:staticintcnt;};templatetypenameTintTestT::cnt0;intmain(){Testint::cnt;Testdouble::cnt;// Testint::cnt 1Testdouble::cnt 1互不干扰return0;}参数数量可变的模板可变参数模板C11引入模板可接收任意个数、任意类型的参数依靠递归展开处理参数包广泛用于打印、转发函数。// 递归终止函数voidprint(){}// 可变参数模板templatetypenameT,typename...Argsvoidprint(T first,Args...rest){coutfirst ;print(rest...);}intmain(){print(1,3.14,text,c);return0;}使用static_assert执行编译阶段检查static_assert结合模板实现编译期静态断言在编译阶段校验模板参数合法性不满足条件直接终止编译并输出提示无运行时开销。templateintNvoidcheckSize(){// 编译期校验N必须小于100static_assert(N100,数值不能大于等于100);}checkSize50;// 正常checkSize200;// 编译报错