C++类型转换方式总结
索引目录
再谈为何会有那四个转换运算符
看起来,我应该把导师讲过、遗漏的有关C++类型转换方面的内容都总结成文了,主要内容都在以上几篇文章中阐述完毕。
上边的每一篇文章,虽然都单独着重强调一种转换方式或运算符,但是也有提到跟其他运算符之间的差异性,以及使用建议,因此基本可以看出各个运算符的使用方式和作用。
在文章也看到const_cast, reinterpret_cast, static_cast都可以用传统的转换方式基于指针进行替代。如果结合typeid运算符,那么dynamic_cast其实也可以用传统转换方式实现。
因此不免会有疑惑:这四个转换运算符不是很多余。
的确,我刚接触这些转换运算符的时候,我也又这样的疑虑。因为在跟着导师学习C++,做一些课程项目的过程中,我都不曾使用过这些转换运算符。一来我需要用到类型转换的地方很少,二来也还不熟悉这些运算符,所以就尽量避免使用。
不过在花了这么多时间和精力研究和总结这些转换运算符之后,我以后一定会更多的去使用他们,因为传统的转换方式能够实现标准转换运算符的功能,主要还是基于"C++中的指针可以无条件互相转换"。因此,对于转换符的实现,其格式基本都是一致的:用强制转换的方式,直接转换指针类型。
正因如此,看到这些转换代码,有时候并不能马上理解其目的用意。而如果用转换运算符来操作,就可以一目了然地通过转换符的名称知道是在去除const,还是想进行指针的重新定义。
另一个角度来说,编译器也对转换运算符做来限制、优化和异常处理,使用他们可以更好地减少错误的产生,以及避免传统转换没有达到预期的目的。
所以,如果碰到需要类型转换的地方,就尽量思考,是否可以用转换运算符来替代,用哪个是最合适的。下边就来讲讲什么时候用什么样的转换符最合适。
转换运算符的应用之所
结合网络上各个站点看到的关于C++转换符的知识,以及前面那些文章得到的反馈,可以将各个转换运算符的使用总结如下:
对于传统的转换方式(C式或函数式),只在数值类型(包括整型、浮点型、字符类型和枚举)上使用。这也是延续C的形式,当然这类转换也是可以用static_cast来替换,但是因为是基本类型,所以传统转换已经很直观。
对于const_cast转换运算符,用在需要去除掉const限定的时候。其实这种情况出现的很少,可能的方法在const_cast一文中已经又举例,不过还是反复强调, 使用const_cast转换后,绝对不可试图修改结果的值。
对于reinterpret_cast转换运算符,一般用在将对象指针类型转换到整数类型或者void * (空指针)。如同在文中举出的隐患,因此注意的是,若要使用其结果,一定要将类型转换回去后使用。也不要将随意的整数转换成指针类型。
对于static_cast转换运算符,将其用在对象的转换之上(虽然static_cast也可以用在有继承关系的类型指针之间,但是还是将这方面的转换交给dynamic_cast来操作吧), static_cast会调用相应的构造函数或者重载的转换运算符。
通过文章的留言反馈,以及Google C++ Style Guide的推荐格式,知道对于单参构造函数的存在可能会引发一些隐式的转换,因此用static_cast也可以明确的指出类型的转换过程,避免生成多余的临时对象造成效率下降。
对于dynamic_cast转换运算符,将其用在具有继承关系的指针类型之间的转换。无论是从基类到子类的转换,还是子类到基类的转换,都将dynamic_cast套上去,也算是标识它们是一家子。
如果任何一种基于指针或引用的转换,套上四个转换运算符之后都失败,那么所要进行的转换可能就触到了"雷区"了:进行了没意义的转换。比如,对于没有关系的两个类型的指针进行了转换,比如试图转换指向方法的指针了。所以转换运算符对于避免代码出错也很有帮助。
基于引用(Reference)的转换运算符使用
前面的文章中,所以对于转换运算符的讲述和举例,都是基于指针的。但实际上,这些转换运算符也可以基于引用来展开。准确说实际上引用类型应该是作为转换的目标类型,源类型则是对象变量(当然也可能用的是引用变量,或是取出指针所指的内容,它们用到的都是实际的类对象)。
由于引用类型“定义时必须初始化”的特别,使得它不同于指针类型随时随地都调用转换运算符,基于引用的转换只在对引用进行初始化的时候才会出现。
下边是const_cast和reinterpret_cast基于引用的运用:
const int int_constant = 21;
int& int_ref = const_cast<int&>(int_constant);
cout << int_ref << endl;
int int_value = 7;
//long& long_ref = int_value; //Error, can not using reference cross types
float& long_ref = reinterpret_cast<float&> (int_value);
cout << long_ref << endl;
对于dynamic_cast的应用基本也是一致的,只是还是限制在具有继承关系的类型之间。不同于基于指针在转换时返回null,dynami_cast在基于引用转换失败时,会抛出std::bad_cast异常,因为不能将空值赋给引用类型。如果要抓住这个异常,则需要引入如下头文件:
#include <typeinfo>
而static_cast转换符前面已经说过推荐直接用在对象之上,不用在指针上,所以也不太会有需要用在引用类型上的情况出现。
山寨C#的TryParse
C#中有很多简洁实用的转换方法,比如从字符串到数值类型的Parse和TryParse,还有包含了各种从object对象到数值类型、时间类型的方法的Convert类,以及检查继承关系的as运算符。
从返回的结果看,C++和dynamic_cast和C#的as很相似,两者都是在失败时候返回null。
不过面向对象的关键点在于什么都是以对象为操作单位,如前所讲dynamic_cast看起来更像是一个全局方法。因此我便模仿C#的数值类的TryParse方法,写了一个包裹dynamic_cast的类型转换方法:
/////////////////////////////////////////////////////////////////////////////
// dynamic_cast_tryparse.cpp
// Language: C++
// Complier: Visual Studio 2010, Xcode3.2.6
// Platform: MacBook Pro 2010
// Application: none
// Author: Ider, Syracuse University, ider.cs@gmail.com
///////////////////////////////////////////////////////////////////////////
#include <string>
#include <iostream>
using namespace std;
class Parents
{
public:
Parents(string n="Parent"){ name = n;}
virtual ~Parents(){}
virtual void Speak()
{
cout << "\tI am " << name << ", I love my children." << endl;
}
void Work()
{
cout << "\tI am " << name <<", I need to work for my family." << endl;;
}
/************** TryParseTo **************/
template<typename T> bool TryParseTo(T** outValue)
{
T* temp = dynamic_cast<T*> (this);
if (temp == NULL) return false;
*outValue = temp;
return true;
}
protected:
string name;
};
class Children : public Parents
{
public:
Children(string n="Child"):Parents(n){ }
virtual ~Children(){}
virtual void Speak()
{
cout << "\tI am " << name << ", I love my parents." << endl;
}
/*
**Children inherit Work() method from parents,
**it could be treated like part-time job.
*/
void Study()
{
cout << "\tI am " << name << ", I need to study for future." << endl;;
}
private:
//string name; //Inherit "name" member from Parents
};
class Stranger
{
public:
Stranger(string n="stranger"){name = n;}
virtual ~Stranger(){}
void Self_Introduce()
{
cout << "\tI am a stranger" << endl;
}
void Speak()
{
//cout << "I am a stranger" << endl;
cout << "\tDo not talk to "<< name << ", who is a stranger." << endl;
}
private:
string name;
};
int main()
{
Children * parsedChild;
Parents * parsedParent;
Stranger * parsedStranger;
Parents * mother = new Parents("Mother who pretend to be a my daugher");
if(mother->TryParseTo<Children>(&parsedChild))
parsedChild->Speak();
else
cout << "Parents parse to Children failed" << endl;
delete mother;
mother = new Children("Daughter who pretend to be a my mother");
if(mother->TryParseTo<Children>(&parsedChild))
parsedChild->Speak();
else
cout << "Parents parse to Children failed" << endl;
delete mother;
Children * son = new Children("Son who pretend to be a my father");
if(son->TryParseTo<Parents>(&parsedParent))
parsedParent->Speak();
else
cout << "Children parse to Parents failed" << endl;
if(son->TryParseTo<Stranger>(&parsedStranger))
parsedStranger->Speak();
else
cout << "Children parse to Stranger failed" << endl;
delete son;
//pointer of child class could pointer to base class object
/*
* son = new Parents("Father who pretend to be a my son");
if(son->TryParseTo<Parents>(&parsedParent))
parsedParent->Speak();
else
cout << "Parse failed" << endl;
delete son;
*/
return 0;
}
/********************* Result *********************/
//Parents parse to Children failed
// I am Daughter who pretend to be a my mother, I love my parents.
// I am Son who pretend to be a my father, I love my parents.
//Children parse to Stranger failed
这段代码中使用到的类跟dynamic_cast一文中使用的基本一样,只是在基类中多了一个模板方法:
template<typename T> bool TryParseTo(T** outValue)
{
T* temp = dynamic_cast<T*> (this);
if (temp == NULL) return false;
*outValue = temp;
return true;
}
该方法需要指定的目标类型,将自身指针转换成目标指针。转换成功,则将结果赋值给相应的变量,并返回真值;若失败则返回假值,不改变指针变量。因为要让外部的指针变量能够接受到改值,因此不得不使用指向指针的指针。
因为在基类中以公共结果的形式出现,所以每一个子类都继承了该方法,无论是基类的对象还是子类的对象都可以调用该方法。而该方法又不是虚方法,因此不并不希望子类去修改它。只是因为方法是模板方法,可能在编译的时候需要多花一些时间。
由于引用必须在定义时就赋值,并且dynamic_cast对于基于引用的转换不成功时将抛出异常,因此对于基于引用的转换,我还没有想出有什么好的山寨形式。
从测试代码的结果也可以看出,对于该发放的调用都是成功有效的。
所以又应了导师常说的一句话:When use C++, the good news is that you can do everything you want, the bad news is that you have to do everything you want.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架