C++函數知識概要總結(二)

關於返回類型和return語句

兩種形式:

  • return;
  • return expression;
關於無返回值函數

返回void的函數不要求非得有return語句,因爲在這類函數最後都會隱式的執行return;所以並不是它真的不需要,而是他本來就有。

注意其實返回void的函數也可以是上面的第二種返回方式,即return expression的形式,只要保證expression是一個返回void的函數即可。不然會報錯。

關於有返回值的函數

在含有return語句的循環後面應該也有一條return語句,如果沒有的話改程序就是錯誤的,但是很多編譯器不會報錯。

關於值是如何被返回的

返回一個值的方式和初始化一個變量或形參的方式完全一樣:返回的值用於初始化調用點的一個臨時量,該臨時量就是函數調用的結果。

如果是用的是引用,那不管是調用函數還是返回結果都不會真正拷貝對象。

函數完成後,它所佔用的存儲空間也隨之被釋放掉,因此,函數終止意味着局部變量的引用將指向不再有效的內存區域。
所以不能返回局部對象的引用或指針。

int *zz(){
    int a=6677;
    int *p = &a;
    return p;
}
int main()
{
   	int *a = zz();
   	cout << *a;
}

並不會報錯,據書上說會引發未定義的行爲。但是我嘗試了並沒有發生奇怪行爲,應該是數據不夠大。
總之,這樣非常之不安全。

引用函數返回左值
char &get_val(string &str, string::size_type ix){
 	return str[ix];
}
int main()
{
	string s("string");
	get_val(s, 0) = 'A';
	cout << s;
}

就這個引用函數會比較陌生,因爲從來都沒有使用過。會返回左值,但返回的是常量引用的時候,也不能這樣寫。(廢話,返回的常量,人家怎麼再賦值)

列表初始化返回值

這個也很陌生,完全就沒用過

寫一個例子

vector<string> p(){
	 return {"I have", "apple", "pen"};	
}
int main()
{
  	vector <string> s = p();
	for(auto c : s)
    cout << c;
}

主函數main的返回值

如若函數的返回值不是void, 那麼它必須返回一個值,這是沒有問題的,但是我們發現我們的主函數的最後有時並不加return也沒有問題,那是因爲編譯器發現沒有的時候會隱式的插入一條返回爲0的return 語句。

主函數main可以看作是狀態指示器,返回0表示成功,返回其他值表示執行失敗,其中非0值的具體含義依機器而定。

爲了使返回值與機器無關
在cstdlib頭文件中定義了兩個預處理變量。

int main(){
	if(some_fail) return EIXT_FAILURE;
	else return EXIT_SUCCESS;
}

關於遞歸

遞歸就是自己調用自己
這裏需要注意main函數不能調用自己。
來個小練習遞歸輸出vector

void print_v(vector<int>v,int len, int i){
    if(i==len) return ;
    cout << v[i]<<endl;
    return print_v(v, len, i+1);
}
int main()
{
	  vector <int> s{1, 2, 3, 4, 5};
	  print_v(s, s.size(), 0);
}

關於返回數組指針

因爲數組不能背拷貝,所以函數不能返回數組,但是卻可以返回數組的指針或是引用。

 typedef int arrT[10];//
using arrT = int[10];//跟上個句子一毛一樣,個人更喜歡用using
arrT* func(int); //表示func返回一個指向10個整數的數組的指針。

使用類型別名簡化了定義數組指針的過程????並不覺得,反而我覺得這樣更容易混淆,個人更傾向於下面的最原始的方式。

基本方式
int (*func(int i))[10];

書上沒例子,只好自己想,這個例子有點難想的。

int a[10];
int (*func(int l))[10]{
   for(int i=0; i<10; i++)
  	  a[i] = l;
	int (*b)[10];
	b = &a;
    return b;
}
int main()
{
   int (*x)[10];
   x = func(88);
   for(auto c : *x)
   cout << c <<endl;
}
尾置返回類型

c++11新標準添加了尾置返回類型,形如

auto func(int i) -> int(*)[10];

稍微改動了點

int a[10];
auto func(int l)->int(*)[10]{//注意這裏只能用auto
    for(int i=0; i<10; i++)
  	  a[i] = l;
  int (*b)[10];
  b = &a;
  return b;
}
使用decltype

直接例子就可

int a[10];
//int (*p)[10];
decltype(a) *func(int l){//上一行註釋去掉的話這一行變成decltype(p) func(int l);
    for(int i=0; i<10; i++)
       a[i] = l;
    int (*b)[10];
    b = &a;
    return b;
}

再回顧一下,decltype(a)返回的不是指針,而是數組,所以這裏在其後還需要加上*。

來個小練習,編寫一個函數的聲明,使其返回數組的引用並且該數組包含10 個string對象。不要使用尾置返回類型、decltype 或者類型別名。

string v[10];
string (&func(const string &s))[10]{
    string (&p)[10] = v;
    for(auto &c : p) c = s;
   return p;
}
int main()
{
   string (&o)[10] = func("hahahah");
   for(auto &c : o) cout << c<<endl;
}
//關於函數定義的改寫
//auto func(const string &s)->string(&)[10] {
//decltype(v) &func(const string &s){

關於函數重載

回顧一下函數重載會忽略頂層const。

main函數不能重載

注意

int lookup(const account &acct)
int lookuo(const account)

第二個省略了形參的名字,其實這倆是一樣的
當調用重載函數時有這麼三種可能的結果

找到最佳匹配,並生成調用該函數的代碼
找不到任何匹配,發出錯誤。
有多餘一個函數可以調用,也將發生錯誤,稱爲二義性調用

關於重載與作用域

在c++語言中,名字查找發生在類型檢查之前。

void read(){cout << "read";}
void func(int iv){
	  read(); // 這一行不會報錯
	  bool read = false;
	  read();  //這一行會報錯
}

通過上述例子我舉得很容易理解作用域。

關於特殊用途語言特徵

介紹三種:

  • 默認實參
  • 內聯函數
  • constexpr函數
關於默認實參
string screen(int ht = 24, int wid = 80, char backgrnd=' ');

這就是默認實參。
注意注意注意:一旦某個形參被賦予了默認值,它後面所有的形參都必須有默認值。

函數調用時實參按其位置解析,默認實參負責填補函數調用缺少的尾部實參(靠右側位置)

舉個例子

string window;
window = screen(); // 等於screen(24,80,' ' );
window = screen(66);//等於screen(66,80,' ' );
window = screen(66, 256);// 等於screen(66,256,' ' );
window = screen(, , '?'); //錯誤,只能省略尾部實參

因此當設計含有默認參數的函數時,需要合理的設置形參的順序,儘量讓不怎麼使用默認值的形參出現在前面,讓那些經常使用默認值的形參出現在後面。

關於默認實參聲明
void fun(int a,string str="apple",double b=3.14);
void fun(int a=1,string str,double b);//補充聲明只需指明補充部分即可,已有部分不能覆蓋
void fun(int a=1,string str,double b=3.14);//錯誤,不能在對b進行覆蓋。

可以補充聲明,但不能覆蓋,就這麼簡單的定義,書上寫的那叫一個及其拗口,讀了半天,不如自己試驗樣例來的實在。

局部變量不能作爲默認實參。只要表達式的類型能轉換成形參所需的類型,該表達式就能作爲默認實參。
用作默認實參的名字在函數聲明的作用域內解析,而求值過程發生在函數調用時。

內聯函數和constexpr函數

函數當然有各種好處,什麼方便啦已讀啦安全啦啥的

但是有一個潛在的缺點,調用函數一般比求等價的表達式的值要慢一些。因爲一次函數調用包含着一系列的工作

調用前要先保存寄存器,並在返回時恢復;可能還需要拷貝實參等等

就比較慢
這個時候 內聯函數登場了。
內聯函數,在編譯過程會被內斂展開。例子

cout << shorterString(s1, s2)<<endl;

編譯時會變成

cout << (s.size()<s2.size() ? s1: s2) << endl;

inline cosnt string &shortString(const string &s1, const string &s2);

內聯說明只是向編譯器發出的一個請求,編譯器可以選擇忽略這個請求。
一般用於優化規模較小,流程直接並且頻繁使用的函數。據說很多編譯器不支持內斂遞歸函數。

關於constexpr函數

用這個函數有這麼幾點要求:

  • 函數的返回類型只能是字面值類型
  • 所有形參類型也只能是字面值類型
  • 有且只能有一條return語句。

編譯器會把constexpr函數的調用替換成其結果值,爲了能在編譯過程中隨時展開,constexpr函數會被隱式的指定爲內聯函數。

constexpr函數內也可以包含其他語句,只要這些語句在運行時不執行任何操作就行,比如空語句,類型別名,using聲明。
注意:constexpr函數不一定返回常量表達式

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章