前言
大家好,這里是 Hello_Embed。在前一篇筆記中,我們用循環實現了 LED 閃爍,其中重復使用了兩段幾乎一樣的延時代碼:
for(i = 0; i < 100000000; i++); // 延時
這種重復不僅讓代碼冗余,還不利于后續修改(比如想調整延時時間,需要改兩處)。有沒有辦法把這段延時 “打包” 成一個可復用的模塊?這就需要用到 C 語言中的函數。本文將從函數的定義、聲明講起,結合實例說明參數傳遞的兩種方式(值傳遞與地址傳遞),以及函數在嵌入式開發中的實用價值。
📦 函數的基本概念:封裝重復代碼
函數的核心作用是 “封裝重復邏輯”,讓代碼更簡潔、易維護。以延時功能為例,我們可以把重復的 for 循環封裝成一個函數,需要時直接調用。
函數的定義與聲明
函數的定義格式為:
返回類型 函數名(參數列表) {// 函數體:實現具體功能
}
- 返回類型:表示函數執行后返回的數據類型(若無需返回值,用
void
); - 參數列表:函數接收的輸入(若無需參數,用
void
); - 函數體:實現功能的代碼塊。
無參數、無返回值的函數
將延時功能封裝成無參數函數
void delay(void) // 第一個void:無返回值;第二個void:無參數
{int i;for(i = 0; i < 100000000; i++); // 延時邏輯
}
調用時直接寫delay();
即可,替代原來的 for 循環。這樣修改延時時間只需改函數內部,無需到處找重復代碼。如果此函數涉及公司機密,還可以將函數封裝為庫,別人使用直接調用庫就可以了。
帶參數的函數
如果想靈活控制延時時長,可以給函數添加參數:
void delay(int cnt) // 參數cnt:控制循環次數
{int i;for(i = 0; i < cnt; i++); // 用參數cnt替代固定值
}
調用時通過delay(100000000);
指定延時長度,甚至可以根據需求動態調整(比如delay(50000000);
實現更短的延時)。
帶返回值的函數
有的時候我們可能會遇到有著兩個不同參數的函數,如下:
int add(int a, int b)
{int sum = a + b;return sum;
}
此時函數就有返回值sum
,所以add
的返回類型是int
。這段代碼還可以將sum
省去,直接返回a + b
。下面我們來實戰演練一遍:
#include <stdio.h>
int add(int a, int b) { return a + b; }int main()
{int a1 = 10, b1 = 20;// 方式1:將返回值賦給變量,再使用int c1 = add(a1, b1);printf("The sum of %d and %d is %d\n", a1, b1, c1);// 方式2:直接在表達式中使用返回值printf("The sum of %d and %d is %d\n", a1, b1, add(a1, b1));return 0;
}
運行結果如圖:
可以看到我們有兩種方法輸出兩數之和:一種是將函數值賦給定義好的c1
,再打印c1
;另一種是直接打印函數返回的兩數之和的值,這種使用方式也是合規的。
🔄 參數傳遞的兩種方式
函數參數的傳遞方式決定了函數內部能否修改外部變量,這在嵌入式開發中尤為重要(比如通過函數修改硬件寄存器的值)。
值傳遞:僅傳遞變量的 “副本”
先看一個例子:
#include <stdio.h>
// 嘗試修改參數a的值
void change_val(int a)
{printf("The value of a 1 is %d\n", a); // 打印傳入的值a = 200; // 修改參數aprintf("The value of a 2 is %d\n", a); // 打印修改后的值
}int main()
{int a = 100;change_val(a); // 傳入a的值printf("The value of a 3 is %d\n", a); // 打印main中的areturn 0;
}
運行結果如圖,函數內部的a
被改成了 200,但 main 中的a
仍為 100:
我們用反證法來理解這段代碼的結果:假設main
中的a
和change_val
中的a
用同一塊內存,順序執行時,先是a = 100
,接著調用函數打印一次,此時打印出a
為 100;接著a = 200
再打印,a
應為 200;函數執行完畢后在主程序里再次打印,理論上a
應為 200,但現實結果卻是 100。這說明:兩個變量a
所占內存不同。
為什么會這樣?這涉及到 “全局變量”“局部變量” 和 “棧” 的概念:
- 在函數之外定義的變量稱為全局變量,在函數之內定義的變量稱為局部變量。
- 程序運行時,內存中會劃出一塊區域稱為 “棧”,還有一個硬件寄存器叫
SP
(棧指針)。當運行到main
函數時,會為其分配空間,SP
會從原來的位置往下移動,SP
原來位置與現在位置之間的區域就是main
的棧,里面存放main
的局部變量。 - 同樣,
change_val
函數被調用時,也會有自己的棧來存放它的局部變量。
所以,代碼中同名的a
實際上是不同的變量:main
中的a
在main
的棧里,change_val
中的a
在它自己的棧里。這段代碼所側重的恰恰是參數之間的傳遞:我們將main
中a
的值 100 傳入change_val
中,所以第一次打印 100;函數內修改的是自己棧里的a
,所以第二次打印 200;最后打印的是main
函數中定義的a
,所以還是 100。
地址傳遞:通過指針修改原變量
那如果我就是想通過change_val
中的a
改變main
中的a
的值呢?當然有辦法解決,這個時候就需要請出指針了:
#include <stdio.h>
// 用指針接收地址,修改原變量
void change_val(int *a) // 參數為int型指針
{printf("The value of a 1 is %d\n", *a); // 訪問指針指向的變量*a = 200; // 修改指針指向的變量(即main中的a)printf("The value of a 2 is %d\n", *a);
}int main()
{int a = 100;change_val(&a); // 傳入a的地址printf("The value of a 3 is %d\n", a); // main中的a被修改return 0;
}
運行結果如圖,main 中的a
成功被改為 200:
原理:指針a
存儲了 main 中a
的地址,通過*a
可以直接操作原變量的內存,因此修改會生效。這種方式在嵌入式中常用(比如通過函數修改寄存器的值,關于指針的更多知識可參考之前的筆記)。當change_val
函數執行完畢,SP
會重新指回main
函數,代表一個函數的結束。
結尾
通過這篇筆記,我們系統學習了函數的基本概念:從無參數、無返回值的簡單封裝(如延時函數),到帶參數、帶返回值的靈活應用(如加法函數),再到參數傳遞的兩種方式 —— 值傳遞(僅傳副本,不影響原變量)和地址傳遞(用指針修改原變量)。我們還理解了局部變量在棧中的存儲特點,這是搞懂參數傳遞機制的關鍵。
函數是 C 語言模塊化編程的核心,在嵌入式開發中,我們可以將硬件初始化、數據處理等邏輯封裝成函數,讓代碼更清晰、易復用。下一篇筆記,我們會進一步學習函數的遞歸。Hello_Embed 會繼續陪伴大家,一步步掌握嵌入式 C 語言的精髓,敬請期待~