淺析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 0i++的執(zhí)行順序是從左到右,跟函數調用順序無關。 另外不能有 構造函數

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) ...

優(yōu)點:

  • 效率最高,因為返回值的對象在函數體外構造,可以一直套用, 可以一處構造,一直使用。
  • 安全,可以定義對象,并不用new或者malloc, 沒有野指針困擾。
    缺點:
  • 代碼可讀性低,不夠優(yōu)美
  • 無法返回nullptr. 一般在 result 中定義一個; 用來表示一個空對象。
  • 容易賦值到一個臨時對象中,當調用getresult({1})會賦值到一個 臨時的 result 對象中,拿不到返回值。正常來說也不會這樣做。

返回一個參數指針

class result
{
public:
int result;
};
result* getresult() ...

優(yōu)點:

  • 簡潔明了
  • 參數傳遞快速
    缺點:
  • 指針如果在 函數內 static 需要考慮多線程。 如果是 new 出來的,多次調用效率不高
  • 指針無法重復使用,(可以用 std::share_ptr 增加對象池來解決問題。但會引入新的復雜度。)
  • 需要考慮釋放的問題

返回一個對象

class result
{
public:
int result;
};
result getresult() ...

優(yōu)點:

  • 沒有內存泄露的風險
  • 簡潔明了
    缺點:
  • 但有個別編譯期優(yōu)化選項問題,會導致一次構造兩次拷貝, 第一次是函數體內對象向返回值拷貝,第二次是 返回值拷貝給外面接收參數的。
  • 開啟編譯期優(yōu)化選項,并且是 在 return result 的時候構造返回對象,才能優(yōu)化。

總結

一般如果是 簡單結構體,用 返回一個臨時對象的方式解決。
如果使用 返回一個參數指針,一般改成返回一個id,用一個manager來管理內存機制?;蛘?共享內存,內存池來解決內存泄露后續(xù)的問題
用 參數引用來返回的話,一般會這么定義int getresult(result& result)函數返回值,用來返回狀態(tài)碼,真正的數據,放到 result 中。

 

函數的幾種變體

inline 函數

  • inline 函數是內聯(lián)函數,是編譯期優(yōu)化的一種手段,一般是直接展開到調用者代碼里,減少函數堆棧的開銷。
  • inline 標識只是建議,并不是一定開啟內聯(lián)。
  • 函數比較復雜或者遞歸有可能編譯期不展開。
  • dll 導出的時候,可以不用加導出標識,會直接導出到目標處。
  • inline 在msvc的平臺,只要實現頭文件中,加不加內聯(lián)是一樣的. (警告頂級調到最高/wall, 不加inline標識的函數會提示,未使用的內聯(lián)函數將被刪除。)
  • 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函數,執(zhí)行效率很高。

lambda 函數

lambda 其實就是一個函數對象,會在編譯期展開成一個函數對象體。

相關文章
亚洲国产精品第一区二区,久久免费视频77,99V久久综合狠狠综合久久,国产免费久久九九免费视频