C++面试问题总结

技术问题:
描述一下通信握手和挥手过程

三次握手、四次挥手、请求确认机制
client发送请求,序号seq为x  SYN到Server
Server收到请求,发送响应,响应内容 请求确认回复,请求确认序号ack=x+1,当前序号seq为y
client收到,发送请求确认回复,请求确认序号为ack=y+1
连接建立

3次握手而不是两次握手:保证可靠性传输,通信双方都拥有对方的序列号信息

 socket模型原理,以TCP为例

补充:应该都有recv(),和send(),图示这里稍微不准。

请问动态链接库和静态链接库的区别,Linux和windows下有什么不同表现

        动态链接库和静态链接库是两种不同的代码库形式,它们的主要区别在于如何将库的代码与应用程序进行绑定。

动态链接库
动态链接库(Dynamic Linking Library),简称DLL,在Windows操作系统中常见。它是一些可执行代码的集合,能够被多个应用程序共享。这些可执行代码会在运行时由操作系统加载到内存中,当应用程序需要调用其中的函数或方法时,就可以直接通过指针访问动态链接库中的相关代码。
Linux系统中动态链接库通常以.so文件的形式存在,而在Windows系统中则以.dll文件的形式存在。相比静态链接库,动态链接库的优点在于:
节省了磁盘空间,因为不同的应用程序可以共享同一个动态链接库;
减少了代码冗余,因为动态链接库中的代码只需要编译一次,即可供多个应用程序使用;
更方便地进行升级和维护,因为只需要更新动态链接库本身即可,不需要重新编译应用程序。
静态链接库
静态链接库(Static Linking Library),简称LIB,在Windows和Linux操作系统中都常见。它是一些可执行代码的集合,能够被一个应用程序独立引用。这些可执行代码会在编译时被链接到应用程序中,
Linux系统中静态链接库通常以.a文件的形式存在,而在Windows系统中则以.lib文件的形式存在。相比动态链接库,静态链接库的优点在于:
应用程序可以独立运行,不需要依赖于其他动态链接库的存在;
调用时速度更快,因为函数或方法的地址已经被编译进了应用程序中,不需要在运行时进行查找。
Linux和Windows的表现
在Linux系统中,动态链接库和静态链接库都得到了广泛的应用。通常情况下,Linux系统中的可执行文件默认会使用动态链接库,在编译时需要指定-l选项来链接相关的动态链接库。如果想使用静态链接库,则需要在编译时指定-lstatic选项。
在Windows系统中,由于DLL的广泛应用,动态链接库得到了大量的应用和支持。与此相对,静态链接库的应用较少。Windows系统中的可执行文件默认会使用动态链接库,在编译时需要指定/dynamicbase选项来启用动态链接库。如果想使用静态链接库,则需要在编译时指定/MT或者/MTd选项。

表达式相关

重载运算符:
为已经存在的运算符赋予了另外一层含义。

左值、右值:
当一个对象用作右值得时候,用的是对象的值(内容)。

条件运算符:
condition ? expression1 : expression2;

const修饰符的作用

        const类型定义: 指明变量或对象的值是不能被更新,引入目的是为了取代预编译指令
可以保护被修饰的东西,防止意外的修改,增强程序的健壮性
编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高
可以节省空间,避免不必要的内存分配
修饰指针本身的const称为顶层const,修饰指针所指向对象的const称为底层const
1. const与指针

        常量指针指向的值不能改变,但是这并不是意味着指针本身不能改变,常量指针可以指向其他的地址。说白了还是个指针,遵循指针的基本原则,指向的内容不变,指针指向可变;
2. const修饰引用

const 引用的目的是,禁止通过修改引用值来改变被引用的对象。
1)const int & e 相当于 const int * const e

2)普通引用 相当于 int *const e
3)当使用常量(字面量)对const引用进行初始化时,C++编译器会为常量值
3.const修饰函数

        函数参数:同理修饰指针对象,如果函数体视图改动内容,编译器报错;修饰对象参数,则地址以及内容都不能修改;

Q:那如果同时定义了两个函数,一个带const,一个不带,会有问题吗?

A:不会,这相当于函数的重载。

        

引用与指针有什么区别?
指针和引用都是地址的概念,指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。
1、程序为指针变量分配内存区域,而不为引用分配内存区域。

2、指针使用时要在前加 * ,引用可以直接使用。

3、引用在定义时就被初始化,之后无法改变;指针可以发生改变。 即引用的对象不能改变,指针的对象可以改变。

4、没有空引用,但有空指针。这使得使用引用的代码效率比使用指针的更高。因为在使用引用之前不需要测试它的合法性。相反,指针则应该总是被测试,防止其为空。

5、对引用使用“sizeof”得到的是变量的大小,对指针使用“sizeof”得到的是变量的地址的大小

6、理论上指针的级数没有限制,但引用只有一级。即不存在引用的引用,但可以有指针的指针。
int **p //合法
int &&p 表示右值引用

++引用与++指针的效果不一样。
例如就++操作而言,对引用的操作直接反应到所指向的对象,而不是改变指向;而对指针的操作,会使指针指向下一个对象,而不是改变所指对象的内容。

强制类型转换

四种强制类型转换介绍:
static_cast:基本数据类型之间的转换、基类和派生类之间指针或引用的转换

static_cast 进行上行转换是安全的,即把派生类的指针转换为基类的;??安全:上行转换(只能调用基类的方法和成员变量)
static_cast 进行下行转换是不安全的,即把基类的指针转换为派生类的。??安全:基类没有派生类的任何信息,而下行转换后会用到派生类的方法和成员变量

dynamic_cast 基类没有派生类的任何信息,而下行转换后会用到派生类的方法和成员变量,安全性高
const_cast  去const属性
reinterpret_cast 转换不相干类型

内存的分区图:

        内存分为四个区域:栈区(堆栈),堆区,全局静态区,只读区(常量区和代码区)

栈区:存放函数的参数值、局部变量等,由编译器自动分配和释放,通常在函数执行完后就释放了。

堆区:通过new,malloc,realloc分配的内存块,编译器不负责自动分配和是否,内存泄漏通常就是说的堆区。

数据段分3段,分3段(bass,data,rodata)前面两段属于全局静态区,后一段属于只读数据(常量)

全局静态区:全局变量和静态变量存放位置;.bass存放未初始化的全局变量和静态变量,.data存放已经初始化的静态数据。

只读区:.rodata存放只读数据-常量。

代码区:存放代码

左值和右值

当对象被用作左值得时候,用的是对象的身份(在内存中的位置)。

右值表示是对象的实体;


顺序容器概述一下,你所知道的全部顺序容器

| 类型 | 作用 |
| ------------ | ------------------------------------------------------------ |
| vector | 可变数组大小。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢。 |
| deque | 双端队列。支持快速随机访问。在头尾位置插入/删除速度很快。 |
| list | 双向链表。只支持双向顺序访问。在list中任何位置进行插入/删除操作速度都很快。 |
| forward_list | 单向链表。只支持单向顺序访问。在链表任何位置进行插入/删除操作速度都很快。 |
| array | 固定大小数组。支持快速随机访问。不能添加或删除元素。 |
| string | 与vector相似的容器,但专门用于保存字符、随机访问快。在尾部插入/删除速度快。 |
关联容器概述一下,你所知道的全部关联容器

关联容器支持高效的关键字查找和访问。

| 类型 | 备注 |
| ------------------ | --------------------------------- |
| map | 关联数组,保存关键字-值对 |
| set | 值保存关键字的容器 |
| multimap | 关键字可重复出现的map |
| multiset | 关键字可重复出现的set |
| unordered_map | 用哈希函数组织的map |
| unordered_set | 用哈希函数组织的set |
| unordered_multimap | 哈希组织的map;关键字可以重复出现 |
| unordered_multiset | 哈希组织的set;关键字可以重复出现 |


1、什么是内存泄漏?面对内存泄漏和指针越界,你有哪些方法?你通常采用哪些方法来避免和减少这类错误?


答:用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元即为内存泄露。
使用的时候要记得指针的长度。
malloc的时候得确定在那里free.
对指针赋值的时候应该注意被赋值指针需要不需要释放.
动态分配内存的指针最好不要再次赋值.


2、问1:同步IO和异步IO的区别?

答:
A. 同步
所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。
按照这个定义,其实绝大多数函数都是同步调用(例如sin isdigit等)。
但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。
最常见的例子就是 SendMessage。
该函数发送一个消息给某个窗口,在对方处理完消息之前,这个函数不返回。
当对方处理完毕以后,该函数才把消息处理函数所返回的值返回给调用者。
B. 异步
异步的概念和同步相对。
当一个异步过程调用发出后,调用者不会立刻得到结果。
实际处理这个调用的部件是在调用发出后,通过状态、通知来通知调用者,或通过回调函数处理这个调用。

4、程序编译的过程?

程序编译的过程中就是将用户的文本形式的源代码(c/c++)转化成计算机可以直接执行的机器代码的过程。主要经过四个过程:预处理、编译、汇编和链接。

5、STL库用过吗?常见的STL容器有哪些?算法用过几个?

STL包括两部分内容:容器和算法
容器即存放数据的地方,比如array, vector,分为两类,序列式容器和关联式容器
序列式容器,其中的元素不一定有序,但是都可以被排序,比如vector,list,queue,stack,heap, priority-queue, slist
关联式容器,内部结构是一个平衡二叉树,每个元素都有一个键值和一个实值,比如map, set, hashtable, hash_set
算法有排序,复制等,以及各个容器特定的算法
迭代器是STL的精髓,迭代器提供了一种方法,使得它能够按照顺序访问某个容器所含的各个元素,但无需暴露该容器的内部结构,它将容器和算法分开,让二者独立设计。

6、什么是虚函数?什么时候使用虚函数?
7、什么是多态?如何实现多态?
8、什么是纯虚函数?如何定义和使用纯虚函数?
9、什么是模板类?什么是模板函数?如何使用模板?
10、什么是智能指针?有哪些类型的智能指针?如何使用智能指针?
11、什么是 RAII?如何使用 RAII 模式来管理资源?
12、C++ 中的 const 关键字有什么作用?const 对象可以调用非 const 成员函数吗?
13、什么是左值和右值?什么是引用折叠?
14、什么是移动语义?为什么需要移动语义?如何实现移动语义?
15、什么是 lambda 表达式?如何定义和使用 lambda 表达式?
16、什么是 noexcept 关键字?它有什么作用?
17、C++11 引入了哪些新特性?C++14 和 C++17 又带来了哪些新特性?
18、什么是迭代器?STL 中提供了哪些类型的迭代器?如何使用迭代器?
19、什么是多线程?C++11 提供了哪些多线程相关的工具和语法?
20、什么是虚拟继承?为什么需要虚拟继承?
21、什么是模板元编程(TMP)?如何使用 TMP 实现编译器优化和元算法?
22、C++ 中的指针和引用有什么区别?如何正确使用指针和引用?
23、什么是模板元类(Template Metaclass)?它可以用来做什么?
24、什么是类型擦除(Type Erasure)?STL 中的任何容器都使用了类型擦除吗?
25、什么是 static_cast、dynamic_cast、const_cast 和 reinterpret_cast?它们之间有什么区别和限制?

什么是虚函数?什么时候使用虚函数?
虚函数是在基类中声明的带有 virtual 关键字的成员函数,可以被子类覆盖并实现多态性。使用虚函数可以实现动态绑定、运行时多态和接口隔离等功能。通常在具体的实例化对象和模板类中使用虚函数。

什么是多态?如何实现多态?
多态指的是同一类型或基类的不同实例对象对同一消息作出不同的响应。多态可以通过虚函数和纯虚函数实现,以及通过重载运算符和模板函数等技术实现。多态可以提高代码的可读性、灵活性和可维护性。

什么是纯虚函数?如何定义和使用纯虚函数?
纯虚函数是在基类中声明的没有任何实现代码的虚函数,用于定义一个接口规范或协议,要求其所有派生类必须实现该函数。纯虚函数可以使用 "= 0" 来表示其没有实现代码。在 C++ 中,如果包含纯虚函数的类称为抽象类,不能直接创建其实例对象,只能通过其派生类来创建对象。

综合问题:什么是动态多态,什么是静态多态?
静态多态:也称为编译期间的多态,编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误。
静态多态有两种实现方式:
函数重载:包括普通函数的重载和成员函数的重载
函数模板的使用
动态多态(动态绑定):即运行时的多态,在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。
动态绑定
1.通过基类类型的引用或者指针调用虚函数
2.必须是虚函数(派生类一定要重写基类中的虚函数),纯虚函数

什么是模板类?什么是模板函数?如何使用模板?
        模板类和模板函数是一种基于泛型编程的技术,可以在不知道具体类型和参数的情况下定义通用算法和数据结构。模板类和模板函数通常以 template 关键字开头,使用<>来包含类型参数或非类型参数,并在需要时使用特化和偏特化等技术处理具体的类型和参数。使用模板可以提高代码的复用性和可扩展性。

什么是智能指针?有哪些类型的智能指针?如何使用智能指针?
智能指针是一个带有自动内存管理的指针,可以避免内存泄漏和悬挂指针等问题。C++11 中引入了 std::shared_ptr 和 std::unique_ptr 两种类型的智能指针,分别实现了共享所有权和独占所有权的语义。使用智能指针可以方便地管理动态分配的对象,并避免手动删除和释放内存。

什么是 RAII?如何使用 RAII 模式来管理资源?
RAII(Resource Acquisition Is Initialization)是一种基于对象生命周期管理资源的技术,它利用 C++ 对象的构造和析构函数来自动获取和释放资源。通常使用智能指针、文件句柄和锁等对象来实现 RAII 模式,并尽量避免使用裸指针和手动内存分配等操作。

C++ 中的 const 关键字有什么作用?const 对象可以调用非 const 成员函数吗?
const 关键字可以修饰变量、函数参数和成员函数,表示其值或状态不可改变。const 对象只能调用 const 成员函数,而不能调用非 const 成员函数,因为后者可能会修改对象的状态或值。通过使用 const 可以提高代码的可读性、安全性和可维护性。

什么是左值和右值?什么是引用折叠?
左值是指在程序中可以取地址的表达式,即具有名称和内存地址的变量或对象。左值通常能够被赋值、修改和传递给函数等操作。例如,一个普通变量、数组元素、结构体成员和类对象等都是左值。
右值是指在程序中不能取地址的表达式,即临时的、无法修改的或无法获取其地址的对象。右值通常只能作为值进行计算、传递和返回,不能对其进行赋值、修改和取地址等操作。例如,一个字面常量、表达式的结果、函数返回值和转换操作等都是右值。

引用折叠是一种 C++11 中的新特性,它允许将左值引用和右值引用“合并”成一种类型即万能引用(universal reference)。当使用 && 符号声明变量时,如果右侧是左值,则会自动转换为左值引用;如果右侧是右值,则会自动转换为右值引用。

什么是移动语义?为什么需要移动语义?如何实现移动语义?
移动语义是 C++11 中的一个新特性,它允许将对象的资源(如内存、文件句柄和网络连接等)从一个对象“移动”到另一个对象,而不是复制这些资源。移动语义可以提高程序的效率、减少资源消耗和避免不必要的拷贝,尤其对于大型对象和容器来说更为显著。移动语义可以通过“右值引用”和 std::move() 等机制来实现,在移动时不会造成资源泄漏或悬挂指针等问题。

什么是 lambda 表达式?如何定义和使用 lambda 表达式?
Lambda 表达式是 C++11 中的一个新特性,它允许在代码中创建轻量级的匿名函数,以便进行一些简单的计算、操作和转换等功能。Lambda 表达式通常使用 [] 符号开头,包含一个参数列表、一个可选的 mutable 关键字、一个返回值类型和一个函数体,可以像普通函数一样调用或传递。Lambda 表达式可以捕获外部变量,并支持多种捕获方式,例如值捕获[=]、引用捕获[&]和隐式捕获[ ]等。Lambda 表达式可以方便地用于 STL 算法、线程池和 GUI 编程等场景。

什么是 noexcept 关键字?它有什么作用?
noexcept 关键字是 C++11 中的一个新特性,用于指示函数是否可能抛出异常。如果函数声明为 noexcept,则表示其保证不会抛出任何异常,否则表示其可能抛出异常或调用其他可能抛出异常的函数。noexcept 关键字可以提高程序的性能和可靠性,允许编译器进行一些优化和代码生成,在编写高效且安全的代码时非常有用。

C++11 引入了哪些新特性?C++14 和 C++17 又带来了哪些新特性?
C++11 引入了大量新特性,其中最重要的包括:
智能指针:std::shared_ptr 和 std::unique_ptr 等智能指针用于管理内存资源,可以避免内存泄漏和悬挂指针等问题;
移动语义:右值引用和 std::move() 等机制用于实现对象的“移动”,大幅提高程序的效率和资源利用率;
Lambda 表达式:匿名函数可以方便地用于 STL 算法、线程池和 GUI 编程等场景,大幅简化代码和提高可读性;
std::thread 和 std::mutex 等多线程库:支持并发编程和锁机制,大幅提高程序的并行度和响应速度;
constexpr 关键字和模板元编程:允许在编译时进行计算和优化,大幅提高程序的执行速度和性能;
新增类型和容器:如 std::array 和 std::tuple 等类型,以及 std::unordered_map 和 std::unordered_set 等容器。
C++14 和 C++17 继续引入了许多新特性,例如:

二进制字面量:允许使用 0b 或 0B 前缀表示二进制数字,方便编写位操作和掩码;
变长模板参数和折叠表达式:允许模板参数数量不定,以及使用 ... 和逗号运算符等语法进行展开;
constexpr 函数和 if constexpr 语句:支持在编译时进行条件判断和计算,使某些函数可以被内联、优化和放在头文件中使用;
std::optional 和 std::variant 等新类型:用于表示可选值和多态对象,提高代码的灵活性和安全性;
std::any 和 std::filesystem 等新库:用于处理任意类型的对象和文件系统操作,方便开发者编写更加通用和健壮的程序。
这些新特性都可以提高程序的效率、灵活性和安全性,但也需要开发者适应并掌握这些新技术,从而编写出高质量的 C++ 程序。

什么是迭代器?STL 中提供了哪些类型的迭代器?如何使用迭代器?
迭代器是 C++ STL(Standard Template Library)中的概念,用于遍历和操作容器中的元素。迭代器本质上类似于指针,是一个特殊类型的对象,提供了一种统一的访问容器元素的接口。STL 中提供了多种类型的迭代器,包括输入迭代器、输出迭代器、前向迭代器、双向迭代器和随机访问迭代器等。不同类型的迭代器支持的操作和运算符也有所不同。使用迭代器可以方便地对容器进行遍历、查找、排序和修改等操作,是 C++ 中常用的编程技巧之一。

什么是多线程?C++11 提供了哪些多线程相关的工具和语法?
        

        多线程是一种并发编程的技术,可以同时执行多个任务或线程,提高程序的响应速度和并行度。C++11 提供了多种多线程相关的工具和语法,如 std::thread 和 std::mutex 等多线程库,以及 lambda 表达式和 std::async 等语法。std::thread 可以创建新的线程,并通过 join() 或 detach() 等方法管理线程的执行;std::mutex 和 std::lock_guard 可以提供锁机制,保证多个线程之间的互斥访问;lambda 表达式和 std::async 可以方便地创建并发任务和线程池,提高程序的并行度和效率。使用多线程需要注意线程的同步和互斥问题,以及避免死锁和竞争条件等问题。
写多线程程序时,可能会存在以下问题:
竞态条件(Race Condition):多个线程同时访问共享资源,并试图在某一时刻修改同一个变量的值,从而导致不可预料的结果。解决方法是使用锁机制,例如 std::mutex 和 std::lock_guard。
死锁(Deadlock):两个或多个线程互相持有对方需要的资源,并无限等待彼此释放资源,从而陷入死循环。解决方法是使用互斥量(Mutex)和条件变量(Condition Variable),并保证所有线程以相同的顺序获取锁。
饥饿(Starvation):某些线程无法获得所需的资源,因为其他线程一直占用着它们,并且不释放资源。解决方法是使用公平锁机制,例如 std::shared_mutex 和 std::condition_variable_any。
并发容器的线程安全性:STL 中的标准容器(例如 vector 和 map)并不是线程安全的,如果多个线程同时修改同一个容器,可能会导致数据损坏和崩溃。解决方法是使用线程安全的容器,例如 std::vector<std::atomic<int>> 和 std::shared_ptr<std::map<int, int>>。
线程池的负载均衡:线程池中的每个线程可能执行的任务数量不同,可能会导致某些线程一直处于高负载状态,而其他线程却一直空闲。解决方法是使用动态调整线程池大小的算法,并实现负载均衡策略。
为了解决这些问题,可以采用以下方法:
使用并发编程库,例如 Boost 和 Poco 等,它们提供了丰富的多线程和异步操作支持,以及高效的并发容器和锁机制等。
使用 C++11 中提供的多线程库,例如 std::thread 和 std::async 等,可以方便地创建线程和执行异步任务,并利用 lambda 表达式和函数对象等技术来简化代码。
在编写多线程程序时,要注意避免竞态条件、死锁和饥饿等问题,尽可能保证线程安全和可靠性。
选择合适的数据结构和算法,以最大限度地减少对共享资源的访问,并降低并发开销和延迟。
对于大规模的多线程程序,可以考虑使用分布式计算和云计算等技术,将任务分配到多个节点上,从而提高程序的扩展性和弹性。

标准线程库中detach和join的区别

在声明一个std::thread对象之后,都可以使用detach和join函数来启动被调线程,区别在于两者是否阻塞主调线程。
(1)当使用join()函数时,主调线程阻塞,等待被调线程终止,然后主调线程回收被调线程资源,并继续运行;
(2)当使用detach()函数时,主调线程继续运行,被调线程驻留后台运行,主调线程无法再取得该被调线程的控制权。当主调线程结束时,由运行时库负责清理与被调线程相关的资源。
当thread::join()函数被调用后,调用它的线程会被block,直到线程的执行被完成。基本上,这是一种可以用来知道一个线程已结束的机制。当thread::join()返回时,OS的执行的线程已经完成,C++线程对象可以被销毁。
当thread::detach()函数被调用后,执行的线程从线程对象中被分离,已不再被一个线程对象所表达--这是两个独立的事情。C++线程对象可以被销毁,同时OS执行的线程可以继续。如果程序想要知道执行的线程何时结束,就需要一些其它的机制。join()函数在那个thread对象上不能再被调用,因为它已经不再和一个执行的线程相关联。
去销毁一个仍然可以“joinable”的C++线程对象会被认为是一种错误。为了销毁一个C++线程对象,要么join()函数需要被调用(并结束),要么detach()函数被调用。如果一个C++线程对象当销毁时仍然可以被join,异常会被抛出。


虚拟继承(Virtual Inheritance)是一种 C++ 中的继承方式,用于解决菱形继承(Diamond Inheritance)带来的二义性和资源浪费问题。虚拟继承可以将公共基类在继承链中只保留一份实例,而不是每个派生类都拷贝一份,从而减少内存开销和数据冗余。虚拟继承通过 virtual 关键字来声明,使得派生类中的虚基类成员变量只被初始化一次,而不是多次。由于虚拟继承涉及到复杂的指针偏移和内存布局等问题,因此需要谨慎使用,并避免出现编译时或运行时错误。

21、什么是模板元编程(TMP)?如何使用 TMP 实现编译器优化和元算法?
模板元编程(TMP)是一种 C++ 中的编程技术,使用模板和编译时计算来实现一些复杂的算法和优化。TMP 可以利用 C++ 编译器的强大优化能力,将某些计算在编译时完成,避免运行时的计算和开销。例如,通过使用 constexpr 函数和模板元函数等技术,可以实现数学计算、类型推导、序列操作和布尔逻辑等功能。TMP 虽然强大,但也很复杂,需要具有较高的编程能力和经验才能正确、高效地使用。

22、C++ 中的指针和引用有什么区别?如何正确使用指针和引用?
指针和引用是 C++ 中的两种重要的数据类型,它们有以下几个区别:
指针是一个变量,存储着某个对象的内存地址,可以对该对象进行间接访问、修改和删除等操作。而引用则是某个变量或对象的别名,表示与其绑定在一起,可以像原来的变量一样使用它。
指针需要使用 * 号进行解引用,获取其所指向对象的值。而引用直接使用即可,不需要解引用操作。
指针可以被重新赋值为其他对象的地址,从而指向不同的对象。而引用不能被重新绑定到其他对象,只能在初始化时确定。
在使用指针和引用时,需要注意避免空指针和野指针的问题,并确保不会越界访问数组和容器等数据结构。指针和引用也常用于函数参数传递和返回值传递等场景。    

23、什么是模板元类(Template Metaclass)?它可以用来做什么?
模板元类(Template Metaclass)是一种 C++20 中新引入的语言特性,可以将类作为模板参数传递,从而实现更灵活的编码技巧。模板元类可以用于实现反射、代码生成和元编程等功能,例如实现序列化和分析数据结构等。模板元类可以使用关键字 class 来声明,同时还需要指定一个元类的名称。模板元类的语法相对较为复杂,需要具有较高的 C++ 编程能力和经验才能正确使用。
模板元类可以用于实现反射、代码生成和元编程等功能,下面分别给出序列化和分析数据结构的示例:
序列化
序列化是将对象转换为字节流或文本流的过程,用于存储、传输和恢复数据。模板元类可以实现自动序列化,从而避免手动编写序列化代码时的繁琐和重复。以下是一个简单的序列化模板元类的示例:
#include <iostream>
#include <sstream>

template <typename T>
class Serializer {
public:
    static std::string serialize(const T& object) {
        std::stringstream stream;
        serialize(stream, object);
        return stream.str();
    }

    static void serialize(std::ostream& stream, const T& object) {
        stream.write(reinterpret_cast<const char*>(&object), sizeof(T));
    }
};

struct Person {
    std::string name;
    int age;
};

int main() {
    Person person{"Alice", 25};
    std::string data = Serializer<Person>::serialize(person);

    std::cout << "Serialized data: " << data << std::endl;
    return 0;
}
在上面的示例中,我们定义了一个名为 Serializer 的模板元类,其中包含一个静态方法 serialize,用于将对象转换为字节流。该方法使用 ostream 和 write 方法将对象的内存表示写入输出流。在主函数中,我们创建了一个 Person 对象,并使用 Serializer<Person>::serialize 方法将其序列化为字符串形式。最后打印序列化的结果。
分析数据结构
分析数据结构是指对数据类型进行反射,获取其成员变量和方法等信息。模板元类可以实现自动分析数据结构,从而避免手动编写反射代码时的繁琐和重复。以下是一个简单的分析数据结构模板元类的示例:
#include <iostream>
#include <string>

template <typename T>
class Reflector {
public:
    static void printMembers() {
        std::cout << "Members of " << typeid(T).name() << ":" << std::endl;
        printMembersImpl(static_cast<T*>(nullptr));
    }

private:
    template <typename U, typename = decltype(U::toString())>
    static void printMember(const char* name) {
        std::cout << "- " << name << ": " << U::toString() << std::endl;
    }

    template <typename U, typename = void>
    static void printMember(const char* name) {
        std::cout << "- " << name << ": unknown" << std::endl;
    }

    template <typename U>
    static void printMembersImpl(U* object) {
        using type = decltype(*object);
        std::cout << "Type: " << typeid(type).name() << std::endl;
        applyMembers<type>(object, &Reflector::printMember);
    }

    template <typename U, typename Func>
    static void applyMembers(U* object, Func func) {
        applyMembers<U, 0>(object, func);
    }

    template <typename U, std::size_t I, typename Func>
    static void applyMembers(U* object, Func func) {
        if constexpr (I < std::tuple_size_v<decltype(U::members())>) {
            auto name = std::get<I>(U::members()).name;
            using member_type = decltype(std::get<I>(U::members()).value);
            func<member_type>(name);
            applyMembers<U, I + 1>(object, func);
        }
    }
};

struct Person {
    std::string name;
    int age;

    static constexpr auto members() {
        return std::make_tuple(
            Member{"name", &Person::name},
            Member{"age", &Person::age}
        );
    }

    struct Member {
        const char* name;
        void* value;
    };

    template <typename U>
    static std::string toString(const U& value) {
        return std::to_string(value);
    }

    static std::string toString(const std::string& value) {
        return value;
    }
};

int main() {
    Reflector<Person>::printMembers();
    return 0;
}
在上面的示例中,我们定义了一个名为 Reflector 的模板元类,其中包含一个 printMembers 方法,用于打印类型 T 的成员变量和对应的值。该方法使用 applyMembers 方法遍历类型 T 的成员变量,并根据成员变量的类型调用 printMember 方法打印其名称和值。

printMember 方法是一个模板函数,它根据成员变量的类型是否拥有静态方法 toString 来进行重载。如果成员变量的类型拥有 toString 方法,则使用该方法将变量的值转换为字符串形式;否则,打印 "unknown" 表示无法解析该变量的值。

applyMembers 方法是一个可变参数模板函数,它接受类型 U、对象指针 object 和函数指针 func 作为参数。它遍历类型 U 的所有成员变量,并对每个成员变量调用 func 方法。

在 Person 类中,我们定义了一个名为 members 的静态成员函数,返回类型为 std::tuple<Member> 的值。该函数包含两个 Member 对象,每个对象包含成员变量的名称和指向成员变量的指针。

Reflector<Person>::printMembers() 方法调用了 Reflector<Person> 类的 printMembers 静态方法,该方法使用 printMembersImpl 方法遍历 Person 类的成员变量并打印名称和值。

总之,利用模板元类实现的反射功能可以自动分析数据结构,获取成员变量和方法等信息,并可以用于调试、测试和代码生成等应用场景。

模板元类是一个新的 C++20 特性,它允许我们在编译时生成代码和执行元编程操作。以下是一些使用模板元类实现代码生成和元编程功能的示例:

1. 用模板元类生成类型
c++
template <typename T>
struct MyType {
    // ...
};

template <typename T>
struct GenerateMyType {
    using type = MyType<T>;
};

using MyIntType = GenerateMyType<int>::type;
在这个示例中,我们定义了一个模板类 MyType 和一个模板元类 GenerateMyType。当我们给定一个类型 T 时,GenerateMyType 会返回一个新的类型 MyType<T>。

2. 用模板元类生成函数
c++
template <auto Val>
struct Constant {
    static constexpr auto value = Val;
};

template <typename T>
struct GenerateConstants {
    static constexpr auto pi = Constant<3.14159265358979323846>{};
    static constexpr auto e = Constant<2.71828182845904523536>{};
};

// 使用
constexpr auto pi = GenerateConstants<float>::pi.value; // 3.141593f
在这个示例中,我们使用模板元类 Constant 创建一个常量对象,该对象具有一个名为 value 的静态成员,其值等于模板参数 Val。然后我们定义了一个模板元类 GenerateConstants,它返回带有几个常量对象的结构体。最后,我们可以使用 GenerateConstants 类型来获取一个特定类型的常量对象。

3. 用模板元类实现反射
c++
#include <iostream>
#include <string_view>
#include <tuple>

template <typename T>
struct Reflect {
    static constexpr auto name = "unknown";
    static constexpr auto size = sizeof(T);
    static constexpr auto num_members = 0;
    using members = std::tuple<>;
};

#define REFLECT_TYPE(type) \
template <> \
struct Reflect<type> { \
    static constexpr auto name = #type; \
    static constexpr auto size = sizeof(type); \
}

#define REFLECT_MEMBER(member) \
std::tuple<&decltype(T::member), #member>

#define REFLECT_MEMBERS(...) \
using members = std::tuple<__VA_ARGS__>; \
static constexpr auto num_members = std::tuple_size<members>::value

// 示例类型
struct Person {
    int age;
    std::string name;
    float height;
};

// 反射声明
REFLECT_TYPE(Person);
REFLECT_MEMBERS(
    REFLECT_MEMBER(age),
    REFLECT_MEMBER(name),
    REFLECT_MEMBER(height)
);

int main() {
    // 输出类型信息
    std::cout << "Type: " << Reflect<Person>::name << std::endl;
    std::cout << "Size: " << Reflect<Person>::size << std::endl;
    std::cout << "Num Members: " << Reflect<Person>::num_members << std::endl;

    // 输出成员信息
    std::cout << "Members: " << std::endl;
    using Members = typename Reflect<Person>::members;
    std::apply([](auto&&... args) {
        ((std::cout << "- " << args << '\n'), ...);
    }, Members{});

    return 0;
}
在这个示例中,我们使用 REFLECT_TYPE 和 REFLECT_MEMBER 宏来定义类型和成员的反射信息。然后,我们使用 REFLECT_MEMBERS 宏将这些成员组合到一个 std::tuple 中,并将其存储在 members 类型中。最后,我们可以使用 Reflect 模板元类访问类型和成员的反射信息,并将它们输出到控制台。


24、什么是类型擦除(Type Erasure)?STL 中的任何容器都使用了类型擦除吗?
类型擦除(Type Erasure)是一种 C++ 中的编程技巧,用于将模板和泛型编程的代码转换成可运行的类型无关的代码。类型擦除可以将不同类型的对象包装成一个相同的类型,并提供统一的接口和操作。STL 中的任何容器都使用了类型擦除技术来实现通用性和灵活性。例如,std::vector 和 std::list 等容器内部实际上存储的是指向元素的指针,而非元素本身,从而避免了模板参数中不同数据类型的问题。

25、什么是 static_cast、dynamic_cast、const_cast 和 reinterpret_cast?它们之间有什么区别和限制?
        

static_cast、dynamic_cast、const_cast 和 reinterpret_cast 是 C++ 中的四种类型转换方式,它们之间有以下区别和限制:
static_cast:用于进行静态类型转换,可以将一个表达式的类型转换为另一个类型。static_cast 可以将指针或引用转换为其他指针或引用类型,但是必须保证转换合法,否则可能会导致程序崩溃。
dynamic_cast:用于进行动态类型转换,可以将一个基类指针或引用转换为派生类指针或引用。dynamic_cast 会在运行时检查转换的合法性,并返回 NULL 或抛出异常来处理错误情况。
const_cast:用于进行常量类型转换,可以将一个 const 对象或指针转换为非 const 对象或指针。const_cast 通常用于解除 const 限制,从而修改某个对象的值。
reinterpret_cast:用于进行低级别的类型转换,可以将任意类型的表达式转换为其他类型,包括指针、引用和指向不同类型的指针等。reinterpret_cast 不会进行任何检查,因此使用时需要格外小心,避免潜在的类型错误和危险。

 26.说说互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景

https://zhuanlan.zhihu.com/p/246114725 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值