函數運用基礎

 

 
實 習 內 容



1. 運用標準函數庫 (standard library functions)

2. 練習運用函數實行由上而下的程序化程式設計 (top-down design)

3. 運用函數來模組化程式

4. 有輸入參數 (input parameters) 的函數

5. 有輸出參數 (output parameters) 的函數

6. 比較複雜的 scanf() 輸入格式

1.

運用標準函數庫裡的函數:

math.h

函數名稱 參數(引數)型態 結果型態 範例
ceil(x) double double if x is 45.23, ceil(x) is 46
cos(x) double (弳度量, radians) double if x is 0, cos(x) is 1
acos(x) double double if x is 0, acos(x) is 1.5707963 (/2)

exp(x)

double double if x is 1, exp(x) is 2.71828
fabs(x) double double if x is -8.432, fabs(x) is 8.432
floor(x) double double if x is 45.23, floor(x) is 45.0
log(x) double double if x is 2.71828, log(x) is 1.0
log10(x) double double if x is 100.0, log10(x) is 2.0
pow(x,y) double, double double if x is 0.16, y is 0.5, pow(x, y) is 0.4
sin(x) double (弳度量) double if x is 1.5708, sin(x) is 1.0
sqrt(x) double double if x is 2.25, sqrt(x) is 1.5
tan(x) double (弳度量) double if x is 0.0, tan(x) is 0.0
atan(x) double double if x is 1, atan(x) is 0.78539815 (/4)

stdlib.h

函數名稱 參數(引數)型態 結果型態 範例
abs(x) int int if x is -10, abs(x) is 10
fabs(x) double double if x is -2.5, fabs(x) is 2.5
srand(seed) unsigned int void initialize the random sequence
rand() void int returns an integer between [0,RAND_MAX]
exit(status) int void terminates the program
system(command) char * void execute the command

考慮以下餘弦定理:

請完成下面程式: 運用標準函數庫中的函數, 由 a, b, c 三個邊長計算 b, c 的夾角 d (單位是角度 )

#include <stdio.h>
#include <________>
int main(void)
{
    double pi, a, b, c, d; 

    pi = ______; // tan(pi/4) = 1
    a = 1.82;
    b = 1.65;
    c = 1.45;
    d = ______________________; // 計算夾角 d (單位:角度)
    printf("The lengths of the three sides are %f, %f, and %f\n", a, b, c);
    printf("The angle between side b and side c is %f degrees\n", d);

    return 0;
}

2.

由上而下 (top-down) 逐步分解的程式設計方法:
/* Program to Draw a Stick Figure */

#include <stdio.h>

/* 函數原型 (Function prototypes) */

void draw_circle(void);      /* Draws a circle */
void draw_intersect(void);   /* Draws intersecting lines */
void draw_base(void);        /* Draws a base line */
void draw_triangle(void);    /* Draws a triangle */

int main(void)
{
    /* Draw a circle. 	*/
    draw_circle();

    /* Draw a triangle. 	*/
    draw_triangle();

    /* Draw intersecting lines.	*/
    draw_intersect();

    return (0);
}
 
/*
 * Draws a circle
 */
void draw_circle(void)
{
    printf("   *   \n");
    printf(" *   * \n");
    printf("  * *  \n");
}

/*
 * Draws intersecting lines
 */
void draw_intersect(void)
{
    printf("  / \\  \n"); /* 在字串中兩個 '\' 代表單一的 '\' */
    printf(" /   \\ \n");
    printf("/     \\\n");
}

/*
 * Draws a base line
 */
void draw_base(void)
{
    printf("-------\n");
}

/*
 * Draws a triangle
 */
void draw_triangle(void)
{
    draw_intersect();
    draw_base();
}
  1. 請拷貝上述程式到 devcpp, 編譯, 執行, 對照程式和執行結果
  2. 請參考上面這個範例, 撰寫一個 draw_reverse_intersect() 函數來畫一個倒V的形狀 (和上面的 draw_intersect() 顛倒過來的形狀).
  3. 請利用 draw_intersect() draw_reverse_intersect() 撰寫一個 draw_diamond() 函數, 在螢幕上畫出一個菱形

這個步驟裡我們寫的函數是不傳遞任何資料進去的函數, 函數的參數都是 void, 使用這種函數不見得是因為函數要呼叫很多次, 可以省下重複的程式碼, 而是因為

  • 可以運用這種結構, 簡化程式的邏輯, 可以讓每一個函數只負責兩三件同性質的事情
  • 邏輯簡單, 需要顧及的事情少, 就使得在寫個別函數的時候比較不容易出錯
  • 個別函數裡存放運算結果的變數也可以隔離開來: 在某一個函數裡定義的變數, 其他函數裡使用不到, 不會互相干擾
  • 給函數一個有代表性的名字 (例如 draw_triangle), 使得程式的其他部份可以藉由呼叫這些自己定義的函數而邏輯變得比較簡單易懂。

你可以嘗試把上面這個程式改成在 main() 裡面完成所有的功能, 不使用任何函數, 如此可以感覺一下兩個程式之間的差異。

你也可以參考一下這個以前沒有運用函數寫出來的程式...閱讀這個程式, 同時去修改這個程式所要花費的力氣, 我自己保守估計和一個5000列的結構化、模組化的程式差不多...

3.

有輸入參數 (input parameters) 的函數:

沒有參數的函數, 每次呼叫所完成的事情一定一模一樣, 當然效果有限, 如果能夠有一些參數, 每一次呼叫這個函數時都針對不同的資料或是命令來處理的話, 這個函數的功能就大得多了

請接續步驟一裡計算角度的程式, 寫一個 calculate_angle() 函數針對不同邊長的三角形計算夾角 d, 這個函數需要傳入三個 double 型態的參數 a, b, c; 同時會傳回計算出來的角度, 你需要寫類似下面這樣的函數定義:

double calculate_angle(double aParam, double bParam, double cParam)
{
    double d; /* 函數裡需要用來存放資料的變數一定要在函數裡定義 */
    double pi=________;

    /* 計算夾角 d */
    d = acos(aParam*aParam - bParam*bParam - ... )/(-2*bParam*...))/pi * 180;

    return d;
}

如此則 main() 函數裡面相關的敘述變成

a = 1.82; 
b = 1.65; 
c = 1.45; 
d = calculate_angle(a, b, c); // 計算夾角 d (單位:角度)
請注意:
  1. calculate_angle() 函數裡只能用到自己有宣告或是定義的變數, 包括 aParam, bParam, cParam, d, 和 pi, 不能用 main() 函數裡面的變數 a, b, c, d, pi, 同樣名稱的變數 d 和 pi 在不同函數中是不同的變數, 所以各個函數執行的時候不會互相干擾
  2. main() 函數中在呼叫 calculate_angle() 函數時, 變數 a 所存放的資料會自動地拷貝到變數 aParam 裡, 變數 b 所存放的資料會自動地拷貝到變數 bParam 裡, 變數 c 所存放的資料會自動地拷貝到變數 cParam 裡, 然後才開始執行 calculate_angle() 函數裡面定義的動作; aParam, bParam, cParam 是函數 calculate_angle() 的參數, 函數的參數也是一種變數, 必須要明確定義它的型態, 一般來說在函數裡面比較少去修改這個變數裡面的資料, 但是如果需要修改的話, 更動它裡面的內容是完全合法的, 你可以想像函數的參數像是函數接受資料的信箱, 呼叫這個函式的人會把資料放進這些信箱, 然後函數裡面可以根據這些資料來處理。

練習到這裡, 請回顧一下, 程式裡運用函數的好處有哪些呢?

4.

再做一個運用函數模組化程式的例子, 同時也看到如何寫有輸出參數 (output parameters) 的函數:

下面這個程式計算並且印出如上圖中多個墊圈(washer)的總重量, 程式的輸入是每一個墊圈的 內徑, 外徑, 厚度, 材質的密度和墊圈的數量

01 /*
02  * Computes the weight of a batch of flat washers.
03  */
04 
05 #include <stdio.h>
06 
07 #define PI 3.14159
08 
09 int main(void)
10 {
11       double hole_diameter; /* input - diameter of hole         */
12       double edge_diameter; /* input - diameter of outer edge   */
13       double thickness;     /* input - thickness of washer      */
14       double density;       /* input - density of material used */
15       double quantity;      /* input - number of washers made   */
16       double weight;        /* output - weight of washer batch  */
17       double hole_radius;   /* radius of hole                   */
18       double edge_radius;   /* radius of outer edge             */
19       double rim_area;      /* area of rim                      */
20       double unit_weight;   /* weight of 1 washer               */
21 
22       /* Get the inner diameter, outer diameter, and thickness.*/
23       printf("Inner diameter in centimeters> ");
24       scanf("%lf", &hole_diameter);
25       printf("Outer diameter in centimeters> ");
26       scanf("%lf", &edge_diameter);
27       printf("Thickness in centimeters> ");
28       scanf("%lf", &thickness);
29 
30       /* Get the material density and quantity manufactured. */
31       printf("Material density in grams per cubic centimeter> ");
32       scanf("%lf", &density);
33       printf("Quantity in batch>");
34       scanf("%lf", &quantity);
35 
36       /* Compute the rim area. */
37       hole_radius = hole_diameter / 2.0;
38       edge_radius = edge_diameter / 2.0;
39       rim_area = PI * edge_radius * edge_radius -
40                  PI * hole_radius * hole_radius;
41  
42       /* Compute the weight of a flat washer. */
43       unit_weight = rim_area * thickness * density;
44 
45       /* Compute the weight of the batch of washers. */
46       weight = unit_weight * quantity;
47 
48       /* Display the weight of the batch of washers. */
49       printf("\nThe expected weight of the batch is %.2f", weight);
50       printf(" grams.\n");
51 
52       return (0);
53 }
 
Inner diameter in centimeters> 1.2
Outer diameter in centimeters> 2.4
Thickness in centimeters> 0.1
Material density in grams per cubic centimeter> 7.87
Quantity in batch> 1000
 
The expected weight of the batch is 2670.23 grams.
  1. 請寫一個 inputData 函數取代第 23-28 列, 上一個步驟中傳遞資料進入一個函數很直覺, 透過參數傳遞資料回 main 函數就需要新的語法了, 如下面程式中紅字部份:
    1. 首先在參數宣告的地方 double * 是一個新的變數型態, 我們稱為浮點數指標型態, 宣告敘述 double *hole_diameterAddr 就是告訴編譯器 hole_diameterAddr 這個變數是用來存放 double 型態變數的記憶體位址
    2. 所以在 main 函數中呼叫 inputData 函數時, 傳進去的不是 hole_diameter 而需要是 &hole_diameter, 在 hole_diameter 變數前面這個 & 符號就是要取得變數 hole_diameter 的記憶體位址的指令
    3. inputData 函數裡面在呼叫 scanf 函數時, hole_diameterAddr 變數前面不需要加上 & 符號, 因為 hole_diameterAddr 變數裡面存放的就是 hole_diameter 變數的記憶體位址。 所以直接寫 scanf("%lf", hole_diameterAddr); 就會得到和原來在 main() 函數第 24 列的 scanf("%lf", &hole_diameter); 一樣的效果
  2. void inputData(double *, double *, double *); // 函數名稱以及參數型態的預先定義
    int main(void) { ... inputData(&hole_diameter, &edge_diameter, &thickness); ... } void inputData(double *hole_diameterAddr, double *edge_diameterAddr, double *thicknessAddr) { printf("Inner diameter in centimeters> "); scanf("%lf", hole_diameterAddr); printf("Outer diameter in centimeters> "); scanf("%lf", edge_diameterAddr); printf("Thickness in centimeters> "); scanf("%lf", thicknessAddr); }

    我們以後在課堂裡還會再詳細解釋上面這個機制, 如果你有疑問也可以先嘗試尋求你比較可以接受的解釋

  3. 請寫一個函數取代第 31-34 列 (注意事項與前一步驟相同)
  4. 請寫一個函數取代第 37-40 列
    double calculateRimArea(double, double); // 兩個參數的名稱可以省略, 這個函數宣告
    // 的敘述並不是在定義完整的函數, // 只是告訴編譯器 calculateRimeArea 是 // 一個函數名稱, 需要兩個 double 型態的 // 輸入參數, 回傳一個 double 型態的資料 int main(void) { ... rim_area = calculateRimArea(hole_diameter, edge_diameter); ... } double calculateRimArea(double hole_diameter, double edge_diameter) { double hole_radius, edge_radius, rim_area; hole_radius = ...; edge_radius = ...; rim_area = ...; return rim_area; }
    請注意函數裡面要使用到的變數都要在函數裡面宣告,並且設定適當的數值, 同時 main() 函數裡不再需要使用 hole_radius 及 edge_radius,所以可以把 main() 裡面的 那兩個變數刪除
  5. 請寫一個函數取代第 43 列
  6. 請寫一個函數取代第 46 列
  7. 請寫一個函數取代第 49-50
線上繳交

請運用函數一步一步將上面這個程式改寫為容易閱讀容易偵錯容易修改的版本, 測試執行過之後, 請線上繳交程式

請將前面長度54列的那個 main() 函式拷貝下來, 編譯, 就可以得到範例執行程式

完成以後, 請在1101-1C e-Tutor 線上繳交, 繳交

5.

測試比較複雜的 scanf() 輸入格式:

請利用 scanf() 函數,由鍵盤讀取下列指定格式的資料到記憶體的變數裡面, 並且列印到螢幕上:

  1. 輸入是兩個整數和一個浮點數, 中間以所謂的 white space (space, tab, or new line) 分隔開來:
    e.g.
    234   567
    
    3.5 
  2. 輸入是兩個整數和一個浮點數, 中間以逗號(,)和空白隔開
    e.g.
    123, 456, 7.89
  3. 輸入是兩個整數和一個浮點數, 中間以逗號和任意個數的空白隔開
    e.g.
    123    ,    456   ,    789.4
  4. 輸入是下列格式的兩個整數和一個浮點數, 其中 number1=, number2=, number3=是固定的輸入文字
    e.g.
    number1=   123 number2=    456 number3=7.89
    

例如:

#include <stdio.h>
int main(void)
{
    int num1, num2;
    double num3;
    scanf("%d%d%lf", &num1, &num2, &num3);
    printf("number1 = %d,  number2 = %d, number3 = %f\n", num1, num2, num3);
    return 0;
}

程式設計課程 首頁

製作日期: 09/16/2012 by 丁培毅 (Pei-yih Ting)
E-mail: [email protected] TEL: 02 24622192x6615
海洋大學 電機資訊學院 資訊工程學系 Lagoon