老湯回味——C語言函數

函數是C語言的重要組成部分,通過函數我們可以將複雜邏輯進行封轉,縮減程序員在編碼和維護中的關注點數量,提高代碼質量,方便對代碼進行維護。


函數聲明用來說明我們的代碼中包含一個什麼樣的函數,函數聲明可以放在頭文件(.h)或者源文件(.c)中,函數聲明的格式如下:

返回值類型 函數名(參數表);


函數定義是函數的實現,函數定義的格式如下:

返回值類型 函數名(參數表) {

        C語言語句

}


函數聲明不是必須的,如果省略了函數聲明,我們必須將函數定義按照調用順序進行組織,比如下面的僞代碼:

void foo1() {

        ........

        // foo3();   //編譯錯誤,找不到函數

        // foo2();   //編譯錯誤,找不到函數

}


void foo3() {

        foo1();     //正確,foo1()在foo3()前面定義

        ........

        // foo2();   //編譯錯誤,找不到函數

}


void foo2() {

        foo3();  //正確,foo3在foo2前面定義

        .......

}


所以,一般把開放給外部的函數聲明放在.h文件中,然後在使用這些函數的.c文件中通過#include導入,


演示一個簡單的函數,function.h中進行聲明

#ifndef FUNCTION_H
#define FUNCTION_H

int add(int a, int b);

#endif

function.c中定義

#include "function.h"

int add(int a, int b) {
	return a + b;
}

該函數實現兩個整型數的相加,通過兩個參數a和b,將計算結果a+b通過return語句返回。main.c中使用該函數

#include <stdio.h>
#include "function.h"

int main(void) {
	int a = 10;
	int b = 20;
	printf("a + b = %d\n", add(a, b));
}

函數的輸出爲

a + b = 30


下面我們着重討論函數的參數,在上面函數定義中,參數列表中的a和b被稱爲形參,他們是函數作用域內的局部變量,如果函數外部還有變量a和b(如main中的變量a和b),形參將會隱藏外部的同名變量。


函數的參數按值傳遞,什麼叫按值傳遞呢?如果參數是基本類型的變量,如int, float等,則將變量值進行傳遞,我們對形參的改變不會改變外部變量的值,如果傳入的參數是指針,則傳入的參數是指針變量的值,也就是一個內存地址,這樣,我們基於形參的指針變量,是可以改變內存地址指向的內存中的數值的,看下面改版的add函數,在function.h中加入一個新的函數聲明

int add(int a, int b);
void add_use_pointer(int a, int b, int *result);


add_use_pointer函數不返回值,而是通過一個指針變量result返回值。function.c中的實現如下

void add_use_pointer(int a, int b, int *result) {
	*result = a + b;
}

不再調用return語句,而是將a+b的值賦值給了一個指針變量所指向的內存。main.c中的調用

int result = 0;
add_use_pointer(a, b, &result);
printf("a + b = %d\n", result);

輸出結果爲

a + b = 30


可以看到,main中定義的result,初始化爲0,但是執行函數add_use_pointer之後,值通過指針被設置爲了30。使用指針返回值,可以讓我們的函數輕鬆返回多個值,不過,使用指針返回多個值,不應該返回過多的值,否則會加長參數列表,這是非常不好的代碼形式。如果採用return,希望返回多個值,則需要將返回值綁爲一個結構體(後面的文章會介紹),而使用結構體,是C語言實現面向對象的一種手段。


介紹完函數,我們補充看一個問題,C語言中{}之間的部分被稱之爲代碼塊,在代碼塊中定義的變量,其作用域就在該代碼塊中,也就是說,在代碼塊之外無法訪問該變量,另外,如果沒有使用動態內存分配,局部變量在離開代碼塊時,佔用的內存會被釋放,生存期僅限於代碼塊。如果一個變量不屬於任何函數,那麼該變量就是一個全局變量,如果在任何代碼塊中都沒有同名變量,則我們可以在代碼的所有位置訪問到該變量,生存期爲整個程序的運行時間。看下面的例子,我們在function.c中定義了一個全局變量

int global_int = 100;

在function.h中,我們使用extern關鍵字導出該全局變量

extern int global_int;

main.c中我們包含了頭文件function.h,所以也就導出了全局變量,我們可以直接使用

printf("global_int = %d\n", global_int);

main.c中我們包含了頭文件function.h,所以也就導出了全局變量,我們可以直接使用


輸出爲

global_int = 100


C語言還提供了一個static關鍵字,用來定義靜態變量或靜態函數,對於靜態變量,可以分爲靜態全局變量和靜態局部變量。給一個簡單的靜態全局變量的例子

static int global_int_static = 100;


我們提到了作用域的問題,靜態全局變量和靜態函數作用域爲文件作用域,也就是說,變量或函數僅在本文件中有效。這裏就爲我們提供了一種封裝機制,如果我們想隱藏某個函數或全局變量不被外部訪問,我們可以在.c文件中將其定義靜態全局變量或靜態函數。但是,如果在.h文件中定義了靜態全局變量和靜態函數,則由於#include會將頭文件複製到引用點,實際上就將靜態全局變量和靜態函數所在的文件改變了,造成了可以在其他文件訪問的假象。


對於靜態局部變量,變量僅在第一次被使用時會被初始化,之後每次進入其作用域,都會保留之前在其作用域中的運行值,也就是說生存期被延長爲整個程序運行階段,但是作用域依然是局部的。


static是我們使用C語言做出面向對象程序的工具之一,static相關的內容比較抽象,希望大家多去了解一些。


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