C++面试八股文快问快答の基础篇
文章目錄
- 基礎篇
- 變量的聲明和定義有什么區別
- 簡述#ifdef、#else、#endif和#ifndef的作用
- 寫出int 、bool、 float 、指針變量與 “零值”比較的if 語句
- 結構體可以直接賦值嗎
- sizeof 和strlen 的區別
- sizeof求類型大小
- C 語言的關鍵字 static 和 C++ 的關鍵字 static 有什么區別
- C 語言的 malloc 和 C++ 中的 new 有什么區別
- 寫一個 “標準”宏MIN
- ++i和i++的區別
- volatile有什么作用
- C++中volatile的作用
- 一個參數可以既是const又是volatile嗎
- a 和&a 有什么區別
- 用C 編寫一個死循環程序
- 結構體內存對齊問題
- 全局變量和局部變量有什么區別?實怎么實現的?操作系統和編譯器是怎么知道的?
- 簡述C、C++程序編譯的內存分配情況
- 簡述strcpy、sprintf 與memcpy 的區別
- 請解析((void ()( ) )0)( )的含義
- typedef 和define 有什么區別
- 指針常量與常量指針區別
- 簡述隊列和棧的異同
- 設置地址為0x67a9 的整型變量的值為0xaa66
- 編碼實現字符串轉化為數字
- C語言的結構體和C++的有什么區別
- 簡述指針常量與常量指針的區別
- 如何避免“野指針”
- 句柄和指針的區別和聯系是什么?
- new/delete與malloc/free的區別是什么
- 說一說extern“C”
- 請你來說一下C++中struct和class的區別
- C++類內可以定義引用數據成員嗎?
- C++中類成員的訪問權限
- 什么是右值引用,跟左值又有什么區別?
- 面向對象的三大特征
- 說一說c++中四種cast轉換
- 1、const_cast
- 2、static_cast
- 3、dynamic_cast*
- 4、reinterpret_cast
- 5、為什么不使用C的強制轉換?
- C++的空類有哪些成員函數
- 對c++中的smart pointer四個智能指針:shared_ptr,unique_ptr,weak_ptr,auto_ptr的理解
- 說說強制類型轉換運算符
- 談談你對拷貝構造函數和賦值運算符的認識
- 在C++中,使用malloc申請的內存能否通過delete釋放?使用new申請的內存能否用free?
- 用C++設計一個不能被繼承的類
- C++自己實現一個String類
- 訪問基類的私有虛函數
- 對虛函數和多態的理解
- 簡述類成員函數的重寫、重載和隱藏的區別
- (1)重寫和重載主要有以下幾點不同。
- (2)隱藏和重寫、重載有以下幾點不同。
- 鏈表和數組有什么區別
- 存儲形式:
- 數據查找:
- 越界問題:
- 注意:
- 用兩個棧實現一個隊列的功能
- 共享數據的保護
- 常引用:
- 常對象:
- 常成員函數:
- extern int a:使其他文件也能訪問該變量
- 程序內存分配方式以及它們的區別
- 棧區(stack)
- 堆區(heap)
- 全局區(靜態區)(static)
- 常量存儲區
- 程序代碼區
- explicit
- mutable關鍵字
- 用const修飾函數的返回值
- 宏、const和enum
- stack的生存期
- 全局變量和static變量的區別
- 為什么棧要比堆速度要快
- c++ 析構函數調用時間
- 靜態綁定 動態綁定 (也叫動態連編,靜態連編)
- C語言的指針和c++的引用有什么區別?
- 請你說說C語言是怎么進行函數調用的
- C++中拷貝賦值函數的形參能否進行值傳遞?
- include頭文件的順序以及雙引號””和尖括號<>的區別
- 一個C++源文件從文本到可執行文件經歷的過程
- 內存泄漏原因和判斷方法
- 段錯誤的產生原因
- 段錯誤是什么
- 段錯誤產生的原因
- C++ 函數調用過程
- 如何調試c++ 多線程程序?
- 面向對象和面向過程的區別
- (過程)優點:
- (對象)優點:
- 關于引用賦值的多態:
- 模板的聲明和實現不能分開的原因
- C++類中引用成員和常量成員的初始化(初始化列表)
- memset為int型數組初始化問題
- 編譯器對 inline 函數的處理步驟
- 優缺點
- 優點
- 缺點
- 虛函數(virtual)可以是內聯函數(inline)嗎?
- 靜態庫和動態庫比較
- 靜態庫
- 動態庫(共享庫)
- 區別
- 虛函數、虛函數表,虛指針
- C/C++如何判斷兩個小數是否相等
- C++空類的大小
- c++ 空類,含有虛函數的類的大小(此問題都是在32位機器上而言)
- 32位機與64位機指針占用空間不同
- 引經據典
基礎篇
變量的聲明和定義有什么區別
變量的定義為變量分配地址和存儲空間, 變量的聲明不分配地址。一個變量可以在多個地方聲明, 但是只在一個地方定義。 加入extern 修飾的是變量的聲明,說明此變量將在文件以外或在文件后面部分定義。
說明:很多時候一個變量,只是聲明不分配內存空間,直到具體使用時才初始化,分配內存空間, 如外部變量。
簡述#ifdef、#else、#endif和#ifndef的作用
利用#ifdef、#endif將某程序功能模塊包括進去,以向特定用戶提供該功能。在不需要時用戶可輕易將其屏蔽。
#ifdef MATH#include “math.c” #endif//在子程序前加上標記,以便于追蹤和調試。#ifdef DEBUGprintf (“Indebugging…!”); #endif應對硬件的限制。由于一些具體應用環境的硬件不一樣,限于條件,本地缺乏這種設備,只能繞過硬件,直接寫出預期結果。
注意:雖然不用條件編譯命令而直接用if語句也能達到要求,但那樣做目標程序長(因為所有語句都編譯),運行時間長(因為在程序運行時間對if語句進行測試)。而采用條件編譯,可以減少被編譯的語句,從而減少目標程序的長度,減少運行時間。
寫出int 、bool、 float 、指針變量與 “零值”比較的if 語句
//int與零值比較 if ( n == 0 ) if ( n != 0 )//bool與零值比較 if (flag) // 表示flag為真 if (!flag) // 表示flag為假//float與零值比較 const float EPSINON = 0.00001; if ((x >= - EPSINON) && (x <= EPSINON) //其中EPSINON是允許的誤差(即精度)。//指針變量與零值比較 if (p == NULL) if (p != NULL)結構體可以直接賦值嗎
聲明時可以直接初始化,同一結構體的不同對象之間也可以直接賦值,但是當結構體中含有指針“成員”時一定要小心。
注意:當有多個指針指向同一段內存時,某個指針釋放這段內存可能會導致其他指針的非法操作。因此在釋放前一定要確保其他指針不再使用這段內存空間。
sizeof 和strlen 的區別
sizeof是一個操作符,strlen是庫函數。
sizeof的參數可以是數據的類型,也可以是變量,而strlen只能以結尾為‘\0’的字符串作參數。
編譯器在編譯時就計算出了sizeof的結果,而strlen函數必須在運行時才能計算出來。并且sizeof計算的是數據類型占內存的大小,而strlen計算的是字符串實際的長度。
數組做sizeof的參數不退化,傳遞給strlen就退化為指針了
sizeof求類型大小
類的大小為類的非靜態成員數據的類型大小之和,也就是說靜態成員數據不作考慮。
普通成員函數與sizeof無關。
虛函數由于要維護在虛函數表,所以要占據一個指針大小,也就是4字節。
類的總大小也遵守類似class字節對齊的,調整規則。
例如有如下結構體:
那么一個這樣的結構體變量占多大內存呢?也就是
cout<<sizeof(Stu)<<endl; 會輸出什么?
在了解字節對齊方式之前想當然的會以為:sizeof(Stu) = sizeof(int)+sizeof(char)+sizeof(float) = 9.
然而事實并非如此!
字節對齊原則:在系統默認的對齊方式下:每個成員相對于這個結構體變量地址的偏移量正好是該成員類型所占字節的整數倍,且最終占用字節數為成員類型中最大占用字節數的整數倍。
在這個例子中,id的偏移量為0(0=40),sex的偏移量為4(4=14),hight的偏移量為8(8=24),此時占用12字節,也同時滿足12=34.所以sizeof(Stu)=12.
struct A {char y;char z;long long x; }; 16字節 struct A {char y;char z;int x; }; 8字節struct A {char y;char* z;int x; };12字節 struct A {char y; }; 1字節我的總結:
最終大小一定是最大數據類型的整數倍;
靜態變量不占空間
每種類型的偏移量為自身的n倍;
詳細請查閱:struct/class等內存字節對齊問題詳解
C 語言的關鍵字 static 和 C++ 的關鍵字 static 有什么區別
在 C 中 static 用來修飾局部靜態變量和外部靜態變量、函數。而 C++中除了上述功能外,還用來定義類的成員變量和函數。即靜態成員和靜態成員函數。
注意:編程時 static 的記憶性,和全局性的特點可以讓在不同時期調用的函數進行通信,傳遞信息,而 C++的靜態成員則可以在多個對象實例間進行通信,傳遞信息。
C 語言的 malloc 和 C++ 中的 new 有什么區別
new 、delete 是操作符,可以重載,只能在C++ 中使用。
malloc、free 是函數,可以覆蓋,C、C++ 中都可以使用。
new 可以調用對象的構造函數,對應的delete 調用相應的析構函數。
malloc 僅僅分配內存,free 僅僅回收內存,并不執行構造和析構函數
new 、delete 返回的是某種數據類型指針,malloc、free 返回的是void 指針。
注意:malloc 申請的內存空間要用free 釋放,而new 申請的內存空間要用delete 釋放,不要混用。
寫一個 “標準”宏MIN
#define min(a,b) ((a)<=(b)?(a):(b))++i和i++的區別
++i先自增1,再返回,i++先返回i,再自增1
volatile有什么作用
狀態寄存器一類的并行設備硬件寄存器。
一個中斷服務子程序會訪問到的非自動變量。
多線程間被幾個任務共享的變量。
注意:雖然volatile在嵌入式方面應用比較多,但是在PC軟件的多線程中,volatile修飾的臨界變量也是非常實用的。
C++中volatile的作用
總結:建議編譯器不要對該變量進行優化
volatile是“易變的”、“不穩定”的意思。volatile是C的一個較為少用的關鍵字,它用來解決變量在“共享”環境下容易出現讀取錯誤的問題。
定義為volatile的變量是說這變量可能會被意想不到地改變,即在你程序運行過程中一直會變,你希望這個值被正確的處理,每次從內存中去讀這個值,而不是因編譯器優化從緩存的地方讀取,比如讀取緩存在寄存器中的數值,從而保證volatile變量被正確的讀取。
在單任務的環境中,一個函數體內部,如果在兩次讀取變量的值之間的語句沒有對變量的值進行修改,那么編譯器就會設法對可執行代碼進行優化。由于訪問寄存器的速度要快過RAM(從RAM中讀取變量的值到寄存器),以后只要變量的值沒有改變,就一直從寄存器中讀取變量的值,而不對RAM進行訪問。
而在多任務環境中,雖然在一個函數體內部,在兩次讀取變量之間沒有對變量的值進行修改,但是該變量仍然有可能被其他的程序(如中斷程序、另外的線程等)所修改。如果這時還是從寄存器而不是從RAM中讀取,就會出現被修改了的變量值不能得到及時反應的問題。如下程序對這一現象進行了模擬。
#include <iostream> using namespace std;int main(int argc,char* argv[]) {int i=10;int a=i;cout<<a<<endl;_asm{mov dword ptr [ebp-4],80}int b=i;cout<<b<<endl; } /* 程序在VS2012環境下生成Release版本,輸出結果是: 10 10 */閱讀以上程序,注意以下幾個要點:
以上代碼必須在Release模式下考查,因為只有Release模式下才會對程序代碼進行優化,而這種優化在變量共享的環境下容易引發問題。
在語句b=i;之前,已經通 過內聯匯編代碼修改了i的值,但是i的變化卻沒有反映到b中,如果i是一個被多個任務共享的變量,這種優化帶來的錯誤很可能是致命的。
匯編代碼[ebp-4]表示變量i的存儲單元,因為ebp是擴展基址指針寄存器,存放函數所屬棧的棧底地址,先入棧,占用4個字節。隨著函數內申明的局部變量的增多,esp(棧頂指針寄存器)就會相應的減小,因為棧的生長方向由高地址向低地址生長。i為第一個變量,棧空間已被ebp入棧占用了4個字節,所以i的地址為ebp-i,[ebp-i]則表示變量i的存儲單元。
一個參數可以既是const又是volatile嗎
可以,用const和volatile同時修飾變量,表示這個變量在程序內部是只讀的,不能改變的,只在程序外部條件變化下改變,并且編譯器不會優化這個變量。每次使用這個變量時,都要小心地去內存讀取這個變量的值,而不是去寄存器讀取它的備份。
注意:在此一定要注意const的意思,const只是不允許程序中的代碼改變某一變量,其在編譯期發揮作用,它并沒有實際地禁止某段內存的讀寫特性。
a 和&a 有什么區別
&a:其含義就是“變量a的地址”。
*a:用在不同的地方,含義也不一樣。
在聲明語句中,*a只說明a是一個指針變量,如int *a;
在其他語句中,*a前面沒有操作數且a是一個指針時,*a代表指針a指向的地址內存放的數據,如b=*a;
*a前面有操作數且a是一個普通變量時,a代表乘以a,如c=ba。
用C 編寫一個死循環程序
while(1) { }注意:很多種途徑都可實現同一種功能,但是不同的方法時間和空間占用度不同,特別是對于嵌入 式軟件,處理器速度比較慢,存儲空間較小,所以時間和空間優勢是選擇各種方法的首要考慮條件。
結構體內存對齊問題
請寫出以下代碼的輸出結果:
#include<stdio.h> struct S1 {int i:8;char j:4;int a:4;double b; };struct S2 {int i:8;char j:4;double b;int a:4; };struct S3 {int i;char j;double b;int a; };int main() {printf("%d\n",sizeof(S1)); // 輸出8printf("%d\n",sizeof(S1); // 輸出12printf("%d\n",sizeof(Test3)); // 輸出8return 0; } sizeof(S1)=16 sizeof(S2)=24 sizeof(S3)=32說明:結構體作為一種復合數據類型,其構成元素既可以是基本數據類型的變量,也可以是一些復合型類型數據。對此,編譯器會自動進行成員變量的對齊以提高運算效率。默認情況下,按自然對齊條件分配空間。各個成員按照它們被聲明的順序在內存中順序存儲,第一個成員的地址和整個結構的地址相同,向結構體成員中size最大的成員對齊。
許多實際的計算機系統對基本類型數據在內存中存放的位置有限制,它們會要求這些數據的首地址的值是某個數k(通常它為4或8)的倍數,而這個k則被稱為該數據類型的對齊模數。
全局變量和局部變量有什么區別?實怎么實現的?操作系統和編譯器是怎么知道的?
全局變量是整個程序都可訪問的變量,誰都可以訪問,生存期在整個程序從運行到結束(在程序結束時所占內存釋放);
而局部變量存在于模塊(子程序,函數)中,只有所在模塊可以訪問,其他模塊不可直接訪問,模塊結束(函數調用完畢),局部變量消失,所占據的內存釋放。
操作系統和編譯器,可能是通過內存分配的位置來知道的,全局變量分配在全局數據段并且在程序開始運行的時候被加載.局部變量則分配在堆棧里面。
簡述C、C++程序編譯的內存分配情況
從靜態存儲區域分配:
內存在程序編譯時就已經分配好,這塊內存在程序的整個運行期間都存在。速度快、不容易出錯, 因為有系統會善后。例如全局變量,static 變量,常量字符串等。
在棧上分配:
在執行函數時,函數內局部變量的存儲單元都在棧上創建,函數執行結束時這些存儲單元自動被釋 放。棧內存分配運算內置于處理器的指令集中,效率很高,但是分配的內存容量有限。大小為2M。
從堆上分配:
即動態內存分配。程序在運行的時候用 malloc 或new 申請任意大小的內存,程序員自己負責在何 時用free 或delete 釋放內存。動態內存的生存期由程序員決定,使用非常靈活。如果在堆上分配了空間,就有責任回收它,否則運行的程序會出現內存泄漏,另外頻繁地分配和釋放不同大小的堆空間將會產生 堆內碎塊。
一個C、C++程序編譯時內存分為5 大存儲區:堆區、棧區、全局區、文字常量區、程序代碼區。
簡述strcpy、sprintf 與memcpy 的區別
操作對象不同,strcpy 的兩個操作對象均為字符串,sprintf 的操作源對象可以是多種數據類型, 目的操作對象是字符串,memcpy 的兩個對象就是兩個任意可操作的內存地址,并不限于何種數據類型。
執行效率不同,memcpy 最高,strcpy 次之,sprintf 的效率最低。
實現功能不同,strcpy 主要實現字符串變量間的拷貝,sprintf 主要實現其他數據類型格式到字 符串的轉化,memcpy 主要是內存塊間的拷貝。
注意:strcpy、sprintf 與memcpy 都可以實現拷貝的功能,但是針對的對象不同,根據實際需求,來 選擇合適的函數實現拷貝功能。
請解析((void ()( ) )0)( )的含義
void (0)( ) :是一個返回值為void,參數為空的函數指針0。
(void ()( ))0:把0轉變成一個返回值為void,參數為空的函數指針。
(void ()( ))0:在上句的基礎上加表示整個是一個返回值為void,無參數,并且起始地址為0的函數的名字。
((void (*)( ))0)( ):這就是上句的函數名所對應的函數的調用。
typedef 和define 有什么區別
用法不同:
typedef 用來定義一種數據類型的別名,增強程序的可讀性。define 主要用來定義 常量,以及書寫復雜使用頻繁的宏。
執行時間不同:
typedef 是編譯過程的一部分,有類型檢查的功能。define 是宏定義,是預編譯的部分,其發生在編譯之前,只是簡單的進行字符串的替換,不進行類型的檢查。
作用域不同:
typedef 有作用域限定。define 不受作用域約束,只要是在define 聲明后的引用 都是正確的。
對指針的操作不同:
typedef 和define 定義的指針時有很大的區別。
注意:typedef 定義是語句,因為句尾要加上分號。而define 不是語句,千萬不能在句尾加分號。
指針常量與常量指針區別
指針常量是指定義了一個指針,這個指針的值只能在定義時初始化,其他地方不能改變。常量指針 是指定義了一個指針,這個指針指向一個只讀的對象,不能通過常量指針來改變這個對象的值。 指針常量強調的是指針的不可改變性,而常量指針強調的是指針對其所指對象的不可改變性。
注意:無論是指針常量還是常量指針,其最大的用途就是作為函數的形式參數,保證實參在被調用 函數中的不可改變特性。
簡述隊列和棧的異同
隊列和棧都是線性存儲結構,但是兩者的插入和刪除數據的操作不同,隊列是“先進先出”,棧是 “后進先出”。
注意:區別棧區和堆區。堆區的存取是“順序隨意”,而棧區是“后進先出”。棧由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等。其操作方式類似于數據結構中的棧。堆一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS 回收。分配方式類似于鏈表。 它與本題中的堆和棧是兩回事。堆棧只是一種數據結構,而堆區和棧區是程序的不同內存存儲區域。
設置地址為0x67a9 的整型變量的值為0xaa66
int *ptr; ptr = (int *)0x67a9; *ptr = 0xaa66;注意:這道題就是強制類型轉換的典型例子,無論在什么平臺地址長度和整型數據的長度是一樣的, 即一個整型數據可以強制轉換成地址指針類型,只要有意義即可。
編碼實現字符串轉化為數字
編碼實現函數atoi(),設計一個程序,把一個字符串轉化為一個整型數值。例如數字:“5486321 ”, 轉化成字符:5486321。
int myAtoi(const char * str) {int num = 0; //保存轉換后的數值 int isNegative = 0; //記錄字符串中是否有負號 int n =0; char *p = str; if(p == NULL) //判斷指針的合法性 { return -1; } while(*p++ != '\0') //計算數字符串度 { n++; } p = str; if(p[0] == '-') //判斷數組是否有負號 { isNegative = 1; } char temp = '0'; for(int i = 0 ; i < n; i++) { char temp = *p++; if(temp > '9' ||temp < '0') //濾除非數字字符 { continue; } if(num !=0 || temp != '0') //濾除字符串開始的0 字符 { temp -= 0x30; //將數字字符轉換為數值 num += temp *int( pow(10 , n - 1 -i) ); } } if(isNegative) //如果字符串中有負號,將數值取反 { return (0 - num); } else { return num; //返回轉換后的數值 } }C語言的結構體和C++的有什么區別
C語言的結構體是不能有函數成員的,而C++的類可以有。
C語言的結構體中數據成員是沒有private、public和protected訪問限定的。而C++的類的成員有這些訪問限定。
C語言的結構體是沒有繼承關系的,而C++的類卻有豐富的繼承關系。
注意:雖然C的結構體和C++的類有很大的相似度,但是類是實現面向對象的基礎。而結構體只可以簡單地理解為類的前身。
簡述指針常量與常量指針的區別
指針常量是指定義了一個指針,這個指針的值只能在定義時初始化,其他地方不能改變。常量指針是指定義了一個指針,這個指針指向一個只讀的對象,不能通過常量指針來改變這個對象的值。
指針常量強調的是指針的不可改變性,而常量指針強調的是指針對其所指對象的不可改變性。
注意:無論是指針常量還是常量指針,其最大的用途就是作為函數的形式參數,保證實參在被調用函數中的不可改變特性。
如何避免“野指針”
指針變量聲明時沒有被初始化。解決辦法:指針聲明時初始化,可以是具體的地址值,也可讓它指向NULL。
指針p被free或者delete之后,沒有置為NULL。解決辦法:指針指向的內存空間被釋放后指針應該指向NULL。
指針操作超越了變量的作用范圍。解決辦法:在變量的作用域結束前釋放掉變量的地址空間并且讓指針指向NULL。
句柄和指針的區別和聯系是什么?
句柄和指針其實是兩個截然不同的概念。Windows系統用句柄標記系統資源,隱藏系統的信息。你只要知道有這個東西,然后去調用就行了,它是個32it的uint。指針則標記某個物理內存地址,兩者是不同的概念。
new/delete與malloc/free的區別是什么
new能自動計算需要分配的內存空間,而malloc需要手工計算字節數。
int *p = new int[2]; int *q = (int )malloc(2sizeof(int));new與delete直接帶具體類型的指針,malloc和free返回void類型的指針。
new類型是安全的,而malloc不是。例如int *p = new float[2];就會報錯;而int p = malloc(2sizeof(int))編譯時編譯器就無法指出錯誤來。
new一般分為兩步:new操作和構造。new操作對應與malloc,但new操作可以重載,可以自定義內存分配策略,不做內存分配,甚至分配到非內存設備上,而malloc不行。
new調用構造函數,malloc不能;delete調用析構函數,而free不能。
malloc/free需要庫文件stdlib.h的支持,new/delete則不需要!
注意:delete和free被調用后,內存不會立即回收,指針也不會指向空,delete或free僅僅是告訴操作系統,這一塊內存被釋放了,可以用作其他用途。但是由于沒有重新對這塊內存進行寫操作,所以內存中的變量數值并沒有發生變化,出現野指針的情況。因此,釋放完內存后,應該講該指針指向NULL。
說一說extern“C”
extern "C"的主要作用就是為了能夠正確實現C++代碼調用其他C語言代碼。加上extern "C"后,會指示編譯器這部分代碼按C語言(而不是C++)的方式進行編譯。由于C++支持函數重載,因此編譯器編譯函數的過程中會將函數的參數類型也加到編譯后的代碼中,而不僅僅是函數名;而C語言并不支持函數重載,因此編譯C語言代碼的函數時不會帶上函數的參數類型,一般只包括函數名。
這個功能十分有用處,因為在C++出現以前,很多代碼都是C語言寫的,而且很底層的庫也是C語言寫的,為了更好的支持原來的C代碼和已經寫好的C語言庫,需要在C++中盡可能的支持C,而extern "C"就是其中的一個策略。
C++代碼調用C語言代碼在C++的頭文件中使用在多個人協同開發時,可能有的人比較擅長C語言,而有的人擅長C++,這樣的情況下也會有用到。
請你來說一下C++中struct和class的區別
在C++中,class和struct做類型定義是只有兩點區別:
默認繼承權限不同,class繼承默認是private繼承,而struct默認是public繼承
class還可用于定義模板參數,像typename,但是關鍵字struct不能同于定義模板參數 C++保留struct關鍵字,原因
保證與C語言的向下兼容性,C++必須提供一個struct
C++中的struct定義必須百分百地保證與C語言中的struct的向下兼容性,把C++中的最基本的對象單元規定為class而不是struct,就是為了避免各種兼容性要求的限制
對struct定義的擴展使C語言的代碼能夠更容易的被移植到C++中
C++類內可以定義引用數據成員嗎?
可以,必須通過成員函數初始化列表初始化。
C++中類成員的訪問權限
C++通過 public、protected、private 三個關鍵字來控制成員變量和成員函數的訪問權限,它們分別表示公有的、受保護的、私有的,被稱為成員訪問限定符。在類的內部(定義類的代碼內部),無論成員被聲明為 public、protected 還是 private,都是可以互相訪問的,沒有訪問權限的限制。在類的外部(定義類的代碼之外),只能通過對象訪問成員,并且通過對象只能訪問 public 屬性的成員,不能訪問 private、protected 屬性的成員
什么是右值引用,跟左值又有什么區別?
左值和右值的概念:
左值:能取地址,或者具名對象,表達式結束后依然存在的持久對象;
右值:不能取地址,匿名對象,表達式結束后就不再存在的臨時對象; 區別:
左值能尋址,右值不能;
左值能賦值,右值不能;
左值可變,右值不能(僅對基礎類型適用,用戶自定義類型右值引用可以通過成員函數改變);
面向對象的三大特征
封裝性:將客觀事物抽象成類,每個類對自身的數據和方法實行 protection (private , protected , public )。
繼承性:廣義的繼承有三種實現形式:實現繼承(使用基類的屬性和方法而無需額外編碼的能力)、可 視繼承(子窗體使用父窗體的外觀和實現代碼)、接口繼承(僅使用屬性和方法,實現滯后到子類實現)。
多態性:是將父類對象設置成為和一個或更多它的子對象相等的技術。用子類對象給父類對象賦值 之后,父類對象就可以根據當前賦值給它的子對象的特性以不同的方式運作。
說一說c++中四種cast轉換
C++中四種類型轉換是:static_cast, dynamic_cast, const_cast, reinterpret_cast
1、const_cast
用于將const變量轉為非const
2、static_cast
用于各種隱式轉換,比如非const轉const,void*轉指針等, static_cast能用于多態向上轉化,如果向下轉能成功但是不安全,結果未知;
3、dynamic_cast*
用于動態類型轉換。只能用于含有虛函數的類,用于類層次間的向上和向下轉化。只能轉指針或引用。向下轉化時,如果是非法的***對于指針返回NULL,對于引用拋異常***。要深入了解內部轉換的原理。
向上轉換:指的是子類向基類的轉換
向下轉換:指的是基類向子類的轉換
它通過判斷在執行到該語句的時候變量的運行時類型和要轉換的類型是否相同來判斷是否能夠進行向下轉換。
4、reinterpret_cast
幾乎什么都可以轉,比如將int轉指針,可能會出問題,盡量少用;
5、為什么不使用C的強制轉換?
C的強制轉換表面上看起來功能強大什么都能轉,但是轉化不夠明確,不能進行錯誤檢查,容易出錯。
C++的空類有哪些成員函數
缺省構造函數。
缺省拷貝構造函數。
缺省析構函數。
缺省賦值運算符。
缺省取址運算符。
缺省取址運算符 const 。
注意:有些書上只是簡單的介紹了前四個函數。沒有提及后面這兩個函數。但后面這兩個函數也是 空類的默認函數。另外需要注意的是,只有當實際使用這些函數的時候,編譯器才會去定義它們。
對c++中的smart pointer四個智能指針:shared_ptr,unique_ptr,weak_ptr,auto_ptr的理解
C++里面的四個智能指針: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三個是c++11支持,并且第一個已經被11棄用。
智能指針的作用是管理一個指針,因為存在以下這種情況:申請的空間在函數結束時忘記釋放,造成內存泄漏。使用智能指針可以很大程度上的避免這個問題,因為智能指針就是一個類,當超出了類的作用域是,類會自動調用析構函數,析構函數會自動釋放資源。所以智能指針的作用原理就是在函數結束時自動釋放內存空間,不需要手動釋放內存空間。
auto_ptr(c++98的方案,cpp11已經拋棄)
采用所有權模式。
此時不會報錯,p2剝奪了p1的所有權,但是當程序運行時訪問p1將會報錯。所以auto_ptr的缺點是:存在潛在的內存崩潰問題!
unique_ptr(替換auto_ptr)
unique_ptr實現獨占式擁有或嚴格擁有概念,保證同一時間內只有一個智能指針可以指向該對象。它對于避免資源泄露(例如“以new創建對象后因為發生異常而忘記調用delete”)特別有用。
采用所有權模式。
unique_ptr p3 (new string (“auto”)); //#4 unique_ptr p4; //#5 p4 = p3;//此時會報錯!!編譯器認為p4=p3非法,避免了p3不再指向有效數據的問題。因此,unique_ptr比auto_ptr更安全。
另外unique_ptr還有更聰明的地方:當程序試圖將一個 unique_ptr 賦值給另一個時,如果源 unique_ptr 是個臨時右值,編譯器允許這么做;如果源 unique_ptr 將存在一段時間,編譯器將禁止這么做,比如:
unique_ptr pu1(new string (“hello world”)); unique_ptr pu2; pu2 = pu1; // #1 not allowed unique_ptr pu3; pu3 = unique_ptr(new string (“You”)); // #2 allowed其中#1留下懸掛的unique_ptr(pu1),這可能導致危害。而#2不會留下懸掛的unique_ptr,因為它調用 unique_ptr 的構造函數,該構造函數創建的臨時對象在其所有權讓給 pu3 后就會被銷毀。這種隨情況而已的行為表明,unique_ptr 優于允許兩種賦值的auto_ptr 。
注:如果確實想執行類似與#1的操作,要安全的重用這種指針,可給它賦新值。C++有一個標準庫函數std::move(),讓你能夠將一個unique_ptr賦給另一個。例如:
unique_ptr ps1, ps2; ps1 = demo(“hello”); ps2 = move(ps1); ps1 = demo(“alexia”); cout << *ps2 << *ps1 << endl; shared_ptrshared_ptr實現共享式擁有概念。多個智能指針可以指向相同對象,該對象和其相關資源會在“最后一個引用被銷毀”時候釋放。從名字share就可以看出了資源可以被多個指針共享,它使用計數機制來表明資源被幾個指針共享。可以通過成員函數use_count()來查看資源的所有者個數。除了可以通過new來構造,還可以通過傳入auto_ptr, unique_ptr,weak_ptr來構造。當我們調用release()時,當前指針會釋放資源所有權,計數減一。當計數等于0時,資源會被釋放。
shared_ptr 是為了解決 auto_ptr 在對象所有權上的局限性(auto_ptr 是獨占的), 在使用引用計數的機制上提供了可以共享所有權的智能指針。
成員函數:
use_count 返回引用計數的個數
unique 返回是否是獨占所有權( use_count 為 1)
swap 交換兩個 shared_ptr 對象(即交換所擁有的對象)
reset 放棄內部對象的所有權或擁有對象的變更, 會引起原有對象的引用計數的減少
get 返回內部對象(指針), 由于已經重載了()方法, 因此和直接使用對象是一樣的.如 shared_ptrsp(new int(1)); sp 與 sp.get()是等價的
weak_ptr
weak_ptr 是一種不控制對象生命周期的智能指針, 它指向一個 shared_ptr 管理的對象. 進行該對象的內存管理的是那個強引用的 shared_ptr. weak_ptr只是提供了對管理對象的一個訪問手段。weak_ptr 設計的目的是為配合 shared_ptr 而引入的一種智能指針來協助 shared_ptr 工作, 它只可以從一個 shared_ptr 或另一個 weak_ptr 對象構造, 它的構造和析構不會引起引用記數的增加或減少。weak_ptr是用來解決shared_ptr相互引用時的死鎖問題,如果說兩個shared_ptr相互引用,那么這兩個指針的引用計數永遠不可能下降為0,資源永遠不會釋放。它是對對象的一種弱引用,不會增加對象的引用計數,和shared_ptr之間可以相互轉化,shared_ptr可以直接賦值給它,它可以通過調用lock函數來獲得shared_ptr。
可以看到fun函數中pa ,pb之間互相引用,兩個資源的引用計數為2,當要跳出函數時,智能指針pa,pb析構時兩個資源引用計數會減一,但是兩者引用計數還是為1,導致跳出函數時資源沒有被釋放(A B的析構函數沒有被調用),如果把其中一個改為weak_ptr就可以了,我們把類A里面的shared_ptr pb_; 改為weak_ptr pb_; 運行結果如下,這樣的話,資源B的引用開始就只有1,當pb析構時,B的計數變為0,B得到釋放,B釋放的同時也會使A的計數減一,同時pa析構時使A的計數減一,那么A的計數為0,A得到釋放。
注意:不能通過weak_ptr直接訪問對象的方法,比如B對象中有一個方法print(),我們不能這樣訪問,pa->pb_->print(); 英文pb_是一個weak_ptr,應該先把它轉化為shared_ptr,如:shared_ptr p = pa->pb_.lock(); p->print();
說說強制類型轉換運算符
static_cast
用于非多態類型的轉換
不執行運行時類型檢查(轉換安全性不如 dynamic_cast)
通常用于轉換數值數據類型(如 float -> int)
可以在整個類層次結構中移動指針,子類轉化為父類安全(向上轉換),父類轉化為子類不安全(因為子類可能有不在父類的字段或方法)
dynamic_cast
用于多態類型的轉換
執行行運行時類型檢查
只適用于指針或引用
對不明確的指針的轉換將失敗(返回 nullptr),但不引發異常
可以在整個類層次結構中移動指針,包括向上轉換、向下轉換
const_cast
用于刪除 const、volatile 和 __unaligned 特性(如將 const int 類型轉換為 int 類型 ) reinterpret_cast
用于位的簡單重新解釋
濫用 reinterpret_cast 運算符可能很容易帶來風險。除非所需轉換本身是低級別的,否則應- 使用其他強制轉換運算符之一。
允許將任何指針轉換為任何其他指針類型(如 char* 到 int* 或 One_class* 到 Unrelated_class* 之類的轉換,但其本身并不安全)
也允許將任何整數類型轉換為任何指針類型以及反向轉換。
reinterpret_cast 運算符不能丟掉 const、volatile 或 __unaligned 特性。
reinterpret_cast 的一個實際用途是在哈希函數中,即,通過讓兩個不同的值幾乎不以相同的索引結尾的方式將值映射到索引。
bad_cast
由于強制轉換為引用類型失敗,dynamic_cast 運算符引發 bad_cast 異常。
bad_cast 使用
談談你對拷貝構造函數和賦值運算符的認識
拷貝構造函數和賦值運算符重載有以下兩個不同之處:
拷貝構造函數生成新的類對象,而賦值運算符不能。
由于拷貝構造函數是直接構造一個新的類對象,所以在初始化這個對象之前不用檢驗源對象 是否和新建對象相同。而賦值運算符則需要這個操作,另外賦值運算中如果原來的對象中有內存分配要先把內存釋放掉。
注意:當有類中有指針類型的成員變量時,一定要重寫拷貝構造函數和賦值運算符,不要使用默認 的。
在C++中,使用malloc申請的內存能否通過delete釋放?使用new申請的內存能否用free?
不能,malloc /free主要為了兼容C,new和delete 完全可以取代malloc /free的。malloc /free的操作對象都是必須明確大小的。而且不能用在動態類上。new 和delete會自動進行類型檢查和大小,malloc/free不能執行構造函數與析構函數,所以動態對象它是不行的。當然從理論上說使用malloc申請的內存是可以通過delete釋放的。不過一般不這樣寫的。而且也不能保證每個C++的運行時都能正常。
用C++設計一個不能被繼承的類
template <typename T> class A { friend T; private: A() {} ~A() {} }; class B : virtual public A<B> { public: B() {} ~B() {} }; class C : virtual public B { public: C() {} ~C() {} }; void main( void ) { B b; //C c; return; }注意:構造函數是繼承實現的關鍵,每次子類對象構造時,首先調用的是父類的構造函數,然后才 是自己的。
C++自己實現一個String類
- #include <iostream>#include <cstring>using namespace std;class String{ public:// 默認構造函數String(const char *str = nullptr);// 拷貝構造函數String(const String &str);// 析構函數~String();// 字符串賦值函數String& operator=(const String &str);private:char *m_data;int m_size; };// 構造函數 String::String(const char *str) {if(str == nullptr) // 加分點:對m_data加NULL 判斷{m_data = new char[1]; // 得分點:對空字符串自動申請存放結束標志'\0'的m_data[0] = '\0';m_size = 0;}else{m_size = strlen(str);m_data = new char[m_size + 1];strcpy(m_data, str);} }// 拷貝構造函數 String::String(const String &str) // 得分點:輸入參數為const型 {m_size = str.m_size;m_data = new char[m_size + 1]; //加分點:對m_data加NULL 判斷strcpy(m_data, str.m_data); }// 析構函數 String::~String() {delete[] m_data; }// 字符串賦值函數 String& String::operator=(const String &str) // 得分點:輸入參數為const {if(this == &str) //得分點:檢查自賦值return *this;delete[] m_data; //得分點:釋放原有的內存資源 m_size = strlen(str.m_data); m_data = new char[m_size + 1]; //加分點:對m_data加NULL 判斷 strcpy(m_data, str.m_data); return *this; //得分點:返回本對象的引用}訪問基類的私有虛函數
寫出以下程序的輸出結果:
#include <iostream.h> class A { virtual void g() { cout << "A::g" << endl; } private: virtual void f() { cout << "A::f" << endl; } }; class B : public A { void g() { cout << "B::g" << endl; } virtual void h() { cout << "B::h" << endl; } }; typedef void( *Fun )( void ); void main() { B b; Fun pFun; for(int i = 0 ; i < 3; i++) { pFun = ( Fun )*( ( int* ) * ( int* )( &b ) + i ); pFun(); } }輸出結果:
B::g A::f B::h注意:考察了面試者對虛函數的理解程度。一個對虛函數不了解的人很難正確的做出本題。 在學習面向對象的多態性時一定要深刻理解虛函數表的工作原理。
對虛函數和多態的理解
多態的實現主要分為靜態多態和動態多態,靜態多態主要是重載,在編譯的時候就已經確定;動態多態是用虛函數機制實現的,在運行期間動態綁定。舉個例子:一個父類類型的指針指向一個子類對象時候,使用父類的指針去調用子類中重寫了的父類中的虛函數的時候,會調用子類重寫過后的函數,在父類中聲明為加了virtual關鍵字的函數,在子類中重寫時候不需要加virtual也是虛函數。
虛函數的實現:在有虛函數的類中,類的最開始部分是一個虛函數表的指針,這個指針指向一個虛函數表,表中放了虛函數的地址,實際的虛函數在代碼段(.text)中。當子類繼承了父類的時候也會繼承其虛函數表,當子類重寫父類中虛函數時候,會將其繼承到的虛函數表中的地址替換為重新寫的函數地址。使用了虛函數,會增加訪問內存開銷,降低效率。
簡述類成員函數的重寫、重載和隱藏的區別
(1)重寫和重載主要有以下幾點不同。
范圍的區別:被重寫的和重寫的函數在兩個類中,而重載和被重載的函數在同一個類中。
參數的區別:被重寫函數和重寫函數的參數列表一定相同,而被重載函數和重載函數的參數列表一 定不同。
virtual 的區別:重寫的基類中被重寫的函數必須要有virtual 修飾,而重載函數和被重載函數可以被 virtual 修飾,也可以沒有。
(2)隱藏和重寫、重載有以下幾點不同。
與重載的范圍不同:和重寫一樣,隱藏函數和被隱藏函數不在同一個類中。
參數的區別:隱藏函數和被隱藏的函數的參數列表可以相同,也可不同,但是函數名肯定要相同。 當參數不相同時,無論基類中的參數是否被virtual 修飾,基類的函數都是被隱藏,而不是被重寫。
注意:雖然重載和覆蓋都是實現多態的基礎,但是兩者實現的技術完全不相同,達到的目的也是完 全不同的,覆蓋是動態態綁定的多態,而重載是靜態綁定的多態。
鏈表和數組有什么區別
存儲形式:
數組是一塊連續的空間,聲明時就要確定長度。鏈表是一塊可不連續的動態空間, 長度可變,每個結點要保存相鄰結點指針。
數據查找:
數組的線性查找速度快,查找操作直接使用偏移地址。鏈表需要按順序檢索結點, 效率低。
數據插入或刪除:
鏈表可以快速插入和刪除結點,而數組則可能需要大量數據移動。
越界問題:
鏈表不存在越界問題,數組有越界問題。
注意:
在選擇數組或鏈表數據結構時,一定要根據實際需要進行選擇。數組便于查詢,鏈表便于插 入刪除。數組節省空間但是長度固定,鏈表雖然變長但是占了更多的存儲空間。
用兩個棧實現一個隊列的功能
typedef struct node { int data; node *next; }node,*LinkStack; //創建空棧: LinkStack CreateNULLStack( LinkStack &S) { S = (LinkStack)malloc( sizeof( node ) ); // 申請新結點 if( NULL == S) { printf("Fail to malloc a new node.\n");return NULL; } S->data = 0; //初始化新結點 S->next = NULL; return S; } //棧的插入函數: LinkStack Push( LinkStack &S, int data) { if( NULL == S) //檢驗棧 { printf("There no node in stack!"); return NULL; } LinkStack p = NULL; p = (LinkStack)malloc( sizeof( node ) ); // 申請新結點 if( NULL == p) { printf("Fail to malloc a new node.\n"); return S; } if( NULL == S->next) { p->next = NULL; } else { p->next = S->next; } p->data = data; //初始化新結點 S->next = p; //插入新結點 return S; } //出棧函數: node Pop( LinkStack &S) { node temp; temp.data = 0; temp.next = NULL; if( NULL == S) //檢驗棧 { printf("There no node in stack!"); return temp; } temp = *S; if( S->next == NULL ) { printf("The stack is NULL,can't pop!\n"); return temp; } LinkStack p = S ->next; //節點出棧 S->next = S->next->next; temp = *p; free( p ); p = NULL; return temp; } //雙棧實現隊列的入隊函數: LinkStack StackToQueuPush( LinkStack &S, int data) { node n; LinkStack S1 = NULL; CreateNULLStack( S1 ); //創建空棧 while( NULL != S->next ) //S 出棧入S1 { n = Pop( S ); Push( S1, n.data ); } Push( S1, data ); //新結點入棧 while( NULL != S1->next ) //S1 出棧入S { n = Pop( S1 ); Push( S, n.data ); } return S; }注意:用兩個棧能夠實現一個隊列的功能,那用兩個隊列能否實現一個隊列的功能呢?結果是否定 的,因為棧是先進后出,將兩個棧連在一起,就是先進先出。而隊列是現先進先出,無論多少個連在一 起都是先進先出,而無法實現先進后出。
共享數據的保護
常引用:
使所引用的形參不能被更新
void display(const double& a);
常對象:
在生存期內不能被更新,但必須被初始化
A const a(3,4);
常成員函數:
不能修改對象中數據成員,也不能調用類中沒有被const 修飾的成員函數(常對象唯一的對外接口).如果聲明了一個常對象,則該對象只能調用他的常函數!->可以用于對重載函數的區分;
void print();
void print() const;
extern int a:使其他文件也能訪問該變量
聲明一個函數或定義函數時,冠以static的話,函數的作用域就被限制在了當前編譯單元,當前編譯單元內也必須包含函數的定義,也只在其編譯單元可見,其他單元不能調用這個函數(每一個cpp 文件就是一個編譯單元)。
程序內存分配方式以及它們的區別
內存分配大致上可以分成5塊:
棧區(stack)
棧,就是那些由編譯器在需要時分配,在不需要的時候自動清除的變量的存儲區。里面的變量通常是局部變量、函數參數等。(由編譯器管理)
堆區(heap)
一般由程序員分配、釋放,若程序員不是放,程序結束時可能由系統回收。注意,它與數據結構中的堆是兩回事,分配方式類似于鏈表。
全局區(靜態區)(static)
全局變量和靜態變量被分配到同
一塊內存中。程序結束后由系統釋放。
常量存儲區
常量字符串就是放在這里的,不允許修改,程序結束后由系統釋放。
程序代碼區
存放函數體的二進制代碼。
explicit
函數聲明時加上explicit可以阻止函數參數被隱式轉換。
Class A {explicit A(int a); }Void main() {A a1=12; //不加explicit時會被隱式轉換位 A a1=A(12);加了此時編譯器會報錯。 }被聲明為explicit的構造函數通常比non-explicit 函數更受歡迎。
mutable關鍵字
mutalbe的中文意思是“可變的,易變的”,跟constant(既C++中的const)是反義詞。在C++中,mutable也是為了突破const的限制而設置的。被mutable修飾的變量(mutable只能由于修飾類的非靜態數據成員),將永遠處于可變的狀態,即使在一個const函數中。
我們知道,假如類的成員函數不會改變對象的狀態,那么這個成員函數一般會聲明為const。但是,有些時候,我們需要在const的函數里面修改一些跟類狀態無關的數據成員,那么這個數據成員就應該被mutalbe來修飾。(使用mutable修飾的數據成員可以被const成員函數修改)。
用const修飾函數的返回值
如果給以“指針傳遞”方式的函數返回值加const修飾,那么函數返回值(即指針)的內容不能被修改,該返回值只能被賦給加const修飾的同類型指針。例如函數
Const char * GetString(void); // 如下語句將出現編譯錯誤: char*str = GetString(); // 正確的用法是 Const char *str =GetString();宏、const和enum
stack的生存期
C++中的static對象是指存儲區不屬于stack和heap、"壽命"從被構造出來直至程序結束為止的對象。這些對象包括全局對象,定義于namespace作用域的對象,在class、function以及file作用域中被聲明為static的對象。其中,函數內的static對象稱為local static對象,而其它static對象稱為non-local static對象。
這兩者在何時被初始化(構造)這個問題上存在細微的差別:
對于local static對象,在其所屬的函數被調用之前,該對象并不存在,即只有在第一次調用對應函數時,local static對象才被構造出來。
而對于non-local static對象,在main()函數開始前就已經被構造出來,并在main()函數結束后被析構。
建議:
1.對內置對象進行手工初始化,因為C++不保證初始化它們。
2.構造函數最好使用成員初值列,而不要在構造函數本體中使用賦值操作。初值列中列出的成員變量,其排序次序應該和它們在class中的聲明次序相同(初始化順序與聲明變量順序一致)。
3.為免除“跨編譯單元的初始化次序問題”,盡量以local static對象替換non-local static對象。
全局變量和static變量的區別
全局變量本身就是靜態存儲方式, 靜態全局變量當然也是靜態存儲方式。 這兩者在存儲方式上并無不同。這兩者的區別在于非靜態全局變量的作用域是整個源程序, 當一個源程序由多個源文件組成時,非靜態的全局變量在各個源文件中都是有效的。 而靜態全局變量則限制了其作用域, 即只在定義該變量的源文件內有效, 在同一源程序的其它源文件中不能使用它。由于靜態全局變量的作用域局限于一個源文件內,只能為該源文件內的函數公用, 因此可以避免在其它源文件中引起錯誤。
為什么棧要比堆速度要快
首先, 棧是本著LIFO原則的存儲機制, 對棧數據的定位相對比較快速, 而堆則是隨機分配的空間, 處理的數據比較多, 無論如何, 至少要兩次定位.
其次, 棧是由CPU提供指令支持的, 在指令的處理速度上, 對棧數據進行處理的速度自然要優于由操作系統支持的堆數據.
再者, 棧是在一級緩存中做緩存的, 而堆則是在二級緩存中, 兩者在硬件性能上差異巨大.
最后, 各語言對棧的優化支持要優于對堆的支持, 比如swift語言中, 三個字及以內的struct結構, 可以在棧中內聯, 從而達到更快的處理速度.
c++ 析構函數調用時間
靜態綁定 動態綁定 (也叫動態連編,靜態連編)
如果父類中存在有虛函數,那么編譯器便會為之生成虛表(屬于類)與虛指針(屬于某個對象),在程序運行時,根據虛指針的指向,來決定調用哪個虛函數,這稱之與動態綁定,與之相對的是靜態綁定,靜態綁定在編譯期就決定了。
泛型
C語言的指針和c++的引用有什么區別?
- 指針有自己的一塊空間,指針是一個變量,只不過這個變量存儲的是一個地址,指向內存的一個存儲單元,即指針是一個實體。而引用只是一個別名;
- 使用sizeof看一個指針的大小是4,而引用則是被引用對象的大小;
- 指針可以被初始化為NULL,而引用必須被初始化且必須是一個已有對象 的引用;
- 作為參數傳遞時,指針需要被解引用才可以對對象進行操作,而直接對引 用的修改都會改變引用所指向的對象;
- 可以有const指針,但是沒有const引用;
- 指針在使用中可以指向其它對象,但是引用只能是一個對象的引用,不能 被改變;
- 指針可以有多級指針(**p),而引用至于一級;
- 指針和引用使用++運算符的意義不一樣;
- 如果返回動態內存分配的對象或者內存,必須使用指針,引用可能引起內存泄露。
請你說說C語言是怎么進行函數調用的
每一個函數調用都會分配函數棧,在棧內進行函數執行過程。調用前,先把返回地址壓棧,然后把當前函數的esp指針壓棧。(ESP(Extended Stack Pointer)為擴展棧指針寄存器,是指針寄存器的一種,用于存放函數棧頂指針)
C語言參數壓棧順序?:從右到左
C++中拷貝賦值函數的形參能否進行值傳遞?
不能。如果是這種情況下,調用拷貝構造函數的時候,首先要將實參傳遞給形參,這個傳遞的時候又要調用拷貝構造函數(aa = ex.aa; //此處調用拷貝構造函數)。。如此循環,無法完成拷貝,棧也會滿。
include頭文件的順序以及雙引號””和尖括號<>的區別
編譯器預處理階段查找頭文件的路徑不一樣
- 使用雙引號包含的頭文件,查找頭文件路徑的順序為:
當前頭文件目錄
編譯器設置的頭文件路徑(編譯器可使用-I顯式指定搜索路徑)
系統變量CPLUS_INCLUDE_PATH/C_INCLUDE_PATH指定的頭文件路徑 - 對于使用尖括號包含的頭文件,查找頭文件的路徑順序為:
編譯器設置的頭文件路徑(編譯器可使用-I顯式指定搜索路徑)
系統變量CPLUS_INCLUDE_PATH/C_INCLUDE_PATH指定的頭文件路徑
一個C++源文件從文本到可執行文件經歷的過程
對于C/C++編寫的程序,從源代碼到可執行文件,一般經過下面四個步驟:
- 預編譯,預編譯的時候做一些簡單的文本替換,比如宏替換,而不進行語法的檢查;
- 編譯,在編譯階段,編譯器將檢查一些語法錯誤,但是,如果使用的函數事先沒有定義這種情況,不再這一階段檢查,編譯后,得到.s文件
- 匯編,將C/C++代碼變為匯編代碼,得到.o或者.obj文件
- 鏈接,將所用到的外部文件鏈接在一起,在這一階段,就會檢查使用的函數有沒有定義
- 鏈接過后,形成可執行文件.exe
詳細請參閱:一個C++源文件從文本到可執行文件經歷的過程
內存泄漏原因和判斷方法
內存泄漏通常是因為調用了malloc/new等內存申請操作,但是缺少了對應的free/delete。
為了判斷內存是否泄漏,我們一方面可以使用Linux環境下的內存泄漏檢查工具Valgrind,另一方面我們寫代碼的時候,可以添加內存申請和釋放的統計功能,統計當前申請和釋放的內存是否一致,以此來判斷內存是否有泄漏。
內存泄漏分類:
- 堆內存泄漏(heap leak)。堆內存值得是程序運行過程中根據需要分配通過malloc\realloc\new等從堆中分配的一塊內存,再完成之后必須要通過調用對應的free或者delete刪除。
如果程序的設計的錯誤導致這部分內存沒有被釋放,那么此后這塊內存將不會被使用,就會產生Heap Leak。 - 系統資源泄露(Resource Leak)。主要指程序使用系統分配的資源比如 Bitmap,handle,SOCKET等沒有使用相應的函數釋放掉,導致系統資源的浪費,嚴重可導致系統效能降低,系統運行不穩定。
- 沒有將基類的析構函數定義為虛函數。當基類指針指向子類對象時,如果基類的析構函數不是virtual,那么子類的析構函數將不會被調用,子類的資源沒有正確的釋放,從而造成內存泄漏。
段錯誤的產生原因
段錯誤是什么
一句話來說,段錯誤是指訪問的內存超出了系統給這個程序所設定的內存空間,例如訪問了不存在的內存地址、訪問了系統保護的內存地址、訪問了只讀的內存地址等等情況。這里貼一個對于“段錯誤”的準確定義。
段錯誤產生的原因
訪問不存在的內存地址
訪問系統保護的內存地址
訪問只讀的內存地址
棧溢出
詳細請參閱:Linux環境下段錯誤的產生原因及調試方法小結
C++ 函數調用過程
總結起來整個過程就三步:
1)根據調用的函數名找到函數入口;
2)在棧中審請調用函數中的參數及函數體內定義的變量的內存空間
3)函數執行完后,釋放函數在棧中的審請的參數和變量的空間,最后返回值(如果有的話)
詳細請查閱:[函數調用過程 / C/C++函數調用過程分析(https://www.cnblogs.com/biyeymyhjob/archive/2012/07/20/2601204.html)
如何調試c++ 多線程程序?
gdb有thread相關命令,如infothread(簡寫成infoth)顯示線程消息,bxxthreadyy可以
詳細請查閱:C++(vs)多線程調試 (轉)
面向對象和面向過程的區別
-
面向對象方法中,把數據和數據操作放在一起,組成對象;對同類的對象抽象出其共性組成類;類通過簡單的接口與外界發生聯系,對象和對象之間通過消息進行通信。面向對象的三大特性是"封裝、“多態”、“繼承”,五大原則是"單一職責原則"、“開放封閉原則”、“里氏替換原則”、“依賴倒置原則”、“接口分離原則”。
-
而面向過程方法是以過程為中心的開發方法,它自頂向下順序進行, 程序結構按照功能劃分成若干個基本模塊,這些模塊形成樹狀結構。
(過程)優點:
性能比面向對象高,因為類調用時需要實例化,開銷比較大,比較消耗源;比如嵌入式開發、Linux/Unix等一般采用面向過程開發,性能是最重要的因素。缺點:沒有面向對象易維護、易復用、易擴展。
(對象)優點:
易維護、易復用、易擴展,由于面向對象有封裝、繼承、多態性的特性,可以設計出低耦合的系統。缺點:性能比面向過程低。
關于引用賦值的多態:
Class B; Class D : public B;B& b; D& d; B& b1 = d ; //父類可以作為子類的引用,此時b1表現和指針形式一致(會調用B的非虛函數) D& d1 = b; //錯誤,不能將子類作為父類的引用模板的聲明和實現不能分開的原因
- 鏈接的時候,需要實例化模板,這時候就需要找模板的具體實現了。假設在main函數中調用了一個模板函數,這時候就需要去實例化該類型的模板。注意main函數里面只包含了.h文件,也就是只有模板的聲明,沒有具體實現。就會報錯。
- 而模板的實現.cpp里面,雖然有模板的具體實現,但是沒有誰在該.cpp里面使用一個模板函數,就不會生成一個具體化的實例
詳細請參閱:C++ 模板類的聲明與實現分離問題 / C++ 模板類的聲明與實現分離問題(模板實例化)
C++類中引用成員和常量成員的初始化(初始化列表)
如果一個類是這樣定義的:
Class A {public:A(int pram1, int pram2, int pram3);privite:int a;int &b;const int c; }假如在構造函數中對三個私有變量進行賦值則通常會這樣寫:
A::A(int pram1, int pram2, int pram3) {a=pram1;b=pram2;c=pram3; }但是,這樣是編譯不過的。因為常量和引用初始化必須賦值。所以上面的構造函數的寫法只是簡單的賦值,并不是初始化。
正確寫法應該是:
A::A(int pram1, int pram2, int pram3):b(pram2),c(pram3) {a=pram1; }采用初始化列表實現了對常量和引用的初始化。采用括號賦值的方法,括號賦值只能用在變量的初始化而不能用在定義之后的賦值。
凡是有引用類型的成員變量或者常量類型的變量的類,不能有缺省構造函數。默認構造函數沒有對引用成員提供默認的初始化機制,也因此造成引用未初始化的編譯錯誤。并且必須使用初始化列表進行初始化const對象、引用對象。
memset為int型數組初始化問題
頭文件:#include <string.h>
memset() 函數用來將指定內存的前n個字節設置為特定的值,其原型為:
void * memset( void * ptr, int value, size_t num );
參數說明:
-
ptr 為要操作的內存的指針。
-
value 為要設置的值。你既可以向 value 傳遞 int 類型的值,也可以傳遞 char 類型的值,int 和 char 可以根據 ASCII 碼相互轉換。
-
num 為 ptr 的前 num 個字節,size_t 就是unsigned int。
【函數說明】memset() 會將 ptr 所指的內存區域的前 num 個字節的值都設置為 value,然后返回指向 ptr 的指針。
無法下面這樣初始化,這樣的結果是a被賦值成168430090,168430090.。。。。。。。。。
int a[10]; memset(a, 1, sizeof(a));這是因為int由4個字節(說)表示,并且不能得到數組a中整數的期望值。
但我經常看到程序員使用memset將int數組元素設置為0或-1。其他值不行!
編譯器對 inline 函數的處理步驟
- 將 inline 函數體復制到 inline 函數調用點處;
- 為所用 inline 函數中的局部變量分配內存空間;
- 將 inline 函數的的輸入參數和返回值映射到調用方法的局部變量空間中;
- 如果 inline 函數有多個返回點,將其轉變為 inline 函數代碼塊末尾的分支(使用 GOTO)
優缺點
優點
- 內聯函數同宏函數一樣將在被調用處進行代碼展開,省去了參數壓棧、棧幀開辟與回收,結果返回等,從而提高程序運行速度。
- 內聯函數相比宏函數來說,在代碼展開時,會做安全檢查或自動類型轉換(同普通函數),而宏定義則不會。
- 在類中聲明同時定義的成員函數,自動轉化為內聯函數,因此內聯函數可以訪問類的成員變量,宏定義則不能。
- 內聯函數在運行時可調試,而宏定義不可以。
缺點
- 代碼膨脹。內聯是以代碼膨脹(復制)為代價,消除函數調用帶來的開銷。如果執行函數體內代碼的時間,相比于函數調用的開銷較大,那么效率的收獲會很少。另一方面,每一處內聯函數的調用都要復制代碼,將使程序的總代碼量增大,消耗更多的內存空間。
- inline 函數無法隨著函數庫升級而升級。inline函數的改變需要重新編譯,不像 non-inline 可以直接鏈接。
- 是否內聯,程序員不可控。內聯函數只是對編譯器的建議,是否對函數內聯,決定權在于編譯器。
虛函數(virtual)可以是內聯函數(inline)嗎?
- 虛函數可以是內聯函數,內聯是可以修飾虛函數的,但是當虛函數表現多態性的時候不能內聯。
- 內聯是在編譯器建議編譯器內聯,而虛函數的多態性在運行期,編譯器無法知道運行期調用哪個代碼,因此虛函數表現為多態性時(運行期)不可以內聯。
- inline virtual 唯一可以內聯的時候是:編譯器知道所調用的對象是哪個類(如 Base::who()),這只有在編譯器具有實際對象而不是對象的指針或引用時才會發生;
靜態庫和動態庫比較
靜態庫
將靜態庫的內容添加到程序中區,此時程序的空間,變成了源程序空間大小+靜態庫空間大小。
動態庫(共享庫)
常駐內存,當程序需要調用相關函數時,會從內存調用。
區別
靜態庫:對空間要求較低,而時間要求較高的核心程序中。
動態庫:對時間要求較低,對空間要求較高。
虛函數、虛函數表,虛指針
在C++的標準規格說明書中說到,編譯器必需要保證虛函數表的指針存在于對象實例中最前面的位置(這是為了保證正確取到虛函數的偏移量)。這意味著我們通過對象實例的地址得到這張虛函數表,然后就可以遍歷其中函數指針,并調用相應的函數。
虛繼承的作用是減少了對基類的重復,代價是增加了虛表指針的負擔(更多的虛表指針)。詳細請查閱:虛指針、虛函數原理
下面總結一下(當基類有虛函數時):
每個類都有虛指針和虛表;
如果不是虛繼承,那么子類將父類的虛指針繼承下來,并指向自身的虛表(發生在對象構造時)。有多少個虛函數,虛表里面的項就會有多少。多重繼承時,可能存在多個的基類虛表與虛指針;
如果是虛繼承,那么子類會有兩份虛指針,一份指向自己的虛表,另一份指向虛基表,多重繼承時虛基表與虛基表指針有且只有一份。
C/C++如何判斷兩個小數是否相等
不能使用等號
const double EPSINON = 0.00000001; ///< double數精度設置為%.8lf即可 bool equal(double a, double b) {if((a-b)>-EPSINON && (a-b)<EPSINON){return true;}else{return false;} }C++空類的大小
本文中所說是C++的空類是指這個類不帶任何數據,即類中沒有非靜態(non-static)數據成員變量,沒有虛函數(virtual function),也沒有虛基類(virtual base class)。
直觀地看,空類對象不使用任何空間,因為沒有任何隸屬對象的數據需要存儲。然而,C++標準規定,凡是一個獨立的(非附屬)對象都必須具有非零大小。換句話說,
C++空類的大小不為0
為了驗證這個結論,可以先來看測試程序的輸出。
#include <iostream> using namespace std;class NoMembers { };int main() {NoMembers n; // Object of type NoMembers.cout << "The size of an object of empty class is: "<< sizeof(n) << endl; }輸出:
The size of an object of empty class is: 1
C++標準指出,不允許一個對象(當然包括類對象)的大小為0,不同的對象不能具有相同的地址。這是由于:
new需要分配不同的內存地址,不能分配內存大小為0的空間
避免除以 sizeof(T)時得到除以0錯誤
故使用一個字節來區分空類。
c++ 空類,含有虛函數的類的大小(此問題都是在32位機器上而言)
1、為何空類的大小不是0呢?
為了確保兩個不同對象的地址不同,必須如此。
類的實例化是在內存中分配一塊地址,每個實例在內存中都有獨一無二的二地址。同樣,空類也會實例化,所以編譯器會給空類隱含的添加一個字節,這樣空類實例化后就有獨一無二的地址了。所以,空類的sizeof為1,而不是0.
2、請看下面的類:
class A{ virtual void f(){} };
class A{
virtual void f(){}
virtual void f1(){}
};
class B:public A{}
此時,類A和類B都不是空類,其sizeof都是4,因為它們都具有虛函數表的地址(32位系統指針4字節,64位系統指針8字節)。多個虛函數,也是對應一個虛函數表,所以都是對應為一個指針大小。
3、請看:
class A{};
class B:public virtual A{};
此時,A是空類,其大小為1;B不是空類,其大小為4.因為含有指向虛基類的指針。
4、多重繼承的空類的大小也是1.
class Father1{}; class Father2{};
class Child:Father1, Father2{};
它們的sizeof都是1.
5、何時共享虛函數地址表:
如果派生類繼承的第一個是基類,且該基類定義了虛函數地址表,則派生類就共享該表首址占用的存儲單元。對于除前述情形以外的其他任何情形,派生類在處理完所有基類或虛基類后,根據派生類是否建立了虛函數地址表,確定是否為該表首址分配存儲單元。
測試:運行下面的代碼,輸出是什么?
class A { };class B { public:B() {} ~B() {}};class C { public:C() {}virtual ~C() {} };int _tmain(int argc, _TCHAR* argv[]) {printf("%d, %d, %d\n", sizeof(A), sizeof(B), sizeof(C));return 0; }答案是1, 1, 4。class A是一個空類型,它的實例不包含任何信息,本來求sizeof應該是0。但當我們聲明該類型的實例的時候,它必須在內存中占有一定的空間,否則無法使用這些實例。至于占用多少內存,由編譯器決定。Visual Studio 2008中每個空類型的實例占用一個byte的空間。
class B在class A的基礎上添加了構造函數和析構函數。由于構造函數和析構函數的調用與類型的實例無關(調用它們只需要知道函數地址即可),在它的實例中不需要增加任何信息。所以sizeof(B)和sizeof(A)一樣,在Visual Studio 2008中都是1。
class C在class B的基礎上把析構函數標注為虛擬函數。C++的編譯器一旦發現一個類型中有虛擬函數,就會為該類型生成虛函數表,并在該類型的每一個實例中添加一個指向虛函數表的指針。在32位的機器上,一個指針占4個字節的空間,因此sizeof?是4。
32位機與64位機指針占用空間不同
#include<iostream> using namespace std; int main() { cout << "sizeof(int*)="<<sizeof(int*) << endl; cout << "sizeof(int)=" << sizeof(int) << endl; }32位機 sizeof(int*)=4, sizeof(int)=4
64位機 sizeof(int*)=8, sizeof(int)=4
sizeof(int)指的int占用的字節數,字節數為4.
sizeof(int*) 指的是指針變量占用的字節數
32為機上:sizeof(char*)=sizeof(int*)=sizeof(short*)=sizeof(long*)=4
64位機:sizeof(char*)=sizeof(int*)=sizeof(shor*)=sizeof(long*)=8
引經據典
感謝以下博主的文章,如有遺漏,請聯系我添加,謝謝!
https://blog.csdn.net/weixin_43519366/article/details/118634870
https://blog.csdn.net/qq_31349683/article/details/112381183
https://blog.csdn.net/lihao21/article/details/47973609
https://blog.csdn.net/yhc166188/article/details/81159415
https://www.cnblogs.com/yuanshijie/p/12884288.html
總結
以上是生活随笔為你收集整理的C++面试八股文快问快答の基础篇的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2020年中国直播电商行业研究报告
- 下一篇: C++面试八股文快问快答のSTL篇