C語言函式一本道來

weixin_34208283發表於2017-12-07
2583346-f27e8ecbd01d277c.png
函式.png

函式的由來

程式=資料+演算法
C程式=資料+函式

  • 模組化程式設計


    2583346-267e76392db540cf.png
    模組化程式設計.png

程式導向的程式設計

  • 以過程為中心的程式設計思想
  • 首先將複雜的問題,分解為一個個容易的問題
  • 分解過後的問題可以按照步驟一步步完成
  • 函式是C語言程式導向的一種體現
  • 解決問題的每個步驟可以用函式來實現

宣告&&定義

  • 宣告 就是預先告訴編譯器實體的存在
  • 定義 就是明確指示編譯器實體的意義
#include <stdio.h>

extern int g_var;//全域性的

void f(int i, int j);//函式宣告

int main()
{
    //extern int g_var;//區域性變數宣告
    int g(int x);//函式宣告
    g_var = 10;
    f(1, 2);
    printf("%d\n", g(3));
    return 0;
}

void f(int i, int j)//函式定義
{
    printf("i + j = %d\n", i + j);
}

int g(int x)//函式定義
{
    return 2 * x + g_var;
}

檔案二

int g_var = 0;

函式的引數

  • 函式的引數在本質上與區域性變數相同,都是在棧上面分配空間
  • 函式引數的初始值是函式呼叫時的實參值
#include <stdio.h>

int f(int i, int j)
{
    printf("%d, %d\n", i, j);
}

int main()
{
    int k = 1;
    f(k, k++);
    printf("%d\n", k);
    return 0;
}

這裡的輸出是2,1,2

解釋

  • 函式的引數的求值順序是依賴於編譯器的實現!
  • 在程式到達順序點的時候,之前所做的一切操作必須反映到後續訪問中.

也就是說在這個例子中,在gcc編譯器下面,先傳第二個引數到函式,然後,再進行++運算,在進行第一個引數傳遞

.

可變引數列表

可變引數的含義

  • C語言中可以定義引數可變的函式
  • 引數可變的函式實現依賴於stdarg.h標頭檔案
  • va_list變數va_start(),va_end和va_arg配合使用能夠訪問引數值

在C語言中是沒有過載,但是可以使用可變引數列表

#include <stdio.h>
#include <stdarg.h>

float average(int n, ...)
{
    va_list args;
    int i = 0;
    float sum = 0;
    va_start(args, n);
    for(i=0; i<n; i++)
    {
        sum += va_arg(args, int);
    }
    va_end(args);
    return sum / n;
}

int main()
{
    printf("%f\n", average(5, 1, 2, 3, 4, 5));
    printf("%f\n", average(4, 1, 2, 3, 4));
    return 0;
}

可變引數的限制

  • 可變引數必須從頭到尾按照順序逐個訪問
  • 引數列表至少一個確定命名的引數
  • 可變引數巨集是沒有辦法判斷實際存在的引數的數量
  • 可變引數巨集無法判斷引數的實際型別

小結

  • 可變引數是C語言提供的一種函式設計的技巧
  • 可變引數提供了一種更方便的函式呼叫方式
  • 可變引數必須順序的訪問
  • 無法直接訪問可變引數列表中間的引數值

函式呼叫行為

活動記錄

  • 活動記錄是函式呼叫時用於記錄一系列相關資訊的記錄
    • 臨時變數域:用來存放臨時變數的值,如k++的中間結果
    • 區域性變數域:用來存放函式本次執行中的區域性變數
    • 機器狀態域:用來儲存呼叫函式之前有關機器狀態的資訊,包括各種暫存器的當前值和返回地址等。(棧頂指標等)
    • 實參域:用來存放函式實參資訊
    • 返回值域:為呼叫者函式存放返回值

函式引數計算的次序是依賴編譯器實現的,引數的入棧次序如何確定?

呼叫約定(在呼叫動態連線庫的時候可以用)

  • 當函式被呼叫時,引數會傳遞給被呼叫的函式,函式呼叫約定就是描述是如何傳遞到棧空間的,以及棧空間由誰維護
  • 引數的傳遞順序
    • 從右到左依次入棧: __stdcall,__cdecl,__thiscall
    • 從左到右依次入棧:__pascal,__fastall
  • 呼叫堆疊清理
    • 呼叫者清除棧
    • 被呼叫函式返回後清除棧

小結

  • 函式呼叫時C語言的核心機制
  • 活動記錄儲存了函式呼叫以及返回所需要的一切資訊
  • 呼叫約定是呼叫者和開發者之間的呼叫協議,常用於不同的開發者編寫的庫函式呼叫

函式的設計技巧

  • 不要再函式中使用全域性變數,儘量讓函式從意義上是一個獨立功能模組
  • 引數名要能夠體現引數意義
  • 如果說傳遞的引數為指標,且僅僅作輸入引數用,則應在型別前加const,以防止該指標在函式體內被惡意修改
  • 不要省略返回值的型別,如果函式沒有返回值,那麼應當宣告為void
  • 在函式體的“入口處”,對引數的有效性進行檢查,對指標的檢查尤為重要
  • 語句不可返回指向“棧記憶體”的“指標”,因為該記憶體會在函式結束後銷燬
  • 函式體的規模要小
  • 相同的輸入應當產生相同的輸出,儘量避免函式帶有“記憶”功能少用static
  • 避免函式有太多的引數,引數個數應當控制在4個以內
  • 有時候函式不需要返回值,但是增加靈活性,可以附加返回值
  • 函式名與返回值在語義上不能衝突

相關文章