C++类型转换总结

embedded/2025/1/22 15:14:56/

类型转换

隐式转换

C++自动执行很多类型转换:

  • 将一种算术类型的值赋给另一种算术类型的变量时,C++将对值进行转换;

  • 表达式中包含不同的类型时,C++将对值进行转换;

  • 将参数传递给函数时,C++将对值进行转换。

C++类型转换的规则

初始化和赋值进行的转换

扩展:将一个值赋给值取值范围更大的类型通常不会导致什么问题。

截取:将浮点型转换为整型时,C++采取截取(丢弃小数部分)而不是四舍五入(查找最接近的整数)。

使用赋值运算符给类初始化

下面是C++ Primer Plus 的一个案例:

// stonewt.h -- definition for the Stonewt class
#ifndef STONEWT_H_
#define STONEWT_H_
class Stonewt
{
private:enum {Lbs_per_stn = 14};      // pounds per stoneint stone;                    // whole stonesdouble pds_left;              // fractional poundsdouble pounds;                // entire weight in pounds
public:Stonewt(double lbs);          // constructor for double poundsStonewt(int stn, double lbs); // constructor for stone, lbsStonewt();                    // default constructor~Stonewt();void show_lbs() const;        // show weight in pounds formatvoid show_stn() const;        // show weight in stone format
};
#endif
// stonewt.cpp -- Stonewt methods
#include <iostream>
using std::cout;
#include "stonewt.h"// construct Stonewt object from double value
Stonewt::Stonewt(double lbs)
{stone = int (lbs) / Lbs_per_stn;    // integer divisionpds_left = int (lbs) % Lbs_per_stn + lbs - int(lbs);pounds = lbs;
}// construct Stonewt object from stone, double values
Stonewt::Stonewt(int stn, double lbs)
{stone = stn;pds_left = lbs;pounds =  stn * Lbs_per_stn +lbs;
}Stonewt::Stonewt()          // default constructor, wt = 0
{stone = pounds = pds_left = 0;
}Stonewt::~Stonewt()         // destructor
{
}// show weight in stones
void Stonewt::show_stn() const
{cout << stone << " stone, " << pds_left << " pounds\n";
}// show weight in pounds
void Stonewt::show_lbs() const
{cout << pounds << " pounds\n";
}

如果编写这样的代码:

Stonewt myCat;
myCat=19.6;//usetStonwt(double) to convert 19.6 to Stonewt

看看这是什么意思?我们使用了赋值运算符,可是=两边的类型不匹配,究竟发生了什么?可能你已经习惯了这种用法,下面用类和对象的知识解释一下:

程序将使用构造函数 Stonewt(double)来创建一个临时的 Stonewt 对象,这个临时对象用19.6初始化值。然后调用默认拷贝构造函数,将tmp 的值复制到myCat。就像这样:

Stonewt myCat;
Stonewt tmp(19.6)
myCat=tmp;//usetStonwt(double) to convert 19.6 to Stonewt

只接受一个参数的构造函数定义了从参数类型到类类型的转换。特别要注意的是:当且仅当转换不存在二义性时,才会进行这种二步转换。也就是说,如果这个类还定义了构造函数 Stonewt(long),则编译器将拒绝这些语句,可能指出:int 可被转换为 long 或 double,因此调用存在二义性。

但是,当我们去掉Stonewt(double lbs);函数,并且Stonewt(int stn, double lbs=0.6); 第二个参数有默认值,结果看起来与我们设想的不一样:

class Stonewt
{
private:enum { Lbs_per_stn = 14 };      // pounds per stoneint stone;                    // whole stonesdouble pds_left;              // fractional poundsdouble pounds;                // entire weight in pounds
public://Stonewt(double lbs);          // constructor for double poundsStonewt(int stn, double lbs=0.6); // constructor for stone, lbsStonewt();                    // default constructor~Stonewt();void show_lbs() const;        // show weight in pounds formatvoid show_stn() const;        // show weight in stone }; // stonewt.cpp -- Stonewt methods
#include <iostream>
using std::cout;
Stonewt::Stonewt(int stn, double lbs)
{stone = stn;pds_left = lbs;pounds = stn * Lbs_per_stn + lbs;
}Stonewt::Stonewt()          // default constructor, wt = 0
{stone = pounds = pds_left = 0;
}Stonewt::~Stonewt()         // destructor
{
}// show weight in stones
void Stonewt::show_stn() const
{cout << stone << " stone, " << pds_left << " pounds\n";
}// show weight in pounds
void Stonewt::show_lbs() const
{cout << pounds << " pounds\n";
}#include <iostream>
using std::cout;
using std::endl;
int main() {/*double a = 34.5643;int b=static_cast<int>(a);cout<<b<<endl;*/Stonewt wt=19;wt.show_stn();//19 stone, 0.6 poundswt.show_lbs();//266.6 poundsStonewt wt1=19.8;wt1.show_stn();//19 stone, 0.6 poundswt1.show_lbs();//266.6 poundsStonewt wt2=19.5;wt2.show_stn();//19 stone, 0.6 poundswt2.show_lbs();//266.6 poundsreturn 0;
}

如果外界有人使用Stonewt wt1=19.8; 不了解Stonewt类的构造函数,是不是容易出现意料之外的结果?当然外界有人就会认为可以用double值给Stonewt赋值,结果与预想完全不同。

因此,C++新增了关键字 explicit,用于关闭这种自动特性。所以应该在构造函数前尽可能加上explicit,避免不必要的二次转换。

 explicit Stonewt(int stn, double lbs=0.6); 

类的转换函数

是否可以将 Stonewt 对象转换为 double 值,就像如下所示的那样?

可以这样做,但不是使用构造函数。构造函数只用于从某种类型到类类型的转换。要进行相反的转换,必须使用特殊的 C++运算符函数——转换函数。
转换函数是用户定义的强制类型转换,可以像使用强制类型转换那样使用它们。

请注意以下几点:

  • 转换函数必须是类方法;

  • 转换函数不能指定返回类型;

  • 转换函数不能有参数。

例如,转换为 double 类型的函数的原型如下:

 operator double();

我们在类中加上声明和定义:

operator int() const;
operator double() const;Stonewt::operator int() const
{return int(pounds + 0.5);}Stonewt::operator double()const
{return pounds;
}

现在Stonewt类可以隐式转换为double类和int类了。但是要避免歧义。比如下面要转成long类型,两种转换函数都可以用,我们只能选择一种转换函数。

在这里插入图片描述

long l = int(wt1);
long l = (double)wt1;

当然,一些不必要的隐式转换也要避免,C++11提供的explicit关键字能起到规避作用,在修改后:

explicit operator int() const;
explicit operator double() const;

在这里插入图片描述

这里是必须显式转换,避免了隐式转换。

更推荐的写法是使用static_cast显式转换

long l = static_cast<int>(wt1);
long l = static_cast<double>(wt1);

以{ }方式初始化时进行的转换(C++11)

C++11 将使用大括号的初始化称为列表初始化(list-initialization),因为这种初始化常用于给复杂的数据类型提供值列表。
当类有一个接受 std::initializer_list 参数的构造函数,或者有匹配参数列表的构造函数,并且这些构造函数不是 explicit 的时候,列表初始化会尝试进行隐式转换。

如果构造函数被声明为 explicit,则不能通过列表初始化进行隐式转换。这防止了意外的类型转换,提高了代码的安全性和清晰度。

const int code = 66;
int x = 66;
char c1 {31325};//narrowing, not allowed
char c2 = {66};//allowed because char can hold 66char c3 {code};//ditto
//这里使用了常量 `code` 来初始化 `char` 类型的变量 `c3`。由于 `code` 的值是66,在 `char` 的范围内,所以这是允许的。
//注释中的“ditto”意味着这与前面的例子类似,即初始化是安全的,不会发生窄化。char c4 = {x};// not allowed x is not constant
//尽管 x 的当前值也是66,但由于 x 不是一个常量,编译器不能保证它的值在未来不会改变。
//因此,当使用花括号进行列表初始化时,编译器会拒绝这种可能引起窄化的转换,即使在当前情况下它是安全的。x = 31325;char c5 = x;//allowed by this form of initialization

C++11 扩大了用大括号括起的列表(初始化列表)的适用范围,使其可用于所有内置类型和用户定义
的类型(即类对象)。使用初始化列表时,可添加等号(=),也可不添加:

int x ={5};
double y {2.75};
short quar[5] {4,5,2,76,1};

另外,列表初始化语法也可用于new表达式中:

int * ar = new int [4] {2,4,6,7};// C++11

创建对象时,也可使用大括号(而不是圆括号)括起的列表来调用构造函数:

class Stump{
private:int roots;double weight;
public:Stump(int r,double w):roots(r)weight(w){}
};Stump s1(3,15.6);// old styleStump s2{5,43.4};// C++11
Stump s3 = {4,32.1};;//C++11

初始化列表语法可防止缩窄,即禁止将数值赋给无法存储它的数值变量。常规初始化允许程序员执行可能没有意义的操作:

char c1 = 1.57e27;// double-to-char,undefined behavior
char c2 = 459585821;// int-to-char, undefined behavior

然而,如果使用初始化列表语法,编译器将禁止进行这样的类型转换,即将值存储到比它“窄”的变量中:

char c1 {1.57e27};// double-to-char,compile-time error
char c2 = {459585821};// int-to-char,out of range, compile-time error

但允许转换为更宽的类型。另外,只要值在较窄类型的取值范围内,将其转换为较窄的类型也是允许的:

char c1 {66};// int-to-char, in range, allowed
double c2 ={66};//int-to-double,allowed

表达式中的转换

整型提升(integral promotion):当运算涉及两种类型时,较小的类型将被转换为较大的类型。

简单地说,有符号整型按级别从高到低依次为 long long、long、int、short 和 signed char。无符号整型的排列顺序与有符号整型相同。类型 char、signed char和 unsigned char 的级别相同。类型 bool 的级别最低。wchar_t、char16_t 和 char32_t 的级别与其底层类型相同。

  • 传递参数时的转换

  • 强制类型转换

显式转换

强制转换的通用格式如下:

(typeName) value// converts value to typeName type
typeName (value)// converts value to typeName type

第一种格式来自C语言,第二种格式是纯粹的C++。新格式的想法是,要让强制类型转换就像是函数调用。这样对内置类型的强制类型转换就像是为用户定义的类设计的类型转换。C++还引入了4个强制类型转换运算符,对它们的使用要求更为严格,这将在第15章介绍。在这四个运算符中,static_cast<>可用于将值从一种数值类型转换为另一种数值类型。

static_cast<typeName> (value)// converts value to typeName type

Stroustrup认为,C语言式的强制类型转换由于有过多的可能性而极其危险。运算符 static_cast<>比传统强制类型转换更严格。

四种强制类型转换

为什么C语言的强制转换不继续用,反而C++自己搞一套呢?

Here is a short quote from Bjarne Stroustrup’s (the author of C++) book The C++ Programming Language 4th edition - page 302.
下面是Bjarne Stroustrup(C++的作者)的书The C++ Programming Language第4版-第302页的简短引用。

This C-style cast is far more dangerous than the named conversion operators because the notation is harder to spot in a large program and the kind of conversion intended by the programmer is not explicit.

Static Cast

static_cast操作符是C++中最常用的类型转换操作符。它执行编译时类型转换,主要用于编译器认为安全的显式转换。常用场景:

  • 基本数据类型之间的转换,如把 int 转换为 char,这种转换带来的安全性问题由程序员来保证。

  • 相关类型的转换(类有转换函数),比如上面提到的Stonewt类可以转换为double类和int类。

  • 把void* 类型的指针转换为任意类型指针。

  • 基类和派生类的转换:基类->派生类:安全;派生类->基类:安全。

Dynamic Cast

reference:An In-Depth Guide to Static_Cast vs Dynamic_Cast in C++ - LinuxHaxor

18.9 动态类型转换 - LearnCPP 中文教程

Syntax语法

dynamic_cast<new_type>(expression) 

static_cast不同,dynamic_cast验证对象expression的求值结果确实是类型new_type,结果如下:

  1. If conversion succeeds, it returns a pointer/reference to the casted object.
    如果转换成功,它返回一个指向转换对象的指针/引用。

  2. If conversion fails and new_type is a pointer, it returns nullptr.
    如果转换失败并且new_type是指针,则返回nullptr。

  3. If conversion fails and new_type is a reference, it throws std::bad_cast exception.
    如果转换失败,并且new_type是引用,它会抛出std::bad_cast异常。

Requirements要求

  1. Base class must contain at least one virtual function for dynamic_cast to work properly.
    基类必须包含至少一个virtual函数,才能使dynamic_cast正常工作。
  2. Dynamic memory allocation – it does not work on stack variables.
    动态内存分配-它不适用于堆栈变量。

相比于static_cast,dynamic_cast运行时需要一点额外的开销。有时,在进行向下类型转换时,我们知道正在处理的是何种类型,这时使用dynamic_cast产生的额外开销就没有必要,可以通过使用static_cast来代替它。 但是如果不符合需要的类型,static_cast会返回错误的指针或引用。

某些时候使用向下转换是一个更好的选择:

  • 当你不能修改基类来添加一个虚函数时(例如:因为基类是标准库中的一种)
  • 当你仍然需要访问一些只有派生类独有的东西时(例如:某个访问函数只存在于派生类)
  • 当添加一个虚函数到你的基类毫无意义时(例如:没有一个合适的值让基类返回)使用纯虚函数也许可以纳入考虑,如果你不要实例化基类。
Const Cast

用于常量指针(或引用)与非常量指针(或引用)之间的转换。

const 对象转换成 non-const 对象,慎用。

Reinterpret Cast

reference: https://stackoverflow.com/questions/573294/when-to-use-reinterpret-cast

c++ - When should static_cast, dynamic_cast, const_cast, and reinterpret_cast be used? - Stack Overflow

https://www.geeksforgeeks.org/reinterpret_cast-in-c-type-casting-operators/

这种类型转换比较随意,慎用。


http://www.ppmy.cn/embedded/156080.html

相关文章

【BUUCTF】[RCTF2015]EasySQL1

二次注入原理 是一种比较隐蔽的 SQL 注入类型 用户输入的数据先被存储到数据库中&#xff08;此时可能未被恶意利用&#xff09;&#xff0c;后续应用程序从数据库中读取该数据并再次使用在 SQL 查询中&#xff0c;而此时就可能导致 SQL 注入问题。 如&#xff0c;用户注册时…

【Node.js]

一、概述 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境 &#xff0c;使用了一个事件驱动、非阻塞式I/O模型&#xff0c; 让JavaScript 运行在服务端的开发平台&#xff0c;它让JavaScript成为与PHP、Python、Perl、Ruby等服务端语言平起平坐的脚本语言。 官网地…

搭建一个基于Spring Boot的书籍学习平台

搭建一个基于Spring Boot的书籍学习平台可以涵盖多个功能模块&#xff0c;例如用户管理、书籍管理、学习进度跟踪、笔记管理、评论和评分等。以下是一个简化的步骤指南&#xff0c;帮助你快速搭建一个基础的书籍学习平台。 — 1. 项目初始化 使用 Spring Initializr 生成一个…

ARM-V9 CCA/RME QEMU环境搭建

整个用于 CCA 的软件栈仍在开发中,这意味着指令会频繁更改,且仓库可能是临时的。有关手动编译该栈以及从 OP-TEE 构建环境编译的指令,均基于 Ubuntu 22.04 LTS 系统编写。 使用 OP-TEE 构建环境 此方法至少需要以下工具和库。下面描述的手动构建方法也需要大部分这些工具。…

Web3 数字资产如何更有趣?解锁 Ultiland 融合 MeMe 与 RWA 的技术路径

链上数字资产的快速发展&#xff0c;如何与艺术创作深度融合&#xff1f;一众实体资产渴望向 Web3 无缝跃迁&#xff0c;你知道 Ultiland 交出了一份怎样的答卷吗&#xff1f;创新 Meme-like RWA 模型&#xff0c;让艺术品、房地产等资产进入 Web3&#xff0c;开启全新投资体验…

RabbitMQ1-消息队列

目录 MQ的相关概念 什么是MQ 为什么要用MQ MQ的分类 MQ的选择 RabbitMQ RabbitMQ的概念 四大核心概念 RabbitMQ的核心部分 各个名词介绍 MQ的相关概念 什么是MQ MQ(message queue)&#xff0c;从字面意思上看&#xff0c;本质是个队列&#xff0c;FIFO 先入先出&am…

仿 RabbitMQ 的消息队列1(实战项目)

一&#xff0c;消息队列的背景知识 我们以前学过阻塞队列&#xff0c;其实阻塞队列和消息队列的原理差不多。 在实际的后端开发中, 尤其是分布式系统⾥, 跨主机之间使⽤⽣产者消费者模型, 也是⾮常普遍的需求. 因此, 我们通常会把阻塞队列, 封装成⼀个独⽴的服务器程序, 并且赋…

基于微信小程序的手机银行系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…