C++基礎知識面試必備、複習細節 (1)
c++變量與基本類型
(解決面試時常問的考點以及易忘點易混點)
一些經驗準則:
- 如果明確數值不爲負,則選擇無符號類型
- 使用int執行整數運算(如果超出int數值範圍則採用 long long)
- 執行浮點數運算採用double(注意double類型不可以用==判斷相等,可以用相差值小於一個很小的值判斷)
- 類型轉換時要注意範圍,浮點轉換到整型會損失小數部分
- 切勿混用帶符號和無符號類型。(當無符號和有符號相加時,帶符號數會轉變爲無符號數,會引發錯誤)
- 定義變量時儘量初始化,尤其在函數體內,因爲在函數體內的內置類型的變量如果沒有初始化其值是未定義
引用和指針
- 引用是對象的一個別名,指針本身就是一個對象
- 引用必須在定義時進行初始化,而指針無需(但爲了避免未知錯誤,通常定義指針時賦初值)
- 指針的值在初始化後可以改變,即指向其它的存儲單元,而引用在進行初始化後就不會再改變了
- ”sizeof引用”得到的是所指向的變量(對象)的大小,而”sizeof指針”得到的是指針本身的大小;
- 將引用作爲函數的參數時,實際上就是直接將該變量傳入了函數,而不是再次拷貝了一個臨時變量,所以對該參數的修改將直接修改的原始的值。
const限定符
-
爲了避免對值的修改,可以採用const限定符。注意:const對象必須初始化
const int a; //錯,必須初始化 const int b=10; //對,編譯時初始化
-
const引用:對常量的引用,對常量的引用不能被用作修改它綁定的對象
const int ci=1024; const int &ri=ci; //正確,引用以及其對應的對象都是常量 int &r2=ci; //錯誤,試圖讓一個非常量引用指向一個常量對象
-
const指針
-
指向常量的指針: const int * 表示的是一個指向const int的指針
const int a=3; const int *p=&a; //正確,a是一個int常量,p是指向int常量的指針
-
常量指針: int *const 表示的是指向int的指針,但其不能被修改,不變的是指針本身而不是那個值
故 const int * const就是一個指向const int的常量指針
int a=0; int *const p=&a; //p是一直指向a的指針 const int b=0; const int *const p2=&b; //p2是指向常量對象的常量指針
-
處理類型
-
採用關鍵字 typedef 創建易懂的變量別名
typedef double wages; //wages將是double的同義詞 wages a; //a是double類型
-
auto類型,可以讓編譯器自動分析表達式類型
auto item=val1+val2; //如果val1和val2是int,則item將是int vector<int> array; for(auto i:array) //自動遍歷array中的每一個元素 { }
自定義數據結構
-
struct 結構體
struct Sales_date{ string book_name; unsigned int prices=0; }; //注意加分號
標準庫類型string
可變長的字符串
-
初始化
string s1; //默認初始化 string s2="hello"; //s2初始化爲"hellp" string s3(10,'a'); //s3的內容是"aaaaaaaaaa"
-
一些常用操作:
s.empty(); //s爲空則返回true,否則返回false s.size(); //返回字符串長度 s.length() s1+s2 ; //返回s1和s2連接後的字符串 s1 < > <= >= s2 //返回的是字典序比較,逐一比較的結果 find (string s, size_t pos)//在當前字符串的pos索引位置開始,查找子串s,返回找到的位置索引
標準庫類型vector
vector是一個對象的集合,其中所有對象的類型都相同。容器
-
初始化
vector<T> v1; vector<int> a{1,2,3,4,5}; vector<int> array(10); //表示array長度爲10,都初始化爲0 vector<int> d(10,1); //初始化爲10個1 int array[]={1,2,3,4,5,6,7} vector<int> ve_array(begin(array),end(array)); //用數組初始化vector
-
常用操作
//添加元素 push_back() v1.push_back(5); //向尾部加入5 //刪除元素 pop_back() v1.pop_back() //判斷是否爲空 v1.empty() //vector的長度 v1.size() //遍歷vector for(auto i:v1) something(); for(auto i=v1.begin();i<v1.end();i++) something(); //查找某一元素 v1.find() //如果找到則返回對應下標,否則返回的是v1.end()
數組名和指針的區別與聯繫
- 數組名存的就是數組第一個元素的地址,即 array==&array[0]; 同樣,可以令指針指向該數組,則指針所存內容也是數組第一個元素的地址,即可以將數組名賦值給指針
- 數組名是不可以修改的,而指針是可以移動的,可以用指針遍歷一個數組
- array+1表示array[1]的地址
- sizeof(array)=sizeof(T)*length 即sizeof一個數組返回的是整個數組佔用的空間,而sizeof指針返回的是一個指針的空間
C++表達式
邏輯運算符求值的短路求值
-
對於邏輯與運算符,當且僅當左側運算對象爲真時纔對右側運算對象求值。也就是說,如果左側爲假,則直接返回false而不繼續判斷右側
index<array.size()&&array[index]>0 //通過該方式避免越界
-
對於邏輯或運算符,當且僅當左側運算符爲假的時纔對右側對象求值。也就是說,如果左側爲真,則表達式爲true已經確定直接返回,而不繼續判斷
if(s.empty()||s[s.size()-1]=='.') //s爲空或者以句號結尾則換行 cout<<endl;
遞增遞減運算符
-
前置和後置的遞增運算符:
- 前置版本:首先將運算對象加1,然後將改變後的對象返回
- 後置版本:將運算對象加1,返回運算對象改變前的值的副本
int i=0,j; j=++i; //j=1,i=1 j=i++; //j=1,i=2
-
通常儘量使用前置版本,如果爲了賦值然後遍歷,則通常採用後置版本
array[i++]=k; //令array[i]=k且向後遍歷一步
位運算符
-
按位與 & ,將參與運算的兩個分量對應的每一位來做邏輯與運算,若兩者都爲真(等於1),則結果才爲真(等於1)。否則爲假(等於0 ) 即:1 & 1 = 1 、1&0 = 0 、0&1 = 1、0&0 = 0
-
按位或 | , 將參與運算的每個分量對應的每一位來做邏輯或運算,即兩者都爲假(爲0)時,才爲假(爲0),否則爲真。即:0|0 = 0、1|0 = 1、0|1 = 1、1|1 = 1
-
按位異或 ^ , 把參與運算的每個分量對應的每一位來做異或運算,即兩者相同爲假,不同爲真 即:0|0 = 0、 1|0 = 1、0|1 = 1、 1|1 = 0
-
按位取反 ~ , 把二進制位的每一位進行取反運算,將二進制中1變成0,0變成1
-
按位右移 >> 把二進制位整體向右移動。 7>>1 = 0000 0111 >> 1 = 0000 0011 = 3
右移等於除了2的N次方,N爲右移的位數。
-
按位左移 << 把二進制位整體向左移動。
sizeof運算符
sizeof運算符返回一條表達式或一個類型名字所佔的字節數。
-
對類型爲引用的對象執行sizeof運算將得到被引用對象所佔空間的大小
-
對類型爲指針的對象執行sizeof運算將得到指針本身所佔空間的大小
-
對數組執行sizeof運算將得到整個數組所佔空間大小,等價於對數組每個元素sizeof然後求和
-
對string和vector類執行sizeof運算只會返回該類型固定部分的大小而不是佔用空間的大小
-
結構體、union、類爲空時,對其sizeof返回爲1
-
靜態變量在sizeof計算時不需要考慮
-
對結構體進行sizeof不是單純的加法,要注意對齊效果,即每個變量起始必須是自己所佔字節的倍數。結構體的大小必須是其中最大寬度的類型大小的整數倍,例子:
struct test{ char a; //1 int c; //4 }; cout<<sizeof(test); //是8而不是5,因爲int跳過char後面3個空間再放置,對齊 struct test{ char a; //1 int *p; //8 char b; //1 }; cout<<sizeof(test); //是24而不是10,首先p需要對齊所以需要到8的倍數處纔可以放置,然後放置b後,結構體的大小需要是p的整數倍,故最終爲24 struct test { }; cout<<sizeof(test); //是1而不是0,哪怕是空結構體也佔1字節
-
對union對象進行sizeof時,結果爲其中最寬的數據類型的長度
union test{ char a; int p; char b; }; cout<<sizeof(test); //是4,最大的是int,4字節
-
對類對象進行sizeof時,大體和struct類似,但要注意虛函數、繼承等關係,虛函數需要有指針指向虛函數表故會增加一個指針的空間,繼承時會繼承父類的空間
class test { ~test() { } }; //sizeof(test)=1 class test2 { virtual ~test2() {} }; //sizeof(test2)=8 因爲是虛函數,所以需要創建一個指針指向虛函數表,佔據內存
局部變量和全局變量
- 作用域:全局變量在函數外聲明,作用域是整個源程序。局部變量在函數內或循環內聲明或作爲函數形參,作用域是當前函數或者循環等。
- 內存存儲:全局變量存儲在全局數據區,局部變量存儲在棧區
- 生命期:全局變量存在於整個執行過程,在程序啓動時創建,直至程序結束才銷燬。局部變量隨着函數或者循環的結束就銷燬了。
- 如果在函數內部定義了與全局變量相同的局部變量,則在該函數內部優先使用局部變量
- 局部靜態對象:static 局部靜態變量在程序的執行路徑第一次經過時初始化,直至程序終止才被銷燬
一個面試題
-
有極大量的數據,10億個int數據,如何去重?
思路:可以建立hash映射,只需要一個bit存儲即可,如果一個數字已經出現則之後遇到就去重,這樣至多需要MAX_INT個bit存儲,內存是可存的。
或者使用布隆過濾器
參數傳遞的一些細節
- 如果形參是引用類型,則將綁定到對應的實參;否則將會將實參的值拷貝後賦給形參
- 傳值參數的修改將不影響具體的實參值,而傳引用參數的修改將直接修改具體的實參;通過傳指針形參,也可以間接地修改對象的值
- 如果參數是比較大的類對象或者容器對象,通常傳引用可以避免低效地拷貝(甚至一些類型不支持拷貝)
- 如果函數無須改變形參的值,則最好將其聲明爲傳引用,且常量引用
- 數組作爲形參時,實際上傳遞的是指向數組首元素的指針。以數組作爲形參時要確保不要越界
- 不要返回局部變量的引用或指針! 因爲在函數完成後其存儲空間將被釋放掉,會導致訪問的內存非法
- 可以在形參列表中賦值設置默認實參,設置默認實參的參數應該放在右邊
重載overload與重寫override
-
重載:同一作用域內函數名字相同,形參列表不同。不允許兩個函數除了返回類型外其他所有要素都相同
不能根據返回值判斷是否重載;重載後,編譯器會根據參數選擇最優的調用,要避免二義性
-
重寫:子類重新定義父類中有相同名稱和參數的虛函數(virtual)。在繼承關係之間。C++利用虛函數實現多態
-
重寫函數必須具有相同的類型、名稱、參數列表
內聯函數 inline
- 將函數聲明爲內聯函數將提高函數的執行效率,把關鍵詞inline放在函數定義前面即可。
- 內聯函數執行時會將它在每個調用位置展開,避免了函數調用的開銷
- 關鍵字 inline 必須與函數定義體放在一起才能使函數成爲內聯,僅將 inline 放在函數聲明前面不起任何作用
- 不用盲目使用內聯,通常函數較爲簡短且簡單時才使用內聯