在 C++ 中,当我们需要手动转换数据类型时,除了兼容 C 语言的 (目标类型)表达式 写法之外,C++ 还专门提供了 4 个转换运算符:static_cast、dynamic_cast、const_cast 和 reinterpret_cast。
为什么要把强制转换拆分成 4 个呢?说白了,C 语言的强制转换把各种安全和不安全的转换全都混在了一起,一旦写错了,编译器也很难帮我们发现。C++ 将它们按用途拆开之后,编译器就能针对不同场景做安全检查了。
在这 4 个运算符中,static_cast(静态类型转换)是日常开发中使用频率最高的一个。凡是基础数值之间的显式转换,或者在编译阶段就能确定安全的关联类型转换,我们都优先使用 static_cast。
基本语法与数值转换
在 C++ 中,不同数值类型除了能由编译器自动完成隐式类型转换外,很多时候还需要我们手动显式转换。使用 static_cast 时,编译器会在编译阶段检查两种类型是否兼容,遇到不合理的转换就会直接报错。
语法:
static_cast<目标类型>(表达式)说明:
其中,尖括号 <> 里填写希望转换成的目标类型,圆括号 () 里填写需要转换的变量或表达式。static_cast 会计算并返回一个目标类型的新值,并不会修改原变量本身的类型。
示例 1:基础数值的显式转换
#include <iostream>
int main() {
int finished = 3;
int total = 8;
// 先转为 double 再做除法
double rate = static_cast<double>(finished) / total * 100;
std::cout << "完成率:" << rate << "%" << '\n';
// 将 double 转为 int
int wholeRate = static_cast<int>(rate);
std::cout << "整数部分:" << wholeRate << "%" << '\n';
return 0;
}运行结果如下。
完成率:37.5%
整数部分:37%分析:
在上面代码中,static_cast<double>(finished) 表示取出整型变量 finished 的值,将其转换为 double 类型的 3.0。随后再除以 total 并乘以 100,就能算出带小数的完成率 37.5。
接着,static_cast<int>(rate) 将浮点数 37.5 转换为整数 37。需要注意的是,浮点数转为整数时会直接舍弃小数部分,并不会四舍五入。如果我们希望按四舍五入取整,可以配合前面学过的 std::round() 函数来实现。
在做除法保留小数时,有一个初学者容易踩坑的细节:圆括号 () 里只能括住被除数 finished,千万不要把整个除法算式都括进去。
// 正确方式
double rate1 = static_cast<double>(finished) / total;
// 错误方式
double rate2 = static_cast<double>(finished / total);为什么错误方式算不出小数呢?因为写成 static_cast<double>(finished / total) 时,程序会先计算括号里的整数除法 3 / 8 得到整数 0,最后再把 0 转成 0.0,小数部分在转换前就已经丢掉了。这一点大家一定要看清楚。
常见应用场景
除了基础数值之间的转换之外,在实际开发中,以下 3 种场景也经常会用到 static_cast:
1. enum class 与整数互转
在 C++ 中,强类型枚举为了保证类型安全,不允许自动隐式转换为 int 整数,也不能直接传给 std::cout 输出。当我们需要取出它对应的整数编号,或者把整数转回枚举值时,就应该使用 static_cast。
示例 2:enum class 与 int 互转
#include <iostream>
enum class Status {
Pending = 1,
Running = 2,
Finished = 3
};
int main() {
Status current = Status::Running;
int code = static_cast<int>(current); // 枚举转 int
std::cout << "状态码:" << code << '\n';
Status next = static_cast<Status>(3); // int 转枚举
if (next == Status::Finished) {
std::cout << "任务已完成" << '\n';
}
return 0;
}运行结果如下。
状态码:2
任务已完成分析:
在这个例子中,Status::Running 属于强类型枚举值,无法直接传给 std::cout 打印。我们通过 static_cast<int>(current) 取出了它对应的整数 2。反过来,static_cast<Status>(3) 则把整数 3 转成了枚举值 Status::Finished。
2. 还原 void* 通用指针
在对接一些底层 C 语言接口或回调函数时,我们偶尔会遇到无类型的通用指针 void*。任何普通数据指针都可以自动隐式转换为 void*,但反过来把 void* 还原为 double* 或 int* 等具体指针时,C++ 要求必须使用 static_cast 显式转换。
示例 3:还原 void* 指针
#include <iostream>
int main() {
double weight = 65.5;
void* rawPtr = &weight; // 隐式转为 void*
// 显式还原为 double* 指针
double* dPtr = static_cast<double*>(rawPtr);
std::cout << *dPtr << '\n';
return 0;
}运行结果如下。
65.5分析:
通用指针 void* 只记录了内存起始地址,不包含具体的数据类型信息,也不知道往后要读几个字节,因此不能直接写 *rawPtr 解引用。
为了方便演示还原语法,这里我们先将 double 变量的地址存入 rawPtr,再通过 static_cast<double*>(rawPtr) 还原回 double* 指针,这样就能通过 *dPtr 正常读出 65.5 了。
这里小伙伴们要注意,还原 void* 时填写的目标指针类型必须与原变量的真实类型一致。如果原变量是 double 而我们误写成 static_cast<int*>(rawPtr),编译虽然能通过,但解引用时会按错误类型读出乱码数据。
3. 继承层次中的指针转换
在学习类的继承时,我们通常会把基类(父类)看作在上方,把派生类(子类)看作在下方。因此,从派生类转为基类叫做 “向上转换”,从基类转回派生类则叫做 “向下转换”。
其中,向上转换本来就是安全的,编译器会自动完成。而向下转换则需要使用转换运算符:当我们明确知道基类指针实际指向的就是某个派生类对象时,可以使用 static_cast 将其转回派生类指针。
示例 4:继承层次中的指针转换
#include <iostream>
class Animal {
};
class Dog : public Animal {
public:
void bark() const {
std::cout << "汪汪!" << '\n';
}
};
int main() {
Dog myDog;
Animal* basePtr = &myDog; // 向上转换
// 向下转换回 Dog* 指针
Dog* dogPtr = static_cast<Dog*>(basePtr);
dogPtr->bark();
return 0;
}运行结果如下。
汪汪!分析:
在这个例子中,指针 basePtr 虽然声明为 Animal* 类型,但它实际指向的就是 myDog 对象。由于基类 Animal 中没有定义 bark() 函数,直接写 basePtr->bark() 会编译报错。
我们通过 static_cast<Dog*>(basePtr) 将它转回 Dog* 指针后,就能正常调用 Dog 类独有的 bark() 成员函数了。
提示 在继承体系中,static_cast 不会在程序运行时检查 basePtr 实际指向的对象是不是 Dog。如果无法提前确定对象的真实类型,向下转换时应该使用 dynamic_cast 做运行时安全检查。
为什么不推荐使用 C 风格强制转换?
可能有些小伙伴会问:“C 语言风格的 (int)rate 写起来只有几个字母,为什么在 C++ 里还要写这么长的 static_cast 呢?”
原因很简单:static_cast 能让编译器在编译阶段替我们把关,提前找出危险的不合理转换,主要体现在以下 2 个方面:
- 拒绝无关类型的指针互转:比如把
int*转为double*。由于两者占用的字节数不同,使用 C 风格(double*)&num强转后读写会导致内存越界,而static_cast<double*>(&num)会在编译阶段直接报错。 - 拒绝去掉指针或引用的 const 限定:
static_cast不能去除指针或引用上的const只读属性,防止我们在无意间修改只读数据。如果我们确实需要去除只读限定,必须使用专门的 const_cast。
下面这两种危险转换如果改用 static_cast 来写,编译器在编译阶段就会直接报错。
int num = 10;
const int limit = 100;
double* p1 = static_cast<double*>(&num); // 报错:无关指针转换
int* p2 = static_cast<int*>(&limit); // 报错:不能去除 const正是因为有了这层编译期检查,平时编写 C++ 代码需要显式转换时,我们都优先使用 static_cast,尽量避免使用 C 语言风格的强制转换。
常见问题
1. 可以使用 static_cast 把字符串(如 "123")直接转换成 int 整数吗?
不能。static_cast 只能用于数值、枚举或存在关联的指针类型之间,无法解析字符串里的文本内容。
如果我们想要在字符串与整数之间互相转换,可以使用 <string> 头文件中的 std::stoi() 和 std::to_string() 函数,后面很快就会学到。
