C++第一阶段——语言基础与核心特性
C++语言基础与核心特性
1. C++ 基本概念与语法
*问题:
-
C++ 和 C 的区别类型?
特性 C 语言 C++ 编程范式 过程式编程 支持面向对象、泛型、函数式编程 标准库 标准库功能有限 强大的 STL 库 (容器、算法等) 函数特性 不支持函数重载 支持函数重载和默认参数 内存管理 malloc/free new/delete 运算符 类型安全 弱类型检查 更强的类型检查和类型转换机制 异常处理 无内置异常处理 try/catch 异常处理机制 结构体/类 结构体仅含数据成员 类包含成员函数和访问控制 关键区别示例:
// C++ 特有特性示例 class Rectangle { // 类定义 private:int width, height; public:Rectangle(int w, int h) : width(w), height(h) {}int area() const { return width * height; } // 成员函数 };template <typename T> // 模板函数 T max(T a, T b) {return a > b ? a : b; }
-
const
关键字的作用和作用(常量、常量指针、指向常量的指针、常量成员函数)?用法 示例 作用说明 常量变量 const int MAX = 100;
定义不可修改的常量 常量指针 int* const ptr = &x;
指针本身不可修改(指向固定) 指向常量的指针 const int* ptr = &x;
指向的数据不可修改 常量成员函数 int get() const { ... }
承诺不修改对象状态 常量引用 void func(const string& s);
避免拷贝且防止修改 // 常量成员函数示例 class Circle {double radius; public:double getRadius() const { // 常量成员函数return radius; // 不能修改成员变量} };
-
static
关键字的作用和作用(静态变量、静态函数、静态成员变量、成员变量)?上下文 作用 示例 局部变量 延长生命周期(整个程序运行) void func() { static int count; }
全局变量/函数 限制作用域为当前文件 static int internalVar;
类静态成员变量 类所有实例共享 class A { static int count; }
类静态成员函数 无 this 指针,访问静态成员 class A { static void f(); }
// 静态成员示例 class Counter {static int count; // 声明 public:Counter() { count++; }static int getCount() { return count; } }; int Counter::count = 0; // 定义初始化
-
volatile
关键字的作用?- 作用:防止编译器优化(告知编译器变量可能被外部修改)
- 使用场景:硬件寄存器、多线程共享变量
- 示例:
volatile bool flag = false;
-
inline
关键字的作用,什么情况下函数会被内联auto
?-
作用:建议编译器内联展开函数体(消除函数调用开销)
-
内联条件:
- 函数体简单(通常不超过10行)
- 无递归/循环复杂结构
- 非虚函数
-
示例:
inline int square(int x) { return x * x; }
-
-
decltype
关键字的作用及使用场景?-
作用:查询表达式的类型
-
使用场景:
int x = 10; decltype(x) y = 20; // y的类型为inttemplate <typename T, typename U> auto add(T t, U u) -> decltype(t + u) {return t + u; }
-
-
关键字的作用?
作用类别 说明 C++ 示例 程序控制 控制代码执行流程(分支、循环、跳转) if
,for
,while
,switch
数据类型定义 声明变量、函数或对象的数据类型 int
,char
,bool
,class
内存管理 管理内存分配与释放 new
,delete
访问控制 限制类成员的访问权限 public
,private
,protected
修饰符 修改变量/函数的行为(常量性、静态性等) const
,static
,volatile
高级特性支持 支持面向对象(继承、多态)、泛型编程等 virtual
,template
,typename
异常处理 处理程序运行时错误 try
,catch
,throw
-
头文件守卫(包括 Guards)的作用和实现方式?
// myheader.h #ifndef MYHEADER_H // 如果没有定义MYHEADER_H #define MYHEADER_H // 则定义它// 头文件内容...#endif // 结束
-
命名空间(Namespace)的作用和使用?
namespace MyLib {void func();class MyClass {}; }// 使用方式: using namespace MyLib; // 引入整个命名空间 using MyLib::func; // 引入特定符号 MyLib::MyClass obj; // 完全限定名
方式 作用域 风险 using namespace std;
引入整个命名空间 可能引起名称冲突 using std::cout;
引入特定符号 风险较低 -
声明和指令的区别?
特性 声明(Declaration) 指令(Instruction) 功能 提供信息,定义结构或实体 执行具体操作,改变程序状态 是否执行 不执行具体操作 执行具体操作 语法结构 通常以特定语法形式出现(如 int a;
)包含操作符和操作数(如 a = 5;
)示例 int a;
(变量声明)void func();
(函数声明)a = 5;
(赋值指令)b = a + 3;
(算术指令) -
C++ 的基本数据类型及其大小?
类型 32位系统大小 64位系统大小 范围 bool
1 byte 1 byte true/false char
1 byte 1 byte -128~127 或 0~255 short
2 bytes 2 bytes -32,768~32,767 int
4 bytes 4 bytes -231~231-1 long
4 bytes 8 bytes 系统相关 long long
8 bytes 8 bytes -263~263-1 float
4 bytes 4 bytes ±3.4e-38~±3.4e38 double
8 bytes 8 bytes ±1.7e-308~±1.7e308 long double
8-16 bytes 16 bytes 大于 double 的范围 -
类型转换(隐式转换、静态转换
nullptr
、动态转换、常量、重新解释转换)及其使用场景和注意事项?C++ 四种强制类型转换
转换类型 语法 使用场景 风险 静态转换 static_cast<type>()
基本类型转换、基类指针向下转换 运行时无检查 动态转换 dynamic_cast<type>()
多态类型安全向下转换 需要 RTTI,性能开销 常量转换 const_cast<type>()
移除 const/volatile 限定符 可能引发未定义行为 重新解释转换 reinterpret_cast<type>()
低层二进制重新解释 高度危险,平台相关 // 静态转换(常用安全转换) double d = 3.14; int i = static_cast<int>(d); // i=3// 动态转换(多态类型检查) Base* b = new Derived; Derived* d = dynamic_cast<Derived*>(b); // 成功// 常量转换(移除 const) const int c = 10; int* p = const_cast<int*>(&c); // 谨慎使用!// 重新解释转换(指针转整数) int* ip = new int(65); char* cp = reinterpret_cast<char*>(ip);
2. 针对对象编程(OOP)特性
*问题类型:
-
OOP 的三大调用时机是什么? 如何理解封装、继承、多态?
- 封装:将数据与操作数据的方法绑定,隐藏实现细节(通过
private
/protected
控制访问)。 - 继承:允许派生类复用基类的属性和方法(
class Derived : public Base
)。 - 多态:同一接口在不同上下文中表现不同行为(通过虚函数实现运行时多态)。
- 封装:将数据与操作数据的方法绑定,隐藏实现细节(通过
-
类(Class)和对象(Object)的区别?
类 对象 抽象蓝图(数据类型定义) 类的具体实例(占用内存实体) 不占用内存 运行时在堆/栈分配内存 例: class Car { ... }
例: Car myCar;
-
构造函数和构造函数:
-
默认构造函数、拷贝构造函数、移动构造函数、赋值操作重载、移动赋值特性重载(“大五"或"大六”)
-
各自的调用时机?
-
为什么需要拷贝构造函数和赋值重载?
函数类型 声明示例 调用时机 默认构造函数 ClassName();
创建无参对象: ClassName obj;
拷贝构造函数 ClassName(const ClassName& other);
拷贝初始化: ClassName obj2 = obj1;
移动构造函数 ClassName(ClassName&& other);
移动资源: ClassName obj2 = std::move(obj1);
拷贝赋值运算符 ClassName& operator=(const ClassName&);
赋值: obj2 = obj1;
移动赋值运算符 ClassName& operator=(ClassName&&);
移动赋值: obj2 = std::move(obj1);
-
-
拷贝深和浅拷贝的区别?如何实现深拷贝?
-
默认浅拷贝仅复制指针(不复制资源),需自定义深拷贝避免重复释放资源。
-
深拷贝实现:
class String { char* data; public: // 深拷贝构造函数 String(const String& other) { data = new char[strlen(other.data) + 1]; strcpy(data, other.data); } // 深拷贝赋值 String& operator=(const String& other) { if (this != &other) { delete[] data; data = new char[strlen(other.data) + 1]; strcpy(data, other.data); } return *this; } };
-
-
什么时候需要自定义这些函数?(三/五/零规则)
三/五/零法则
- 三法则:需自定义析构函数时,通常需同时定义拷贝构造和拷贝赋值。
- 五法则(C++11):增加移动构造和移动赋值。
- 零法则:不管理资源时,依赖编译器默认实现。
-
虚解析构造函数的作用?
-
作用:确保通过基类指针删除派生类对象时,调用完整的析构链。
class Base { public: virtual ~Base() {} // 虚析构 }; class Derived : public Base {}; Base* ptr = new Derived(); delete ptr; // 正确调用 Derived 的析构函数
-
-
继承:
-
公继承、保护继承、本土继承的区别?
继承类型 基类 public
成员在派生类中的访问权限public
public
protected
protected
private
private
-
多继承的优缺点,菱形继承问题及解决方案(虚继承)?
-
菱形继承:
- 问题:
Final
类包含两份Base
子对象(二义性)。 - 解决方案:虚继承(
class B : virtual public A
)。
- 问题:
-
-
虚继承的原理?
虚继承原理:虚基类子对象被共享,通过虚基类表(vtable)偏移访问。
-
-
多态:
-
什么是多态? 实现多态的方式(编译时多态、运行时多态)?
-
编译时多态:函数重载、模板(静态绑定)。
-
运行时多态:虚函数(动态绑定)。
class Shape { public: virtual void draw() = 0; // 纯虚函数 → 抽象类 }; class Circle : public Shape { public: void draw() override { /* 实现 */ } // 重写虚函数 }; Shape* s = new Circle(); s->draw(); // 调用 Circle::draw()
-
-
虚函数(
virtual
),纯虚函数(=0
),抽象类?概念 是否可实例化 是否含函数体 派生类强制重写 普通虚函数 ✅ ✅ ❌ 纯虚函数 ❌ ❌ ✅ 抽象类 ❌ 可含其他函数实现 必须实现所有纯虚函数 📌 关键记忆点:
- 虚函数:
virtual
+ 可选重写 → 支持多态 - 纯虚函数:
virtual ... = 0
→ 强制重写 - 抽象类:含纯虚函数 → 定义接口规范
- 虚函数:
-
-
虚函数表(vtable)和虚指针(vptr)的原理?
- 每个含虚函数的类有一个
vtable
(存储函数指针数组)。 - 对象隐含
vptr
指针指向vtable
,运行时通过vptr
定位实际函数。
- 每个含虚函数的类有一个
-
重载(Overload)、覆盖/调用(Override)、隐藏(Hide)的区别?
类型 作用域 关键特征 重载 同一类/命名空间 函数名相同,参数列表不同 覆盖 基类与派生类 虚函数 + 相同签名( override
)隐藏 基类与派生类 非虚函数 + 同名(屏蔽基类函数) -
友元函数和友元类:作用和使用场景?打破封装性?
-
作用:允许访问类的
private
/protected
成员。class A { private: int secret; friend class B; // 友元类 friend void func(); // 友元函数 };
-
打破封装性:谨慎使用,仅用于紧密协作的类/函数。
-
-
接口(Interface)在 C++ 中如何实现?
-
C++ 通过纯虚抽象类实现接口:
class IPrintable { public: virtual void print() const = 0; virtual ~IPrintable() = default; }; class Document : public IPrintable { public: void print() const override { ... } };
-
3. 模板(Templates)
*问题类型:
-
函数模板和类模板的定义与使用?
定义与使用:
类型 定义 使用示例 函数模板 泛型函数,操作不同类型数据 cpp<br>template <typename T><br>T max(T a, T b) { return a > b ? a : b; }<br>
类模板 泛型类,成员数据类型/方法可参数化 cpp<br>template <typename T><br>class Stack {<br> T data[100];<br>public:<br> void push(T item);<br>};<br>
调用方式: // 函数模板显式实例化 cout << max<int>(3, 5); // 输出 5 // 类模板实例化 Stack<string> s; // 创建字符串栈 s.push("Hello");
-
模板的实例化过程?
编译器处理流程:
- 解析模板声明:检查基本语法
- 延迟实例化:直到使用具体类型时才生成代码
- 生成特化版本:为每个类型参数创建具体类/函数
关键特性:
- 隐式实例化:编译器自动推导类型(如
max(3.14, 2.71)
→double
版本) - 显式实例化:手动指定类型(如
max<float>(...)
) - 编译期开销:每用一新类型实例化,代码体积增大
-
模板特化(Template Specialization)和偏特化(Partial Specialization)?
特化类型:
类型 作用 示例 全特化 为特定类型提供定制实现 cpp<br>template <><br>const char* max(const char* a, const char* b) {<br> return strcmp(a, b) > 0 ? a : b;<br>}<br>
偏特化 为部分参数特化(仅类模板) cpp<br>template <typename T><br>class Stack<T*> { // 指针类型的特化<br> // 特殊实现<br>};<br>
应用场景:
- 优化特定类型性能(如
char*
避免深拷贝) - 特殊类型处理(如指针、
bool
等)
- 优化特定类型性能(如
-
为什么模板的定义(实现)通常放在头文件中?
根本原因:模板是编译期生成代码的机制
-
分离编译问题:
- 编译器在实例化模板时需要看到完整定义
- 若声明/定义分离(.hpp/.cpp),链接器无法找到具体实现
-
解决方案:
-
推荐方法:模板定义直接写在头文件(.hpp)
-
显式实例化(需列出所有可能类型):
// stack.cpp template class Stack<int>; // 显式实例化 int 版本 template class Stack<float>; // 显式实例化 float 版本
-
export
关键字(C++11 支持有限,不推荐)
-
-
-
模板元编程(Template Metaprogramming)是什么?有什么应用?
定义:在编译期执行计算的编程技术(图灵完备)
核心机制:- 模板特化 + 递归实例化
- 类型推导(
typedef
、using
) - 值计算(
enum
、constexpr
)
经典示例:编译期阶乘计算
template <int N> struct Factorial { enum { value = N * Factorial<N-1>::value }; }; template <> struct Factorial<0> { // 特化终止递归 enum { value = 1 }; }; // 编译期计算结果 constexpr int fact5 = Factorial<5>::value; // 等于 120
核心应用:
领域 用途 类型安全容器 std::vector<T>
,std::map<K,V>
编译期计算 数学运算、单位转换(如 std::ratio
)静态多态 CRTP(奇异递归模板模式) 条件编译 std::enable_if
, 特性检查(traits)高性能库 Eigen(矩阵运算), Boost.MPL 优势与代价:
优势 代价 ⚡ 零运行时开销 📈 编译时间显著增加 🛡️ 更强的类型安全 🧠 代码可读性降低 🔧 生成高度优化的专用代码 💻 调试困难(编译器错误复杂) 📌 重要原则:优先用
constexpr
函数替代 TMP(C++11 起),除非需要类型操作或编译期分支。
4. 异常处理(异常处理)
*问题类型:
-
try
,catch
,throw
关键字?关键字 作用 示例 throw
抛出异常对象 throw std::runtime_error("Error!");
try
定义可能抛出异常的代码块 try { risky_operation(); }
catch
捕获并处理特定类型异常 catch (const std::exception& e) { ... }
-
异常处理的流程?
-
栈展开(Stack Unwinding)?
- 过程:
- 异常抛出后,从当前函数开始逆向回溯调用栈
- 每层栈帧的局部对象自动调用析构函数
- 直到找到匹配的
catch
块或程序终止
- 关键点:
- 仅析构完全构造的对象
- 内置类型(指针等)不会自动释放资源 → 需 RAII 管理
- 过程:
-
异常安全(异常安全)的级别(Basic, Strong, Nothrowguarantee)?
级别 描述 基本保证 (Basic) 不泄露资源,对象保持有效状态(但内容可能改变) 强保证 (Strong) 操作要么完全成功,要么回滚到操作前状态(事务语义) 不抛保证 (Nothrow) 承诺不抛出任何异常(如 swap()
、析构函数)📌 实现强保证的典型方法:copy-and-swap 惯用法
-
RAII(资源获取即初始化)是什么?如何实现异常安全?
-
核心思想:资源生命周期绑定对象生命周期
-
实现异常安全:
class FileHandler { FILE* file; public: FileHandler(const char* path) : file(fopen(path, "r")) { if (!file) throw std::runtime_error("Open failed"); } ~FileHandler() { if (file) fclose(file); } // 自动释放资源 }; void process_file() { FileHandler fh("data.txt"); // 构造函数获取资源 // 即使此处抛出异常,析构函数仍会关闭文件 }
-
标准库应用:
std::lock_guard
,std::unique_ptr
,std::vector
等
-
-
C++ 中异常处理返回的优缺点,与错误码的区别?
特性 异常 错误码 传播方式 自动跨调用栈传播 手动逐层返回检查 错误处理 集中式处理(catch 块) 分散在每个调用点 性能开销 抛出时开销大(栈展开) 无额外开销 可忽略性 不可忽略(必须处理或终止) 可被忽略(不检查返回值) 适用场景 严重错误、跨多层调用 预期内的错误(如文件不存在) -
noexcept
关键字的作用?- 作用:
- 声明函数不抛出异常:
void func() noexcept;
- 若声明
noexcept
的函数抛出异常 → 程序直接终止(std::terminate()
)
- 声明函数不抛出异常:
- 优势:
- 性能优化:编译器可移除异常处理开销
- 安全强化:防止异常传播破坏关键操作
- 移动操作:标准库对
noexcept
移动构造函数优先优化(如std::vector
扩容)
⚠️ 重要规则:析构函数默认
noexcept
(C++11 起),若可能抛出异常需显式声明noexcept(false)
- 作用:
5.智能指针(Smart Pointers)
*问题类型:
-
为什么需要智能指针?解决什么问题(内存溢出、悬空指针)?
问题类型 描述 智能指针解决方案 内存泄漏 忘记 delete
导致资源永久占用自动生命周期管理 悬空指针 访问已释放内存( delete
后未置空)自动置空或释放后失效 重复释放 多次 delete
同一内存所有权模型(独占/共享) 异常安全 异常抛出时资源未释放 RAII 机制保证析构调用 📌 核心价值:通过 RAII 实现自动资源管理,避免手动内存管理错误
-
std::unique_ptr
、、std::shared_ptr
的std::weak_ptr
区别、使用场景和实现原理?类型 所有权模型 复制语义 线程安全 使用场景 实现原理 std::unique_ptr
独占所有权 ❌ 仅移动 ❌ 非原子操作 单一所有者场景(工厂模式、资源句柄) 原始指针 + 自定义删除器 std::shared_ptr
共享所有权 ✅ 引用计数增加 ⚠️ 计数原子安全 多对象共享资源(缓存、观察者模式) 控制块(计数+原始指针+删除器) std::weak_ptr
无所有权 ✅ 不增加计数 ✅ 与 shared_ptr 同步 解决循环引用、缓存临时访问 依赖 shared_ptr 的控制块 关键操作示例:
// unique_ptr 移动语义 auto p1 = std::make_unique<int>(42); auto p2 = std::move(p1); // p1 变为 nullptr// shared_ptr 共享 auto s1 = std::make_shared<Object>(); auto s2 = s1; // 引用计数+1// weak_ptr 安全访问 std::weak_ptr<Object> w = s1; if (auto tmp = w.lock()) { // 转为 shared_ptrtmp->use(); // 安全使用 }
-
std::shared_ptr
的循环引用问题及解决方案(std::weak_ptr
)?-
问题场景:
class Parent {std::shared_ptr<Child> child; // 强引用 }; class Child {std::shared_ptr<Parent> parent; // 强引用 → 循环引用! };
-
结果:
Parent
和Child
引用计数永远 >0 → 内存泄漏 -
解决方案:
weak_ptr
打破循环class Child {std::weak_ptr<Parent> parent; // 弱引用 };
-
原理:
weak_ptr
不增加引用计数,允许对象正常析构
-
-
std::shared_ptr
的线程安全问题?操作对象 线程安全 说明 同一 shared_ptr 实例 ❌ 非原子操作 同时读写需加锁(如 mutex) 引用计数 ✅ 原子操作 任何线程增减计数安全 管理的资源 ❌ 非线程安全 需额外同步机制保护 不同 shared_ptr 实例 ✅ 可并行访问 指向不同对象时无冲突 ⚠️ 即使引用计数原子安全,修改指向的对象仍需同步:
// 线程不安全示例: if (!ptr) {ptr.reset(new Object); // 可能多个线程同时执行 }
-
如何删除自定义程序(Deleter)?
-
作用:定制资源释放逻辑(非
delete
场景) -
实现方式:
// 文件句柄自定义删除器 auto file_deleter = [](FILE* f) { if (f) fclose(f); };// 应用于智能指针 std::unique_ptr<FILE, decltype(file_deleter)> fptr(fopen("data.txt", "r"), file_deleter);std::shared_ptr<FILE> sfptr(fopen("log.txt", "w"), file_deleter); // shared_ptr 类型更简洁
-
典型场景:
- 关闭文件/网络连接
- 释放 GPU 资源
- 回调通知机制
-