文章是由本身筆試面試騰訊的筆記整理而來,整理的時候又回顧了一遍,中間工做忙斷斷續續整理了半個月,才完成如今的樣子。主要是針對面試的C++後臺開發崗位,涵蓋了大部分C++相關的可能會被問到的技術點,做爲面試技術的參考回頭查閱。html
文章每週持續更新,各位的「三連」是對我最大的確定。能夠微信搜索公衆號「 後端技術學堂 」第一時間閱讀(通常比博客早更新一到兩篇)python
這篇筆記是基礎C++知識點總結,沒有過多的闡述後臺開發的系統架構和分佈式後臺服務設計相關,還有c++11新特性,這些筆試面試也會被問到但不在這篇討論範圍,能夠關注專欄後面若是有機會再補上。linux
基類指針能夠指向派生類的對象(多態性),若是刪除該指針delete []p;就會調用該指針指向的派生類析構函數,而派生類的析構函數又自動調用基類的析構函數,這樣整個派生類的對象徹底被釋放。若是析構函數不被聲明成虛函數,則編譯器實施靜態綁定,在刪除基類指針時,只會調用基類的析構函數而不調用派生類析構函數,這樣就會形成派生類對象析構不徹底。因此,將析構函數聲明爲虛函數是十分必要的。c++
當前line有函數調用的時候,next會直接執行到下一句 ,step會進入函數.git
(gdb)p &a //打印變量地址github
gdb)x 0xbffff543 //查看內存單元內變量面試
0xbffff543: 0x12345678算法
(gdb) x /4xb 0xbffff543 //單字節查看4個內存單元變量的值shell
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 linenum 顯示程序第linenum行的周圍的程序
list function 顯示程序名爲function的函數的源程序
ln -s 源文件 目標文件, ln -s / /home/good/linkname連接根目錄/到/home/good/linkname
一、軟連接就是:「ln –s 源文件 目標文件」,只會在選定的位置上生成一個文件的鏡像,不會佔用磁盤空間,相似與windows的快捷方式。
二、硬連接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 fcntl(sockfd, F_SETFL, O_NONBLOCK);
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的實現和select很是類似,只是描述fd集合的方式不一樣,poll使用pollfd結構而不是select的fd_set結構,其餘的都差很少,管理多個描述符也是進行輪詢,根據描述符的狀態進行處理,可是poll沒有最大文件描述符數量的限制。poll和select一樣存在一個缺點就是,包含大量文件描述符的數組被總體複製於用戶態和內核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨着文件描述符數量的增長而線性增大。
https://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事件: https://bbs.csdn.net/topics/390630226
#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服務端:
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 套接字不會接收任何異步錯誤;
服務端:
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))
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字節單位-片偏移單位
1.vector數據結構
vector和數組相似,擁有一段連續的內存空間,而且起始地址不變。
所以能高效的進行隨機存取,時間複雜度爲o(1);
但由於內存空間是連續的,因此在進行插入和刪除操做時,會形成內存塊的拷貝,時間複雜度爲o(n)。
另外,當數組中內存空間不夠時,會從新申請一塊內存空間並進行內存拷貝。
2.list數據結構
list是由雙向鏈表實現的,所以內存空間是不連續的。
只能經過指針訪問數據,因此list的隨機存取很是沒有效率,時間複雜度爲o(n);
但因爲鏈表的特色,能高效地進行插入和刪除。
這個問題其實很簡單,在調用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 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<int> 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協議RFC1889和RFC3550 G711 PCMU
Linux shell之數組:http://www.javashuo.com/article/p-unztpode-eb.html
Linux expr命令:http://www.runoob.com/linux/linux-comm-expr.html
shell中變量類型:local,global,export關鍵字: https://www.cnblogs.com/kaishirenshi/p/10274179.html
Linux let 命令:http://www.runoob.com/linux/linux-comm-let.html
vim修改tab成4個空格寫python: http://www.cnblogs.com/wi100sh/p/4938996.html
python判斷文件是否存在的幾種方法: http://www.javashuo.com/article/p-kpgzhykr-gz.html
python--文件操做刪除某行: https://www.cnblogs.com/nopnog/p/7026390.html
pytho3字典遍歷的幾種操做: https://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來表示。
動態分配數組int *pia = new int[10]; // array of 10 uninitialized ints
釋放分配的數組 delete [] pia;
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 operator-普通的new關鍵字
operator new-僅僅申請內存返回void*
placement new-在指定內存調用構造函數初始化類
new [] operator-若是是類對象,會在首部多申請4字節內存用於保存對象個數
深刻探究 new 和 delete https://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)
https://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);
在棧上分配類內存: https://www.cnblogs.com/weekbo/p/8533368.html
new與malloc區別
b. new和malloc最大區別: new會調用類的構造函數,malloc不會;
c. delete和free同理;new/delete是運算符,malloc/free函數。因此new/delete效率應該會高點。
#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>
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++對象模型
經過虛表指針訪問虛成員函數,對普通成員函數的訪問區別於虛成員函數。具體以下:
virtual member function虛成員函數normalize()的調用實際上轉換成:
(*ptr->vpter[1])(ptr)
函數指針也有差異,下面第一個是普通函數指針或者static member function。第二個是non-static member function成員函數指針。
終於寫完了篇幅較長,寫這篇文章是一方面是但願能給想來鵝廠或者準備面試任何一家公司C++開發的同窗一些參考,另外一方面是對知識的回顧。對編程和技術感興趣的小夥伴能夠關注個人公衆號,之後有更新會第一時間推送。
本文提到的後臺開發學習的知識點,我整理了電子書和學習資料,在公衆號 「後端技術學堂」 關注後回覆 「1024」 便可免費獲取,資料和書都是我幾年來學習過程當中收集整理的分享給你們。
能夠微信搜索公衆號「 後端技術學堂 」回覆「資料」有我給你準備的各類編程學習資料。文章每週持續更新,咱們下期見!