C++ static_cast

在 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() 函数,后面很快就会学到。

上一篇:

下一篇:

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号