变量与数据类型
建议先阅读:04_第一个程序与输入输出
原理
内存布局
变量是命名内存区域。类型决定了编译器为该变量分配多少字节以及如何解释这些字节:
| 类型 | 字节 (x86-64) | 解释方式 |
|---|---|---|
bool | 1 | 0 或 1 |
char | 1 | ASCII 码值,-128~127 |
unsigned char | 1 | 0~255 |
short | 2 | -32768~32767 |
int | 4 | 补码整数,-2³¹ ~ 2³¹-1 |
long long | 8 | 补码整数,-2⁶³ ~ 2⁶³-1 |
float | 4 | IEEE 754 单精度 (符号1 + 指数8 + 尾数23) |
double | 8 | IEEE 754 双精度 (符号1 + 指数11 + 尾数52) |
long double | 16 | 扩展精度(平台相关) |
char 本质是 1 字节整数——CPU 不区分”字符”和”数字”,只是编译器在 cout 时选择输出字符还是数值。
整数类型对比(C vs C++)
C 语言依赖平台特定的 int 大小,而 C++ 引入了 <cstdint> 固定宽度类型:
#include <cstdint>
int32_t fixed_int = 42; // 保证 32 位
uint64_t big_num = 1000000000ULL; // 保证 64 位无符号
int8_t tiny = -128; // 保证 8 位
// C 语言: int 可能是 16/32/64 位,依赖平台
// C++11+: std::int32_t 等固定宽度类型C 的 long 在不同平台上大小不同(Windows 32/64位都是4字节,Linux 64位是8字节),这是可移植性问题的常见来源。
内存对齐(Memory Alignment)
CPU 读取内存时按对齐边界效率最高。编译器会在结构体成员之间插入填充字节(padding):
struct Bad {
char a; // 1 byte + 3 padding
int b; // 4 bytes
char c; // 1 byte + 3 padding
}; // sizeof = 12 (不是 6!)
struct Good {
int b; // 4 bytes
char a; // 1 byte
char c; // 1 byte + 2 padding
}; // sizeof = 8对齐规则:成员地址必须是 min(成员大小, 对齐上限) 的倍数。x86-64 通常对齐上限为 8 字节。
使用 alignof 查看对齐要求,offsetof 查看成员偏移:
#include <cstddef>
std::cout << alignof(char) << std::endl; // 1
std::cout << alignof(int) << std::endl; // 4
std::cout << alignof(double) << std::endl; // 8
std::cout << offsetof(Bad, c) << std::endl; // 8 (偏移量)初始化与未定义行为
局部变量不自动初始化——其值为栈上残留的任意数据。读取未初始化变量是未定义行为(UB)。全局/静态变量则自动初始化为零,因为它们存储在 .bss 段,操作系统在加载时清零。
int global_var; // 自动初始化为 0,存储在 .bss
int main() {
int local_var; // 未初始化,栈上的随机值
// int x = local_var; // 未定义行为!
}名词解释:未定义行为(Undefined Behavior, UB) 是指 C++ 标准未规定其结果的操作,编译器可以产生任何结果,包括看似正确的代码突然出错。
浮点精度
浮点数用二进制表示,绝大多数十进制小数无法精确表示。0.1 + 0.2 != 0.3 源于 IEEE 754 的固有限制。浮点比较应使用 epsilon 范围判断。
#include <cmath>
double a = 0.1, b = 0.2;
bool equal = std::abs(a + b - 0.3) < 1e-9; // 正确比较方式
// 而不是: if (a + b == 0.3) // 错误!整数溢出
有符号整数溢出是未定义行为——编译器可能删除相关分支。无符号整数溢出是定义良好的(模 2^n 回绕)。
unsigned int x = UINT_MAX; // 4294967295
x++; // 变成 0(定义良好)
int y = INT_MAX; // 2147483647
// y++; // 未定义行为! 不要这样做整数提升(Integer Promotion)
C++ 在表达式中会自动提升小类型。char、short 参与运算时先提升为 int:
char a = 100, b = 100;
// char result = a + b; // 错误: a+b 是 int (200),超出 char 范围
int safe = a + b; // 正确
// C 语言同样有此规则,但容易被忽视导致溢出隐式类型转换规则
转换按”安全级别”自动进行,低级别可隐式转为高级别:
bool → char → short → int → unsigned int → long → unsigned long → long long → unsigned long long
↘ float → double → long double
int i = 42;
double d = i; // 安全: int → double,无损
float f = i; // 安全: int → float
char c = 42;
int big = c; // 安全: char → int
// 反向: double → int 截断,int → char 可能溢出语法
声明与初始化
int a = 10; // 拷贝初始化
int b(20); // 直接初始化
int c{30}; // 列表初始化 (C++11, 推荐, 禁止窄化转换)
int d{}; // 初始化为 0
auto e = 3.14; // 类型推导: double列表初始化
int x{3.14}直接编译错误,而非静默截断。这是 C++11 的安全改进。
const 与 constexpr
const int MAX = 100; // 运行时常量,初始化后不可改
constexpr int SIZE = 50; // 编译时常量,必须编译期可求值
const int runtime_val = get_input(); // OK: const 不要求编译期
// constexpr int bad = get_input(); // 错误: constexpr 要求编译期可求值
constexpr int square(int x) { return x * x; } // 可在编译期执行
int arr[square(5)]; // arr[25]constexpr 隐式包含 const,但约束更强:值必须在编译时确定。
类型转换
double pi = 3.14159;
int n = pi; // 隐式转换,截断为 3
int m = static_cast<int>(pi); // 推荐: 显式标注意图
int bad = (int)pi; // C 风格: 不检查,危险
int a = 5, b = 2;
double result = static_cast<double>(a) / b; // 2.5,而非 2
// reinterpret_cast: 位级重新解释(极其危险)
int val = 65;
char ch = reinterpret_cast<char&>(val); // 可能得到 'A'四种 C++ 强制转换:
static_cast(编译期检查)、const_cast(去/加 const)、dynamic_cast(运行时类型转换,需 RTTI)、reinterpret_cast(位级重新解释)。C 风格(type)expr等价于任意一种,无编译器检查。
sizeof
sizeof(int); // 返回编译时常量,通常是 4
sizeof(arr); // 整个数组的字节数
sizeof(arr) / sizeof(arr[0]); // 数组元素个数
// C++17: sizeof 可用于类型
sizeof(int); // 传统写法
sizeof(int); // C++11 后也可: sizeof(int)auto 类型推导
auto 让编译器根据初始化表达式推导类型(C++11):
auto x = 42; // int
auto y = 3.14; // double
auto z = "hello"; // const char*
auto w = {1, 2, 3}; // std::initializer_list<int>
// 配合 STL 极大简化代码
std::vector<int> v = {1, 2, 3};
for (auto it = v.begin(); it != v.end(); ++it) {
std::cout << *it << ' ';
}
// C++14: auto 返回类型
auto add(auto a, auto b) { return a + b; } // C++20 简写auto 推导规则:忽略引用,保留 const/volatile。
const int& r = 42; auto x = r;推导为int(不是const int&)。
decltype(C++11)
decltype 查询表达式的类型,常用于模板和泛型编程:
int x = 42;
decltype(x) y = 0; // int: y 的类型和 x 完全相同
decltype(x + 0.5) z = 1.0; // double: 表达式结果类型
// 用途: 模板返回类型
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) { // 尾置返回类型
return a + b;
}auto vs decltype
const int& ref = 42;
auto a = ref; // int (去引用、去const)
decltype(ref) b = ref; // const int& (保留引用和const)
// auto: 推导变量类型,常用于循环、lambda
// decltype: 查询精确类型,常用于模板C++14 简化了尾置返回类型:
auto add(T a, U b) { return a + b; }直接用 auto 推导返回类型。
结构化绑定(Structured Bindings, C++17)
#include <tuple>
#include <map>
// 绑定 pair
std::map<std::string, int> scores = {{"Alice", 95}, {"Bob", 87}};
for (auto& [name, score] : scores) {
std::cout << name << ": " << score << '\n';
}
// 绑定 tuple
auto [x, y, z] = std::make_tuple(1, 2.0, "three");
// x=1, y=2.0, z="three"
// 绑定数组
int arr[] = {10, 20, 30};
auto [a, b, c] = arr;
// a=10, b=20, c=30变量作用域与生命周期
| 作用域 | 存储位置 | 初始化 | 生命周期 |
|---|---|---|---|
| 局部变量 | 栈 (stack) | 不自动初始化 | 函数返回时销毁 |
| static 局部 | .data/.bss | 自动初始化为0 | 程序结束时销毁 |
| 全局变量 | .data/.bss | 自动初始化为0 | 程序结束时销毁 |
| auto 变量 (C++17) | 容器管理 | 由构造函数决定 | 离开作用域时 |
void counter() {
static int count = 0; // 只初始化一次
count++;
std::cout << "调用次数: " << count << '\n';
}
// 每次调用 count 不会重置C 语言没有
static局部变量的概念(C89 有,但用法不同),C++ 的static局部变量是线程安全的初始化(C++11 起)。
实践
ASCII 码关键值:'A'=65, 'Z'=90, 'a'=97, 'z'=122, '0'=48, '9'=57。大小写转换差 32。
安全运算——将 int 转为 long long 再运算以防溢出:
long long safe = static_cast<long long>(a) * b;常见陷阱
// 1. 大小写转换
char lower = 'A' + 32; // 'a'
char upper = 'a' - 32; // 'A'
// 2. 数字字符转数字
int digit = '7' - '0'; // 7
// 3. 判断奇偶 (位运算)
bool is_odd = n & 1; // 比 n % 2 更快
// 4. 交换两个数 (不用临时变量)
a ^= b; b ^= a; a ^= b; // 仅适用于整数,且不能是同一变量类型选择建议
// 竞赛/算法:
int // 大多数情况
long long // 可能超过 2^31 时
double // 浮点计算
// 工程代码:
size_t // 数组索引/大小
int32_t // 需要固定宽度时
uint8_t // 字节数据练习
| 题号 | 题目 | 链接 | 知识点 |
|---|---|---|---|
| P1001 | A+B Problem | https://www.luogu.com.cn/problem/P1001 | 输入输出、变量 |
| P1000 | 超级玛丽游戏 | https://www.luogu.com.cn/problem/P1000 | 顺序结构、输出 |
| P1003 | 铺地毯 | https://www.luogu.com.cn/problem/P1003 | 数组、循环 |
| P1012 | 拼数 | https://www.luogu.com.cn/problem/P1012 | 语法练习 |
| 202 | 快乐数 | https://www.luogu.com.cn/problem/P1001 | 整数运算、循环检测 |
| 258 | 各位相加 | https://www.luogu.com.cn/problem/P1001 | 数学运算、类型选择 |
| 412 | Fizz Buzz | https://www.luogu.com.cn/problem/P1001 | 取模运算、条件判断 |
| 415 | 字符串相加 | https://www.luogu.com.cn/problem/P1001 | 字符与数字转换 |