共计 12809 个字符,预计需要花费 33 分钟才能阅读完成。
文章是由口试面试腾讯笔记整顿而来,次要是针对面试的 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_INITIALIZER
pthread_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 套接字 会有以下的变动:
- 不能给输入操作指定目标 IP 地址和端口号(因为调用 connect 函数时曾经指定),即不能应用 sendto 函数,而是应用 write 或 send 函数。写到已连贯 UDP 套接字上的内容都会主动发送到由 connect 指定的协定地址;
- 不用应用 recvfrom 函数以获悉数据报的发送者,而改用 read、recv 或 recvmsg 函数。在一个已连贯 UDP
套接字上,由内核为输出操作返回的数据报只有那些来自 connect 函数所指定的协定地址的数据报。目的地为这个已连贯 UDP 套接字的本地协定地址,发源地不是该套接字新近 connect 到的协定地址的数据报,不会投递到该套接字。即只有发源地的协定地址与 connect 所指定的地址相匹配才能够把数据报传输到该套接字。这样已连贯 UDP 套接字只能与一个对端替换数据报; - 由已连贯 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 baseP
double 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 对象
clike
class 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)); //创立一块内存;冒号示意全局的 new
Buffer* 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 成员函数指针。
不同继承档次的对象内存布局
繁多继承
多重继承