6.3 返回類型和return語句
返回值類型分爲兩種,一種是有返回值類型,還有一直是無返回值類型,
無返回值類型的函數,可以只寫一個return;也可以不寫return
有返回值類型的函數,必須返回一個值,該值爲返回的類型或者可以隱式的轉化爲返回值類型。
無返回值
return;
有返回值
return experssion;//返回一個表達式,表達式由一個或者多個求值對象組成。
值是如何被返回的?
返回的值會在調用點初始化一個臨時量,這個臨時量就是函數調用的結果。
返回值類型可以是引用類型也可以是指針類型。需要注意的是,不要返回局部變量的引用以及指針類型。因爲局部變量在函數調用結束之後,聲明週期就結束了,內存被回收,所以返回局部變量的引用或者指針將產生未定義行爲。
int & f(){
int num=1;
return num;
}
引用返回左值
返回值類型爲引用的話,返回的是左值,既然是左值就擁有左值的屬性,如果返回的是一個非常量的引用,那麼函數可以放在賦值語句的左邊。
char& get_value(string &str,string::size_type index){
return str[index];
}
string str="123";
get_value(str,1)='c';
cout<<str<<endl;
>>> 1c3
看起來有點彆扭,但是試想以下,如果是類的成員函數這樣使用,看起來就正常很多了
String str="123";
str.get_value(1)='c';
這樣看是不是正常很多
列表初始化返回值
可以使用初始化列表對調用點的臨時變量進行初始化。
這裏我的理解是,調用點的臨時變量的類型爲返回值的類型,然後返回值類型使用初始化列表進行初始化,這樣一來就和平時用初始化列表初始化變量的形式一樣了。
如果是空列表{},則臨時變量使用值初始化。
內置類型初始化列表中只能由一個值
類類型的列表初始化,由類類型自己決定。
main函數的返回值
main函數可以不寫返回值,如果不寫的話,默認就是return 0;return 0表示程序執行成功,如果return 其他的值,含義則和具體的機器有關。
函數可以在函數體中調用自己,無論是直接調用還是間接調用,這樣調用的方式叫做遞歸。。遞歸最終要有終止條件,不然遞歸會一直進行下去直到調用棧滿了,然後報錯。
練習
6.30
bool str_subrange(const string& str1,const string &str2) {
if (str1.size()==str2.size()) {
return str1 == str2;
}
auto size = str1.size() < str2.size() ? str1.size() : str2.size();
for (decltype(size) i = 0; i < size;++i ) {
if (str1[i]!=str2[i]) {
return false;//這裏會報錯,提示沒有返回值
}
}
//return false;這裏不寫只會提示警告
}
6.31
當返回的是局部變量的引用時,返回的引用是無效的。
同樣返回的是局部變量的常量引用是無效的。
6.32
是正確的,該函數返回數組,對應index下標所對應的元素的引用
6.33
void print(vector<int>::iterator beg,vector<int>::iterator end) {
if (beg==end) {//要有終止條件
return;
}
else {
cout<<*beg<<endl;
print(beg+1,end);
}
}
6.34
如果把終止條件加上 if(val!=0)
if factorial(int val){
if(val>1){
return factorial(val-1)*val;
}
if(val!=1){
return 1;
}
可見,當val等於1
的時候,並沒有對應的return語句。 在vs2017中會編譯器會發出警告,執行之後,得到一個不可預料的值。
6.35
因爲val–,返回的是val-1之前的副本,這樣程序永遠都不會結束。
6.3.3 返回數組指針
之前說了,形參數組,返回值也可以是數組。但是數組是不能夠拷貝的,而且數組在使用時變成了指向其列表第一個元素的指針,所以我們不能直接返回數組,但是可以返回數組的引用 或者指針。
有四種方式可以返回數組的指針或者引用
1.最原始的方法,這個方法和聲明數組引用很像。
只不過變量名變成了函數名()
string (&get_string_arr())[10] {
}
2.使用類型別名
using string_arr_10 = string[10];
string_arr_10& get_string_arr() {
}
這種方式非常的直觀,可讀性也很高
3.尾置返回類型
auto get_string_arr()->string(&)[10]{
}
在平時填返回值類型的地方,使用auto關鍵字,使用->來寫上真的返回類型。
這種方式看起來也非常的直觀,直到返回的就是大小爲10的string數組的引用。不需要額外語句。
4.使用decltype
string arr[10];
decltype(arr)& get_string_arr() {
}
這種方式需要先定義一個變量,我覺得最不好用。。
下面這種方式看起來也是很清晰,然而這種方式不錯誤的,編譯不了。
string(&)[10] get_string_arr() {
}
練習
6.36
已經寫在上面了
6.37
尾置返回類型,因爲尾置返回類型不需要額外的定義語句,而且可讀性很高
6.38
把*換成&就可以了。
函數重載
什麼是函數重載,C++ Primer中說的很明白,如果同一個作用域內的幾個函數名字相同但是形參列表不同,我們稱之爲重載函數。
所以下面的類型都是重載函數
int func(int i);
int func(double i);
int func(string i);
注意,main函數不能重載,同時main函數也不也能遞歸調用。
我們定義的多個重載函數,在調用時由實參的類型來確定,要注意的是,某些函數我們認爲是重載的,但是其實它們就是同一個函數。
1.返回值類型不同,不是重載函數,這個從重載函數的定義就可以看到,定義中說的是形參列表不同,函數名相同
double func(int i);
int func(int i);
2.變量名字不同,不是重載函數 ,這些定義的其實是一個函數。形參的名字並不作爲重載函數的判斷條件。
int func(int);
int func(int a);
int func(int b);
3.類型別名,定義的形參列表和原來的類型不構成重載函數,INT本質上還是int,所以不構成重載函數。
using INT = int;
int func(INT i);
int func(int i);
4.形參是頂層const的形參(不是複合類型)無法和非const形參(不是複合類型)區分開來,不是重載函數。
int func(const int i);
int func(inti i);
int fun(int * i);
int func(int * const i)
以上的兩種情況都不是重載函數。
5.形參如果是某種類型的引用或者指針,定義了const的函數和沒有定義const的函數可以區分開來,構成重載函數。
int func(int& i);
int func(const int& i);
int func(int* i);
int func(const int* i);
複合類型的const和非const形參還是可以區分開來的。
但是在調用的時候,實參如果是非const,那麼會優先調用非const的函數。
之前在介紹函數的返回值類型時,寫了函數可以返回引用。但是有這樣一個情況。
const string& get_short_string(const string &s1,const string &s2){
return s1.size()<=s2.size()?s1:s2;
}
上面這個函數獲取較短的字符串,但是返回的結果爲const string& 類型,這就意味着無法修改它內部的值(當然使用const_cast 可以修改,但是這意味着我們的程序設計有問題)。
所以爲了滿足可以修改其內部的值,我們可以定義一個函數
string & get_short_string(string &s1,string &s2){
auto &r = get_short_string(const_cast<const string&>(s1),const_cast<const string7>(s2));
return const_cast<string&>(r);
}
使用這個函數得到的string類型是可以修改的,這樣如果我們傳入的類型是非常量的類型,那麼調用的就是這個函數,返回的類型可以修改,如果我們傳入的實參是常量類型,那麼調用的就是返回常量的版本。
調用函數的重載
我們定義了多個重載函數,那麼編譯器該怎麼決定調用哪一個呢。
之前說了是按照實參的類型和數量來確定。
但是有些時候,不同函數的形參類型可以相互轉化時就不太好確定了,這裏怎麼確定還沒看
目前需要i記住的是。
1。編譯器會選擇一個實參最匹配的函數來調用
2.如果能夠匹配多個函數,則編譯器報錯
3.如果沒有任何一個函數可以匹配,則編譯器報錯
練習
a。兩個函數的形參不構成重載
b。返回值類型不同,不構成重載
c。沒問題
6.4.1 重載和作用域
函數的聲明和定義一般都會定義在所有的作用域之外,但是也可以定義在局部作用域內。
和之前變量在局部作用域中的屬性一樣,局部作用域中的變量會屏蔽到外層的同名函數。
int func(double);
int func(string)
int main(){
int func(int);
func(123);//調用int
func(123.3);//double轉化爲int
func("123");//報錯
}
int main(){
int func;
func(1231);
}
上面的代碼中,局部定義域中聲明瞭函數func,屏蔽了外部定義的函數。所以func(“123“);會報錯
同樣,如果代碼中有變量的名字和函數的名字一樣,變量的名字也會屏蔽掉函數的名字。