那些陌生的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关键字。这里仅仅是在语义上强调模板使用的类型参数不一定是类类型,可以是所有类型。这里typenameclass没有任何区别。

使用格式:

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.0VS2010测试发现,无论是否加上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放在寄存器中,供bc的计算使用。更有甚者,编译器确定a的值是0,会直接计算出b=0c=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_castconst_castdynamic_castreinterpret_cast

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

(类型)表达式

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

使用格式:

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

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

static_cast<int>(0.1);

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

 

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下测试结果表明cxx的地址为同一内存单元!

显然,使用单独的变量初始化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++关键字了,希望本文对你有所帮助!

原文地址:https://www.cnblogs.com/fanzhidongyzby/archive/2012/11/07/2759326.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


对象的传值与返回说起函数,就不免要谈谈函数的参数和返回值。一般的,我们习惯把函数看作一个处理的封装(比如黑箱),而参数和返回值一般对应着处理过程的输入和输出。这种情况下,参数和返回值都是值类型的,也就是说,函数和它的调用者的信息交流方式是用过数据的拷贝来完成,即我们习惯上称呼的“值传递”。但是自从引
从实现装饰者模式中思考C++指针和引用的选择最近在看设计模式的内容,偶然间手痒就写了一个“装饰者”模式的一个实例。该实例来源于风雪涟漪的博客,我对它做了简化。作为一个经典的设计模式,本身并没有太多要说的内容。但是在我尝试使用C++去实现这个模式的实例的时候,出现了一些看似无关紧要但是却引人深思的问题
关于vtordisp知多少?我相信不少人看到这篇文章,多半是来自于对标题中“vtordisp”的好奇。其实这个关键词也是来源于我最近查看对象模型的时候偶然发现的。我是一个喜欢深究问题根源的人(有点牛角尖吧),所以当我第一次发现vtordisp的时候,我也是很自然的把它输进google查找相关资料,但
那些陌生的C++关键字学过程序语言的人相信对关键字并不陌生。偶然间翻起了《C++ Primer》这本书,书中列举了所有C++的关键字。我认真核对了一下,竟然发现有若干个从未使用过的关键字。一时间对一个学了六年C++的自己狠狠鄙视了一番,下决心一定要把它们搞明白!图1红色字体给出的是我个人感觉一般大家
命令行下的树形打印最近在处理代码分析问题时,需要将代码的作用域按照树形结构输出。问题的原型大概是下边这个样子的。图中给了一个简化的代码片段,该代码片段包含5个作用域:全局作用域0、函数fun作用域1、if语句作用域2、else语句作用域3和函数main作用域4。代码作用域有个显著的特点就是具有树形结
虚函数与虚继承寻踪封装、继承、多态是面向对象语言的三大特性,熟悉C++的人对此应该不会有太多异议。C语言提供的struct,顶多算得上对数据的简单封装,而C++的引入把struct“升级”为class,使得面向对象的概念更加强大。继承机制解决了对象复用的问题,然而多重继承又会产生成员冲突的问题,虚继
不要被C++“自动生成”所蒙骗C++对象可以使用两种方式进行创建:构造函数和复制构造函数。假如我们定义了类A,并使用它创建对象。Aa,b;Ac=a;Ad(b);对象a和b使用编译器提供的默认构造函数A::A()创建出来,我们称这种创建方式为对象的定义(包含声明的含义)。对象c和d则是使用已有的对象,
printf背后的故事 说起编程语言,C语言大家再熟悉不过。说起最简单的代码,Helloworld更是众所周知。一条简单的printf语句便可以完成这个简单的功能,可是printf背后到底做了什么事情呢?可能很多人不曾在意,也或许你比我还要好奇!那我们就聊聊printf背后的故事。 一、printf
定义 浮点数就是小数点位置不固定的数,也就是说与定点数不一样,浮点数的小数点后的小数位数可以是任意的,根据IEEE754-1985(也叫IEEE Standard for Binary Floating-Point Arithmetic)的定义,浮点数的类型有两种:单精度类型(用4字节存储)和双精度
在《从汇编看c++的引用和指针》一文中,虽然谈到了引用,但是只是为了将两者进行比较。这里将对引用做进一步的分析。1 引用的实现方式在介绍有关引用的c++书中,很多都说引用只是其引用变量的一个别名。我自己不是很喜欢这种解释,因为觉得这种解释会给人误解,好像引用和变量就是一回事,而且,书中也没有给出,为
今天写程序的时候,创建了一个结构体:struct BufferObj {char* buf;int bufLen;SOCKADDR_STORAGE addr;int addrLen;struct BufferObj* next;};该结构体有一个next指针,本意是这个指针初始的时候应该为NULL,
placement operator new是重载的operator new运算符,它允许我们将对象放到一个指定的内存中。下面来看c++源码:class X {private: int _x;public: X(int xx = 0) : _x(xx) {} ~X() {} void* operat
编码的目的,就是给抽象的字符赋予一个数值,好在计算机里面表示。常见的ASCII使用8bit给字符编码,但是实际只使用了7bit,最高位没有使用,因此,只能表示128个字符;ISO-8859-1(也叫Latin-1,或者直接8859)使用全8bit编码,可以看成是ASCII的超集,因为它的低128个字
在宏定义当中,常常可以看到宏的参数以及整个宏的定义都被小括号包围,就像下面的 MIN、MAX、ABS 宏一样: 上面的图截取自 iOS 的系统库,那为什么它们需要这些括号包围起来呢? 下面假如我们自定义了宏 ceil_div,代码如下: #define ceil_div(x, y) (x + y -
c++中,当继承结构中含有虚基类时,在构造对象时编译器会通过将一个标志位置1(表示调用虚基类构造函数),或者置0(表示不调用虚基类构造函数)来防止重复构造虚基类子对象。如下图菱形结构所示:当构造类Bottom对象时,Bottom构造函数里面的c++伪码如下(单考虑标志位,不考虑其他)://Botto
在C中,使用fopen打开文件有两种模式:一种是文本模式,一种是二进制模式。那这两种模式之间有什么区别,是不是使用文本模式打开的文件就只能使用文本函数比如fprintf来操作,而使用二进制打开的文件就只能使用二进制函数比如fwrite来操作呢? 答案是否定的。C里面之所以有文本模式和二进制模式,完全
尾数英文名叫mantissa,significand,coefficient,用于科学计数法中。科学计数法的表示方法为: Mantissa x Base^Exponent 举个例子,123.45用科学计数法可以表示为: 12345 x 10^(-2) 其中12345就是尾数Mantissa,10是基
定义宏时可以让宏接收可变参数,对于可变参数的定义,标准 C 和 GNU C(GNU 对 C的扩展)是不一样的。 标准 C 标准 C 对于可变参数的定义如下,使用...: #define eprintf(...) fprintf (stderr, __VA_ARGS__) 在宏定义中,__VA_ARG
宏分为两种,一种是 object-like 宏,比如: #define STR &quot;Hello, World!&quot; 另一种是 function-like 宏,比如: #define MIN(X, Y) ((X) &lt; (Y) ? (X) : (Y)) 对于 function-li
副作用(Side Effect) 在计算机当中,副作用指当调用一个函数时,这个函数除了返回一个值之外,还对主调函数产生了影响,比如修改了全局变量,修改了参数等等。 宏的重复副作用 对于求两个数中的最小数,常常可以定义一个宏 MIN,定义如下: #define MIN(X, Y) ((X) &lt;