面試總結:鵝廠Linux後台開發面試筆試C++知識點參考筆記
文章每周持續更新,各位的「三連」是對我最大的肯定。可以微信搜索公眾號「 後端技術學堂 」第一時間閱讀(一般比部落格早更新一到兩篇)
文章是由自己筆試面試騰訊的筆記整理而來,整理的時候又回顧了一遍,中間工作忙斷斷續續整理了半個月,才完成現在的樣子。主要是針對面試的C++後台開發崗位,涵蓋了大部分C++相關的可能會被問到的技術點,作為面試技術的參考回頭查閱。
文末提供了本文知識點學習資源獲取方式,需要的同學自取。
這篇筆記是基礎C++知識點總結,沒有過多的闡述後台開發的系統架構和分散式後台服務設計相關,還有c++11新特性,這些筆試面試也會被問到但不在這篇討論範圍,可以關注專欄後面如果有機會再補上。
為什麼析構函數要是虛函數?
基類指針可以指向派生類的對象(多態性),如果刪除該指針delete []p;就會調用該指針指向的派生類析構函數,而派生類的析構函數又自動調用基類的析構函數,這樣整個派生類的對象完全被釋放。如果析構函數不被聲明成虛函數,則編譯器實施靜態綁定,在刪除基類指針時,只會調用基類的析構函數而不調用派生類析構函數,這樣就會造成派生類對象析構不完全。所以,將析構函數聲明為虛函數是十分必要的。
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|on\step 默認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的節點數,以此類推)
互斥鎖
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 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原理
//www.cnblogs.com/Anker/archive/2013/08/17/3263780.html
#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事件: //bbs.csdn.net/topics/390630226
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
預處理指令
#pragma once 防止頭文件重複引用
一位元組對齊
#pragma pack(push, 1)
#pragma pack(pop)
class面向對象
類繼承
class LayerManager : public ILayerManager{};
覆蓋虛函數機制
在某些情況下,希望覆蓋虛函數機制並強制函數調用使用虛函數的特定版
本,這裡可以使用作用域操作符:
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
操作符重載
輸出操作符
輸出操作符通常是非成員函數,定義成類的友元
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
Linux基礎
Linux shell之數組://www.cnblogs.com/Joke-Shi/p/5705856.html
Linux expr命令://www.runoob.com/linux/linux-comm-expr.html
shell中變數類型:local,global,export關鍵字: //www.cnblogs.com/kaishirenshi/p/10274179.html
Linux let 命令://www.runoob.com/linux/linux-comm-let.html
vim修改tab成4個空格寫python: //www.cnblogs.com/wi100sh/p/4938996.html
python判斷文件是否存在的幾種方法: //www.cnblogs.com/jhao/p/7243043.html
python–文件操作刪除某行: //www.cnblogs.com/nopnog/p/7026390.html
pytho3字典遍歷的幾種操作: //www.jb51.net/article/138414.htm
chmod
命令名稱: chmod
執行許可權: 所有用戶
功能描述: 改變文件或目錄許可權
語法: 第一種方法 chmod [{ugoa}{+-=}{rwx}] [文件或目錄]
備註: u:所有者 g:所屬組 o:其他人 a:所有人
+:為用戶增加許可權 -:為用戶減少許可權 =:為用戶賦予許可權
r:讀許可權 w:寫許可權 x:執行許可權
第二種方法 chmod -R [mode=421] [文件或目錄] ←(這種方法用的比較多)
備註: r:4 w:2 x:1
r為讀許可權,可以用4來表示,
w為寫許可權,可以用2來表示,
x為執行許可權,可以用1來表示。
new操作
動態分配數組int *pia = new int[10]; // array of 10 uninitialized ints
釋放分配的數組 delete [] pia;
new數組
int *arr = new int[1024]
delte [] a
# 堆上new 對象
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 //blog.csdn.net/codedoctor/article/details/76187567
當我們使用關鍵字new在堆上動態創建一個對象A時,比如 A* p = new A(),它實際上做了三件事:
向堆上申請一塊記憶體空間(做夠容納對象A大小的數據)(operator new)
調用構造函數 (調用A的構造函數(如果A有的話))(placement new)
返回正確的指針
當然,如果我們創建的是簡單類型的變數,那麼第二步會被省略。
當我們delete的時候也是如此,比如我們delete p 的時候,其行為如下:
定位到指針p所指向的記憶體空間,然後根據其類型,調用其自帶的析構函數(內置類型不用)
然後釋放其記憶體空間(將這塊記憶體空間標誌為可用,然後還給作業系統)
將指針標記為無效(指向NULL)
//blog.csdn.net/rain_qingtian/article/details/14225211
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);
在棧上分配類記憶體: //www.cnblogs.com/weekbo/p/8533368.html
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成員函數指針。
不同繼承層次的對象記憶體布局
單一繼承
多重繼承
結語
終於寫完了篇幅較長,寫這篇文章是一方面是希望能給想來鵝廠或者準備面試任何一家公司C++開發的同學一些參考,另一方面是對知識的回顧。對編程和技術感興趣的小夥伴可以關注我的公眾號,以後有更新會第一時間推送。
本文提到的後台開發學習的知識點,我整理了電子書和學習資料,在公眾號 「後端技術學堂」 關注後回復 「1024」 即可免費獲取,資料和書都是我幾年來學習過程中收集整理的分享給大家。
可以微信搜索公眾號「 後端技術學堂 」回復「資料」有我給你準備的各種編程學習資料。文章每周持續更新,我們下期見!