01 c++常见面试题总结
https://www.cnblogs.com/yjd_hycf_space/p/7495640.html? ?C++常見的面試題
http://c.tedu.cn/workplace/217749.html? ?C++開發需要掌握的技能
https://www.cnblogs.com/LUO77/p/5771237.html? ?常見C++面試題及基本知識點總結
?
1. 引用:
- 申明一個引用的時候,切記要對其進行初始化(引用只能在定義時指定,之后不能再次指向比的變量,即變成其他變量的引用)
- 本身不是一種數據類型,因此引用本身不占存儲單元
- 數組的元素不能是引用。但是可以建立數組的引用。??
解釋:
?
?
2. 常量引用:
不能通過引用的別名改變值(不能通過引用對目標變量的值進行修改),但原變量是可以隨意改變值的
3.?不能返回函數內部new分配的內存的引用
雖然new不存在局部變量的被動銷毀問題,但是當被函數返回的引用只是作為一個臨時變量出現,而沒有被賦予一個實際的變量,那么這個引用所指向的空間(由new分配)就無法釋放,造成memory?leak。
https://blog.csdn.net/why_ny/article/details/7901670
//編譯可以通過,但是容易造成內存泄漏 string& foo() {string* str = new string("abc");return *str; }//不會泄露 string& tmp = foo(); string str = tmp; delete &tmp;//臨時變量時,無法釋放 string str = "hello" + foo(); //調用foo()產生的臨時變量,由于沒有變量名,不能被釋放//無法釋放
string str = foo(); //沒有取引用,相當于返回一個臨時的引用對象,然后該引用對象又向str賦值。即str與返回值并沒有綁定 #include <iostream> using namespace std;int & ret(int &a){a++;return a; }int main() {int a=1,aa=1;int b=ret(a); //返回一個臨時引用變量,該臨時變量又給b賦值int &c=ret(aa);cout<<"b: "<<b<<endl;cout<<"c: "<<c<<endl;b=3;c=3;cout<<"b-a: "<<a<<endl; //b與a不相關cout<<"c-aa: "<<aa<<endl; //c與aa相關cout << "Hello World"<<endl;//cout<<1&1<<endl;return 0; }輸出: b: 2 c: 2 b-a: 2 c-aa: 3 Hello World
4. 關于運算符操作的引用
流操作符<<和>>,這兩個操作符常常希望被連續使用,例如:cout?<<?"hello"?<<?endl; 因此這兩個操作符的返回值應該是一個仍然支持這兩個操作符的流引用。可選的其它方案包括:返回一個流對象和返回一個流對象指針。但是對于返回一個流對象,程序必須重新(拷貝)構造一個新的流對象,也就是說,連續的兩個<<操作符實際上是針對不同對象的!這無法讓人接受。對于返回一個流指針則不能連續使用<<操作符。因此,返回一個流對象引用是惟一選擇。這個唯一選擇很關鍵,它說明了引用的重要性以及無可替代性,也許這就是C++語言中引入引用這個概念的原因吧。?
賦值操作符=。這個操作符象流操作符一樣,是可以連續使用的,例如:x?=?j?=?10;或者(x=10)=100;賦值操作符的返回值必須是一個左值,以便可以被繼續賦值。因此引用成了這個操作符的惟一返回值選擇。
?ostream &operator << (ostream &, class &)??
1)返回值是引用 原因是為了多次<<,即cout<<a<<b<<"返回值"<<endl;?這樣能夠將a,b,”返回值”都存放在一個cout的緩沖區?
如果返回值是對象,生成了另一個實例。相當于是將后續的內容放到了另一個ostream類實例中,而這個實例可能并未與標準輸出設備連接,這樣就沒辦法輸出了
2)執行一次cout<<a并不一定直接將a輸出到屏幕,而是將a轉到IO的緩沖區,具體什么時候將a輸出到屏幕要看系統什么時候有時間和c++的IO具體是怎么實現的。 系統掌握著消息隊列,每次從里面抽取一條消息執行,因為CPU效率很高,我們感覺程序是連續運行的,其實不連續。對于多核處理器來說可以發生真實的同時執行幾個程序。要強制系統將數據輸出到屏幕上,可以“刷新”一下,比如用cout<<endl;就是打一個換行,再刷新緩沖區,刷到屏幕上。
在另外的一些操作符中,卻千萬不能返回引用:+-*/?四則運算符。它們不能返回引用,主要原因是這四個操作符沒有side?effect,因此,它們必須構造一個對象作為返回值,可選的方案包括:返回一個對象、返回一個局部變量的引用,返回一個new分配的對象的引用、返回一個靜態對象引用。根據前面提到的引用作為返回值的三個規則,2、3兩個方案都被否決了。靜態對象的引用又因為((a+b)?==?(c+d))會永遠為true而導致錯誤。所以可選的只剩下返回一個對象了。
5.? struct與union的區別:
https://www.cnblogs.com/nktblog/p/4027107.html?
例題:union類型是共享內存的,以size最大的結構作為自己的大小。所以注意其他size小的變量是怎么存儲的
union myun { struct { int x; int y; int z; }u; int k; }a; int main() { a.u.x =4; a.u.y =5; a.u.z =6; a.k = 0; printf("%d %d %d\n",a.u.x,a.u.y,a.u.z); return 0; }myun這個結構就包含u這個結構體,而大小也等于u這個結構體的大小,在內存中的排列為聲明的順序x,y,z從低到高,然后賦值的時候,在內存中,就是x的位置放置4,y的位置放置5,z的位置放置6,現在對k賦值,對k的賦值因為是union,要共享內存,所以從union的首地址開始放置,首地址開始的位置其實是x的位置,這樣原來內存中x的位置就被k所賦的值代替了,就變為0了,這個時候要進行打印,就直接看內存里就行了,x的位置也就是k的位置是0,而?y,z的位置的值沒有改變,所以應該是0,5,6。
例題:?struct成員對齊
缺省情況下,編譯器為結構體的每個 成員按其自然對界(natural alignment)條件分配空間。各個成員按照它們被聲明的順序在內存中順序存儲,第一個成員的地址和整個結構的地址相同。自然對界(natural alignment)即默認對齊方式,是指按結構體的成員中size最大的成員對齊。
struct naturalalign { char a; short b; char c; }; //在上述結構體中,size最大的是short,其長度為2字節,因而結構體中的char成員a、c都以2為單位對齊,sizeof(naturalalign)的結果等于6; struct naturalalign { char a; int b; char c; }; //結果顯然為12?指定對界
一般地,可以通過下面的方法來改變缺省的對界條件:--使用偽指令#pragma pack (n),編譯器將按照n個字節對齊;--使用偽指令#pragma pack (),取消自定義字節對齊方式。 注意:如果#pragma pack (n)中指定的n大于結構體中最大成員的size,則其不起作用,結構體仍然按照size最大的成員進行對界。 #pragma pack (n) struct naturalalign { char a; int b; char c; }; 當n為4、8、16時,其對齊方式均一樣,sizeof(naturalalign)的結果都等于12。而當n為2時,其發揮了作用,使得sizeof(naturalalign)的結果為8
n=2時,int為4Byte,char向2字節對齊。 注意這里int不會變成2字節,是比n小的向n對齊
題目(微軟)
#include <iostream.h> #pragma pack(8) struct example1 { short a; long b; }; struct example2 { char c; example1 struct1; short e; }; #pragma pack() int main(int argc, char* argv[]) { example2 struct2; cout << sizeof(example1) << endl; cout << sizeof(example2) << endl; cout << (unsigned int)(&struct2.struct1) - (unsigned int)(&struct2) << endl; return 0; } 輸出: 8 16 (以結構1中的long對齊) 4 注:此處long取4字節(與系統有關)6.?C++不是類型安全的,兩個不同類型的指針之間可以強制轉換(用reinterpret?cast)。C#是類型安全的。
類型安全是指同一段內存在不同的地方,會被強制要求使用相同的辦法來解釋(內存中的數據是用類型來解釋的)。
Java語言是類型安全的,除非強制類型轉換。(也是不安全的)
C語言不是類型安全的,因為同一段內存可以用不同的數據類型來解釋,比如1用int來解釋就是1,用boolean來解釋就是true。
7.main?函數執行以前,還會執行什么代碼?
全局對象的構造函數會在main?函數之前執行。
8. 無論什么類型的指針(char、int)都是4字節(與系統有關,與類型無關)
注意當數組作為函數的參數進行傳遞時,該數組自動退化為同類型的指針。 void Func(char a[100]) //盡管形參寫的是數組,但實際上并不是數組,退化成指針 {cout<< sizeof(a) << endl; // 4 字節而不是100 字節 }9.? 函數指針
https://www.cnblogs.com/lvchaoshun/p/7806248.html??
返回類型 (*指針名)(形參類型)? ? ? 因為函數聲明與返回類型和形參類型決定,與函數名沒有關系
typedef decltype(函數名) 別名? ? ? ?定義了函數名函數的別名? ? ?使用時, 別名*? 指針名。
typedef decltype(函數名)* 別名? ? ? 定義了可指向函數名函數的函數指針
10. 基類非虛函數,繼承類定義了同名的虛函數
#include <iostream> using namespace std;class A { public: void fuc() { printf("Hello fuc()\n"); } void fuc2() { printf("Hello A::fuc2()\n"); } }; class B:public A { public: virtual void fuc2() { printf("Hello B::fuc2()\n"); } }; int main() {B datab;A *PA=&datab;PA->fuc2(); //Hello A::fuc2() //cout << "Hello World";return 0; } 輸出:Hello A::fuc2() PA->fuc2(); 不會發生動態綁定。會調用A類的函數。 若A類中 virtual void fuc2()也是虛函數,則運行時動態綁定,調用B類的函數。11.?棧內存與文字常量區
char str1[] = "abc";char str2[] = "abc";const char str3[] = "abc";const char str4[] = "abc";//以下四個指針地址相同const char *str5 = "abc";const char *str6 = "abc";char *str7 = "abc";char *str8 = "abc";cout << ( str1 == str2 ) << endl;//0 分別指向各自的棧內存cout << ( str3 == str4 ) << endl;//0 分別指向各自的棧內存cout << ( str5 == str6 ) << endl;//1指向文字常量區地址相同cout << ( str7 == str8 ) << endl;//1指向文字常量區地址相同結果是:0 0 1 1解答:str1,str2,str3,str4是數組變量,它們有各自的內存空間;而str5,str6,str7,str8是指針,它們指向相同的常量區域。因為對指針 str5,str6,str7,str8 來說,"abc" 屬于常量字符串,不可改變。
12.?將程序跳轉到指定內存地址
要對絕對地址0x100000賦值,我們可以用(unsigned?int*)0x100000?=?1234;? ? ? //用地址代替變量名。
讓程序跳轉到絕對地址是0x100000去執行:
將0x100000強制轉換成函數指針,然后調用 *((void (*)( ))0x100000 ) ( ); 首先要將0x100000強制轉換成函數指針,即: (void (*)())0x100000 然后再調用它: *((void (*)())0x100000)();用typedef可以看得更直觀些: typedef void(*)() voidFuncPtr; *((voidFuncPtr)0x100000)();13. 復雜聲明
void * ( * (*fp1)(int))[10];float (*(* fp2)(int,int,int))(int);int (* ( * fp3)())[10]();【標準答案】???????????????????????????????????????????????????????????
1.void * ( * (*fp1)(int))[10]; ??fp1是一個指針,指向一個函數,這個函數的參數為int型,函數的返回值是一個指針,這個指針指向一個數組,這個數組有10個元素,每個元素是一個void*型指針。
2.float (*(* fp2)(int,int,int))(int); ??fp2是一個指針,指向一個函數,這個函數的參數為3個int型,函數的返回值是一個指針,這個指針指向一個函數,這個函數的參數為int型,函數的返回值是float型。
3.int (* ( * fp3)())[10](); ??fp3是一個指針,指向一個函數,這個函數的參數為空,函數的返回值是一個指針,這個指針指向一個數組,這個數組有10個元素,每個元素是一個指針,指向一個函數,這個函數的參數為空,函數的返回值是int型。
14. 析構函數
以下代碼是否完全正確,執行可能得到的結果是____。 class A{int i; }; class B{A *p; public:B(){p=new A;}~B(){delete p;} }; void sayHello(B b){ } int main(){B b;sayHello(b); } A.程序正常運行 B.程序編譯錯誤 C.程序崩潰 D.程序死循環當類中存在指針類型的成員變量時賦值和析構要格外注意,這道題的問題就出在類B對象b中的指針p被析構了兩次?! ?/p>
調用函數 sayHello(b),將b賦值給函數的形參,由于淺拷貝,兩個對象的指針部分指向同一塊內存。該函數結束,形參調用析構函數,被釋放。而主函數結束后b也會調用析構函數,釋放內存。但是該函數已經被釋放了(形參釋放)。因此同一塊內存會被釋放2次,報錯。選擇C。
注意,雖然main函數沒有定義return語句,但是編譯器會自動生成return語句。因此編譯是正確的。
15.???常見數據類型及其長度:
- 注意long int和int一樣是4byte,long double和double一樣是8byte。(關于long double,ANSI C標準規定了double變量存儲為 IEEE 64 位(8 個字節)浮點數值,但并未規定long double的確切精度。所以對于不同平臺可能有不同的實現。有的是8字節,有的是10字節,有的是12字節或16字節。)
- 在64位操作系統下,int的長度還是32位的。
16. static的使用
- 對局部變量
當static用來修飾局部變量的時候,它就改變了局部變量的存儲位置(從原來的棧中存放改為靜態存儲區)及其生命周期(局部靜態變量在離開作用域之后,并沒有被銷毀,而是仍然駐留在內存當中,直到程序結束,只不過我們不能再對他進行訪問),但未改變其作用域。
- 全局變量
static修飾全局變量,并未改變其存儲位置及生命周期,而是改變了其作用域,使當前文件外的源文件無法訪問該變量,好處如下:
(1)不會被其他文件所訪問,修改
(2)其他文件中可以使用相同名字的變量,不會發生沖突。對全局函數也是有隱藏作用。
而普通全局變量只要定義了,任何地方都能使用,使用前需要聲明所有的.c文件,只能定義一次普通全局變量,但是可以聲明多次(外部鏈接)。注意:全局變量的作用域是全局范圍,但是在某個文件中使用時,必須先聲明。
-
類內數據成員
static數據成員必須在類外進行初始化(初始化格式: int base::var=10;),而不能在構造函數內進行初始化。可以為靜態成員提供const整數類型的類內初始值,不過要求靜態成員必須是字面值常量類型,即常量表達式。
static constexpr int period = 30;
1. 不要試圖在頭文件中定義(初始化)靜態數據成員。在大多數的情況下,這樣做會引起重復定義這樣的錯誤。即使加上#ifndef #define #endif或者#pragma once也不行。?
2. 靜態數據成員可以成為成員函數的可選參數,而普通數據成員則不可以。
3. 靜態數據成員的類型可以是所屬類的類型,而普通數據成員則不可以。普通數據成員的只能聲明為 所屬類類型的指針或引用。
-
成員函數
在靜態成員函數的實現中不能直接引用類中說明的非靜態成員,可以引用類中說明的靜態成員。因為靜態成員函數不含this指針。
不可以同時用const和static修飾成員函數。
1. C++編譯器在實現const的成員函數的時候為了確保該函數不能修改類的實例的狀態,會在函數中添加一個隱式的參數const this*。
2. const與指針:
const char *p 表示 指向的內容不能改變。
char * const p,就是將P聲明為常指針,它的地址不能改變,是固定的,但是它的內容可以改變。
17. 指針和引用的區別
(1)指針:指針是一個變量,只不過這個變量存儲的是一個地址,指向內存的一個存儲單元;而引用跟原來的變量實質上是同一個東西,只不過是原變量的一個別名而已。如:
int a=1;int *p=&a;
int a=1;int &b=a;
上面定義了一個整形變量和一個指針變量p,該指針變量指向a的存儲單元,即p的值是a存儲單元的地址。
而下面2句定義了一個整形變量a和這個整形a的引用b,事實上a和b是同一個東西,在內存占有同一個存儲單元。
(指針占用內存、引用不占用內存)
(2)可以有const指針,也有const引用(const引用可讀不可改,與綁定對象是否為const無關)
(3)指針可以有多級,但是引用只能是一級(int **p;合法 而 int &&a是不合法的)
(4)指針的值可以為空,但是引用的值不能為NULL,并且引用在定義的時候必須初始化;
(5)指針的值在初始化后可以改變,即指向其它的存儲單元,而引用在進行初始化后就不會再改變了。
(6)"sizeof引用"得到的是所指向的變量(對象)的大小,而"sizeof指針"得到的是指針本身的大小;
(7)指針和引用的自增(++)運算意義不一樣;
指針傳參的時候,還是值傳遞,試圖修改傳進來的指針的值是不可以的。只能修改地址所保存變量的值。 引用傳參的時候,傳進來的就是變量本身,因此可以被修改。18. 隱藏
Overwrite(隱藏):隱藏,是指派生類的函數屏蔽了與其同名的基類函數,規則如下:
(1)如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無virtual關鍵字,基類的函數將被隱藏。
(2)如果派生類的函數與基類的函數同名,并且參數也相同,但是基類函數沒有virtual關鍵字。此時,基類的函數被隱藏。
?
19. 虛函數表
第4部分內容
20.?深拷貝與淺拷貝:
淺拷貝:默認的復制構造函數只是完成了對象之間的位拷貝,也就是把對象里的值完全復制給另一個對象,如A=B。這時,如果B中有一個成員變量指針已經申請了內存,那A中的那個成員變量也指向同一塊內存。這就出現了問題:當B把內存釋放了(如:析構),這時A內的指針就是野指針了,出現運行錯誤。
深拷貝:自定義復制構造函數需要注意,對象之間發生復制,資源重新分配,即A有5個空間,B也應該有5個空間,而不是指向A的5個空間。
21.?vector中size()和capacity()的區別
- ?size()指容器當前擁有的元素個數(對應的resize(size_type)會在容器尾添加或刪除一些元素,來調整容器中實際的內容,使容器達到指定的大小。);
- ?capacity()指容器在必須分配存儲空間之前可以存儲的元素總數。
size表示的這個vector里容納了多少個元素,capacity表示vector能夠容納多少元素,它們的不同是在于vector的size是2倍增長的。如果vector的大小不夠了,比如現在的capacity是4,插入到第五個元素的時候,發現不夠了,此時會給他重新分配8個空間,把原來的數據及新的數據復制到這個新分配的空間里。(會有迭代器失效的問題)
22. 各容器的特點:
注:
deque容器,雙端隊列,能夠快速地隨機訪問任一個元素,并且能夠高效地插入和刪除容器的尾部元素。
23.?map和set的原理
?(map和set的四個問題)
紅黑樹:
性質1 節點是紅色或黑色。 性質2 根節點是黑色。 性質3 每個葉節點(NIL節點,空節點)是黑色的。 性質4 每個紅色節點的兩個子節點都是黑色。(從每個葉子到根的所有路徑上不能有兩個連續的紅色節點) 性質5 從任一節點到其每個葉子的所有路徑都包含相同數目的黑色節點。 這些約束的好處是:保持了樹的相對平衡,同時又比AVL的插入刪除操作的復雜性要低許多。(深入探討紅黑樹)
?
?
?
轉載于:https://www.cnblogs.com/GuoXinxin/p/10599341.html
總結
以上是生活随笔為你收集整理的01 c++常见面试题总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Eclipse 中 Maven 项目默认
- 下一篇: git 拉取远程分支到本地