[C++学习笔记] 第 12 章 友元、异常和其他

第 12 章 友元、异常和其他

12.1 友元

​ 类并非只能拥有友元函数,也可以将类作为友元。在这种情况下,友元类的所有方法都可以访问原始类的私有成员和保护成员

12.1.1 友元类

​ 假定需要编写一个模拟电视机和遥控器的程序,决定编写一个 TvRemote 类。这两个类存在一定关系,但这种关系并不是 is-ahas-a,而是遥控器可以改变电视机的状态,这表明应将 Remote 类作为 Tv 类的一个友元。

​ 下面的语句可以使得 Remote 成为友元类:

friend class Remote;

​ 友元声明可以位于公有、私有或保护部分,其所在位置无关紧要,如:

class Tv
{
private:
	int state;
	int volume;
	int maxchannel;
	int channel;
	int mode;
	int input;
public:
	friend class Remote;
	enum { Off, On };
	enum { MinVal, MaxVal = 20 };
	enum { Antenna, Cable };
	enum { TV, DVD };

	Tv(int s = Off, int mc = 125) :state(s), volume(5), maxchannel(mc), channel(2), mode(Cable), input(TV) {  }
	void onoff() { state = (state == On ? Off : On); }
	bool ison() const { return state == On; }
	bool volup();
	bool voldown();
	void chanup();
	void chandown();
	void set_mode() { mode = (mode == Antenna ? Cable : Antenna); }
	void set_input() { input = (input == TV ? DVD : TV); }
	void settings() const;
};

class Remote
{
private:
	int mode;		//controls tv or dvd
public:
	Remote(int m = Tv::TV) :mode(m) {  }
	bool volup(Tv& t) { return t.volup(); }
	bool voldown(Tv& t) { return t.voldown(); }
	void onoff(Tv& t) { t.onoff(); }
	void chanup(Tv& t) { t.chanup(); }
	void chandown(Tv& t) { t.chandown(); }
	void set_chan(Tv& t, int c) { t.channel = c; }
	void set_mode(Tv& t) { t.set_mode(); }
	void set_input(Tv& t) { t.set_input(); }
};
#include "Tv.h"
#include <iostream>
using namespace std;

bool Tv::volup()
{
	if (volume < MaxVal)
	{
		volume++;
		return true;
	}
	else
		return false;
}
bool Tv::voldown()
{
	if (volume > MinVal)
	{
		volume--;
		return true;
	}
	else
		return false;
}
void Tv::chanup()
{
	if (channel < maxchannel)
		channel++;
	else
		channel = 1;
}
void Tv::chandown()
{
	if (channel == 1)
		channel = maxchannel;
	else
		channel--;
}
void Tv::settings() const
{
	cout << "TV is " << (state == Off ? "Off" : "On") << endl;
	if (state == On)
	{
		cout << "Volume setting = " << volume << endl;
		cout << "Channel setting = " << channel << endl;
		cout << "Mode = " << (mode == Antenna ? "Antenna" : "Cable") << endl;
		cout << "Input = " << (input == TV ? "TV" : "DVD") << endl;
	}
}
12.1.2 友元成员函数

​ 可以选择仅让特定的类成员成为另一个类的友元,而不必让整个类成为友元。例如:

class Tv
{
    friend void Remote::set_chan(Tv &t, int c);
    ...
};

​ 这将使得 Remote 中的方法 set_chan 成为 Tv 类的友元。

​ 要使编译器能够处理这条语句,它必须知道 Remote 的定义,而 set_chan()Remote 类的方法,这意味着 Remote 的定义应放在 Tv 类之后,这将导致循环依赖。避开这种依赖的方法是,使用前向声明:

class Tv;
class Remote {...};
class Tv {...};

​ 能否按以下顺序排列呢?

class Remote;
class Tv {...};
class Remote {...};

​ 答案是不能。原因在于:在编译器在 Tv 类的声明中看到 Remote 的一个方法被声明为 Tv 类的友元之前,应先看到 Remote 类的声明和 set_chan() 方法的声明。

​ 还有一个麻烦:Remote 声明中包含了内联代码:void onoff(Tv & t) { t. onoff(); }。由于 Tv 类的声明在 Remote 之后,这将导致无法通过编译。解决方法是:使 Remote 声明中仅包含方法声明,并将实际的定义放在 Tv 类之后,并使用关键字 inline 使其仍然可以成为内联方法。完整代码如下:

class Tv;

class Remote
{
private:
	int mode;
public:
	enum State { Off, On };
	enum { MinVal, MaxVal = 20 };
	enum { Antenna, Cable };
	enum { TV, DVD };

	Remote(int m = TV) :mode(m) {  }
	bool volup(Tv& t);
	bool voldown(Tv& t);
	void onoff(Tv& t);
	void chanup(Tv& t);
	void chandown(Tv& t);
	void set_chan(Tv& t, int c);
	void set_mode(Tv& t);
	void set_input(Tv& t);
};

class Tv
{
private:
	int state;
	int volume;
	int maxchannel;
	int channel;
	int mode;
	int input;
public:
	friend void Remote::set_chan(Tv& t, int c);
	enum State { Off, On };
	enum { MinVal, MaxVal = 20 };
	enum { Antenna, Cable };
	enum { TV, DVD };

	Tv(int s = Off, int mc = 125) :state(s), volume(5), maxchannel(mc), channel(2), mode(Cable), input(TV) {  }
	void onoff() { state = (state == On ? Off : On); }
	bool ison() const { return state == On; }
	bool volup();
	bool voldown();
	void chanup();
	void chandown();
	void set_mode() { mode = (mode == Antenna ? Cable : Antenna); }
	void set_input() { input = (input == TV ? DVD : TV); }
	void settings() const;
};

inline bool Remote::volup(Tv& t) { return t.volup(); }
inline bool Remote::voldown(Tv& t) { return t.voldown(); }
inline void Remote::onoff(Tv& t) { t.onoff(); }
inline void Remote::chanup(Tv& t) { t.chanup(); }
inline void Remote::chandown(Tv& t) { t.chandown(); }
inline void Remote::set_chan(Tv& t, int c) { t.channel = c; }
inline void Remote::set_mode(Tv& t) { t.set_mode(); }
inline void Remote::set_input(Tv& t) { t.set_input(); }

​ 内联函数的链接性是内部的,这意味着函数定义必须在使用函数的文件中。一般可以放在头文件中。

12.1.3 其他友元关系

​ 有时友情可能是相互的。一些 Remote 方法可以影响 Tv 对象,而一些 Tv 对象也可以影响 Remote 对象。这可以通过让类彼此成为对方的友元来实现,例如:

class Tv
{
    friend class Remote;
    ...
};
class Remote
{
    friend class Tv;
    ...
};
12.1.4 共同的友元

​ 需要使用友元的另一种情况是,函数需要访问两个类的私有数据。这个时候可以将函数作为这两个类的友元,例如:

class Analyzer;
class Probe
{
    friend void f(Analyzer & a, const Probe & p);
}
class Analyzer
{
    friend void f(Analyzer & a, const Probe & p);
}

12.2 嵌套类

​ 在 C++ 中,可以将类声明放在另一个类中,称为嵌套类。包含类的成员函数可以创建和使用被嵌套类的对象;而仅当声明位于公有部分,才能在包含类的外面使用嵌套类,而且必须使用作用域解析运算符。

​ 对类进行嵌套与包含不同。包含意味着将类对象作为另一个类的成员,而对类进行嵌套不创建类成员,而是定义了一种类型,该类型仅在包含嵌套类声明的类中有效。

​ 例如:

class Queue
{
private:
    struct Node {Item item; Node* next;};
    ...
}

​ 由于结构是一种其成员在默认情况下为公有的类,所以 Node 实际上是一个嵌套类。

12.2.1 嵌套类的作用域

​ 如果嵌套类是在另一个类的私有部分声明的,则只有后者知道它。且该类的派生类也无法访问嵌套类,因为派生类不能直接访问基类的私有部分。

​ 如果嵌套类是在另一个类的保护部分声明的,则它对于后者来说是可见的,并且对于派生类也是可见的。

​ 如果嵌套类是在另一个类的公有部分声明的,则允许后者、后者的派生类以及外部世界使用它。在外部使用时,必须使用类限定符。

声明位置包含它的类是否可以使用它派生类是否可以使用他在外部是否可以使用
私有部分
保护部分
公有部分
12.2.2 模板中的嵌套

​ 类模板中可以嵌套类,例如:

template <typename Item>
class QueueTp
{
private:
	enum { Q_SIZE = 10 };
	class Node
	{
	public:
		Item item;
		Node* next;
		Node(const Item& i) :item(i), next(0) {  }
	};
	Node* front, * rear;
	int items;
	const int qsize;
	QueueTp(const Queue& q) :qsize(0) {  }
	QueueTp& operator=(const Queue& q) { return *this; }
public:
	QueueTp(int qs = Q_SIZE);
	~QueueTp();
	bool isempty() const;
	bool isfull() const;
	int queuecount() const;
	bool enqueue(const Item& item);
	bool dequeue(Item& item);
};

​ 将该类的复制构造函数和赋值运算符放在私有部分是一个小技巧:可以避免编译器自动生成这两个函数的错误版本而导致错误,并且避免外部调用这两个函数。

12.3 异常

​ 程序有时候会遇到运行阶段错误,导致程序无法正常运行下去。C++ 异常为处理这种情况提供了一种功能强大而灵活的工具。

​ 讨论异常之前,先看看除了异常之外可使用的一些基本方法。假设要计算 x*y/(x+y),当 x+y=0 时将导致除以 0 0 0,这会产生问题。

12.3.1 调用 abort()

​ 对于这个问题,处理方式之一是调用 abort() 函数。该函数位于头文件 cstdlib 中,其典型实现是向标准错误流发送消息 abnormal program termination,然后终止程序。可以使用 exit() 代替 abort()

double hmean(double a, double b)
{
    if(a == -b)
    {
        cout << "参数错误!" << endl;
        abort();
    }
    else
        return a * b / (a + b);
}
12.3.2 返回错误码

​ 一种比异常终止更加灵活的方法是:使用函数的返回值来指出问题。这样,程序可以根据函数的返回值来做出相应的处理,而不是终止程序。例如:

bool hmean(double a, double b, double * ans)
{
    if(a == -b)
    {
        *ans = DBL_MAX;
        return false;
    }
    else
    {
        *ans = a * b / (a + b);
        return true;
    }
}
int main()
{
    double x, y, z;
    while(cin >> x >> y)
    {
        if(heman(x, y, &z))
        {
            ...
        }
        else
            ...
    }
}
12.3.3 异常机制

​ C++ 异常是对程序运行过程中发生异常情况的一种响应。异常提供了将控制权从程序的一个部分传递到另一个部分的途径。对异常的处理有 3 3 3 个组成部分:

  • 使用 try

  • 引发异常

  • 使用处理程序捕获异常

    下面是一个简单的例子:

    #include <iostream>
    using namespace std;
    double hmean(double a, double b)
    {
        if (a == -b)
            throw "bad hmean() arguments: a = -b not allowed";
        return 2.0 * a * b / (a + b);
    }
    int main()
    {
        double x, y, z;
        cout << "Enter two numbers: ";
        while (cin >> x >> y)
        {
            try
            {
                z = hmean(x, y);
            }
            catch (const char *s)
            {
                cout << s << endl;
                cout << "Enter a new pair of numbers: ";
                continue;
            }
            cout << "Harmonic mean of " << x << " and " << y << " is " << z << endl;
            cout << "Enter next set of numbers <q to quit>: ";
        }
        cout << "Bye!";
    }
    
    1. 使用 try

      try 块标识其中特定的异常可能被激活的代码块,它后面跟一个或多个 catch 块。如果位于 try 块的某条语句引发异常,则其后面的 catch 块将对异常进行处理。执行完 try 块中的语句后,如果没有引发任何异常,则程序将跳过后面的 catch 块,直接执行处理程序后面的第一条语句。

    2. 引发异常
      程序使用关键字 throw 引发异常,紧随其后的字符串指出了异常的特征。异常类型可以是字符串或其他 C++ 类型,通常为类类型。执行 throw 语句类似于执行返回语句,因为它也将终止函数的执行。

    3. 使用处理程序捕获异常

      ​ 处理程序以关键字 catch 开头,随后是位于括号中的类型声明,它指出了异常处理程序要响应的异常类型;然后是一个用花括号括起的代码块,指出要采取的措施。catch 块有点类似于函数的定义,因为匹配的引发将被赋给 s。当异常与处理程序匹配时,程序将执行括号中的代码。

​ 如果函数引发了异常,而没有 try 块或没有匹配的处理程序时,默认情况下程序将调用 abort() 函数。

12.3.4 将对象用作异常类型

​ 通常,引发异常的函数将传递一个对象。这样做的重要优点之一是,可以使用不同的异常类型来区分不同的函数在不同情况下引发的异常。另外,对象可以携带信息,程序员可以根据这些信息来确定引发异常的原因,例如:

#include <iostream>
using namespace std;

class bad_hmean
{
private:
	double v1, v2;
public:
	bad_hmean(double a = 0, double b = 0) :v1(a), v2(b) {  }
	void mesg()
	{
		cout << "hmean(" << v1 << ", " << v2 << "): " << "invalid arguments: a = -b\n";
	}
};

class bad_gmean
{
public:
	double v1, v2;
	bad_gmean(double a, double b) :v1(a), v2(b) {  }
	const char* mesg()
	{
		return "gmean() arguments should be >=0\n";
	}
};
#include <iostream>	
#include <cmath>
#include "exc_mean.h"
double hmean(double a, double b)
{
	if (a == -b)
		throw bad_hmean(a, b);
	else
		return 2.0 * a * b / (a + b);
}
double gmean(double a, double b)
{
	if (a < 0 || b < 0)
		throw bad_gmean(a, b);
	else
		return sqrt(a * b);
}

int main()
{
	double x, y, z;
	cout << "Enter two numbers: ";
	while (cin >> x >> y)
	{
		try
		{
			z = hmean(x, y);
			cout << "Harmonic mean of " << x << " and " << y << " is " << z << endl;
			cout << "Geometric mean of " << x << " and " << y << " is " << gmean(x, y) << endl;
			cout << "Enter next set of numbers <q to quit>: ";
		}
		catch (bad_hmean& bg)
		{
			bg.mesg();
			cout << "Try again.\n";
			continue;
		}
		catch (bad_gmean& hg)
		{
			cout << hg.mesg();
			cout << "Values used: " << hg.v1 << ", " << hg.v2 << endl;
			cout << "Sorry, you don't get to play any more.\n";
			break;
		}
	}
	cout << "Bye!";
}
12.3.5 栈解退

​ 假设函数由于出现异常而终止,则程序将释放函数在栈中的内存,但不会在释放完函数之后停止,而是继续释放栈,直到找到一个位于 try 块中的返回地址。随后,控制权将转到块尾的异常处理程序,而不是函数调用后的第一条语句。这个过程被称为栈解退。

​ 程序进行栈解退以回到能够捕获异常的地方时,将释放栈中的自动存储型变量。如果变量是类对象,将为该对象调用析构函数。

12.3.6 其他异常特性

​ 虽然 throw-catch 机制类似于函数参数和函数返回机制,但还是有些不同之处。其中之一是函数 freturn 语句将控制权返回到调用 f 的函数,但 throw 语句将控制权向上返回到第一个这样的函数:包含能够捕获相应异常的 try-catch 组合。例如:

double means(double a, double b)
{
    double am, hm, gm;
    am = (a + b) / 2.0;
    try
    {
        hm = hmean(a,b);
        gm = gmean(a,b);
    }
    catch (bad_hmean & bg)
    {
        bg.mesg();
        cout << "Caugh in means()\n";
        throw;
    }
    return (am + hm + gm) / 3.0;
}
int main()
{
    double x, y, z;
    while(cin >> x >> y)
    {
        try
        {
            z = means(x,y);
        }
        catch (bad_hmean& bg)
        {
            bg.mesg();
            cout << "Try again.\n";
            continue;
        }
        catch (bad_gmean& hg)
        {
            cout << hg.mesg();
            break;
        }
    }
}

​ 调用 means() 时,当函数 hmean() 引发异常时,控制权将返回到 means(),因为 means() 函数中有可以捕获 hmean 异常的 catch 块;当 gmean() 引发异常时,控制权将向上传递到 main()

另一个不同之处是,引发异常时编译器总是创建一个临时拷贝。例如:
class problem {...};
void super()
{
    ....
    problem oops;
    throw oops;
}
...
try
{
    super();
}
catch(problem & p)
{
    ...
}

​ 上述代码中,p 将指向 oops 的副本而不是 oops 本身。这是件好事,因为 super() 执行完之后,oops 将不复存在。

​ 既然 throw 语句生成副本,为何 catch 块中仍然使用引用呢?因为引用还有另一个重要特征:基类引用可以指向派生类对象。假设有一组通过继承关联起来的异常类型,则在异常规范中只需列出一个基类引用,它将与任何派生类对象匹配。

​ 如果有一个异常类继承层次结构,应这样排列 catch 块:将捕获派生次数最多的派生类的 catch 块放在最前面。将捕获基类异常的 catch 语句放在最后面,因为捕获基类异常的 catch 块可以捕获所有派生类异常。例如:

class bad_1 {...};
class bad_2 : public bad_1 {...};
class bad_3 : public bad_2 {...};
...
try
{...}
catch(bad_3& ) {...}
catch(bad_2& ) {...}
catch(bad_1& ) {...}

​ 可以使用省略号(三个 .)来表示异常类型,从而捕获任何异常,这有点类似于 switch 语句中的 default

catch(...) {...}
12.3.7 exception

​ C++ 异常的主要目的是为设计容错程序提供语言级支持。较新的 C++ 编译器将异常合并到语言中。例如 exception 类,其位于头文件 exception 中,C++ 可以把它用作其他异常类的基类。其中有一个名为 what() 的虚方法,其特征随实现而异,但可以在派生类中重新定义它,例如:

#include <exception>
class bad_hmean : public exception
{
public:
    const char * what() { return "bad arguments to hmean()"; }
};
class bad_gmean : public exception
{
public:
    const char * what() { return "bad arguments to gmean()"; }
};

​ 如果不想以不同的方式来处理这些派生而来的异常,可以使用同一个基类处理程序来捕获它们:

catch(exception & e)
{
    cout << e.what() << endl;
}

​ C++ 库定义了很多基于 exception 的异常类型,见于 P512,此处不再详细介绍。

12.3.8 异常何时会迷失方向

​ 异常被引发后,在两种情况下,会导致问题。

  • 如果它是在带异常规范的函数中引发的,则必须与规范列表中的某种异常匹配,否则称为意外异常。在默认情况下,这将导致程序异常终止。

  • 如果异常不是在函数中引发的(或者函数没有异常规范),则必须捕获它。如果没有被捕获(在没有 try 块或没有匹配的 catch 块时,将出现这种情况),则异常称为未捕获的异常。在默认情况下,这将导致程序终止。

    可以修改程序对意外异常和未捕获异常的反应。

  • 未捕获异常

    ​ 未捕获异常不会导致程序立刻异常终止。相反,程序将首先调用函数 terminate()。在默认情况下,terminate() 函数将调用 abort() 函数。可以指定 terminate() 应调用的函数来修改这种行为,为此,需要调用 set_terminate() 函数,该函数可以修改 terminate() 函数所调用的函数。这两个函数都位于头文件 exception 中。

    ​ 例如,假设希望程序未捕获异常时调用 myQuit() 函数,则需要在程序的开头使用以下语句:

    set_terminate(myQuit);
    

    ​ 当程序引发异常且未被捕获时,程序将调用 terminate() 函数,而后者将调用 myQuit 函数。

  • 意外异常

    ​ 该部分较复杂,且异常规范已经被 C++ 标准摒弃,此处不再介绍,见书 P518。

12.3.9 有关异常的注意事项

​ 从前面关于如果使用异常的讨论可知,应在设计程序时就加入异常处理功能,而不是以后再添加。添加异常也有一些缺点,例如,使用异常会增加程序代码,降低程序运行速度。异常规范不适用于模板,因为模板函数引发的异常可能随特定的具体化而异。异常和动态内存分配并非总能协同工作。

​ 下面进一步讨论异常和动态内存分配。

void test1(int n)
{
    string mesg = "abc";
    ...
    if(oh_no)
        throw exception();
    ...
    return;
}

string 类采用动态内存分配。通常,当函数结束时,将为 mesg 调用析构函数。虽然 throw 语句过早地终止了函数,但它仍然使得析构函数被调用,这要归功于栈解退。因此在这里,内存被正确的管理。

void test2(int n)
{
    double * ar = new double[n];
    ...
    if(oh_no)
        throw exception();
    ...
    delete [] ar;
    return;
}

​ 上面的函数将导致问题。栈解退时,将删除栈中的变量 ar,这将导致末尾的 delete[] ar 被忽略,引起内存泄漏。

​ 这种泄露是可以避免的,一种方法是在函数中捕获该异常,并包含一些清理代码,然后重新引发异常:

void test3(int n)
{
    double * ar = new double[n];
    ...
    if(oh_no)
        throw exception();
    catch(exception & ex)
    {
        delete[] ar;
        throw;
    }
    ...
    delete [] ar;
    return;
}

​ 另一种解决方法是使用智能指针模板,这将在下一章介绍。

12.4 RTTI

​ RTTI 是运行阶段类型识别(Runtime Type Identification)的简称,其为类型转换提供了更加安全的支持。

​ C++ 有三个支持 RTTI 的元素:

  • 当将基类指针转换为派生类指针时,如果可能的话,dynamic_cast 运算符将使用一个指向基类的指针来生成一个指向派生类的指针;否则,该运算符返回空指针。
  • typeid 运算符返回一个指出对象类型的值。
  • type_info 结构存储了有关特定类型的信息。

​ 只能将 RTTI 用于包含虚函数的类层次结构。原因在于只有这种类层次结构,才应将派生对象的地址赋给基类指针。

  1. dynamic_cast 运算符

    ​ 考虑以下类层次结构:

    class Grand {...};
    class Superb : public Grand {...};
    class Magnificen : public Superb {...};
    

    ​ 接下来假设有下面指针以及一些类型转换:

    Grand * pg = new Grand;
    Grand * ps = new Superb;
    Grand * pm = new Magnificent;
    
    Magnificent * p1 = (Magnificent *) pm;  //#1
    Magnificent * p2 = (Magnificent *) pg;	//#2
    Superb * p3 = (Magnificent *) pm;		//#3
    

    ​ 哪些类型转换是安全的?根据类声明,它们可能都是安全的,但只有那些指针类型与对象的类型相同的类型转换才一定是安全的。例如,类型转换 #1 就是安全的;类型转换 #2 就是不安全的,因为它将基类对象赋给派生类指针;#3 是安全的,因为它将派生类对象赋给基类指针。

    dynamic_cast 提供了更加安全的类型转换:通常,如果指向的对象(*pt)的类型是 Type 或者是从 Type 派生而来的类型,则下面语句将指针 pt 转换为 Type 类型的指针,否则 pt 将变为空指针。

    dynamic_cast<Type *>(pt)
    

    ​ 也可以将 dynamic_cast 用于引用,其用法稍微有点不同:没有与空指针对应的引用值,因此无法使用特殊的引用值来指示失败。当请求不正确时,dynamic_cast 将引发类型为 bad_cast 的异常,这种异常是从 exception 类派生而来的,它是在头文件 typeinfo 中定义的。因此,可以像下面这样使用:

    #include <typeinfo>
    ...
    try
    {
        Superb & rs = dynamic_cast<Superb &>(rg);
    }
    catch(bad_cast &)
    {
        ...
    }
    
  2. typeid 运算符和 type_info

    typeid 运算符使得能够确定两个对象是否为同种类型。它与 sizeof 有些相像,可以接受两种类型参数:

    • 类名

    • 结果为对象的表达式

typeid 运算符返回一个对 type_info 对象的引用,其中,type_info 是在头文件 typeinfo 中定义的一个类。这个类重载了 ==!= 运算符,以便可以使用这些运算符来对类型进行比较。例如:

typeid(Magnificent) == typeid(*pg);

​ 如果 pg 是一个空指针,程序将引发 bad_typeid 异常,该异常是从 exception 类派生而来的。

type_info 类的实现随厂商而异,但包含一个 name() 成员,该函数返回一个随实现而异的字符串,通常是类的名称,例如:

Grand * pg = new Grand;
cout << typeid(*pg).name();
//将输出 "Grand"

12.5 类型转换运算符

​ C++ 创始人看来,C 语言中的类型转换运算符过于松散。对于这种情况,C++ 采取了更严格地限制允许的类型转换,并添加 4 4 4 个类型转换运算符,下面分别介绍:

  1. dynamic_cast

    该运算符已经介绍过了,主要用于在类层次结构中进行向上的转换。

  2. const_cast

    该运算符只用于执行一种用途的类型转换:改变值为 constvolatile。例如:

    High bar;
    const High* pbar = &bar;
    ...
    High * pb = const_cast<High *>(pbar);
    

    提供该运算符的原因是,有时候可能需要这样一个值,它在大多数时候是常量,而有时又是可以修改的。在这种情况下,可以将这个值声明为 const,并在需要修改它的时候使用 const_cast

  3. static_cast

    该运算符语法与其他类型转换运算符相同:

    static_cast<type-name>(expression);
    

    仅当 type_name 可被隐式转换为 expression 所属的类型或 expression 可被隐式转换为 type-name 所属的类型时,上述转换才是合法的,否则将出错。例如:

    class High {...};
    class Low : public High {...};
    
    High bar;
    Low blow;
    High * pb = static_cast<High*> (&blow);	//有效
    Low * pl = static_cast<Low*> (&bar);	//有效
    

    第一种转换是合法的,因为向上转换可以显式地进行。第二种转换是从基类指针到派生类指针,在不进行显示类型转换的情况下,将无法进行。但由于无需进行类型转换,便可以进行另一个方向的类型转换。因此虽然有效,但是是不安全的,因为在转换过程中没有运行时检查(RTTI)。如果确实需要运行时检查,应使用 dynamic_cast

    同理,由于无需进行类型转换,枚举值就可以被转换为整型,所以可以用 static_cast 将整型转换为枚举值。同样,可以使用 static_castdouble 转换为 int、将 float 转换为 long 以及其他各种数值转换。

  4. reinterpret_cast

    该运算符用于比较危险的类型转换,例如:

    int main()
    {
        struct dat
        {
            short a;
            short b;
        };
        long value = 0xA224B118;
        dat *pd = reinterpret_cast<dat *>(&value);
        cout << hex << pd->a;
    }
    //将输出 "B118" 或者 "A224",取决于机器是大端存储还是小端存储
    

    通常,这样的转换适用于依赖于实现的底层编程技术,是不可移植的,

    然而,该运算符并不支持所有的类型转换。例如,可以将指针转换为足以存储指针表示的整型,但不能转换为更小的整型或浮点型。另一个限制是,不能将函数指针转化为数据指针,反之亦然。

在这里插入代码片
  • 21
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值