函數是 C 語言模組化程式設計的基石,而遞歸則是解決複雜問題的利器。本文將透過七個經典案例,系統講解函數定義、聲明、參數傳遞、程式終止控制,以及遞歸與遞推的實戰應用。
函數定義包含返回類型、函數名、參數列表與函數體。透過 return 將運算結果傳回呼叫端。
#include <stdio.h>
int getInt() {
return 10;
}
double getDouble() {
return 5.5;
}
int main() {
int n = getInt();
printf("%d\n", n);
double d = getDouble();
printf("%lf\n", d);
return 0;
}
解析:getInt 回傳整數 10,getDouble 回傳浮點數 5.5。主函數透過變數接收返回值並列印。
int,參數不檢查(C99 已廢棄)void 明確表示無參數#include <stdio.h>
int main() {
f1(); // 隱式聲明
double f2(/*void*//*int i*/); // 顯式聲明,參數註解示意
f2(1, 2);
return 0;
}
int f1() {
printf("f1()\n");
return 0;
}
double f2(int a, int b) {
printf("f2()\n");
return 3.0;
}
注意:f2 宣告時參數可寫 void(不接受參數)或留空(接受任意參數),定義時需一致。
#include <stdio.h>
void fun1(int a) { // 值傳遞
printf("fun1 入參 a=%d\n", a);
a += 10;
printf("fun1 修改後 a=%d\n", a);
}
void fun2(int array[]) { // 陣列傳遞(實為指標)
printf("fun2 入參 array[0]=%d\n", array[0]);
array[0] += 10;
printf("fun2 修改後 array[0]=%d\n", array[0]);
}
int main() {
int a = 5;
int arr[10] = {1};
printf("main 呼叫前 a=%d\n", a);
fun1(a);
printf("main 呼叫後 a=%d\n\n", a); // 未變
printf("main 呼叫前 arr[0]=%d\n", arr[0]);
fun2(arr);
printf("main 呼叫後 arr[0]=%d\n", arr[0]); // 已變
return 0;
}
return vs exit()return:僅結束當前函數,控制權回呼叫端exit():立即終止整個程式,執行清理再退出#include <stdio.h>
#include <stdlib.h>
void fun(int e) {
if (1 == e)
exit(EXIT_SUCCESS); // 直接結束程式
else
return; // 僅返回主函數
}
int main() {
int n;
printf("輸入 1 呼叫 exit(),輸入 2 執行 return:");
scanf("%d", &n);
fun(n);
printf("只有選擇 2 才會看到這行\n");
return 0;
}
int factorial(int n) {
if (1 == n) return 1; // 遞歸出口
return n * factorial(n - 1); // 遞歸步進
}
int f = 1;
for (int i = 2; i <= n; i++)
f *= i;
#include <stdio.h>
int factorial(int n) {
if (1 == n) return 1;
return n * factorial(n - 1);
}
int main() {
int n;
printf("請輸入求階乘的數:");
scanf("%d", &n);
// 遞推
int f = 1;
for (int i = 2; i <= n; i++) f *= i;
printf("遞推法:%d! = %d\n", n, f);
// 遞歸
f = factorial(n);
printf("遞歸法:%d! = %d\n", n, f);
return 0;
}
// 遞推 O(n)
long fib(int n) {
long f1 = 1, f2 = 1, fn = 1;
for (int i = 2; i < n; i++) {
fn = f1 + f2;
f1 = f2;
f2 = fn;
}
return fn;
}
// 遞歸 O(2^n) - 指數級爆炸
long fib_f(int n) {
if (n == 1 || n == 2) return 1;
return fib_f(n - 1) + fib_f(n - 2);
}
#include <time.h>
int main() {
int n;
printf("請輸入月份:");
scanf("%d", &n);
time_t begin = time(0);
long sum = fib(n);
time_t stop = time(0);
printf("遞推:第 %d 月兔子數 %ld,耗時 %lf 秒\n", n, sum, difftime(stop, begin));
begin = time(0);
sum = fib_f(n);
stop = time(0);
printf("遞歸:第 %d 月兔子數 %ld,耗時 %lf 秒\n", n, sum, difftime(stop, begin));
return 0;
}
結論:遞歸程式碼簡潔但效能差,遞推適合大規模計算。實務上可用備忘錄或動態規劃優化遞歸。
將 n 個盤子從 A 柱借助 B 柱移至 C 柱,規則:一次移一個、小盤在大盤上。
#include <stdio.h>
void move(int n, char a, char b, char c) {
if (n == 1) {
printf("\t%c->%c\n", a, c);
} else {
move(n - 1, a, c, b); // n-1: A->B
printf("\t%c->%c\n", a, c); // 第 n 個: A->C
move(n - 1, b, a, c); // n-1: B->C
}
}
int main() {
int n;
printf("請輸入盤子數:");
scanf("%d", &n);
move(n, 'A', 'B', 'C');
return 0;
}
#include <stdio.h>
int main() {
int a, b;
scanf("%*d%d", &a, &b); // %*d 跳過第一個整數
printf("a=%d, b=%d\n", a, b);
printf("%10d,%-10d,%d\n", 100, 200, 300); // 寬度、靠左/右
printf("%08d\n", 100); // 前導補零
printf("%0*d\n", 8, 100); // 動態寬度補零
printf("0%o,0x%x\n", 8, 16); // 八/十六進制
printf("%.2f\n", 12.34567); // 保留兩位小數
printf("%10.2f\n", 12.34567); // 寬度 10 含小數點
return 0;
}
#include <stdlib.h>
int *pi = malloc(sizeof(int));
*pi = 10;
printf("%d\n", *pi);
free(pi); // 必須釋放
// 動態陣列
int *arr = malloc(sizeof(int) * 10);
for (int i = 0; i < 10; i++) arr[i] = i + 1;
free(arr);
// 結構體
struct Data { int a; double b; char c; };
struct Data *p = malloc(sizeof(struct Data));
p->a = 10; p->b = 12.34; p->c = 'a';
free(p);
#include <time.h>
int main() {
time_t t = time(0);
struct tm *local = localtime(&t);
printf("%d年%d月%d日 %d:%d:%d\n",
1900 + local->tm_year,
local->tm_mon + 1,
local->tm_mday,
local->tm_hour, local->tm_min, local->tm_sec);
return 0;
}
| 模式 | 說明 |
|---|---|
r | 唯讀,檔案必須存在 |
w | 唯寫,存在則清空,不存在則建立 |
a | 追加寫入,不存在則建立 |
w+ | 讀寫,同 w |
fprintf/fscanfFILE *fp = fopen("data.txt", "w");
fprintf(fp, "%c %d %f %lf", 'a', 10, 12.34f, 12345.6789);
fclose(fp);
fp = fopen("data.txt", "r");
char c; int i; float f; double d;
fscanf(fp, "%c%d%f%lf", &c, &i, &f, &d);
printf("%c\n%d\n%f\n%lf\n", c, i, f, d);
fclose(fp);
fwrite/fread// 寫入
fwrite(&a, sizeof(int), 1, fp);
fwrite(&d, sizeof(double), 1, fp);
int len = strlen(str) + 1;
fwrite(&len, sizeof(int), 1, fp); // 先寫長度
fwrite(str, sizeof(char), len, fp); // 再寫內容
// 讀取
fread(&a1, sizeof(int), 1, fp);
fread(&d1, sizeof(double), 1, fp);
fread(&len1, sizeof(int), 1, fp);
fread(str1, sizeof(char), len1, fp);
struct Student { int id; char name[20]; char addr[200]; };
struct Student stu = {10000, "zhangsan", "江蘇南京"};
fwrite(&stu, sizeof(struct Student), 1, fp); // 一次寫入整個結構
fseek/ftell
// SEEK_SET: 檔案開頭, SEEK_CUR: 目前位置, SEEK_END: 檔案結尾
fseek(fp, 0, SEEK_SET); // 移至開頭
fseek(fp, 5, SEEK_CUR); // 向後 5 位元組
fseek(fp, -10, SEEK_END); // 從結尾向前 10 位元組
// 取得檔案大小
fseek(fp, 0, SEEK_END);
long len = ftell(fp);
printf("檔案大小:%ld bytes\n", len);
FILE *src = fopen("file.c", "r");
FILE *dst = fopen("file副本.c", "w");
char ch;
while ((ch = getc(src)) != EOF)
putc(ch, dst);
fclose(src); fclose(dst);
sprintf/sscanfchar buf[] = "abcdefghijklmnopqrstuvwxyz";
fprintf(fp, "%lu%s", strlen(buf), buf); // 寫入長度+內容
// 讀取
int len; fscanf(fp, "%d", &len);
char *buf1 = malloc(len + 1);
fscanf(fp, "%s", buf1);
buf1[len] = '\0';
#include <stdarg.h>
int max(int cnt, ...) {
va_list ap;
va_start(ap, cnt);
int maxv = va_arg(ap, int);
for (int i = 1; i < cnt; i++) {
int v = va_arg(ap, int);
if (v > maxv) maxv = v;
}
va_end(ap);
return maxv;
}
// 呼叫
max(10, 55, 34, 28, 16, 47, 62, 77, 86, 91, 9); // 返回 91
return 與 exit() 語意不同,勿混用fopen/fclose,二進制讀寫注意位元組序熟練這些基礎,即可應對絕大多數 C 語言工程實戰。