【C語言】 函數-006

第六章 函數

6.1 概述


6.1.1 函數分類

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

從函數定義的角度看,函數可分爲系統函數和用戶定義函數兩種:

  • 系統函數,即庫函數:這是由編譯系統提供的,用戶不必自己定義這些函數,可以直接使用它們,如我們常用的打印函數printf()
  • 用戶定義函數:用以解決用戶的專門需要。

6.1.2 函數的作用

  • 函數的使用可以省去重複代碼的編寫,降低代碼重複率
// 求兩數的最大值
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;
}

  • 函數可以讓程序更加模塊化,從而有利於程序的閱讀,修改和完善

假如我們編寫一個實現以下功能的程序:讀入一行數字;對數字進行排序;找到它們的平均值;打印出一個柱狀圖。如果我們把這些操作直接寫在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()函數 )勝任。但是,如果這個公司很大( 大型應用程序 ),任務多而雜,如果只是一個部門管理( 相當於沒有部門,沒有分工 ),我們可想而知,公司管理、運營起來會有多混亂,不是說這樣不可以運營,只是這樣不完美而已,如果根據公司要求分成一個個部門( 根據功能封裝一個一個函數 ),招聘由行政部門負責,研發由技術部門負責等,這樣就可以分工明確,結構清晰,方便管理,各部門之間還可以相互協調。

6.2 函數的定義


6.2.1 函數定義格式

函數定義的一般形式:

返回類型 函數名(形式參數列表)
{
	數據定義部分;
	執行語句部分;
}

在這裏插入圖片描述

6.2.2 函數名字、形參、函數體、返回值

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

2.形參列表
在定義函數時指定的形參,在未出現函數調用時,它們並不佔內存中的存儲單元,因此稱它們是形式參數或虛擬參數,簡稱形參,表示它們並不是實際存在的數據,所以,形參裏的變量不能賦值。

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

在定義函數時指定的形參,必須是,類型+變量的形式:
//1: right, 類型+變量
void max(int a, int b)
{
}

//2: error, 只有類型,沒有變量
void max(int, int)
{
}

//3: error, 只有變量,沒有類型
int a, int b;
void max(a, b)
{
}

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

// 沒形參, 圓括號內容爲空
void max()
{
}

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

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

4.返回值
函數的返回值是通過函數中的return語句獲得的,return後面的值也可以是一個表達式。

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

int max() // 函數的返回值爲int類型
{
	int a = 10;
	return a;// 返回值a爲int類型,函數返回類型也是int,匹配
}

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

double max() // 函數的返回值爲double類型
{
	int a = 10;
	return a;// 返回值a爲int類型,它會轉爲double類型再返回
}

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

c.return語句的另一個作用爲中斷return所在的執行函數,類似於break中斷循環、switch語句一樣。

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

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

void max()// 最好要有void關鍵字
{
	return; // 中斷函數,這個可有可無
}

6.3 函數的調用


定義函數後,我們需要調用此函數才能執行到這個函數裏的代碼段。這和main()函數不一樣,main()爲編譯器設定好自動調用的主函數,無需人爲調用,我們都是在main()函數裏調用別的函數,一個 C 程序裏有且只有一個main()函數。

6.3.1函數執行流程

#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, 程序執行完畢。

6.3.2 函數的形參和實參

  • 形參出現在函數定義中,在整個函數體內都可以使用,離開該函數則不能使用。
  • 實參出現在主調函數中,進入被調函數後,實參也不能使用。
  • 實參變量對形參變量的數據傳遞是“值傳遞”,即單向傳遞,只由實參傳給形參,而不能由形參傳回來給實參。
  • 在調用函數時,編譯系統臨時給形參分配存儲單元。調用結束後,形參單元被釋放。
  • 實參單元與形參單元是不同的單元。調用結束後,形參單元被釋放,函數調用結束返回主調函數後則不能再使用該形參變量。實參單元仍保留並維持原值。因此,在執行一個被調用函數時,形參的值如果發生改變,並不會改變主調函數中實參的值。

6.3.3 無參函數調用

如果是調用無參函數,則不能加上“實參”,但括號不能省略。

// 函數的定義
void test()
{
}

int main()
{
	// 函數的調用
	test();	// right, 圓括號()不能省略
	test(250); // error, 函數定義時沒有參數

return 0;
}

6.3.4有參函數調用

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

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

int main()
{
	int p = 10, q = 20;
	test(p, q);	// 函數的調用

	return 0;
}

b.實參與形參的個數應相等,類型應匹配(相同或賦值兼容)。實參與形參按順序對應,一對一地傳遞數據。
在這裏插入圖片描述
c.實參可以是常量、變量或表達式,無論實參是何種類型的量,在進行函數調用時,它們都必須具有確定的值,以便把這些值傳送給形參。所以,這裏的變量是在圓括號( )外面定義好、賦好值的變量。

// 函數的定義
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;
}

6.3.5 函數返回值

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(); // 雖然調用成功沒有意義, p爲char *, 函數返回值爲int, 類型不匹配

	// error, 必須定義一個匹配類型的變量來接收返回值
	// int只是類型,沒有定義變量
	int = test();	
	
	// error, 必須定義一個匹配類型的變量來接收返回值
	// int只是類型,沒有定義變量
	int test();
	
	return 0;
}

6.4 函數的聲明


如果使用用戶自己定義的函數,而該函數與調用它的函數(即主調函數)不在同一文件中,或者函數定義的位置在主調函數之後,則必須在調用此函數之前對被調用的函數作聲明。

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

注意:一個函數只能被定義一次,但可以聲明多次。

#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. 聲明的作用則是把函數的名字、函數類型以及形參的個數、類型和順序(注意,不包括函數體)通知編譯系統,以便在對包含函數調用的語句進行編譯時,據此對其進行對照檢查(例如函數名是否正確,實參與形參的類型和個數是否一致)。

6.5 main函數與exit函數


main函數中調用exitreturn結果是一樣的,但在子函數中調用return只是代表子函數終止了,在子函數中調用exit,那麼程序終止。

#include <stdio.h>
#include <stdlib.h>

void fun()
{
	printf("fun\n");
	//return;
	exit(0);
}

int main()
{
	fun();
	while (1);

	return 0;
}

6.6 多文件(分文件)編程


6.6.1 分文件編程

  • 把函數聲明放在頭文件xxx.h中,在主函數中包含相應頭文件
  • 在頭文件對應的xxx.c中實現xxx.h聲明的函數
    在這裏插入圖片描述

6.6.2 防止頭文件重複包含

當一個項目比較大時,往往都是分文件,這時候有可能不小心把同一個頭文件 include 多次,或者頭文件嵌套包含。

a.h 中包含 b.h :

#include "b.h"

b.h 中包含 a.h:

#include "a.h"

main.c 中使用其中頭文件:

#include "a.h"

int main()
{
	return 0;
}

編譯上面的例子,會出現如下錯誤:
在這裏插入圖片描述

爲了避免同一個文件被include多次,C/C++中有兩種方式,一種是 #ifndef 方式,一種是 #pragma once 方式。

方法一:

#ifndef __SOMEFILE_H__
#define __SOMEFILE_H__

// 聲明語句

#endif

方法二:

#pragma once

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