最新文章专题视频专题问答1问答10问答100问答1000问答2000关键字专题1关键字专题50关键字专题500关键字专题1500TAG最新视频文章推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37视频文章20视频文章30视频文章40视频文章50视频文章60 视频文章70视频文章80视频文章90视频文章100视频文章120视频文章140 视频2关键字专题关键字专题tag2tag3文章专题文章专题2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章专题3
当前位置: 首页 - 正文

C++程序设计 第十六章 NSI C++标准语法补充

来源:动视网 责编:小OO 时间:2025-10-02 07:37:50
文档

C++程序设计 第十六章 NSI C++标准语法补充

C++程序设计第16章ANSIC++标准语法补充大多数C++语言系统都支持ANSI/ISOC++标准。本章介绍该标准中的部分内容,作为前面各章节的语法补充。本章将介绍逻辑型bool、命名空间namespace、两个修饰符explicit和mutable、运行时刻类型信息RTTI和typeid运算符、以及4种新型的强制类型转换运算符。本章各部分之间相对,相互之间没有严格次序。16.1逻辑型boolC语句中没有逻辑类型,而C++标准有逻辑型bool。逻辑型也被称为布尔型。逻辑值只有真true和
推荐度:
导读C++程序设计第16章ANSIC++标准语法补充大多数C++语言系统都支持ANSI/ISOC++标准。本章介绍该标准中的部分内容,作为前面各章节的语法补充。本章将介绍逻辑型bool、命名空间namespace、两个修饰符explicit和mutable、运行时刻类型信息RTTI和typeid运算符、以及4种新型的强制类型转换运算符。本章各部分之间相对,相互之间没有严格次序。16.1逻辑型boolC语句中没有逻辑类型,而C++标准有逻辑型bool。逻辑型也被称为布尔型。逻辑值只有真true和
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个新的强制类型转换运算符,以取代传统的强制类型转换,好处是更具体、功能弱化、相对安全。

文档

C++程序设计 第十六章 NSI C++标准语法补充

C++程序设计第16章ANSIC++标准语法补充大多数C++语言系统都支持ANSI/ISOC++标准。本章介绍该标准中的部分内容,作为前面各章节的语法补充。本章将介绍逻辑型bool、命名空间namespace、两个修饰符explicit和mutable、运行时刻类型信息RTTI和typeid运算符、以及4种新型的强制类型转换运算符。本章各部分之间相对,相互之间没有严格次序。16.1逻辑型boolC语句中没有逻辑类型,而C++标准有逻辑型bool。逻辑型也被称为布尔型。逻辑值只有真true和
推荐度:
  • 热门焦点

最新推荐

猜你喜欢

热门推荐

专题
Top