文章是由口试面试腾讯笔记整顿而来,次要是针对面试的C++后盾开发岗位,涵盖了大部分C++后盾开发相干可能会考查和被问到的技术点。
自认为这篇笔记是比拟全面的总结,不论你是曾经工作筹备加入社招,还是在校学生筹备加入校招,笔记都能够作为技术面试筹备阶段参考查阅,查缺补漏。

笔记是根底C++知识点总结,没有过多的论述后盾开发的零碎架构和分布式后盾服务设计相干,还有c++11新个性,这些口试面试也会被问到但不在这篇探讨范畴,能够关注我前面有工夫再补上。

举荐视频:如何建设本人的常识体系,搭建本人的技术栈
提供一张c/c++ linux后盾开发常识体系纲要,供大家查漏补缺↓↓↓

gdb调试命令

step和next的区别?
以后line有函数调用的时候,next会间接执行到下一句 ,step会进入函数.

查看内存
(gdb)p &a //打印变量地址

(gdb)x 0xbffff543 //查看内存单元内变量

0xbffff543: 0x12345678

(gdb) x /4xb 0xbffff543 //单字节查看4个内存单元变量的值

0xbffff543: 0x78 0x56 0x34 0x12

多线程调试
(gdb) info threads:查看GDB以后调试的程序的各个线程的相干信息

(gdb) thread threadno:切换以后线程到由threadno指定的线程

break filename:linenum thread all 在所有线程相应行设置断点,留神如果主线程不会执行到该行,并且启动all-stop模式,主线程执行n或s会切换过来

set scheduler-locking off|onstep 默认off,执行s或c其它线程也同步执行。on,只有以后线程执行。step,只有以后线程执行

show scheduler-locking 显示以后模式

thread apply all command 每个线程执行批准命令,如bt。或者thread apply 1 3 bt,即线程1,3执行bt。

查看调用堆栈
(gdb)bt

(gdb)f 1 帧简略信息

(gdb)info f 1 帧详细信息

断点
b test.cpp:11

b test.cpp:main

gdb attach 调试办法:

gdb->file xxxx->attach pid->这时候过程是进行的->c 持续运行

带参数调试
输出参数命令set args 前面加上程序所要用的参数,留神,不再带有程序名,间接加参数,如:

(gdb)set args -l a -C abc

list命令
list linenum  显示程序第linenum行的四周的程序

list function  显示程序名为function的函数的源程序

static关键字的作用

软硬链接
ln -s 源文件 指标文件, ln -s / /home/good/linkname链接根目录/到/home/good/linkname

1、软链接就是:“ln –s 源文件 指标文件”,只会在选定的地位上生成一个文件的镜像,不会占用磁盘空间,相似于windows的快捷方式。

2、硬链接ln源文件指标文件,没有参数-s, 会在选定的地位上生成一个和源文件大小雷同的文件,无论是软链接还是硬链接,文件都放弃同步变动。

函数指针
函数指针 int (*func)(int, int)

函数指针数组 int (*funcArry[10])(int, int)

const int* p; 指向const int的指针

int const* p; 同上

int* const p; const指针

设计模式
单例模式

观察者模式(也叫公布订阅模式)

工厂模式 三种:简略工厂模式、工厂办法模式、形象工厂模式

为什么要用工厂模式?

起因就是对下层的使用者隔离对象创立的过程;或者是对象创立的过程简单,使用者不容易把握;或者是对象创立要满足某种条件,这些条件是业务的需要也好,是零碎束缚也好,没有必要让下层使用者把握,减少他人开发的难度。所以,到这时咱们应该分明了,无论是工厂模式,还是下面的战友说的开闭准则,都是为了隔离一些简单的过程,使得这些简单的过程不向外裸露,如果裸露了这些过程,会对使用者减少麻烦,这也就是所谓的团队单干。

数据结构

各种排序算法
堆排序
要害:1.初始建堆从最初一个非叶节点开始调整 2.筛选从顶点开始往下调整

通俗易懂的快排
二叉树定理
度为2节点数 = 叶子节点数 - 1

证实:树枝数=节点数-1, n00 +n11 +n2*2 = n0+n1+n2-1 (n0代表度为0的节点数,以此类推)

须要C/C++ Linux服务器架构师学习材料加群812855908(材料包含C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),收费分享

互斥锁

pthread_mutex_t m_mutex;pthread_mutex_init(&m_mutex, NULL)等效于pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZERpthread_mutex_lock(&m_mutex);pthread_mutex_unlock(&m_mutex)pthread_mutex_destroy(&m_mutex)int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);bool g_flag = false;void* t1(void* arg) { cout << "create t1 thread success" << endl; pthread_mutex_lock(&m_mutex); g_flag = true; pthread_mutex_unlock(&m_mutex);}void* t2(void* arg) { cout << "create t2 thread success" << endl; pthread_mutex_lock(&m_mutex); g_flag = false; pthread_mutex_unlock(&m_mutex);}int main(int argc, char* argv[]) { pthread_t tid1, tid2; pthread_create(&tid1, NULL, t1, NULL); sleep(2); pthread_create(&tid2, NULL, t2, NULL); pthread_join(tid1, NULL); pthread_join(tid2, NULL);}

大小端转换

#define BigLittleSwap32(A) ((((uint32)(A) & 0xff000000) >> 24) |                     (((uint32)(A) & 0x00ff0000) >> 8) |                     (((uint32)(A) & 0x0000ff00) << 8) |                     (((uint32)(A) & 0x000000ff) << 24))

io多路复用
为什么 IO 多路复用要搭配非阻塞IO

设置非阻塞 io fcntl(sockfd, F_SETFL, O_NONBLOCK);

select

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);void FD_CLR(int fd, fd_set *set);int FD_ISSET(int fd, fd_set *set);void FD_SET(int fd, fd_set *set);void FD_ZERO(fd_set *set);fd_set rdfds;struct timeval tv;int ret;FD_ZERO(&rdfds);FD_SET(socket, &rdfds);tv.tv_sec = 1;tv.tv_uses = 500;ret = select (socket + 1, %rdfds, NULL, NULL, &tv);if(ret < 0) perror (“select”);else if (ret = = 0) printf(“time out”);else{printf(“ret = %d/n”,ret);if(FD_ISSET(socket, &rdfds)){/* 读取socket句柄里的数据 */}留神select函数的第一个参数,是所有退出汇合的句柄值的最大那个那个值还要加1.比方咱们创立了3个句柄;

poll实现
poll的实现和select十分类似,只是形容fd汇合的形式不同,poll应用pollfd构造而不是select的fd_set构造,其余的都差不多,治理多个描述符也是进行轮询,依据描述符的状态进行解决,然而poll没有最大文件描述符数量的限度。poll和select同样存在一个毛病就是,蕴含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不管这些文件描述符是否就绪,它的开销随着文件描述符数量的减少而线性增大。

epoll原理

#include <sys/epoll.h>int epoll_create(int size);int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:

LT模式:当epoll_wait检测到描述符事件产生并将此事件告诉应用程序,应用程序能够不立刻解决该事件。下次调用epoll_wait时,会再次响应应用程序并告诉此事件。

ET模式:当epoll_wait检测到描述符事件产生并将此事件告诉应用程序,应用程序必须立刻解决该事件。如果不解决,下次调用epoll_wait时,不会再次响应应用程序并告诉此事件。

ET模式在很大水平上缩小了epoll事件被反复触发的次数,因而效率要比LT模式高。epoll工作在ET模式的时候,

必须应用非阻塞套接口,以防止因为一个文件句柄的阻塞读/阻塞写操作把解决多个文件描述符的工作饿死。

Epoll ET模型下,为什么每次EPOLLIN事件都会带一次EPOLLOUT事件:https://bbs.csdn.net/topics/3...

udp套接字

#include <sys/socket.h>ssize_t sendto(int sockfd, void *buff, size_t nbytes, int flags,  const struct sockaddr *destaddr, socklen_t addrlen);ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags,  struct sockaddr *addr, socklen_t *addrlen);

网络套接字

udp原理与套接字
udp服务端:

sockListener=socket(AF_INET,SOCK_DGRAM,0)bind(sockListener,(struct sockaddr*)&addrListener,sizeof(addrListener))nMsgLen=recvfrom(sockListener,szBuf,1024,0,(struct sockaddr*)&addrClient,&addrLen) 

udp客户端

sockClient=socket(AF_INET,SOCK_DGRAM,0);bind(sockClient,(struct sockaddr*)&addrLocal,sizeof(addrLocal))FD_ZERO(&setHold);FD_SET(STDIN_FILENO,&setHold);FD_SET(sockClient,&setHold);cout<<"you can type in sentences any time"<<endl;while(true){   setTest=setHold;   nReady=select(sockClient+1,&setTest,NULL,NULL,NULL);   if(FD_ISSET(0,&setTest))  {       nMsgLen=read(0,szMsg,1024);       write(sockClient,szMsg,nMsgLen);  }   if(FD_ISSET(sockClient,&setTest))  {       nMsgLen=read(sockClient,szRecv,1024);       szRecv[nMsgLen]='0';       cout<<"read:"<<szRecv<<endl;  }} 

UDP中应用 connect 函数成为已连贯的套接字

已连贯 UDP 套接字 绝对于 未连贯 UDP 套接字 会有以下的变动:

  1. 不能给输入操作指定目标 IP 地址和端口号(因为调用 connect 函数时曾经指定),即不能应用 sendto 函数,而是应用write 或 send 函数。写到已连贯 UDP 套接字上的内容都会主动发送到由 connect 指定的协定地址;
  2. 不用应用 recvfrom 函数以获悉数据报的发送者,而改用 read、recv 或 recvmsg 函数。在一个已连贯 UDP
    套接字上,由内核为输出操作返回的数据报只有那些来自 connect 函数所指定的协定地址的数据报。目的地为这个已连贯 UDP套接字的本地协定地址,发源地不是该套接字新近 connect 到的协定地址的数据报,不会投递到该套接字。即只有发源地的协定地址与 connect 所指定的地址相匹配才能够把数据报传输到该套接字。这样已连贯 UDP 套接字只能与一个对端替换数据报;
  3. 由已连贯 UDP 套接字引发的异步谬误会返回给它们所在的过程,而未连贯 UDP 套接字不会接管任何异步谬误;

tcp套接字
服务端:

listenfd = socket(AF_INET , SOCK_STREAM , 0)bind(listenfd , (struct sockaddr*)&servaddr , sizeof(servaddr))listen(listenfd , LISTENQ)connfd = accept(listenfd , (struct sockaddr *)&cliaddr , &clilen))n = read(connfd , buff , MAX_LINE)write(connfd , buff , n)

客户端:

sockfd = socket(AF_INET , SOCK_STREAM , 0)connect(sockfd , (struct sockaddr *)&servaddr , sizeof(servaddr))write(sockfd , sendline , strlen(sendline))

IP分片与重组

MTU是1500是指的以太网的MTU,能够用 netstat -i 命令查看这个值。如果IP层有数据包要传,而且数据包的长度超过了MTU,

那么IP层就要对数据包进行分片(fragmentation)操作,使每一片的长度都小于或等于MTU。

咱们假如要传输一个UDP数据包,以太网的MTU为1500字节,个别IP首部为20字节,UDP首部为8字节,数据的净荷(payload)

局部预留是1500-20-8=1472字节。如果数据局部大于1472字节,就会呈现分片景象,

偏移量的单位为8Byte

以ID标示是不是同一个分片,以偏移量标示在报文里的地位,每个不残缺的ID报文有一个期待计时器,到时抛弃IP层不保障可能送达,

如果丢了下层本人解决参考rfc 791

IP报文长度单位口诀

4字节单位- 首部长度单位 1字节单位-总长度单位 8字节单位-片偏移单位

STL容器

vector与list
1.vector数据结构

vector和数组相似,领有一段间断的内存空间,并且起始地址不变。

因而能高效的进行随机存取,工夫复杂度为o(1);

但因为内存空间是间断的,所以在进行插入和删除操作时,会造成内存块的拷贝,工夫复杂度为o(n)。

另外,当数组中内存空间不够时,会从新申请一块内存空间并进行内存拷贝。

2.list数据结构

list是由双向链表实现的,因而内存空间是不间断的。

只能通过指针拜访数据,所以list的随机存取十分没有效率,工夫复杂度为o(n);

但因为链表的特点,能高效地进行插入和删除。

Vector动态内存调配
这个问题其实很简略,在调用push_back时,若以后容量曾经不可能放入心得元素(capacity=size),那么vector会从新申请一块内存,把之前的内存里的元素拷贝到新的内存当中,而后把push_back的元素拷贝到新的内存中,最初要析构原有的vector并开释原有的内存。所以说这个过程的效率是极低的,为了防止频繁的分配内存,C++每次申请内存都会成倍的增长,例如之前是4,那么从新申请后就是8,以此类推。当然不肯定是成倍增长,比方在我的编译器环境下实测是0.5倍增长,之前是4,从新申请后就是6

TinySTL

预处理指令

pragma once 避免头文件反复援用

一字节对齐

pragma pack(push, 1)

pragma pack(pop)

class面向对象

类继承
class LayerManager : public ILayerManager{};

为什么析构函数要是虚函数?

基类指针能够指向派生类的对象(多态性),如果删除该指针delete []p;就会调用该指针指向的派生类析构函数,而派生类的析构函数又主动调用基类的析构函数,这样整个派生类的对象齐全被开释。如果析构函数不被申明成虚函数,则编译器施行动态绑定,在删除基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不齐全。所以,将析构函数申明为虚函数是十分必要的。

笼罩虚函数机制
在某些状况下,心愿笼罩虚函数机制并强制函数调用应用虚函数的特定版

本,这里能够应用作用域操作符:

Item_base *baseP = &derived;// calls version from the base class regardless of the dynamic type of basePdouble d = baseP->Item_base::net_price(42);

这段代码强制将 net_price 调用确定为 Item_base 中定义的版本,该调用

将在编译时确定。只有成员函数中的代码才应该应用作用域操作符笼罩虚函数机制。

为什么会心愿笼罩虚函数机制?最常见的理由是为了派生类虚函数调用基类中的版本。在这种状况下,基类版本能够实现继承档次中所有类型的公共工作,而每个派生类型只增加本人的非凡工作。

例如,能够定义一个具备虚操作的 Camera 类档次。Camera 类中的 display函数能够显示所有的公共信息,派生类(如 PerspectiveCamera)可能既须要显示公共信息又须要显示本人的独特信息。能够显式调用 Camera 版本以显示公共信息,而不是在 PerspectiveCamera 的 display 实现中复制 Camera 的操作。

在这种状况下,曾经确切晓得调用哪个实例,因而,不须要通过虚函数机制。派生类虚函数调用基类版本时,必须显式应用作用域操作符。如果派生类函数疏忽了这样做,则函数调用会在运行时确定并且将是一个本身调用,从而导致无穷递归。

名字抵触与继承
尽管能够间接拜访基类成员,就像它是派生类成员一样,然而成员保留了它的基类成员资格。个别咱们并不关怀是哪个理论类蕴含成员,通常只在基类和派生类共享同一名字时才须要留神。

与基类成员同名的派生类成员将屏蔽对基类成员的间接拜访。

struct Base{   Base(): mem(0) { }   protected:   int mem;};struct Derived : Base {   Derived(int i): mem(i) { } // initializes Derived::mem   int get_mem() { return mem; } // returns Derived::mem   protected:   int mem; // hides mem in the base};

get_mem 中对 mem 的援用被确定为应用 Derived 中的名字。如果编写如下代码:

Derived d(42);cout << d.get_mem() << endl; // prints 42

则输入将是 42。

应用作用域操作符拜访被屏蔽成员

能够应用作用域操作符拜访被屏蔽的基类成员:

struct Derived : Base {int get_base_mem() { return Base::mem; }};

作用域操作符批示编译器在 Base 中查找 mem。

设计派生类时,只有可能,最好防止与基类数据成员的名字雷同

类成员函数的重载、笼罩和暗藏区别?
a.成员函数被重载的特色:

(1)雷同的范畴(在同一个类中);

(2)函数名字雷同;

(3)参数不同;

(4)virtual 关键字可有可无。

b.笼罩是指派生类函数笼罩基类函数,特色是:

(1)不同的范畴(别离位于派生类与基类);

(2)函数名字雷同;

(3)参数雷同;

(4)基类函数必须有virtual 关键字。

c.“暗藏”是指派生类的函数屏蔽了与其同名的基类函数,规定如下:

(1)如果派生类的函数与基类的函数同名,然而参数不同。此时,不管有无virtual关键字,基类的函数将被暗藏(留神别与重载混同,仅同名就能够)。

(2)如果派生类的函数与基类的函数同名,并且参数也雷同,然而基类函数没有virtual 关键字。此时,基类的函数被暗藏(留神别与笼罩混同)

纯虚函数

class Disc_item : public Item_base {   public:   double net_price(std::size_t) const = 0;};

含有(或继承)一个或多个纯虚函数的类是形象基类。除了作

为形象基类的派生类的对象的组成部分,甚至不能创立形象类型Disc_item的对象。

模板编程

函数模板

template <typename T> int compare(const T &v1, const T &v2) {   if (v1 < v2) return -1;   if (v2 < v1) return 1;   return 0;}

应用compare(1, 2)

类模板

template <class Type> class Queue {public:   Queue (); // default constructor   Type &front (); // return element from head of Queue   const Type &front () const;   void push (const Type &); // add element to back of Queue   void pop(); // remove element from head of Queue   bool empty() const; // true if no elements in the Queue   private:   // ...};

应用Queue qi;

操作符重载

输入操作符
输入操作符通常是非成员函数,定义成类的友元

friend ostream& operator<<(ostream& out, const Sales_item& s){   out << s.isbn << "t" << s.units_sold << "t"   << s.revenue << "t" << s.avg_price();   return out;}

算术和关系操作
算术和关系操作符定义为非成员函数

为了与内置操作符保持一致,加法返回一个右值,而不是一个援用。

Sales_item operator+(const Sales_item& lhs, const Sales_item& rhs){   Sales_item ret(lhs); // copy lhs into a local object that we'll   ret += rhs; // add in the contents of rhs   return ret; // return ret by value}int operator<(const TableIndex2D& right) const;friend bool operator== (const UEContext& info1,const UEContext& info2) const{   if(info1.ContextID != info2.ContextID) return false;   return true;}friend bool operator!= (const UEContext& info1,const UEContext& info2) const{return !(info1 == info2);}

复制管制
包含,一个拷贝构造函数,一个赋值运算符,一个析构函数,一对取址运算符

如果你这么写:class Empty{};

和你这么写是一样的:

class Empty {   public:   Empty();            // 缺省构造函数   Empty(const Empty& rhs);    // 拷贝构造函数   ~Empty();            // 析构函数 ---- 是否            // 为虚函数看下文阐明    Empty& operator=(const Empty& rhs);  // 赋值运算符    Empty* operator&();       // 取址运算符    const Empty* operator&() const;};Empty(const Empty& rhs){   a = rhs.a}

类赋值操作符必须是类的成员,以便编译器能够晓得是否须要合成一个, 赋值必须返回对 *this 的援用。

一般而言,赋值操作符与复合赋值操作符应返回操作符的援用

Guti& Guti::operator=( const Guti& rhs ){ mtmsi_m = rhs.mtmsi_m; mmeCode_m = rhs.mmeCode_m; mmeGid_m = rhs.mmeGid_m; plmnId_m = rhs.plmnId_m; return *this;};//留神,查看对本人赋值的状况c& c::operator=(const c& rhs){// 查看对本人赋值的状况if (this == &rhs) return *this;...}

构造函数初始化式

初始化const对象和援用对象的惟一机会。P389 C++ Primer 5th

协定
RTP/RTSP/RTCP
RTP协定RFC1889和RFC3550 G711 PCMU

HTTP

new操作
动态分配数组int *pia = new int[10]; // array of 10 uninitialized ints

开释调配的数组 delete [] pia;

new数组

 int *arr = new int[1024]delte [] a#堆上new 对象clikeclass MyClass{   MyClass(int a) {};   int empty() {return 0;};};MyClass *p = new MyClass(1);delete p;#栈上调配 对象MyClass test(1);

搁置式new
辨别以下几种操作符号:

new operator-一般的new关键字

operator new-仅仅申请内存返回void*

placement new-在指定内存调用构造函数初始化类

new [] operator-如果是类对象,会在首部多申请4字节内存用于保留对象个数

深刻探索 new 和 delete

当咱们应用关键字new在堆上动态创建一个对象A时,比方 A* p = new A(),它实际上做了三件事:

向堆上申请一块内存空间(做够包容对象A大小的数据)(operator new)

调用构造函数 (调用A的构造函数(如果A有的话))(placement new)

返回正确的指针

当然,如果咱们创立的是简略类型的变量,那么第二步会被省略。

当咱们delete的时候也是如此,比方咱们delete p 的时候,其行为如下:

定位到指针p所指向的内存空间,而后依据其类型,调用其自带的析构函数(内置类型不必)

而后开释其内存空间(将这块内存空间标记为可用,而后还给操作系统)

将指针标记为有效(指向NULL)

void* p=::operator new (sizeof(Buffer));  //创立一块内存;冒号示意全局的newBuffer* bp= start_cast<Buffer*>(p);  //指针进行装换Buffer* buf3=new(bp) Buffer(128);   //把bp指针指向的内存租借buf3,buf3->put('c');buf3->~Buffer();  //这里析构函数要显示调用::operator delete(p);

搁置new结构对象数组

new与malloc区别

b. new和malloc最大区别: new会调用类的构造函数,malloc不会;

c. delete和free同理;new/delete是运算符,malloc/free函数。所以new/delete效率应该会高点。

Linux IPC机制汇总

管道

#include <unistd.h>无名管道: int pipe(int pipedes[2])有名管道:int mkfifo(const char *pathname, mode_t mode)

音讯队列

#include <sys/msg.h>int msgget(key_t key, int msgflg) //创立int msgctl(int msqid, int cmd, struct msqid_ds *buf) //设置/获取音讯队列的属性值int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg) //发送音讯到音讯队列(增加到尾端)ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg) //接管音讯

共享内存

#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg) //创立一个共享内存空间int shmctl(int shmid, int cmd, struct shmid_ds *buf) //对共享内存过程操作,包含:读取/设置状态,删除操作void *shmat(int shmid, const void *shmaddr, int shmflg) //将共享内存空间挂载到过程中int shmdt(const void *shmaddr) //将过程与共享内存空间拆散 **(****只是与共享内存不再有分割,并没有删除共享内存****)**

信号

#include</usr/include/bits/signum.h>

手动实现strcpy

char *strcpy(char *strDest, const char *strSrc) {   if ( strDest == NULL || strSrc == NULL)   return NULL ;   if ( strDest == strSrc)   return strDest ;   char *tempptr = strDest ;   while( (*strDest++ = *strSrc++) != ‘/0’)   return tempptr ;}

C++对象内存布局

这部分具体内容能够参考《深度摸索C++对象模型》

虚函数多态机制
通过虚表指针拜访虚成员函数,对一般成员函数的拜访区别于虚成员函数。具体如下:

virtual member function虚成员函数normalize()的调用实际上转换成:

(*ptr->vpter[1])(ptr)

函数指针也有差异,上面第一个是一般函数指针或者static member function。第二个是non-static member function成员函数指针。

不同继承档次的对象内存布局

繁多继承

多重继承