视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
C++程序设计 第十六章 NSI C++标准语法补充
2025-10-02 07:37:50 责编:小OO
文档
C++程序设计

第16章 ANSI C++标准语法补充

    大多数C++语言系统都支持ANSI/ISO C++标准。本章介绍该标准中的部分内容,作为前面各章节的语法补充。本章将介绍逻辑型bool、命名空间namespace、两个修饰符explicit和mutable、运行时刻类型信息RTTI和typeid运算符、以及4种新型的强制类型转换运算符。本章各部分之间相对,相互之间没有严格次序。

16.1 逻辑型bool

    C语句中没有逻辑类型,而C++标准有逻辑型bool。逻辑型也被称为布尔型。逻辑值只有真true和假false两个值,而且只能进行逻辑运算。C语言用整数int来表示逻辑值,0值表示false,非0为true。整数表示逻辑值的缺点是算术运算的结果可直接作为逻辑值,而且“逻辑值”也可进行算术运算,这不符合高级编程语言的要求。

C++可定义bool类型变量,可直接赋值true或false,可作为关系表达式和逻辑表达式的计算结果。bool类型变量支持逻辑运算非!、与&&、或||运算。一个bool值在内存中占1字节,故此sizeof(bool)为1。但内部采用整数值0表示false,1表示true。

    例16-1逻辑型bool的例子。

#include         

#include         

using namespace std;    

void main(){

    bool boolean = false;

    int x = 0;

cout<<"boolean is "<        <<"\\nEnter an integer:";

cin >> x;

    cout<<"integer " <        <<(x ? "nonzero":"zero")

        <<" and interpreted as ";

    if (x)

        cout<<"true\\n";

    else

        cout<<"false\\n";

    bool b = true;

    boolean = false && b;

cout<<"boolean is "< cout<<"\\nboolean output with boolalpha manipulator is "

        < cout<<"sizeof(boolean)="<    

boolean = x * 3 > 10;

    if (boolean)

        cout<<" x * 3 > 10"<    else

        cout<<" x * 3 <= 10"<}

执行程序,输入45,输出结果如下:

boolean is 0

Enter an integer:45

integer 45 is nonzero and interpreted as true

boolean is 0

boolean output with boolalpha manipulator is false

sizeof(boolean)=1

x * 3 > 10

    本质上bool类型仍然是一个整型值。在cout<<输出时,仍然输出0或1,而不是false或true,除非用boolalpha作为格式控制符。另一个需要注意的是bool类型变量仍然可以进行算术运算,例如:

bool b2 = true, b3 = false;

    bool b4 = b2 - b3;            //bool型变量之间不应该允许算术运算。

    对上面语句,编译器仅给出警告而不是错误。

    但无论如何,引入bool类型本身就是一种改进,建议在C++程序中尽可能采用bool类型。

16.2 命名空间namespace

命名空间(namespace)是解决大程序中多个元素(如类、全局函数和全局变量)命名冲突的一种机制。当我们要把几个部分(往往来自不同的人员或团队)合并成为一个大程序时,往往就会出现命名冲突的问题:类名、全局函数名、全局变量名都可能重名。解决的方法就是把这些名字放在不同的命名空间中,在访问这些名字时使用各自的命名空间作为限定符。

16.2.1 命名空间的定义

命名空间类似文件系统中的目录,空间中的成员类似目录中的文件。全局空间相当根目录,一个目录名作为其中多个文件的命名空间,子目录作为嵌套空间,文件作为空间中的成员。同时一个命名空间也是一个作用域。

命名空间的基本规则如下:

●一个程序所用的多个命名空间在相同层次上不重名;

●在同一个命名空间中的所有成员名字不重复;

●在一个命名空间中可以嵌套定义其内层的多个子空间。

定义命名空间的语法格式如下:

namespace [<空间名>]{一组成员}

其中,namespace是关键字,后面给出一个空间的名字标识符。后面用花括号括起来一组成员,可以是一组类、一组函数、一组变量。如果空间名缺省则为无名空间。无名空间中的元素类似于全局变量,只是在本文件中访问,而全局空间中的成员可以被其它文件访问。

命名空间可嵌套说明,就如同目录与子目录之间的关系。

    例如下面代码:

int myInt = 98;                 //全局空间中的变量

namespace Example{                //说明了一个Example空间

    const double PI = 3.14159;    //Example中的变量成员

    const double E = 2.71828;    //Example中的变量成员

    int myInt = 8;                //Example中的变量成员,隐藏了全局同名变量

    void printValues();            //Example中的函数成员的原型

    namespace Inner{                //嵌套空间,名为Inner

        enum Years{FISCAL1 = 2005, FISCAL2, FISCAL3};    //嵌套空间中的成员

        int h = ::myInt;        //用全局变量进行初始化,98

int g = myInt;            //用外层成员进行初始化,8

    }

}

namespace{                       //无名空间

    double d = 88.22;            //无名空间中的变量成员

    int myInt = 45;                //无名空间中的变量成员

}

图16.1 空间的结构

图16.1表示了上面例子所说明的空间的结构。

在描述成员的全称时,要使用作用域运算符“::”。例如,Example是一个空间,其中各成员的全称为:

    Example::PI;

    Example::E;

Example::myInt;    

Example::printValues();    

Example::Inner是一个嵌套空间,Example被称为Inner的外层空间,嵌套空间中的成员的全称为:

Example::Inner::Years

Example::Inner::h

Example::Inner::g

    

16.2.2 空间中成员的访问

如何访问某空间中的成员?这与访问文件系统中的文件相似。访问文件可用相对路径,也可用全路径。全路径就是把全部路径名作为文件名的限定符。

如何查找一个成员?在当前空间中对一个名字k的访问需要查找过程,在编译时确定被访问的实体。有以下3种形式:

1、限定名形式:“空间名::k”。先在当前空间的限定嵌套空间中查找k,相当于一个相对路径。在嵌套空间中如果未找到成员k,就将该空间名作为全路径的空间名再查找成员k,如果仍未找到,就给出错误信息。这种访问形式对相对路径的空间名优先。

2、全局限定形式:“::k”。在全局空间中查找,如果未找到就出错。

3、无限定形式:“k”。按局部优先原则,先在当前空间中查找。如果未找到,就向外层空间找,直到全局空间。如果未找到,就从导入空间中查找,如果找到一个就确定,如果在全局空间和导入空间中找到多于一个就指出二义性错误。如果仍未找到k就是一个错误名字。

如果要多次访问某个空间中的成员,你可能觉得成员名前面总是挂上一串空间名很麻烦。有一个简单方法就是用using namespace语句来导入(import)一个命名空间。格式如下:

using namespace [::] [空间名::] 空间名;

其中,using和namespace是关键字,然后指定一个空间名,或者一个嵌套空间名。例如:

using namespace std;

就是导入空间名“std”,所有标准C++库都定义在命名空间std中,也包括标准模板库STL,因此这条语句经常使用。

导入空间语句仅在当前文件中有效。在一个文件中可以有多条导入语句,来导入多个空间名。

实际上,一个无名空间的定义都隐含着以下两条语句:

namespace unique{...}

using namespace unique;

其中unique是系统隐含的一个唯一的空间名。导入命令使得无名空间中的成员仅能在本文件中访问,因此无名空间就是一种缺省导入的空间。

如果只想导入某空间中的某个成员,而不是全部成员,可按下面格式说明:

using [::]空间名::成员名;

例16-2 命名空间的定义和访问。

#include

using namespace std;

int myInt = 98;

namespace Example{

    const double PI = 3.14159;

    const double E = 2.71828;

    int myInt = 8;

    void printValues();

    namespace Inner{

        enum Years{FISCAL1 = 2005, FISCAL2, FISCAL3};

        int h = ::myInt;

        int g = myInt;

    }

}

namespace{

    double d = 88.22;

    int myInt = 45;

}

void main(){

cout<<"In main";

    cout<<"\\n(global) myInt = "<<::myInt;            //A

    cout<<"\\nd = "< cout<<"\\nExample::PI = "<        <<"\\nExample::E = "<        <<"\\nExample::myInt = "<        <<"\\nExample::Inner::FISCAL3 = "<        <<"\\nExample::Inner::h = "<        <<"\\nExample::Inner::g = "<    Example::printValues();

}

void Example::printValues(){

cout<<"\\nIn Example::printValues:\\n"

        <<"myInt = "<        <<"\\nd = "<        <<"\\nPI = "<        <<"\\nE = "<        <<"\\n::myInt = "<<::myInt                    //D

        <<"\\nInner::FISCAL3 = "<        <<"\\nInner::h = "<        <<"\\nExample::Inner::g = "<}

执行程序,输出结果如下:

In main

(global) myInt = 98

d = 88.22

Example::PI = 3.14159

Example::E = 2.71828

Example::myInt = 8

Example::Inner::FISCAL3 = 2007

Example::Inner::h = 98

Example::Inner::g = 8

In Example::printValues:

myInt = 8

d = 88.22

PI = 3.14159

E = 2.71828

::myInt = 98

Inner::FISCAL3 = 2007

Inner::h = 98

Example::Inner::g = 8

函数main是全局函数,其中代码要访问某个空间中的成员就要给出绝对路径全称。注意到在全局空间和无名空间中都定义了myInt成员,因此在全局空间中如果直接用“myInt”来访问,就会造成二义性。假如A行用“myInt”来访问,就会产生二义性。用“::myInt”是就访问全局变量。B行用“d”来访问无名空间中的成员,此时无二义性。

函数printvalues是Example空间中的成员,其中代码可用绝对路径,也可用相对路径来访问成员。C行用“myInt”访问当前空间中的成员,尽管在全局空间和无名空间中都有同名成员,但当前命名空间中的成员具有优先权,故此无二义性。D行用“::myInt”限定全局变量。E行和F行采用了相对路径,而G行采用了绝对路径,给出了全称。

对于命名空间的使用,应注意以下要点:

●尽量使全局空间中的成员最少,这样发生冲突的可能性就会减少。

●空间的命名应尽可能地表示自然结构,而不仅仅是为了避免命名冲突。

●在一个源文件中应尽可能避免说明多个平行的空间。

●二义性往往发生在全局空间和导入空间中含有同名元素,而程序中用无限定的形式来访问该元素。

16.3 修饰符explicit

我们在第10章介绍过,在一个类中,如果说明了单参构造函数,就可用于隐式的类型转换。这种隐式的类型转换可能会被误用,尤其是在函数调用时,实参到形参的隐式转换,可能在不经意之间就创建了对象。

例如,如果一个函数的形参是一个对象或对象引用,而该对象类型恰好有单参构造函数,那么调用方就可利用此形参隐式创建新对象,再传给函数做实参。用explicit修饰符就能避免这种无意的创建对象。

    如果将一个单参构造函数修饰为explicit,该函数就不能用于隐式的类型转换,而只能用于显式地创建对象。

例16-3 分析下面例子。

#include

#include

class IntArray{                        //一个整数数组类

    int size;

    int *ptr;

    friend ostream &operator<<(ostream &, const IntArray &);    //运算符<<

public:

    IntArray(int = 10);                //A 单参构造函数,同时也是缺省构造函数

    ~IntArray();

};

IntArray::IntArray(int arraySize){        //单参构造函数

size = (arraySize>0 ? arraySize : 10);

    ptr = new int[size];

    assert(ptr != 0);                         //用断言检查点

    for (int i = 0; i < size; i++)            //置缺省值

        ptr[i] = 0;

}

IntArray::~IntArray(){delete[] ptr;}        //析构函数

ostream &operator<<(ostream &output, const IntArray &a){    //运算符<<重载函数

    for (int i = 0; i < a.size; i++)                        //输出各元素

        output<    return output;

}

void outputArray(const IntArray &a){    //B全局函数,调用<<输出各元素

cout<<"The array:\\n"<}

void main(){

  IntArray integers1(7);    //说明一个整数数组

  outputArray(integers1);    //调用全局函数,输出个元素

  outputArray(15);            //C 隐式创建对象

}

    A行说明了一个单参构造函数,形参为一个int,这意味着可以从一个int值来创建一个IntArray对象。例如:IntArray a1= 15; 在需要一个IntArray对象的地方,提供一个int值就能自动创建一个IntArray对象,再提供给它。B行函数形参恰好就是IntArray对象,那么C行调用函数时提供了一个int值15,实际上转换为

outputArray(IntArray(15));

所以语法上没有错误。但实际上可能不是你想要的。你只是无意中犯了一个错误,所以希望C行能给出编译错误,而不想自动创建一个IntArray对象。

此时修饰符explicit就有用了。只要对类中构造函数原型添加这样一个修饰,如下:

explicit IntArray(int =10);

就可以避免这种隐式的创建对象,此时C行代码编译报错。

对于一些“比较大”的类,比如STL容器类(如vector、deque、list等),它们的单参构造函数都用explicit修饰,目的就是避免误建对象。

16.4 修饰符mutable

    我们在第10章介绍过,用const修饰的成员函数不能改变对象的状态,即不能改变数据成员的值。但有个例外,用mutable修饰的数据成员可以被改变。

    例16-4 分析下面例子。

class TestMutable{

    mutable int value;                    //A 用mutable修饰的数据成员

public:

    TestMutable(int v = 0){value = v;}

    void modifyValue() const {value++;}    //B const函数中可改变mutable成员

    int getValue() const {return value;}

};

void main(){

    const TestMutable t(99);

    cout<<"Initial value is "<    t.modifyValue();

cout<<"\\nModified value is "<}

A行用mutable说明了一个数据成员。B行是一个const函数,但能改变这个数据成员。

修饰符mutable的用途就是说明一个数据成员是可变的,在任何成员函数中都可改变。

注意,mutable与修饰符volatile的区别。volatile修饰符说明一个数据成员是易变的,随时可能被程序外部其它东西所改变,如操作系统、硬件、并发线程等。它要求编译器对它的访问不能进行优化。volatile对于一般的编程没有直接影响。而mutable对于编程是有影响的,const成员函数中不能改变数据成员的值,但除了mutable修饰的成员。

16.5 RTTI与typeid

    RTTI是Run-Time Type Information运行期类型信息的简写。在运行时刻我们常常要利用RTTI动态掌握有关类型的信息,尤其是对于抽象类编程和模板编程。在抽象层面上可根据具体类型做出不同处理,更方便通用性编程。

    typeid是一个关键字,就像sizeof一样,以函数的形式,在运行时刻获得指定对象或表达式的类型信息。typeid有下面两种形式:

const type_info& typeid( type-id )        //求类型type-id的具体类型

const type_info& typeid( expression )    //求表达式的具体类型

返回值的类型type_info是一个对象类,定义在typeinfo.h文件中:

class type_info {

public:

virtual ~type_info(); 

int operator==(const type_info& rhs) const;   

int operator!=(const type_info& rhs) const;   

int before(const type_info& rhs) const;   

const char* name() const;                       //读取类型的名字

const char* raw_name() const;

};

可以用“typeid(<表达式>).name()”来获取<表达式>在运行时刻的类型名称。对于基本类型就直用其名,如“int”、“double”。对于类名,要加前缀“class”。

例16-5 运行期类型信息的例子。

#include

#include

#include

using namespace std;

template

T maxmum(T value1, T value2, T value3){

    T max = value1;

if (value2 > max)

        max = value2;

if (value3 > max)

        max = value3;

    const char * dataType = typeid(T).name();                    //A 

cout<        <<"\\nLargest "<    return max;

}

class A{

public:

    void f(){

        cout<<"f1 this is "<    }

    void f()const{

        cout<<"f2 this is "<    }

};

void main(){

    int a = 2,b = 3, c= 1;

    double d = 4.3, e = 7.7, f= 2.3;

    string s1 = "one", s2 = "two", s3 = "three";

cout< cout< cout<    A a1;

    a1.f();

    const A a2;

    a2.f();

    const char * str1 = "const string";                            //D

cout<    char const * str2 = "const string";                            //E

cout<    cout<    cout<<"type of a > b is "< b).name()<}

执行程序,输出结果如下:

ints are compared.

Largest int is 3

doubles are compared.

Largest double is 7.7

class std::basic_string,class std::allocator<

char> >s are compared.

Largest class std::basic_string,class std::al

locator > is two

f1 this is class A *

f2 this is class A const *

char const *

char const *

char [6]

type of a > b is bool

A行读取模板形参T的运行时刻具体类型的名称,下面再显示出来。A行中也可对某个形参变量来处理,如...=typeid(value1).name(),得到一样结果。

可看到string的运行时刻的具体类型,“class std::basic_string,class std::allocator >”,这些信息对于通用性模板的编程分析具有重要作用。

B行和C行分别显示在非const成员函数和const成员函数中的this的类型。可以看到,在非const成员函数中的this类型为class A *,而在const成员函数中则为class A const *。实际上,我们习惯将后者表示为class const A *,将const放在类型名之前,与放在类型名与*之间是一样的。因此D行与E行描述的两个字符串类型是一样的。

对于字符串字面常量的类型,也可以看到,F行显示char [6]。

关系表达式的类型是bool型,而不是int,G行显示可以验证。

16.6 强制类型转换

我们前面介绍了强制类型转换cast,作用于基本类型的变量或表达式,转换为另一种类型。在面向对象编程中也介绍了强制类型转换,将一个基类的指针或引用强制转换为其派生类的指针或引用。一般称为“向下转换”或者“窄化”。传统类型转换形式为“(类型名)变量名/表达式”,存在的问题是过于笼统、功能过强而易失控、不够安全。

C++标准中引入了4个新的强制类型转换运算符,以取代传统的强制类型转换,好处是更具体、功能弱化、相对安全。表16.1给出了这4种新型类型转换的特点。

表16.1 新型类型转换

类型转换语法  (注意尖括号不能缺少)

特点
静态转换static_cast<目标类型名>(变量名/表达式)

编译时检查合法性。比较安全
动态转换dynamic_cast<目标类型名>(变量名/表达式)

运行时刻检查合法性,适用于指针转换。转换之后应判断指针是否为0,若为0则转换失败,若非0则转换成功。比较安全

常量转换const_cast<目标类型名>(变量名)

专门对const或volatile修饰的变量进行转换。会破坏既有的const约定,建议慎用。

重释转换

reinterpret_cast<目标类型名>(变量名/表达式)

专门处理指针转换。指针之间的转换,也可将指针随意转换到其它类型,或将其它类型转换到指针。技巧性和危险性并存。
下面介绍这4种运算符。

16.6.1 static_cast运算符

    静态转换在编译时刻进行类型检查。对于非法的转换将给出错误信息。例如,将const类型转换为非const类型、把基类转换到非公有继承的派生类、从一个类型转换到不具有特定构造函数或转换函数的类型等。对于基本类型,采用静态转换替代传统转换是比较安全的。

语法格式如下:

static_cast<目标类型名>(变量名/表达式)

例16-6 静态转换的例子。

#include

class BaseClass{

public:

    void f()const{cout<<"BASE\\n";}            //A  非虚函数

};

class DerivedClass: public BaseClass{

public:

    void f()const{cout<<"Derived\\n";}        //B

};

void test(BaseClass * basePtr){

    DerivedClass *derivedPtr;

    derivedPtr = static_cast(basePtr);     //C

    derivedPtr = (DerivedClass *)basePtr;                    //D

derivedPtr->f();

}

void main(){

    double d = 8.22;

    int x = d;                            //E warning: possible loss of data

    int y = (int)d;                        //F

    int z = static_cast(d);        //G double -> int 

    double d2 = y;                        //H

cout<<"x is "< cout<<"y is "< cout<<"z is "< cout<<"d2 is "<    char * str1 = "C++";

    void * vp = str1;                    //I

    char * str2 = (char *)(vp);            //J 

    char * str3 = static_cast(vp);    //K 

    if (str2 == str3)

        cout<<"str2 == str3"<    cout< cout<    BaseClass base;

    test(&base);

}

执行程序,输出结果如下:

x is 8

y is 8

z is 8

d2 is 8

str2 == str3

C++

C++

Derived

A行定义的函数是非虚函数,B行派生类定义的同名同参函数并非改写override。

C行使用了静态转换,效果与D行的传统转换一样。

E行将一个double赋给一个int,导致一个警告。

F行是传统转换,与G行的静态转换效果一样。

H行将一个int赋给一个double,不会导致警告。

I行将一个char*赋给一个void*,不会导致警告。

J行使用传统转换,与K行的静态转换效果一样。

可以看出,多数传统的类型转换都可用静态转换来实现,只是静态转换的类型检查更严格。如果将公有继承改变为缺省的私有继承,静态转换的C行将报错,而传统转换的D行却不报错。

16.6.2 dynamic_cast运算符

这种转换被称为“动态转换”。在运行时刻进行类型检查,一般作用于类的指针或引用,也包括void指针。对于指针进行动态转换之后,应立即判断新的指针值是否为0,如果为0,表示转换失败,如果非0,表示转换成功,然后才能通过该指针进行操作。

语法格式如下:

dynamic_cast<目标类型名>(变量名/表达式)

例16-7 动态转换的例子。

#include

#include

class B{

public:

    virtual void foo(){cout<<"Base"<};

class D:public B{

public:

    void foo(){cout<<"Drived"<};

void func(B *pb){

    if (pb == 0) return;

    cout<    D *pd1 = static_cast(pb);                            //D

pd1->foo();

    D *pd2 = dynamic_cast(pb);                            //E

    if (pd2 != 0)

        pd2->foo();

    else

        cout<<"pd2 == 0"<}

void main(){

    D d;

    func(&d);                                                //F

    B b;

    func(&b);                                                //G

}

在VC++6环境中编译上面程序时,要求改变项目选项,否则就会对C和D行给出警告,而且会造成运行错误。方法如下:菜单“Project”下拉选择“Settings…”菜单,在对话框中选择“C/C++”,然后在下方“Project Options”中添加“/GR”,按“OK”。

执行程序,输出结果如下:

class D object is provided

Drived

Drived

class B object is provided

Base

pd2 == 0

    A行在基类中说明一个虚函数,使A类成为多态性基类。派生类D在B行改写了这个虚函数。C行用typeid获得对象的实际类型,再用name函数读取名字并显示出来。

D行用静态转换将形参B* pb转换为派生类指针D*pd1。这对于F行调用是安全的,因为实参就是派生类的对象。但对于G行调用就出错了,因为实参是基类对象,而用派生类指针来操作,虽然能调用虚函数,但仍执行基类的函数,而不是派生类的函数。

E行用动态转换形参B* pb转换为派生类指针D*pd2。如果转换成功,指针为非0;如果失败,指针为0。对于0指针不能进行任何操作。因此对于类的指针的转换,使用动态转换是比较安全的。

16.6.3 const_cast运算符

    这种转换被称为“常量转换”,专门对const或volatile修饰的变量进行转换。

    语法格式如下:

    const_cast<目标类型名>(变量名)    

    例16-8 常量转换的例子。

#include

class ConstCastClass{

    int number;

public:

    void setNumber(int num){number = num;}

    int getNumber()const{return number;}

    void printNumber() const{                                //A

        ConstCastClass* newThis;

        newThis = const_cast(this);    //B

        newThis = (ConstCastClass* const)this;                //C

        newThis->number--;                                    //D

        cout<<"\\nNumber after modification:"<    }

};

void main(){

    ConstCastClass x;

    x.setNumber(8);

cout<<"Initial value of number is "<    x.printNumber();

}

执行程序,输出结果如下:

Initial value of number is 8

Number after modification:7

A行定义成员函数为const函数,那么该函数中就不能改变数据成员number的值。这是利用this指针的const性质来实现的。

在非const成员函数中this指针的类型为“class 类名*const”,不能改变this的值使其指向另一个对象,但能改变当前对象的值。

在const成员函数中this指针的类型为“const class 类名*const”,不能改变this的值,也不能通过this指针来改变数据成员的值,也不能通过this指针调用非const函数。

B行用常量转换将this的类型由“const ConstCastClass *const”转换为“ConstCastClass* const”,这样通过该指针就能改变数据成员number的值。这个效果与C行传统转换一样。最终结果是在一个const函数中改变了一个数据成员的值。使用mutable来修饰一个数据成员也能达到同样目的,但常量转换后能改变所有的数据成员,而不限一个。

可以看出,使用常量转换会破坏函数的约定,建议慎用。

16.6.4 reinterpret_cast运算符

这种转换被称为“重释转换”,只能对指针进行转换或者转换到指针,最具技巧性,也最危险。

语法格式如下:

reinterpret_cast<目标类型名>(变量名/表达式)

例16-9 分析下面例子。

#include

void main(){

    unsigned x = 22, *unsignedPtr;

    void * voidPtr = &x;                                    //A

    char * charPtr = "C++";

    unsignedPtr = reinterpret_cast(voidPtr);    //B

cout<<"*unsignedPtr is "<<*unsignedPtr

        <<"\\ncharPtr is "< cout<<"\\nchar * to unsigned result in:"

        <<(x = reinterpret_cast(charPtr));        //C

cout<<"\\nunsigned to char * result in:"

        <(x)<    int i = 9;

    double *d = reinterpret_cast(&i);                //E

cout<<*d<}

执行程序,输出结果如下:

*unsignedPtr is 22

charPtr is C++

char * to unsigned result in:4350096

unsigned to char * result in:C++

2.07234e-307

A行使一个void*指针指向一个unsigned变量,B行用重释转换将这个void*指针转换为unsigned*指针,使unsignedPtr指针指向x。然后用cout语句通过该指针访问x的值,没有错误。

C行用重释转换将一个char*指针转换为一个unsigned值,赋给x并输出,可以看到该指针的值,就是字符串"C++"的存储地址。此时你可以随便改变x的值。D又用重释转换将unsigned变量x转换为一个char*,并输出。

E行用重释转换将一个int*转换为一个double*,而得到与原值不相干的结果。

可以看出,重释转换可以将指针随意转换到其它类型,或将其它类型转换到指针。具危险性,建议读者慎用。

小 结

●C++支持逻辑型bool,可直接赋值true或false,也可作为关系表达式或逻辑表达式的结果。内存中占1字节。bool类型变量支持逻辑运算非!、与&&、或||运算。

●命名空间(namespace)是解决大程序中多个元素(如类、全局函数和全局变量等)命名冲突问题的一种机制。

●一个程序所用的多个命名空间在相同层次上不重名;在同一个命名空间中的所有成员名字不重复;一个命名空间可以嵌套定义其内层的多个不重名的空间。

●使用namespace关键字来定义命名空间,及其成员。嵌套空间也如此定义。

●用作用域运算符“::”来分隔空间名以及成员名。

●使用using namespace来导入空间,以方便访问特定空间中的成员。

●当用一个名字k来访问成员时,按局部优先原则,先在当前空间中查找。如果未找到,就向外层空间找,直到全局空间。如果未找到,就从导入空间中查找。如果在全局空间和导入空间中找到多于一个就是二义性错误。

●修饰符explicit用来单参构造函数,避免隐式地创建对象,对于大对象类有用。

●修饰符mutable使类的数据成员可改变,而不管是否在const成员函数之中。const成员函数中不能改变数据成员的值,但除了mutable修饰的成员。

●运行期类型信息RTTI,Run-Time Type Information在运行时刻掌握类型信息对于通用性编程,对于复杂程序的分析都有用。typeid是关键词,需要包含typeinfo.h文件,常用“typeid(<表达式>).name()”来获取<表达式>在运行时刻的类型名称。

●传统的强制类型转换存在的问题是过于笼统、功能过强而易失控、不够安全。标准C++引入了4个新的强制类型转换运算符,以取代传统的强制类型转换,好处是更具体、功能弱化、相对安全。下载本文

显示全文
专题