- c++函式引數和回傳值
- 函式存盤位置
- 函式引數入堆疊順序
- 初始化串列
- 函式的回傳值
- 用引數參考來回傳
- 回傳一個引數指標
- 回傳一個物件
- 總結
- 函式的幾種變體
- inline 函式
- 函式物件
- lambda 函式
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=https://www.cnblogs.com/zijian-yang/archive/2023/05/19/10}的物件,然后呼叫多載函式operator(), 回傳 10 + 1 = 11
上述代碼因為是在頭檔案實作的,所以編譯期會自動把operator()函式當成inline函式,執行效率很高,
lambda 函式
lambda 其實就是一個函式物件,會在編譯期展開成一個函式物件體,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/552927.html
標籤:其他
下一篇:返回列表