- [c++函數參數和返回值](#c函數參數和返回值) - [函數存儲位置](#函數存儲位置) - [函數參數入棧順序](#函數參數入棧順序) - [初始化列表](#初始化列表) - [函數的返回值](#函數的返回值) - [用參數引用來返回](#用參數引用來返回) - [返回一個參數指針](#返回 ...
c++函數參數和返回值
c++一直以來是一個關註效率的代碼,這樣關於函數的參數傳遞和返回值的接收,是重中之重。下文提供了一些個人的見解。
函數存儲位置
函數參數在編譯期展開,目前各平臺的編譯期均有不同。
名稱 | 存儲位置 |
---|---|
函數名稱和邏輯 | 代碼段存儲 |
函數參數和返回值 | 棧中或者寄存器(64位會有6個寄存器使用) |
new malloc 的變數 | 堆 |
函數參數入棧順序
關鍵字 | 堆棧清理 | 參數傳遞 |
---|---|---|
__cdecl | 調用方 | 在堆棧上按相反順序推送參數(從右到左) |
__clrcall | 不適用 | 按順序將參數載入到 CLR 表達式堆棧上(從左到右)。 |
__stdcall | 被調用方 | 在堆棧上按相反順序推送參數(從右到左) |
__fastcall | 被調用方 | 存儲在寄存器中,然後在堆棧上推送 |
__thiscall | 被調用方 | 在堆棧上推送;存儲在 ECX 中的 this 指針 |
__vectorcall | 被調用方 | 存儲在寄存器中,然後按相反順序在堆棧上推送(從右到左) |
所以直接在函數參數上,調用表達式和函數來回去值的話,非常危險
初始化列表
class Init1
{
public:
void Print()
{
std::cout << a << std::endl;
std::cout << b << std::endl;
std::cout << c << std::endl;
}
int c, a, b;
};
A這個類,可以通過 A a{1,2,3}; 來初始化對象。
看著很美好,但是有幾個問題需要註意。
參數是的入棧順序是跟著類的屬性的順序一致, 當前是 c, a, b;
int i = 0;
Init1 a = {i++, i++, i++};
a.Print();
當我如此調用的時候,得到的返回值是 1 2 0
i++的執行順序是從左到右,跟函數調用順序無關。 另外不能有 構造函數
class Init1
{
public:
Init1(int ia, int ib, int ic)
{
std::cout << "construct" << std::endl;
a = ia;
b = ib;
c = ic;
}
Init1(const Init1& other)
{
std::cout << "copy " << std::endl;
a = other.a;
b = other.b;
c = other.c;
}
void Print()
{
std::cout << a << std::endl;
std::cout << b << std::endl;
std::cout << c << std::endl;
}
int c, a, b;
};
當我添加了構造函數的時候。 用下麵代碼測試。會得到兩種結果
void Test_InitilizeList()
{
int i = 0;
//Init1 a = { i++, i++, i++ }; // 0 1 2
Init1 a(i++, i++, i++); // 2 1 0
a.Print();
}
函數的返回值
函數返回值的聲明周期在函數體內。
用參數引用來返回
class Result
{
public:
int result;
};
void GetResult(Result& result) ...
優點:
- 效率最高,因為返回值的對象在函數體外構造,可以一直套用, 可以一處構造,一直使用。
- 安全,可以定義對象,並不用new或者malloc, 沒有野指針困擾。
缺點: - 代碼可讀性低,不夠優美
- 無法返回nullptr. 一般在 Result 中定義一個; 用來表示一個空對象。
- 容易賦值到一個臨時對象中,當調用
GetResult({1})
會賦值到一個 臨時的 Result 對象中,拿不到返回值。正常來說也不會這樣做。
返回一個參數指針
class Result
{
public:
int result;
};
Result* GetResult() ...
優點:
- 簡潔明瞭
- 參數傳遞快速
缺點: - 指針如果在 函數內 static 需要考慮多線程。 如果是 new 出來的,多次調用效率不高
- 指針無法重覆使用,(可以用 std::share_ptr 增加對象池來解決問題。但會引入新的複雜度。)
- 需要考慮釋放的問題
返回一個對象
class Result
{
public:
int result;
};
Result GetResult() ...
優點:
- 沒有記憶體泄露的風險
- 簡潔明瞭
缺點: - 但有個別編譯期優化選項問題,會導致一次構造兩次拷貝, 第一次是函數體內對象向返回值拷貝,第二次是 返回值拷貝給外面接收參數的。
- 開啟編譯期優化選項,並且是 在 return Result 的時候構造返回對象,才能優化。
總結
一般如果是 簡單結構體,用 返回一個臨時對象的方式解決。
如果使用 返回一個參數指針,一般改成返回一個id,用一個manager來管理記憶體機制。或者 共用記憶體,記憶體池來解決記憶體泄露後續的問題
用 參數引用來返回的話,一般會這麼定義 int GetResult(Result& result)
函數返回值,用來返回狀態碼,真正的數據,放到 result 中。
函數的幾種變體
inline 函數
- inline 函數是內聯函數,是編譯期優化的一種手段,一般是直接展開到調用者代碼里,減少函數堆棧的開銷。
- inline 標識只是建議,並不是一定開啟內聯。
- 函數比較複雜或者遞歸有可能編譯期不展開。
- dll 導出的時候,可以不用加導出標識,會直接導出到目標處。
- inline 在msvc的平臺,只要實現頭文件中,加不加內聯是一樣的. (警告頂級調到最高/Wall, 不加inline標識的函數會提示,未使用的內聯函數將被刪除。)
- inline 函數比全局函數更快,但是全局函數無法定義在頭文件中(會報多重定義函數。)所以一般用class 包一層 static inline 函數,用來寫工具類。
函數對象
class A {
public :
int value;
int operator() (int val) {
return value + val;
}
}
上述代碼是一個函數對象,重載operator()得到一個函數對象。
int a = A{10}(1)
會返回11, 顯示構造了一個A{value=10}的對象,然後調用重載函數operator(), 返回 10 + 1 = 11
上述代碼因為是在頭文件實現的,所以編譯期會自動把operator()函數當成inline函數,執行效率很高。
lambda 函數
lambda 其實就是一個函數對象,會在編譯期展開成一個函數對象體。