大家好,我是阿Q。
先祝大家国庆节快乐!
十月,对于每个校招er都是一个新的开始。“金九银十”,这也是你最后的一个黄金月。今年后边的话,大规模的校招可能不太会有了,所以抓住这一个月的时间,好好看看面经,巩固自己的基础知识,捋捋自己的项目。
今天给大家水一篇富途后端C++的面经。我最近还总结了其它大小公司的面经,供大家学习巩固。只要你肯努力,一切皆有可能!
虽然可能你已经出去玩、放松休息了,但是不要紧,我依然会将有用的东西整理分享给大家。
来源:
https://www.nowcoder.com/discuss/535934289968402432
#include <iostream>#include <string>std::string removeAdjacentDuplicates(const std::string& input) {std::string result;for (char currentChar : input) {// 如果结果字符串为空或者当前字符与结果字符串的最后一个字符不同,将当前字符添加到结果字符串中if (result.empty() || currentChar != result.back()) {result += currentChar;}}return result;}int main() {std::string input = "aaabbcdddeefffgghhh";std::string result = removeAdjacentDuplicates(input);std::cout << "Original String: " << input << std::endl;std::cout << "Processed String: " << result << std::endl;return 0;}

在Linux系统上,动态库通常位于 /lib
、/usr/lib
或自定义的库路径下。如果你自己编译了一个动态库,库文件通常会在当前目录下或自定义的库路径下。在Windows系统上,动态库通常具有 .dll
扩展名,它们可以位于系统目录、应用程序目录或自定义路径下。系统目录通常是C:\Windows\System32
。应用程序目录是包含可执行文件的目录。
确保库文件位于你指定的搜索路径中。你可以使用绝对路径或相对路径,但要确保路径是正确的。 确保你使用了正确的库文件名。在 -l
选项中,通常不需要包含库文件的前缀(如lib
)或扩展名(如.dll
或.so
)。在Windows系统中,确保动态库文件位于系统的 PATH 环境变量指定的路径中。这样可以使操作系统能够找到库文件。 确保库文件实际存在于指定的路径中。你可以使用文件浏览器或命令行来验证。 如果你的库依赖于其他库,确保这些依赖库也可用,并且路径正确。如果依赖关系出现问题,可能导致库加载失败。 注意不同操作系统下,库文件的扩展名和路径可能会有所不同。在Linux中,通常使用 .so
扩展名,而在Windows中使用.dll
扩展名。
目标(target):指定要构建的结果的名称。可以是可执行文件、库文件等。 依赖关系(dependencies):指定构建目标所依赖的文件或其他目标。如果依赖文件发生变化,目标需要重新构建。 命令(commands):描述如何从依赖文件构建目标文件的具体步骤。通常使用TAB键缩进来表示命令。
myprogram的可执行文件:
myprogram: main.o utils.og++ -o myprogram main.o utils.omain.o: main.cppg++ -c main.cpputils.o: utils.cppg++ -c utils.cpp
make
Makefile或
makefile的文件,并根据文件中的规则执行构建。如果要构建特定的目标,使用:
make target_name
myprogram,可以运行:
make myprogram
CC = g++CFLAGS = -Wall -O2SRC = main.cpp utils.cppmyprogram: main.o utils.o$(CC) -o myprogram main.o utils.omain.o: main.cpp$(CC) $(CFLAGS) -c main.cpputils.o: utils.cpp$(CC) $(CFLAGS) -c utils.cpp
libA.dll和
libB.dll,它们都包含一个名为
myFunction的函数。
g++ -o myprogram myprogram.cpp -L/path/to/libA -lA -L/path/to/libB -lB
libA.dll中的
myFunction将被调用,因为它在命令行中出现在
libB.dll之前。
libB.dll中的
myFunction,可以将链接顺序颠倒:
g++ -o myprogram myprogram.cpp -L/path/to/libB -lB -L/path/to/libA -lA
libB.dll中的
myFunction被调用。
-Bsymbolic选项,这将使得在动态库中搜索函数的顺序不受影响,但这种方法可能会引入其他问题,因此需要谨慎使用。
协议层次: TCP(传输控制协议):TCP位于 OSI 模型的传输层,负责提供可靠的、面向连接的数据传输。它确保数据可靠地从一个节点传输到另一个节点,处理数据的分段、重传、流控制等问题。 HTTP(超文本传输协议):HTTP位于 OSI 模型的应用层,是一种用于传输超文本(例如网页)的协议。HTTP依赖于底层的传输协议,通常是TCP,但也可以是其他协议(如HTTP/2使用的是TCP或TLS)。 目的和职责: TCP:TCP的主要目的是提供端到端的可靠数据传输,确保数据的可靠性和顺序性。它处理数据的分段、重传、流量控制等,但不关心数据的内容。 HTTP:HTTP的主要目的是在客户端和服务器之间传输和交互超文本资源。它定义了请求-响应模型,允许客户端发送HTTP请求来获取服务器上的资源(例如网页、图像、视频等),并接收服务器的响应。 数据格式: TCP:TCP传输的是原始二进制数据,没有对数据的结构和语义进行解释。它只关心数据的可靠传输。 HTTP:HTTP传输的是文本数据,通常使用ASCII字符集编码。HTTP定义了请求和响应的格式,包括请求方法、头部字段、正文等。HTTP的语义规则定义了如何解释这些数据。 连接性: TCP:TCP是面向连接的协议,它在通信开始前建立连接,然后进行数据传输,最后关闭连接。这种连接需要维护一些状态信息,如源端口、目标端口、序列号等。 HTTP:HTTP是无状态的协议,每个HTTP请求都是独立的,服务器不会保留关于客户端的状态信息。为了保持状态,HTTP使用机制如Cookie来在请求之间传递状态信息。 端口: TCP:TCP使用端口来标识不同的服务和应用程序。每个端口都有一个特定的编号,用于将数据传递给正确的应用程序。 HTTP:HTTP通常使用TCP的80端口(HTTP)或443端口(HTTPS)来进行通信,但可以使用其他端口,只要客户端和服务器都知道。 安全性: TCP:TCP本身不提供任何安全性保障,数据传输是明文的。但可以通过其他机制(如TLS/SSL)来在TCP上实现安全性。 HTTP:HTTP也是明文传输的协议,但可以通过使用HTTPS(HTTP Secure)来加密通信内容,提供安全性。
请求(Request):客户端使用HTTP请求来获取服务器上的资源。HTTP请求通常包括以下信息: 请求方法(HTTP Method):定义了客户端的操作类型,如GET、POST、PUT、DELETE等。 请求URL(Uniform Resource Locator):指定了要访问的资源的地址。 请求头(Request Headers):包含了请求的元信息,如用户代理、接受的内容类型、认证信息等。 请求体(Request Body):通常包含了客户端发送给服务器的数据,如表单数据、JSON数据等。在GET请求中,请求体通常为空。 响应(Response):服务器使用HTTP响应来回应客户端的请求。HTTP响应通常包括以下信息: 状态码(Status Code):用于指示请求的结果状态,如200 OK表示成功,404 Not Found表示资源不存在,500 Internal Server Error表示服务器内部错误等。 响应头(Response Headers):包含了响应的元信息,如服务器信息、响应的内容类型、响应时间等。 响应体(Response Body):包含了服务器返回给客户端的实际数据,如网页内容、JSON数据等。 其他元信息: HTTP还可以传输其他的元信息,如Cookies、缓存控制指令、认证信息等,以控制和管理通信的各个方面。
请求行(Request Line):请求行包含了请求的方法、请求的URL和HTTP协议版本。例如:
GET /index.html HTTP/1.1
请求头部(Request Headers):请求头部包含了关于请求的附加信息,以键值对的形式表示。常见的请求头部包括: Host
:指定服务器的主机名和端口号。User-Agent
:包含了客户端的信息,如浏览器类型和版本。Accept
:指定客户端可接受的响应内容类型。Cookie
:包含了客户端的会话信息。Authorization
:用于进行身份验证的信息。空行(Blank Line):空行用于分隔请求头部和请求体,通常只包含一个回车换行符。 请求体(Request Body):请求体包含了客户端向服务器发送的数据。这部分在一些请求中可能为空,例如GET请求。在POST请求中,请求体通常包含表单数据、JSON数据或其他内容。
状态行(Status Line):状态行包含了响应的HTTP协议版本、状态码和状态消息。例如:
HTTP/1.1 200 OK
响应头部(Response Headers): 响应头部包含了关于响应的附加信息,以键值对的形式表示。常见的响应头部包括: Content-Type
:指定响应的内容类型,如text/html、application/json等。Content-Length
:指定响应内容的长度(字节数)。Set-Cookie
:设置在客户端的Cookie。Location
:用于重定向,指定新的URL。空行(Blank Line):空行用于分隔响应头部和响应体,通常只包含一个回车换行符。 响应体(Response Body): 响应体包含了服务器返回给客户端的数据,例如HTML页面、JSON数据、图片等。
哈希表的核心是哈希函数,它接受一个键作为输入,然后将该键映射到数组的一个索引位置。这个映射的过程是快速的,通常需要常数时间。哈希函数的设计和性能直接影响了哈希表的性能。 哈希表通常由一个数组和一组哈希函数组成。每个数组位置称为“桶”,每个桶可以存储一个或多个键值对。当多个键映射到同一个桶时,通常会使用链表、红黑树等数据结构来解决冲突。 哈希冲突: 开放地址法(Open Addressing):尝试将键放入其他空闲桶,直到找到一个空桶或遍历完所有桶。 链地址法(Chaining):在每个桶中使用链表或其他数据结构存储多个键值对,具有相同哈希值的键都存储在同一个桶中。 哈希表的操作 插入(Insertion):将一个键值对插入哈希表。 查找(Lookup):根据键查找对应的值。 删除(Deletion):根据键删除对应的键值对。 哈希表的应用 数据库中的索引结构。 缓存数据的存储。 编程语言中的集合和字典数据结构(如Python中的字典)。 分布式系统中的数据分片和路由。 哈希表还用于密码学中的散列函数,如SHA-256、MD5等。
选择合适的哈希函数:哈希函数应该将关键字映射到一个特定的桶中,最好是均匀地分布在哈希表中。常见的哈希函数有除余法(mod)、乘法散列等。在实现哈希函数时,应该考虑到哈希表的大小和负载因子等因素。 选择适当的存储结构:哈希表通常使用数组来实现,每个数组元素对应一个桶,存储与该桶对应的关键字。如果桶中有多个关键字,可以使用链表或二叉搜索树等结构来解决哈希冲突。 实现哈希表的基本操作:哈希表的基本操作包括插入、查找和删除。在实现这些操作时,需要先通过哈希函数计算出关键字对应的桶,然后根据具体的存储结构进行操作。
#include <iostream>#include <list>using namespace std;class HashTable {private:int tableSize;list<int> *table;int hashFunction(int key) {return key % tableSize;}public:HashTable(int size) {tableSize = size;table = new list<int>[tableSize];}void insert(int key) {int index = hashFunction(key);table[index].push_back(key);}bool search(int key) {int index = hashFunction(key);for (auto it = table[index].begin(); it != table[index].end(); it++) {if (*it == key) {return true;}}return false;}void remove(int key) {int index = hashFunction(key);table[index].remove(key);}~HashTable() {delete[] table;}};int main() {HashTable ht(7);ht.insert(10);ht.insert(20);ht.insert(15);cout << ht.search(10) << endl; // 输出1,表示存在关键字10cout << ht.search(30) << endl; // 输出0,表示不存在关键字30ht.remove(10);cout << ht.search(10) << endl; // 输出0,表示关键字10已被删除return 0;}
快速查找和插入:哈希表在查找和插入操作上具有常数时间复杂度 O(1) 的优势,这意味着无论哈希表的大小如何,操作的时间开销都是可接受的。相比之下,链表和数组的查找操作通常需要线性时间复杂度 O(n),其中 n 是元素的数量。 空间利用率:哈希表通常具有较高的空间利用率。虽然哈希表的大小可能会动态调整以适应元素的增长,但由于哈希函数的作用,它不会像数组一样需要提前分配大块内存。链表通常需要额外的指针来维护节点之间的关系,因此在存储大量元素时可能会浪费一些内存。 键值对的关联性:哈希表适用于键值对(Key-Value Pairs)的存储和检索。每个键都通过哈希函数映射到一个唯一的位置,因此可以根据键快速找到对应的值。这在许多实际应用中非常有用,如数据库索引、缓存和字典数据结构。
快速查找和插入:哈希表在查找和插入操作上具有常数时间复杂度 O(1) 的性能。这意味着无论哈希表的大小如何,操作的时间开销都是恒定的。 适应动态数据:哈希表可以根据数据的动态增长自动扩展,而无需提前分配大块内存。这使得它适用于需要频繁插入和删除的情况。 键值对关联:哈希表适用于存储键值对,并能够根据键快速查找对应的值。这在许多应用中非常有用,如数据库索引、缓存、字典数据结构等。
直接访问元素:数组具有连续的内存存储,可以通过索引直接访问元素。因此,在已知索引的情况下,访问元素非常快速,具有常数时间复杂度 O(1)。 节省内存:数组通常比哈希表占用更少的内存,因为它们不需要额外的指针来维护元素之间的关系。
查找操作:哈希表通常在查找操作上更快,因为它们使用哈希函数将键映射到索引,而无需遍历整个数组。 插入和删除操作:哈希表在动态插入和删除操作上更快,因为数组需要移动元素来维持连续存储。 内存占用:数组通常占用更少的内存,因为哈希表需要额外的内存来存储哈希函数的索引和指针。 数据访问模式:如果访问模式是随机访问,并且可以通过索引快速定位元素,数组更有优势。如果是根据键进行查找和插入,哈希表更合适。
#include <iostream>class A {public:A() {func();}virtual void func() {std::cout << "A::func()" << std::endl;}virtual void test() {func();}};class B : public A {public:B() {func();}virtual void func(){std::cout << "B::func()" << std::endl;}};int main() {B b;b.test();A* a = &b;a->test();return 0;}
问b对象调用的是父类的func还是子类的func
问b.test调用的是父类的func还是子类的func
b.test()调用的是子类
B的
func()。虽然
test()函数在父类
A中声明,但由于在子类
B中重写了虚拟函数
func(),在
test()函数中调用的是子类
B的版本。
问a->test调用的是父类的func还是子类的func
a->test()调用的是子类
B的
func()。虽然
a是一个指向父类
A的指针,但在运行时它指向的是子类
B的对象。因此,调用
a->test()时会根据对象的实际类型调用子类
B的版本。
class B {virtual void funcB(){};};class C {virtual void funcC(){};}class A : public B, public C {virtual void funcB(){};virtual void funcC(){};};void func() {A* a = new A();B* b = a;C* c = a;}
A* a = new A();
创建了一个A
类的对象,并将指针a
指向该对象。B* b = a;
创建了一个B*
类型的指针b
,并将a
指向的A
对象的地址赋值给了b
。这实际上是将A
对象的地址视为B
类型的指针。C* c = a;
创建了一个C*
类型的指针c
,并将a
指向的A
对象的地址赋值给了c
。同样,这实际上是将A
对象的地址视为C
类型的指针。
A类对象
a的地址与
B类指针
b和
C类指针
c的地址是相同的,但在类型系统中,它们是不同的类型。这也就意味着:
b
指针的静态类型是B*
,因此你可以使用b
指针访问B
类中的成员函数和数据成员,但不能访问C
类的成员。同样, c
指针的静态类型是C*
,因此你可以使用c
指针访问C
类中的成员函数和数据成员,但不能访问B
类的成员。a
指针的静态类型是A*
,因此你可以使用a
指针访问A
类、B
类和C
类中的成员函数和数据成员,因为A
类同时继承了B
类和C
类的成员。
事务隔离级别SELECT a FROM tbl1 WHERE id = 1; a = 1000事务1BEGIN;UPDATE tbl1 SET a = a + 200 WHERE id = 1;COMMIT;事务2BEGIN;SELECT a FROM tbl1 WHERE id = 1;......SELECT a FROM tbl1 WHERE id = 1;COMMIT;
事务1修改数据并提交,但未提交前,事务2可以读取到事务1的未提交的修改。这意味着事务2可以看到脏数据(未提交的数据)。 在上述示例中,事务2可以在事务1修改并提交之前读取到修改的结果。
事务1修改数据并提交后,事务2才能读取到事务1的修改。这保证了已提交的数据对其他事务可见。 在上述示例中,事务2只能在事务1提交后才能读取到修改的结果。
事务1修改数据并提交后,事务2仍然看不到事务1的修改,直到事务2自身提交。这保证了事务2在整个事务过程中看到一致的数据状态,但在事务2提交之前,会出现不可重复读(同一数据在不同时间点读取结果不同)的情况。 在上述示例中,事务2只有在自身提交后才能读取到修改的结果,从而避免了脏读和不可重复读。
串行化是最高隔离级别,确保事务之间完全隔离,不会发生并发冲突。在串行化隔离级别下,事务1修改数据并提交后,事务2只能在事务1完成后才能执行。 在上述示例中,事务2只有在事务1完成(包括提交)后才能执行,从而避免了脏读、不可重复读和幻读(事务2查询到了事务1未提交的新增数据)。
读未提交:允许脏读、不可重复读、幻读。 读提交:允许不可重复读和幻读,但不允许脏读。 可重复读:允许幻读,但不允许脏读和不可重复读。 串行化:最高级别,不允许任何并发问题,但性能最差。
以上便是这个小伙伴的相关面经题目,我也给我一些参考答案,大家可以参考,如果有不合适的地方,还请你们能够给我改错的机会。
最后,阿Q新建了一个学习交流群,人虽然不多,但都是想努力上进的小伙伴,感兴趣可免费进,我们一起进步!!

文章转载自阿Q正砖,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




