C++Primer-第六章 函數

第六章 函數

函數就是一個命名了的代碼塊,我們通過調用函數執行相應的代碼。

6.1 函數基礎

一個典型的函數定義包括以下部分:返回類型、函數名、由0個或者多個形參組成的列表以及函數體。

  • 編寫函數
int fact(int val)
{
	int ret=1;			//局部變量,用於保存計算結果
	while(val>1)		
	{
		ret*=val--;		//
	}
	return ret;
}
  • 調用函數
    函數的調用完成兩項工作:
    • 用實參初始化函數對應的形參
    • 將控制權轉移給被調用函數,此時主函數的執行被暫時中斷。
  • 形參和實參
    • 實參是形參的初始值。
    • 實參的類型必須與對應的形參類型匹配
  • 函數的形參列表
    函數的形參列表可以爲空,但是不能省略。
void f1(){/***/}				//隱式定義空形參列表
void f2(void){/****/}			//顯示地定義空形參列表
int f3(int v1,v2){/****/}		//錯誤: 參數類型不能省略
int f4(int v1,int v2){/****/}	//正確

6.1.1 局部對象

在C++語言中,名字有作用域,對象有聲明週期
名字的作用域是程序文本的一部分,名字在其中可見。
對象的聲明週期是程序執行過程中該對象存在的一段時間。

  • 自動對象
    只存在於塊執行期間的對象稱爲自動對象。當塊執行結束後,塊中創建的自動對象的值就會變成未定義的了。(是不是可以理解爲銷燬? 銷燬要根據具體情況來考慮,比如釋放或者垃圾回收)
  • 局部靜態對象
    在程序的執行路徑第一次經過對象定義語句時初始化,並且直到程序終止才被銷燬,在此期間即使對象所在的函數結束也不會對他有影響。
size_t count_calls()
{
	static size_t ctr=0;		//調用結束後,這個值仍然有效
	return ++ctr;
}

6.1.2 函數聲明

  • 和其他名字一樣,函數的名字也必須在使用之前聲明。
  • 函數的聲明和函數的定義非常類似,唯一的區別是函數聲明無函數體,用一個分號替代即可。
  • 函數的聲明不包含函數體,所有也就無需形參的名字。
  • 函數的聲明建議在頭文件中聲明,在源文件中定義。

6.1.3 分離式編譯

隨着程序的越來越複雜,可以把文件中的函數或者其他類等按照邏輯關係將其劃分開來,然後每個文件獨立編譯

6.2 參數傳遞

每次調用函數時都會重新創建他的形參,並用傳入的實參對形參進行初始化。

形參初始化的機理與便利初始化一樣

  • 當形參是引用類型時,我們說它對應的實參是引用類型或者函數被傳入引用個調用。
  • 當實參的值別拷貝給形參時,形參和實參是兩個獨立的對象。我們說這樣的實參是值傳遞。

6.2.1 傳值參數

  • 非引用變量
    當初始化一個非引用類型的變量時,初始值被拷貝給變量。此時的改動不會影響初始值。
int n=0;		//int 類型的初始變量
int i=n;		//i是n的值的副本
i=42;			// i的值改變,n的值不變
  • 指針形參
    指針的行爲和其他非引用類型一樣。當執行指針拷貝操作時,拷貝的是指針的值。拷貝之後,兩個指針是不同的指針,但兩個指針都指向同一個對象。
int n=0,i=42;
int *p=&n,*q=&i;			// p指向n; q指向i
*p=42;						//n的值改變;p不變
p=q;						// p現在指向了i;但是i和n的值都不變

//函數接受一個指針,然後將指針所指的值置爲0
void reset(int *ip)
{
	*ip=0;			// 改變指針ip所指對象的值
	ip=0;			// 只改變了ip的局部拷貝,實參未被改變
}
#include <iostream>
using namespace std;

void test(int *a, int *b)
{
	cout << "&a:" << &a << endl;
	cout << "*a:" << *a << endl;
	*a = 7, *b = 8;
}

int main()
{
	int a = 3, b = 4;
	cout << "&a:" <<&a<< endl;
	cout << "a:" << a << endl;
	test(&a, &b);
	return 0;
}
結果:
&a:00ACFDC0
a:3
&a:00ACFCDC
*a:3

6.2.2 傳引用參數

引用的操作實際上是作用在引用的對象上,引用形參丙丁初始化它的對象。

  • 使用引用避免拷貝
bool isShorter(const string &s1,const &string s2)
{
	reutrn s1.size() < s2.size();
}

拷貝大的類類型對象或者容器對象比較低效,甚至有的類類型根本不支持拷貝操作。當某種類型不支持拷貝操作時,函數只能銅鼓引用形參訪問該類型的對象。

如果函數無須改變引用形參的值,最好將其聲明爲常量引用。

  • 使用引用形參返回額外信息
    一個函數只能返回一個值,然而有時函數需要同時返回多個值,引用形參爲我們一次返回多個結果提供了有效的途徑。
string::size_type find_char(const string &s,char c,string::size_type &occurs)
{
	auto ret=s.size();		//第一次出現的位置(如果有的話)
	occurs=0;				//設置表示出現次數的形參的值
	for(decltype(ret)i=0;i!=s.size();++i)
	{
		if(s[i]==c)
		{
			if(ret==s.size())
			{
				ret=i;		//記錄c第一次出現的位置
			}
			++occurs;		//將出現的次數加1
		}
	}
	return ret;				//出現次數通過occurs隱式地返回
}

6.2.3 const形參和實參

當形參是const時,一定要注意const是否是頂層const,頂層const作用於對象本身:

const int ci=1024;		//不能改變ci,const是頂層const
int i=ci;				//正確:當拷貝ci時,忽略它的頂層const
int *const p=&i;		//錯誤:const是頂層的,不能給p賦值
*p=0;					//正確:通過p改變獨享的內容是運行的
  • 指針或引用形參與const
    形參的初始化方式和變量的初始化方式是一樣的。
  • 儘量使用常量引用
    把函數不會改變的形參定義成引用是一種比較常見的錯誤,這麼做帶給函數的調用者一種誤導,即函數可以修改它的實參的值。所有把形參定義爲const類型的能避免錯誤。

6.2.4 數組形參

數組的兩個特殊性質對我們定義和使用作用在數組上的函數有影響:

  1. 不允許拷貝數組
  2. 使用數組時會將其轉換成指針

6.2.5 main: 處理命令行選項

main函數有兩個參數

int main(int argc,char *argv[]){……}
int main(int argc,char **argv[]){……}

6.2.6 含有可變形參的函數

  • 使用函數
void error_msg(ErrCode e,initializer_list<string> il)
{
	cout<<e.msg()<<":";
	for(const auto &elem:il)
		cout<<elem<<" ";
	cout<<endl;
}
  • 省略符形參
void foo(param_list,...);
void foo(...);

6.3 返回類型和return語句

在這裏插入圖片描述

6.4 函數重載

如果同一作用域內的幾個函數名字相同但形參列表不同,我們稱之爲重載函數

void print(const char *cp);
void print(const int *beg,const int *end);
void print(const int ia[],size_t size);

main函數不能重載

6.5 特殊用途語言特性

6.5.1 默認實參

某些函數有這樣一種形參,在函數的很多次調用中他們都被賦予一個相同的值,此時,我們把這個反覆出現的值稱爲幻術的默認實參

typedef string::size_type sz;		//取個別名
string screen(sz ht=24,sz wid=80,char background=' ');

6.5.2 內聯函數和constexpr函數

我們把規模較小的操作定義成函數有很多好處:

  1. 閱讀和理解函數的調用要比讀懂等價的條件表達式容易得多。
  2. 使用函數就可以確保行爲的統一,每次相關操作都能保證按照同樣的方式進行。
  3. 如果我們需要修改計算過程,顯然修改函數要比先找到等價表達式所有出現的地方再逐一進行修改更容易。
  4. 函數可以被其他應用重複利用,省去了程序員重新編寫的代價。
  • 內聯函數可避免函數調用的開銷
    將函數指定爲內聯函數,通常就是將他在每個調用點上內聯地展開。

6.6 函數匹配

  • 確定候選函數和可行函數
    • 函數匹配:
      • 選定本次調用對應的重載函數集,集合中的函數稱爲候選函數
        候選函數具備兩個特徵:
        • 與被調用的函數同名
        • 其聲明在調用點可見
      • 考察本次調用提供的實參,然後從候選函數中選出能被這組實參調用的函數,這些新選出的函數 稱爲可行函數
        可行函數有兩個特徵:
        • 其形參數量與本次調用提供的實參數量相等
        • 每個實參的類型與對應的形參類型相同,或者能轉換成形參的類型
      • 從可行函數中選擇與本次調用最匹配的函數。

6.7 函數指針

函數指針指向的是函數而非對象。
要想聲明一個可以指向函數的指針,只要需要用指針替換函數名即可。

//比較兩個string對象的長度
bool lengthCompare(const string &,const string &);		//該函數的類型是bool

//pf指向一個函數,該函數的參數是兩個const string 的引用,返回值是bool類型
bool (*pf)(const string &,const string &);

*pf兩段的括號必不可少。如果不寫這對括號,則pf是一個返回值爲bool指針的函數。

  • 使用指針函數
pf = lengthCompare;		//pf指向名爲 lengthCompare的函數
pf = &lengthCompare;	//等價的賦值語句:取地址符是可選的
  • 重載函數的指針
void ff(int *);
void ff(unsigned int);
void (*pf1)(unsigned int)=ff;		//pf1指向ff(unsigned)
  • 函數指針形參
//第三個形參是函數類型,它會自動轉換成指向函數的指針
void useBigger(const string &s1,const string &s2,bool pf(const string &,const string &));
//等價的聲明:顯示地將形參定義成指向函數的指針
void useBigger(const string &s1,const string &s2,bool(*pf)(const string &,const string &));

我們可以直接把函數作爲實參作用,此時它會自動轉換成指針。

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