前言这是一篇面向初学者的 C++ 入门梳理。标题叫"大杂烩",确实是因为入门阶段需要同时建立好几条线索:程序怎么从源码变成可执行文件、变量和类型是怎么回事、控制流和函数怎么写、类为什么存在、标准库能帮我们省掉哪些重复劳动。这些内容单独看都不难,混在一起就容易只见树木不见森林。入门阶段最常见的误解有三个。第一,把 C++ 当成"带类的 C",以为了解printf和for循环就算入门,结果一遇到std::string、引用、构造函数就卡住。第二,过早纠结语法细节,比如指针的指针、模板偏特化,却不知道标准库的std::vector和std::string已经能覆盖绝大多数日常需求。第三,照抄网上代码但不理解生命周期,写出返回局部变量引用的函数,程序在某个时刻莫名崩溃。本文按"编译模型 → 类型与变量 → 控制流与函数 → 类 → 标准库"的顺序走一遍,每个部分只讲最核心的概念,并给出一个综合示例。代码以 C++17 为基准。文中不会声称任何代码"已经编译运行",所有结论都按标准语义描述;读者应当在本地工具链上自行验证。一、从源码到可执行文件C++ 是编译型语言。一个源文件大致要经过四个阶段:阶段工具主要工作预处理预处理器展开#include、替换宏、处理条件编译编译编译器把翻译单元转成汇编或目标代码,做类型检查汇编汇编器把汇编转成机器码形式的目标文件链接链接器合并目标文件与库,解析符号,生成可执行文件初学者最容易忽略的是"声明"与"定义"的区别。声明告诉编译器"有这么个名字,类型是这样";定义则真正分配存储或给出函数体。一个函数可以声明多次,但只能定义一次。头文件里通常放声明,源文件里放定义,原因就在这里。最小的 C++ 程序长这样:// 适用于 C++17#include <iostream>int main() {std::cout << "Hello, C++\n";return 0;}main的返回类型必须是int,void main()从来不是标准写法,某些编译器容忍它只是扩展。main函数比较特殊:不写return语句时,标准规定等价于返回 0。std::cout是标准输出流,\n是换行符,比std::endl少一次强制刷新,通常更合适。源文件的扩展名(.cpp、.cc、.cxx)由编译器约定,标准并不规定。编译命令因工具链而异,本文不展开具体命令行参数。二、类型、变量与初始化C++ 是静态类型语言,每个变量在编译期就有确定的类型。常用基础类型包括int、long long、double、char、bool。标准只规定了最小范围,例如int至少 16 位,实际宽度由实现决定;sizeof(long)在 Windows 的 LLP64 模型下通常是 4 字节,在 Linux 的 LP64 模型下通常是 8 字节。写跨平台代码时不要假设具体宽度,需要固定宽度就用<cstdint>里的std::int32_t、std::int64_t。初始化方式有好几种,推荐使用花括号初始化:// 适用于 C++17int a = 0; // 复制初始化int b(0); // 直接初始化int c{0}; // 列表初始化,推荐double d{1.5};bool flag{false};花括号初始化的好处是会拒绝窄化转换。例如int x{3.14};在标准 C++ 中是编译错误,而int x = 3.14;只会悄悄截断。这类"把错误提前到编译期"的特性,入门阶段就应该养成习惯。auto让编译器从初始化表达式推导类型,从 C++11 起可用。它减少了冗长的类型名,但不要滥用:当读者需要从代码直接看出类型时,写明确类型更清晰。const表示"初始化后不可修改",应当优先使用;它能帮助编译器优化,也能防止误改。constexpr表示"可以在编译期求值",从 C++11 起可用,C++14 起对函数体的限制放宽。// 适用于 C++17const int kMaxCount = 100;constexpr int kSquare(int x) { return x * x; }auto name = std::string{"C++"};命名习惯上,常量常用k前缀或全大写,变量用小驼峰或下划线分隔,团队统一即可,标准不做要求。三、控制流与函数控制流语句与 C 类似:if/else、switch、for、while、do/while、break、continue。C++11 增加了范围for,遍历容器时非常方便:// 适用于 C++17std::vector<int> values{1, 2, 3, 4, 5};int sum = 0;for (int v : values) {sum += v;}for (const auto& v : values) {// 只读遍历,避免拷贝}函数由返回类型、名字、参数列表和函数体组成。C++ 支持重载:同名函数只要参数列表不同即可共存,返回类型不参与重载决议。默认参数只能从右向左连续给出。函数声明放在头文件,定义放在源文件,或者对短小的函数使用inline。参数传递有三种常见方式,选择哪种直接影响语义和开销:方式写法适用场景传值void f(int x)小型平凡类型,或需要在函数内修改副本传引用void f(std::string& s)需要修改调用方对象传常量引用void f(const std::string& s)只读且对象较大,避免拷贝传右值引用void f(std::string&& s)移动语义,通常配合std::move返回局部变量的引用或指针是典型的悬垂引用,属于未定义行为。返回局部变量本身(按值)是安全的,编译器会做返回值优化或移动。四、类与对象入门类把数据和对数据的操作放在一起。一个最基本的类包含构造函数、成员函数和访问控制:// 适用于 C++17#include <string>#include <utility>class Rectangle {public:Rectangle(double width, double height): width_(width), height_(height) {}double area() const { return width_ * height_; }void scale(double factor) {width_ *= factor;height_ *= factor;}private:double width_;double height_;};这里有几个入门必须掌握的点。构造函数初始化列表按成员声明顺序执行,不是按书写顺序,所以成员初始化最好与声明顺序一致。area() const末尾的const表示该函数不修改对象状态,const 对象只能调用 const 成员函数。数据成员设为private,外部通过公开接口访问,这就是封装。如果类需要被继承并且会通过基类指针删除派生类对象,基类析构函数必须是virtual,否则是未定义行为。类如果管理了资源(比如裸指针),要么手写析构、拷贝构造、拷贝赋值(规则三),要么用 RAII 成员让编译器生成(零规则)。现代 C++ 强烈推荐零规则:用std::string、std::vector、std::unique_ptr作为成员,不手写资源管理代码。对象切片也值得提前知道:把派生类对象按值传给基类参数,只会保留基类部分,派生类的数据和行为丢失。需要多态时应传引用或指针。五、标准库:先用起来,再谈原理初学者不必急着实现容器。标准库已经提供了高质量的基础设施:std::string:动态长度的字符串,替代char[]和strcpy。std::vector<T>:动态数组,连续存储,随机访问 O(1),尾部追加均摊 O(1)。std::array<T, N>:固定长度数组,不涉及堆分配。std::map/std::unordered_map:有序/无序键值容器。std::sort:排序算法,平均复杂度 O(n log n),要求比较器满足严格弱序。std::iostream:输入输出流。使用容器要包含对应头文件,并注意std::限定。不要在头文件里写using namespace std;,那会把整个命名空间塞给所有包含者。源文件里为了简短可以用,但更推荐显式写std::。std::vector的operator[]不做边界检查,越界是未定义行为;需要检查时用at(),越界会抛std::out_of_range。size()返回的是无符号的std::size_t,与有符号整数比较时要注意转换规则,编译器通常会给出警告。六、综合示例:统计学生成绩下面这个程序把结构体、容器、范围 for、lambda、排序和函数组合在一起,适合作为第一阶段的练手目标。// 适用于 C++17#include <algorithm>#include <iostream>#include <string>#include <vector>struct Student {std::string name;int score;};double average(const std::vector<Student>& students) {if (students.empty()) {return 0.0;}int sum = 0;for (const auto& s : students) {sum += s.score;}return static_cast<double>(sum) / static_cast<double>(students.size());}int main() {std::vector<Student> students{{"Alice", 90},{"Bob", 75},{"Carol", 88}};std::sort(students.begin(), students.end(),[](const Student& a, const Student& b) {return a.score > b.score;});for (const auto& s : students) {std::cout << s.name << ": " << s.score << '\n';}std::cout << "average: " << average(students) << '\n';return 0;}逐点说明。Student是聚合类型,可以用花括号按成员顺序初始化。std::vector的初始化列表构造会依次复制这些元素。std::sort的比较器返回a.score > b.score,表示降序;比较器必须满足严格弱序,用>=是常见错误,会导致未定义行为。average接收 const 引用,避免复制整个容器;先判断空容器,避免除零;把sum转成double再除,避免整数除法截断。按标准语义,输出顺序应为 Carol、Alice、Bob,平均分为 84.333...,具体小数位数由std::cout的默认精度决定。常见坑点❌ 写void main()。 ✅ 标准要求int main(),末尾可省略return 0;。❌ 用int x = 3.14;做初始化,窄化转换被静默接受。 ✅ 用int x{3.14};,让编译器在窄化时报错。❌ 返回函数内局部变量的引用或指针。 ✅ 按值返回,或返回生命周期覆盖调用方的对象。❌ 在头文件里写using namespace std;。 ✅ 头文件用完整限定名,源文件中也优先显式写std::。❌ 用std::vector::operator[]访问不确定是否越界的位置。 ✅ 需要检查时用at(),或先判断下标小于size()。❌std::sort的比较器写成a.score >= b.score。 ✅ 使用严格弱序a.score > b.score,相等时返回false。❌ 认为sizeof(long)在所有平台都是 8 字节。 ✅ 它是实现定义行为,需要固定宽度用<cstdint>的类型。❌ 手写new[]/delete[]管理数组。 ✅ 用std::vector或std::array,让 RAII 负责释放。总结主题入门要点编译模型预处理、编译、汇编、链接;声明可多次,定义只有一次类型与初始化静态类型;优先花括号初始化;const、auto、constexpr常用控制流与函数范围 for 遍历容器;按值/引用/const 引用按需选择类构造函数初始化列表、const 成员函数、封装、虚析构标准库先用std::string、std::vector、std::sort,不要重复造轮子安全习惯不返回局部引用、不越界、比较器满足严格弱序入门阶段最重要的不是记住所有语法,而是建立"类型—生命周期—所有权"这条主线。把这条主线抓住,后面学移动语义、智能指针、模板都会顺很多。
前言这是一篇面向初学者的 C++ 入门梳理。标题叫"大杂烩",确实是因为入门阶段需要同时建立好几条线索:程序怎么从源码变成可执行文件、变量和类型是怎么回事、控制流和函数怎么写、类为什么存在、标准库能帮我们省掉哪些重复劳动。这些内容单独看都不难,混在一起就容易只见树木不见森林。入门阶段最常见的误解有三个。第一,把 C++ 当成"带类的 C",以为了解printf和for循环就算入门,结果一遇到std::string、引用、构造函数就卡住。第二,过早纠结语法细节,比如指针的指针、模板偏特化,却不知道标准库的std::vector和std::string已经能覆盖绝大多数日常需求。第三,照抄网上代码但不理解生命周期,写出返回局部变量引用的函数,程序在某个时刻莫名崩溃。本文按"编译模型 → 类型与变量 → 控制流与函数 → 类 → 标准库"的顺序走一遍,每个部分只讲最核心的概念,并给出一个综合示例。代码以 C++17 为基准。文中不会声称任何代码"已经编译运行",所有结论都按标准语义描述;读者应当在本地工具链上自行验证。一、从源码到可执行文件C++ 是编译型语言。一个源文件大致要经过四个阶段:阶段工具主要工作预处理预处理器展开#include、替换宏、处理条件编译编译编译器把翻译单元转成汇编或目标代码,做类型检查汇编汇编器把汇编转成机器码形式的目标文件链接链接器合并目标文件与库,解析符号,生成可执行文件初学者最容易忽略的是"声明"与"定义"的区别。声明告诉编译器"有这么个名字,类型是这样";定义则真正分配存储或给出函数体。一个函数可以声明多次,但只能定义一次。头文件里通常放声明,源文件里放定义,原因就在这里。最小的 C++ 程序长这样:// 适用于 C++17#include <iostream>int main() {std::cout << "Hello, C++\n";return 0;}main的返回类型必须是int,void main()从来不是标准写法,某些编译器容忍它只是扩展。main函数比较特殊:不写return语句时,标准规定等价于返回 0。std::cout是标准输出流,\n是换行符,比std::endl少一次强制刷新,通常更合适。源文件的扩展名(.cpp、.cc、.cxx)由编译器约定,标准并不规定。编译命令因工具链而异,本文不展开具体命令行参数。二、类型、变量与初始化C++ 是静态类型语言,每个变量在编译期就有确定的类型。常用基础类型包括int、long long、double、char、bool。标准只规定了最小范围,例如int至少 16 位,实际宽度由实现决定;sizeof(long)在 Windows 的 LLP64 模型下通常是 4 字节,在 Linux 的 LP64 模型下通常是 8 字节。写跨平台代码时不要假设具体宽度,需要固定宽度就用<cstdint>里的std::int32_t、std::int64_t。初始化方式有好几种,推荐使用花括号初始化:// 适用于 C++17int a = 0; // 复制初始化int b(0); // 直接初始化int c{0}; // 列表初始化,推荐double d{1.5};bool flag{false};花括号初始化的好处是会拒绝窄化转换。例如int x{3.14};在标准 C++ 中是编译错误,而int x = 3.14;只会悄悄截断。这类"把错误提前到编译期"的特性,入门阶段就应该养成习惯。auto让编译器从初始化表达式推导类型,从 C++11 起可用。它减少了冗长的类型名,但不要滥用:当读者需要从代码直接看出类型时,写明确类型更清晰。const表示"初始化后不可修改",应当优先使用;它能帮助编译器优化,也能防止误改。constexpr表示"可以在编译期求值",从 C++11 起可用,C++14 起对函数体的限制放宽。// 适用于 C++17const int kMaxCount = 100;constexpr int kSquare(int x) { return x * x; }auto name = std::string{"C++"};命名习惯上,常量常用k前缀或全大写,变量用小驼峰或下划线分隔,团队统一即可,标准不做要求。三、控制流与函数控制流语句与 C 类似:if/else、switch、for、while、do/while、break、continue。C++11 增加了范围for,遍历容器时非常方便:// 适用于 C++17std::vector<int> values{1, 2, 3, 4, 5};int sum = 0;for (int v : values) {sum += v;}for (const auto& v : values) {// 只读遍历,避免拷贝}函数由返回类型、名字、参数列表和函数体组成。C++ 支持重载:同名函数只要参数列表不同即可共存,返回类型不参与重载决议。默认参数只能从右向左连续给出。函数声明放在头文件,定义放在源文件,或者对短小的函数使用inline。参数传递有三种常见方式,选择哪种直接影响语义和开销:方式写法适用场景传值void f(int x)小型平凡类型,或需要在函数内修改副本传引用void f(std::string& s)需要修改调用方对象传常量引用void f(const std::string& s)只读且对象较大,避免拷贝传右值引用void f(std::string&& s)移动语义,通常配合std::move返回局部变量的引用或指针是典型的悬垂引用,属于未定义行为。返回局部变量本身(按值)是安全的,编译器会做返回值优化或移动。四、类与对象入门类把数据和对数据的操作放在一起。一个最基本的类包含构造函数、成员函数和访问控制:// 适用于 C++17#include <string>#include <utility>class Rectangle {public:Rectangle(double width, double height): width_(width), height_(height) {}double area() const { return width_ * height_; }void scale(double factor) {width_ *= factor;height_ *= factor;}private:double width_;double height_;};这里有几个入门必须掌握的点。构造函数初始化列表按成员声明顺序执行,不是按书写顺序,所以成员初始化最好与声明顺序一致。area() const末尾的const表示该函数不修改对象状态,const 对象只能调用 const 成员函数。数据成员设为private,外部通过公开接口访问,这就是封装。如果类需要被继承并且会通过基类指针删除派生类对象,基类析构函数必须是virtual,否则是未定义行为。类如果管理了资源(比如裸指针),要么手写析构、拷贝构造、拷贝赋值(规则三),要么用 RAII 成员让编译器生成(零规则)。现代 C++ 强烈推荐零规则:用std::string、std::vector、std::unique_ptr作为成员,不手写资源管理代码。对象切片也值得提前知道:把派生类对象按值传给基类参数,只会保留基类部分,派生类的数据和行为丢失。需要多态时应传引用或指针。五、标准库:先用起来,再谈原理初学者不必急着实现容器。标准库已经提供了高质量的基础设施:std::string:动态长度的字符串,替代char[]和strcpy。std::vector<T>:动态数组,连续存储,随机访问 O(1),尾部追加均摊 O(1)。std::array<T, N>:固定长度数组,不涉及堆分配。std::map/std::unordered_map:有序/无序键值容器。std::sort:排序算法,平均复杂度 O(n log n),要求比较器满足严格弱序。std::iostream:输入输出流。使用容器要包含对应头文件,并注意std::限定。不要在头文件里写using namespace std;,那会把整个命名空间塞给所有包含者。源文件里为了简短可以用,但更推荐显式写std::。std::vector的operator[]不做边界检查,越界是未定义行为;需要检查时用at(),越界会抛std::out_of_range。size()返回的是无符号的std::size_t,与有符号整数比较时要注意转换规则,编译器通常会给出警告。六、综合示例:统计学生成绩下面这个程序把结构体、容器、范围 for、lambda、排序和函数组合在一起,适合作为第一阶段的练手目标。// 适用于 C++17#include <algorithm>#include <iostream>#include <string>#include <vector>struct Student {std::string name;int score;};double average(const std::vector<Student>& students) {if (students.empty()) {return 0.0;}int sum = 0;for (const auto& s : students) {sum += s.score;}return static_cast<double>(sum) / static_cast<double>(students.size());}int main() {std::vector<Student> students{{"Alice", 90},{"Bob", 75},{"Carol", 88}};std::sort(students.begin(), students.end(),[](const Student& a, const Student& b) {return a.score > b.score;});for (const auto& s : students) {std::cout << s.name << ": " << s.score << '\n';}std::cout << "average: " << average(students) << '\n';return 0;}逐点说明。Student是聚合类型,可以用花括号按成员顺序初始化。std::vector的初始化列表构造会依次复制这些元素。std::sort的比较器返回a.score > b.score,表示降序;比较器必须满足严格弱序,用>=是常见错误,会导致未定义行为。average接收 const 引用,避免复制整个容器;先判断空容器,避免除零;把sum转成double再除,避免整数除法截断。按标准语义,输出顺序应为 Carol、Alice、Bob,平均分为 84.333...,具体小数位数由std::cout的默认精度决定。常见坑点❌ 写void main()。 ✅ 标准要求int main(),末尾可省略return 0;。❌ 用int x = 3.14;做初始化,窄化转换被静默接受。 ✅ 用int x{3.14};,让编译器在窄化时报错。❌ 返回函数内局部变量的引用或指针。 ✅ 按值返回,或返回生命周期覆盖调用方的对象。❌ 在头文件里写using namespace std;。 ✅ 头文件用完整限定名,源文件中也优先显式写std::。❌ 用std::vector::operator[]访问不确定是否越界的位置。 ✅ 需要检查时用at(),或先判断下标小于size()。❌std::sort的比较器写成a.score >= b.score。 ✅ 使用严格弱序a.score > b.score,相等时返回false。❌ 认为sizeof(long)在所有平台都是 8 字节。 ✅ 它是实现定义行为,需要固定宽度用<cstdint>的类型。❌ 手写new[]/delete[]管理数组。 ✅ 用std::vector或std::array,让 RAII 负责释放。总结主题入门要点编译模型预处理、编译、汇编、链接;声明可多次,定义只有一次类型与初始化静态类型;优先花括号初始化;const、auto、constexpr常用控制流与函数范围 for 遍历容器;按值/引用/const 引用按需选择类构造函数初始化列表、const 成员函数、封装、虚析构标准库先用std::string、std::vector、std::sort,不要重复造轮子安全习惯不返回局部引用、不越界、比较器满足严格弱序入门阶段最重要的不是记住所有语法,而是建立"类型—生命周期—所有权"这条主线。把这条主线抓住,后面学移动语义、智能指针、模板都会顺很多。