1. 程式人生 > >C語言入門學習——函式的使用

C語言入門學習——函式的使用

函式的概述

C 程式是由函式組成的,我們寫的程式碼都是由主函式 main()開始執行的。函式是 C 程式的基本模組,是用於完成特定任務的程式程式碼單元。

從函式定義的角度看,函式可分為系統函式使用者定義函式兩種:
(1)系統函式,即庫函式:這是由編譯系統提供的,使用者不必自己定義這些函式,可以直接使用它們,如我們常用的列印函式printf()。

(2)使用者定義函式:用以解決使用者的專門需要。

為什麼要使用函式呢?

1)函式的使用可以省去重複程式碼的編寫。

如果程式中需要多次使用某種特定的功能( 求兩個數的最大值 ),直接寫在main()裡,程式碼的重複率很比較高,同時,程式碼給人的感覺會很冗餘,如下:

#include <stdio.h>

int main()
{
	// 操作1 ……
	// ……
	int a1 = 10, b1 = 20, c1 = 0;
	if(a1 > b1){
		c1 = a1;
	}else{
		c1 = b1;
	}
	
	// 操作2 ……
	// ……
	int a2 = 11, b2 = 21, c2 = 0;
	if(a2 > b2){
		c2 = a2;
	}else{
		c2 = b2;
	}
	
	// ……
	
	return 0;
}

使用函式後,降低程式碼的重複率,如下:

#include <stdio.h>

// 求兩數的最大值
int max(int a, int b)
{
	if(a > b){
		return a;
	}else{
		return b;
	}
}

int main()
{
	// 操作1 ……
	// ……
	int a1 = 10, b1 = 20, c1 = 0;
	c1 = max(a1, b1); // 呼叫max()
	
	// 操作2 ……
	// ……
	int a2 = 11, b2 = 21, c2 = 0;
	c2 = max(a2, b2); // 呼叫max()
	
	// ……
	
	return 0;
}

2)函式可以讓程式更加模組化,從而有利於程式的閱讀,修改和完善。

假如我們編寫一個實現以下功能的程式:讀入一行數字;對數字進行排序;找到它們的平均值;打印出一個柱狀圖。

如果我們把這些操作直接寫在main()裡,這樣可能會給使用者感覺程式碼會有點凌亂。但,假如我們使用函式,這樣可以讓程式更加清晰、模組化。

#include <stdio.h>

int main()
{
	float list[50];
	
	// 這裡只是舉例,函式還沒有實現
	readlist(list,50);
	sort(list,50);
	average(list,50);
	bargraph(list,50);

	return 0;
}

這裡我們可以這麼理解,程式就像公司,公司是由部門組成的,這個部門就類似於C程式的函式。預設情況下,公司就是一個大部門( 只有一個部門的情況下 ),相當於C程式的main()函式。如果公司比較小( 程式比較小 ),因為任務少而簡單,一個部門即可( main()函式 )勝任。但是,如果這個公司很大( 大型應用程式 ),任務多而雜,如果只是一個部門管理( 相當於沒有部門,沒有分工 ),我們可想而知,公司管理、運營起來會有多混亂,不是說這樣不可以運營,只是這樣不完美而已,如果根據公司要求分成一個個部門( 根據功能封裝一個一個函式 ),招聘由行政部門負責,研發由技術部門負責等,這樣就可以分工明確,結構清晰,方便管理,各部門之間還可以相互協調。

接下來我們一起學習函式:函式的定義,函式的呼叫,函式的宣告

函式的定義

函式定義的一般形式:

返回型別 函式名形式引數列表 )

{

資料定義部分;

執行語句部分;

}

例子如下圖:


這裡還有幾點說明:

1)函式名:理論上是可以隨意起名字,最好起的名字見名知意,應該讓使用者看到這個函式名字就知道這個函式的功能。注意,函式名的後面有個圓換號(),代表這個為函式,不是普通的變數名。

2)形參列表:在定義函式時指定的形參,在未出現函式呼叫時,它們並不佔記憶體中的儲存單元,因此稱它們是形式引數或虛擬引數,簡稱形參,表示它們並不是實際存在的資料,所以,形參裡的變數不能賦值

int max(int a = 10, int b = 20) // error, 形參不能賦值
{

}

在定義函式時指定的形參,必須是,型別+變數的形式

///1: right, 型別+變數
int max(int a, int b)
{

}

///2: error, 只有型別,沒有變數
int max(int, int)
{

}

///3: error, 只有變數,沒有型別
int a, int b;
int max(a, b)
{

}

在定義函式時指定的形參,可有可無,根據函式的需要來設計,如果沒有形參,圓括號內容為空,或寫一個void關鍵字:

// 沒形參, 圓括號內容為空
int max()
{

}

// 沒形參, 圓括號內容為void關鍵字
int max(void) 
{

}

3)函式體:花括號{ }裡的內容即為函式體的內容,這裡為函式功能實現的過程,這和以前的寫程式碼沒太大區別,以前我們把程式碼寫在main()函式裡,現在只是把這些寫到別的函式裡。

4)返回型別:函式的值是指函式被呼叫之後,執行函式體中的程式段所取得的並返回給主調函式的值。函式的返回值是通過函式中的return語句獲得的,return後面的值也可以是一個表示式。

a)儘量保證return語句中表達式的值和函式返回型別是同一型別。

int max() // 函式的返回值為int型別
{
	int a = 10;
	return a;// 返回值a為int型別,函式返回型別也是int,匹配
}

b)如果函式返回的型別和return語句中表達式的值不一致,則以函式返回型別為準,即函式返回型別決定返回值的型別。對數值型資料,可以自動進行型別轉換

int max() // 函式的返回值為int型別
{
	double a = 10.1f;
	return a;// 返回值a為double型別,它會轉為int型別(a = 10)再返回
}

如果函式返回的型別和return語句中表達式的值不一致,而它又無法自動進行型別轉換,程式則會報錯。

c)return語句的另一個作用為中斷return所在的執行函式,類似於break中斷迴圈,switch語句一樣

int max()
{
	return 1;// 執行到,函式已經被中斷,所以下面的return 2無法被執行到
	return 2;// 沒有執行
}

d)如果函式帶返回值,return後面必須跟著一個值,如果函式沒有返回值,函式名字的前面必須寫一個void關鍵字,這時候,我們寫程式碼時也可以通過return中斷函式(也可以不用),只是這時,return後面不帶內容( 分號“;”除外)。

void max()// 必須要有void關鍵字
{
	return; // 中斷函式,這個可有可無
}

函式的呼叫

定義函式後,我們需要呼叫此函式才能執行到這個函式裡的程式碼段。這和main()函式不一樣,main()為編譯器設定好自動呼叫的主函式,無需人為呼叫,我們都是在main()函式裡呼叫別的函式,一個 C 程式裡有且只有一個main()函式。

我們有這麼一個簡單程式:

#include <stdio.h>

int main()
{
	printf("this is for test\n");
	
	return 0;
}

接著,我們將這個列印的操作封裝在自定義函式裡,這個函式即沒返回值,也沒形參:

#include <stdio.h>

void print_test()
{
	printf("this is for test\n");
}

int main()
{
	print_test();	// print_test函式的呼叫
	
	return 0;
}

這個程式執行的效果,和上面的是等價的。

程式的執行流程:

1)進入main()函式

2)呼叫print_test()函式:

a) 它會在main()函式的前尋找有沒有一個名字叫“print_test”的函式定義;

b) 如果找到,接著檢查函式的引數,這裡呼叫函式時沒有傳參,函式定義也沒有形參,引數型別匹配;

c) 開始執行print_test()函式,這時候,main()函式裡面的執行會阻塞( 停 )在print_test()這一行程式碼,等待print_test()函式的執行。

3)print_test()函式執行完( 這裡列印一句話 ),main()才會繼續往下執行,執行到return 0, 程式執行完畢。

C語言中,函式呼叫的一般形式為:

函式名(實際引數表)

1)實際引數表,常稱“實參”, 這裡要區別好形參和實參,函式定義時圓括號裡的是形參,函式呼叫時圓括號裡的是實參

a)如果是呼叫無參函式,則不能加上“實參”,但括號不能省略。

// 函式的定義
void test()
{

}

int main()
{
	// 函式的呼叫
	test();	// right, 圓括號()不能省略
	test(250); // error, 函式定義時沒有引數
	
	return 0;
}

b)如果實參表列包含多個實參,則各引數間用逗號隔開。

// 函式的定義
void test(int a, int b)
{

}

int main()
{
	int p = 10, q = 20;
	test(p, q);	// 函式的呼叫
	
	return 0;
}


c)實參與形參的個數應相等,型別應匹配(相同或賦值相容)。實參與形參按順序對應,一對一地傳遞資料。d)實參可以是常量、變數或表示式,無論實參是何種型別的量,在進行函式呼叫時,它們都必須具有確定的值,以便把這些值傳送給形參。所以,這裡的變數是在圓括號( )外面定義好、賦好值的變數
// 函式的定義
void test(int a, int b)
{

}

int main()
{
	// 函式的呼叫
	int p = 10, q = 20;
	test(p, q);	// right
	test(11, 30-10); // right
	
	test(int a, int b); // error, 不應該在圓括號裡定義變數
	
	return 0;
}

2)函式的返回值

a)如果函式定義沒有返回值,函式呼叫時不能寫void關鍵字,呼叫函式時也不能接收函式的返回值。

// 函式的定義
void test()
{

}

int main()
{
	// 函式的呼叫
	test(); // right
	void test(); // error, 函式呼叫時不能寫void關鍵字
	int a = test();	// error, 函式定義根本就沒有返回值
	
	return 0;
}


b)如果函式定義有返回值,這個返回值我們根據使用者需要可用可不用,但是,假如我們需要使用這個函式返回值,我們需要定義一個匹配型別的變數來接收。

// 函式的定義, 返回值為int型別
int test()
{

}

int main()
{
	// 函式的呼叫
	int a = test(); // right, a為int型別
	int b;
	b = test();	// right, 和上面等級
	
	char *p = test(); // error, p為char *, 函式返回值為int, 型別不匹配
	
	int = test();	// error, 必須定義一個匹配型別的變數來接收返回值,
			// int只是型別,沒有定義變數
	int test();// error, 必須定義一個匹配型別的變數來接收返回值,
			// int只是型別,沒有定義變數
	
	return 0;
}

下面,我們寫一個完整例子:

#include <stdio.h>
/****************************
功能:求兩個數的最大值
引數:
	x, y: 需要比較的兩個整數
返回值:兩個數的最大值
******************************/
int max(int x, int y)
{
	return x>y ? x : y;
}

int main()
{
	int a = 10, b = 25, num_max = 0;
	num_max = max(a, b); // 函式的呼叫
	
	printf("num_max = %d\n", num_max);
	
	return 0;
}

當呼叫max(a, b)時,實參變數(a, b)對形參變數(x, y)的進行資料傳遞( “值傳遞” )。只有在發生函式呼叫時,函式max中的形參這時候才會被分配記憶體單元,以便接收從實參傳來的資料。在呼叫結束後,形參所佔的記憶體單元也被釋放。同時,把函式的返回值賦給main()裡的num_max變數。

關於形參和實參的幾點說明:

1)形參出現在函式定義中,在整個函式體內都可以使用,離開該函式則不能使用。

2)實參出現在主調函式中,進入被調函式後,實參變數也不能使用。

3)實參變數對形參變數的資料傳遞是“值傳遞”,即單向傳遞,只由實參傳給形參,而不能由形參傳回來給實參

4)在呼叫函式時,編譯系統臨時給形參分配儲存單元。呼叫結束後,形參單元被釋放。

5)實參單元與形參單元是不同的單元。呼叫結束後,形參單元被釋放,函式呼叫結束返回主調函式後則不能再使用該形參變數。實參單元仍保留並維持原值。因此,在執行一個被呼叫函式時,形參的值如果發生改變,並不會改變主調函式中實參的值。

函式的宣告

在一個函式中呼叫另一個函式(即被呼叫函式)需要具備哪些條件呢?

1)首先被呼叫的函式必須是已經存在的函式。

#include <stdio.h>

int main()
{
	int a = 10, b = 25, num_max = 0;
	num_max = max(a, b); // 函式的呼叫,這個函式不存在
	
	printf("num_max = %d\n", num_max);
	
	return 0;
}

編譯程式會報錯:



2)如果使用庫函式,包含所需要標頭檔案即可用。

#include <stdio.h>
#include <string.h> // strlen()所需標頭檔案

int main()
{
	char *p = "hello, I am mike!"
	int len = strlen(p); // 測字串的長度
	printf("len = %d\n", len);
	
	return 0;
}

3)如果使用使用者自己定義的函式,而該函式與呼叫它的函式(即主調函式)在檔案中,且函式定義的位置在主調函式之後,則必須在呼叫此函式之前對被呼叫的函式作宣告。

所謂函式宣告,就是在函式尚在未定義的情況下,事先將該函式的有關資訊通知編譯系統,相當於告訴編譯器,函式在後面定義,以便使編譯能正常進行。

函式宣告其一般形式為( 後面的分號不能省略 ):
型別說明符 被調函式名(型別 形參,型別 形參 …);
或為:
型別說明符 被調函式名(型別,型別 …);

括號內給出了形參的型別和形參名,或只給出形參型別。這便於編譯系統進行檢錯,以
防止可能出現的錯誤。

如:
int max(int a,int b);
或寫為:
int max(int,int);

完整例子如下:

#include <stdio.h>

int max(int x, int y); // 函式的宣告,分號不能省略
// int max(int, int); // 另一種方式

int main()
{
	int a = 10, b = 25, num_max = 0;
	num_max = max(a, b); // 函式的呼叫
	
	printf("num_max = %d\n", num_max);
	
	return 0;
}

// 函式的定義
int max(int x, int y)
{
	return x>y ? x : y;
}

函式定義和定義的區別:

1)定義是指對函式功能的確立,包括指定函式名、函式型別、形參及其型別、函式體等,它是一個完整的、獨立的函式單位。

2)宣告的作用則是把函式的名字、函式型別以及形參的個數、型別和順序(注意,不包括函式體)通知編譯系統,以便在對包含函式呼叫的語句進行編譯時,據此對其進行對照檢查(例如函式名是否正確,實參與形參的型別和個數是否一致)。