变量与基本类型
定义与作用
变量是程序中数据的最小容器。C++ 的类型系统是静态强类型——每个变量的类型在编译期确定,且不可改变。基本类型(fundamental types)是 C++ 类型系统的基石,理解它们的存储大小、取值范围和运算规则,是编写正确、高效程序的先决条件。
核心原理
基本类型全景
类型大小与取值范围
| 类型 | 最小宽度 | 典型大小(64 位系统) | 取值范围 |
|---|---|---|---|
bool | 未指定 | 1 字节 | true / false |
char | 8 位 | 1 字节 | -128 ~ 127 或 0 ~ 255 |
short | 16 位 | 2 字节 | -32768 ~ 32767 |
int | 16 位 | 4 字节 | 约 ±21 亿 |
long | 32 位 | 8 字节(Linux)/4 字节(Windows) | 平台相关 |
long long | 64 位 | 8 字节 | 约 ±9×10¹⁸ |
float | IEEE 754 单精度 | 4 字节 | 约 7 位有效数字 |
double | IEEE 754 双精度 | 8 字节 | 约 15 位有效数字 |
long double | ≥ double | 8/16 字节 | 平台相关 |
使用
<cstdint>中的int32_t、uint64_t等固定宽度类型,可以消除平台差异。
内存布局示意
多字节类型的字节序分为大端序(高位在前)和小端序(低位在前),x86/x64 架构使用小端序。
初始化方式
C++ 提供多种变量初始化语法,C++11 统一了初始化语法:
int a = 10; // 拷贝初始化
int b(10); // 直接初始化(C++98)
int c{10}; // 列表初始化(C++11,推荐)
int d = {10}; // 拷贝列表初始化
int e{}; // 值初始化 → 0
列表初始化的优势:禁止窄化转换(narrowing),在编译期捕获精度丢失。
int valid{42}; // 正确
int invalid{3.14}; // 错误:窄化转换(double → int 丢失信息)
完整示例
示例一:学生信息卡片——基本类型实践
场景说明:创建一个学生信息结构,使用所有基本类型存储学号、年龄、成绩、是否在读等字段,并格式化输出。
#include <iostream>
#include <string>
#include <iomanip>
int main() {
// 整型:学号、年龄、班级人数
long long student_id{20240101001LL};
short age{20};
int class_size{45};
// 浮点:GPA、学费
float gpa{3.85f};
double tuition{58000.50};
// 字符与布尔
char grade_level{'A'};
bool is_enrolled{true};
bool has_scholarship{false};
// 固定宽度类型(推荐用于跨平台)
uint32_t enrollment_year{2024};
std::cout << "========== 学生信息卡片 ==========" << std::endl;
std::cout << std::left << std::setw(18) << "学号:"
<< student_id << std::endl;
std::cout << std::setw(18) << "年龄:" << age << std::endl;
std::cout << std::setw(18) << "年级等级:" << grade_level << std::endl;
std::cout << std::setw(18) << "入学年份:" << enrollment_year << std::endl;
std::cout << std::fixed << std::setprecision(2);
std::cout << std::setw(18) << "GPA:" << gpa << std::endl;
std::cout << std::setw(18) << "学费:" << tuition << std::endl;
std::cout << std::boolalpha; // 输出 true/false 而非 1/0
std::cout << std::setw(18) << "在读状态:" << is_enrolled << std::endl;
std::cout << std::setw(18) << "奖学金:" << has_scholarship << std::endl;
std::cout << std::setw(18) << "班级人数:" << class_size << std::endl;
// 类型大小展示
std::cout << "\n===== 类型大小 =====" << std::endl;
std::cout << "short: " << sizeof(short) << " 字节" << std::endl;
std::cout << "int: " << sizeof(int) << " 字节" << std::endl;
std::cout << "long long: " << sizeof(long long) << " 字节" << std::endl;
std::cout << "float: " << sizeof(float) << " 字节" << std::endl;
std::cout << "double: " << sizeof(double) << " 字节" << std::endl;
std::cout << "bool: " << sizeof(bool) << " 字节" << std::endl;
return 0;
}
预期输出(64 位系统):
========== 学生信息卡片 ==========
学号: 20240101001
年龄: 20
年级等级: A
入学年份: 2024
GPA: 3.85
学费: 58000.50
在读状态: true
奖学金: false
班级人数: 45
===== 类型大小 =====
short: 2 字节
int: 4 字节
long long: 8 字节
float: 4 字节
double: 8 字节
bool: 1 字节
逐段分析:
long long确保学号不受平台影响(至少 64 位)float用f后缀,double是默认浮点字面量类型uint32_t来自<cstdint>,保证 32 位无符号,跨平台一致std::boolalpha让bool输出为true/false而非1/0sizeof是编译期运算符,返回类型或变量占用的字节数
示例二:类型溢出与精度陷阱
场景说明:演示整型溢出和浮点精度问题,帮助理解类型边界。
#include <iostream>
#include <limits>
#include <cstdint>
int main() {
// ---- 整型溢出 ----
int16_t small_max = 32767; // int16_t 最大值
int16_t overflow = small_max + 1; // 溢出!
std::cout << "===== 整型溢出 =====" << std::endl;
std::cout << "int16_t 最大值: " << small_max << std::endl;
std::cout << "最大值 + 1: " << overflow
<< " ← 回绕到最小值(有符号溢出是未定义行为)" << std::endl;
// 无符号溢出是明确定义的(回绕)
uint16_t unsigned_max = 65535;
uint16_t unsigned_overflow = unsigned_max + 1;
std::cout << "uint16_t 最大值 + 1: " << unsigned_overflow
<< " ← 回绕到 0(定义良好)" << std::endl;
// ---- 浮点精度 ----
std::cout << "\n===== 浮点精度 =====" << std::endl;
float f1 = 0.1f;
float f2 = 0.2f;
float sum = f1 + f2;
std::cout << std::setprecision(10);
std::cout << "0.1f + 0.2f = " << sum << std::endl;
std::cout << "是否等于 0.3f? " << std::boolalpha
<< (sum == 0.3f) << " ← float 无法精确表示 0.1" << std::endl;
// double 精度更高,但仍是近似
double d1 = 0.1;
double d2 = 0.2;
double dsum = d1 + d2;
std::cout << "0.1 + 0.2 (double) = " << dsum << std::endl;
// 大数吃小数
float large = 1.0e8f;
float small = 1.0f;
std::cout << "\n1 亿 + 1 (float) = " << (large + small)
<< " ← 精度丢失," << small << " 被吞掉了" << std::endl;
// ---- 类型极限 ----
std::cout << "\n===== 类型极限 =====" << std::endl;
std::cout << "int 最大值: " << std::numeric_limits<int>::max() << std::endl;
std::cout << "int 最小值: " << std::numeric_limits<int>::min() << std::endl;
std::cout << "double 最小值: " << std::numeric_limits<double>::min() << std::endl;
std::cout << "double 精度: " << std::numeric_limits<double>::epsilon()
<< std::endl;
return 0;
}
预期输出:
===== 整型溢出 =====
int16_t 最大值: 32767
最大值 + 1: -32768 ← 回绕到最小值(有符号溢出是未定义行为)
uint16_t 最大值 + 1: 0 ← 回绕到 0(定义良好)
===== 浮点精度 =====
0.1f + 0.2f = 0.3000000119
是否等于 0.3f? false ← float 无法精确表示 0.1
0.1 + 0.2 (double) = 0.30000000000000004
1 亿 + 1 (float) = 100000000 ← 精度丢失,1 被吞掉了
===== 类型极限 =====
int 最大值: 2147483647
int 最小值: -2147483648
double 最小值: 2.22507e-308
double 精度: 2.22045e-16
逐段分析:
- 有符号整型溢出是未定义行为(UB),编译器可能做任何优化假设,不应依赖回绕行为
- 无符号整型溢出是明确定义的(模运算回绕),但仍应避免
- 浮点数用 IEEE 754 二进制表示,0.1 在二进制中是无限循环小数,无法精确存储
float约 7 位有效数字,1 亿 + 1超出精度范围,小数被"吞掉"<limits>头文件中的std::numeric_limits<T>提供每种类型的极限值查询
易错场景与面试考点
易错场景
| 场景 | 错误表现 | 正确做法 |
|---|---|---|
| 有符号整型溢出 | 回绕值不可预测,编译器可能删除溢出检查 | 使用更大的类型或 unsigned |
浮点数用 == 判等 | 0.1 + 0.2 == 0.3 为 false | 用 fabs(a - b) < epsilon 比较 |
| 浮点大数吃小数 | 累加结果偏离预期 | 从小到大累加或使用 double |
sizeof(long) 依赖平台 | Windows 4 字节,Linux 8 字节 | 使用 int64_t 固定宽度类型 |
char 有无符号不确定 | 位运算结果跨平台不一致 | 明确使用 signed char 或 unsigned char |
| 列表初始化窄化转换 | int x{3.14} 编译报错 | 明确写出转换意图或使用正确类型 |
常见面试问题
int占几个字节?——标准只规定最小宽度(16 位),实际大小由实现决定,现代平台上通常为 4 字节(32 位)。有符号整型溢出是未定义行为,为什么?——C++ 标准不规定有符号整型的编码方式(虽然绝大多数平台用补码),因此溢出行为未定义,编译器可以做更激进的优化。
0.1 + 0.2 == 0.3为什么是 false?如何正确比较浮点数?——0.1 和 0.2 在 IEEE 754 中无法精确表示。应使用fabs(a - b) < epsilon判等,epsilon 通常取std::numeric_limits<double>::epsilon()的若干倍。float和double的区别,什么时候用float?——float4 字节约 7 位有效数字,double8 字节约 15 位。图形/游戏中的顶点数据可用float节省带宽和缓存;科学计算和金融场景必须用double。long和long long的区别?C++11 为什么引入long long?——long最小 32 位,long long最小 64 位。C++11 将long long标准化以提供跨平台的 64 位整型。列表初始化
{}相比=有什么优势?——禁止窄化转换;不会发生"最令人烦恼的解析"(most vexing parse);统一了各种初始化场景的语法。
小结
- C++ 基本类型分整型、浮点、字符和其他四大类
- 使用
<cstdint>的固定宽度类型消除跨平台差异 - 列表初始化
{}是推荐写法,禁止窄化转换 - 有符号溢出是未定义行为;浮点数不能用
==判等