## 一、继承与成员变量隐藏

**问题：**  
`class B : public A` 中，`B` 已经继承了 `A`，为什么还要定义自己的 `mess`？

### 示例代码

cpp

#include <iostream>
#include <cstring>
using namespace std;
class A {
private:
    char mess[18];
public:
    A() { strcpy(mess, "base class A"); }
    const char *GetMess() const { return mess; }
};
class B : public A {
private:
    char mess[18];  // 隐藏了 A::mess
public:
    B() { strcpy(mess, "derived class B"); }
    const char *GetMess() const { return mess; }  // 返回 B::mess
};
int main() {
    try {
        throw B();
    }
    catch (const B &b) {
        cout << b.GetMess() << endl;  // 输出 B::mess
    }
    catch (const A &a) {
        cout << a.GetMess() << endl;
    }
}

### 运行结果

text

derived class B

### 核心结论

- `B` 定义自己的 `mess` 是为了**隐藏父类 `A` 的 `mess`**，让 `B::GetMess()` 返回不同内容。
    
- 父类的 `mess` 仍然存在，但在 `B` 中不可直接访问（除非用 `A::mess`）。
    
- 这是**成员变量隐藏**，不同于虚函数的**覆盖（override）**。
    

### 如果 B 不定义自己的 mess：

cpp

class B : public A {
public:
    B() {}  // 不定义自己的 mess
    const char *GetMess() const { return mess; }  // 返回 A::mess
};

**输出：**

text

base class A

---

## 二、`throw B()` 与异常捕获

**问题：**  
`try` 里不是写可能出现异常的语句吗？为什么直接写 `throw B()`？

### 示例代码

cpp

#include <iostream>
using namespace std;
class A {
public:
    virtual const char* GetMess() const { return "A"; }
};
class B : public A {
public:
    const char* GetMess() const override { return "B"; }
};
int main() {
    try {
        throw B();  // 直接抛出异常，用于演示
    }
    catch (const B &b) {          // 子类在前
        cout << "Caught B: " << b.GetMess() << endl;
    }
    catch (const A &a) {          // 父类在后
        cout << "Caught A: " << a.GetMess() << endl;
    }
}

### 运行结果

text

Caught B: B

### 如果 catch 顺序反过来：

cpp

try {
    throw B();
}
catch (const A &a) {   // 父类在前，会先捕获 B 的异常
    cout << "Caught A: " << a.GetMess() << endl;
}
catch (const B &b) {   // 永远不会执行
    cout << "Caught B: " << b.GetMess() << endl;
}

**输出：**

text

Caught A: B

### 更贴近实际的写法

cpp

void mightFail(bool error) {
    if (error) {
        throw B();  // 条件触发异常
    }
}
int main() {
    try {
        mightFail(true);  // 可能抛异常的函数
    }
    catch (const B &b) {
        cout << "Caught B: " << b.GetMess() << endl;
    }
}

### 核心结论

- `try` 块通常包裹**可能抛出异常的代码**，但也可以直接 `throw`，用于演示或测试。
    
- `catch` 顺序很重要：**子类必须放在父类之前**，否则父类会先捕获子类异常。
    
- 实际开发中，`throw` 通常由条件触发，而非直接写。
    

---

## 三、`'\n'` 与 `endl` 的区别

**问题：**  
`cout << V[i] << '\n'` 会换行吗？换行不是 `endl` 吗？

### 示例代码

cpp

#include <iostream>
#include <vector>
using namespace std;
int main() {
    vector<int> V = {1, 2, 3};
    cout << V[0] << '\n';   // 仅换行
    cout << V[1] << endl;   // 换行 + 刷新缓冲区
    cout << V[2] << "\n";   // 同 '\n'
    return 0;
}

### 运行结果

text

1
2
3

### 对比表

|特性|`'\n'`|`endl`|
|---|---|---|
|作用|插入换行符|换行 + **刷新输出缓冲区**|
|性能|更快|较慢（强制 flush）|
|场景|普通换行|需要立即输出（调试、日志）|

### 核心结论

- `'\n'` 和 `endl` **都会换行**，但 `endl` 额外调用 `flush()`。
    
- 默认推荐用 `'\n'`，性能更好。
    
- `"\n"`（字符串）和 `'\n'`（字符）在 `cout` 中效果相同。
    

---

## 四、C 风格格式化 vs C++ 格式化

**问题：**  
C++ 可以用 C 风格的格式化吗？C++ 本身的格式化很复杂呢？

### （1）C 风格格式化

cpp

#include <cstdio>
int main() {
    int num = 42;
    double pi = 3.1415926;
    char str[] = "hello";
    printf("int=%d, double=%.2 f, str=%s\n", num, pi, str);
    int input;
    scanf("%d", &input);
    printf("You entered: %d\n", input);
    return 0;
}

**输出：**

text

int=42, double=3.14, str=hello

### （2）传统 C++ 流式格式化

cpp

#include <iostream>
#include <iomanip>
int main() {
    int num = 42;
    double pi = 3.1415926;
    std::string str = "hello";
    std::cout << "int=" << num 
              << ", double=" << std::fixed << std::setprecision(2) << pi 
              << ", str=" << str << std::endl;
    return 0;
}

**输出：**

text

int=42, double=3.14, str=hello

### （3）C++20 `std::format`（推荐）

cpp

#include <format>
#include <iostream>
int main() {
    int num = 42;
    double pi = 3.1415926;
    std::string str = "hello";
    std::string s = std::format("int={}, double={:.2 f}, str={}", num, pi, str);
    std::cout << s << std::endl;
    return 0;
}

**输出：**

text

int=42, double=3.14, str=hello

### （4）第三方库 {fmt}（兼容旧 C++）

cpp

#include <fmt/core.h>
int main() {
    std::string msg = fmt::format("The answer is {}", 42);
    fmt::print("{}\n", msg);
    return 0;
}

### 选择建议

|场景|推荐方式|
|---|---|
|高性能、简单格式化|C 风格 `printf`|
|现代 C++|C++20 `std::format`|
|旧版 C++|`<iomanip>` 或 {fmt}|

---

## 五、`new int` 与空指针的区别

**问题：**  
`int *p = new int;` 创建的指针不就是空指针吗？

### （1）`new int`（非空指针）

cpp

int *p = new int;   // 分配堆内存，p 指向它（未初始化）
*p = 42;
std::cout << *p;    // 输出 42
delete p;

### （2）空指针

cpp

int *p = nullptr;   // 空指针
// *p = 42;         // 错误！解引用空指针会崩溃
if (p == nullptr) {
    std::cout << "p is null\n";
}

### （3）`new` 失败时抛异常

cpp

try {
    int *p = new int[10000000000];  // 可能失败
} catch (const std::bad_alloc& e) {
    std::cerr << "Allocation failed: " << e.what() << "\n";
}

### （4）`nothrow` 让 `new` 失败时返回 `nullptr`

cpp

int *p = new (std::nothrow) int;
if (p == nullptr) {
    std::cerr << "Allocation failed!\n";
} else {
    *p = 42;
    delete p;
}

### （5）未初始化指针（野指针）

cpp

int *p;       // 未初始化，野指针，指向随机地址
// *p = 42;   // 未定义行为！

### 对比表

|情况|`p` 状态|可解引用|
|---|---|---|
|`int *p = new int;`|指向堆内存（未初始化）|✅ 值不确定|
|`int *p = new int();`|指向堆内存（初始化为 0）|✅|
|`int *p = nullptr;`|空指针|❌|
|`int *p;`（未初始化）|野指针|❌|
|`new (nothrow) int` 失败|`nullptr`|❌|

### 最佳实践

cpp

// 1. 初始化指针
int *p = nullptr;
// 2. 动态分配后检查
p = new (std::nothrow) int(42);
if (p == nullptr) { /* 处理错误 */ }
// 3. 释放后置 nullptr
delete p;
p = nullptr;
// 4. 优先使用智能指针
auto sp = std::make_unique<int>(42);  // 自动释放

---

## 总体总结表

|主题|核心结论|关键示例|
|---|---|---|
|继承与成员隐藏|子类同名成员隐藏父类成员|`B::mess` 隐藏 `A::mess`|
|`throw` 与异常捕获|`catch` 子类在前、父类在后|`throw B();`|
|`'\n'` vs `endl`|都换行，`endl` 额外刷新缓冲区|`cout << x << '\n';`|
|C vs C++ 格式化|C++20 `std::format` 最推荐|`std::format("{}", x)`|
|`new int` vs 空指针|`new int` 分配堆内存，非空指针|`int *p = new int;`|