本章目标:选对类型(会溢出就用 long long);用安全的初始化方式;把流程控制写对。大部分语法和 Java 相似,重点在“哪里不一样”。
2.1 变量与初始化
心智模型:变量是“贴了标签的盒子”。问题是——盒子里的东西你放没放进去?没放的盒子(未初始化变量)里装的是垃圾。
四种初始化方式,推荐花括号
int a; 不初始化(值是垃圾,读它=未定义行为);int b = 0; 拷贝初始化;int c(0); 直接初始化;int d{0}; 列表初始化(C++11 起推荐)。列表初始化的特殊价值:禁止窄化转换。int n{3.14} 会直接编译报错,而 int n = 3.14 会静默把小数丢掉。
int n1 = 3.14; // 编译通过,n1 变成 3,静默丢失小数
int n2{3.14}; // 编译错误:narrowing conversion,编译器拦住
int x{} 是最省事的“保证有值”
空花括号把内置类型初始化为 0。写 int x{}; 比写 int x = 0; 更简洁,也比不初始化安全。类类型(如 std::string s;)即使不写初始化,也会调用默认构造函数,是安全的。
作用域与生存期
变量离开它所在的花括号作用域,生命周期就结束;栈上的对象会在这里自动析构。这是 C++ 最重要的资源管理机制 RAII 的基础(第 4 章细讲)。Java 里对象活到 GC 认定没人引用为止,和这里完全不同。
✍️ 本节练习
声明一个 int(值 7)、一个 double(值 3.5)、一个 std::string(值 hi),在一行里依次输出,用空格隔开。
输入 无
输出 7 3.5 hi
(本题无输入)7 3.5 hi💡 提示 推荐用花括号初始化:int n{7};
✅ 查看参考答案与解析
#include <iostream>
#include <string>
int main() {
int n{7};
double d{3.5};
std::string s{"hi"};
std::cout << n << ' ' << d << ' ' << s << '\n';
return 0;
}
解析 string 在 <string> 头文件里;花括号初始化是 C++11 起的推荐写法。
读入圆的半径 r(double),输出圆的面积,保留 3 位小数。圆周率用 3.14159265。
输入 一个实数 r。
输出 面积,保留 3 位小数。
212.566💡 提示 std::fixed << std::setprecision(3),需要 #include <iomanip>。
✅ 查看参考答案与解析
#include <iomanip>
#include <iostream>
int main() {
double r;
std::cin >> r;
std::cout << std::fixed << std::setprecision(3)
<< 3.14159265 * r * r << '\n';
return 0;
}
解析 fixed 表示定点小数,setprecision(3) 保留 3 位小数。
2.2 基本类型与溢出
选错类型是 C++ 新手第一类隐蔽 bug:程序看起来没问题,结果悄悄错了。
类型大小:心里要有数
bool 1 字节、char 1、short 2、int 4、long long 8、指针在 64 位下 8。注意 long 在 Windows 是 4、Linux 是 8,不要依赖它。需要精确宽度时用 <cstdint> 里的 int32_t、int64_t 等别名——只有它们才给你和 Java 一样的保证。
有符号整数溢出 = 未定义行为
Java 的 int 溢出会回绕成一个错误但确定的数;C++ 里有符号整数溢出是未定义行为,编译器优化后可能出现完全无法预测的结果。所以判断“会不会溢出”要在算之前想清楚。
long long a, b;
std::cin >> a >> b;
std::cout << a * b << '\n'; // 两个 10^9 相乘是 10^18,必须 long long
✍️ 本节练习
读入两个整数(可能很大,乘积会超过 int 范围),输出它们的乘积。
输入 一行两个整数 a b(|a|,|b| ≤ 10^9)。
输出 a × b 的结果。
1000000 10000001000000000000💡 提示 两个 10^9 相乘是 10^18,int 装不下,用 long long。
✅ 查看参考答案与解析
#include <iostream>
int main() {
long long a, b;
std::cin >> a >> b;
std::cout << a * b << '\n';
return 0;
}
解析 int 最大约 21 亿;10^9 × 10^9 = 10^18,必须 long long。
读入两个整数 a、b,判断 a + b 是否会超过 int 的范围。若会溢出输出 overflow,否则输出 a + b。用 long long 计算并比较边界。
输入 一行两个整数 a b(|a|,|b| ≤ 10^9)。
输出 overflow 或 a + b 的结果。
2000000000 1000000000overflow💡 提示 int 范围:约 -2147483648 到 2147483647。用 long long 求和后与边界比较。
✅ 查看参考答案与解析
#include <iostream>
int main() {
long long a, b;
std::cin >> a >> b;
long long s = a + b;
if (s > 2147483647LL || s < -2147483648LL) {
std::cout << "overflow\n";
} else {
std::cout << s << '\n';
}
return 0;
}
解析 永远不要先让 int 溢出再判断——溢出本身是未定义行为。用 long long 算完再比边界。
2.3 运算符与表达式
优先级不用背全表,但这几个细节必须形成条件反射,它们是算法题的常客。
位运算细节
1 << k 表示“第 k 位为 1”;x >> 1 相当于除以 2。注意 1 << 31 对 int 是溢出,要写 1LL << 31。对负数右移的行为在 C++20 起才明确规定,实践中先转成无符号更稳。
浮点比较不要用 ==,除法整除要小心
0.1 + 0.2 == 0.3 是 false(浮点误差)。要判等用 fabs(a - b) < 1e-9;要排序请转成整数(比如把小数乘 1000)。整数除法 5 / 2 得 2(向零取整,和 Java 相同);-5 % 3 得 -2(结果符号与被除数相同),需要非负余数时写 ((x % m) + m) % m。
类型转换:四种命名转换
static_cast 在相关类型间转换(编译期检查,最常用);dynamic_cast 用于多态向下转换(运行期检查,失败返回 nullptr);const_cast 只改 const 限定;reinterpret_cast 重新解释二进制(最危险)。C 风格 (int)d 仍可用,但会把四种混在一起,新代码统一用 static_cast 等。
✍️ 本节练习
读入一个整数 n,如果它是偶数输出 even,否则输出 odd。
输入 一个整数 n。
输出 even 或 odd。
7odd💡 提示 n % 2 == 0 判断偶数。
✅ 查看参考答案与解析
#include <iostream>
int main() {
int n;
std::cin >> n;
if (n % 2 == 0) { std::cout << "even\n"; }
else { std::cout << "odd\n"; }
return 0;
}
解析 if 的条件要能转成 bool,n % 2 == 0 是 bool 表达式。
2.4 流程控制
if、switch、循环和 Java 基本一致,这里只讲容易翻车的差异点。
switch 的两个注意点
case 必须是整型或枚举的编译期常量(不能是字符串);每个 case 结尾要 break,否则会“贯穿”到下一个 case;在 case 里定义变量要用花括号圈出作用域。
switch (op) {
case '+': std::cout << a + b << '\n'; break;
case '/':
if (b == 0) { std::cout << "error\n"; }
else { std::cout << a / b << '\n'; }
break;
default: std::cout << "error\n";
}
范围 for:三种写法各有用处
for (int x : v) 中 x 是拷贝,改它不影响 v;for (int& x : v) 是引用,能改原元素;for (const int& x : v) 只读且不拷贝。类型太长时用 auto&。
std::vector<int> v{1, 2, 3};
for (int& x : v) { x *= 2; } // 改原元素:v 变成 2 4 6
for (const int& x : v) { /* 只读 */ }
条件表达式不要求是 bool
if (p) 对指针合法(非空为真)、if (n) 对整数合法(非零为真)。这很方便,也更容易写错。while (std::cin >> n) 是读入直到结束的标准写法。
✍️ 本节练习
读入一个操作符(+ - * /)和两个整数 a、b,输出运算结果。除法是整数除法;除数为 0 输出 error。
输入 第一行操作符;第二行两个整数 a b。
输出 结果,或 error。
+
3 58💡 提示 switch (op) 里每个 case 记得 break。
✅ 查看参考答案与解析
#include <iostream>
int main() {
char op;
int a, b;
std::cin >> op >> a >> b;
switch (op) {
case '+': std::cout << a + b << '\n'; break;
case '-': std::cout << a - b << '\n'; break;
case '*': std::cout << a * b << '\n'; break;
case '/':
if (b == 0) { std::cout << "error\n"; }
else { std::cout << a / b << '\n'; }
break;
default: std::cout << "error\n";
}
return 0;
}
解析 switch 的 case 必须 break,否则会贯穿;除法要先判 b == 0。
2.5 常量与编译期计算
心智模型:const 是“运行期不许改”,constexpr 是“编译期就算好”。
const / constexpr / 宏怎么选
一句话:能写 constexpr 就写 constexpr,不能就写 const,几乎不要写宏常量。constexpr 是编译期常量,能用在数组长度、模板参数里;const 是运行期常量;#define 是纯文本替换,没有类型、没有作用域,还会踩各种坑。
const int MAXN = 100005; // 运行期常量
constexpr int MOD = 1000000007; // 编译期常量
// #define MOD 1000000007 // 宏:不推荐
constexpr 函数 + static_assert
constexpr 函数在输入编译期已知时,编译期就算出结果;static_assert 是编译期断言,不满足直接编译失败。这是“把计算放到编译期”的思维。
#include <iostream>
constexpr int square(int x) { return x * x; }
static_assert(square(5) == 25); // 编译期检查,错了编译不过
int main() { std::cout << square(12); }
144
✍️ 本节练习
写一个 constexpr 平方函数,用 static_assert 验证 square(5) == 25,然后输出 square(12) 的结果。
输入 无
输出 144
(本题无输入)144💡 提示 constexpr int square(int x) { return x * x; };static_assert 放 main 外。
✅ 查看参考答案与解析
#include <iostream>
constexpr int square(int x) { return x * x; }
static_assert(square(5) == 25);
int main() {
std::cout << square(12) << '\n';
return 0;
}
解析 static_assert 编译期检查,不满足直接编译失败。
用 constexpr 定义一个数组长度 N=100000,开一个 int 数组并全部初始化为 0,输出数组大小(用 sizeof 计算元素个数)。
输入 无
输出 100000
(本题无输入)100000💡 提示 int a[N]{}; 花括号把所有元素初始化为 0。
✅ 查看参考答案与解析
#include <iostream>
constexpr int N = 100000;
int main() {
int a[N]{};
std::cout << sizeof(a) / sizeof(a[0]) << '\n';
return 0;
}
解析 constexpr 常量可以当数组长度;sizeof(a)/sizeof(a[0]) 是求元素个数的传统写法。
本章小结
- 初始化推荐花括号 {},禁止窄化转换;int x{} 保证有值。
- 可能超过 21 亿就换 long long;有符号溢出是未定义行为。
- 浮点比较用 fabs(a-b) < 1e-9;switch 的 case 要 break;范围 for 别改容器大小。
- 能写 constexpr 就写 constexpr,配 static_assert 编译期验证。
🧩 本章综合练习
这几道题把本章多个知识点串起来,建议合上资料独立完成,再展开答案对照。
读入 n 个分数(0~100),输出最高分、最低分、平均分(保留 2 位小数)和及格人数(≥60)。覆盖:累加用 long long、整除与浮点除法的区别、浮点输出要 fixed + setprecision。
输入 第一行整数 n(1 ≤ n ≤ 100000);第二行 n 个整数分数。
输出 四行:max=、min=、avg=(2 位小数)、pass=。
5
88 92 75 100 59max=100
min=59
avg=82.80
pass=4💡 提示 sum 用 long long;avg 要 static_cast<double>(sum)/n,直接写 sum/n 会先做整除。
✅ 查看参考答案与解析
#include <iomanip>
#include <iostream>
int main() {
int n{};
std::cin >> n;
int maxv = -1, minv = 101, pass = 0;
long long sum = 0;
for (int i = 0; i < n; ++i) {
int s{};
std::cin >> s;
if (s > maxv) { maxv = s; }
if (s < minv) { minv = s; }
sum += s; // long long 累加,不怕溢出
if (s >= 60) { ++pass; }
}
std::cout << "max=" << maxv << '\n';
std::cout << "min=" << minv << '\n';
std::cout << "avg=" << std::fixed << std::setprecision(2)
<< static_cast<double>(sum) / n << '\n';
std::cout << "pass=" << pass << '\n';
return 0;
}
解析 整数除法向零取整:sum / n 两个都是整数时会丢掉小数,必须转 double。