C++简介
C++ 是一种静态类型的、编译式的、通用的、大小写敏感的、不规则的编程语言,支持过程化编程、面向对象编程和泛型编程。
C++ 是 C 的一个超集,事实上,任何合法的 C 程序都是合法的 C++ 程序。
四大特性
C++ 完全支持面向对象的程序设计,包括面向对象开发的四大特性:
- 封装(Encapsulation):封装是将数据和方法组合在一起,对外部隐藏实现细节,只公开对外提供的接口。这样可以提高安全性、可靠性和灵活性。
- 继承(Inheritance):继承是从已有类中派生出新类,新类具有已有类的属性和方法,并且可以扩展或修改这些属性和方法。这样可以提高代码的复用性和可扩展性。
- 多态(Polymorphism):多态是指同一种操作作用于不同的对象,可以有不同的解释和实现。它可以通过接口或继承实现,可以提高代码的灵活性和可读性。
- 抽象(Abstraction):抽象是从具体的实例中提取共同的特征,形成抽象类或接口,以便于代码的复用和扩展。抽象类和接口可以让程序员专注于高层次的设计和业务逻辑,而不必关注底层的实现细节。
标准库
标准的 C++ 由三个重要部分组成:
核心语言,提供了所有构件块,包括变量、数据类型和常量,等等。
C++ 标准库,提供了大量的函数,用于操作文件、字符串等。
标准模板库(STL),提供了大量的方法,用于操作数据结构等。
ANSI 标准
ANSI 标准是为了确保 C++ 的便携性 —— 您所编写的代码在 Mac、UNIX、Windows、Alpha 计算机上都能通过编译。
由于 ANSI 标准已稳定使用了很长的时间,所有主要的 C++ 编译器的制造商都支持 ANSI 标准。
C++语言结构
实例
#include <iostream>
using namespace std;
// main() 是程序开始执行的地方
int main()
{
cout << "Hello World"; // 输出 Hello World
return 0;
}
C++ 语言定义了一些头文件,这些头文件包含了程序中必需的或有用的信息。上面这段程序中,包含了头文件 <iostream>。
下一行 using namespace std; 告诉编译器使用 std 命名空间。命名空间是 C++ 中一个相对新的概念。
下一行 // main() 是程序开始执行的地方 是一个单行注释。单行注释以 // 开头,在行末结束。基本概念
C++数据类型
注意:默认情况下,int、short、long都是带符号的,即 signed。
注意:long int 8 个字节,int 都是 4 个字节
C++指针
地址思维:指针存储的是地址,不是值本身
类型匹配:指针类型必须与指向的数据类型匹配
生命周期管理:确保指针有效期间指向的内存也有效
现代实践:优先使用智能指针,避免裸指针的内存管理问题
智能指针(smart pointer)是封装了“裸指针”的类模板,利用 RAII 在对象生命周期结束时自动释放资源,从根本上防止内存泄漏和悬空指针。
C++11 之后标准库主要提供三种:
std::unique_ptr——独占所有权,不能拷贝,只能移动;std::shared_ptr——共享所有权,内部引用计数,最后一个拷贝销毁时才释放;std::weak_ptr——弱引用,不计数,用来打破shared_ptr的循环引用。
除了智能指针,C++ 里常见的“指针”类别还有:
- 裸指针
T*/const T* - 指向成员的指针
T C::*/const T C::* - 函数指针
R (*)(Args…) - 迭代器(本质是“类指针”对象)
- 自定义句柄类(如文件描述符封装
unique_handle等)
C++ 引用 vs 指针
引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。
一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
引用必须在定义时初始化,并且一旦绑定到一个变量后,就不能再绑定到其他变量。
引用很容易与指针混淆,它们之间有三个主要的不同:
不存在空引用,引用必须连接到一块合法的内存。
一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
引用必须在创建时被初始化。指针可以在任何时间被初始化。
引用的对象必须是一个变量,而指针必须是一个地址。
C++结构体
结构体是 C++ 中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。
结构用于表示一条记录,假设您想要跟踪图书馆中书本的动态,您可能需要跟踪每本书的下列属性。
结构体优点:
简单数据封装:适合封装多种类型的简单数据,通常用于数据的存储。
轻量级:相比 class,结构体语法更简洁,适合小型数据对象。
面向对象支持:支持构造函数、成员函数和访问权限控制,可以实现面向对象的设计。
C++构造函数
Details
#constexpr构造函数(C++11+)
编译时计算的构造函数
用于创建常量表达式对象
class MyClass {
public:
// 1. 默认构造函数(无参数)
MyClass() {
// 初始化代码
}
// 2. 参数化构造函数
MyClass(int x, int y) {
this->x = x;
this->y = y;
}
// 3. 拷贝构造函数
MyClass(const MyClass& other) {
this->x = other.x;
this->y = other.y;
}
// 4. 移动构造函数(C++11)
MyClass(MyClass&& other) noexcept {
this->x = std::move(other.x);
this->y = std::move(other.y);
}
// 5. 委托构造函数(C++11)
MyClass(int x) : MyClass(x, 0) {
// 委托给两个参数的构造函数
}
// 6. 转换构造函数(单参数)
MyClass(int x) {
this->x = x;
}
// 7. 继承中的构造函数
class Derived : public Base {
public:
// 使用using声明继承基类构造函数(C++11)
using Base::Base;
// 或显式调用基类构造函数
Derived(int x, int y) : Base(x), y(y) {}
};
private:
int x, y;
};析构函数
析构函数是类的特殊成员函数,在对象生命周期结束时自动调用,用于清理资源。函数名:~ + 类名。无参数,无返回值
派生类
派生类(Derived Class)是通过继承(inheritance)从基类(Base Class)创建的新类。获得基类的所有成员(除构造函数、析构函数、私有成员),可以添加新成员,可以修改(重写)继承的方法,是基类的特化版本。
函数重载
函数重载:同一个类中,函数名相同,参数列表不同
C+++基础问题
一、基础篇(校招/初中级 90% 覆盖率)
- new/delete 与 malloc/free 区别
答纲:new 调用构造函数、返回类型安全、不可重载;malloc 只干内存分配。 - 指针与引用区别
答纲:引用必须初始化且不可改指向;指针可空可改可算术。 - struct 与 class 区别
答纲:默认访问级别不同(public vs private),其余一样。 - const 与 #define 差异
答纲:const 有类型/作用域/调试信息;宏纯文本替换。 - static 关键字 3 种用法
答纲:文件内链接、函数内静态变量、类静态成员/函数。 - C++ 四种类型转换
答纲:static_const 编译期;dynamic_cast 运行时多态;const_cast 去常性;reinterpret_cast 最低级位模式。 - 深拷贝 vs 浅拷贝
答纲:深拷贝重新分配资源,浅拷贝只复制指针值。 - 拷贝构造函数何时被调用
答纲:对象以值传参、以值返回、显式拷贝初始化。 - 赋值运算符与拷贝构造区别
答纲:拷贝构造是从无到有;赋值是改写已有对象。 - vector/list/deque 复杂度对比
答纲:随机访问 O(1)/O(n)/O(1);中间插入 O(n)/O(1)/O(n)。 - map 与 unordered_map 差异
答纲:红黑树 vs 哈希表;有序 vs 无序;O(logN) vs 平均 O(1)。 - RAII 思想
答纲:资源获取即初始化,利用对象生命周期管理资源,异常安全。 - 构造函数能否是虚函数?
答纲:不能,对象还没构造完无虚表。 - 析构函数何时必须虚?
答纲:类会被继承且可能通过基类指针删除派生对象。 - 重载、重写、隐藏对比
答纲:同一作用域参数不同→重载;派生类改虚函数→重写;派生类同名非虚→隐藏。
C++高级
- 虚函数表与动态分派机制
答纲:每个多态类一张 vtable,对象首地址前 8 字节存 vptr,运行时查表定位函数。 - 模板实例化与代码膨胀控制
答纲:显式实例化、common_type 技巧、extern template 声明。 - SFINAE 与 enable_if 写法
答纲:替换失败不是错误,用于模板重载决议;C++17 后用 if constexpr 替代。 - move 语义完美转发实现
答纲:std::move 强制右值引用,std::forward 保持值类别,配合引用折叠规则。 - 内存模型与 std::memory_order
答纲:六种顺序,relaxed/acquire/release/acq_rel/seq_cst;无锁队列用 acq/rel 保证可见性。 - ABA 问题及解决
答纲:CAS 检查值相同但已改回;用版本号/双宽 CAS/ hazard pointer。 - shared_ptr 控制块与循环引用
答纲:引用计数+弱引用计数;weak_ptr 打破循环;make_shared 一次分配优化。 - chrono 与 自定义时钟
答纲:steady_clock 单调、system_clock 可映射日历;可写自己的 clock 满足 TrivialClock。 - 协程(C++20 co_await)状态机
答纲:compiler 生成 promise_type、coroutine_frame、挂起点恢复点;注意对称转移。 - 零开销抽象反例
答纲:std::function 动态分配+类型擦除,虚函数调用无法内联;function_ref 替代方案。 - consteval/constinit 区别
答纲:consteval 强制编译期求值,constinit 只保证静态初始化线程安全。 - 模块化(C++20 Modules)解决什么问题
答纲:替代头文件,减少重复解析,提供 BMI 二进制接口,缩短构建时间。 - 设计一个线程池——任务窃取如何实现
答纲:每个线程本地双端队列,pop 从队尾 steal 从队头,减少竞争;用 std::atomic 索引。 - placement new 与显式析构顺序
答纲:先构造数组再逐个析构,需手动调用析构函数,再 operator delete(p, buf)。
C++中内存泄漏问题
| 问题类型 | 典型症状 | 解决方案 |
|---|---|---|
| 堆内存泄漏 | RSS 持续增长,OOM | 1. 使用 ASan/Valgrind 找到未 delete 处 2. 改用智能指针 ( std::unique_ptr, std::shared_ptr)3. 检查循环引用 ( weak_ptr) |
| 资源句柄泄漏 | FD 耗尽,Socket 连不上 | 1. 检查 open/socket 后未 close 2. 使用 RAII 封装文件描述符 |
| 内存碎片化 | 内存总和使用不高,但 malloc 失败 | 1. 更换分配器 (Jemalloc/TCMalloc) 2. 避免频繁分配大小不一的小对象 (使用内存池) |
| Cache Miss 高 | CPU 利用率低,Stall 高 | 1. 优化数据结构布局 (连续内存替代链表) 2. 循环分块 (Loop Tiling) 3. 数据预取 |
| NUMA 失衡 | 多核扩展性差,延迟抖动 | 1. 绑定线程与内存 (numactl)2. 使用 mbind 本地分配 |
| 伪共享 | 多线程计数器等原子操作极慢 | 1. 关键变量添加 alignas(64) 填充 |