1. C++ 与 C 的区别

279 阅读9分钟

1. C 和 C++的区别

1. 核心区别

维度CC++
设计目标底层控制,最小抽象高性能 + 高级抽象
代码组织函数为中心类/泛型为中心
内存管理手动(malloc/free)RAII(智能指针、容器)
错误处理返回值/errno异常 + noexcept
多态支持函数指针模拟虚函数 + 模板
类型安全弱(void*强转常见)强(模板/类型推导)
编译期计算宏(预处理期)constexpr + 模板元编程
标准库基础(stdio.h, stdlib.h)强大(STL容器、算法、线程等)
适用场景内核、嵌入式、硬件交互大型应用、游戏、高频交易

2. 深度对比

(1) 内存管理方式

C 方式:

int *arr = malloc(100 * sizeof(int));
if (!arr) { /* 错误处理 */ }
free(arr); // 必须手动释放

问题:

C++ 方式:

// 方案一:智能指针
std::unique_ptr<int[]> arr(new int[100]); // 自动释放
// 方案二:STL容器
std::vector<int> arr(100); // 自动管理内存+边界检查
// 方案三:类似C方式,使用new,delete或者C系列的内存管理函数

(2) 多态实现

C方式(通过函数指针模拟)

声明函数指针的语法:

// 返回类型 (*指针变量名)(参数类型列表)
int (*func_ptr)(int, int);

给函数指针赋值:

int add(int a, int b) {
    return a + b;
}
func_prt = add;   // 也可以写作 func_ptr = &add;

接下来,我们可以将一个函数指针作为参数传递给另一个函数(注册回调函数),比如:

int cmp(const void* a, const void* b) {
    return (*(int*)a - *(int*)b);
}

qsort(arr, n, sizeof(int), cmp); // cmp 就是函数指针

也可以实现简单的多态/策略模式:

int op_add(int a, int b) { return a + b; }
int op_mul(int a, int b) { return a * b; }

void compute(int x, int y, int (*op)(int, int)) {
    printf("Result: %d\n", op(x, y));
}

缺点:

  • 需要手动维护函数指针,类型不安全
  • 无法实现运行时类型识别

注意事项:

  • 函数名本身可以当作函数指针使用(不加 &
  • 函数指针不能指向内联函数或宏
  • 在使用 typedef 时尤其能大幅提升可读性
  • 函数指针的调用在某些平台(嵌入式)中需要特别注意 ABI 和对齐

C++方式(面向对象)

class Animal {
public:
    virtual void speak() = 0; // 纯虚函数
};
class Dog : public Animal {
    void speak() override { std::cout << "Woof!\n"; }
};

Animal *animal = new Dog();
animal->speak(); // 动态绑定
delete animal;   // 需注意内存管理(可用智能指针优化)

优势

  • 真正的运行时多态(虚函数表vtable支持)。
  • 类型安全(override关键字防止错误重载)。

总结: C++的虚函数机制虽然有小性能开销(vtable查找),但在大型项目中,清晰的接口抽象比微优化更重要。我们通过finaloverride关键字进一步确保多态安全性。

(3) 泛型编程

泛型编程 是一种编程范式,其核心目标是:编写与类型无关、可复用、可扩展的代码,依赖抽象而非具体类型

其核心理念在于:

  • 类型参数化:通过将类型作为参数,使得代码对类型不敏感
  • 编译期多态(静态多态):与传统面向对象的运行时多态(如虚函数)不同,泛型编程依赖于模板和类型推导,实现在编译期间的多态行为
  • 基于抽象操作而非接口继承:比如要求类型支持“+”运算、迭代等,而不要求某个特定的基类

C方式(宏展开或代码复用):

// 用宏实现泛型max
#define MAX(a,b) ((a) > (b) ? (a) : (b))

C++方式(模板)

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

// 使用(隐式)
auto val = max(3, 5);      // 编译期实例化int版本
auto val2 = max(3.14, 2.0); // 实例化double版本

优势

  • 编译期类型安全。
  • 零运行时开销(模板在编译期展开)
(4) 类型系统

C 的类型系统

基本类型

  • int, char, float, double, void
  • 修饰符:signed, unsigned, short, long

派生类型

  • 指针(int*)、数组(int arr[10])、函数指针、结构体(struct)、共用体(union

用户定义类型

  • typedef(类型别名)
  • struct / union / enum

特性

  • 类型系统是弱类型 + 静态类型:允许大量隐式转换,兼容性强但不够安全。
  • 函数参数不支持重载。
  • 不支持面向对象(没有类、继承、虚函数等)。
  • 类型检查较为宽松(比如函数声明不匹配时的默认行为更宽容)。

C++的类型系统

C++ 继承了 C 的类型系统,但大幅扩展,主要体现在:

类类型(Class Types)

  • 类(class)、结构体(struct)本质上是相同的,但支持:

    • 成员函数
    • 继承
    • 多态(虚函数)
    • 封装(private/protected/public)
    • 构造/析构函数

引用类型

  • int& 引用类型是 C++ 独有,表现为别名(非指针)
  • C++11 起加入 右值引用int&&),用于移动语义和完美转发

模板与泛型

  • C++ 的类型系统可以在编译期生成类型安全的代码
  • 支持类型参数化(模板类、模板函数)
  • template<typename T>

类型推导

  • auto(C++11):自动推导变量类型
  • decltype:根据表达式推导类型
  • template deduction: 编译器在模板实例化时推导类型

类型安全增强

  • 强制函数重载区分参数类型
  • 更严格的转换规则(如 explicit 构造函数)
  • 引入了 static_cast, dynamic_cast, reinterpret_cast, const_cast

常量表达力更强

  • const 用法更严格、适用更广
  • C++11 引入 constexpr 支持编译期常量求值

类型擦除(Type Erasure)

  • std::functionstd::anystd::variant 等实现了运行时多态和类型抽象

3. 常见问题

(1) C++比C慢吗?

“C++的抽象通常是零成本的(如STL容器、模板)。但错误使用(如滥用虚函数、异常)可能导致开销。关键点:

  • 默认情况下,C++性能≈C。
  • RAII和智能指针甚至能优化C的手动管理错误。
  • 游戏引擎(Unreal)和金融系统(高频交易)用C++证明其性能。”
(2) 什么场景下该用C而不是C++?

“三类场景优先C:

  1. 极端受限环境(单片机固件、内核开发)。
  2. 跨编译器ABI兼容(动态库接口需兼容不同编译器)。
  3. 遗留系统维护(如Linux驱动开发)。
  4. 其他情况,C++的工程优势更明显。”
(3) C++的缺点有哪些?
  1. 语言复杂度过高,导致学习成本增加。C++ 是一个“多范式混合体”:过程式 + 面向对象 + 泛型编程 + 函数式风格(C++11起)+ 元编程 + 并发 + 模块化(C++20)等。
  2. 编译慢 且 可执行文件。首先模板机制(尤其是STL + 元编程)导致大量代码膨胀;其次头文件包含机制导致编译重复冗余。
  3. ABI不稳定 & 模板库难以封装。模板类/函数只能在头文件中实现,无法封装成 .so/.dll 提供 ABI;不同编译器/版本生成的二进制接口不兼容
  4. 安全问题依然严重。尽管C++提供了面向对象、RAII、智能指针等机制,但依然保留了指针与裸内存操作、手动new/delete、未定义行为,易导致悬垂指针、缓冲区溢出、资源泄露。
  5. 错误信息晦涩(尤其是模板)。泛型编程错误信息极长,且难以理解,Concepts 改善了部分情况,但编译期调试仍不友好。
  6. STL 与抽象代价。- STL 容器设计追求通用性,有时带来抽象开销,如:std::map(红黑树)插入性能低于手写哈希表、std::function 存在 type erasure 与动态分配开销,迭代器风格虽统一但不总是直观。

2. C++独有的特性

1. 移动语义(C++11起)

std::vector<int> createLargeData() {
    std::vector<int> data(1'000'000); 
    return data; // 触发移动构造(非拷贝)
}

auto v = createLargeData(); // 零成本传递所有权

为什么重要

  • 避免深拷贝大对象(如容器、字符串)
  • 实现资源所有权转移(如std::unique_ptr
  • 面试提示:解释std::move与右值引用(区别于拷贝)

2. Lambda表达式(C++11起)

std::vector<int> nums = {1, 2, 3};
std::sort(nums.begin(), nums.end(), [](int a, int b) {
    return a > b; // 降序排序
});

为什么重要

  • 简化回调、STL算法定制(如std::for_each
  • 替代函数对象(Functor),代码更紧凑
  • 面试提示:捕获列表([=][&])的作用域控制

3. 类型推导(auto/decltype)

auto x = 42; // 推导为int
decltype(x) y = x; // y的类型与x相同

template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u) { return t + u; }

为什么重要

  • 减少模板代码冗余
  • 配合泛型编程提升可读性
  • 面试提示auto与模板类型推导规则的差异

4. 范围for循环(C++11起)

for (const auto& num : nums) { // 遍历容器
    std::cout << num << " ";
}

为什么重要

  • 比C风格的for (int i=0; i<n; i++)更安全(无越界风险)
  • 支持所有STL容器和自定义迭代器
  • 面试提示:对比for (auto&& x : range)的性能优化

5. 模板元编程(TMP)

template<int N>
struct Factorial {
    static const int value = N * Factorial<N-1>::value;
};
template<>
struct Factorial<0> { static const int value = 1; };

int x = Factorial<5>::value; // 编译期计算120

为什么重要

  • 编译期计算(零运行时开销)
  • 标准库(如std::tuple)的基石
  • 面试提示:C++11后的constexpr更易用,但TMP仍是面试高频题

6. 并发支持(C++11起)

std::mutex mtx;
std::vector<std::thread> threads;

for (int i = 0; i < 10; ++i) {
    threads.emplace_back([&mtx, i] {
        std::lock_guard<std::mutex> lock(mtx);
        std::cout << "Thread " << i << "\n";
    });
}
for (auto& t : threads) t.join();

为什么重要

  • 原生支持线程、互斥锁、条件变量
  • 比C的pthread更类型安全(RAII管理锁)
  • 面试提示std::async与线程池的取舍

7. 用户定义字面量(C++11起)

auto duration = 250ms; // 来自std::chrono
auto size = 64_KB;     // 自定义字面量

为什么重要

  • 提升代码可读性(如单位明确化)
  • 标准库应用(std::stringstd::complex
  • 面试提示:如何实现自定义_KB后缀

8. 结构化绑定(C++17起)

std::map<int, std::string> m = {{1, "one"}, {2, "two"}};
for (const auto& [key, value] : m) { // 直接解构
    std::cout << key << ": " << value << "\n";
}

为什么重要

  • 简化多返回值处理(替代std::tie
  • 支持pair、tuple、结构体等

9. 概念(Concepts,C++20起)

template<typename T>
concept Addable = requires(T a, T b) { a + b; };

template<Addable T>
T sum(T a, T b) { return a + b; }

为什么重要

  • 取代SFINAE,提升模板可读性
  • 编译期类型约束(如限制模板参数必须可相加)

10. 协程(C++20起)

generator<int> range(int start, int end) {
    for (int i = start; i < end; ++i)
        co_yield i; // 协程挂起
}

for (int n : range(1, 10)) { /* 惰性求值 */ }

为什么重要

  • 简化异步代码(如网络IO)
  • 比回调或Promise更直观