C++ 面试题常用总结 详解(满足c++ 岗位必备,不定时更新)

C++ 面试题常用总结 详解(满足c++ 岗位必备,不定时更新)
📚 本文主要总结了一些常见的C++面试题,主要涉及到语法基础、STL标准库、内存相关、类相关和其他辅助技能,掌握这些内容,基本上就满足C++的岗位技能(红色标记为重点内容),欢迎大家前来学习指正,会不定期去更新面试内容。
 Hi~!欢迎来到碧波空间,平时喜欢用博客记录学习的点滴,欢迎大家前来指正,欢迎欢迎~~
✨✨ 主页:碧波
📚 📚 专栏:C++ 系列文章
目录

一、C ++ 语法基础

🔥 谈谈变量的使用和生命周期,声明和初始化

🔥 谈谈C++的命名空间的作用

🔥  include " " 和 <> 的区别

🔥 指针是什么?

🔥 什么是指针数组和数组指针

🔥 引用是什么?

🔥 指针和引用的区别

🔥 什么是函数指针和指针函数以及区别

🔥 什么是常量指针和指针常量以及区别

🔥 智能指针的本质是什么以及实现原理

🔥 weak_ptr 是否有计数方式,在那分配空间?

🔥 类型强制转换有哪几种?

🔥 函数参数传递时,指针、引用以及值传递有什么区别?

🔥 进程间的通信方式有那些?

🔥 线程间的通信方式有那些?

🔥 简单谈谈对线程的理解,线程间共享资源时该怎么处理?

🔥 常用关键字含义及其使用

🔥 const 可以修饰那些内容

🔥 class 和 struct 的区别?有了class 为啥仍还保留 struct?

二、标准库

🔥 什么是STL?

🔥 数组和vector的区别?

🔥 list 和 vector 的主要区别是什么?

🔥 在什么情况下使用 list 而不是 vector?

🔥 set 的底层数据结构是什么?

🔥 set 和 multiset 有什么不同?

🔥 map 和 unordered_map 的主要区别是什么?

三、内存

🔥 RAII是什么?

🔥  使用RAII的原因以及使用方法?

🔥 new 和 malloc 的区别?delete 和 free 的区别?

🔥 C++的内存框架分布情况

🔥 堆和栈的区别?

🔥 什么是内存对齐,内存对齐有什么好处?

🔥 什么是内存泄漏?写代码时应该怎么防止内存泄漏。

🔥 说说什么是深拷贝和浅拷贝?

四、类相关

🔥 讲讲C++的三大特性(封装、继承、多态)

🔥 多态是怎么实现的? 

🔥 什么是虚函数和纯虚函数?以及区别?

🔥 虚函数是怎么实现的?虚函数在那个阶段生成?

🔥 构造函数和析构函数的作用?

🔥 构造函数和析构函数那个可以被声明为虚函数,为什么?

🔥 构造函数和析构函数那个可以被重载,为什么?

🔥 谈谈什么是重载、重写、隐藏?

🔥 this 指针,为什么会存在this指针?

五、其他必备

🔥 什么是git? 以及git常用命令

🔥 什么是svn? git 和 svn的区别

🔥 Linux 系统下常用的命令会那些,举例?


一、C ++ 语法基础

🔥 谈谈变量的使用和生命周期,声明和初始化

全局变量:在程序的任何地方都能访问到的变量,它们通常在整个程序执行期间都是有效的。

局部变量:定义在函数或者代码块内部的变量,它们只能在声明它们的函数或代码块内部访问

静态变量:一般是在变量前加static 进行声明。

如下表格是各变量之前的差异点,熟练掌握每种变量的属性,就可以正确使用变量。

各变量间的区别全局变量局部变量静态全局静态局部
声明方式在函数外部声明,通常在文件的顶部或者在函数定义之外在函数或代码块的开始部分声明在变量声明时加上 static 关键字在变量声明时加上 static 关键字
代码示例

// 全局变量声明并初始化

int globalVar = 10;

void func() {

// 局部变量声明并初始化

int localVar = 20;

}

// 静态全局变量声明并初始化

static int globalVar = 10;

void func() {

// 静态局部变量声明并初始化

static int localVar = 20;

}

初始化在声明时初始化建议在声明时初始化,避免未知错误在声明时或者在第一次使用之前初始化在声明时或者在第一次使用之前初始化
内存存储位置存储在常量区(静态存储区)存储在栈区存储在常量区(静态存储区)存储在常量区(静态存储区)
作用域整个文件都可访问函数内部整个文件都可访问函数内部
生命周期全局生命周期,会一直存在到程序结束函数被调用期间全局生命周期具有全局生命周期,但作用域仍然受限于其声明的函数或代码块,保持其值在函数调用之间持久化

🔥 谈谈C++的命名空间的作用

命名空间

避免命名冲突: 可以帮助避免不同部分的代码中出现相同的名称,从而防止命名冲突。
组织代码: 可以将相关的函数、类等封装在一起,提高代码的组织性和可读性。
模块化开发: 可以将代码划分为不同的模块,使得代码更易于维护和扩展。

命名空间使用

namespace MyNamespace { int x; void func(); }

标准命名空间

// 通常需要加上 std:: 前缀, std::cout << "Hello, world!" << endl; // 或者使用 using namespace std; using namespace std; cout << "Hello, world!" << endl;

🔥  include " " 和 <> 的区别

#include <文件名> 是包含标准库头文件的方式,编译器会按照标准路径顺序搜索。

#include "文件名" 是包含用户自定义或者项目内部头文件的方式,优先在当前目录查找,然后才是按照标准路径顺序搜索。

🔥 指针是什么?

指针是一个 用来存储变量地址 的特殊数据类型。简单来说,指针变量存储的是内存地址,而不是常规的值。通过指针,我们可以直接访问和操作内存中的数据,而不必知道实际存储的值是什么。

可以使用 解引用操作符 * 来访问指针所指向的变量,使用地址运算符 & 来获取变量的地址。

🔥 什么是指针数组和数组指针

指针数组是一个数组,其中的每个元素都是指针。这些指针可以指向不同的内存地址,通常用于存储一组相同类型的指针。

// ptrArray 是一个包含 5 个元素的数组, // 每个元素都是 int* 类型的指针,可以分别指向不同的整数 int *ptrArray[5]; 

数组指针是一个指针,它指向数组的首地址。它本身是一个指针,但指向的内容是一个数组对象。

// arrPtr 是一个指针,指向一个包含 5 个整数的数组 int (*arrPtr)[5]; 

指针数组常用于需要动态管理一组指针的场景,而数组指针则用于处理数组的整体,特别是在函数参数传递和多维数组的处理中比较常见。

🔥 引用是什么?

引用提供了一个变量的别名。它使用 & 符号来定义。

// ref 是 num 的引用,即 ref 和 num 引用同一个内存位置的整数值 int num = 10; int &ref = num; // ref 是 num 的引用 
  • 引用必须在定义时初始化,并且一旦初始化后,它就不能再绑定到其他变量。

应用场景:

引用可以用于函数参数,允许在函数内部直接修改传递的变量,而不是复制一份值。

🔥 指针和引用的区别

引用不能指向空值(null),而指针可以。

引用在使用时不需要解引用操作(不需要 * 符号),而指针需要。

引用在定义时必须初始化,而指针可以在后续指向不同的对象。

🔥 什么是函数指针和指针函数以及区别

函数指针 是指 指向一个函数的指针变量。它可以指向一个特定类型和签名(参数类型和返回类型)的函数。函数指针的声明形式类似于指向其他类型的指针,但其类型是指向函数的指针类型。

// 声明一个函数指针类型 typedef void (*FuncPtr)(int); // FuncPtr 是一个指向返回类型为 void,参数为 int 的函数指针类型 // 定义一个函数 void myFunction(int x) { // 函数体 } int main() { // 声明一个函数指针变量并初始化 FuncPtr ptr = &myFunction; // 通过函数指针调用函数 ptr(10); // 相当于调用 myFunction(10); return 0; } 

指针函数 指的是 返回类型 为 指向函数的指针 的函数。换句话说,指针函数是一个返回类型为函数指针的函数。

// 声明一个指针函数 int (*funcPtr)(int, int); // funcPtr 是一个函数,返回类型为 int*,参数为两个 int // 定义一个函数 int add(int a, int b) { return a + b; } // 另一个函数,返回一个函数指针 int (*getAddFunctionPointer())(int, int) { return &add; } int main() { // 获取 add 函数的函数指针 funcPtr = getAddFunctionPointer(); // 通过函数指针调用函数 int result = funcPtr(3, 4); // 相当于调用 add(3, 4),result 等于 7 return 0; } 

区别

函数指针是指指向函数的指针变量,而指针函数是一个返回类型为函数指针的函数。

函数指针在声明时需要指定其指向的函数的签名(参数类型和返回类型),而指针函数的返回类型是一个函数指针类型。

函数指针直接指向一个已存在的函数,可以通过该指针调用该函数;而指针函数返回一个函数指针,需要通过该函数指针再调用相应的函数。

🔥 什么是常量指针和指针常量以及区别

常量指针: 指的是指针本身的值(即指向的地址)可以改变,但指针指向的对象的值不能通过这个指针进行修改。

int x = 10; int* const ptr = &x; // ptr 是一个常量指针,指向 int 类型的对象 // 无法再修改 ptr 指向的对象,但可以修改对象本身的值 *ptr = 20; // 合法,修改了 x 的值为 20 // 以下操作不合法,因为 ptr 是常量指针,不能改变指向 // ptr = &y; // 错误,无法改变 ptr 的指向 

指针常量:指的是指针本身的值(即指向的地址)不能改变,但可以通过这个指针修改指向对象的值

int x = 10; const int* ptr = &x; // ptr 是一个指向常量 int 的指针 // 以下操作合法,可以修改 ptr 所指向对象的值 x = 20; // 修改了 x 的值为 20 // 以下操作不合法,因为 ptr 所指向的对象是常量,不能修改其值 // *ptr = 30; // 错误,不能通过 ptr 修改其指向的对象的值 // 以下操作合法,因为 ptr 本身不是常量,可以改变其指向 int y = 50; ptr = &y; // 合法,修改了 ptr 的指向为变量 y // 另一种写法 * 解引用,也是取值。 int const * p;

区别

常量指针 强调指针本身是常量,指向对象的值可以改变;

指针常量 强调指针所指向的对象是常量,指针的指向可以改变

简单记忆:

距const 修饰右边最近的值是常量 ,不能被修改。

🔥 智能指针的本质是什么以及实现原理

智能指针是一种用于管理动态分配内存和自动释放资源的工具,其本质是利用了 RAII(资源获取即初始化)的设计模式。即在对象初始化的时候获取资源(比如动态分配的内存),在对象析构的时候自动释放资源。

它提供了一个封装了指针的对象,通过其析构函数来确保在对象生命周期结束时,所管理的资源能够被正确释放,避免内存泄漏和资源泄漏。

实现原理

智能指针实现是引入计数引用机制。智能指针对象本身持有一个指向动态分配资源的指针,并维护一个引用计数。每当有一个新的智能指针指向同一块内存时,引用计数增加;当智能指针超出作用域或被显式销毁时,引用计数减少。当引用计数为零时,智能指针负责释放其管理的资源。

常见的智能指针类型:

std::shared_ptr:允许多个指针共享同一块资源,通过引用计数来管理资源的生命周期,适用于多个所有者的情况。

std::unique_ptr:独占所指向的对象,保证在任何时刻只有一个指针可以指向该对象,移动语义保证资源的所有权可以传递但不共享。

std::weak_ptr:用于协助 std::shared_ptr,不会增加引用计数,避免循环引用问题,通常用于解决 shared_ptr 的环状引用问题。

智能指针通过结合 RAII 设计模式和引用计数技术,提供了一种高效、安全、方便的动态内存管理机制,是现代 C++ 开发中推荐使用的重要工具之一。

🔥 weak_ptr 是否有计数方式,在那分配空间?

std::weak_ptr 是一种观测型智能指针,用于解决 std::shared_ptr 的循环引用问题,它本身并不进行内存空间分配或引用计数,而是依赖于 std::shared_ptr 来管理资源的生命周期。

std::weak_ptr 可以通过 std::lock() 成员函数获取一个 std::shared_ptr。

调用 std::lock() 成员函数可以获取一个 std::shared_ptr 对象,该对象指向 std::weak_ptr 所观测的对象(如果它仍然存在)。

如果 std::weak_ptr 过期(即其管理的对象已经被释放),std::lock() 返回一个空的 std::shared_ptr

#include <iostream> #include <memory> int main() { std::shared_ptr<int> shared = std::make_shared<int>(42); std::weak_ptr<int> weak = shared; // 使用 std::lock() 获取 std::shared_ptr std::shared_ptr<int> locked = weak.lock(); if (locked) { // 如果成功获取到 std::shared_ptr,则可以安全地使用它 std::cout << "Value pointed by shared_ptr: " << *locked << std::endl; } else { std::cout << "The shared_ptr is no longer available." << std::endl; } // 在此之后,shared_ptr 可能会超出作用域,对象被销毁 return 0; } 

🔥 类型强制转换有哪几种?

 有四种类型转换方式,它们分别是:

静态转换(static_cast)

常量转换(const_cast)

动态转换(dynamic_cast)

重新解释转换(reinterpret_cast)

🔥 函数参数传递时,指针、引用以及值传递有什么区别?

参数传递区别

 值传递:复制实参的值给形参,函数内部操作的是副本,不影响原始值。

 引用传递:形参是原始实参的别名,函数内部操作影响原始值。

 指针传递:形参是原始实参的地址,函数内部通过解引用操作影响原始值。

🔥 进程间的通信方式有那些?

各种通信方式的详细介绍,参加 线程进程以及多线程多进程 

管道
命名管道
消息队列
共享内存
信号量
套接字

🔥 线程间的通信方式有那些?

 各种通信方式的详细介绍,参加 线程进程以及多线程多进程 

共享内存

互斥锁

条件变量

🔥 简单谈谈对线程的理解,线程间共享资源时该怎么处理?

  线程的详细介绍,参加 线程进程以及多线程多进程 

线程(Thread)是操作系统调度的基本单位,是进程内的一条执行路径,每个进程至少包含一个线程。

线程间共享资源时,需要注意数据的一致性和并发访问的安全性,避免数据竞争和不可预料的结果。一般会采用 互斥锁 来避免多线程访问导致的资源调用错误。

🔥 常用关键字含义及其使用

static:关键字在不同上下文中有不同含义,主要用于:

 静态变量:属于类或函数的静态存储区域,生命周期延续到程序结束。

 静态函数:属于类的静态成员函数,与类的实例无关,可以直接通过类名访问。

const :用来声明常量。

 常量变量:一旦初始化就不能修改的变量。

 常量成员函数:在成员函数声明或定义中的 const 关键字表示该函数不会修改对象的状态。

sizeof: 是一个操作符,用于计算数据类型或变量的字节大小。

sizeof(type):返回类型或变量 type 所占的字节数。

例如:sizeof(int) 返回 int 类型的字节数,在大多数系统中是4个字节。

final:用于指示某个类、虚函数或者虚继承在派生时不可被继承或重写。

 类:final class MyClass final { ... };,表示该类不能被继承。

 虚函数:virtual void myFunc() final;,表示该虚函数在子类中不能被重写。

override:是C++11引入的关键字,用于 显式指示函数 是在派生类中覆盖了基类中的虚函数。

在派生类中重写基类的虚函数时,可以使用 override 关键字,以确保正确性和可读性。

virtual: 用于声明虚函数,即在基类中声明的函数,可以在派生类中被重写(override)

在基类中声明虚函数:virtual void foo();

派生类中可以选择重写基类的虚函数:void foo() override;

虚函数支持动态绑定,即在运行时根据对象的实际类型决定调用哪个版本的函数。

volatile: 用来声明一个变量是易变的,可能会被意外修改,通常用于多线程或者硬件相关的编程。

如:volatile int sensorValue;,告诉编译器不要对 sensorValue 进行优化,因为它可能会在程序控制之外被改变。

explicit:用来声明构造函数为显式构造函数,防止隐式类型转换。

 例如:explicit MyClass(int value);,禁止编译器根据上下文进行隐式转换,必须显式调用构造函数。

inline:声明可以用于 函数定义 或者 成员函数 定义,建议编译器将 函数的代码插入到每个调用点处,而不是通过函数调用的方式进行调用。

如:inline int add(int a, int b) { return a + b; }

extern C 的作用

用于告诉编译器按照C语言的方式进行链接,主要用于解决C++代码和C代码混合编译的问题。

define 和 typedef 的区别

define: 用于定义宏,预处理阶段替换源代码中的标识符;

如:#define PI 3.14159

typedef : 用于给数据类型取别名。后续可以直接使用别名进行声明变量。

如:typedef unsigned int UINT;    UINT num;   // 等价于unsigned int num。

🔥 const 可以修饰那些内容

变量、指针和引用、函数、类对象或类指针、函数返回值。

🔥 class 和 struct 的区别?有了class 为啥仍还保留 struct?

class 和 struct 用来定义 类 和 结构体,主要区别在于默认的访问权限不同(class 默认私有,struct 默认公有)

它们的区别主要体现在 成员默认的访问权限 和 继承方式 上:

在 class 中,默认的成员访问权限是 private,在 struct 中,默认的成员访问权限是 public

 当没有显式指定继承方式时,class 默认是 private 继承,而 struct 默认是 public 继承。

虽然 classstruct 在语法上有一些细微差别,但其实它们提供了类似的功能。保留 struct 主要是为了提供语法上的灵活性和向后兼容性。

二、标准库

🔥 什么是STL?

STL 是 C++ 标准模板库的缩写。它是 C++ 标准库的一部分,提供了一组通用的模板类和函数,实现了常用的数据结构和算法,以及一些函数对象和迭代器概念,使得 C++ 程序员可以更加高效地进行编程。

容器

如 vector(动态数组)、deque(双端队列)、list(双向链表)、set(集合)、map(映射)等,这些容器提供了高效的数据存储和访问方式,各自适用于不同的需求和使用场景。

算法

如排序、搜索、合并等,这些算法独立于任何特定的容器类型,可以利用这些算法直接对容器进行操作。

迭代器

提供了一种统一的方式来遍历容器中的元素。

函数对象

即重载了函数调用操作符 () 的对象。比如比较、排序等。

适配器

用于修改容器的接口或行为,使其能够满足特定的需求。

🔥 数组和vector的区别?

大小:数组的大小在创建时是固定的,而vector的大小是动态可变的。

内存分配:数组的内存分配在栈上(静态数组)或堆上(动态数组),而vector的内存总是动态分配的,通常在堆上。

功能vector提供了许多内置的操作,如动态扩展、插入和删除等,而数组只能直接访问元素,不支持动态调整大小。

性能vector在需要扩展时可能会重新分配内存,而数组的内存分配则不会改变。

🔥 listvector 的主要区别是什么?

存储方式: list 是一个双向链表,而 vector 是一个动态数组。

访问时间: list 不支持常数时间的随机访问,vector 支持常数时间的随机访问。

插入和删除: 在 list 中,在任意位置插入或删除元素。而在 vector 中,不太适合在中间插入或删除元素。

🔥 在什么情况下使用 list 而不是 vector

当你需要在容器的中间频繁插入和删除元素,而不是在末尾插入或删除时,list 更适合。

当你不需要随机访问元素,而是需要频繁进行插入和删除操作时,list 是更好的选择,因为它的这些操作在常数时间内完成。

🔥 set 的底层数据结构是什么?

set 通常使用红黑树或其他自平衡的二叉搜索树实现。它确保所有元素是唯一的,并且保持排序状态,因此插入、删除和查找操作的时间复杂度是对数时间 (O(log n))。

🔥 setmultiset 有什么不同?

set 中的元素是唯一的,不能有重复的元素。

multiset 允许有重复的元素。

🔥 mapunordered_map 的主要区别是什么?

底层数据结构: map 通常使用红黑树或其他自平衡的二叉搜索树实现,而 unordered_map 使用哈希表实现。

访问时间: map 提供对数时间 (O(log n)) 的查找、插入和删除操作,保持元素的排序。unordered_map 提供平均常数时间 (O(1)) 的查找、插入和删除操作,但不保持元素的排序。

排序: map 中的元素是按照键的顺序排序的,而 unordered_map 中的元素没有特定的顺序。

三、内存

🔥 RAII是什么?

RAII 的核心思想是:资源的获取(如内存、文件句柄、网络连接等)与对象的初始化绑定在一起。具体来说,资源在对象的构造函数中获取,并在对象的析构函数中释放。

🔥  使用RAII的原因以及使用方法?

使用 RAII 的原因

自动管理资源:利用对象的生命周期自动管理资源,避免忘记释放资源。

异常安全:即使发生异常,RAII 也能确保资源被正确释放,防止资源泄漏。

代码简洁:通过封装资源管理,减少代码重复和复杂性。

使用方法

定义资源管理类:创建一个类,负责资源的分配和释放。

在构造函数中分配资源:例如,分配内存、打开文件等。

在析构函数中释放资源:确保资源在对象生命周期结束时得到释放。

🔥 new 和 malloc 的区别?delete 和 free 的区别?

new:是 C++ 操作符,用于动态分配内存并调用构造函数。它会返回指向新分配内存的指针,并且可以用 delete 释放内存。new 会调用对象的构造函数初始化对象。

malloc:是 C 标准库函数,用于动态分配内存,但不调用构造函数。它返回一个 void* 指针,需要强制转换为所需类型的指针。使用 malloc 分配的内存需要用 free 释放。

delete:用于释放 new 分配的内存,并调用析构函数。对于数组使用 delete[]

free:用于释放 malloc 分配的内存。free 不会调用析构函数。

int* p = new int(10); // 使用 new delete p; // 使用 delete int* q = (int*)malloc(sizeof(int)); // 使用 malloc free(q); // 使用 free 

🔥 C++的内存框架分布情况

C++ 的内存框架主要包括以下几个区域:

栈(Stack):用于存储局部变量和函数调用信息。栈内存分配速度快,但空间有限。

堆(Heap):用于动态分配内存。程序员可以控制分配和释放内存,但需要显式管理。内存的生命周期由程序员控制。

静态存储区(Static Storage Area):用于存储静态变量和全局变量。这些变量在程序运行期间一直存在。

常量区(Constant Data Area):用于存储常量数据,如字符串常量。

🔥 堆和栈的区别?

分配方式自动分配和释放手动分配和释放(使用 new/malloc 和 delete/free)。
生命周期函数调用时分配,函数返回时释放。由程序员控制。
大小通常较小(受限于栈大小)。通常较大(受限于系统可用内存)。
速度较快。较慢(需要管理和合并空闲块)。
用途局部变量、函数调用信息。动态内存分配。

🔥 什么是内存对齐,内存对齐有什么好处?

内存对齐 是将数据结构的内存地址按某个特定的对齐边界(通常是数据类型的大小)进行排列。对齐可以提高内存访问效率,因为许多处理器在对齐的数据上访问更快。

好处

提高性能:对齐的数据结构能更有效地利用缓存和减少内存访问时间。

防止错误:某些硬件架构要求数据必须对齐,否则会引发硬件异常或性能下降。

🔥 什么是内存泄漏?写代码时应该怎么防止内存泄漏。

内存泄漏 是指程序在动态分配内存后,没有释放这些内存,导致系统资源浪费并最终可能导致程序崩溃或变慢。

防止内存泄漏的方法

使用 RAII:通过对象的生命周期自动管理资源。

智能指针:使用 std::unique_ptr 和 std::shared_ptr 管理动态内存,避免手动释放内存。

内存分析工具:使用工具如 Valgrind、AddressSanitizer 等检测和分析内存泄漏。

良好的编码实践:确保每个 new 都有对应的 delete,每个 malloc 都有对应的 free

🔥 说说什么是深拷贝和浅拷贝?

浅拷贝

定义:复制对象时,只复制对象的指针值,而不复制指针指向的内容。结果是原始对象和拷贝对象共享相同的资源。

问题:修改一个对象的资源会影响到另一个对象,可能导致资源冲突和错误。

深拷贝

定义:复制对象时,创建对象指针指向的资源的完整副本。这样,原始对象和拷贝对象各自拥有自己的资源。

好处:避免共享资源带来的问题,保证对象的独立性。

四、类相关

🔥 讲讲C++的三大特性(封装、继承、多态)

封装:将数据(成员变量)和对数据的操作(成员函数)捆绑在一起,并对外部隐藏数据的具体实现。

继承:通过继承,子类可以继承父类的属性和行为,从而实现代码的重用和扩展。

多态:允许不同的对象以相同的接口进行操作,即同一操作可以作用于不同的对象上,表现出不同的行为。

🔥 多态是怎么实现的? 

多态的实现主要依赖于虚函数和动态绑定。多态允许程序在运行时决定调用哪个具体的函数实现。

运行时多态:通过使用虚函数实现,允许基类指针或引用调用派生类中的重写函数。这是通过虚函数表(vtable)和虚指针(vptr)机制实现的。

🔥 什么是虚函数和纯虚函数?以及区别?

虚函数

定义:在基类中声明为 virtual 的成员函数,允许派生类重写(override)该函数。虚函数实现运行时多态。

作用:通过基类指针或引用调用虚函数时,实际调用的是派生类中的重写函数。

纯虚函数

定义:在基类中声明为 virtual 并且等于 0 的虚函数,例如 virtual void func() = 0;

作用:定义接口,要求所有派生类必须实现纯虚函数。使得基类成为抽象类,不能实例化。

🔥 虚函数是怎么实现的?虚函数在那个阶段生成?

虚函数表(vtable):每个类(如果包含虚函数)会有一个虚函数表,它是一个指向虚函数实现的指针数组。每个对象会有一个指向虚函数表的指针(vptr)。

动态绑定:通过 vptr 指针,运行时可以查找虚函数表中的具体函数地址,决定调用哪个函数实现。

生成阶段:

编译阶段:编译器生成虚函数表和虚指针。虚函数表在类定义时生成,虚指针在对象实例化时设置。

🔥 构造函数和析构函数的作用?

构造函数

作用:在创建对象时初始化对象的状态。构造函数可以用于分配资源、初始化数据成员等。

特性:构造函数与类名相同,无返回值,可以被重载。

析构函数

作用:在对象生命周期结束时清理对象的资源。析构函数负责释放构造函数分配的资源,如内存、文件句柄等。

特性:析构函数的名字前加 ~,无返回值,不能被重载。

🔥 构造函数和析构函数那个可以被声明为虚函数,为什么?

析构函数可以被声明为虚函数:如果基类的析构函数是虚函数,确保派生类的析构函数在基类指针或引用被销毁时正确调用。防止资源泄漏和不完全析构。

构造函数不能被声明为虚函数:构造函数在对象创建时调用,此时对象还未完全构造,无法设置虚函数表,因此构造函数不能是虚函数。

🔥 构造函数和析构函数那个可以被重载,为什么?

析构函数不可以被重载:每个类只能有一个析构函数,负责对象生命周期结束时的清理操作。如果允许重载,会导致析构时不确定性。

构造函数可以被重载:允许创建对象时通过不同的参数进行初始化。通过重载构造函数,可以提供多种初始化方式。

🔥 谈谈什么是重载、重写、隐藏?

重载:同一作用域内多个函数名相同但参数列表不同。

覆盖:派生类重写基类的虚函数,提供特定实现。

重定义:派生类中定义了与基类同名但参数列表不同的函数,导致基类函数不可见。

🔥 this 指针,为什么会存在this指针?

定义this 指针是指向当前对象的指针。在类的成员函数内部,this 指针指向调用该函数的对象。

访问对象成员:允许成员函数访问对象的属性和其他成员函数。

区分成员变量和参数:在成员函数中,this 指针可以用来区分同名的成员变量和函数参数。

实现链式调用:可以返回 *this 实现链式调用。

五、其他必备

🔥 什么是git? 以及git常用命令

Git 是一个分布式版本控制系统,用于跟踪文件的更改,特别是源代码。它允许多人协作开发,通过分支和合并功能管理项目的不同版本和特性。

git常用命令:

  • git clone [url]:克隆远程仓库到本地。
  • git add [file]:将文件添加到暂存区。
  • git commit -m "message":提交更改到本地仓库,-m 用于添加提交信息。
  • git status:查看工作目录和暂存区的状态。
  • git diff:查看文件的更改内容。
  • git remote add [name] [url]:添加远程仓库。
  • git fetch [remote]:从远程仓库获取最新的更改,但不合并。
  • git pull:从远程仓库获取并合并更改。
  • git push:将本地提交推送到远程仓库。
  • git log:查看提交历史。
  • git revert [commit]:撤销指定提交,但保留历史记录。
  • git reset [commit]:重置当前分支到指定提交。

🔥 什么是svn? git 和 svn的区别

SVN 是一个集中式版本控制系统,用于跟踪文件的更改和管理版本。与 Git 不同,SVN 采用集中式存储模式,所有版本历史都存储在中央服务器上。

git 一般用于存放代码,svn一般用于存放文档等。

🔥Linux 系统下常用的命令会那些,举例?

  • ls:列出目录内容。
  • cd [dir]:切换目录。
  • pwd:显示当前工作目录。
  • cp [source] [destination]:复制文件或目录。
  • mv [source] [destination]:移动文件或目录。
  • rm [file]:删除文件,rm -r [dir] 删除目录及其内容。
  • cat [file]:显示文件内容。
  • vim [file]:使用 Vim 编辑器编辑文件。
  • chmod [permissions] [file]:改变文件权限。
  • chown [owner]:[group] [file]:改变文件所有者和组。
  • ps:显示当前进程。
  • top:实时显示系统进程和资源使用情况。
  • df -h:显示文件系统的磁盘使用情况。
  • du -sh [dir]:显示目录的大小。
  • kill [pid]:结束进程,pid 是进程ID。
  • ping [host]:测试网络连接。
  • ifconfig 或 ip a:显示网络接口信息。
  • netstat:显示网络状态和端口使用情况。

Read more

Python + BS4实战:手把手带你爬取商业数据

Python + BS4实战:手把手带你爬取商业数据

目录 一、bs4篇 1.bs4介绍 1.1 什么是BeautifulSoup4? 1.2 为什么选择BeautifulSoup4?       核心优势 2.bs4详解 2.1 首先下载bs4 2.2 接下来引入一个使用bs4的例子让我们快速熟悉它 2.3 运行结果 3.bs4使用实战案例 3.1 完整代码 3.2 为什么会影响翻页 3.3 反爬机制 3.4 已知信息 3.5 解决思路 3.6 结果展示 3.7 容易混淆的一点 3.8 图片爬虫 🌟 Hello,

By Ne0inhk
Python + AI:打造你的智能害虫识别助手

Python + AI:打造你的智能害虫识别助手

Python + AI:打造你的智能害虫识别助手 在农业生产中,病虫害是影响作物产量和品质的“隐形杀手”。传统的害虫识别依赖人工巡查,不仅耗时耗力,还容易因经验不足导致误判、漏判。而随着智慧农业的普及,AI技术正成为破解这一难题的关键——今天,我们就用Python从零搭建一个智能害虫识别助手,让电脑替你“火眼金睛”辨害虫,轻松搞定农作物病虫害预警! 一、为什么要做这个项目? 智慧农业的核心是“精准、高效、低成本”,而害虫识别正是其中的典型场景: * 对农户:无需专业植保知识,拍照就能识别害虫种类,快速匹配防治方案; * 对开发者:这是一个“小而美”的实战项目,覆盖AI开发全流程,从数据处理到模型部署,学完就能落地; * 技术价值:融合Python、深度学习、Web部署,是入门AI+垂直领域应用的绝佳案例。 这个项目不需要你有深厚的AI功底,只要掌握Python基础,跟着步骤走,就能做出一个能实际使用的智能识别工具。 二、项目核心技术栈 先明确我们要用到的工具,都是行业主流、

By Ne0inhk
C++中的父继子承:继承方式实现栈及同名隐藏和函数重载的本质区别, 派生类的4个默认成员函数

C++中的父继子承:继承方式实现栈及同名隐藏和函数重载的本质区别, 派生类的4个默认成员函数

🎬 胖咕噜的稞达鸭:个人主页 🔥 个人专栏: 《数据结构》《C++初阶高阶》《算法入门》 ⛺️技术的杠杆,撬动整个世界! 学习完本文,你将知道:(各位大佬预知答案几何请移步文章结尾!) 1. 当子类继承了父类,父类的私有成员在子类中是不可见的,所以父类的私有成员在子类中有没有被继承下来? 2. 子类对象一定比父类大? 3. 函数重载和函数隐藏的区别是什么?同名了有什么影响? 4. 派生类构造函数初始化列表的位置必须显式调用基类的构造函数,已完成基类部分成员的初始化? 5. 派生类构造函数先初始化子类成员,再初始化基类成员?派生类对象构造函数先调用子类构造函数,在调用基类构造函数? 接着来步入今天的正文: 面向对象三大特性:封装,继承,多态 我们之前学过了封装,类的定义是一个封装,迭代器实现也是一个封装,屏蔽了底层的实现细节。模板的使用也是一个封装。接下来讲解面向对象第二大特性:继承。 继承的定义: 假设大学学生和大学的老师,作为一个人的共性,都有姓名,住址和电话号码,但是不同的是,老师授课有职称,学生有学号,这是老师和学生不同的地方。

By Ne0inhk

Clang 17正式发布:C++26十大新特性你必须马上掌握

第一章:Clang 17正式发布:C++26新特性的整体概览 Clang 17 的正式发布标志着对 C++26 标准早期特性的全面支持迈出了关键一步。作为 LLVM 项目的重要组成部分,Clang 17 不仅提升了编译性能与诊断能力,更率先实现了多项处于提案阶段的 C++26 核心语言特性,为开发者提供了前沿的实验平台。 核心语言特性的演进 C++26 正在推进一系列旨在提升代码简洁性与安全性的变更。Clang 17 已初步支持以下关键特性: * 类模板参数推导(CTAD)在别名模板中的扩展应用 * 隐式移动的进一步放宽规则,减少不必要的拷贝操作 * 基于范围的循环支持初始化语句(类似 if 和 switch 的 init-statement) 模块化系统的增强 Clang 17 深化了对 C++20 模块的支持,并为 C+

By Ne0inhk