首页 > 编程语言 >C++类型转换总结

C++类型转换总结

时间:2025-01-21 18:57:11浏览次数:3  
标签:类型转换 总结 转换 int double C++ cast Stonewt

类型转换

隐式转换

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 stone
    int stone;                    // whole stones
    double pds_left;              // fractional pounds
    double pounds;                // entire weight in pounds
public:
    Stonewt(double lbs);          // constructor for double pounds
    Stonewt(int stn, double lbs); // constructor for stone, lbs
    Stonewt();                    // default constructor
    ~Stonewt();
    void show_lbs() const;        // show weight in pounds format
    void 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 division
    pds_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 stone
    int stone;                    // whole stones
    double pds_left;              // fractional pounds
    double pounds;                // entire weight in pounds
public:
    //Stonewt(double lbs);          // constructor for double pounds

    Stonewt(int stn, double lbs=0.6); // constructor for stone, lbs
    Stonewt();                    // default constructor
    ~Stonewt();
    void show_lbs() const;        // show weight in pounds format
    void 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 pounds
    wt.show_lbs();//266.6 pounds


    Stonewt wt1=19.8;

    wt1.show_stn();//19 stone, 0.6 pounds
    wt1.show_lbs();//266.6 pounds

    Stonewt wt2=19.5;

    wt2.show_stn();//19 stone, 0.6 pounds
    wt2.show_lbs();//266.6 pounds
    return 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 66

char 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 style

Stump 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/

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

标签:类型转换,总结,转换,int,double,C++,cast,Stonewt
From: https://blog.csdn.net/weixin_62760649/article/details/145288758

相关文章

  • 【2025】Visual Studio详细安装使用教程(C/C++编译器)零基础入门到精通,收藏这一篇就够了
    Part1VisualStudio2022简介微软在官方开发博客中宣布,于2021年夏季发布VisualStudio2022的首个预览版,2022版本更快、更易于使用、更轻量级,专为学习者和构建工业规模解决方案的人设计。64位版的VisualStudio不再受内存限制困扰,主devenv.exe进程不再局限于4GB,用户......
  • git 使用总结
    https://gitee.com/****************.gitgitconfig--globaluser.name'***'gitconfig--globaluser.email'f******@gmail.com'git--version查看版本originhttps://gitee.com/f*****************.git(fetch)originhttps://gitee.com/********......
  • 2024又是一年的CSDN之旅-总结过去展望未来
     一、前言    一年就这样在忙忙碌碌的工作和生活中一晃而过,总结今年在CSDN上发表的博客,也有上百篇之多,首先感谢CSDN这个平台,能让我有一个地方记录工作中的点点滴滴,也在上面学到了不少知识,解决了工作中遇到的不少问题。由于个人能力有限,在CSDN上也没做出什么大的贡献,......
  • C++ 如何讲隐藏的函数释放出来
    如果有一个基类:classDog{public: virtual~Dog(){} voidshow(inta) { cout<<"我是一只狗!"<<a<<"岁"<<'\n'; } voidmysong() { cout<<"哈哈哈..."<<'\n'; }privat......
  • Ubuntu 22.04上编译安装C++ libconfig库
    一、前言libconfig是一个C/C++配置文件解析库,支持读取和写入配置文件。它使用了一种简单易懂的语法,非常适合用于各种项目的配置管理。本文将详细介绍如何在Ubuntu22.04上编译和安装libconfig库。二、环境准备在开始编译安装libconfig之前,需要确保系统已经安装了必要的开发工具......
  • 旺仔Sec的2024年度技术总结:在探索中成长,于分享中前行
    旺仔Sec的2024年度技术总结:在探索中成长,于分享中前行文章目录旺仔Sec的2024年度技术总结:在探索中成长,于分享中前行一、创作成果概览二、技术领域深耕(一)软件测试:聚焦职业竞赛真题,提升实践能力(二)网络安全:靶场实战与竞赛结合,强化专业技能三、对职业教育与行业的贡献四、遇......
  • C++template模板
    目录函数模板(FunctionTemplate)示例:类模板(ClassTemplate)示例:模板参数(TemplateParameters)非类型模板参数示例:模板特化(TemplateSpecialization)示例:C++中的模板(Template)是一种强大的特性,允许程序员编写与类型无关的代码。模板可以用于函数和类,使得代码更加通用和可......
  • 在系统重构中的工作计划与总结
    在软件开发的过程中,系统重构是不可避免的。随着业务需求的不断变化和技术栈的更新,系统往往会积累大量的技术债务,导致代码质量下降、性能瓶颈凸显、维护成本增加。作为一名开发者,我深知系统重构的重要性。本文将围绕我在系统重构中的工作目标、落地执行策略以及实践经验展开讨论......
  • 【C++提高篇】—— C++泛型编程之模板基本语法和使用的详解
    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录前言一、模板的概念二、函数模板2.1函数模板的使用2.2函数模板注意事项2.3普通函数与函数模板的区别2.4普通函数与函数模板的调用规则2.5模板的局限性三、类模板3.1类模板的使用3.2类模板......
  • Mybatis 学习总结
    Mybatis学习总结一.环境说明:jdk8+MySQL5.7.19maven-3.6.1IDEA二.学习前需要掌握:JDBCMySQLJava基础MavenJunit2.1什么是MyBatisMyBatis是一款优秀的持久层框架MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集的过程MyBatis可以使用简单的X......