1. 程式人生 > >面試總結:鵝廠Linux後臺開發面試筆試C++知識點參考筆記

面試總結:鵝廠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指標 ### 設計模式 [單例模式](http://blog.csdn.net/wuzhekai1985/article/details/6665869) [觀察者模式(也叫釋出訂閱模式](http://blog.csdn.net/wuzhekai1985/article/details/6674984)) [工廠模式](http://blog.csdn.net/wuzhekai1985/article/details/6660462) 三種:簡單工廠模式、工廠方法模式、抽象工廠模式 為什麼要用工廠模式?原因就是對上層的使用者隔離物件建立的過程;或者是物件建立的過程複雜, 使用者不容易掌握;或者是物件建立要滿足某種條件,這些條件是業務的需求也好,是系統約束也好 ,沒有必要讓上層使用者掌握,增加別人開發的難度。所以,到這時我們應該清楚了,無論是工廠模式, 還是上面的戰友說的開閉原則,都是為了隔離一些複雜的過程,使得這些複雜的過程不向外暴露, 如果暴露了這些過程,會對使用者增加麻煩,這也就是所謂的團隊合作。 ### 資料結構 #### [各種排序演算法](http://blog.csdn.net/daguairen/article/details/52611874) #### [堆排序](https://www.cnblogs.com/0zcl/p/6737944.html) 關鍵:1.初始建堆從最後一個非葉節點開始調整 2.篩選從頂點開始往下調整 #### [通俗易懂的快排]( http://blog.csdn.net/vayne_xiao/article/details/53508973) #### 二叉樹定理 度為2節點數 = 葉子節點數 - 1 證明:樹枝數=節點數-1, n0*0 +n1*1 +n2*2 = n0+n1+n2-1 (n0代表度為0的節點數,以此類推) ### 互斥鎖 ```c 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); } ``` ### 大小端轉換 ```c #define BigLittleSwap32(A) ((((uint32)(A) & 0xff000000) >> 24) | \ (((uint32)(A) & 0x00ff0000) >> 8) | \ (((uint32)(A) & 0x0000ff00) << 8) | \ (((uint32)(A) & 0x000000ff) << 24)) ``` ### io多路複用 [為什麼 IO 多路複用要搭配非阻塞IO]( https://www.zhihu.com/question/37271342) 設定非阻塞 `io fcntl(sockfd, F_SETFL, O_NONBLOCK); ` #### select ```c 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原理 https://www.cnblogs.com/Anker/archive/2013/08/17/3263780.html ```c #include
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 #### udp套接字 [ref1](http://blog.csdn.net/chenhanzhun/article/details/41914029) [ref1](https://www.cnblogs.com/bleopard/p/4004916.html) ```c #include
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服務端: ```c 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客戶端 ```c 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"<
4位元組單位- 首部長度單位 1位元組單位-總長度單位 8位元組單位-片偏移單位 ### STL容器 #### vector與list 1.vector資料結構 vector和陣列類似,擁有一段連續的記憶體空間,並且起始地址不變。 因此能高效的進行隨機存取,時間複雜度為o(1); 但因為記憶體空間是連續的,所以在進行插入和刪除操作時,會造成記憶體塊的拷貝,時間複雜度為o(n)。 另外,當陣列中記憶體空間不夠時,會重新申請一塊記憶體空間並進行記憶體拷貝。 2.list資料結構 list是由雙向連結串列實現的,因此記憶體空間是不連續的。 只能通過指標訪問資料,所以list的隨機存取非常沒有效率,時間複雜度為o(n); 但由於連結串列的特點,能高效地進行插入和刪除。 #### [Vector動態記憶體分配]( https://blog.csdn.net/xnmc2014/article/details/86748138) 這個問題其實很簡單,在呼叫push_back時,若當前容量已經不能夠放入心得元素(capacity=size),那麼vector會重新申請一塊記憶體,把之前的記憶體裡的元素拷貝到新的記憶體當中,然後把push_back的元素拷貝到新的記憶體中,最後要析構原有的vector並釋放原有的記憶體。所以說這個過程的效率是極低的,為了避免頻繁的分配記憶體,C++每次申請記憶體都會成倍的增長,例如之前是4,那麼重新申請後就是8,以此類推。當然不一定是成倍增長,比如在我的編譯器環境下實測是0.5倍增長,之前是4,重新申請後就是6 [TinySTL]( https://github.com/zouxiaohang/TinySTL/tree/master/TinySTL) ### 預處理指令 \#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 的操作。 在這種情況下,已經確切知道呼叫哪個例項,因此,不需要通過虛擬函式機制。 派生類虛擬函式呼叫基類版本時,必須顯式使用作用域操作符。 如果派生類函式忽略了這樣做,則函式呼叫會在執行時確定並 且將是一個自身呼叫,從而導致無窮遞迴。 #### 名字衝突與繼承 雖然可以直接訪問基類成員,就像它是派生類成員一樣,但是成員保留了它 的基類成員資格。一般我們並不關心是哪個實際類包含成員,通常只在基類和派 生類共享同一名字時才需要注意。 與基類成員同名的派生類成員將遮蔽對基類成員的直接訪問。 ```c 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。 使用作用域操作符訪問被遮蔽成員 可以使用作用域操作符訪問被遮蔽的基類成員: ```c 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 關鍵字。此時,基類的函式被隱藏(注意別與覆蓋混淆) #### 純虛擬函式 ```c class Disc_item : public Item_base { public: double net_price(std::size_t) const = 0; }; ``` 含有(或繼承)一個或多個純虛擬函式的類是抽象基類。除了作 為抽象基類的派生類的物件的組成部分,甚至不能建立抽象型別Disc_item的物件。 ### 模板程式設計 #### 函式模板 ```c template int compare(const T &v1, const T &v2) { if (v1 < v2) return -1; if (v2 < v1) return 1; return 0; } ``` 使用compare(1, 2) #### 類模板 ```c template 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; ### 操作符過載 #### 輸出操作符 輸出操作符通常是非成員函式,定義成類的友元 ```c 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; } ``` #### 算術和關係操作 算術和關係操作符定義為非成員函式 為了與內建操作符保持一致,加法返回一個右值,而不是一個引用。 ```c 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{};` 和你這麼寫是一樣的: ```c 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 的引用。 一般而言,賦值操作符與複合賦值操作符應返回操作符的引用 ```c 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之陣列:https://www.cnblogs.com/Joke-Shi/p/5705856.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判斷檔案是否存在的幾種方法: https://www.cnblogs.com/jhao/p/7243043.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來表示。 ### new操作 動態分配陣列int *pia = new int[10]; // array of 10 uninitialized ints 釋放分配的陣列 delete [] pia; #### new陣列 ```c 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 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 ```c void* p=::operator new (sizeof(Buffer)); //建立一塊記憶體;冒號表示全域性的new Buffer* bp= start_cast(p); //指標進行裝換 Buffer* buf3=new(bp) Buffer(128); //把bp指標指向的記憶體租借buf3, buf3->put('c'); buf3->~Buffer(); //這裡析夠函式要顯示呼叫 ::operator delete(p); ``` [放置new構造物件陣列](https://bbs.csdn.net/topics/392271390) ![放置new物件陣列](https://upload-images.jianshu.io/upload_images/7842464-1b62834c5932f1fa?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 在棧上分配類記憶體: https://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機制彙總](https://www.cnblogs.com/Jimmy1988/p/7744659.html) #### 管道 ```c #include 無名管道: int pipe(int pipedes[2]) 有名管道:int mkfifo(const char *pathname, mode_t mode) ``` #### 訊息佇列 ```c #include 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) //接收訊息 ``` #### 共享記憶體 ```c #include 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` ### 手動實現strcpy ```c 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++物件模型](https://book.douban.com/subject/10427315/) #### 虛擬函式多型機制 通過虛表指標訪問虛成員函式,對普通成員函式的訪問區別於虛成員函式。具體如下: virtual member function虛成員函式normalize()的呼叫實際上轉換成: (*ptr->vpter[1])(ptr) ![在這裡插入圖片描述](https://upload-images.jianshu.io/upload_images/7842464-813b3b02ae17f85e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 函式指標也有差別,下面第一個是普通函式指標或者static member function。第二個是non-static member function成員函式指標。 ![在這裡插入圖片描述](https://upload-images.jianshu.io/upload_images/7842464-9b2bdd90e3164fd6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) #### 不同繼承層次的物件記憶體佈局 ##### 單一繼承 ![在這裡插入圖片描述](https://upload-images.jianshu.io/upload_images/7842464-cec01db8f0aea169?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![在這裡插入圖片描述](https://upload-images.jianshu.io/upload_images/7842464-ad45f9bd68b98af6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ##### 多重繼承 ![在這裡插入圖片描述](https://upload-images.jianshu.io/upload_images/7842464-370992e22d096bfa?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![在這裡插入圖片描述](https://upload-images.jianshu.io/upload_images/7842464-f5ecf026bb1ad501.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ![多重繼承2](https://upload-images.jianshu.io/upload_images/7842464-8268877f54c78ebe?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ## 結語 終於寫完了篇幅較長,寫這篇文章是一方面是希望能給想來鵝廠或者準備面試任何一家公司C++開發的同學一些參考,另一方面是對知識的回顧。對程式設計和技術感興趣的小夥伴可以關注我的公眾號,以後有更新會第一時間推送。 本文提到的後臺開發學習的知識點,我整理了**電子書和學習資料**,在公眾號 「**後端技術學堂**」 關注後回覆 「**1024**」 即可免費獲取,資料和書都是我幾年來學習過程中收集整理的分享給大家。 > 可以微信搜尋公眾號「 後端技術學堂 」回覆「資料」有我給你準備的各種程式設計學習資料。文章每週持續更新,我們下