內聯函數
概念
以inline修飾的函數叫做內聯函數,編譯時C++編譯器會在調用內聯函數的地方展開,沒有函數調用建立棧幀的開銷,內聯函數提升程序運行的效率
對比C的宏
C語言不足:宏
#define ADD(x, y) ((x)+(y))int main()
{int ret1 = ADD(2, 3); //((2)+(3))*5int a = 1, b = 2;int ret2 = ADD(a | b, a & b); //((a | b)+(a & b))return 0;
}
宏的缺點:
- 容易出錯,語法細節多
- 不能調試
- 沒有類型安全的檢查
inline int Add(int x, int y)
{int c = x + y;return c;
}int main()
{int ret1 = Add(1, 2);int ret2 = ADD(1, 2);return 0;
}
用enum,const,inline替代宏
enum,const替代的是宏常量
inline替代宏函數
普通函數要建立棧幀,會有很多的消耗
內聯函數的特點:
- 不用建立棧幀,會在調用的地方展開,沒有函數調用棧幀的開銷,提高了效率
- 克服了宏的缺點
- 可以調試
- 好寫,語法簡單
查看方式
Debug版本下
內聯函數在默認情況下,沒有去展開
在項目屬性里將常規里的調試信息格式改為程序數據庫
優化里的內聯函數擴展改為只適用于_inline(/Ob1)
再進行調試
這是沒有call了,不會建立棧幀
特性
- inline是一種以空間換時間的做法,如果編譯器將函數當成內聯函數處理,在編譯階段,會用函數體替換函數調用,缺陷:可能會使目標文件變大,優勢:少了調用開銷,提高程序運行效率。
- inline對于編譯器而言只是一個建議,不同編譯器關于inline實現機制可能不同,一般建議:將函數規模較小(即函數不是很長,具體沒有準確的說法,取決于編譯器內部實現)、不是遞歸、且頻繁調用的函數采用inline修飾,否則編譯器會忽略inline特性。
- inline不建議聲明和定義分離,分離會導致鏈接錯誤。因為inline被展開,就沒有函數地址了,鏈接就會找不到。
內聯只適合于頻繁調用的小函數,小于等于10行代碼
指令影響編譯好的可執行程序的大小
如果暴力地對100行的大函數內聯展開
比如有10000個調用這個函數的地方
展開合計:100*10000
行指令
如果展開,不需要call,有多少個調用的地方就展開多少次,假設調用的地方是1行指令,展開成100行指令
不展開合計:100+10000
行指令
有10000個調用的地方,不展開,每次調用只有一行指令
內聯函數聲明定義分離
#pragma once
#include <iostream>
using namespace std;inline void f(int i);
#include "Func.h"void f(int i)
{cout << i << endl;
}
#include "Func.h"int main()
{f(10);return 0;
}
內聯函數不能聲明和定義分離
如果分離在兩個文件,會報錯,發生鏈接錯誤
在編譯的時候,發現函數和參數能匹配上,先過
#pragma once
#include <iostream>
using namespace std;inline void f(int i);
void func();
#include "Func.h"void f(int i)
{cout << i << endl;
}void func()
{f(5);
}
#include "Func.h"int main()
{//f(10);func();return 0;
}
調f不可以,調func可以
不可以直接調,可以間接調
調用f的時候需要找f的地址,因為編譯的時候只有聲明
鏈接的時候沒有找到內聯函數的地址
因為內聯函數不會生成地址,或者生成的地址不會生成符號表
內聯函數直接再調用的地方展開了,就不需要地址
所以會報錯
在Test.cpp自己的文件里調的時候,不會到鏈接那一步去找
因為直接有聲明和定義,在Func.cpp里面,直接拿定義就在這展開了
內聯函數如果分離在.h和.cpp,就不能再其他文件去用
如果再其他文件去用會報錯
只能在當前這個cpp文件里使用
內聯函數最好不要聲明和定義分離
這樣不管在哪個文件都可以使用,聲明和定義在同一個文件就不需要鏈接了,因為直接就能找到定義
auto關鍵字
隨著程序越來越復雜,程序中用到的類型也越來越復雜,經常體現在:
- 類型難于拼寫
- 含義不明確導致容易出錯
是可以通過typedef給類型取別名
但是在編程時,常常需要把表達式的值賦值給變量,這就要求在聲明變量的時候清楚地知道表達式的類型。然而有時候要做到這點并非那么容易,因此C++11給auto賦予了新的含義
auto簡介
可以自己推導類型
int main()
{int a = 0;int b = a;auto c = a;auto d = &a;auto* e = &a;auto& f = a;cout << typeid(c).name() << endl;cout << typeid(d).name() << endl;cout << typeid(e).name() << endl;cout << typeid(f).name() << endl;return 0;
}
這里c就是int類型
typeid可以用來打印一個對象的類型
可以省略下面的類型的定義,使代碼短一點點
#include <vector>
#include <string>int main()
{vector<string> v;//vector<string>::iterator it = v.begin();auto it = v.begin();return 0;
}
注意
- 使用auto定義變量時必須對其進行初始化,在編譯階段編譯器需要根據初始化表達式來推導auto的實際類型。因此auto并非是一種“類型”的聲明,而是一個類型聲明時的“占位符”,編譯器在編譯期會將auto替換為變量實際的類型。
- auto與指針和引用結合起來使用
用auto聲明指針類型時,用auto和auto*
沒有任何區別,但用auto聲明引用類型時則必須加& - 在同一行定義多個變量
當在同一行聲明多個變量時,這些變量必須是相同的類型,否則編譯器將會報錯,因為編譯器實際只對第一個類型進行推導,然后用推導出來的類型定義其他變量。 - auto不能作參數,返回值也不支持
- auto的意義就是,定義對象時,類型較長,用它比較方便
- auto不能聲明數組
范圍for
對于一個有范圍的集合而言,由程序員來說明循環的范圍是多余的,有時候還會容易犯錯誤。因此C++11中引入了基于范圍的for循環。for循環后的括號由冒號“ :”分為兩部分:第一部分是范圍內用于迭代的變量,第二部分則表示被迭代的范圍
int main()
{int array[] = {1, 2, 3, 4, 5};for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)array[i] *= 2;for (int* p = array; p < array + sizeof(array) / sizeof(array[0]); ++p)cout << *p << endl;
}
對數組進行遍歷,C語言進行遍歷,使用下標計算數組的大小,或者搞一個數組的指針去算,比較麻煩
int main()
{int array[] = {1, 2, 3, 4, 5};for (auto e : array){cout << e << " ";}cout << endl;return 0;
}
會依次取數組中的值賦值給e
自動判斷結束,自動++往后遍歷
這里的auto雖然也可以輸入實際的類型,但是用了auto,就算后面數組對象的類型變了,這里也不需要修改代碼
注意:與普通循環類似,可以用continue來結束本次循環,也可以用break來跳出整個循環
int main()
{int array[] = {1, 2, 3, 4, 5};for (auto e : array){e++;cout << e << " ";}cout << endl;for (auto e : array){cout << e << " ";}cout << endl;return 0;
}
如果想對每個數據++一下
發現并沒有真正++。
只是修改了e,沒有修改了數組中的數值。
因為是依次取數組中的每個值賦值給e,e的改變不會影響數組里面。
要修改的話,要加個引用
int main()
{int array[] = {1, 2, 3, 4, 5};for (auto& e : array){e++;cout << e << " ";}cout << endl;for (auto e : array){cout << e << " ";}cout << endl;return 0;
}
使用條件
- for循環迭代的范圍必須是確定的
對于數組而言,就是數組中第一個元素和最后一個元素的范圍;對于類而言,應該提供begin和end的方法,begin和end就是for循環迭代的范圍。 - 迭代的對象要實現++和
==
的操作。
指針空值nullptr
void f(int)
{ cout<<"f(int)"<<endl;
}
void f(int*)
{ cout<<"f(int*)"<<endl;
} int main()
{ f(0); f(NULL); f((int*)NULL);return 0;
}
函數參數只寫形參的類型,沒有寫形參的變量
不會報錯,實參傳給形參,形參可以不要,不要的時候可以作形參的匹配
第一個調用的是int,第二個NULL也調用的是int
因為C++的庫,在實現的時候NULL定義成了一個宏
NULL可能被定義為字面常量0,或者被定義為無類型指針(void*)的常量
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
C++98中,字面常量0既可以是一個整形數字,也可以是無類型的指針(void*)常量,但是編譯器默認情況下將其看成是一個整形常量,如果要將其按照指針方式來使用,必須對其進行強轉(void*)0
注意
- 在使用nullptr表示指針空值時,不需要包含頭文件,因為nullptr是C++11作為新關鍵字引入的。
- 在C++11中,sizeof(nullptr) 與 sizeof((void*)0)所占的字節數相同。
- 為了提高代碼的健壯性,在后續表示指針空值時建議最好使用nullptr。