很多C語言初學者都非常好奇的問題,怎樣定義可以可變參數函數?

2020-12-05 IT劉小虎

如果一定要說哪段C語言代碼最「著名」,我想非「hello world」莫屬了。大多數初學者人生中編寫的第一段C語言代碼就是這段「裡程碑」式的代碼:

#include <stdio.h>

int main()

{

printf("hello world\n");

return 0;}

也正因為這段著名的程序,printf() 函數成為大多數C語言初學者接觸到的第一個標準庫函數。

「裡程碑」式的代碼

C語言中的可變參數函數

隨著學習的推進,初學者逐步學會調用別的C語言函數,以及定義自己的函數,觀察力敏銳的會注意到 printf() 函數似乎與其他函數不太一樣——printf()函數沒有固定數目的參數,它似乎可以接收任意多的參數。

而其他C語言函數則不同,它們大都有固定數量的參數(0個,3個等),調用這些函數必須傳遞對應數目的參數。

有些持有「特殊論」的初學者認為像 printf() 這樣的「可變參數」函數是「特殊的」,是系統定義的,我們程式設計師只能定義固定參數的函數,其實不是的,C語言是有手段定義自己「可變參數」函數的。

printf() 究竟是不是只能由系統定義的「特殊」函數呢?

怎樣定義自己的可變參數函數?

事實上,標準庫 <stdarg.h>就是方便C語言程式設計師定義自己的「可變參數」函數的。如果讀者和我一樣使用的是 Linux 系統,則可以方便的通過 man 命令查詢到相關庫函數:

C語言的stdarg標準庫

頭文件<stdarg.h>聲明了 va_list 類型用於描述可變參數,並且定義了上述 4 個方法解析。這裡不打算介紹過多枯燥的理論知識,我們直接看實例,請看相關C語言代碼:

C語言代碼

上述代碼定義了可變參數函數 foo(),它可以接收類似於 printf() 的函數,並且將 fmt 中的 s 解析為字符串,d 解析為整數,c 解析為字符,因此編譯並執行這段C語言代碼,可得到如下輸出:

# gcc t.c

# ./a.out

string hello

int 12

char m通過這段實例,可以看出使用C語言定義可變參數函數並不複雜,在處理可變參數時,只需先調用 va_start() 將參數序列加載到 va_list 結構的變量中,然後調用 va_arg() 依次解析。解析完畢後,再調用 va_end() 結束解析。

va_start -> va_arg -> va_end。

唯一需要注意的是使用 va_arg() 解析參數時,需要指定類型。但是這個過程也很簡單,可變參數函數的實現者可以指定一套規則,用於約束函數調用者傳遞參數,這樣就知道接下來需要解析的參數是何種類型。例如上面的C語言代碼就約定了 fmt 中的 s 表示接下來的要解析的參數是字符串,d 表示整數等。

計算機是如何處理可變參數函數的?

計算機是如何處理可變參數函數的?

C語言定義可變參數函數的過程並不複雜,藉助於<stdarg.h>,我們能夠輕易的定義接收任意多參數的函數,不過到這裡,有讀者發現問題了:我們人類可以按照規則寫出可變參數函數,但是計算機是如何理解這一套規則的呢?或者換句話說,計算機是如何處理「可變參數」的?

以Linux為例,看過我之前文章的讀者應該明白,每個C語言程序進程都有屬於自己的棧,進程中的每個函數則有屬於自己的棧幀,當有函數調用時,例如:

foo("%d%d%d", 3,2,1);

C語言編譯器會產生類似於下面這樣的彙編代碼:

push 1push 2push 3push "%d%d%d"call foo也即將 foo() 函數的參數先壓入棧中,然後再調用 foo() 函數。鑑於棧這種數據結構「先進後出」的特點,一般函數參數的入棧順序是從右至左的。

「先進後出」

按照這樣的參數入棧順序,foo() 函數使用參數很方便,依次從棧中將參數取出就可以了。至於如何解析棧中的參數,則可以根據可變參數實現者指定的規則,例如在格式化字符串 fmt 中遇到 s 就解析為字符串等。

如果可變參數 foo() 接收到其他數目的參數,對於最終程序來說,也僅僅只需要修改壓棧的參數數目,其他並無太多不同。

小結

本文主要討論了C語言中可變參數函數的定義方法,以及計算機如何處理可變參數函數的過程,其實並不複雜。C語言不像C++那樣支持函數重載,但是藉助於可變參數函數和宏,我們可以像定義「偽類」那樣,定義自己的「偽函數重載」,這是一種編程技巧,以後有機會再討論了。

點個關注吧

歡迎在評論區一起討論,質疑。文章都是手打原創,每天最淺顯的介紹C語言、linux等嵌入式開發,喜歡我的文章就關注一波吧,可以看到最新更新和之前的文章哦。

未經許可,禁止轉載。

相關焦點

  • C語言中的main()函數可以有好幾種類型,為何都能做入口函數呢?
    而C語言沒有重載語法,為什麼在C語言程序中,可以有不同類型的 main() 函數呢?為什麼在C語言程序中,可以有不同類型的 main() 函數呢?C語言程序支持多種類型 main() 函數,其實和支持可變參數函數是類似的。
  • Python函數的五種參數類型
    我們看看執行的效果:傳入三個參數的調用成功,並且實參對應函數形參的位置被列印出來;傳入兩個參數的調用失敗,Python彈出「缺少一個參數c」的報錯信息。【2】默認參數針對位置參數少傳一個參數報錯的問題,我們是否可以設置一個默認的值呢?這樣在函數調用時不用傳入該參數時也不會報錯。這就是「默認參數」。
  • java基礎入門-day18-可變參數
    1 使用數組為方法參數int sum(int a, int b) {return a + b;}int sum(int a, int b, int c) {return a + b + c;}int sum(int a, int b, int c, int d) {return a + b + c + d;} 看上面代碼
  • 言C語言陷阱與技巧第21節,函數只能返回一個值嗎?有多個返回值怎麼...
    return a, b但是很多C語言初學者在編寫某個函數,需要返回多個值時會感到困難,畢竟C語言的 return 語法只能返回一個值,返回多個值就會引起語法錯誤:int fun(){ ...這樣做當然沒問題,但是如果涉及到多線程編程,沒有同步保護的使用全局變量就不安全了,很容易給程序帶來意想不到的問題,所以,在實際的C語言程序開發中,極少使用這種方法。怎樣才能更好的讓C語言函數返回多個值呢?
  • C語言函數的調用 - 百度經驗
    在實際工程項目中,一個程序通常都是由很多個子程序模塊組成的,一個模塊實現一個特定的功能,在 C 語言中,這個模塊就用函數來表示。一個 C 程序一般由一個主函數和若干個其他函數構成。主函數可以調用其它函數,其它函數也可以相互調用,但其它函數不能調用主函數。在我們的 51 單片機程序中,還有中斷服務函數,是當相應的中斷到來後自動調用的,不需要也不能由其它函數來調用。
  • C語言陷阱與技巧第2節,使用inline函數可以提升程序效率,但是讓...
    打開 Linux 內核原始碼,會發現內核在定義C語言函數時,有很多都帶有 「inline」關鍵字,請看下圖,那麼這個關鍵字有什麼作用呢?inline 關鍵字的作用在C語言程序開發中,inline 一般用於定義函數,inline 函數也被稱作「內聯函數」,C99 和 GNU C 均支持內聯函數。那麼在C語言中,內聯函數和普通函數有什麼不同呢?
  • 《Python語言程序設計》學習筆記-函數的定義與使用
    1 函數的理解和定義函數是一段代碼的表示,所指定的參數是一種佔位符,如果不經過調用,不會被執行,參數是輸入、函數體是處理、結果是輸出 (IPO)。函數是一段具有特定功能的、可重用的語句組函數是一種功能的抽象,一般函數表達特定功能兩個作用:降低編程難度和代碼復用2 函數的使用及調用過程函數的調用是運行函數代碼的方式,調用時要給出實際參數,用實際參數替換定義中的參數-,函數調用後得到返回值。3 函數的參數傳遞函數可以有參數,也可以沒有,但必須保留括號。
  • r語言work_r語言work函數 - CSDN
    它包括條件語句、循環語句、用戶自定義的遞歸函數以及輸入輸出接口。(6) R語言是徹底面向對象的統計程式語言。(7) R語言和其它程式語言、資料庫之間有很好的接口。(8) R語言是自由軟體,可以放心大膽地使用,但其功能卻不比任何其它同類軟體差。(9) R語言具有豐富的網上資源一 入門訓練1.
  • C語言中的main函數參數,你了解嗎?
    小豆丁:今天我才發現,C語言中main函數還有參數,可是我不知道這個參數表示的是什麼含義,也不知道怎麼用。老張:就這點問題?小豆丁:嗯吶,我沒研究明白,好沮喪...老張:這個問題不難,別放棄哈,我教你!
  • C語言編程:以實例教你學指向函數的指針
    指針是C語言的精髓,對於初學者來講,指針是C語言語法學習中比較難的知識點,而這裡面指向函數的指針更是不太容易理解。下面給大家講下怎樣學習理解C語言中指向函數的指針及編程方法和使用例子。注意:這是一篇關於C語言編程的基礎語法內容,C語言大神請繞過。
  • 深入理解C語言
    導讀:Dennis Ritchie過世了,他發明了C語言,一個影響深遠並徹底改變世界的計算機語言。一門經歷40多年的到今天還長盛不訓的語言,今天很多語言都受到C的影響,C++,Java,C#,Perl,PHP,Javascript等等。但是,你對C了解嗎?相信你看過本站的《C語言的謎題》還有《誰說C語言很簡單?》。
  • 定義只有一個數組成員的C語言結構體有什麼用?
    方便的數組值傳遞看過我之前文章的讀者應該明白,調用C語言函數時,如果將數組作為參數傳遞給函數,那麼在被調用函數內部,數組常常會退化成指針。而如果將數組封裝在結構體內部,將結構體作為參數,那麼在函數內部,我們依然可以獲得完整的數組,請看下面的C語言代碼示例:#include <stdio.h>typedef struct{char arr[16];}String;void fun(String *str){printf("sizeof
  • Java之可變參數的簡單介紹
    各位小夥伴們大家好,這次小編要介紹的是Java當中的可變參數,什麼是可變參數呢?就是可以變化的參數呀。什麼時候可以用可變參數呢?當方法的參數列表數據類型已經確定,但是參數個數不確定的時候,是可以使用可變參數的,接下來小編要講的是使用格式。
  • Python函數參數的使用方法
    但對函數參數的傳遞沒有詳細討論,本文主要討論函數參數的傳遞。函數中的參數起到了傳遞數據的作用,函數調用者可以通過函數參數把函數內部需要的數據從外部傳遞過去。例如下面的代碼定義了函數summation,它有一個參數number,函數需要這個參數來計算自然數的累加和。調用者調用函數時,需要傳入一個自然數進去。
  • 奇怪的C語言代碼,在變量前加上(void)是什麼操作?有什麼用?
    }fun() 函數中省略掉的代碼沒有使用到 ud 和 size 參數,這裡有兩個問題:一是既然用不到這兩個參數,為什麼不刪去它們呢?再就是兩個參數前的 (void) 類型轉換有什麼用呢?,因為「統一的函數指針」類型是固定的,所以 fun() 函數的原型必須符合該函數指針的原型,所以,即使 fun() 函數用不到 ud 和 size 參數,也是不能將其刪除的,否則就無法通過「統一的函數指針」調用 fun() 函數了。當然了,也有可能純粹是因為開發人員懶得修改 fun() 函數原型。現在明白了第一個問題,再來考慮第二個問題。
  • C語言的一些高級議題
    指針是C語言的靈魂,我們經常聽到這樣的說法,當我們初學C語言的時候,似乎覺得也沒有什麼,但是當你越來越深入的了解它,你就會發現C語言的強大有時甚至超乎你的想像。C語言作為一種相對較為底層的語言,在某些方面有著不可替代的優勢。
  • C語言編程技巧:控制臺程序中自定義函數實現數組內容的特定顯示
    在用C語言編寫算法調試方面的程序中,經常會遇到這種情況,在不同地方需要對處理後的數組內容多次進行顯示,並且很多情況下並非顯示數組裡面的全部內容,而僅僅是想觀察數組中的部分數據內容,若每次顯示時都用printf函數寫的話,未免太過麻煩了。
  • ARM中ADS環境下C語言和彙編語言混合編程及示例
    稍大規模的嵌入式程序設計中,大部分的代碼都是用C來編寫的,主要是因為C語言具有較強的結構性,便於人的理解,並且具有大量的庫支持。但對於一寫硬體上的操作,很多地方還是要用到彙編語言,例如硬體系統的初始化中的CPU 狀態的設定,中斷的使能,主頻的設定,RAM控制參數等。
  • 極限是用函數語言定義的嗎
    數學作為方法論工具從它誕生時就開始了,而我們從小到大隻顧著刷題,除了算數問題與日常生活相關外,其餘只是為了考試,考試結束都還給老師了,把數學與自己的人生脫鉤,沒有再用它來體檢美好的生活和描繪美麗的世界。更可悲的是我們中又去當老師的人,他們仍然視數學為「考試之學」,再去教學生。(教小學生的老師也應該本科畢業,有了豐富的數學知識和思維會更好地把孩子領進數學之門。)
  • 葫蘆絲初學者買哪種葫蘆絲?
    很多葫蘆絲初學者,不知道怎樣選擇第一支葫蘆絲,不知道選擇哪種材質、價位的葫蘆絲。下面從三個方面給大家說說怎樣選擇第一把葫蘆絲:一、調性。成年人選降B調,孩子選小c調(具體原因我在另一篇文章有詳細講到,大家可以關注我並找到相應的文章)。二、材質。選傳統的天然材料(天然竹子、天然葫蘆)製作的葫蘆絲即可。現在製作葫蘆絲的材料通常有傳統的方式(竹子+葫蘆)、木材加工而成(紅木、黑檀木等)、塑膠等。