<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
有時,您可能會碰到這樣的情況,您希望函數帶有可變數量的引數,而不是預定義數量的引數。
C 語言為這種情況提供了一個解決方案,它允許您定義一個函數,能根據具體的需求接受可變數量的引數。
比如我們最常用的printf
函數,它的函數宣告是:int printf(const char *format, ...);
該函數就是一個典型的應用可變引數的範例,後面那三個...
就是說明該函數是可變引數函數。
要使用可變函數,得參照一個標頭檔案#include <stdarg.h>
該檔案提供了實現可變引數功能的函數和宏。
使用可變引數的步驟如下:
1.定義一個函數,最後一個引數為省略號...
,省略號前面可以設定自定義引數(至少得有一個固定引數)。如int getSum(int num, ...)//定義可變引數的函數
2.在函數中定義va_list
型別的變數list
,該型別在stdarg.h
中已定義。
3.使用宏函數va_start
來初始化變數list
,該宏函數在stdarg.h
中已定義。
4.使用宏函數va_arg
和list
來存取參數列中的每個項。
5.使用宏函數va_end
來清理賦予list
變數的記憶體。
宏的宣告
/** brief 初始化 ap 變數,它與 va_arg 和 va_end 宏是一起使用的。 * last_arg 是最後一個傳遞給函數的已知的固定引數,即省略號之前的引數。 * 這個宏必須在使用 va_arg 和 va_end 之前被呼叫。 * * param ap -- 這是一個 va_list 型別的物件, * 它用來儲存通過 va_arg 獲取額外引數時所必需的資訊。 * param last_arg -- 最後一個傳遞給函數的已知的固定引數(省略號前面的那個引數)。 * return 無 * */ void va_start(va_list ap, last_arg) /** brief 檢索函數參數列中型別為 type 的下一個引數。它無法判斷檢索到的引數是否是傳給函數的最後一個引數。 * * param ap -- 這是一個 va_list 型別的物件,儲存了有關額外引數和檢索狀態的資訊。 * 該物件應在第一次呼叫 va_arg 之前通過呼叫 va_start 進行初始化。 * param type -- 這是一個型別名稱。該型別名稱是作為擴充套件自該宏的表示式的型別來使用的。 * return 該宏返回下一個額外的引數,是一個型別為 type 的表示式。 * */ type va_arg(va_list ap, type) /** brief 該宏允許使用了 va_start 宏的帶有可變引數的函數返回(釋放記憶體)。如果在從函數返回之前沒有呼叫 va_end,則結果為未定義。 * * param ap -- 這是之前由同一函數中的 va_start 初始化的 va_list 物件。 * return 無 * */ void va_end(va_list ap)
範例1一個可變引數的函數,求和
#include <stdio.h> #include <stdarg.h>//參照可變引數宏標頭檔案 int getSum(int num, ...)//定義可變引數的函數 { int sum = 0; va_list list;//建立va_list型別的變數 va_start(list, num);//初始化可變引數list for(int i = 0; i < num; i++) { sum += va_arg(list, int);//存取參數列中的每個項 } va_end(list);//釋放記憶體 return sum; } int main() { printf("%dn", getSum(4, 4, 5, 6, 7)); }
範例2輸出字串
#include <stdio.h> #include <stdarg.h>//參照可變引數宏標頭檔案 void func(char *demo, ...) { char *pstr = NULL; va_list list; va_start(list, demo); while(1) { pstr = va_arg(list, char *); if(*pstr == '$')//以 '$' 代表結束 break; printf("%sn", pstr); } va_end(list); } int main() { func("demo", "ABC", "123", "Hello Wolrd!", '$'); }
這裡特別注意一下,宏va_arg
無法判斷檢索到的引數是否是傳給函數的最後一個引數,所以我們需要告訴該引數是不是最後一個引數,有2個方法,一是在使用一個函數引數來說明可變引數的數量,一是定義一個結束標誌符。
可變引數的另外的一種使用方式
#include <stdio.h> int getSum(int num, ...) { int sum = 0; char *p = NULL; p = (char*)# p += 8; for(int i = 0; i < num; i++) { sum += *((int*)p); p += 8; } return sum; } int main() { int a = 1; int b = 2; int c = 3; printf("sum = %dn", getSum(3, a, b, c)); } /* 輸出結果 sum = 6; */
為什麼這樣也可以存取可變引數呢?為什麼指標p要加8呢?
因為這與函數引數的入棧出棧及函數引數的記憶體對齊有關。
首先我們來看函數void func(int a, int b, int c)
各個引數在棧中的位置
c | 高地址 |
---|---|
b | ↓ |
a | 低地址 |
函數引數的傳遞儲存在棧中,從右至左壓入棧中,壓棧過程為遞減;出棧過程為遞增。
所以我們只需要知道a的地址,在a的地址上加上偏移量就可以存取b或者c了。
那應該加上多少偏移量呢?
#include <stdio.h> void func(int a, int b, int c) { printf("a = %pn", &a); printf("b = %pn", &b); printf("c = %pn", &c); } int main() { int a,b,c; func(a, b, c); } /* 輸出結果 a = 000000000061FDF0 b = 000000000061FDF8 c = 000000000061FE00 */
通過上例,發現它們之間相差8,為什麼是8呢?
因為我是在Window64位元上執行的,故需要按照8位元組對齊。
綜上,函數引數的傳遞儲存在棧中,從右至左壓入棧中,壓棧過程為遞減,出棧過程為遞增;並且需要進行記憶體對齊,Window64位元為8位元組對齊,32位元為4位元組對齊。
下面是我做的一些實驗,更改了函數引數型別。
短整型
#include <stdio.h> void func(char a, short b, long long c) { printf("a = %pn", &a); printf("b = %pn", &b); printf("c = %pn", &c); } int main() { char a = 1; short b = 2; long long c = 3; func(a, b, c); } /* 輸出結果 a = 000000000061FDF0 b = 000000000061FDF8 c = 000000000061FE00 */
浮點型
#include <stdio.h> void func(double a, double b, double c) { printf("a = %pn", &a); printf("b = %pn", &b); printf("c = %pn", &c); } int main() { double a = 1; double b = 2; double c = 3; func(a, b, c); } /* 輸出結果 a = 000000000061FDF0 b = 000000000061FDF8 c = 000000000061FE00 */
結構體1
#include <stdio.h> typedef struct { char c[7]; }str_t; void func(str_t a, str_t b, str_t c) { printf("a = %pn", &a); printf("b = %pn", &b); printf("c = %pn", &c); } int main() { str_t a; str_t b; str_t c; func(a, b, c); } /* 輸出結果 a = 000000000061FDF0 b = 000000000061FDE0 c = 000000000061FDD0 */
結構體2
#include <stdio.h> typedef struct { char c[4]; }str_t; void func(str_t a, str_t b, str_t c) { printf("a = %pn", &a); printf("b = %pn", &b); printf("c = %pn", &c); } int main() { str_t a; str_t b; str_t c; func(a, b, c); } /* 輸出結果 a = 000000000061FDF0 b = 000000000061FDF8 c = 000000000061FE00 */
結構體1出問題了,具體沒搞明白,歡迎大佬指導。
建議可變引數使用參照標頭檔案stdarg.h
得方式較好。
本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注it145.com的更多內容!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45