C++ 函數(一)參數傳遞/return語句/返回類型
6.1 基礎
函數的返回值不能是數組或函數類型,但可以是指向數組或函數的指針
6.1.1 作用域,局部對象
函數體是一個塊,構成一個新的作用域,其中定義的變量和形參都是局部變量
在函數體外部定義的對象存在於程序的整個執行過程中
自動對象:
只存在於塊執行期間的對象,例如形參,函數終止,形參會被銷毀,局部變量如果本身不帶初始值,則會執行默認初始化,意味內置類型的未初始化局部變量將產生未定義的值
局部靜態對象
定義為static類型,如果沒有顯式的初始值,則將初始化為0
函數聲明
又叫函數原型,不包含函數體,無需形參名(也可以有)
6.2 參數的傳遞
pass by value
將實參的值拷貝后賦值給形參,對形參的操作不會影響實參
指針形參
傳遞指針,拷貝的是指針的值,拷貝后的兩個指針是不同的指針,但是由於指針可以間接的訪問所指的對象,所以可以通過指針修改它所指的對象值
void reset(int *p){
*p = 0;//改變了指向的對象的值
p = 0;//只改變了p的局部拷貝,實參未改變
}
C++建議用引用類型的形參代替指針
pass by reference
void reset(int &p){}
改變引用形參,就是改變初始化形參的實參
盡量使用引用來避免拷貝,而且有些類不支持拷貝(比如IO類)
6.2.2 const形參,const實參
頂層const作用於對象本身,形參有頂層const時,傳遞常量或非常量對象都可以(實參初始化形參時會忽略頂層const)
void func(const int n){}
const int i = 1;
int j = 2;
func(i);
func(j);
//上面兩種調用方法都正確
//由於這種忽略,導致下面兩種函數其實是一樣的,所以不是重載
void func(const int i);
void func(int i);
6.2.3 指針,引用形參
可以使用非常量初始化一個底層const,但是不能反過來,一個普通的引用必須用同類型的對象初始化
int i = 0;
const int ci = i;
string::size_type ctr = 0;
reset(&i);//形參類型為int*
reset(ci);//錯誤,不能使用const int對象的指針去初始化int*
reset(i);//形參為int&時
reset(ci);//不能將普通引用綁定到const對象ci上
reset(42);//不能綁定字面值
reset(ctr);//類型不匹配,ctr是無符號類型
可以使用字面值初始化常量引用
盡量使用常量引用,避免限制函數能接收的實參類型(字面值)
6.2.4 數組形參
數組的兩個特殊性質:
- 不能拷貝數組:無法值傳遞
- 使用數組時通常轉化成指針:傳遞參數時,實際上傳遞的是指向數組首元素的指針
數組的大小對函數調用沒影響
管理指針形參:(主要是長度問題)
一種方法是使用標記指定長度,只適用於C語言中類似C風格字符串,以空字符結束
所以更實用的是另外兩種方法:
-
標准庫函數begin,end
void print(const int *beg,const int *end){ while(beg!=end){ cout<<*beg++<<endl; } } int j[2] = {0,1}; print(begin(j),end(j));
-
顯式傳遞一個數組大小形參
print(j,end(j)-begin(j));
數組引用形參/const形參
不需要改變數組元素值時,最好加上const
//數組的引用,維度是類型的一部分
void print(int (&arr)[10]){}
//必須加(),否則arr就是引用的數組,而不是具有10個整數的整型數組的引用
//傳遞參數時,只能將函數作用域大小為10的數組
int i[2] = {2,1};
print(i);//錯誤
多維數組
C++的多維數組就是數組的數組
數組的數組,首元素本身就是一個數組,第二維不能省略
void print(int (*matrix)[10],int rowsize);
//如果沒有(),則是十個指針構成的數組,而不是指向含有10個整數的數組的指針
void print(int matrix[][10],int rowsize);
6.2.5 main(int argc,char *argv[])
6.2.6 可變形參
處理不同數量的實參的函數:
-
如果所有實參的類型相同,可以傳遞標准庫類型:initializer_list
-
不同則寫可變參數模板
-
省略符(一般只用於與C函數交互的接口程序)
形式:
void fun(parm_list,...)
void fun(...)
僅用於C和C++通用的類型
initializer_list對象中的元素永遠是常量值
拷貝,賦值一個initializer_list對象不會拷貝列表中的元素,拷貝后原始列表和副本共享元素
list2(list1);//拷貝
list2 = list1;//賦值
void error_msg(initializer_list<string> il){
//initializer_list有begin,end,所以可以使用指針,范圍for循環遍歷列表
}
//調用:expected,actuall為string對象
error_msg({"functionX",expected,actuall});
6.3 返回值 (return)
無返回值的return后可以什么都沒有,也可以跟另外一個返回void的函數
不要返回局部對象的引用或指針
函數結束后,局部變量的內存空間會被釋放
const string&manip(){//下面兩個都錯
string ret;
if(){
return ret;//局部對象
}
else{
return "empty";//臨時局部變量
}
}
引用返回左值:
調用運算符優先級和點,箭頭相同
//符合左結合律,返回指針,引用,類的對象:
auto s = func(s1,s2).size();
調用返回引用的函數會得到左值,其他類型為右值
因此這種情況下函數的返回值可以像使用其他左值一樣:
char &get_val(string &str,string::size_type ix){
return str[ix];
}
int main()
{
string s("a value");
get_val(s,0) = 'A';
//s[0]被改為A
return 0;
}
//返回值是個常量引用就不能賦值了
列表初始化返回值
vector<string>pro(){
return {"one","two"};
}
如果返回內置類型則{}中最多包含一個值
返回數組指針
數組不能拷貝,所以函數不能返回數組
使用類型別名簡化返回數組的指針和引用
//下面兩行代碼是等價的
typedef int arr[10];
using arr = int[10];
//返回一個指向含有十個整數的數組的指針
arr* func(int i);
//如果不使用類型別名:
Type (*function(parameter_list))[dimension]
int (*func(parameter_list))[10];
//必須有*之外的(),否則聲明的函數返回類型就會是指針的數組,而不是數組的指針
尾置返回:
函數真正的返回類型在形參列表之后,格式:
auto func(int i)->int(*)[10];
//任何函數定義都能使用尾置返回
decltype
如果知道函數返回的指針指向哪個數組,則可以使用decltype
int o[] = {12,3};
int o2[] = {1,2};
decltype(o)* arrptr(int i){}
decltype的結果是數組,所以還要加上*