1. C++中類成員的訪問權限和繼承權限問題
- 三種訪問權限
- public:用該關鍵字修飾的成員表示公有成員,該成員不僅可以在類內可以被 訪問,在類外也是可以被訪問的,是類對外提供的可訪問接口;
- private:用該關鍵字修飾的成員表示私有成員,該成員僅在類內可以被訪問,在類體外是隱藏狀態;
- protected:用該關鍵字修飾的成員表示保護成員,保護成員在類體外同樣是隱藏狀態,但是對於該類的派生類來說,相當於公有成員,在派生類中可以被訪問。
- 三種繼承方式
- 若繼承方式是public,基類成員在派生類中的訪問權限保持不變,也就是說,基類中的成員訪問權限,在派生類中仍然保持原來的訪問權限;
- 若繼承方式是private,基類所有成員在派生類中的訪問權限都會變爲私有(private)權限;
- 若繼承方式是protected,基類的共有成員和保護成員在派生類中的訪問權限都會變爲保護(protected)權限,私有成員在派生類中的訪問權限仍然是私有(private)權限。
2. cout和printf有什麼區別?
- cout<<是一個函數,cout<<後可以跟不同的類型是因爲cout<<已存在針對各種類型數據的重載,所以會自動識別數據的類型。輸出過程會首先將輸出字符放入緩衝區,然後輸出到屏幕。
cout是有緩衝輸出:
cout < < "abc " < <endl; 或cout < < "abc\n ";cout < <flush; 這兩個纔是一樣的. endl相當於輸出回車後,再強迫緩衝輸出。
flush立即強迫緩衝輸出。
2. printf是無緩衝輸出。有輸出時立即輸出
3. 重載運算符?
類屬關係運算符"."、成員指針運算符".*"、作用域分辨符"::"、sizeof運算符和三目運算符"?:"
- 我們只能重載已有的運算符,而無權發明新的運算符;對於一個重載的運算符,其優先級和結合律與內置類型一致纔可以;不能改變運算符操作數個數;
- . :: ?: sizeof typeid **不能重載;
- 兩種重載方式,成員運算符和非成員運算符,成員運算符比非成員運算符少一個參數;下標運算符、箭頭運算符必須是成員運算符;
- 引入運算符重載,是爲了實現類的多態性;
- 當重載的運算符是成員函數時,this綁定到左側運算符對象。成員運算符函數的參數數量比運算符對象的數量少一個;至少含有一個類類型的參數;
- 從參數的個數推斷到底定義的是哪種運算符,當運算符既是一元運算符又是二元運算符(+,-,*,&);
- 下標運算符必須是成員函數,下標運算符通常以所訪問元素的引用作爲返回值,同時最好定義下標運算符的常量版本和非常量版本;
- 箭頭運算符必須是類的成員,解引用通常也是類的成員;重載的箭頭運算符必須返回類的指針;
4. 定義和聲明的區別
- 如果是指變量的聲明和定義
從編譯原理上來說,聲明是僅僅告訴編譯器,有個某類型的變量會被使用,但是編譯器並不會爲它分配任何內存。而定義就是分配了內存。 - 如果是指函數的聲明和定義
聲明:一般在頭文件裏,對編譯器說:這裏我有一個函數叫function() 讓編譯器知道這個函數的存在。
定義:一般在源文件裏,具體就是函數的實現過程 寫明函數體。
5. C++類型轉換有四種
- static_cast能進行基礎類型之間的轉換,也是最常看到的類型轉換。它主要有如下幾種用法:
-
1 . 用於類層次結構中父類和子類之間指針或引用的轉換。進行上行轉換(把子類的指針或引用轉換成父類表示)是安全的;
2 . 進行下行轉換(把父類指針或引用轉換成子類指針或引用)時,由於沒有動態類型檢查,所以是不安全的;
3 . 用於基本數據類型之間的轉換,如把int轉換成char,把int轉換成enum。這種轉換的安全性也要開發人員來保證。
4 . 把void指針轉換成目標類型的指針(不安全!!)
5 . 把任何類型的表達式轉換成void類型。
- const_cast運算符用來修改類型的const或volatile屬性。除了去掉const 或volatile修飾之外, type_id和expression得到的類型是一樣的。但需要特別注意的是const_cast不是用於去除變量的常量性,而是去除指向常數對象的指針或引用的常量性,其去除常量性的對象必須爲指針或引用。
- reinterpret_cast它可以把一個指針轉換成一個整數,也可以把一個整數轉換成一個指針(先把一個指針轉換成一個整數,在把該整數轉換成原類型的指針,還可以得到原先的指針值)。
- dynamic_cast 主要用在繼承體系中的安全向下轉型。它能安全地將指向基類的指針轉型爲指向子類的指針或引用,並獲知轉型動作成功是否。轉型失敗會返回null(轉型對象爲指針時)或拋出異常bad_cast(轉型對象爲引用時)。 dynamic_cast 會動用運行時信息(RTTI)來進行類型安全檢查,因此 dynamic_cast 存在一定的效率損失。當使用dynamic_cast時,該類型必須含有虛函數,這是因爲dynamic_cast使用了存儲在VTABLE中的信息來判斷實際的類型,RTTI運行時類型識別用於判斷類型。typeid表達式的形式是typeid(e),typeid操作的結果是一個常量對象的引用,該對象的類型是type_info或type_info的派生。
6. 靜態成員與普通成員的區別
- 生命週期:靜態成員變量從類被加載開始到類被卸載,一直存在;普通成員變量只有在類創建對象後纔開始存在,對象結束,它的生命期結束;
- 共享方式:靜態成員變量是全類共享;普通成員變量是每個對象單獨享用的;
- 定義位置:普通成員變量存儲在棧或堆中,而靜態成員變量存儲在靜態全局區;
- 初始化位置:普通成員變量在類中初始化;靜態成員變量在類外初始化;
- 默認實參:可以使用靜態成員變量作爲默認實參,
7. 多繼承的優缺點,作爲一個開發者怎麼看待多繼承
- C++允許爲一個派生類指定多個基類,這樣的繼承結構被稱做多重繼承。
- 多重繼承的優點很明顯,就是對象可以調用多個基類中的接口;
- 如果派生類所繼承的多個基類有相同的基類,而派生類對象需要調用這個祖先類的接口方法,就會容易出現二義性
- 加上全局符確定調用哪一份拷貝。比如pa.Author::eat()調用屬於Author的拷貝。
- 使用虛擬繼承,使得多重繼承類Programmer_Author只擁有Person類的一份拷貝。
8. 迭代器++it,it++哪個好,爲什麼
- 前置返回一個引用,後置返回一個對象
-
// ++i實現代碼爲:
int& operator++()
{
*this += 1;
return *this;
}
- 前置不會產生臨時對象,後置必須產生臨時對象,臨時對象會導致效率降低
-
//i++實現代碼爲:
int operator++(int)
{
int temp = *this;
++*this;
return temp;
}
9. 模板和實現可不可以不寫在一個文件裏面?爲什麼?
- 因爲在編譯時模板並不能生成真正的二進制代碼,而是在編譯調用模板類或函數的CPP文件時纔會去找對應的模板聲明和實現,在這種情況下編譯器是不知道實現模板類或函數的CPP文件的存在,所以它只能找到模板類或函數的聲明而找不到實現,而只好創建一個符號寄希望於鏈接程序找地址。但模板類或函數的實現並不能被編譯成二進制代碼,結果鏈接程序找不到地址只好報錯了。
- 《C++編程思想》第15章(第300頁)說明了原因:模板定義很特殊。由template<…>處理的任何東西都意味着編譯器在當時不爲它分配存儲空間,它一直處於等待狀態直到被一個模板實例告知。在編譯器和連接器的某一處,有一機制能去掉指定模板的多重定義。所以爲了容易使用,幾乎總是在頭文件中放置全部的模板聲明和定義。
10. class、union、struct的區別?
- C語言中,struct只是一個聚合數據類型,沒有權限設置,無法添加成員函數,無法實現面向對象編程,且如果沒有typedef結構名,聲明結構變量必須添加關鍵字struct。
- C++中,struct功能大大擴展,可以有權限設置(默認權限爲public),可以像class一樣有成員函數,繼承(默認public繼承),可以實現面對對象編程,允許在聲明結構變量時省略關鍵字struct。
- C與C++中的union:一種數據格式,能夠存儲不同的數據類型,但只能同時存儲其中的一種類型。C++ union結構式一種特殊的類。它能夠包含訪問權限、成員變量、成員函數(可以包含構造函數和析構函數)。它不能包含虛函數和靜態數據變量。它也不能被用作其他類的基類,它本身也不能有從某個基類派生而來。Union中得默認訪問權限是public。union類型是共享內存的,以size最大的結構作爲自己的大小。每個數據成員在內存中的起始地址是相同的。
- 在C/C++程序的編寫中,當多個基本數據類型或複合數據結構要佔用同一片內存時,我們要使用聯合體;當多種類型,多個對象,多個事物只取其一時(我們姑且通俗地稱其爲“n 選1”),我們也可以使用聯合體來發揮其長處。在某一時刻,一個union中只能有一個值是有效的。union的一個用法就是可以用來測試CPU是大端模式還是小端模式。
11. 動態聯編與靜態聯編
- 在C++中,聯編是指一個計算機程序的不同部分彼此關聯的過程。按照聯編所進行的階段不同,可以分爲靜態聯編和動態聯編;
- 靜態聯編是指聯編工作在編譯階段完成的,這種聯編過程是在程序運行之前完成的,又稱爲早期聯編。要實現靜態聯編,在編譯階段就必須確定程序中的操作調用(如函數調用)與執行該操作代碼間的關係,確定這種關係稱爲束定,在編譯時的束定稱爲靜態束定。靜態聯編對函數的選擇是基於指向對象的指針或者引用的類型。其優點是效率高,但靈活性差。
- 動態聯編是指聯編在程序運行時動態地進行,根據當時的情況來確定調用哪個同名函數,實際上是在運行時虛函數的實現。這種聯編又稱爲晚期聯編,或動態束定。動態聯編對成員函數的選擇是基於對象的類型,針對不同的對象類型將做出不同的編譯結果。C++中一般情況下的聯編是靜態聯編,但是當涉及到多態性和虛函數時應該使用動態聯編。動態聯編的優點是靈活性強,但效率低。動態聯編規定,只能通過指向基類的指針或基類對象的引用來調用虛函數,其格式爲:指向基類的指針變量名->虛函數名(實參表)或基類對象的引用名.虛函數名(實參表)
- 實現動態聯編三個條件:
- 必須把動態聯編的行爲定義爲類的虛函數;
- 類之間應滿足子類型關係,通常表現爲一個類從另一個類公有派生而來;
- 必須先使用基類指針指向子類型的對象,然後直接或間接使用基類指針調用虛函數;
12. 動態編譯與靜態編譯
- 靜態編譯,編譯器在編譯可執行文件時,把需要用到的對應動態鏈接庫中的部分提取出來,連接到可執行文件中去,使可執行文件在運行時不需要依賴於動態鏈接庫;
- 動態編譯的可執行文件需要附帶一個動態鏈接庫,在執行時,需要調用其對應動態鏈接庫的命令。所以其優點一方面是縮小了執行文件本身的體積,另一方面是加快了編譯速度,節省了系統資源。缺點是哪怕是很簡單的程序,只用到了鏈接庫的一兩條命令,也需要附帶一個相對龐大的鏈接庫;二是如果其他計算機上沒有安裝對應的運行庫,則用動態編譯的可執行文件就不能運行。
13. 講講大端小端,如何檢測(三種方法)
-
大端模式:是指數據的高字節保存在內存的低地址中,而數據的低字節保存在內存的高地址端。
-
小端模式,是指數據的高字節保存在內存的高地址中,低位字節保存在在內存的低地址端。
- 直接讀取存放在內存中的十六進制數值,取低位進行值判斷
-
int a = 0x12345678;
int *c = &a;
c[0] == 0x12 大端模式
c[0] == 0x78 小段模式
- 用共同體來進行判斷
-
union共同體所有數據成員是共享一段內存的,後寫入的成員數據將覆蓋之前的成員數據,成員數據都有相同的首地址。Union的大小爲最大數據成員的大小。
union的成員數據共用內存,並且首地址都是低地址首字節。Int i= 1時:大端存儲1放在最高位,小端存儲1放在最低位。當讀取char ch時,是最低地址首字節,大小端會顯示不同的值。
union w w p;
{ p.i = 1;
int i; if(ch == 1)
char ch;}
14. 查看內存的方法
- 首先打開vs編譯器,創建好項目,並且將代碼寫進去,這裏就不貼代碼了,你可以隨便的寫個做個測試;
- 調試的時候做好相應的斷點,然後點擊開始調試;
- 程序調試之後會在你設置斷點的地方暫停,然後選擇調試->窗口->內存,就打開了內存數據查看的窗口了。
15. 空類會默認添加哪些東西?怎麼寫?
- Empty(); // 缺省構造函數//
- Empty( const Empty& ); // 拷貝構造函數//
- ~Empty(); // 析構函數//
- Empty& operator=( const Empty& ); // 賦值運算符//
16. 爲什麼拷貝構造函數必須傳引用不能傳值?
-
拷貝構造函數的作用就是用來複製對象的,在使用這個對象的實例來初始化這個對象的一個新的實例。
2) 參數傳遞過程到底發生了什麼?
將地址傳遞和值傳遞統一起來,歸根結底還是傳遞的是"值"(地址也是值,只不過通過它可以找到另一個值)!
i)值傳遞:
對於內置數據類型的傳遞時,直接賦值拷貝給形參(注意形參是函數內局部變量);
對於類類型的傳遞時,需要首先調用該類的拷貝構造函數來初始化形參(局部對象);如void foo(class_type obj_local){}, 如果調用foo(obj); 首先class_type obj_local(obj) ,這樣就定義了局部變量obj_local供函數內部使用
ii)引用傳遞:
無論對內置類型還是類類型,傳遞引用或指針最終都是傳遞的地址值!而地址總是指針類型(屬於簡單類型), 顯然參數傳遞時,按簡單類型的賦值拷貝,而不會有拷貝構造函數的調用(對於類類型).
上述1) 2)回答了爲什麼拷貝構造函數使用值傳遞會產生無限遞歸調用,內存溢出。拷貝構造函數用來初始化一個非引用類類型對象,如果用傳值的方式進行傳參數,那麼構造實參需要調用拷貝構造函數,而拷貝構造函數需要傳遞實參,所以會一直遞歸
17. 空類的大小是多少?爲什麼?
- C++空類的大小不爲0,不同編譯器設置不一樣,vs設置爲1;
- C++標準指出,不允許一個對象(當然包括類對象)的大小爲0,不同的對象不能具有相同的地址;
- 帶有虛函數的C++類大小不爲1,因爲每一個對象會有一個vptr指向虛函數表,具體大小根據指針大小確定;
- C++中要求對於類的每個實例都必須有獨一無二的地址,那麼編譯器自動爲空類分配一個字節大小,這樣便保證了每個實例均有獨一無二的內存地址。
18. 你什麼情況用指針當參數,什麼時候用引用,爲什麼?
- 使用引用參數的主要原因有兩個:
-
程序員能修改調用函數中的數據對象
通過傳遞引用而不是整個數據–對象,可以提高程序的運行速度
- 一般的原則:
對於使用引用的值而不做修改的函數:
-
如果數據對象很小,如內置數據類型或者小型結構,則按照值傳遞;
如果數據對象是數組,則使用指針(唯一的選擇),並且指針聲明爲指向const的指針;
如果數據對象是較大的結構,則使用const指針或者引用,已提高程序的效率。這樣可以節省結構所需的時間和空間;
如果數據對象是類對象,則使用const引用(傳遞類對象參數的標準方式是按照引用傳遞);
- 對於修改函數中數據的函數:
-
如果數據是內置數據類型,則使用指針
如果數據對象是數組,則只能使用指針
如果數據對象是結構,則使用引用或者指針
如果數據是類對象,則使用引用
19. 大內存申請時候選用哪種?C++變量存在哪?變量的大小存在哪?符號表存在哪?
- 大內存申請時,採用堆申請空間,用new申請;
- 不同的變量存儲在不同的地方,局部變量、全局變量、靜態變量;
- C++對變量名不作存儲,在彙編以後不會出現變量名,變量名作用只是用於方便編譯成彙編代碼,是給編譯器看的,是方便人閱讀的
20. 靜態函數能定義爲虛函數嗎?常函數?
- static成員不屬於任何類對象或類實例,所以即使給此函數加上virutal也是沒有任何意義的。
- 靜態與非靜態成員函數之間有一個主要的區別。那就是靜態成員函數沒有this指針。虛函數依靠vptr和vtable來處理。vptr是一個指針,在類的構造函數中創建生成,並且只能用this指針來訪問它,因爲它是類的一個成員,並且vptr指向保存虛函數地址的vtable.對於靜態成員函數,它沒有this指針,所以無法訪問vptr. 這就是爲何static函數不能爲virtual.虛函數的調用關係:this -> vptr -> vtable ->virtual function