有一個函數,是咱們從學習c語言就開始的第一天就接觸的,那就是printf函數,但是這個家族的函數,帶給咱們的便利卻不是一點半點,因此寫一篇用法總結。linux
1.printf函數編程
格式化輸出,能夠輸出八進制,十進制,十六進制,能夠輸出字符串,%p輸出地址。基本的東西就不在贅述了。數組
printf是有返回值的,只是通常咱們用不到。printf()函數也有一個返回值,它返回所打印的字符的數目。若是有輸出錯誤,那麼printf()會返回一個負數(printf( ) 的一些老版本會有不一樣的返回值)。安全
*號符,在printf函數中有着很強的格式化做用,如同linux中同樣,* 表明任意匹配。函數
再看一個格式化輸出十六進制的例子:學習
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> int main(void) { unsigned int a=0x1; printf("%#010x",a); return 0; }
在編程中常常會打印地址,而通常要求打印8個字節,而字節前導咱們不但願使用空格而是使用前導0來填充,就可使用上面的寫法。編碼
# 號:使用格式說明的可選形式,#o 打印的則以0(零)開始,八進制,#x或者#X,則以0x或者0X開始,十六進制。spa
0(零):對於全部數字格式,用前導零填充而不是空格。若是出現 - 標誌或者指定了精度(對於整數)則忽略該標誌。3d
那麼%#010x:就表示:以十六進制輸出,輸出長度一共爲10,前導用0填充而不是空格。因爲#x的做用佔用了兩個位置,由於要輸出0x,因此還剩下8個位置,這樣就指定輸出了十六進制8個字節長度的數據。指針
若是這樣以爲很差閱讀,能夠不使用#,直接手動書寫0x前綴,指定寬度爲8,前導0便可:如:
這是printf函數的經常使用方法,可是正真重要的,仍是在於可變參數及其家族的變種函數。
2.fprintf函數
專一於文件操做的file printf。文件輸出,實際上是往文件中寫內容,如同printf函數同樣,雖說它是輸出函數,但實質上往輸出流寫數據。fprintf彙集了printf家族的傳統,能夠格式化寫文件:
#include<stdio.h> #include<stdlib.h> int main(void) { float imag[10]; float j=0; FILE *fp; for(int i=0;i<10;i++,j=j+0.2) { imag[i]=1.0+j; } if((fp=fopen("test.txt","w+"))==NULL) { printf("err\n"); fflush(stdout); getchar(); exit(1); } fprintf(fp,"%s","imag[]={"); for(int i=0;i<10;i++) fprintf(fp," %.4f,",imag[i]); fprintf(fp,"%c",'}'); fclose(fp); return 0; }
輸出文件:
fopen一個函數,使用w+的方式表示,打開一個文件,能夠進行更新(讀取和寫入),若是該文件存在,就將其長度變爲0,若是不存在則先建立之。
int fprintf( FILE *stream, const char * restrict format , ...);
按照format格式輸出到stream。其實,在linux學習中,咱們知道一切皆文件,這個FILE指針,是能夠映射到標準輸出的,即:printf(...)=fprintf(stdout,...).
3.sprintf
這個函數用得很是多,也很是重要,務必掌握。
做爲printf家族成員,天然printf自帶的格式化操做它都具備。sprintf主要用於把格式化輸出寫到指定字符數組中。
先看基礎用法:
經過下面的例子瞭解sprintf的原理:
能夠看到,sprintf把十進制數108放在字符數組buf中,可是存放的原理是,將十進制數的每一位變成字符存放在buf數組中,49對應ASCII字符1,48對應0,56對應8.這樣以後,咱們就能夠預估計buf數組給多大空間合適。
sprintf能夠很方便的格式化類型放在數組中,咱們再看一個例子:
如今試想一個問題,咱們常常編程的時候想要實現中文和英文的同時保存,可是,咱們怎麼以除了直接初始化的方式來執行英文和中文的連接呢?舉例說明一下這個問題,咱們想獲得一個字符串,包含中文和英文,例如上面的hi,你好。咱們能夠這樣初始化:char str[40]="hi,你好";可是,若是咱們的中文,須要由其餘地方給出呢?咱們怎麼作到拼接字符串?咱們應該知道,中文所佔字節必定是大於一個字節的,根據文本編碼,對應2-4個字節(這個要注意哦)。好比如今給你兩個字符串,一個是英文的,一個是中文的,你怎麼把它拼接到一塊兒?固然,庫函數提供 了這樣拼接功能的函數,但是,咱們的sprintf也能夠作到,而又由於sprintf支持各類格式,因此它使用是最頻繁的,如上面例子呈現的那樣。
sprintf函數等同於fprintf,除了輸出被寫入一個數組(由參數s指定),而不是一個流。 空字符(\0)寫在寫入的字符的末尾; 它不計入返回值的一部分。 若是複製發生在重疊的對象之間,行爲是未定義的。
那麼咱們試試違規操做呢?
能夠看到,sprintf對溢出是沒有保護的,上面例子,str至少應該4個字節,就由於這樣,纔有了下面的snprintf函數。
3.1 snprintf
這個函數就是在sprintf的基礎上增長了一點內容。
函數snprintf()和vsnprintf()不寫入超出字節(包括終止空字節('\ 0'))。 若是因爲此限制致使輸出被截斷,那麼返回值是若是有足夠的空間可用,則該字符數將被寫入最終字符串(不包括終止空字節)。 所以,返回值大小比指定的n更大或者相等,意味着輸出被截斷。
仍是上代碼來看,由於sprintf會由於使用不當形成內存溢出,而snprintf則不會:
#include <stdio.h> #include <string.h> int main() { char str[5]; int ret = snprintf(str, 3, "%s", "abcdefg"); printf("%d\n", ret); printf("%s\n", str); return 0; }
linux下運行:
vs2017運行:
gcc for Windows:
看來和編譯器有點關係呀,不過咱們學習以c標準的爲基準。
snprintf函數等同於fprintf,除了輸出被寫入一個數組(由參數s指定)而不是一個流。 若是n爲零,則不寫任何內容,
而且s多是空指針。 不然超出n-1的輸出字符丟棄而不是寫入數組(這就是比sprintf安全的地方了),而且空字符寫入實際寫入數組的字符的末尾。 若是複製發生在重疊的對象之間,行爲是未定義的。
snprintf函數返回已寫入的字符數,n已經足夠大,不計算終止空字符,若是發生編碼錯誤返回負值。 所以,\0終止的輸出已經被當且僅當返回的值爲非負數且小於n時才徹底寫入。
見了c標準以後,能夠知道,咱們的gcc for Windows是和vc6.0同樣的輸入,這樣實際上是沒有依照c99標準的,故再也不分析這樣編譯器的輸出。
那麼在如今的基礎上來分析上面的代碼:
指定n=3,輸出最多n-1個,由於snprintf須要給咱們一個結尾\0,故只有ab被寫入,返回值,是不包含結尾\0的應該輸入的大小,這裏是7。這證實了咱們的字符有7個(不包含\0)須要輸出,因此要想所有輸出,咱們應該指定n=8.
snprintf比sprintf安全,有了溢出保護,建議使用。
總結一下就是:snprintf有溢出保護,就算你給超出空間的內容也不會出現溢出錯誤,會被snprintf截斷。輸入的n,表明的是你想要格式化輸出到 s指針指向的空間的內容加上1,通俗一點的意思就是,你想格式化輸出7個字節到 s 指針指向的空間,那麼你的 n就應該指定爲8,由於snprintf只會傳遞n-1個字符過去,它要保留一個結尾\0.它的返回值,若是空間足夠,沒有發生截斷的狀況下,返回寫入字符的個數,不包含結尾\0,若是發生截斷,返回的是應該寫入的長度,不包含結尾\0,因此無論截斷不截斷,返回值的數學值都是相同的,只是表明的意義不一樣,若是寫入發生錯誤,返回負數。
4.vsprintf
#include<stdio.h> #include <stdio.h> #include <stdarg.h> /* 在LCD_printf中應用 ※※※ */ char buffer[30]; int vspfunc(char *format, ...) { va_list aptr; int ret; va_start(aptr, format); ret = vsprintf(buffer, format, aptr); va_end(aptr); return(ret); } int main() { int i = 1115; float f = 27.0; char str[10] = "world"; vspfunc("hello %d %f %s", i, f, str); printf("%s", buffer); return(0); }
4.1 vsnprintf
和snprintf的功能對於sprintf同樣,vsnprintf也是處於這樣的緣由成爲升級版的vsprintf