好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

陌生的C++关键字

陌生的C++关键字

 

学过程序语言的人相信对关键字并不陌生。偶然间翻起了《 C++ Primer 》这本书,书中列举了所有 C++ 的关键字。我认真核对了一下,竟然发现有若干个从未使用过的关键字。一时间对一个学了六年 C++ 的自己狠狠鄙视了一番,下决心一定要把它们搞明白!图 1 红色字体给出的是我个人感觉一般大家都会比较陌生的关键字,下边我们逐个学习。

 

 

图 1 C++  关键字

一、 typeid

从名字直观看来,该关键字应该是获取语言元素的类型 ID 。其实它和 sizeof 类似,是一个类型运算符。有时候代码可能需要获取某个变量或者类型的名字,这时候使用 typeid 就比较合适。

使用格式 : typeid(int) 或 typeid(i+1)

这样操作后返回有个 type_info 类型的对象,比较常用的对象成员函数一般有比较是否相等和获取类型名。

例如:

typeid(int).name();//获取int类型名,结果为“int”。
typeid(1)!= typeid(1.0);//比较表达式类型,结果为true。

使用过 Java 的读者想必发现该运算符和 Java 关键字 instanceof 功能比较类似,相比而言, instanceof 可能使用起来更方便些。对 typeid 用法更详细的内容请点击 参考博文 。

二、 typename

这个关键字和上边的很相近,刚开始我还以为是这个关键字获取类型的名字呢(想当然害死人啊 ~ ),但是他们之间一点关系都没有! C++ 使用 typename 的情况有两种:

第一种情况是在函数模板和类模板声明中。一般模板声明中,使用 class 关键字指定类型参数,后来 C++ 支持使用 typename 代替 class 关键字。这里仅仅是在语义上强调模板使用的类型参数不一定是类类型,可以是所有类型。这里 typename 和 class 没有任何区别。

使用格式:

template<class  T, class Y>

template<typename  T, typename Y> 完全等价!

第二种情况使用情况比较特殊,简单说起来就是在使用类内成员类型的时候。类内成员类型就是在类定义内声明了一个类型,该类型属于类型内部,可见性由权限访问符限定。

下面就是一个类内的成员类型的声明。

class MyClass
{
public:
       typedef int MyType;
};

类内类型可以像类的静态成员一样使用,例如:

MyClass::MyType var;//定义变量
MyClass::MyType * pvar;//定义指针
typedef MyClass::MyType MyType;//重新命名类型

这些使用方式并没有太大问题,问题可能出现在带有模板的代码中,例如:

template<class T>
void MyMethod( T my )
{
       T::MyType * pvar;
       typedef T:: MyType MyType;
}

函数参数类型来自于模板,如果 MyClass 对象是实际参数,那么函数内将声明一个 MyClass::MyType 类型的指针,以及对 MyClass::MyType 类型重新命名为 MyType 。由于类内类型使用方式和类成员完全相同,对于第一种语句,可以解释为一个指针声明,也可以解释为一个类成员和变量的乘法操作。第二种语句把 T::MyType 解释为类型是没有问题的,但是解释为成员变量就产生了错误,因为 typedef 操作的对象只能是类型。这种二义性对于编译器是不愿意看到的,为了消除这种问题,就可以使用 typename 进行显示的类型声明。

使用格式:

typename T::MyType * pvar;

typedef typename T:: MyType MyType;

引发这种问题的本质原因来自于模板类型 T 的不确定性,和直接使用 MyClass::MyType 不同的是,后者能在编译时期检查出该引用的语法成分。通过 typename 明确的告诉编译器,这里使用的是类型。这样编译器就明确类型 T 引出的成员是类型,而不是变量或者函数名。因此, typename 的使用范围也被限定在模板函数内部。

其实这些问题在目前的编译器中并不存在,使用 VC6.0 和 VS2010 测试发现,无论是否加上 typename 程序都不会出错。对该关键字的保留大概是为了兼容旧式编译器的代码。关于 typename 的用法读者感兴趣可以点击 参考链接 。

三、 mutable

Mutable 的含义是可变的,它和 const 关键字是相对的。同样是修饰变量的声明,但是 mutable 的使用范围比 const 要小。我们知道类的常成员函数在语义上是不允许修改类的成员变量的,但是有时候可能根据代码的需要并不是这么绝对。那么就可以使用 mutable 声明一个类的成员变量,它告诉编译器类的常成员函数可以修改这个变量。

使用格式:

mutable int var;// 类内声明

例如:

 

class MyClass
{
       mutable int member;
       void constFun()const
       {
              member=0;
       }
};

 

如果不使用 mutable 修饰 member 定义,就会编译报错。

四、 volatile

Volatile 是易变的意思,编译器在编译时期可能不能获取变量是否被多个线程或者进程修改的信息。这时候一个变量是否在两次“读操作”之间发生改变,编译器肯定无法确定。然而编译优化的技术针对一般的变量都会做出优化,例如:

int a=0;
int b=a;
int c=a+1;

编译器极可能把 a 放在寄存器中,供 b , c 的计算使用。更有甚者,编译器确定 a 的值是 0 ,会直接计算出 b=0 , c=1 !如果在实际运行中 a 的值被其他线程修改,这么做就改变了代码的语意。

使用格式:

volatile int a;// 这里对 a 是否初始化已经不再重要了

为了消除这种问题,使用 volatile 关键字告诉编译器每次访问 a 的时候都需要读内存,而不对其优化。

五、 explicit

Explicit 的含义是显式的,它和 C++ 中的隐式转换相关。例如:

double a=100;

编译器会自动将整数 100 转化为浮点类型。对于用户数据类型, C++ 提供了转换构造函数和类型转换函数实现用户数据类型和内置类型的相互转换。而 explicit 是因为转换构造函数而存在的。下面给出一个转换构造函数的例子:

 

class A
{
public:
       A(int x)
       {
       }
};
void fun(A a)
{
}
fun(1);

 

最后的函数调用语句是合法的,虽然 fun 只接受 A 类型的参数,但是因为 A 的构造函数除了初始化 A 外,还提供了整数转换为 A 类型的方式——转换构造函数。但是有些情况下,这样做可能是不利的,比如 fun 可能有单独处理整形参数的重载,或者 fun 根本不需要转换构造函数生成的对象。

使用格式 :

explicit A(int x)

{}

通过使用 explicit 限制构造函数必须是显式调用,禁止隐式类型转换就可以按照程序作者的需要限定构造函数的功能。

六、 static_cast 、 const_cast 、 dynamic_cast 、 reinterpret_cast

之所以把这四个关键字放在一起,是因为它们处理相似的问题——显式类型转换。 C++ 延续了 C 风格的强制类型转换的语法:

( 类型 ) 表达式

但是 C 风格的转换具体很大的风险性,为此, C++ 支持四种关键字对不同形式的类型转换进行分别处理。

使用格式:

转换关键字 < 类型 >( 表达式 )

static_cast 和 C 风格类型转换功能完全相同,它属于在编译时期静态的类型转换。如果把一个 double 类型转换为整形,形式如下:

static_cast<int>(0.1);

static_cast 功能有所限制,比如不能转化 struct 类型到 int ,不能转化指针到 double 等。另外,它不能在转换中消除 const 和 volatile 属性。

 

const_cast 用于消除引用或者指针的 const 或者 volatile 属性。

const int &ci=100;
int &i=const_cast<int&>(ci);

通过这种方式, ci 引用的内存单元虽然无法通过修改 ci 改变,但是可以修改 i 改变内存的值。这里是把 const 属性消除,这里想多说一点的是把 const 加上的问题。例如:

int x=100;
const int &cx=x;
const int &cy=x+1;

对 const 对象的引用允许使用表达式初始化,比如 cy 引用的内存单元的值应该就是 x+1 的值即 101 。正因为此《 C++ Primer 》也假设了编译器了的工作方式:

int temp=x+1;
const int &cy=temp;

如果按照这种工作方式, cx 引用的内存单元应该不是 x 的内存单元,但是在 VS2010 下测试结果表明 cx 和 x 的地址为同一内存单元!

 

 

显然,使用单独的变量初始化 const 引用的值不会产生额外的存储空间,通过修改原先的变量是可以修改常量引用的值的。

 

dynamic_cast 一般出现在类到子类或兄弟类的转换,并要求基类有虚函数。而且它能提供转换后的结果和状态,一旦转换失败则返回空指针。如果没有继承关系的转换一般使用 static_cast 。

对于 dynamic_cast 使用方式如下:

 

class Base
{
       virtual void fun(){};//必须拥有虚函数
};
class A:public Base//必须是供有继承才能默认转换
{
};
Base b;
A *a=dynamic_cast<A*>(&b);//基类到子类,显式转换
Base*pb=a;//子类到基类,默认转换

 

 

reinterpret_casts 一般用作函数指针的转换,而且使用它的代码可移植性很差,因为无法确定编译器的函数调用方式等。有可能会导致函数调用出错,一般不常用。例如:

typedef void (*FuncPtr)();//funcPtr是指向无参无返回值类型函数的指针
int func()//一个无参返回整数的函数定义
{
      return 0;
}
FuncPtr pf=reinterpret_cast<FuncPtr>(func);

直接把 func 赋值给 pf 是不行的,使用 reinterpret_cast 将函数指针强制转换即可。

至此,我们把那些陌生的 C++ 关键字的“老底”摸了个遍,相信以后应该不会再碰到搞不清楚的 C++ 关键字了,希望本文对你有所帮助!

作者: Florian

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

分类:  C++

标签:  C++

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于陌生的C++关键字的详细内容...

  阅读:66次