← C++ 教材目录(共 13 章)

第 2 章 变量、类型与流程控制

red wenzi · 2026-09-15 · 编程语言 · C++ · 📖 预计阅读 31 分钟 · 共 9 道练习
🎯 本章你会学到:类型选对、初始化别踩坑、流程写清楚。建议边读边敲代码,每节的练习先自己做,再展开答案对照。

本章目标:选对类型(会溢出就用 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,编译器拦住
⚠️ 易错点 Java 要求局部变量使用前必须确定赋值,C++ 不要求——所以更容易写出读垃圾值的代码。默认用 {} 初始化。

int x{} 是最省事的“保证有值”

空花括号把内置类型初始化为 0。写 int x{}; 比写 int x = 0; 更简洁,也比不初始化安全。类类型(如 std::string s;)即使不写初始化,也会调用默认构造函数,是安全的。

⚠️ 易错点 vector 的经典误会:std::vector<int> v(5) 是“5 个 0”,v{5} 是“1 个 5”。

作用域与生存期

变量离开它所在的花括号作用域,生命周期就结束;栈上的对象会在这里自动析构。这是 C++ 最重要的资源管理机制 RAII 的基础(第 4 章细讲)。Java 里对象活到 GC 认定没人引用为止,和这里完全不同。

⚠️ 易错点 块作用域 {} 里定义的变量,出了花括号就不存在。别在外面用它。

✍️ 本节练习

2.1.1必做三种类型各就各位

声明一个 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 起的推荐写法。

2.1.2挑战圆的面积

读入圆的半径 r(double),输出圆的面积,保留 3 位小数。圆周率用 3.14159265。

输入 一个实数 r。

输出 面积,保留 3 位小数。

样例输入
2
样例输出
12.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 一样的保证。

⚠️ 易错点 算法题里:累加、乘法、下标计算一旦可能超过 21 亿,就换 long long。这是必须养成的习惯。

有符号整数溢出 = 未定义行为

Java 的 int 溢出会回绕成一个错误但确定的数;C++ 里有符号整数溢出是未定义行为,编译器优化后可能出现完全无法预测的结果。所以判断“会不会溢出”要在算之前想清楚。

示例代码
long long a, b;
std::cin >> a >> b;
std::cout << a * b << '\n';   // 两个 10^9 相乘是 10^18,必须 long long
⚠️ 易错点 永远不要先让 int 溢出再判断——溢出本身就是未定义行为。

✍️ 本节练习

2.2.1必做大数相乘

读入两个整数(可能很大,乘积会超过 int 范围),输出它们的乘积。

输入 一行两个整数 a b(|a|,|b| ≤ 10^9)。

输出 a × b 的结果。

样例输入
1000000 1000000
样例输出
1000000000000

💡 提示 两个 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。

2.2.2挑战溢出预警

读入两个整数 a、b,判断 a + b 是否会超过 int 的范围。若会溢出输出 overflow,否则输出 a + b。用 long long 计算并比较边界。

输入 一行两个整数 a b(|a|,|b| ≤ 10^9)。

输出 overflow 或 a + b 的结果。

样例输入
2000000000 1000000000
样例输出
overflow

💡 提示 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 起才明确规定,实践中先转成无符号更稳。

⚠️ 易错点 if (x = 5) 永远为真——把 == 写成 = 是 C 系语言第一坑,编译器只会给个警告。

浮点比较不要用 ==,除法整除要小心

0.1 + 0.2 == 0.3 是 false(浮点误差)。要判等用 fabs(a - b) < 1e-9;要排序请转成整数(比如把小数乘 1000)。整数除法 5 / 2 得 2(向零取整,和 Java 相同);-5 % 3 得 -2(结果符号与被除数相同),需要非负余数时写 ((x % m) + m) % m。

⚠️ 易错点 循环里 ++i 和 i++ 对 int 没区别;但对迭代器和类类型,++it 不产生临时对象,习惯写前置。

类型转换:四种命名转换

static_cast 在相关类型间转换(编译期检查,最常用);dynamic_cast 用于多态向下转换(运行期检查,失败返回 nullptr);const_cast 只改 const 限定;reinterpret_cast 重新解释二进制(最危险)。C 风格 (int)d 仍可用,但会把四种混在一起,新代码统一用 static_cast 等。

⚠️ 易错点 和 Java 最大的不同:static_cast 不检查实际类型,转错了是未定义行为。要安全检查用 dynamic_cast。

✍️ 本节练习

2.3.1必做奇偶判断

读入一个整数 n,如果它是偶数输出 even,否则输出 odd。

输入 一个整数 n。

输出 even 或 odd。

样例输入
7
样例输出
odd

💡 提示 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";
}
⚠️ 易错点 编译器可能给 -Wimplicit-fallthrough 警告提示漏了 break。

范围 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) { /* 只读 */ }
⚠️ 易错点 在范围 for 里修改容器大小(如 v.push_back)会让迭代器失效,行为未定义。

条件表达式不要求是 bool

if (p) 对指针合法(非空为真)、if (n) 对整数合法(非零为真)。这很方便,也更容易写错。while (std::cin >> n) 是读入直到结束的标准写法。

⚠️ 易错点 do-while 至少执行一次;while 先判断后执行。别弄反。

✍️ 本节练习

2.4.1挑战简易计算器

读入一个操作符(+ - * /)和两个整数 a、b,输出运算结果。除法是整数除法;除数为 0 输出 error。

输入 第一行操作符;第二行两个整数 a b。

输出 结果,或 error。

样例输入
+
3 5
样例输出
8

💡 提示 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         // 宏:不推荐
⚠️ 易错点 C++ 里 const int n = 10; 是常量表达式,int a[n]; 合法(C 里不行)。

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
⚠️ 易错点 static_assert 放在 main 外面(命名空间作用域)也可以,它就是编译期的工具。

✍️ 本节练习

2.5.1必做编译期平方

写一个 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 编译期检查,不满足直接编译失败。

2.5.2挑战constexpr 数组

用 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]) 是求元素个数的传统写法。

本章小结

🧩 本章综合练习

这几道题把本章多个知识点串起来,建议合上资料独立完成,再展开答案对照。

2.A综合成绩统计:类型选择 + 流程控制 + 格式化输出

读入 n 个分数(0~100),输出最高分、最低分、平均分(保留 2 位小数)和及格人数(≥60)。覆盖:累加用 long long、整除与浮点除法的区别、浮点输出要 fixed + setprecision。

输入 第一行整数 n(1 ≤ n ≤ 100000);第二行 n 个整数分数。

输出 四行:max=min=avg=(2 位小数)、pass=

样例输入
5
88 92 75 100 59
样例输出
max=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。

📚 本文概念都在知识大全:

变量初始化 · 整数溢出 · 类型转换 · 编译期常量