函數:
意思是指「有傳回值的副程式」,通常應用在一段程式重複出現多次,我們可以把它獨立成函數,以減少程式撰寫工作,易於維護,並增加程式的可讀性。
格式:
傳回值型態 函數名稱(參數1,參數2……)
{
函數的本體
}
傳回值型態:可以在函數名稱前加上型別宣告,其用來宣告其傳回值的資料型別。若無傳回值,則宣告其型別為void。
函數名稱:開頭第一字元不可以是數字,其餘部份由英文字母、數字…構成。
參數:函數名稱後緊接著"( )"符號,不管是否有參數值,都一定要有此符號。
範例1:
#include <stdio.h>
int addFun(int x, int y) {
return x + y;
}
void main() {
int a = 10, b = 20, r;
r = addFun(a, b);
printf("%d + %d = %d \n", a, b, r);
}
範例2:
#include <stdio.h>
void list(void) {
printf("不傳參數、不傳回值\n");
}
int main( ) {
list();
}
範例3:
#include <stdio.h>
int add_Fun(void) {
int a = 5, b = 10, z;
z = a + b;
return z;
}
int main() {
int c;
c = add_Fun();
printf("The result is %d \n", c);
}
範例4:
#include <stdio.h>
void subtract_Fun(int i, int j) {
print(" %d - %d = %d\n", i, j, i - j);
}
int main() {
int a = 14, b = 15;
subtract_Fun(a, b);
}
範例5:
#include <stdio.h>
int multiply_Fun(int x, int y) {
return x * y;
}
int main() {
int a=2, b=19;
printf("%d * %d = %d \n", a, b, multiply_Fun(a, b));
printf("%d\n", multiply_Fun(a, b) + multiply_Fun(2*a, 2*b));
printf("%d\n", multiply_Fun(multiply_Fun(a, b), b));
}
為了讓compiler能夠正確編譯函數呼叫,在呼叫函數A前必須要先做定義函數A,因此前面幾個範例內的函數都放在main的前面。某些時候由於遞迴呼叫或是其他原因,可能在定義函數A前就要用到該函數,則可以用以下的方法來解決:
#include <stdio.h>
double multiply_Fun(double, double);
int main() {
double a=2, b=19, c;
c = multiply_Fun(a, b);
printf("%lf * %lf = %lf \n", a, b, c);
}
double multiply_Fun(double x, double y) {
return x * y;
}
第二行只是定義multiply_Fun所需要的參數和傳回值。如果用到系統的函數庫,就要include相關的.h檔,其原因也是如此。讀者可以找找你所用開發環境內的stdio.h長甚麼樣子。
C語言定義參數傳遞的方式為"Call By Value",中文翻成傳值呼叫。其機制是將運算式的值複製到堆疊上(也就是說在堆疊上產生新的變數), 然後由被呼叫的函數在堆疊上存取該參數。這種做法的最大優點是,被呼叫者看不到呼叫者定義的區域變數,因此呼叫者不怕其變數被被呼叫者改掉。
void fun(int x, int y) {
x = 5;
y = 5;
}
void main() {
int x = 0;
int y = 0;
fun(x, y);
printf("%d %d\n", x, y);
}
上述的程式執行後會在螢幕上印出兩個0,而不是兩個5。fun(int x, int y)所宣告的x,y和main裡面所宣告的x,y是完全不同的變數。fun只能看到自己的x,y不能看到main裡面的x,y。呼叫fun(x, y)時,這裡的x,y不是變數,而是兩個運算式,這兩個運算式就是LOAD x和LOAD y兩個機器指令。因此Call By Value所傳遞的不是變數的地址,而是該運算式的計算結果。以下範例就更加清楚了:
void fun(int x, int y) {
x = 5;
y = 5;
}
void main() {
int x = 0;
int y = 0;
fun(x + 1, y + 1);
printf("%d %d\n", x, y);
}
fun(x + 1, y + 1)裡運算式x + 1不會有人把它當成變數吧,既然如此fun(x, y)裡的x也一樣不是變數而是運算式。
討論變數的範圍時,我們必須了解變數的兩項特質
如果變數定義於函數之外,如
int x;
int main() {
}
則該變數
如果變數定義於函數內,如
int main() {
int x;
}
則該變數
由於函數可以遞迴呼叫,因此函數內的變數消滅前可能會呼叫自己而再產生新的變數。此類變數稱為local variable或auto variable,Compiler會把它放在堆疊上。
如果函數可以看到兩個以上同名的變數,則運算式內的變數會以位置最近的為準,所謂位置指的是最接近的{}
double x = 3.14;
int main() {
int x = 0;
x = 100; // 此處的x是指上一行的整數x
}
C語言有兩個關鍵字static和extern可用來改變變數的存取範圍
static int x;
int main() {}
int main() {
static int x;
}
extern則表示此變數是在別的檔案內宣告(給空間),此處只是要讓Compiler能夠翻譯相關的運算式,但在此處並不分配空間。這種用法主要在大型專案管理中,有許多程式設計人員撰寫程式,需要用到共同的全域變數,如果每個人定義一次,則Linker會抱怨該變數宣告了一次以上。若都加上static,雖然可以產生執行檔,但實際上大家用的不是同一個變數。正確的做法是:
global.h檔內定義 extern int x; global.c內定義 int x; 其他人的.c檔內 #include "global.h"
C的前置處理器(Preprocessor)有一個#define命令,可用來取代原始程式內的某些字串:
#define PI 3.14159
main() {
double r1 = 3.0L, r2 = 5.0L;
printf("Circle(3) area = %lf", 2 * PI * r1 * r1);
printf("Circle(5) area = %lf", 2 * PI * r2 * r2);
}
就相當於將程式寫成
main() {
double r1 = 3.0L, r2 = 5.0L;
printf("Circle(3) area = %lf", 2 * 3.14159 * r1 * r1);
printf("Circle(5) area = %lf", 2 * 3.14159 * r2 * r2);
}
define不但可以做簡單的字串取代,還可以加上參數以完成複雜的字串取代工作
#define max(A, B) ((A) > (B) ? (A) : (B))
main() {
int x, p=3, q=5, r=2, s=7;
x = max(p+q, r+s);
}
上述程式相當於
main() {
int x, p=3, q=5, r=2, s=7;
x = ((p+q) > (r+s) ? (p+q) : (r+s));
}
這種巨集是由preprocessor透過字串取來達成的,和正常的函數呼叫完全不同。在下面的例子中,如果不知道square是巨集的話,就會搞不清楚為何跑出來的結果不正確了
#define square(x) x * x
main() {
int z = 3;
printf("%d\n", square(z + 1));
}
印出來是7而不是16喔
遞迴:
意思是重覆呼叫執行自己本身的程式片段,直到符合終止條件為止。撰寫遞迴程式的精神是
求1+2+3+...+n
解析
int sum(int n) {
if (n == 1) {
return 1;
}
return n + sum(n - 1);
}
算1*2+2*3+3*4+…+(n-1)*n之和
/* 程式功能: 用遞迴求算1*2+2*3+3*4+…+(n-1)*n之和 */ #includeint sum(int n); void main() { int n; printf("Input the number n: "); scanf("%d",&n); printf("1*2+2*3+3*4+...+(n-1)*n=%d", sum(n)); } int sum(int n) { if (n == 1) { return 0; } else { return sum(n-1)+n*(n-1); } }
輸入兩數字A, B,利用遞迴求得A的B次方
#include <stdio.h>
power(int a, int b);
void main(void) {
int x, y;
printf("Please input two number:");
scanf("%d %d", &x, &y);
printf("\n%d^%d = %d", x, y, power(x, y));
}
int power(int a, int b) {
switch(b) {
case 0: return 1;
case 1: return a;
default: return (a * power(a, b - 1));
}
}
兩個整數m,n的最大公因數
解析
int gcd(int m, int n) {
if (n == 0) {
return m;
}
return gcd(n, m % n);
}
費式數列
解析
int fab(int num) {
if (num <= 1) {
return num;
}
return fab(num - 1) + fab(num - 2);
}
Ackerman函數
A(m, n)定義為
怎麼寫?
河內塔
在河內塔的問題中,有三根柱子,n個大小不一樣的碟子,一開始時所有n個碟子以大下小上排在某根柱子上。在一次只能移動一個碟子,且不違反大下小上的原則下,如何把這n個碟子全部移到另一根柱子上。
再次回憶遞迴的要點
解析
/**
* 河內塔的解法
*/
#include <stdio.h>
void move(int n, int from, int to, int another) {
if (n > 0) { // 記得遞迴程式要先寫邊際條件才能作遞迴呼叫
move(n - 1, from, another, to);
printf("move %d to %d\n", from, to); // 以印出訊息代表搬動的過程
move(n - 1, another, to, from);
}
}
int main() {
int n=0;
// 一直讀入數字並執行move,直到使用者輸入的數字小於等於0為止
while (scanf("%d",&n) != EOF && n > 0) {
move(n,1,2,3);
}
}