1. 程式人生 > >[C++] 函式相關總結

[C++] 函式相關總結

1.函式的定義

定義函式三要素:函式名、引數列表、返回值;

1.2.函式原型

在函式使用前,必須對它進行宣告,這個宣告也稱為函式原型。如:

#include <iostream>
//add()函式原型
int add(const int& i,const  int& j);
int main()
{
	using namespace std;

	int i = 45;
	int j = 200;
	int sum = add(i,j);
	system("pause");
	return 0;
}

int add(const int& i, const int&
j) { std::cout << "i:" << i << ",j:" << j << std::endl; return i + j; }

函式原型和函式唯一的區別就是函式原型不提供函式體,它描述了函式到編譯器的介面,由於不需要方法體,因此函式宣告不要求提供變數名,不過應儘可能寫上。

函式原型一般在標頭檔案中宣告。

2.函式引數的傳遞

函式引數的傳遞有三種方式:按值傳遞、指標傳遞、引用傳遞,下面我們來看他們各自的特點。

2.1.按值傳遞

c++中通常按值傳遞(非引用型別),也就是說當實參傳遞給形參時,將實參的值賦給了形參,實參並沒有影響,如:

#include <iostream>

void Swap(int i,int j);
int main()
{
	using namespace std;
	int fir = 100;
	int sec = 300;
	Swap(fir, sec);
	cout << "fir:" << fir << ",sec:" << sec << endl;
	system("pause");
	return 0;
}

void Swap(int i, int j) {
	int temp;
	temp = i;
	i = j;
	j =
temp; }

執行該程式,發現並沒有交換fir和sec的值,在函式呼叫時,函式將建立一個int型別的變數i,然後將fir的值100賦給它,因此實際上函式Swap()中操作的是區域性變數i,而非變數fir。

按值傳遞導致被呼叫函式使用呼叫程式傳遞的值的拷貝。

2.2.按指標傳遞

當函式傳遞指標型別引數時,實際上,也是按值傳遞,但是這個值是一個地址,因此,就可以通過這個指標來修改所指物件的值了。如:

#include <iostream>

void Swap(int* i,int* j);
int main()
{
	using namespace std;
	int fir = 100;
	int sec = 300;
	int* p1 = &fir;
	int* p2 = &sec;
	cout << "fir:" << fir << ",sec:" << sec << endl;
	Swap(p1, p2);
	cout << "fir:" << fir << ",sec:" << sec << endl;
	system("pause");
	return 0;
}

void Swap(int* i, int* j) {
	int temp;
	temp = *i;
	*i = *j;
	*j = temp;
}

在執行Swap()函式時,函式首先建立了兩個指向int型別的指標i和j,然後分別將指標p1、p2的值賦給他們,而這個值是一個地址,因此,i和j也就指向p1和p2所指的物件了,從而操作所指的物件的值。

2.2.1.陣列形參

陣列有兩個特殊性質:

  • 1.不允許拷貝;
  • 2.當且進當陣列作為函式引數時,陣列名和指標等價。

因此,當陣列作為函式引數時,將其當做指標處理。以下三個函式原型宣告的函式完全相同:

const double add(const double* );
const double add(const double arr[]);
const double add(const double [10]);

以上每個函式的引數都是const double*。

無論函式引數為指標還是陣列,傳遞引數時並不違反按值傳遞的方法,只不過這個值是一個地址,而不是陣列的內容。

2.3.按引用傳遞

對於基本資料型別而言,按值傳遞引數可以實現功能,但如果是資料較大的型別,如類、結構等,按值傳遞則影響不僅消耗記憶體,還影響時間。因此可以將指標作為引數傳遞,除了傳遞指標以外,還可以傳遞引用。

我們知道,引用是一個變數的別名,在宣告引用時就會為它初始化。引用變數的主要用途就是用於形參,如下示例中使用引用來交換兩個變數的值:

#include <iostream>

void Swap(int& i, int& j);
int main()
{
	using namespace std;
	int fir = 100;
	int sec = 300;
	cout << "fir:" << fir << ",sec:" << sec << endl;
	Swap(fir, sec);
	cout << "fir:" << fir << ",sec:" << sec << endl;
	system("pause");
	return 0;
}
void Swap(int& i, int& j) {
	int temp = i;
	i = j;
	j = temp;
}
/*
fir:100,sec:300
fir:300,sec:100
*/

在這個示例中,當呼叫Swap(fir,sec)函式時,將引用型別的形參i和j作為了實參fir和sec的別名,從而完成值的交換。

2.3.1.儘量使用常量引用作為形參

當引用作為形參時,因儘量使用常量引用,其原因有三:

  • 1.const限定符可以保護實參,避免無意識的修改;
  • 2.使用const限定符可以處理const型別實參和非const型別實參,否則只能使用非const型別資料;
  • 3.使用const限定符後,如果函式引數型別不匹配,則將會生成一個臨時變數,並讓形參指向這個臨時變數,否則不會生成臨時變數。

如:

double multiply(double& h, double& k);
double add(const double& a, const double& b);
int i = 3,j = 4;
multiply(i,j);//BAD,不能使用int型別的值初始化double型別的引用
add(i,j);//OK,此時a指向一個臨時變數,將i值拷貝給了臨時變數

當函式返回型別為引用時,有一點非常重要需要注意,要避免返回函式結束時不再存在的記憶體單元的引用,即不能返回自動變數(區域性變數)。

2.4.總結

  • 1.如果資料物件很小,如基本類資料或小型結構,則按值傳遞,但若需要修改資料物件,則使用指標;
  • 2.如果資料物件是陣列,則使用指標。若不需要修改資料,則宣告為指向const的指標(如const int *);
  • 3.如果資料物件為結構或類,則使用引用,若不需要修改資料,則宣告為const引用。

3.行內函數

在函式原型或函式定義時,加上關鍵字inline就可以普通函式變為行內函數。
一般的做法是省略函式原型,將整個函式的定義放在函式原型處。

inline void show(const int& i);

行內函數和普通函式相比,區別不在於他們的編寫方式,而是C++編譯器如何將他們進行組合。

C++ 編譯器在呼叫普通函式時,是讓程式跳轉到獨立的程式碼段,執行完畢後再跳轉回來。

而對於行內函數,C++編譯器將使用函式程式碼替換函式的呼叫,不會進行程式碼段的跳轉。

因此,行內函數的執行速度比普通函式快,但卻需要佔用更多的記憶體。

使用行內函數時需要注意:

  • 1.只有對於較短的函式,才可以使用行內函數;
  • 2.行內函數不能遞迴。

4.函式預設引數

可以在宣告函式原型時,指定一個預設引數,如:

double play(std::string name, int times=1);

使用預設引數時需要注意兩點:

  • 1.預設引數只在函式原型中指定,函式定義中和普通引數完全相同;
  • 2.必須從右向左新增預設值。

通過定義預設引數,可以減少方法的過載數量。

函式過載

函式過載是指兩個或多個函式名相同,但引數列表不同的函式。

特點

  • 1.其特徵標是函式引數列表,對返回值無要求。
  • 2.引用和型別本身視為同一個特徵標;
int show(const int & i,double d);
//void show(int & i1,double d);//bad

過載和const形參

過載函式中const形參比較有趣。在一組過載函式中,如果形參為頂層const,則和非const形參不會進行區分,比如:

void show(const std::string s);
void show(std::string s);//BAD,重複宣告

但如果如果形參是底層const,則會進行區分,如:

void show(const std::string& s);
void show(std::string& s);//OK,函式過載

頂層const:表示任何的物件是常量。
底層const:表示指標所指的物件是一個常量,以及用於宣告引用的const。

呼叫過載函式

定義了一組過載函式後,需要以合理的實參去呼叫他們,對於引數個數不同或型別不同的過載函式,編譯器可以很容器確定要呼叫的函式,但是在有些情況下,就比較困難了,比如一組過載函式的引數數量相同且引數型別可以互相轉換。

當呼叫過載函式時,有三種可能的結果:

  • 1.編譯器找到一個和實參最佳匹配的函式並呼叫;
  • 2.編譯器找不到任何一個可匹配的函式,此時編譯器將報出無匹配錯誤資訊;
  • 3.編譯器找到多於一個的函式匹配,但都不是最佳匹配,此時編譯器將報出二義性錯誤資訊。

如下面示例中定義了一組過載函式:

#include <iostream>
#include <string>

void print(std::string& s );
void print(double);
void print(int);
void print(const char *);
void print(const std::string &);

int main()
{
	using namespace std;
	string s1 = "welcom c++ world";
	const string s2 = "hello world";
	const char * ch = "let's do it";
	int i = 30;
	double d = 12.3;
	print(s1);
	print(d);
	print(i);
	print(ch);
	print(s2);
	
	const int& i2 = 23;
	print(i2);
	system("pause");
	return 0;
}

void print(std::string&  s) {
	std::cout << "print(std::string&  s):" << s << std::endl;
}
void print(double d) {
	std::cout << "print(double d):" << d << std::endl;
}
void print(int i) {
	std::cout << "print(int i):" << i << std::endl;
}
void print(const char * pc) {
	std::cout << "print(const char * pc):" << pc << std::endl;
}
void print(const std::string & s) {
	std::cout << "print(const std::string & s):" << s << std::endl;
}
/*
print(std::string&  s):welcom c++ world
print(double d):12.3
print(int i):30
print(const char * pc):let's do it
print(const std::string & s):hello world
print(int i):23
*/

5.函式模板

函式模板屬於C++的泛型程式設計,可以通過泛型來定義函式,在定義函式時,將型別作為引數傳遞給模板,從而使得編譯器生成該型別的定義。

5.1.為何需要函式模板?

在程式中,可能會存在多個函式功能相同,但引數不同的情況,比如在交換值時分別定義了交換int型別和double型別的函式Swap()

void Swap(int * i, int * j );
void Swap(double * d, double * p);

如果還有其他型別,則還需要過載Swap()函式多次。

因此,C++編譯器提供了函式模板特性,當需要對不同型別使用同一種演算法時,就可以使用汗俗話模板了。

5.2.格式

定義函式模板時,格式如下:

template <typename T>
void Swap(T t1, T t2);

關鍵字typename可以使用class代替。

在函式原型和函式定義處,都需要有關鍵字template 修飾。

5.3.工作原理

函式模板不會建立任何函式,函式模板實際上經過了二次編譯,分別在編譯cpp檔案時和函式呼叫時,在函式呼叫時,將會根據傳入的引數生成對應的函式。

5.4.模板的過載

和常規函式一樣,函式模板也可以進行過載,如下面示例中:

template <typename T,typename N>
void Swap(T & t1, N & n1);
template <typename T>
void Swap(T & t1, T & t2);