数据变量进阶

内容简介

本篇补充类型转换、整数/浮点运算细节、size_t 和固定宽度整数类型。C/C++ 会在表达式中自动进行类型提升,也允许程序员手动强制转换。

这里面向 CandCPP 主线,讲到能避开常见坑为止。C 语言规则的完整细节,包括整型提升、通常算术转换、有符号/无符号混用和指针强转边界,放到 C语言类型转换规则

这部分看起来像“小细节”,但实际很容易造成 bug:

  • 8 / 5 为什么是 1
  • int value = 23.9; 为什么小数丢失。
  • unsigned int a = 0; a - 1 为什么变成很大的数。
  • size_t 为什么经常和 int 比较时有警告。
  • int32_tint 有什么区别。

用法

自动类型转换

整型提升

小于 int 的整数类型参与运算时,通常会先提升为 int 再计算。

char a = 1;
short b = 2;
int c = a + b;  // a 和 b 会先提升为 int 再计算

这就是为什么 char 参与算术运算后,结果通常不是 char

整数除法和浮点除法

std::cout << 8 / 5 << std::endl;     // 1
std::cout << 8.0 / 5 << std::endl;   // 1.6

8 / 5 两边都是整数,所以执行整数除法,小数部分直接丢弃。

如果需要小数结果,至少让一个操作数是浮点数:

double result = 8.0 / 5;

赋值时的窄化转换

double price = 23.9;
int value = price;  // value 是 23,小数部分被截断

从范围更大或精度更高的类型转到范围更小或精度更低的类型时,可能发生数据丢失。

有符号和无符号混用

int a = -1;
unsigned int b = 1;
 
std::cout << (a < b) << std::endl;  // 结果可能不符合直觉

有符号整数和无符号整数混合运算时,编译器可能把有符号数转换成无符号数。负数转换成无符号数后会变成很大的值。

强制类型转换

C 风格强制转换:

double result = (double)8 / 5;

C++ 推荐写法:

double result = static_cast<double>(8) / 5;

static_cast 比 C 风格转换更清楚,也更容易被代码审查发现。

size_t

size_t 是一种无符号整数类型,通常用来表示大小、长度、下标范围和内存字节数。

常见来源:

std::string text = "hello";
std::size_t len = text.size();
 
std::vector<int> nums = {1, 2, 3};
std::size_t count = nums.size();

常见用途:

for (std::size_t i = 0; i < nums.size(); ++i) {
    std::cout << nums[i] << std::endl;
}

需要注意:size_t 是无符号类型,不适合直接表示可能为负的值。

std::size_t n = 0;
std::cout << n - 1 << std::endl;  // 下溢,结果变成很大的数

size_tint 比较时也容易出现警告:

int i = -1;
std::vector<int> nums = {1, 2, 3};
 
if (i < nums.size()) {
    // 容易产生有符号/无符号比较问题
}

更稳妥的做法是先确认变量含义:

  • 表示数量、长度、内存大小:用 std::size_t
  • 表示可能为负的差值、偏移、业务数值:用有符号整数。

固定宽度整数:int8_tint16_tint32_tint64_t

普通 int 的大小通常是 32 位,但 C/C++ 标准并不把所有平台细节都写死。固定宽度整数用于明确指定整数位数。

头文件:

#include <cstdint>

常见类型:

类型含义常见范围
int8_t8 位有符号整数-128 到 127
uint8_t8 位无符号整数0 到 255
int16_t16 位有符号整数-32768 到 32767
uint16_t16 位无符号整数0 到 65535
int32_t32 位有符号整数约 -21 亿到 21 亿
uint32_t32 位无符号整数0 到约 42 亿
int64_t64 位有符号整数很大
uint64_t64 位无符号整数非常大

示例:

#include <cstdint>
#include <iostream>
 
int main()
{
    std::int32_t user_id = 1001;
    std::uint64_t file_size = 1024;
 
    std::cout << user_id << std::endl;
    std::cout << file_size << std::endl;
    return 0;
}

适合使用固定宽度整数的场景:

  • 网络协议字段。
  • 二进制文件格式。
  • 嵌入式寄存器。
  • 跨平台序列化。
  • 明确要求位宽的数据结构。

普通业务代码如果没有明确位宽要求,intlong longstd::size_t 往往更自然。

最佳代码实践

  • C++ 中优先使用 static_cast<T>(value),比 C 风格强转更明确。
  • 涉及有符号和无符号整数混合运算时,先明确类型。
  • 需要小数结果时,把至少一个操作数转成浮点类型。
  • 不要忽略编译器关于类型转换、溢出、精度丢失的警告。
  • 表示容器大小、字符串长度、内存大小时使用 std::size_t
  • 对二进制协议、文件格式、嵌入式寄存器使用 std::int32_tstd::uint32_t 等固定宽度整数。

错误用法

int value = 23.9;  // 小数部分被截断,value 是 23
unsigned int a = 0;
std::cout << a - 1 << std::endl;  // 无符号下溢,结果会变成很大的数
double result = static_cast<double>(8 / 5);  // 先做整数除法,结果仍然是 1.0
for (int i = 0; i < nums.size(); ++i) {
    // 错误倾向:int 和 size_t 混合比较,可能有警告
}
std::size_t index = -1;  // 错误倾向:-1 转成很大的无符号数
std::int8_t value = 200;  // 超出 int8_t 常见范围

注意事项

  • 整型提升大致遵循“小类型先提升到 int,再参与运算”。
  • 浮点数有精度误差,不适合直接表示金额等要求精确的小数。
  • 强制类型转换能绕过部分类型检查,使用前要明确转换是否真的合理。
  • std::size_t 来自 <cstddef>,很多标准库头文件也会间接提供它;写 C++ 时推荐使用 std::size_t
  • 固定宽度整数来自 <cstdint>,C 语言中对应 <stdint.h>
  • int8_tuint8_t 在很多实现中本质接近字符类型,直接输出时可能按字符显示;需要数值显示时可转换成 int
  • C 语言细节继续看 C语言类型转换规则;C++ 转换写法继续看 cpp自动类型转换

返回:CandCpp基础数据变量

学习路径