天天看點

c語言qsort_《C語言要點》第六章 子產品化——函數

C語言讀書摘錄筆記,筆記内容絕大部分摘錄整理自李春葆、李筏馳老師編著的《直擊招聘——程式員面試筆試C語言深度解析》一書,少部分來自于網絡部落格及網上資源(盡量保留了資源原始連結)

6.1 函數基礎

6.1.1函數的定義與調用

  1. 函數的定義
  • 預設的函數類型為int
  • void型函數無傳回值,不能包含帶傳回值的return語句;其他類型的函數至少包含一個return語句
  • 函數的定義不能嵌套,即不能在一個函數體内又包含另一個函數的定義。這就保證了 每一個函數是一個獨立的、功能單一的程式單元
  • 複合語句 (用花括号{}括起來的語句)申明的變量的作用域隻在複合語句中,出了複合語句就不起作用。複合語句中的變量名和複合語句外面的變量即使同名也不是同一變量。

2. 函數的調用

    • 實參(argument)

      全稱為"實際參數",是在調用時傳遞給函數的參數。實參可以是常量、變量、表達式、函數等, 無論實參是何種類型的量,在進行函數調用時,它們都

      必須具有确定的值 , 以便把這些值傳送給形參。 是以應預先用指派,輸入等辦法使實參獲得确定值
    • 形參(parameter)

      全稱為"形式參數" ,由于它不是實際存在變量,是以又稱虛拟變量。是在定義函數名和函數體的時候使用的參數,目的是

      用來接收調用該函數時傳入的參數

      。在調用函數時,實參将指派給形參。因而,必須注意實參的個數,類型應與形參一一對應,并且實參必須要有确定的值。沒有形參時,圓括号也不可省;多個參數之間應用逗号分隔。參數包括參數名和參數類型

      來源:牛客網

6.1.2 函數的傳回值與return語句

  1. return的語句功能:傳回調用函數(終止該函數的執行),并将return語句中表達式的值帶給調用函數
  2. return語句中表達式的類型與函數的類型不一緻時則 以函數類型定義為準 ,系統自動進行類型轉換
  3. C語言中可以使用不帶表達式的語句直接傳回,C++必須使用帶表達式的return語句傳回
  4. return 語句不能傳回局部變量的位址,因為該位址中存放的局部變量在函數執行完畢後被釋放,但可以傳回靜态局部變量的位址,因為靜态局部變量的空間不是在棧幀中,而是在靜态資料區,即使棧幀退棧了,它仍然存在

6.1.3 函數的聲明

  1. 函數聲明語句也稱為 函數原型
  2. 如果調用一個函數出現在該函數的定義之前,則在調用前必須對該函數進行聲明
  3. 如果函數原型放在調用函數定義的内部,則該聲明僅對該調用函數有效
  4. fun()與fun(void)聲明的差別:對于前者,編譯器編譯時不檢查該函數調用的參數傳遞情況;對于後者,括号中有void,編譯器編譯時會嚴格檢查該函數調用時的參數傳遞情況,如果帶參數調用,則會編譯錯誤或者警告

6.1.4 外部函數與内部函數

  1. 函數預設類型是 外部函數 ,其作用域是整個源程式,即:除了可被本源檔案中的其他函數調用外,還可被其他源檔案中的函數調用(其他源檔案調用時,需要對被調用的外部函數用extern語句進行聲明)
  2. 内部函數 ,也稱為 靜态函數 ,使用static關鍵字定義,其作用域局限于定義它的源檔案内部,即:隻能被本源檔案中的函數調用,不能被統一程式的其他源檔案中的函數調用,其有以下優點:
  • 其他源檔案中可以定義相同名字的函數,不會發生沖突
  • 靜态函數不能被其他源檔案所用,達到“隐藏”目的

6.1.5 函數間的參數傳遞

參數傳遞有兩種方式:

傳值

傳位址
  1. 傳值方式 一個函數調用另一個函數時直接将實參的值傳遞給對應的形參,這稱為 傳值方式 ,對應的形參稱為值參數。傳值方式實作了把資料由調用函數傳遞給被調用函數。由于資料在傳遞方(實參方)和被傳遞方(形參方)占用不同的記憶體空間(函數的形參屬于自動變量,函數執行完畢後自動釋放),是以形參在被調用函數中無論如何變化都不會影響調用函數中相應實參的值,也就是說調用函數時實參和形參之間是單向的從實參到形參的值傳遞。
  2. 傳位址方式 如果要通過一個函數fun改變某個實參y (對應形參為x, 資料類型為Type )的值,需要在fun 形參表定義為

    Type *x

    , 在調用函數的語句中指定為&y (取y 的位址)。這樣此時形參變量指向的記憶體位址與實參的位址相同,是以通過 解引用 形參變量指針進行的操作等同于對實參進行操作。

6.1.5 函數調用的實作原理

大多數CPU上的程式使用

棧空間

來支援函數調用操作。單個函數調用操作所使用的函數調用棧被稱為

棧幀(stack frame) 結構

。每次函數調用時都會相應地建立一幀,儲存返問位址、函數形參和局部變量值等,并将該幀壓入調用棧。若在該函數傳回之前又發生新的調用,則同樣要将與新函數對應的一幀進棧,成為棧頂。函數一旦執行完畢,對應的幀便出棧(此時局部變量的生命周期結束),控制權交還給該函數的上層調用函數,并按照該幀中儲存的傳回位址确定程式中繼續執行的位置。

  • 函數調用要點
    • 棧空間中每個棧幀的大小是有限的,是以在—個函數中不要定義很大空間的數組,否則可能會導緻 棧溢出 ,程式崩潰。
    • 每個棧幀對應着一個未運作完的函數。棧幀中儲存了該函數的傳回位址和局部變量,每個函數的每次調用,都有它自己獨立的一個棧幀,這個棧幀維護着函數調用所需要的各種資訊。函數的傳回位址和參數,儲存目前函數調用前的“斷點”資訊,也就是函數調用前的指令位置,以便在函數傳回時能夠恢複到函數被調用前的代碼區中繼續執行指令。函數棧幀的大小并不固定,一般與其對應函數的局部變量多少有關。函數運作過程中,其棧幀大小也是在不停變化的!

版權聲明:本文為CSDN部落客「YYtengjian」的原創文章,遵循 CC 4.0 BY-SA 版權協定,轉載請附上原文出處連結及本聲明。

    • 當—個函數多次調用時,每次調用都會建立一個棧幀,為同名的局部變量配置設定空間,但它們的位址是不同的,它們之間也沒有關系。

6.1.7 函數調用時參數的求值順序

如果所有實參表達式的求值沒有二義性,那麼從右往左求值和從左往右求值結果是相同的。人們一般認為是從右往左順序求值的。但,如果出現二義性,輸出結果便是不确定的。例如:

#include <stdio.h>
int main()
{
    int a=l;
    printf("%d,%d,%dn",a++,++a,a++);
    return 0;
}
           

此程式在VC++中輸出“2,2,1”,但在Dev C++中輸出“3,4,1”,這是因為printf函數的實參存在二義性,因為在函數的所有參數指派之後且在函數的第一條語句執行之前有一個順序點,而參數間的逗号處沒有順序點,任意兩個順序點之間的副作用的求值次序都是不确定的,這裡有3 個副作用,是以輸出結果不确定。

  • 見筆記第一章1.2.1中第4點表達式求值的副作用
  • C語言參考手冊:求值順序

6.1.8 atexit()函數

即使main()函數終止以後仍然可以執行一些代碼,這需要使用stdlib.h 頭檔案中的atexit()函數。一般來說,如果在main()中調用某個函數,程式的執行會跳轉到該函數并執行它。在執行該函數後控制權又交還給main()函數。

當使用了atexit() 函數以後,程序的執行可以簡單地了解為

當main()函數終止後跳轉到atexit()函數,然後再也不會傳回到main()函數

。atexit()函數的使用格式如下:

atexit(函數名);
           
由于atexit()函數是按後進先出的方式注冊這些函數的,是以最後注冊的函數先調用

6.2 數組作為函數參數

以二維數組為例:

int a[M][N]; int (*pa)[n] = a ;
void func(int a[][N]); void func(int a[M][N]);//函數聲明
func(a); func(pa);//調用
           

6.3 指針數組作為函數參數

當指針數組作為實參時,對應的形參應當是一個指向指針的指針變量.例如以下三種形式:

void func(int *a[]);
void func(int *a[N]);
void func(int **a);
           

6.4 指針函數和函數指針

6.4.1 指針函數

例如:

int *func(int a,float x);

解釋

  • 定義指針型函數時前面的

    *

    号與“資料類型”相結合,表示此函數是指針型函數。上例,定義func()函數時首部中的

    int*

    是一個整體,表示該函數傳回的是整型變量的位址
  • 在程式中不要使用數組名接收指針型函數的傳回值,因為數組名為位址常量,不能向它指派

6.4.2 函數指針

函數的存儲首位址又稱為函數的執行入口位址, C 規定函數的首位址就是函數名。當指針變量儲存函數的入口位址時它就指向了該函數,是以稱這種指針變量為指向函數的指針變量,簡稱為

函數指針

.

定義函數指針的一般格式:

函數類型(*函數指針名)(形參表);

解釋

  • 在定義函數指針變量時,“函數指針名“兩邊的圓括号不能省略,它表示函數指針名先與

    *

    結合,即為指針變量,然後再與後面的”(形參表)”相結合,表示該指針變量指向函數。如果少了前面的一組括号,則變為

    函數類型 *函數名(形參表)

    它表示傳回值為位址值(指針)的函數
  • 函數指針變量的類型是被指向的函數類型
  1. 給函數指針指派格式:

    函數指針名=函數名;

  2. 通過函數指針調用函數格式:

    (*函數指針)(實參表);

  3. 函數指針的作用主要展現于在函數間傳遞函數,這種傳遞不是傳遞任何資料,而是傳遞函數的執行位址,或者說是傳遞函數的調用控制。 當函數在兩個函數間傳遞時,調用函數的實參應該是被傳遞函數的函數名,而被調用函數的形參應該是接收函數位址的函數指針
  4. 函數指針的用處:
  • 使用函數指針的目的是為了增加執行函數的通用性,特别是在可能調用的函數可變的情況下,可以動态設定内容,有靈活性。如:排序的qsort函數需要傳入比較的函數指針,來确定排序是從大到小還是從小到大,如下:
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*));
//其中參數compar——用來比較兩個元素的函數,即函數指針(回調函數)
           

6.2.3 兩個函數指針執行個體

1.執行個體一:

c語言qsort_《C語言要點》第六章 子產品化——函數
  1. 執行個體二:
void ( *func(void (*p)(void *),void *x) ) (void *);
           
解釋

c語言qsort_《C語言要點》第六章 子產品化——函數

6.5 遞歸函數

  • 遞歸函數又稱 自調用函數 ,其特點是在函數内部調用自己。C 規定不允許函數遞歸定義,即不允許在一個函數體中定義另一個函數,但可以遞歸調用。在執行遞歸函數時将反複調用其自身,每調用一次就進入新的一層。
  • 一般地, 一個遞歸函數定義由兩個部分組成,即 遞歸結束情況 遞推關系情況 。遞推關系就是把一個不能或不好直接求解的“大問題“轉化成一個或兒個“小問題”來解決,再把這些“小問題”進一步分解成更小的“小問題”來解決(即遞推),如此分解,直到每個“小問題”都可以直接解決(此時分解到遞歸結束情況)。
  • Silencht:《C語言要點》第一章 程式設計基礎——變量
  • Silencht:《C語言要點》第二章 資料處理——控制結構
  • Silencht:《C語言要點》第三章 記憶體操作——指針
  • Silencht:《C語言要點》第四章 資料組織——數組
  • Silencht:《C語言要點》第五章 資料結構II——結構體與聯合體
  • Silencht:《C語言要點》第七章 位操作——位運算和位域
  • Silencht:《C語言要點》第八章 編譯前的處理——預處理