【C++】C/C++中的类型转换
一、C语言中类型转换
1.1 C语言中的隐式转换
在 C 语言里,隐式转换是编译器自动进行的类型转换操作,不需要程序员手动干预。下面是C语言中常见的隐式转换例子:
1.1.1 算术转换
在进行算术运算时,不同类型的数据进行混合运算,编译器会自动将操作数转换为合适的类型,以保证运算的准确性。算术转换遵循 “类型提升” 原则,将低精度类型转换为高精度类型,常见的转换规则如下:
char、signed char、unsigned char、short int 和 unsigned short int 类型在参与运算时,通常会被提升为 int 类型。如果 int 无法表示原类型的所有值(例如在 16 位系统上,unsigned short 的范围超出 int),则会提升为 unsigned int 类型。
#include <stdio.h>
int main() {
char a = 10;
char b = 20;
int result = a + b; // 这里 char 类型的 a 和 b 先提升为 int 类型再进行加法运算
printf("Result: %d\n", result);
return 0;
}
当不同类型的数据进行运算时,会按照以下层次从低到高进行转换:int < unsigned int < long < unsigned long < float < double。例如,int 类型和 float 类型进行运算时,int 会被转换为 float 类型。
#include <stdio.h>
int main() {
int num1 = 5;
float num2 = 3.2f;
float result = num1 + num2; // int 类型的 num1 被转换为 float 类型再进行加法运算
printf("Result: %f\n", result);
return 0;
}
1.1.2 赋值转换
当把一个表达式的值赋给一个变量时,如果表达式的类型和变量的类型不一致,编译器会自动将表达式的值转换为变量的类型。
如果是将高精度类型赋值给低精度类型,可能会发生数据截断。例如,将 float 类型的值赋给 int 类型的变量,小数部分会被直接丢弃。
#include <stdio.h>
int main() {
float f = 3.14f;
int i = f; // float 类型的 f 被转换为 int 类型,小数部分丢失
printf("i: %d\n", i);
return 0;
}
1.1.3 函数调用时的转换
在函数调用过程中,实参的类型与形参的类型不一致时,编译器会自动将实参转换为形参的类型
#include <stdio.h>
// 函数接受一个 double 类型的参数
void printDouble(double num) {
printf("Double value: %f\n", num);
}
int main() {
int i = 10;
printDouble(i); // int 类型的 i 被转换为 double 类型传递给函数
return 0;
}
1.1.4 数组到指针的转换
在大多数表达式中,数组名会被隐式转换为指向数组首元素的指针。但在 sizeof 运算符、& 运算符和使用字符串字面量初始化字符数组这几种情况下,数组名不会进行这种转换。
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr; // 数组名 arr 被隐式转换为指向数组首元素的指针
printf("First element: %d\n", *ptr);
return 0;
}
1.1.5 指针类型的转换
空指针转换:NULL 指针(通常定义为 (void *)0)可以隐式转换为任意类型的指针。
#include <stdio.h>
int main() {
int *ptr = NULL; // NULL 指针被隐式转换为 int* 类型
return 0;
}
void* 类型的指针可以与其他类型的指针进行相互隐式转换。这在一些通用的内存操作函数(如 malloc、free)中很常见。
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr = (int *)malloc(sizeof(int)); // malloc 返回 void* 类型,隐式转换为 int* 类型
if (ptr != NULL) {
*ptr = 10;
printf("Value: %d\n", *ptr);
free(ptr);
}
return 0;
}
1.2 C语言中的强制转换
1.2.1 整数类型和浮点类型之间的转换
将浮点类型转换为整数类型:这种转换会直接截断小数部分,只保留整数部分
#include <stdio.h>
int main() {
float f = 3.14;
int i = (int)f; // 将 float 类型的 f 强制转换为 int 类型
printf("i: %d\n", i);
return 0;
}
将整数类型转换为浮点类型:转换后数值的大小不变,但类型变为浮点类型。
#include <stdio.h>
int main() {
int i = 5;
float f = (float)i; // 将 int 类型的 i 强制转换为 float 类型
printf("f: %f\n", f);
return 0;
}
1.2.2 不同整数类型之间的转换
当在不同大小的整数类型(如 char、short、int、long 等)之间进行转换时,可能会发生数据截断或扩展。
从大整数类型转换为小整数类型:会截断高位数据,只保留低位数据。
#include <stdio.h>
int main() {
int i = 300;
char c = (char)i; // int 类型的 i 强制转换为 char 类型,可能会发生数据截断
printf("c: %d\n", c);
return 0;
}
从小整数类型转换为大整数类型:如果是有符号类型,会进行符号扩展;如果是无符号类型,会进行零扩展。
#include <stdio.h>
int main() {
char c = -1;
int i = (int)c; // char 类型的 c 强制转换为 int 类型,进行符号扩展
printf("i: %d\n", i);
return 0;
}
符号扩展(Signed Extension)
- 符号扩展会将原数据的符号位(即最高位)复制到扩展后的所有新增位上。也就是说,如果原数据的符号位是 0(表示正数),则扩展后的新增位都为 0;如果原数据的符号位是 1(表示负数),则扩展后的新增位都为 1。这样做的目的是保证扩展后的数据在数值上与原数据相等。
#include <stdio.h>
int main() {
char c = -1; // char 类型通常是 8 位,-1 的二进制表示为 11111111
int i = (int)c; // 进行符号扩展,将 char 类型扩展为 int 类型(通常为 32 位)
printf("Value of c: %d\n", c);
printf("Value of i: %d\n", i);
return 0;
}
无符号位扩展
- 无符号扩展会将原数据的所有位直接复制到扩展后的低位,新增的高位全部用 0 填充。这是因为无符号整数没有符号位,不需要考虑符号的问题。
#include <stdio.h>
int main() {
unsigned char uc = 255; // unsigned char 类型是 8 位,255 的二进制表示为 11111111
unsigned int ui = (unsigned int)uc; // 进行无符号扩展,将 unsigned char 类型扩展为 unsigned int 类型(通常为 32 位)
printf("Value of uc: %u\n", uc);
printf("Value of ui: %u\n", ui);
return 0;
}
1.2.3 指针类型的转换
不同类型指针之间的转换可以将一个指针从一种类型强制转换为另一种类型,但需要谨慎使用,因为这可能会导致未定义行为。
#include <stdio.h>
int main() {
int num = 10;
int *intPtr = #
char *charPtr = (char *)intPtr; // 将 int* 类型的指针强制转换为 char* 类型
printf("Value at charPtr: %d\n", *charPtr);
return 0;
}
函数指针类型的转换允许将一个函数指针从一种类型转换为另一种类型,但同样需要谨慎操作,因为不同类型的函数指针在调用时可能有不同的参数和返回值要求。
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
typedef void (*VoidFuncPtr)();
int main() {
int (*intFuncPtr)(int, int) = add;
VoidFuncPtr voidFuncPtr = (VoidFuncPtr)intFuncPtr; // 将函数指针类型进行强制转换
// 这里直接调用 voidFuncPtr 会导致未定义行为,仅作示例展示类型转换
return 0;
}
二、C++中的类型转换
C++是兼容C的,因此C++除了有上述的转换方式,还有C++引入的类型转换方式。
2.1 static_cast 类型转换
static_cast主要用于基本数据类型之间的转换,以及具有继承关系的类对象指针或引用之间的转换。在进行类对象指针或引用的转换时,它不进行运行时类型检查,需要程序员自己确保转换的安全性。
#include <iostream>
class Base {};
class Derived : public Base {};
int main() {
double d = 3.14;
int i = static_cast<int>(d); // 基本数据类型转换
Derived derived;
Base* basePtr = static_cast<Base*>(&derived); // 类对象指针转换
return 0;
}
2.2 dynamic_cast 类型转换
要用于具有继承关系的类对象指针或引用之间的安全转换,尤其是在多态情况下。它会在运行时进行类型检查,如果转换不安全,对于指针类型会返回 nullptr,对于引用类型会抛出 std::bad_cast 异常。
#include <iostream>
class Base {
public:
virtual void func() {}
};
class Derived : public Base {};
int main() {
Base* basePtr = new Derived();
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr); // 安全的向下转换
if (derivedPtr) {
std::cout << "Conversion successful." << std::endl;
}
delete basePtr;
return 0;
}
2.3 const_cast 类型转换
专门用于添加或删除 const 修饰符。可以将 const 对象转换为非 const 对象,但需要注意的是,只有当对象本身不是 const 时,这种转换才是安全的,否则修改转换后的对象会导致未定义行为。
#include <iostream>
void printNonConst(int& num) {
std::cout << "Non-const value: " << num << std::endl;
}
int main() {
const int c = 10;
int& nonConstRef = const_cast<int&>(c); // 去除 const 修饰
// nonConstRef = 20; // 修改 const 对象会导致未定义行为
return 0;
}
2.4 reinterpret_cast 类型转换
reinterpret_cast 本质上是一种位模式的重新解释。它并不改变被转换对象的实际二进制数据,只是告诉编译器以另一种类型来解释这些二进制位。也就是说,它直接将一个类型的二进制表示按照目标类型进行解读,而不考虑类型之间的逻辑关系和语义。
reinterpret_cast提供最低级别的类型转换,它可以将任何类型的指针转换为其他类型的指针,也可以在整数类型和指针类型之间进行相互转换。这种转换不进行类型检查或格式转换,非常危险,容易导致未定义行为,一般只在与硬件或底层代码交互时使用。
#include <iostream>
int main() {
int num = 10;
int* intPtr = #
char* charPtr = reinterpret_cast<char*>(intPtr); // 指针类型转换
return 0;
}
2.5 explicit 关键字
在 C++ 中,explicit 关键字主要用于修饰类的构造函数和类型转换运算符,其核心作用是防止编译器进行隐式类型转换
当类的构造函数只有一个参数(或者除了第一个参数外其余参数都有默认值)时,编译器会默认允许使用这个构造函数进行隐式类型转换。使用 explicit 关键字修饰该构造函数后,就可以禁止这种隐式转换,只能进行显式的类型转换。
#include <iostream>
class MyClass {
public:
// 使用 explicit 修饰构造函数
explicit MyClass(int value) : data(value) {}
int getData() const {
return data;
}
private:
int data;
};
void printMyClass(const MyClass& obj) {
std::cout << obj.getData() << std::endl;
}
int main() {
// 显式转换,合法
MyClass obj1(10);
printMyClass(obj1);
// 隐式转换,会报错,因为构造函数被 explicit 修饰
// printMyClass(20);
// 显式转换,合法
printMyClass(MyClass(20));
return 0;
}
explicit 关键字也可以用于修饰类型转换运算符,防止通过该运算符进行隐式类型转换。
#include <iostream>
class MyInt {
public:
MyInt(int value) : data(value) {}
// 使用 explicit 修饰类型转换运算符
explicit operator int() const {
return data;
}
private:
int data;
};
int main() {
MyInt myInt(5);
// 显式转换,合法
int num = static_cast<int>(myInt);
std::cout << num << std::endl;
// 隐式转换,会报错,因为类型转换运算符被 explicit 修饰
// int num2 = myInt;
return 0;
}