跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

C++ 核心知识点解析(九)

综述由AI生成解析了 C++ 十大核心知识点,包括源文件包含规范、深浅拷贝区别及实现、命名空间用法、友元机制、线程安全类设计(互斥锁/原子操作)、C 语言库调用、编译链接流程、auto 与 decltype 类型推断、虚函数多态原理以及 new/delete 配对规则。内容涵盖基础语法、内存管理、并发编程及底层机制,适合 C++ 开发者复习巩固。

Ne0发布于 2026/3/29更新于 2026/5/2529 浏览
C++ 核心知识点解析(九)

1. C++ 是否可以 include 源文件?

可以 include 源文件。不过,尽管技术上可行,但这并不是一个推荐的做法,也不符合良好的编程习惯。


2. C++ 中什么是深拷贝?什么是浅拷贝?写一个标准的拷贝构造函数?

  1. **浅拷贝:**浅拷贝只是简单地复制对象的值,而不复制对象所拥有的资源或内存。也就是说,两个对象共享同一个资源或内存。当一个对象修改了该资源或内存,另一个对象也会受到影响。这种情况通常发生在默认的拷贝构造函数或赋值操作中。
  2. **深拷贝:**深拷贝不仅复制对象的值,还会新分配内存并复制对象所拥有的资源。这样两个对象之间就不会共享同一个资源或内存,修改其中一个对象的资源或内存不会影响到另一个对象。

举个例子,一个标准的深拷贝构造函数可以这样写:

#include <cstring>
#include <iostream>
// for std::strlen and std::strcpy
class MyClass {
 private:
  char* data;
 public:
  MyClass(const char* inputData) {
   data = new char[std::strlen(inputData) + 1];
   std::strcpy(data, inputData);
  }
  // 深拷贝构造函数
  MyClass(const MyClass& other) {
   data = new char[std::strlen(other.data) + 1];
   std::strcpy(data, other.data);
  }
  // Destructor
  ~MyClass() {
   delete[] data;
  }
  // 打印数据
  void printData() {
   std::cout << data << std::endl;
  }
};
int main() {
  MyClass obj1("Hello");
  MyClass obj2 = obj1;
  obj1.printData(); // Output: Hello
  obj2.printData(); // Output: Hello
  return 0;
}

在这个例子中,MyClass类有一个指向字符数组的指针data。拷贝构造函数对另一个对象进行深拷贝,即为data分配新的内存并复制字符串,因此两个对象各自独立地拥有自己的数据。


3. C++ 中命名空间有什么作用?如何使用?

命名空间(namespace)主要用于解决名字冲突问题。当项目规模较大,包含很多函数、类、变量的时候,很容易出现名字相同的情况,这时候命名空间就显得特别重要。

命名空间的基本用法如下:

namespace MyNamespace {
 int myVar;
 void myFunc() {
  // do something
 }
}

你可以通过命名空间的名称来访问其中的成员,比如 MyNamespace::myVar和MyNamespace::myFunc()。


4. C++ 中友元类和友元函数有什么作用?

两者主要用于提供访问私有成员和保护成员的权限。

友元关系是一种单向的访问权限,并不会破坏封装性,同时也不会牵涉到类之间的继承关系。友元的使用在以下情况下特别有用:

**1. 友元函数:**允许一个函数访问某个类的私有成员和保护成员。

class MyClass {
 private:
  int privateMember;
 public:
  MyClass() : privateMember(0) {}
  //声明友元函数
  friend void friendFunction(MyClass &obj);
};
void friendFunction(MyClass &obj) {
 //访问 privateMember
 obj.privateMember = 10;
}

**2. 友元类:**允许另一个类访问某个类的私有成员和保护成员。

class B; //前向声明
class A {
 private:
  int privateMember;
 public:
  A() : privateMember(0) {}
  //声明B为友元类
  friend class B;
};
class B {
 public:
  void accessA(A &obj) {
   //访问 A 的 privateMember
   obj.privateMember = 20;
  }
};

扩展知识

下面进一步讨论下它们的作用场景和设计考量:

1. 封装与开放:

  • 封装是面向对象编程的基本原则之一,它将数据和操作数据的方法绑定到一起,防止外部代码直接访问对象的内部状态。友元的引入让类在需要的时候能够部分地开放它的内部状态,通常不会滥用。
  • 友元函数和友元类提供了一种在不破坏封装性的条件下,安全访问私有成员的方式。

2. 友元的替代方案:

  • 如果友元机制的使用本质上意味着违反封装性或设计初衷,那么可能需要重新考量类的设计。
  • 你可以选择通过公开接口提供访问权限 (如 getter/setter 方法),或利用继承、多态等其他 OOP 特性来实现同样的目的。

3. 访问控制复杂度:

  • 使用友元可能会增加代码的复杂度,因为它打破了类的封装性,代码的维护变得相对困难。所以,在维护代码时,需要非常小心,确保友元使用的合理性和必要性。

友元是一种方便但需要慎用的工具,合理使用能够简化代码,但滥用则会破坏类的封装性,增加代码维护的难度。建议在实际编程中能够权衡利弊,合理利用这一机制。


5. C++ 如何设计一个线程安全的类?

可以从以下几个方面避免:

  1. 使用互斥锁 (mutex) 保护共享资源。
  2. 部分逻辑可以使用无锁编程,原子变量控制。
  3. 使用线程消息队列形式,保证此类里的所有操作任务在一个队列里,都在一个线程内调度,自然而然就解决了多线程问题。

要设计一个线程安全的类,通常情况下,我们会使用互斥锁 (mutex) 来保护共享资源,确保在任何时刻只有一个线程可以访问修改这些资源。

下面是一个简单示例,展示了如何使用 std::mutex 来实现一个线程安全的类:

#include <iostream>
#include <thread>
#include <mutex>

class SafeCounter {
 public:
  SafeCounter() : value(0) {}
  void increment() {
   std::lock_guard<std::mutex> lock(mutex_);
   ++value;
  }
  int getValue() {
   std::lock_guard<std::mutex> lock(mutex_);
   return value;
  }
 private:
  int value;
  std::mutex mutex_;
};

int main() {
 SafeCounter counter;
 auto increment_func = [&counter]() {
  for (int i = 0; i < 100; ++i) {
   counter.increment();
  }
 };
 std::thread t1(increment_func);
 std::thread t2(increment_func);
 t1.join();
 t2.join();
 std::cout << "Final value: " << counter.getValue() << std::endl;
 return 0;
}
  1. std::mutex 用于保护共享数据的访问。
  2. std::lock_guard 是一个 RAII 类型的锁机制,用来确保在作用域结束时自动释放锁。
  3. increment方法和getValue方法都使用锁保护共享数据。

扩展知识

再看下其他方案:

1. 读写锁

有时我们需要实现的场景是多线程可以同时读数据,但写数据时需要独占锁。这可以使用 std::shared_mutex(C++17 引入) 来实现。std::shared_lock允许多个线程同时获取读锁,而std::unique_lock则用于写锁。

2. 原子操作

对于一些简单的整型操作,可以使用std::atomic来代替互斥锁。std::atomic提供了高效的原子操作,避免了锁的开销。

#include <atomic>

class AtomicCounter {
 public:
  AtomicCounter() : value(0) {}
  void increment() {
   value.fetch_add(1, std::memory_order_relaxed);
  }
  int getValue() {
   return value.load(std::memory_order_relaxed);
  }
 private:
  std::atomic<int> value;
};

3. 使用合适的同步机制

当多个线程需要协调工作时,可以使用条件变量来等待特定条件满足后再进行操作。


6. C++ 如何调用 C 语言的库?

可以使用 extern"c" 来告诉编译器按照 C 语言的链接方式处理某些代码:

  1. 在 C++ 代码中包含 C 语言头文件时,用extern"c"进行声明,比如:
extern "c" {
 #include "your_c_library.h"
}
  1. 需要在链接阶段确保 C++ 项目和 C 语言库都被正确链接。可通过编写合适的 CMakeLists.txt 或 Makefile 来实现。
  2. 也可以不使用 extern"c",源文件后缀名改为.c 也行。

7. 介绍一下 C++ 程序从编写到可执行的整个过程?

总共分 5 步:

  1. **编写代码:**编写 C++ 源代码,保存为 .cpp、.cc、.h文件。
  2. **预处理:**预处理器根据源代码中的预处理指令 (如#include替换、#define替换等) 对代码进行处理,生成纯净的源代码。
  3. **编译:**编译器 (如 g++ 或 clang++) 将预处理后的源代码翻译成汇编代码。
  4. **汇编:**汇编器 (如 as) 将汇编代码转换成机器码,生成目标文件 (.o文件)。
  5. **链接:**链接器 (如ld) 将多个目标文件和库文件链接在一起,生成最终的可执行文件。

8. 什么是 C++ 中的 auto 和 decltype?

两者都是 C++11 引入的新特性,主要用于类型推断。

1. auto关键字:用于自动推断变量的类型。编译器会根据变量的初始化表达式来推导变量的类型,这样开发者就不需要显式地声明类型。

例子:

auto x = 10; // x 被推断为 int
auto y = 3.14; // y 被推断为 double
auto str = "Hello, world!"; // str 被推断为 const char*

2. decltype关键字:用于推断表达式的类型。它会返回一个表达式所对应的类型信息,而不进行计算。这个在模板编程中特别有用。

例子:

int a = 10;
deprecated(a) b = 20; // b 被推断为 int
deprecated(a + 10.0) c = 30.0; // c 被推断为 double,因 a+10.0 结果是 double

9. 请介绍 C++ 多态的实现原理?

回答多态的实现原理,主要可以围绕在虚函数、虚函数表和虚函数表指针方向上。

多态通过虚函数实现。通过虚函数,子类可以重写父类的方法,当通过基类指针或引用调用时,会根据对象的实际类型调用对应的函数实现。

而这更深层次的原理,是通过虚表 (vtable) 和虚表指针 (vptr) 机制实现的。虚表是一个函数指针数组,包含了该类所有虚函数的地址,而虚表指针存储在对象实例中,指向属于该对象的虚表。

1. 虚函数和重写:在基类中使用关键字 virtual 声明虚函数后,在子类中可以重写这个函数。

class Base {
 public:
  virtual void show() {
   std::cout << "Base show" << std::endl;
  }
};
class Derived : public Base {
 public:
  void show() override {
   std::cout << "Derived show" << std::endl;
  }
};

2. 虚表 (vtable):每个包含虚函数的类都会有一个虚表 (vtable),这个虚表在编译时生成。它包含了该类所有虚函数的指针。对于每个类 (而不是每个对象),编译器会创建一个唯一的虚表。

3. 虚表指针 (vptr):每个包含虚函数的对象实例会有一个隐藏的虚表指针 (vptr),它在对象创建时自动初始化,指向该类的虚表。不同类型的对象,其虚表指针会指向不同的虚表。例如,上述示例中,Base 和 Derived 对象的虚表指针分别指向它们各自的虚表。

4. 多态的调用机制:当通过基类指针或引用调用虚函数时,程序会通过该指针或引用找到对应的对象,然后通过虚表指针找到正确的虚表中的函数地址,最终调用适当的函数实现,这样程序能够在运行时决定调用哪一个函数实现。

5. 实际示例:

void demonstratePolymorphism(Base &obj) {
 obj.show(); // 依赖于实际对象的类型
}
int main() {
 Base b;
 Derived d;
 demonstratePolymorphism(b); // 输出 "Base show"
 demonstratePolymorphism(d); // 输出 "Derived show"
 return 0;
}

10. C++ 中为什么 new[] 和 delete[] 一定要配对使用?

一定要配对使用,如果不正确地配对使用 new[]和 delete[],会导致:

  1. **内存泄漏:**如果用 new[]分配内存但用 delete(不带中括号) 来释放,数组中的每个对象的析构函数不会被调用,导致内存泄漏。
  2. **运行时错误:**如果用 new分配内存,但却用delete[]来释放,可能导致未定义行为。

具体来说,当用 new[]分配一块连续的内存时,编译器不仅管理实际数据的存储位置,还存储了数组的大小信息,以便在调用 delete[]时能够正确释放这块内存。而且,delete[]会负责调用数组中每个对象的析构函数,再释放整个数组的内存。

目录

  1. 1. C++ 是否可以 include 源文件?
  2. 2. C++ 中什么是深拷贝?什么是浅拷贝?写一个标准的拷贝构造函数?
  3. 3. C++ 中命名空间有什么作用?如何使用?
  4. 4. C++ 中友元类和友元函数有什么作用?
  5. 5. C++ 如何设计一个线程安全的类?
  6. 6. C++ 如何调用 C 语言的库?
  7. 7. 介绍一下 C++ 程序从编写到可执行的整个过程?
  8. 8. 什么是 C++ 中的 auto 和 decltype?
  9. 9. 请介绍 C++ 多态的实现原理?
  10. 10. C++ 中为什么 new[] 和 delete[] 一定要配对使用?
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • PX4+ROS 无人机 Offboard 控制:模式解析与实战
  • 基于 CSANMT 的实时中英对照翻译服务实战
  • 本地运行 LLM 的 AI 助手 Jan 部署与使用指南
  • Git 实战:如何精准合并指定分支的特定提交
  • 位运算算法实战:6 道经典题目详解(字符唯一性、缺失数字等)
  • Llama-2-7b 昇腾 NPU 部署与性能测评实战
  • 夸克网盘精选资源:电子书、软件与 AI 学习资料汇总
  • C# 基础学习二十:常用算法与函数
  • Android Framework 工程师面试核心知识点与能力要求
  • Dify 大语言模型私有化部署指南:Linux 环境搭建与 Docker 配置
  • 自然语言处理高级应用与前沿发展
  • 【CS创世SD NAND征文】为无人机打造可靠数据仓:工业级存储芯片CSNP32GCR01-AOW在飞控系统中的应用实践
  • Fooocus 部署实战:本地配置与云方案对比
  • SharpaWave 量产:视觉基触觉手实现 0.005N 感知与模块化设计
  • OpenClaw 接入 QQ 机器人配置教程
  • VS Code + WSL 环境下 GitHub 及 Copilot 连接异常修复方案
  • 医疗垂类大模型应走出“应试”误区,聚焦真实场景落地
  • 基于 LangChain 构建 LLM 应用程序指南
  • Vue 3 实战指南:10 个提升开发体验的核心技巧
  • 从零开始学 AI 绘画:麦橘超然部署与实战指南

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online