printf家族探祕

有一個函數,是咱們從學習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支持各類格式,因此它使用是最頻繁的,如上面例子呈現的那樣。

返回值

返回寫入buffer 的字符數,出錯則返回負數。
sprintf 返回以format爲格式argument爲內容組成的結果被寫入buffer 的字節數,結束字符‘\0’不計入內。即,若是「Hello」被寫入空間足夠大的buffer後,函數sprintf 返回5

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

相關文章
相關標籤/搜索