🧭 C/C++ 学习站

C++ 入门 + 进阶训练

公网版 · 手机/电脑自适应

🐣 纯新手先看这一框(30 秒秒懂)

1

C++ 和 C 啥关系?C++ 是在 C 的基础上长出来的:C 的几乎所有写法在 C++ 里都能用,C++ 又加了“类/对象、模板、STL 标准库”等现代能力。

2

为什么要学它?写小程序 C 够用;写更大的项目(游戏、桌面软件、服务器),C++ 的“面向对象”和现成容器(vector/map)能省很多事。

3

怎么跑起来?同样靠“编译器”把代码翻成机器指令。本站「练习场」选 C++ 点运行即可,不用自己装环境;想深入再装 g++。

课程说明C 基础之上,系统掌握现代 C++

C++ 由 Bjarne Stroustrup 在 1979 年起基于 C 发展而来,目标是“带类的 C”,后来逐步加入模板、异常、STL、RTTI、智能指针、lambda 等。它几乎完全兼容 C,同时提供面向对象、泛型、函数式等抽象能力,既能写底层(操作系统、游戏引擎、嵌入式),也能写上层(桌面、服务、AI 框架)。

本课程假设你已学过 C(或看过 C 语言课程)。重点放在C++ 区别于 C 的现代特性:引用、类、模板、STL、RAII、智能指针——并反复强调“别把 C 的写法平移过来踩雷”。

编译器与标准g++clang++ 编译。强烈建议 -std=c++17 -Wall -Wextra。C++ 标准演进:C++98 → C++11(现代 C++ 起点)→ C++14 → C++17 → C++20。本课程以 C++17 常用子集为主,适当提及 C++11。

1. 从 C 到 C++iostream、命名空间、编译四阶段

#include <iostream>
using namespace std;          // 引入标准命名空间

int main() {
    cout << "Hello, C++!" << endl;   // 输出到标准输出
    int x;
    cin >> x;                         // 从标准输入读
    cout << "x = " << x << "\n";
    return 0;
}
g++ -std=c++17 -Wall -Wextra hello.cpp -o hello
./hello

1.1 与 C 的核心区别

  • IOcin/cout 替代 scanf/printf(也可混用 <cstdio>)。
  • 命名空间:标准库都在 std 里,用 std::coutusing namespace std; 引入。
  • 类型安全nullptr 替代 NULLmalloc 不调用构造。
  • 更宽松的声明位置:变量可在使用时定义(RAII 友好)。
using namespace std 的争议头文件里写 using namespace std; 会把它“泄漏”到所有包含该头文件的文件,极易命名冲突。源文件里可写,但更推荐用 std:: 前缀,或只引入用到的名字(using std::cout;)。

2. 引用与 const别名、引用传参、const 正确性

引用是“变量的别名”,必须初始化且一旦绑定不能改指别处。比指针更安全、语法更干净,常用于函数参数与返回值。

#include <iostream>
using namespace std;

void swap(int& a, int& b) {     // 引用传参,直接改外部
    int t = a; a = b; b = t;
}

int main() {
    int x = 1, y = 2;
    swap(x, y);
    cout << x << " " << y << "\n";   // 2 1
    return 0;
}

2.1 三种常见引用用法

形式含义用途
T&普通左值引用修改外部对象、避免大对象拷贝
const T&只读引用只读访问且免拷贝(最常用参数类型)
T&&右值引用移动语义(第 16 节)
const 正确性 能加 const 的地方都加:const int& v 只读不改;int get() const 承诺成员函数不修改对象状态。const 正确性能让编译器帮你挡掉大量误改 bug,也是良好接口的标配。

3. 函数增强重载、默认参数、inline、函数模板雏形

#include <iostream>
using namespace std;

int add(int a, int b = 0) {        // 默认参数(从右往左给)
    return a + b;
}
int add(double a, double b) {       // 函数重载:同名不同参数
    return (int)(a + b);
}
inline int square(int x) { return x * x; }  // 建议内联,减少小函数调用开销

int main() {
    cout << add(3) << "\n";      // 3  (b 用默认值 0)
    cout << add(3, 4) << "\n";   // 7
    return 0;
}
注意 默认参数只能在声明或定义之一给出,且必须从右向左连续(f(int a, int b=0) 合法,f(int a=0, int b) 非法);重载靠参数类型/个数/const 区分,不能仅靠返回值区分

4. string 字符串类自动管理、拼接、查找、与 C 字符串互转

std::string 自动管理内存、可动态增长,告别 char[] 与手动 \0 的麻烦。

#include <iostream>
#include <string>
using namespace std;

int main() {
    string s = "hello";
    s += " world";          // 拼接
    s.append("!");
    cout << s << " len=" << s.size() << "\n";   // hello world! len=12
    cout << s.substr(0, 5) << "\n";              // hello
    if (s.find("world") != string::npos) cout << "found\n";
    return 0;
}

4.1 常用成员

成员作用
size()/length()字符个数
empty()是否为空
s += t / s.append(t)拼接
s.substr(pos, n)取子串
s.find(t)查找,返回位置或 npos
s.c_str()转 C 风格 const char*
陷阱 s.c_str() 返回的指针在 s 被修改或销毁后失效;若要把 C 字符串长期保存,应 strcpy 到自己的缓冲区或用 std::string 持有。

5. 类与对象class、访问控制、成员函数、this

#include <iostream>
#include <string>
using namespace std;

class Student {
private:
    int id;
    string name;
public:
    void set(int i, string n) { id = i; name = n; }
    void show() const { cout << id << " " << name << "\n"; }
    int getId() const { return id; }   // const 成员函数
};

int main() {
    Student s;
    s.set(1, "XiaoMing");
    s.show();
    return 0;
}
封装 private 成员对外隐藏实现细节,只通过 public 接口交互——这是面向对象“封装”的核心。类的成员函数内有个隐式 this 指针指向调用对象。

6. 构造 / 析构 / 拷贝初始化列表、三/五法则、深浅拷贝

#include <iostream>
#include <cstring>
using namespace std;

class Buffer {
    char* data;
    int len;
public:
    Buffer(int n) : len(n) {            // 初始化列表
        data = new char[n];
    }
    ~Buffer() { delete[] data; }        // 析构:释放资源

    // 拷贝构造(深拷贝)
    Buffer(const Buffer& o) : len(o.len) {
        data = new char[len];
        memcpy(data, o.data, len);
    }
    // 拷贝赋值
    Buffer& operator=(const Buffer& o) {
        if (this != &o) {               // 防自赋值
            delete[] data;
            len = o.len;
            data = new char[len];
            memcpy(data, o.data, len);
        }
        return *this;
    }
};

int main() {
    Buffer a(10);
    Buffer b = a;     // 调用拷贝构造(深拷贝,互不影响)
    return 0;
}
三/五法则 若类手动管理资源(如自己 new),就必须同时正确实现析构、拷贝构造、拷贝赋值(三法则;C++11 起还有移动构造/移动赋值,称五法则),否则默认浅拷贝会让两个对象指向同一块内存,析构时双重释放。现代写法是用智能指针或 STL 容器替你管资源,根本不用手写这些(见第 13 节)。
初始化列表: len(n) 初始化成员,比在函数体内 len = n; 赋值更高效;const 成员、引用成员、无默认构造的子类只能在初始化列表里初始化

7. 继承公有继承、is-a、访问控制、构造顺序

#include <iostream>
using namespace std;

class Animal {
public:
    void eat() { cout << "eating\n"; }
protected:
    int legs = 4;                 // 子类可见,对外不可见
};

class Dog : public Animal {       // 公有继承 = is-a
public:
    void bark() { cout << "wang, legs=" << legs << "\n"; }
};

int main() {
    Dog d;
    d.eat();     // 继承自 Animal
    d.bark();
    return 0;
}
继承方式基类 public基类 protected基类 private
publicpublicprotected不可见
protectedprotectedprotected不可见
privateprivateprivate不可见
要点 优先用 public 继承表达“是一个”关系。派生类构造时先构造基类再构造自己,析构顺序相反。多态基类的析构必须 virtual(见下节)。

8. 多态与虚函数虚函数表、虚析构、纯虚/抽象类、override

基类指针/引用指向派生类对象,调用虚函数时,运行时按对象“真实类型”决定调用哪个版本——这就是运行时多态。

#include <iostream>
using namespace std;

class Shape {
public:
    virtual double area() const = 0;   // 纯虚函数 → 抽象类,不能实例化
    virtual ~Shape() = default;        // 多态基类:析构必须 virtual
};

class Circle : public Shape {
    double r;
public:
    Circle(double r) : r(r) {}
    double area() const override { return 3.14159 * r * r; }
};

int main() {
    Shape* s = new Circle(2.0);
    cout << s->area() << "\n";   // 12.56... 调的是 Circle::area
    delete s;                    // 虚析构保证调用 Circle 析构
    return 0;
}
铁律 通过基类指针 delete 派生类对象时,基类析构必须为 virtual,否则只调用基类析构,派生部分内存泄漏。C++11 起用 override 关键字,让编译器检查你是否真的重写了虚函数(拼错函数名会直接报错而不是悄悄新建函数)。
原理 含虚函数的类会有一个“虚函数表(vtable)”,对象内藏一个 vptr 指向它;调用虚函数时经 vptr→vtable 找到正确版本。这是运行时多态的底层机制(有极小开销)。

9. 运算符重载成员/友元、常见重载、约定

#include <iostream>
using namespace std;

class Vec {
    int x, y;
public:
    Vec(int x, int y) : x(x), y(y) {}
    Vec operator+(const Vec& o) const { return Vec(x + o.x, y + o.y); }
    bool operator==(const Vec& o) const { return x == o.x && y == o.y; }
    friend ostream& operator<<(ostream& os, const Vec& v) {
        return os << "(" << v.x << "," << v.y << ")";
    }
};

int main() {
    Vec a(1, 2), b(3, 4);
    cout << (a + b) << " " << (a == b) << "\n";   // (4,6) 0
    return 0;
}
克制使用 不要重载得“反直觉”(如把 + 写成减法)。二元运算符若需访问私有成员,常写为友元函数(friend)。赋值运算符 operator= 要注意自赋值与深拷贝(配合第 6 节)。

10. 模板函数模板、类模板、特化思路

模板让函数/类“参数化类型”,写一次适配多种类型,是泛型编程的基础。

#include <iostream>
using namespace std;

template <typename T>
T maxof(T a, T b) { return a > b ? a : b; }

template <typename T>
class Box {
    T v;
public:
    Box(T v) : v(v) {}
    T get() const { return v; }
};

int main() {
    cout << maxof(3, 7) << "\n";          // 7
    cout << maxof(3.14, 2.7) << "\n";     // 3.14
    Box<string> b("hi");
    cout << b.get() << "\n";               // hi
    return 0;
}
原理 模板在编译期“按需实例化”——用到 maxof<int> 就生成一份 int 版本,用到 Box<string> 就生成一份 string 版本。所以模板实现通常写在头文件里(编译器需要看到完整定义才能实例化)。

11. STL 容器与算法vector/map/set、迭代器、algorithm、范围 for

STL(标准模板库)是 C++ 生产力的核心,提供容器迭代器算法三者协作。

#include <iostream>
#include <vector>
#include <map>
#include <algorithm>
using namespace std;

int main() {
    vector<int> v = {5, 2, 9, 1};
    sort(v.begin(), v.end());                  // 算法 + 迭代器
    for (int x : v) cout << x << " ";          // 范围 for:1 2 5 9
    cout << "\n";

    map<string, int> m;                        // 有序键值对(红黑树)
    m["alice"] = 90;
    m["bob"] = 80;
    if (m.count("alice")) cout << m["alice"] << "\n";

    auto it = find(v.begin(), v.end(), 5);     // 算法 find
    if (it != v.end()) cout << "found 5\n";
    return 0;
}
容器特点典型场景
vector动态数组,尾部高效通用序列
string字符序列文本
map / set红黑树,有序需要有序/去重
unordered_map哈希,平均 O(1)高频查找
stack / queue适配器算法结构
首选 STL 99% 情况优先用 vector 而非手写数组/链表;用 algorithm 里的 sort/find/accumulate/count 而非手搓循环。

12. new/delete 与 RAII配对、数组形式、资源获取即初始化

C++ 用 new/delete 代替 malloc/free,会调用构造/析构。更关键的是 RAII(Resource Acquisition Is Initialization):把资源绑定到对象生命周期,对象离开作用域时析构自动释放。

#include <iostream>
using namespace std;

int main() {
    int* p = new int(42);
    cout << *p << "\n";
    delete p;                 // 配 new(不是 free)
    p = nullptr;

    int* arr = new int[10];   // 数组用 delete[]
    delete[] arr;
    return 0;
}
别手写 new 后忘了 delete 会泄漏;异常可能在 delete 前跳出导致泄漏。几乎永远不要裸写 new/delete——用智能指针(下节)或 STL 容器,让析构替你兜底。
RAII 例子 std::lock_guard 锁、std::fstream 文件、std::unique_ptr 指针都是 RAII:构造即获得资源,析构即释放,异常安全。

13. 智能指针unique_ptr、shared_ptr、weak_ptr、make_*

智能指针是 RAII 的现成工具,自动管理生命周期,杜绝绝大多数内存问题。

#include <iostream>
#include <memory>
using namespace std;

int main() {
    unique_ptr<int> u = make_unique<int>(10);  // 独占所有权
    cout << *u << "\n";                        // 离开作用域自动 delete

    shared_ptr<int> s1 = make_shared<int>(20); // 引用计数共享
    shared_ptr<int> s2 = s1;                   // 计数 +1
    cout << s1.use_count() << "\n";            // 2
    return 0;                                  // 计数归零自动释放
}
指针语义选用场景
unique_ptr独占,零开销默认首选(大多数情况)
shared_ptr引用计数共享需要多处共享所有权
weak_ptr不增计数,观察打破 shared_ptr 循环引用
最佳实践 优先 make_unique / make_shared(异常安全、代码更短);传参时若只是借用用 const shared_ptr& 或原始指针 T*,不要无脑按值传 shared_ptr(会无谓增计数)。

14. 异常处理throw、try/catch、标准异常、栈展开

#include <iostream>
#include <stdexcept>
using namespace std;

int divide(int a, int b) {
    if (b == 0) throw runtime_error("div by zero");
    return a / b;
}

int main() {
    try {
        cout << divide(10, 0) << "\n";
    } catch (const exception& e) {        // 按基类引用捕获
        cerr << "error: " << e.what() << "\n";
    }
    return 0;
}
建议 用标准异常类(std::runtime_errorstd::invalid_argument 等);按 const exception& 捕获基类以便处理所有派生;异常沿调用栈“栈展开”,局部对象会正常析构(RAII 保证资源不漏)。
性能 异常“不抛时不花钱”,但抛出有开销。不要用异常做普通控制流(如循环退出);错误码与异常按场景取舍。

15. Lambda 表达式捕获列表、参数、mutable、配合算法

C++11 的 Lambda 是“匿名函数对象”,配合 STL 算法极好用。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main() {
    vector<int> v = {5, 2, 9, 1, 7};
    sort(v.begin(), v.end(), [](int a, int b) { return a > b; }); // 降序
    for (int x : v) cout << x << " ";
    cout << "\n";

    int base = 3;
    auto f = [base](int x) { return x + base; };  // 值捕获 base
    cout << f(10) << "\n";                        // 13
    return 0;
}
捕获含义
[]不捕获
[x]值捕获 x
[&x]引用捕获 x
[=]值捕获所有
[&]引用捕获所有
推荐 默认用 [&] 引用捕获或按需 [x, &y] 精确捕获,避免 [=] 不小心拷贝大对象;引用捕获要留意生命周期(别捕获局部变量返回出去)。

16. 移动语义与 auto右值引用、std::move、auto、范围 for

C++11 引入右值引用 &&std::move,把“即将销毁的临时对象”的资源“搬家”而非深拷贝,大幅提升性能。

#include <iostream>
#include <vector>
#include <utility>
using namespace std;

int main() {
    vector<int> a = {1, 2, 3};
    vector<int> b = move(a);   // 把 a 的资源移动给 b,a 变为空(可析构状态)
    cout << "b=" << b.size() << " a=" << a.size() << "\n";

    auto it = b.begin();       // auto 自动推导类型
    for (auto& x : b) x *= 2;  // 范围 for + 引用修改
    return 0;
}
理解层次 入门阶段知道“返回大对象时编译器会自动移动/拷贝省略(RVO)”即可;写自己的类才需手写移动构造/移动赋值。这是进阶主题,先把前面用熟。
auto 好处 auto 减少冗长类型书写、避免写错类型(如复杂迭代器 vector<int>::const_iterator),遍历容器用 for (auto& x : v) 既简洁又高效。

17. C/C++ 差异与陷阱移植注意、常见错误对照

必注意
  • C++ 类型检查更严格:void* 不能隐式转其它指针,须显式强转。
  • malloc 不调用构造、free 不调用析构,混用危险;C++ 用 new/delete 或容器。
  • nullptr 而非 NULL(类型安全,不会误当整数)。
  • 定义变量不必集中在函数开头,可在使用时定义(RAII 友好)。
  • struct 在 C++ 里默认 public,且可直接当类型名用(不必写 struct X)。
  • 默认全局 const 变量在 C++ 是内部链接(C 是外部),跨文件要用 extern const
学习路径 C 基础 → C++ 面向对象 → STL → 智能指针/RAII → 现代 C++(auto、lambda、移动语义)。别一上来死磕模板元编程;先用好 STL 和智能指针,代码质量就能甩开“C 风格 C++”。

进阶训练场(含答案)

题 1:写一个带构造/析构的计数器类

Counter 持有一个 int 计数,构造时打印 "ctor" 并初始化为 0,inc() 自增,get() 返回,析构打印 "dtor"。

#include <iostream>
using namespace std;
class Counter {
    int n = 0;
public:
    Counter() { cout << "ctor\n"; }
    ~Counter() { cout << "dtor\n"; }
    void inc() { n++; }
    int get() const { return n; }
};

题 2:用 vector + lambda 求偶数平方和

给定 vector<int>,用 accumulate 与 lambda 求所有偶数的平方之和。

#include <iostream>
#include <vector>
#include <numeric>
using namespace std;
int sum_even_sq(const vector<int>& v) {
    return accumulate(v.begin(), v.end(), 0,
        [](int acc, int x) { return x % 2 == 0 ? acc + x * x : acc; });
}

题 3:用 unique_ptr 管理动态对象

make_unique 创建 int,读值后离开作用域,验证无内存泄漏(无需手写 delete)。

#include <iostream>
#include <memory>
using namespace std;
int main() {
    auto p = make_unique<int>(99);
    cout << *p << "\n";   // 99,离开作用域自动释放
    return 0;
}

题 4:抽象基类 Shape + 两个派生类

Shape 有纯虚 area(),派生 Rect(w,h) 与 Circle(r),用基类指针(智能指针)遍历打印面积。

#include <iostream>
#include <vector>
#include <memory>
using namespace std;
struct Shape { virtual double area() const = 0; virtual ~Shape() = default; };
struct Rect : Shape { double w, h; Rect(double w,double h):w(w),h(h){}
    double area() const override { return w * h; } };
struct Circle : Shape { double r; Circle(double r):r(r){}
    double area() const override { return 3.14159 * r * r; } };
int main() {
    vector<unique_ptr<Shape>> v;
    v.push_back(make_unique<Rect>(3, 4));
    v.push_back(make_unique<Circle>(2));
    for (auto& s : v) cout << s->area() << "\n";
    return 0;
}

题 5:函数模板实现通用 min

写模板函数 my_min,支持 int、double、string 等可比较类型,返回较小者的 const 引用。

template <typename T>
const T& my_min(const T& a, const T& b) {
    return (a < b) ? a : b;
}
// my_min(3,5) → 3;my_min(string("b"),string("a")) → "a"

题 6:用 map 统计单词出现次数

给定字符串数组,统计每个单词出现次数并输出。

#include <iostream>
#include <map>
#include <string>
using namespace std;
int main() {
    string words[] = {"a", "b", "a", "c", "b", "a"};
    map<string, int> cnt;
    for (auto& w : words) cnt[w]++;
    for (auto& kv : cnt) cout << kv.first << ":" << kv.second << "\n";
    return 0;
}

题 7:自定义类支持排序(运算符重载)

定义 Person(name, age),重载 < 使 vector<Person> 按 age 升序排。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct Person {
    string name; int age;
    bool operator<(const Person& o) const { return age < o.age; }
};
int main() {
    vector<Person> v = {{"A",30},{"B",20},{"C",25}};
    sort(v.begin(), v.end());
    for (auto& p : v) cout << p.name << " " << p.age << "\n";
    return 0;
}

题 8:try/catch 包装除法

写函数对除零抛异常,main 里捕获并打印错误信息,验证程序不崩溃。

#include <iostream>
#include <stdexcept>
using namespace std;
double safe_div(double a, double b) {
    if (b == 0) throw runtime_error("divide by zero");
    return a / b;
}
int main() {
    try { cout << safe_div(1, 0) << "\n"; }
    catch (const exception& e) { cerr << "err: " << e.what() << "\n"; }
    return 0;
}

💡 连老师都不会教你的干货(工业级技巧)

为什么单列这一节C++ 比 C 多出面向对象、模板、RAII、移动语义,也多出一堆"教科书不教但工程必踩"的坑。下面这些能让你少掉几根头发。
  1. 对象模型。对象在内存里就是"数据 + 布局",成员函数不占对象空间(存在于代码段),靠隐式 this 区分实例;虚函数通过对象里的 vptr 指向 vtable 实现多态,每个含虚函数的对象因此多一个指针开销。
  2. 零开销抽象(Zero-overhead)。模板/内联/RAII 在 release 下不引入运行时成本——你不为不用的东西付代价。这是 C++ 的设计哲学,也区别于带虚拟机的语言。
  3. 值语义 vs 引用语义。C++ 默认值语义(拷贝即独立副本),与 Java/Python"引用即共享"完全不同。搞懂这点才明白拷贝构造、移动语义为何关键。
  4. 三/五法则。类自己管理资源(自己 new)时,必须同时正确实现析构、拷贝构造、拷贝赋值;C++11 起还要移动构造、移动赋值(五法则)。缺任何一个都会浅拷贝 → 双重释放。
  5. 移动语义与 std::move。把"即将销毁的对象"的资源"偷"过来,避免昂贵深拷贝。但 std::move 后原对象处于"有效但未指定状态",不可再使用其值。
  6. RAII 是资源管理的核心。把资源(内存/文件/锁)绑定到对象生命周期:构造获取、析构释放,异常安全且不会漏。智能指针、lock_guard 都是 RAII。
  7. 不要裸 new/delete。优先 unique_ptr(独占)/ shared_ptr(共享)/ 容器 vector。裸指针只做"观察",不拥有资源。
  8. 虚析构函数。基类若会被多态删除(基类指针删派生对象),析构必须 virtual,否则派生部分不释放,造成内存泄漏。
  9. 初始化顺序陷阱。基类先于成员构造;成员按声明顺序(不是初始化列表顺序)构造;跨编译单元的全局对象初始化顺序未定义(Static Initialization Order Fiasco),别在全局构造里依赖其他全局。
  10. 模板两阶段编译。模板定义时只检查不依赖模板参数的部分,实例化时才检查依赖部分;报错常出现在调用点,要学会读冗长报错。
  11. 名字查找(ADL)。函数调用时编译器会到参数所在命名空间找候选,使 std::swap 等被"自动"找到,也偶尔引发意外重载,理解它才能看懂某些诡异调用。
  12. ABI 稳定性。C++ 没有官方稳定的 ABI,不同编译器/版本混链可能崩溃;而 C 的 ABI 长期稳定——这就是为什么系统接口、嵌入式多为 C。
  13. const 正确性与 mutable。const 成员函数承诺不改对象逻辑状态;需要缓存的字段可用 mutable 在 const 方法里修改。
  14. 异常开销与 noexcept。异常路径有成本;标明 noexcept 让编译器更激进优化,且对移动操作至关重要(否则回退到拷贝)。

自测题(点击展开答案)

  • Q1:C++ 中传参想“改外部变量且避免拷贝”,应优先用?
    A:引用 T&(只读时用 const T&),比指针安全、比值传递高效。
  • Q2:基类指针删除派生类对象时,基类析构应声明为?
    A:virtual。否则只调基类析构,派生部分泄漏。
  • Q3:现代 C++ 管理动态内存首选?
    A:智能指针(unique_ptr/shared_ptr)或 STL 容器,几乎不要裸 new/delete。
  • Q4:空指针字面量应写?
    A:nullptr(类型安全),而非 NULL(本质是 0)或 0
  • Q5:auto 与范围 for 主要带来什么好处?
    A:减少冗长类型书写、避免类型写错,让遍历容器更简洁安全。
  • Q6:类手动管理资源(自己 new)时,必须遵守?
    A:三/五法则——同时正确实现析构、拷贝构造、拷贝赋值(及移动构造/赋值),否则浅拷贝导致双重释放。
  • Q7:shared_ptr 循环引用应如何打破?
    A:把其中一方改为 weak_ptr,它不增加引用计数,避免两者互相持有导致永不释放。
  • Q8:C++ 里 using namespace std; 写在头文件有何风险?
    A:会把 std 全部名字暴露到所有包含该头文件的翻译单元,易引发命名冲突;头文件里应避免。
本 C++ 课程与 C 语言课程 配套,内容遵循 C++17 常用子集,手机与电脑端自适应。配套 g++ 练习效果最佳。 公网参考:cppreference.com · isocpp.org FAQ · C++ Core Guidelines。

🧪 动手实验室

不用从零敲代码:下面每个程序都已写好,点「▶ 运行」直接看结果;想改就在框里改,再点运行。手机、电脑都一样用。打开本页时会自动跑一次,方便你先看效果。

① 用 iostream 输出(对比 C 的 printf)

② 引用做交换(C++ 特色,不用指针)

③ 用 vector + 范围 for(比数组更现代)

④ 简单类与对象

每个框都能用下拉切换语言、「↺ 重置」回到初始代码。想写完整程序或体验其他语言,去顶部「练习场」(支持 Python/Java/Go/JS/TS/Rust/C#/Ruby 等)。
🛠 在线练习