121.static_cast比C语言中的转换强在哪里?

发布时间 2023-08-02 21:36:27作者: CodeMagicianT

121.static_cast比C语言中的转换强在哪里?

  1. 更加类型安全:static_cast在执行类型转换之前会进行类型检查,如果转换不可行,编译时会产生错误或警告。这有助于及早发现潜在的错误并进行修复,而不是在运行时出现未定义的行为。
  2. 适用于指针和引用:static_cast可以用于指针和引用的类型转换,而C语言中的类型转换只能用于数值类型之间的转换。这使得static_cast可以更灵活地处理指针和引用的类型转换,例如在多态和运行时类型识别(RTTI)中使用。
  3. 用于类层次结构转换:在类层次结构中,static_cast可以用于将一个基类指针或引用转换为派生类指针或引用。这种转换在多态的情况下非常有用,因为可以通过基类指针或引用调用派生类的成员函数。然而,需要注意的是,如果转换没有意义或者存在风险,编译器可能会产生警告或错误。
  4. 用于标准库容器操作:在C++标准库的容器中,如std::vectorstd::list等,static_cast可以用于执行一些安全的类型转换操作,例如将一个std::string转换为const char*,或者将一个整数类型的迭代器转换为另一个整数类型的迭代器。

1.C语言中的类型转换

C语言和C++都是强类型语言,如果赋值运算符左右两侧变量的类型不同,或形参与实参的类型不匹配,或返回值类型与接收返回值的变量类型不一致,那么就需要进行类型转换。

C语言中有两种形式的类型转换,分别是隐式类型转换和显式类型转换:

●隐式类型转换:编译器在编译阶段自动进行,能转就转,不能转就编译失败。
●显式类型转换:需要用户自己处理,以(指定类型)变量的方式进行类型转换。
需要注意的是,只有相近类型之间才能发生隐式类型转换,比如int和double表示的都是数值,只不过它们表示的范围和精度不同。而指针类型表示的是地址编号,因此整型和指针类型之间不会进行隐式类型转换,如果需要转换则只能进行显式类型转换。比如:

//为什么C++需要四种类型转换
int main()
{
	//隐式类型转换
	int i = 1;
	double d = i;
	cout << i << endl;
	cout << d << endl;

	//显式类型转换
	int* p = &i;
	int address = (int)p;
	cout << p << endl;
	cout << address << endl;
	return 0;
}

输出:

1
1
000000B7F4AFF864
-189794204

2.为什么C++需要四种类型转换

C风格的转换格式虽然很简单,但也有很多缺点:

  1. 隐式转换缺点:
    • 可能会导致数据精度丢失:当将一个数据类型转换为另一个数据类型时,可能会发生精度丢失,例如将一个浮点数转换为整数时,小数部分会被截断。
    • 可能导致未定义行为:隐式转换可能会导致未定义行为,例如将一个指针类型转换为整数类型可能会导致未定义行为。
    • 代码不够清晰:隐式转换可能会使代码不够清晰,因为它不需要在代码中明确地指定转换的类型。
  2. 显式转换缺点:
    • 可能会导致数据失真:当使用显式转换将一个数据类型转换为另一个数据类型时,可能会导致数据失真,例如将一个整数转换为浮点数时,小数部分可能会出现不准确的情况。
    • 可能会导致代码不够清晰:显式转换需要程序员在代码中明确地指定转换的类型,这可能会使代码不够清晰,同时也增加了代码的复杂性。

C++中的类型转换更加灵活和强大,可以实现更复杂的类型转换操作。C++中的类型转换包括自动类型转换、强制类型转换、类型解析转换和类型推断转换,这四种类型转换可以帮助开发者更好地管理代码中的数据类型,提高代码的可读性和可维护性。

  因此C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符,分别是static_cast、reinterpret_cast、const_cast和dynamic_cast。

————————————————
原文链接:[(7条消息) C++的类型转换_2021dragon的博客-CSDN博客]

3.隐式转换

当一个值拷贝给另一个兼容类型的值时,隐式转换会自动进行。所谓隐式转换,是指不需要用户干预,编译器私下进行的类型转换行为。

short a=2000;
int b;
b=a;

在这里,a在没有任何显示操作符的干预下,由short类型转换为int类型。这就是标准转换,标准转换将影响基本数据类型,并允许数字类型之间的转换(short到int,int到float,double到int…),和bool与其他数字类型转换,以及一些指针转换。

对于非基本类型,数组和函数隐式地转换为指针,并且指针允许如下转换:

●NULL指针可以转换为任意类型指针
●任意类型的指针可以转换为void指针
●指针向上提升:一个派生类指针可以被转换为一个可访问的无歧义的基类指针,不会改变它的const或volatile属性
原文链接:[(7条消息) C++类型转换:隐式转换和显式转换_c++隐式类型转换_SOC罗三炮的博客-CSDN博客]

3.1为什么要进行隐式转换

  C++面向对象的多态特性,就是通过父类的类型实现对子类的封装。通过隐式转换,你可以直接将一个子类的对象使用父类的类型进行返回。再比如,数值和布尔类型的转换,整数和浮点数的转换等。某些方面来说,隐式转换给C++程序开发者带来了不小的便捷。C++是一门强类型语言,类型的检查是非常严格的。如果没有类型的隐式转换,这将给程序开发者带来很多的不便。

3.2C++隐式转换的原则

基本数据类型 基本数据类型的转换以取值的范围作为转换基础(保证精度不丢失)。隐式转换发生在从小到大的转换中。比如从char转换为int。从int到long。

●自定义对象子类对象可以隐式的转换为父类对象

3.3C++隐式转换发生条件

●混合类型的算术运算表达式中。例如:

int a = 3;
double b = 4.5;
a + b; // a将会被自动转换为double类型,转换的结果和b进行加法操作

●不同类型的赋值操作。例如:

int a = true ; ( bool 类型被转换为 int 类型)
int * ptr = null;(null被转换为 int *类型

●函数参数传值。例如:

void func( double a );
func(1); // 1被隐式的转换为double类型1.0

●函数返回值。例如:

double add( int a, int b)
{
     return a + b;

} //运算的结果会被隐式的转换为double类型返回

#以上四种情况下的隐式转换,都满足了一个基本原则:低精度 –> 高精度转换。不满足该原则,隐式转换是不能发生的。

数组转换成指针:在大多数用到数组的表达式中,数组自动转换成指向数组首元素的指针:

int ia[10];//含有10个整数的数组
int* ip = ia;//ia转换成指向数组首元素的指针 

●数组转换成指针

  当数组被用作decltype关键字的参数,或者作为取地址符(&)、sizeof及typeid(第19.2.2节, 732页将介绍)等运算符的运算对象时,上述转换不会发生。同样的,如果用一个引用来初始化数组(参见3.5.1节,第102页), 上述转换也不会发生。我们将在6.7节(第221页)看到, 当在表达式中使用函数类型时会发生类似的指针转换。

●指针的转换

   C++还规定了几种其他的指针转换方式, 包括常量整数值0或者字面值 nullptr能转换成任意指针类型:指向任意非常量的指针能转换成void*:指向任意对象的指针能转换成canst void*。 15.2.2节(第530页)将要介绍,在有继承关系的类型间还有另外一种指针转换的方式。
●转换成布尔类型

  存在一种从算术类型或指针类型向布尔类型自动转换的机制。 如果指针或算术类型的值为0,转换结果是false;否则转换结果是true:

char *cp = get_string (); 
if(cp) / *... * / //如果指针cp不是0,条件为真
while (*cp) /*... */ //如果*cp不是空字符,条件为真

●转换成常量

  允许将指向非常量类型的指针转换成指向相应的常品类型的指针,对于引用也是这样。也就是说,如果T是一种类型,我们就能将指向T的指针或引用分别转换成指向const T的指针或引用(参见2.4.1节, 第54页和2.4.2节,第56页):

int i; 
const int &j = i;//非常量转换成const int的引用
const int *p = &i;//非常量的地址转换成const的地址
int &r = j, *q = p;//错误:不允许const转换成非常量

  相反的转换并不存在, 因为它试图删除掉底层const。

●类类型定义的转换

  类类型能定义由编译器自动执行的转换,不过编译器每次只能执行一种类类型的转换。 在7.5.4节(第263页)中我们将看到一个例子,如果同时提出多个转换请求,这些请求将被拒绝。

  之前的程序已经使用过类类型转换:一处是在需要标准库string类型的地方使用C风格字符串(参见3.5.5节, 第111页);另一处是在条件部分读入istream:

String s, t = "a value";//字符串字面值转换成string类型
while (cin >> s)//while的条件部分把cin转换成布尔值

  条件(cin>>s)读入cin的内容并将cin作为其求值结果。条件部分本来需要一个布尔类型的值,但是这里实际检查的是istream类型的值。幸好,IO库定义了从istream向布尔值转换的规则, 根据这一规则,cin自动地转换成布尔值。所得的布尔值到底是什么由输入流的状态决定,如果最后一次读入成功,转换得到的布尔值是true; 相反,如果最后一次读入不成功,0转换得到的布尔值是false。

3.4隐式转换的风险

类的隐式转换:在类中,隐式转换可以被三个成员函数控制:

  • 单参数构造函数:允许隐式转换特定类型来初始化对象
  • 赋值操作符:允许从特定类型的赋值进行隐式转换
  • 类型转换操作符:允许隐式转换到特定类型

隐式转换的风险一般存在于自定义的类构造函数中。

按照默认规定,只有一个参数的构造函数也定义了一个隐式转换,将该构造函数对应数据类型的数据转换为该类对象。

#include <iostream>
#include<cstdlib>
#include<ctime>
 
using namespace std;
 
class Str
{
public:
	// 用C风格的字符串p作为初始化值
	Str(const char*p)
    {
		cout << p << endl;
	}
	//本意是预先分配n个字节给字符串
	Str(int n) 	
    {
		cout << n << endl;
	}
};
 
int main(void) 
{
	Str s = "Hello";//隐式转换,等价于Str s = Str("Hello");
    //Str s = 1;//也正确
	//下面两种写法比较正常:
	Str s2(10);   //OK 分配10个字节的空字符串
	Str s3 = Str(10); //OK 分配10个字节的空字符串
 
	//下面两种写法就比较疑惑了:
	Str s4 = 10; //编译通过,也是分配10个字节的空字符串
	Str s5 = 'a'; //编译通过,分配int(‘a’)个字节的空字符串,使用的是Str(int n)构造函数
	//s4 和s5 分别把一个int型和char型,隐式转换成了分配若干字节的空字符串,容易令人误解。
	return 0;
}
/*
 *
Hello
10
10
10
97
*/

例二
如下例:

#include <iostream>
#include<cstdlib>
#include<ctime>
 
using namespace std;
class Test {
public:
	Test(int a):m_val(a) {}
	bool isSame(Test other)
	{
		return m_val == other.m_val;
	}
private:
		int m_val;
};
 
int main(void)
{
	Test a(10);
	if (a.isSame(10)) //该语句将返回true
	{
		cout << "隐式转换" << endl;
	}
	return 0;
}

本来用于两个Test对象的比较,竟然和int类型相等了。这里就是由于发生了隐式转换,实际比较的是一个临时的Test对象。这个在程序中是绝对不能允许的。

例三

C++ primer 中有这么一句话:可以用单个实参来调用的构造函数定义了从形参类型到该类型的一个隐式转换。

这么解释:

比如有个类A的对象a的成员函数的参数应该是类A的对象,但是把一个别的类型B的对象b传进去了,而且这个对象b的类型恰好是A的单参数构造函数参数类型,这时系统就用这个b自作聪明的创建了一个类A的临时对象c,虽然ca都是A类型,但是是不同的对象。

这种隐式转换有很大风险,可以用explicit加在单参数构造函数前来避免这种类类隐形转换。

我个人认为这就是一种人为使用错误,而编译器又恰恰没有指出。

本来应该A.a(A(b)),凭什么用成A.a(b)?哈哈……

#include <string>
#include <iostream>
using namespace std;

class Fruit //定义一个类,名字叫Fruit
{
    string name;//定义一个name成员
    string colour;//定义一个colour成员

public:
    bool isSame(const Fruit& otherFruit)//期待的形参是另一个Fruit类对象,测试是否同名
    {
        return name == otherFruit.name;
    }

    void print()//定义一个输出名字的成员print()
    {
        cout << colour << "  " << name << endl;
    }

    Fruit(const string& nst, const string& cst = "green") :name(nst), colour(cst) {}

    Fruit() {}
};

int main()
{
    Fruit apple("apple");
    Fruit orange("orange");

    cout << "apple = orange? : " << apple.isSame(orange) << endl;

    //隐式转换
    cout << "apple = \"apple \"? :" << apple.isSame(string("apple")) << endl;

    return 0;
}

你会发现最后的使用上,我们用一个string类型作一个期待Fruit类形参的函数的参数,结果竟然得出了是true1),不要感到奇怪,这就是我现在要讲的东西,隐式类类型转换:“可以用单个实参来调用的构造函数定义了从形参类型到该类型的一个隐式转换。”(C++ Primer)

首先要单个实参,你可以把构造函数colour的默认实参去掉,也就是定义一个对象必须要两个参数的时候,文件编译不能通过。然后满足这个条件后,系统就知道怎么转换了,不过这里比较严格

以前我们构造对象的时候Fruit apple("apple")其实也已经有了一个转换const char *C字符串格式,转为string,在这里,你再apple.isSame("apple")的话,蠢系统不懂得帮你转换两次,所以你必须要用 string()来先强制转换,然后系统才知道帮你从string隐式转换为Fruit,当然其实你自己也可以帮他完成。 cout<<"apple = /"apple/" ?:"<<apple.isSame(Fruit("apple"));这样。Fruit apple = Fruit("apple");  //定义一个Fruit类对象apple。也就是这样转换的。不过这就叫显式转换了,我们不标出来,系统帮我们完成的,叫隐式的呗。

这里要说的是,假如你显示转换就可以不管有多少参数了,比如在前面提到的必须需要两个参数的构造函数时的例子。

int main()
{
    Fruit apple("apple");
    Fruit orange("orange");

    cout << "apple = orange? : " << apple.isSame(orange) << endl;

    //隐式转换
    cout << "apple = \"apple \"? :" << apple.isSame(string("apple")) << endl;

    return 0;
}

在你不想隐式转换,以防用户误操作怎么办?

C++提供了一种抑制构造函数隐式转换的办法,就是在构造函数前面加explicit关键字,你试试就知道,那时你再希望隐式转换就会导致编译失败,但是,要说明的是,显式转换还是可以进行,出于不提供错误源代码例子的原则,错误的情况就不提供了,自己试试吧

参考:[(7条消息) 隐式类类型转换_隐式转换_dingyuanpu的博客-CSDN博客]