第十八章:特殊工具与技术:运行时类型识别
通过运行时类型识别(RTTI),程序能够使用基类的指针或引用来检查这些指针或引用所指向对象的实际类型。
通过两个操作符提供RTTI:
1:typeid操作符,它返回指针或引用所指对象的实际类型。
如typeid(*p);typeid(ref);
2:dynamic_cast操作符,将基类类型的指针或引用安全的转换为派生类类型的指针或引用。
只有当类带有一个或多个虚函数时,这些操作符在运行时才返回动态类型信息。其他类型则在编译过程中,返回静态类型信息。
当基类指针需要调用派生类特有的操作时,需要动态强制类型转换。但是要特别注意:程序员必须知道应该将对象强转为哪种类型,并且检查转换是否成功。使用这种机制很容易出错。
dynamic_cast操作符用于将基类类型的对象的引用或指针,转换为同一继承层次的 ,派生类类型的引用或指针。此操作符涉及运行时类型检查,判断绑定到引用或指针的对象是不是目标类型的对象。如果转换到指针的dynamic_cast 失败,则返回0值。如果转换到引用的dynamic_cast失败,则抛出一个bad_cast异常。
因此只有转换有效才执行转换操作。所有这些都是在运行时进行的。
假定base是至少带一虚函数的类。并且derived继承自base。
如:
if(derived*dptr=dynamic_cast<derived*>(pbase))
{
//转换成功,dptr指向派生类对象。
}
else
{
//转换失败,使用pbase指向的基类对象。
}
在运行过程中,如果bptr实际指向derived对象,则转换成功。并且dptr被初始化为derived对象。否则转换的结果为0。转换成功后,dptr就可以访问派生类中特有的成员。
调用dynamic_cast进行转换后,必须判断是否转换成功。防止转换失败被误用。
在转换引用时,因为不存在空引用,因此不能对引用,使用用于指针强制类型转换的检查策略。当失败时,它抛出std::bad_cast异常。该异常在typeinfo中定义。
typeid操作符能问一个表达式你是什么类型。如typeid(e);
如果表达式是类类型且包含一个或多个虚函数,则表达式的动态类型不同于它的静态编译类型。如:如果表达式对基类指针解引用,则该表达式的静态编译时类型是基类类型,但是如果指针实际指向派生类对象,则typeid操作符返回派生类的类型。
typeid操作符可以与任何类型的表达式一起使用。内置类型以及常量都可以作为它的操作数。如果操作数不是类类型或是没有虚函数的类,则typeid操作符指出操作数的静态类型。typeid的操作符返回typeinfo标准库类型对象的引用 。
typeid(p)当p是指针时将返回指针的静态类型。所以在判断某一指针所指对象的类型时应使用*p。
如果指针p为0,且p指向带虚函数的类型,则typeif(*p)将抛出一个bad_typid异常。
typeid返回typeinfo对象。该对象不能定义、复制或赋值。唯一产生该对象的方法是使用typeid操作符 。name成员函数返回类型名c风格字符串。
类成员的指针
成员指针包括成员变量指针和成员函数指针。它可以绑定到成员变量或成员函数,通过指针对成员变量或成员函数进行间接操作。
如:
class Test
{
public :
inti;
void print();
int show(const string &,conststring&);
};
我们可以定义指向Test的成员变量i的成员指针,形如:
int Test::*pi=&Test::i;
也可以定义指向Test的成员函数print和show的指针,形如:
void (Test::*pprint)()=&Test::print;
int (Test::*pshow)(const string &,conststring&);
pshow=&Test::show;
由于调用操作符()的优先级高于成员指针操作符,因此包围Test::*的括号是必须的。
成员函数的指针必须在以下三个方面与所指函数类型匹配:
1:函数形参类型和数目,包括成员是否为const。
2:返回类型。
3:所属类的类型。
由于成员函数中指针定义太长,我们可以使用typedef来定义别名。如
typedef int (Test::*alias)(conststring&,const string &);
定义后,alias是Test类的接受两个const string类型引用的形参并返回int类型的成员指针的别名。可以使用它来声明函数形参和函数返回类型。
可以使用.*和->*来调用类成员指针。如
void (Test::*pprint)()=&Test::print;
int (Test::*pshow)(const string&,conststring&)=&Test::pshow;
int (Test::*pi)=&Test::i;
Test t;
Test *pt=&t;
t.*pi=20;
pt->*pi=39;
(t.*pprint)();//调用操作符的优先级很高,注意括号。
(pt->*pshow)(“Hello”,”world!”);
函数指针和成员函数指针一个公共用途是将它们存在函数表中。函数表存储相同类型的函数指针。一般用数组实现。
嵌套类
可以在一个类内部定义另一个类。这样的类称为嵌套类。也称为嵌套类型。它本质上就是定义它的外围类的一个类型成员。
嵌套类与外围类不相关,它们是互相独立的。嵌套类不具备外围类定义的成员,外围类的成员也不具备嵌套类所定义的成员 。
嵌套类的名字在其外围类的作用域中可见。它与外围类定义的类型成员一样。当其为public时,可以在外围类之外访问它,当其为protected时,只能在外围类、外友元或是外围类的派生类访问它。
可以在外围类内部定义并实现嵌套类。如
class outer
{
public:
outer(){}
classinner
{
public:
inner()
{
i=0;
}
void show()
{
}
};
};
当然也可以在外围类的外部定义嵌套类的成员,如:
outer::inner::i=20;
void Outer::inner::show()
{
}
注意:嵌套类的成员不能定义在外围类的内部。
另外一种定义嵌套类的方法为:在嵌套类中声明嵌套类,但是在外围类外部定义整个类。如:
cass outer
{
public:
outer(){}
class inner;
};
class outer::inner
{
inner()
{
i=0;
}
void show()//当然,也可以在外部定义。
{
}
};
如果嵌套类有一个静态成员,则它的定义要在嵌套类外部:
int outer::inners=20;
虽然嵌套类与外围类互相独立,但是嵌套类可以直接引用外围类的静态成员、类型名和枚举成员。
联合
联合是一种特殊的类。一个union对象可以有多个成员,但只能有一个成员有值。当一个值赋给其中一个成员时,其他成员都变为未定义的。
union对象所占空间至少与最大数据成员一样大。
union的定义以关键字union开始,后接可选的union名字和一对以分号结尾的花括号。
union可以跟类一样指定保护标记,使成员成为公用的、私有的或受保护的。默认情况下 ,union表现的像struct。
union不能有静态数据成员、引用成员或类数据成员。
默认情况下,union是未初始化的,可以显式初始化,但是只能为第一个成员提供初始化式。
未给union对象命名的的union成为匿名联合。由于没提供访问其成员的途径,匿名联合的名字出现在外围作用域中。供外围作用域直接访问。
局部类
在函数体内定义的类称为局部类。局部类定义了一个类型,但是只能在定义它的局部作用域中可见。
与嵌套类不同,局部类是严格受限的。局部类的所有成员必须完全定义在类定义体内部。因此成员函数一般仅有数行代码。
局部类不能与static成员,且不能访问函数作用域中的变量。与嵌套类一样,它只能访问外围作用于中的类型名、static变量和枚举成员。
外围类不能访问局部类的private成员。实际上,局部类中的private成员通常是没有必要的。一般局部类的所有成员都是pubic的。
也可以将一个类嵌套在局部类的内部。此时,嵌套类定义可以出现在局部类定义体之外,函数内部。
固有的不可移植的特征
C++从c语言继承而来的不可移植的特征:位域、volatile限定符以及链接指示。
关键字volatile给编译器的指示,指出对这个对象不应该执行优化。
如:
volatile int i=10;
int j = i;
int k = i;
volatile 告诉编译器i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的可执行码会重新从i的地址读取数据放在k中。
而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读到寄存器的数据放在k中。而不是重新从i里面读到寄存器中。这样以来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问,不会出错。
extern 有两种含义:
1:被extern "C"限定的函数或变量是extern类型的;
extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。记住,下列语句:
extern int a;
仅仅是一个变量的声明,其并不是在定义变量a,并未为a分配内存空间。变量a在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。
通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只 需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生 成的目标代码中找到此函数。
2: 被extern "C"修饰的变量和函数是按照C语言方式编译和连接的;
作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例 如,假设某个函数的原型为:
void foo( int x, int y );
该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。_foo_int_int这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函 数void foo( int x, int y )与void foo( int x, float
y )编译生成的符号是不相同的,后者为_foo_int_float。
C++程序有时需要调用其他程序设计语言编写的函数,最常见的是c语言,编译器按处理普通C++函数一样的方式检查对外部语言函数的调用,但是编译器必须产生不同的代码来调用其他语言编写的函数。C++使用链接指示指出任意非C++函数所用的语言。
链接指示不能出现在类或函数定义的内部,它必须出现在函数的第一次声明上。
链接指示有两种形式:
1:单个的:如:
extern “C” size_t strlen(const char*);
由关键字extern后接字符串字面值,再接普通的函数声明。
2复合的。
extern “C”//以C语言的方式使用此函数。
{
intstrcmp(const char*,const char*);
char*strcat(char *,const char *);
}
通过将几个函数的声明放在链接指示之后的花括号内部。花括号的作用是将应用链接指示的声明聚合起来。
也可以将多重声明形式应用于整个头文件,如
extern “C”//如果使用的是C++的方式链接这个C库文件的话,那么就会出现链接错误.
{
#include<string.h>
}
通过对函数定义使用链接指示,使得其他语言可以使用C++函数:
extern “C” double calc(double val)
{
}
当编译器为该函数产生代码时,它将产生适用于指定语言的代码。
C函数的指针与C++函数的指针具有不同的类型,不能将C函数的指针初始化或赋值为C++函数的指针,反之亦然。
由于《C++primer》在介绍extern不太详细,此处部分引用此文http://www.cnblogs.com/flyingfish/archive/2007/02/28/659165.html。在此向原作者致谢。
总结:从五月十五日至今天,整整半个月,其实除去周末,看书的时间大概有10天。将《C++primer》从头至尾又看了一遍!前面面向过程的章节看的比较快,主要是查漏补缺,把容易忘得、易出错的、以前没接触过得知识点整理到笔记里。后面面向对象的章节进度稍微减慢,笔记记录相对详细,也仿照书上写了一些代码。即使以后忘记通过看笔记也可以很快回想起来,我想这是我写笔记的一个很重要的原因吧。中间出现过一些问题,出现了盲目赶进度的情况,也略去了一些东西。比如面向对象编程那一章对句柄使用的例子。觉得《C++primer》在有些知识点上介绍的不太详细。估计这跟它的定位相符:面向初学者。将C++有的东西粗略的介绍下,详细的使用还需要再查阅其他资料。因此以后有必要看一些提高类型的书籍。所有这些即使看了第二遍也不能完全吸收,需要在以后的项目实践中慢慢消化,不断的查漏补缺。
2012年5月31日15:31于郑州
分享到:
相关推荐
C++ Primer 是一本深入学习C++语言的经典教材,这份笔记虽然不完整,但涵盖了书本的前十个章节,提供了关于C++基础语法和核心概念的概述。 1. **变量与初始化** - C++中未初始化的变量使用是一个潜在的问题,因为...
"C++ Primer读书笔记" 本文总结了C++ Primer读书笔记的要点,涵盖了C++语言的基础知识,包括变量、基本类型、常量、输入输出等。 1. 变量和基本类型 C++语言中有多种基本类型,包括整型、浮点型、字符型等。在...
C++ Primer 阅读笔记提供了许多关于C++编程的基础知识和重要概念。以下是这些笔记中的关键点: 1. **GNU 编译器**:C++的常用编译器是`g++`,用于编译C++源代码,如`g++ filename.cc –o executename`,将`filename...
《C++ Primer》是一本经典的C++学习书籍,这篇阅读笔记涵盖了多个重要的C++概念和技术。以下是笔记中提及的关键知识点的详细解释: 1. **浮点数类型选择**:在C++中,`double`类型的浮点数通常比`float`具有更高的...
### C++ Primer 读书笔记概览 在深入探讨C++ Primer各章节的精要之前,我们先简要回顾一下这本经典教材的核心价值。《C++ Primer》是学习C++编程语言的必读之选,它不仅覆盖了C++的基础语法,还深入讲解了面向对象...
### C++ Primer(4ed)读书笔记知识点梳理 #### 一、基础数据类型的选择 - **整型数据的选择**: - **32位 `int`**:在大多数现代计算机系统中,`int` 类型通常占用 32 位。 - **64位 `long`**:在某些系统中,`...
C++ Primer笔记主要涵盖C++语言的基础语法和标准库的使用。以下是这些知识点的详细解释: 1. **基本语法**: - **main函数**:程序的入口点,其返回值用于表示程序执行状态,0表示成功执行。 - **标准输入与输出*...
### C++ Primer 笔记知识点总结 #### 2.1 类型定义与基本数据类型 - **基础数据类型**:包括 `char` 和 `wchar_t`。`char` 类型通常用于存储单个字符,而 `wchar_t` 类型则用于宽字符(如 Unicode 字符)。 - **...
标题《C++ Primer中文版(第四版)学习笔记收集.pdf》表明这份文档是关于C++语言学习的笔记整理,参考的是经典教材《C++ Primer》的第四版。这份笔记将涵盖该书中重要的知识点和示例,对于学习C++基础语法和面向对象...
C++ Primer 中文版第四版的学习笔记涵盖了C++的基础知识,包括程序结构、变量和基本类型以及标准库类型。在快速入门章节,我们了解到每个C++程序至少包含一个名为`main`的函数,这是程序执行的起点。操作系统直接...
《C++ Primer Plus》读书笔记(三)涵盖了C++编程中的关键概念,主要涉及程序的组织结构、编译过程、存储持续性、链接性、变量声明以及与内存管理相关的操作。以下是这些知识点的详细说明: 1. **C++程序的组成**: ...
### C++ Primer Plus 学习笔记关键知识点解析 #### 一、预编译指令与宏定义 **预编译指令**是在编译之前由预处理器处理的指令,它们不被视为程序的一部分,而是作为预处理阶段的一部分来执行。预编译指令在C++编程...