C語言函式操作大全----(超詳細)

An_angel_of_joy發表於2015-05-12
fopen(開啟檔案)
相關函式 open,fclose
表標頭檔案 #include<stdio.h>
定義函式 FILE * fopen(const char * path,const char * mode);
函式說明 引數path字串包含欲開啟的檔案路徑及檔名,引數mode字串則代表著流形態。
mode有下列幾種形態字串:
r 開啟只讀檔案,該檔案必須存在。
r+ 開啟可讀寫的檔案,該檔案必須存在。
w 開啟只寫檔案,若檔案存在則檔案長度清為0,即該檔案內容會消失。若檔案不存在則建立該檔案。
w+ 開啟可讀寫檔案,若檔案存在則檔案長度清為零,即該檔案內容會消失。若檔案不存在則建立該檔案。
a 以附加的方式開啟只寫檔案。若檔案不存在,則會建立該檔案,如果檔案存在,寫入的資料會被加到檔案尾,即檔案原先的內容會被保留。
a+ 以附加方式開啟可讀寫的檔案。若檔案不存在,則會建立該檔案,如果檔案存在,寫入的資料會被加到檔案尾後,即檔案原先的內容會被保留。
複製程式碼 程式碼如下:

r      Open text file for reading.  The stream is positioned at the beginning of the file.
r+     Open for reading and writing.  The stream is positioned at the beginning of the file.
w      Truncate file to zero length or create text file for writing.  The stream is positioned at the beginning of the file.
w+     Open for reading and writing.  The file is created if it does not exist, otherwise it is truncated.  The  stream  is  posi‐
       tioned at the beginning of the file.
a      Open  for  appending  (writing at end of file).  The file is created if it does not exist.  The stream is positioned at the
       end of the file.
a+     Open for reading and appending (writing at end of file).  The file is created if it does not exist.  The initial file posi‐
       tion for reading is at the beginning of the file, but output is always appended to the end of the file.

上述的形態字串都可以再加一個b字元,如rb、w+b或ab+等組合,加入b 字元用來告訴函式庫開啟的檔案為二進位制檔案,而非純文字檔案。不過在POSIX系統,包含Linux都會忽略該字元。由fopen()所建立的新檔案會具有S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH(0666)許可權,此檔案許可權也會參考umask值。
返回值 檔案順利開啟後,指向該流的檔案指標就會被返回。若果檔案開啟失敗則返回NULL,並把錯誤程式碼存在errno 中。
附加說明 一般而言,開檔案後會作一些檔案讀取或寫入的動作,若開檔案失敗,接下來的讀寫動作也無法順利進行,所以在fopen()後請作錯誤判斷及處理。
範例
複製程式碼 程式碼如下:

#include<stdio.h>
main()
{
FILE * fp;
fp=fopen(“noexist”,”a+”);
if(fp= =NULL) return;
fclose(fp);
}

1. fprintf
功能:傳送格式化輸出到一個檔案中
表標頭檔案:#include<stdio.h>
函式原型:int fprintf(FILE *stream, char *format[, argument,...]);
FILE* 一個FILE型的指標
char* 格式化輸入函式,和printf裡的格式一樣
返回值:成功時返回轉換的位元組數,失敗時返回一個負數
fp = fopen("/local/test.c","a+");
fprintf(fp,"%s\n",str);

2. fscanf
功能:從一個流中執行格式化輸入
表標頭檔案:#include<stdio.h>
函式原型:int fscanf(FILE *stream, char *format[,argument...]);
FILE* 一個FILE型的指標
char* 格式化輸出函式,和scanf裡的格式一樣
返回值:成功時返回轉換的位元組數,失敗時返回一個負數
fp = fopen("/local/test.c","a+");
fscanf(fp,"%s",str);

3. clearerr(清除檔案流的錯誤旗標)
相關函式 feof
表標頭檔案 #include<stdio.h>
定義函式 void clearerr(FILE * stream);
函式說明 clearerr()清除引數stream指定的檔案流所使用的錯誤旗標。
返回值
 
4.fclose(關閉檔案)
相關函式 close,fflush,fopen,setbuf
表標頭檔案 #include<stdio.h>
定義函式 int fclose(FILE * stream);
函式說明 fclose()用來關閉先前fopen()開啟的檔案。此動作會讓緩衝區內的資料寫入檔案中,並釋放系統所提供的檔案資源。
返回值 若關檔案動作成功則返回0,有錯誤發生時則返回EOF並把錯誤程式碼存到errno。
錯誤程式碼 EBADF表示引數stream非已開啟的檔案。
範例 請參考fopen()。
 
5.fdopen(將檔案描述詞轉為檔案指標)
相關函式 fopen,open,fclose
表標頭檔案 #include<stdio.h>
定義函式 FILE * fdopen(int fildes,const char * mode);
函式說明 fdopen()會將引數fildes 的檔案描述詞,轉換為對應的檔案指標後返回。引數mode 字串則代表著檔案指標的流形態,此形態必須和原先檔案描述詞讀寫模式相同。關於mode 字串格式請參考fopen()。
返回值 轉換成功時返回指向該流的檔案指標。失敗則返回NULL,並把錯誤程式碼存在errno中。
範例
複製程式碼 程式碼如下:

#include<stdio.h>
main()
{
FILE * fp =fdopen(0,”w+”);
fprintf(fp,”%s/n”,”hello!”);
fclose(fp);
}
執行 hello!

6.feof(檢查檔案流是否讀到了檔案尾)
相關函式 fopen,fgetc,fgets,fread
表標頭檔案 #include<stdio.h>
定義函式 int feof(FILE * stream);
函式說明 feof()用來偵測是否讀取到了檔案尾,尾數stream為fopen()所返回之檔案指標。如果已到檔案尾則返回非零值,其他情況返回0。
返回值 返回非零值代表已到達檔案尾。
 
7.fflush(更新緩衝區)
相關函式 write,fopen,fclose,setbuf
表標頭檔案 #include<stdio.h>
定義函式 int fflush(FILE* stream);
函式說明 fflush()會強迫將緩衝區內的資料寫回引數stream指定的檔案中。如果引數stream為NULL,fflush()會將所有開啟的檔案資料更新。
返回值 成功返回0,失敗返回EOF,錯誤程式碼存於errno中。
錯誤程式碼 EBADF 引數stream 指定的檔案未被開啟,或開啟狀態為只讀。其它錯誤程式碼參考write()。
 
8.fgetc(由檔案中讀取一個字元)
相關函式 open,fread,fscanf,getc
表標頭檔案 include<stdio.h>
定義函式 nt fgetc(FILE * stream);
函式說明 fgetc()從引數stream所指的檔案中讀取一個字元。若讀到檔案尾而無資料時便返回EOF。
返回值 getc()會返回讀取到的字元,若返回EOF則表示到了檔案尾。
範例
複製程式碼 程式碼如下:

#include<stdio.h>
main()
{
FILE *fp;
int c;
fp=fopen(“exist”,”r”);
while((c=fgetc(fp))!=EOF)
printf(“%c”,c);
fclose(fp);
}

9.fgets(由檔案中讀取一字串)
相關函式 open,fread,fscanf,getc
表標頭檔案 include<stdio.h>
定義函式 har * fgets(char * s,int size,FILE * stream);
函式說明 fgets()用來從引數stream所指的檔案內讀入字元並存到引數s所指的記憶體空間,直到出現換行字元、讀到檔案尾或是已讀了size-1個字元為止,最後會加上NULL作為字串結束。
返回值 gets()若成功則返回s指標,返回NULL則表示有錯誤發生。
範例
複製程式碼 程式碼如下:

#include<stdio.h>
main()
{
char s[80];
fputs(fgets(s,80,stdin),stdout);
}
執行 this is a test /*輸入*/
this is a test /*輸出*/

10.fileno(返回檔案流所使用的檔案描述詞)
相關函式 open,fopen
表標頭檔案 #include<stdio.h>
定義函式 int fileno(FILE * stream);
函式說明 fileno()用來取得引數stream指定的檔案流所使用的檔案描述詞。
返回值 返回檔案描述詞。
範例
複製程式碼 程式碼如下:

#include<stdio.h>
main()
{
FILE * fp;
int fd;
fp=fopen(“/etc/passwd”,”r”);
fd=fileno(fp);
printf(“fd=%d/n”,fd);
fclose(fp);
}
執行 fd=3

12.fputc(將一指定字元寫入檔案流中)
相關函式 fopen,fwrite,fscanf,putc
表標頭檔案 #include<stdio.h>
定義函式 int fputc(int c,FILE * stream);
函式說明 fputc 會將引數c 轉為unsigned char 後寫入引數stream 指定的檔案中。
返回值 fputc()會返回寫入成功的字元,即引數c。若返回EOF則代表寫入失敗。
範例
複製程式碼 程式碼如下:

#include<stdio.h>
main()
{
FILE * fp;
char a[26]=”abcdefghijklmnopqrstuvwxyz”;
int i;
fp= fopen(“noexist”,”w”);
for(i=0;i<26;i++)
fputc(a,fp);
fclose(fp);
}

13.fputs(將一指定的字串寫入檔案內)
相關函式 fopen,fwrite,fscanf,fputc,putc
表標頭檔案 #include<stdio.h>
定義函式 int fputs(const char * s,FILE * stream);
函式說明 fputs()用來將引數s所指的字串寫入到引數stream所指的檔案內。
返回值 若成功則返回寫出的字元個數,返回EOF則表示有錯誤發生。
範例 請參考fgets()。
fread(從檔案流讀取資料)
相關函式 fopen,fwrite,fseek,fscanf
表標頭檔案 #include<stdio.h>
定義函式 size_t fread(void * ptr,size_t size,size_t nmemb,FILE * stream);
函式說明 fread()用來從檔案流中讀取資料。引數stream為已開啟的檔案指標,引數ptr 指向欲存放讀取進來的資料空間,讀取的字元數以引數size*nmemb來決定。Fread()會返回實際讀取到的nmemb數目,如果此值比引數nmemb 來得小,則代表可能讀到了檔案尾或有錯誤發生,這時必須用feof()或ferror()來決定發生什麼情況。
返回值 返回實際讀取到的nmemb數目。
附加說明
範例
複製程式碼 程式碼如下:

#include<stdio.h>
#define nmemb 3
struct test
{
char name[20];
int size;
}s[nmemb];
int main(){
FILE * stream;
int i;
stream = fopen(“/tmp/fwrite”,”r”);
fread(s,sizeof(struct test),nmemb,stream);
fclose(stream);
for(i=0;i<nmemb;i++)
printf(“name[%d]=%-20s:size[%d]=%d/n”,i,s.name,i,s.size);
}
執行
name[0]=Linux! size[0]=6
name[1]=FreeBSD! size[1]=8
name[2]=Windows2000 size[2]=11

14.freopen(開啟檔案)
相關函式 fopen,fclose
表標頭檔案 #include<stdio.h>
定義函式 FILE * freopen(const char * path,const char * mode,FILE * stream);
函式說明 引數path字串包含欲開啟的檔案路徑及檔名,引數mode請參考fopen()說明。引數stream為已開啟的檔案指標。Freopen()會將原stream所開啟的檔案流關閉,然後開啟引數path的檔案。
返回值 檔案順利開啟後,指向該流的檔案指標就會被返回。如果檔案開啟失敗則返回NULL,並把錯誤程式碼存在errno 中。
範例
複製程式碼 程式碼如下:

#include<stdio.h>
main()
{
FILE * fp;
fp=fopen(“/etc/passwd”,”r”);
fp=freopen(“/etc/group”,”r”,fp);
fclose(fp);
}

15.fseek(移動檔案流的讀寫位置)
相關函式 rewind,ftell,fgetpos,fsetpos,lseek
表標頭檔案 #include<stdio.h>
定義函式 int fseek(FILE * stream,long offset,int whence);
函式說明 fseek()用來移動檔案流的讀寫位置。引數stream為已開啟的檔案指標,引數offset為根據引數whence來移動讀寫位置的位移數。
引數 whence為下列其中一種:
SEEK_SET從距檔案開頭offset位移量為新的讀寫位置。SEEK_CUR 以目前的讀寫位置往後增加offset個位移量。
SEEK_END將讀寫位置指向檔案尾後再增加offset個位移量。
當whence值為SEEK_CUR 或SEEK_END時,引數offset允許負值的出現。
下列是較特別的使用方式:
1) 欲將讀寫位置移動到檔案開頭時:fseek(FILE *stream,0,SEEK_SET);
2) 欲將讀寫位置移動到檔案尾時:fseek(FILE *stream,0,0SEEK_END);
返回值 當呼叫成功時則返回0,若有錯誤則返回-1,errno會存放錯誤程式碼。
附加說明 fseek()不像lseek()會返回讀寫位置,因此必須使用ftell()來取得目前讀寫的位置。
範例
複製程式碼 程式碼如下:

#include<stdio.h>
main()
{
FILE * stream;
long offset;
fpos_t pos;
stream=fopen(“/etc/passwd”,”r”);
fseek(stream,5,SEEK_SET);
printf(“offset=%d/n”,ftell(stream));
rewind(stream);
fgetpos(stream,&pos);
printf(“offset=%d/n”,pos);
pos=10;
fsetpos(stream,&pos);
printf(“offset = %d/n”,ftell(stream));
fclose(stream);
}
執行 offset = 5
offset =0
offset=10

16.ftell(取得檔案流的讀取位置)
相關函式 fseek,rewind,fgetpos,fsetpos
表標頭檔案 #include<stdio.h>
定義函式 long ftell(FILE * stream);
函式說明 ftell()用來取得檔案流目前的讀寫位置。引數stream為已開啟的檔案指標。
返回值 當呼叫成功時則返回目前的讀寫位置,若有錯誤則返回-1,errno會存放錯誤程式碼。
錯誤程式碼 EBADF 引數stream無效或可移動讀寫位置的檔案流。
範例 參考fseek()。
 
17.fwrite(將資料寫至檔案流)
相關函式 fopen,fread,fseek,fscanf
表標頭檔案 #include<stdio.h>
定義函式 size_t fwrite(const void * ptr,size_t size,size_t nmemb,FILE * stream);
函式說明 fwrite()用來將資料寫入檔案流中。引數stream為已開啟的檔案指標,引數ptr 指向欲寫入的資料地址,總共寫入的字元數以引數size*nmemb來決定。Fwrite()會返回實際寫入的nmemb數目。
返回值 返回實際寫入的nmemb數目。
範例
複製程式碼 程式碼如下:

#include<stdio.h>
#define set_s (x,y) {strcoy(s[x].name,y);s[x].size=strlen(y);}
#define nmemb 3
struct test
{
char name[20];
int size;
}s[nmemb];
main()
{
FILE * stream;
set_s(0,”Linux!”);
set_s(1,”FreeBSD!”);
set_s(2,”Windows2000.”);
stream=fopen(“/tmp/fwrite”,”w”);
fwrite(s,sizeof(struct test),nmemb,stream);
fclose(stream);
}
執行 參考fread()。

18.getc(由檔案中讀取一個字元)
相關函式 read,fopen,fread,fgetc
表標頭檔案 #include<stdio.h>
定義函式 int getc(FILE * stream);
函式說明 getc()用來從引數stream所指的檔案中讀取一個字元。若讀到檔案尾而無資料時便返回EOF。雖然getc()與fgetc()作用相同,但getc()為巨集定義,非真正的函式呼叫。
返回值 getc()會返回讀取到的字元,若返回EOF則表示到了檔案尾。
範例 參考fgetc()。
 
19.getchar(由標準輸入裝置內讀進一字元)
相關函式 fopen,fread,fscanf,getc
表標頭檔案 #include<stdio.h>
定義函式 int getchar(void);
函式說明 getchar()用來從標準輸入裝置中讀取一個字元。然後將該字元從unsigned char轉換成int後返回。
返回值 getchar()會返回讀取到的字元,若返回EOF則表示有錯誤發生。
附加說明 getchar()非真正函式,而是getc(stdin)巨集定義。
範例
複製程式碼 程式碼如下:

#include<stdio.h>
main()
{
FILE * fp;
int c,i;
for(i=0li<5;i++)
{
c=getchar();
putchar(c);
}
}
執行 1234 /*輸入*/
1234 /*輸出*/

20.gets(由標準輸入裝置內讀進一字串)
相關函式 fopen,fread,fscanf,fgets
表標頭檔案 #include<stdio.h>
定義函式 char * gets(char *s);
函式說明 gets()用來從標準裝置讀入字元並存到引數s所指的記憶體空間,直到出現換行字元或讀到檔案尾為止,最後加上NULL作為字串結束。
返回值 gets()若成功則返回s指標,返回NULL則表示有錯誤發生。
附加說明 由於gets()無法知道字串s的大小,必須遇到換行字元或檔案尾才會結束輸入,因此容易造成緩衝溢位的安全性問題。建議使用fgets()取代。
範例 參考fgets()
 
21.mktemp(產生唯一的臨時檔名)
相關函式 tmpfile
表標頭檔案 #include<stdlib.h>
定義函式 char * mktemp(char * template);
函式說明 mktemp()用來產生唯一的臨時檔名。引數template所指的檔名稱字串中最後六個字元必須是XXXXXX。產生後的檔名會借字串指標返回。
返回值 檔案順利開啟後,指向該流的檔案指標就會被返回。如果檔案開啟失敗則返回NULL,並把錯誤程式碼存在errno中。
附加說明 引數template所指的檔名稱字串必須宣告為陣列,如:
char template[ ]=”template-XXXXXX”;
不可用char * template=”template-XXXXXX”;
範例
複製程式碼 程式碼如下:

#include<stdlib.h>
main()
{
char template[ ]=”template-XXXXXX”;
mktemp(template);
printf(“template=%s/n”,template);
}

22.putc(將一指定字元寫入檔案中)
相關函式 fopen,fwrite,fscanf,fputc
表標頭檔案 #include<stdio.h>
定義函式 int putc(int c,FILE * stream);
函式說明 putc()會將引數c轉為unsigned char後寫入引數stream指定的檔案中。雖然putc()與fputc()作用相同,但putc()為巨集定義,非真正的函式呼叫。
返回值 putc()會返回寫入成功的字元,即引數c。若返回EOF則代表寫入失敗。
範例 參考fputc()。
 
23.putchar(將指定的字元寫到標準輸出裝置)
相關函式 fopen,fwrite,fscanf,fputc
表標頭檔案 #include<stdio.h>
定義函式 int putchar (int c);
函式說明 putchar()用來將引數c字元寫到標準輸出裝置。
返回值 putchar()會返回輸出成功的字元,即引數c。若返回EOF則代表輸出失敗。
附加說明 putchar()非真正函式,而是putc(c,stdout)巨集定義。
範例 參考getchar()。
 
24.rewind(重設檔案流的讀寫位置為檔案開頭)
相關函式 fseek,ftell,fgetpos,fsetpos
表標頭檔案 #include<stdio.h>
定義函式 void rewind(FILE * stream);
函式說明 rewind()用來把檔案流的讀寫位置移至檔案開頭。引數stream為已開啟的檔案指標。此函式相當於呼叫fseek(stream,0,SEEK_SET)。
返回值
範例 參考fseek()

25.setbuf(設定檔案流的緩衝區)
相關函式 setbuffer,setlinebuf,setvbuf
表標頭檔案 #include<stdio.h>
定義函式 void setbuf(FILE * stream,char * buf);
函式說明 在開啟檔案流後,讀取內容之前,呼叫setbuf()可以用來設定檔案流的緩衝區。引數stream為指定的檔案流,引數buf指向自定的緩衝區起始地址。如果引數buf為NULL指標,則為無緩衝IO。Setbuf()相當於呼叫:setvbuf(stream,buf,buf?_IOFBF:_IONBF,BUFSIZ)
返回值
 
26.setbuffer(設定檔案流的緩衝區)
相關函式 setlinebuf,setbuf,setvbuf
表標頭檔案 #include<stdio.h>
定義函式 void setbuffer(FILE * stream,char * buf,size_t size);
函式說明 在開啟檔案流後,讀取內容之前,呼叫setbuffer()可用來設定檔案流的緩衝區。引數stream為指定的檔案流,引數buf指向自定的緩衝區起始地址,引數size為緩衝區大小。
返回值

27.setlinebuf(設定檔案流為線性緩衝區)
相關函式 setbuffer,setbuf,setvbuf
表標頭檔案 #include<stdio.h>
定義函式 void setlinebuf(FILE * stream);
函式說明 setlinebuf()用來設定檔案流以換行為依據的無緩衝IO。相當於呼叫:setvbuf(stream,(char * )NULL,_IOLBF,0);請參考setvbuf()。
返回值

28.setvbuf(設定檔案流的緩衝區)
相關函式 setbuffer,setlinebuf,setbuf
表標頭檔案 #include<stdio.h>
定義函式 int setvbuf(FILE * stream,char * buf,int mode,size_t size);
函式說明 在開啟檔案流後,讀取內容之前,呼叫setvbuf()可以用來設定檔案流的緩衝區。引數stream為指定的檔案流,引數buf指向自定的緩衝區起始地址,引數size為緩衝區大小,引數mode有下列幾種
_IONBF 無緩衝IO
_IOLBF 以換行為依據的無緩衝IO
_IOFBF 完全無緩衝IO。如果引數buf為NULL指標,則為無緩衝IO。
返回值

29.ungetc(將指定字元寫回檔案流中)
相關函式 fputc,getchar,getc
表標頭檔案 #include<stdio.h>
定義函式 int ungetc(int c,FILE * stream);
函式說明 ungetc()將引數c字元寫回引數stream所指定的檔案流。這個寫回的字元會由下一個讀取檔案流的函式取得。
返回值 成功則返回c 字元,若有錯誤則返回EOF。
複製程式碼 程式碼如下:

#include <stdio.h>
#include <stdlib.h>
int main()
{
     FILE *fp = NULL;
     char* str;
     char re;
     int num = 10;
     str = (char*)malloc(100);
     //snprintf(str, 10,"test: %s", "0123456789012345678");
    // printf("str=%s\n", str);
     fp = fopen("/local/test.c","a+");
     if (fp==NULL){
        printf("Fail to open file\n");
     }
//     fseek(fp,-1,SEEK_END);
     num = ftell(fp);
     printf("test file long:%d\n",num);
     fscanf(fp,"%s",str);
     printf("str = %s\n",str);
     printf("test a: %s\n",str);
     while ((re=getc(fp))!=EOF){//getc可以用作fgetc用
        printf("%c",re);
     }
     //fread(str,10,10,fp);
     fgets(str,100,fp);
     printf("test a: %s\n",str);
     sprintf(str,"xiewei test is:%s", "ABCDEFGHIGKMNI");
     printf("str2=%s\n", str);
   //  fprintf(fp,"%s\n",str);
     fwrite(str,2,10,fp);
     num = ftell(fp);
     if(str!=NULL){
        free(str);
     }
     fclose(fp);
     return 0;
}


基於typedef的用法詳解

也許新手用這個關鍵字不多,但它卻是一個很有用的關鍵字,可以使程式碼模組化程度更好(即與其它程式碼的關聯較少),在C++中還是實現Traits技術的基礎,也是模板程式設計的基本語法之一。

若說變數定義是為變數命名,而typedef(或稱為型別定義)就是為型別命名。既然都是命名,那就會有很多類似的地方。而變數定義我想大家都會使用,因此型別定義也必然會使用。

型別定義的語法可以歸結為一句話:只要在變數定義前面加上typedef,就成了型別定義。這兒的原本應該是變數的東西,就成為了型別。
如,下面的變數定義:
int integer;     //整型變數
int *pointer;   //整型指標變數
int array [5]; //整型陣列變數
int *p_array [5]; //整型指標的陣列的變數
int (*array_pointer) [5];//整型陣列的指標的變數
int function (int param);//函式定義,也可將函式名看作函式的變數
int *function (int param);//仍然是函式,但返回值是整型指標
int (*function) (int param);//現在就是指向函式的指標了
若要定義相應型別,即為型別來起名字,就是下面的形式:
typedef int integer_t;                      //整型型別
typedef int *pointer_t;     //整型指標型別
typedef int array_t [5]; //整型陣列型別
typedef int *p_array_t [5];    //整型指標的陣列的型別
typedef int (*array_pointer_t) [5]; //整型陣列的指標的型別
typedef int function_t (int param);     //函式型別
typedef int *function_t (int param);    //函式型別
typedef int (*function_t) (int param); //指向函式的指標的型別
注意:上面的函式型別在C中可能會出錯,因為C中並沒有函式型別,它的函式變數會自動退化成函式指標;在C++中好像是可以的。在這裡主要說明的是形式上的相似性.
 
typedef的一般形式為:
typedef   型別     定義名;
在程式設計中使用typedef目的一般有兩個,一個是給變數一個易記且意義明確的新名字,另一個是簡化一些比較複雜的型別宣告。
其實,在C語言中宣告變數的時候,有個儲存型別指示符(storage-class-specifier),它包括我們熟悉的extern、static、auto、register。在不指定儲存型別指示符的時候,編譯器會根據約定自動取預設值。另外,儲存型別指示符的位置也是任意的(但要求在變數名和指標*之前),也就是說以下幾行程式碼是等價的:
static const int i;
const static int i;
int const static i;
const int static i;
根據C語言規範,在進行句法分析的時候,typedef和儲存型別指示符是等價的!所以,我們把上述使用static的地方替換為typedef:
typedef const int i;
const typedef int i;
int const typedef i;
const int typedef i;
上述程式碼的語義是:將i定義為一個型別名,其等價的型別為const int。以後如果我們有i   a程式碼,就等價於const int a。對於有指標的地方也是一樣的,比如:
int const typedef *t;那麼程式碼t   p。就相當於int const *p。
另外,typedef不能和static等儲存型別指示符同時使用,因為每個變數只能有一種儲存型別,所以程式碼:typedef static int i;是非法的。
使用typedef簡化複雜的變數宣告
1)、定義一個有10個指標的陣列,該指標指向一個函式,該函式有一個整形引數,並返回一個整型?
第一種方法:int (*a[10])(int);
第二種方法:typedef int (*pfunc)(int);
             pfunc a[10];
2)、定義一個有10個指標的陣列,該指標指向一個函式,該函式有一個函式指標(不帶引數,返回值為空)引數,並返回空。
第一種方法:void (*a[10])(void (*)(void));
第二種方法:typedef void (*pfuncParam)(void);
               typedef void (*pfunc)(pfuncParam);
pfunc a[10];
3)、一個指向有10個函式指標(不帶引數,返回值為double)陣列的指標
第一種方法:double (*)(void) (*p)[10];
第二種方法:typedef double (*pfunc)(void);
             typedef pfunc (*pfuncParam)[10];
             pfuncParam p;
總結:
typedef有兩種用法:
一、一般形式,定義已有型別的別名
  typedef   型別    定義名;
二、建立一個新的型別
     typedef   返回值型別   新型別名(引數列表);

 

C語言中的回撥函式例項

如果函式A的指標作為函式B的引數,在函式B中利用該指標呼叫函式A,則此時的A就是回撥函式。

在C語言中一般用typedef來為回撥函式定義別名(引數名)。 別名通過巨集定義typedef來實現,不是簡單的巨集替換。可以用作同時宣告指標型的多個物件。

比如:

複製程式碼 程式碼如下:

char *pa,pb;//pa是一個char型指標,但pb是一個char型字元。我們可以這樣來實現
typedef char* PCHAR;
PCHAR pa,pb;//pa和pb都是char型指標


先看一個回撥函式的例子:

複製程式碼 程式碼如下:

#include<stdio.h>

//方法指標的格式為:int (*ptr)(char *p) 即:返回值(指標名)(引數列表)
typedef int (*CallBackFun)(char *p);    //為回撥函式命名,型別命名為 CallBackFun,引數為char *p

//方法 Afun,格式符合 CallBackFun 的格式,因此可以看作是一個 CallBackFun  
int Afun(char *p)
{
    printf("Afun 回撥列印出字元%s!\n", p);  
    return 0;
}

// 方法 Cfun,格式符合 CallBackFun 的格式,因此可以看作是一個 CallBackFun
int Cfun(char *p)
{  
    printf("Cfun 回撥列印:%s, Nice to meet you!\n", p);  
    return 0;
}

// 執行回撥函式,方式一:通過命名方式,pCallBack可以看做是CallBackFun的別名
int call(CallBackFun pCallBack, char *p)
{  
    printf("call 直接列印出字元%s!\n", p);  
    pCallBack(p);  
    return 0;
}

// 執行回撥函式,方式二:直接通過方法指標   
int call2(char *p, int (*ptr)())  //或者是int call2(char *p, int (*ptr)(char *)) 同時ptr可以任意取名
{
    printf("==============\n", p);   
    (*ptr)(p);
}

int main()
{  
    char *p = "hello";
    call(Afun, p);  
    call(Cfun, p);
    call2(p, Afun);  
    call2(p, Cfun);
    return 0;
}
再看一個回撥函式的例子:

#include <stdio.h>
typedef void (*callback)(char *);
void repeat(callback function, char *para)
{
    function(para);
    function(para);
}

void hello(char* a)
{
     printf("Hello %s\n",(const char *)a);
}

void count(char *num)
{
     int i;
     for(i=1;i<(int)num;i++)
          printf("%d",i);
     putchar('\n');
}

int main(void)
{
     repeat(hello,"Huangyi");
     repeat(count, (char *)4);
}


本例中回撥函式的引數按什麼型別解釋由呼叫者(repeat)規定,實現者(hello,count)就是一個void 指標,實現者只負責將這個指標轉交給回撥函式,而不關心它到底指向什麼資料型別。呼叫者知道自己傳的引數是char 型的,那麼在自己提供的回撥函式中就應該知道引數要轉換成char *型來解釋。

 

C語言可變引數函式詳解示例

一般我們程式設計的時候,函式中形式引數的數目通常是確定的,在呼叫時要依次給出與形式引數對應的實際引數。但在某些情況下我們希望函式的引數個數可以根據需要確定,因此c語言引入可變引數函式。典型的可變引數函式的例子有printf()、scanf()等,下面我就開始講解

先看程式碼

複製程式碼 程式碼如下:

printf(“hello,world!”);其引數個數為1個。
printf(“a=%d,b=%s,c=%c”,a,b,c);其引數個數為4個。


如何編寫可變引數函式呢?我們首先來看看printf函式原型是如何定義的。
在linux下,輸入man 3 printf,可以看到prinf函式原型如下:

複製程式碼 程式碼如下:

SYNOPSIS
#include <stdio.h>
int printf(const char *format, ...);


後面的三個點...表示printf引數個數是不定的.
如何實現可變引數函式?
2. 編寫可變函式準備
為了編寫可變引數函式,我們通常需要用到<stdarg.h>標頭檔案下定義的以下函式:

複製程式碼 程式碼如下:

void va_start(va_list ap, last);
type va_arg(va_list ap, type);
void va_end(va_list ap);
void va_copy(va_list dest, va_list src);


其中:
va_list是用於存放引數列表的資料結構。
va_start函式根據初始化last來初始化引數列表。
va_arg函式用於從引數列表中取出一個引數,引數型別由type指定。
va_copy函式用於複製引數列表。
va_end函式執行清理引數列表的工作。
上述函式通常用巨集來實現,例如標準ANSI形式下,這些巨集的定義是:

複製程式碼 程式碼如下:

typedef char * va_list; //字串指標
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )


使用巨集_INTSIZEOF是為了按照整數字節對齊指標,因為c呼叫協議下面,引數入棧都是整數字節(指標或者值)。
函式官方說明,如果你看到英文就煩,可以自行忽略以下說明。
va_start()
       The  va_start() macro initializes ap for subsequent use by va_arg() and
       va_end(), and must be called first.
       The argument last is the name of the last argument before the  variable
       argument list, that is, the last argument of which the calling function
       knows the type.
       Because the address of this argument may  be  used  in  the  va_start()
       macro,  it should not be declared as a register variable, or as a func‐
       tion or an array type.
va_arg()
       The va_arg() macro expands to an expression that has the type and value
       of  the  next  argument in the call.  The argument ap is the va_list ap
       initialized by va_start().  Each call to va_arg() modifies ap  so  that
       the  next  call returns the next argument.  The argument type is a type
       name specified so that the type of a pointer to an object that has  the
       specified type can be obtained simply by adding a * to type.
       The  first use of the va_arg() macro after that of the va_start() macro
       returns the argument after last.   Successive  invocations  return  the
       values of the remaining arguments.
       If  there  is  no  next argument, or if type is not compatible with the
       type of the actual next argument (as promoted according to the  default
       argument promotions), random errors will occur.
       If  ap is passed to a function that uses va_arg(ap,type) then the value
       of ap is undefined after the return of that function.
va_end()
       Each invocation of va_start() must be matched by a corresponding  invo‐
       cation of va_end() in the same function.  After the call va_end(ap) the
       variable ap is undefined.  Multiple traversals of the list, each brack‐
       eted  by va_start() and va_end() are possible.  va_end() may be a macro
       or a function.
GNU給出的一個例項:

複製程式碼 程式碼如下:

#include <stdio.h>
#include <stdarg.h>
void
foo(char *fmt, ...)
{
  va_list ap;
  int d;
  char c, *s;
 va_start(ap, fmt);
 while (*fmt)
     switch (*fmt++) {
     case 's': /* string */
     s = va_arg(ap, char *);
         printf("string %s\n", s);
         break;
     case 'd': /* int */
         d = va_arg(ap, int);
         printf("int %d\n", d);
         break;
     case 'c': /* char */
/* need a cast here since va_arg only takes fully promoted types */
        c = (char) va_arg(ap, int);
        printf("char %c\n", c);
        break;
   }
   va_end(ap);
}


說明:
va_start(ap, fmt);用於根據fmt初始化可變引數列表。
va_arg(ap, char *);用於從引數列表中取出一個引數,其中的char *用於指定所取的引數的型別為字串。每次呼叫va_arg後,引數列表ap都會被更改,以使得下次呼叫時能得到下一個引數。
va_end(ap);用於對引數列表進行一些清理工作。呼叫完va_end後,ap便不再有效。
以上程式給了我們一個實現printf函式的是思路,即:通過呼叫va_start函式,來得到引數列表,然後我們一個個取出引數來進行輸出即可。
3.例項
例如:對於printf(“a=%d,b=%s,c=%c”,a,b,c)語句;fmt的值為a=%d,b=%s,c=%c,呼叫va_start函式將引數a,b,c存入了ap中。注意到:fmt中的%為特殊字元,緊跟%後的引數指明瞭引數型別.
因此我們的簡易printf函式如下:

複製程式碼 程式碼如下:

#include <stdio.h>
#include <stdarg.h>
void
myprintf(char *fmt, ...)
{
  va_list ap;
  int d;
  double f;
  char c;
  char *s;
  char flag;
  va_start(ap,fmt);
  while (*fmt){
   flag=*fmt++;
   if(flag!='%'){
 putchar(flag);
 continue;
  }
  flag=*fmt++;//記得後移一位
    switch (flag)
  {
   case 's':
 s=va_arg(ap,char*);
 printf("%s",s);
 break;
   case 'd': /* int */        
 d = va_arg(ap, int);        
 printf("%d", d);        
 break;    
   case 'f': /* double*/        
 d = va_arg(ap,double);        
 printf("%d", d);        
 break;
   case 'c': /* char*/  
 c = (char)va_arg(ap,int);       
 printf("%c", c);       
 break;
   default:
 putchar(flag);
 break;
  }  
  }
  va_end(ap);
}
int main(){
  char str[10]="linuxcode";
  int i=1024;
  double f=3.1415926;
  char c='V';
  myprintf("string is:%s,int is:%d,double is:%f,char is :%c",str,i,f,c);
}


從上面我們可以知道可變引數函式的編寫,必須要傳入一個引數fmt,用來告訴我們的函式怎樣去確定引數的個數。我們的可變引數函式是通過自己解析這個引數來確定函式引數個數的。
比如,我們編寫一個求和函式,其函式實現如下:

複製程式碼 程式碼如下:

int sum(int cnt,...){
    int sum=0;
int i;
    va_list ap;
    va_start(ap,cnt);
for(i=0;i<cnt;++i)
 sum+=va_arg(ap,int);
    va_end(ap);
return sum;
}


總結一下就是:通過va_start初始化引數列表(也就能得到具體的引數個數了),然後使用va_arg函式從引數列表中取出你想要的引數,最後呼叫va_end執行清理工作。

 

 

使用C語言中的time函式獲取系統時間

 

可以通過time()函式來獲得計算機系統當前的日曆時間(Calendar Time),處理日期時間的函式都是以本函式的返回值為基礎進行運算。其原型為:
time_t time(time_t * t);
如果你已經宣告瞭引數t,你可以從引數t返回現在的日曆時間,同時也可以通過返回值返回現在的日曆時間,即從一個時間點(例如:1970年1月1日0時0分0秒)到現在此時的秒數。如果引數為空(NULL),函式將只通過返回值返回現在的日曆時間,比如下面這個例子用來顯示當前的日曆時間:

複製程式碼 程式碼如下:

#include <SPAN style="FONT-FAMILY: Times New Roman"><stdio.h></SPAN>
int main(void) {
    time_t t;
    t=time(NULL);
    printf("The number of seconds since January 1, 1970 is  %d\n",t);
    return 0;
}


執行的結果與當時的時間有關,我當時執行的結果是: 
The Calendar Time now is 1266637045
其中1266637045就是我執行程式時的日曆時間。即從1970年1月1日0時0分0秒到此時的秒數。
第6行中給time函式的引數設定為NULL,可得到具體的秒數。
可將第6行改寫為以下形式:
time(&t);
變數t中存放當前的日期和時間(相當於函式返回值);

 

C語言中system()函式的用法總結

system()函式功能強大,很多人用卻對它的原理知之甚少先看linux版system函式的原始碼:

複製程式碼 程式碼如下:

#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>

int system(const char * cmdstring)
{
    pid_t pid;
    int status;


    if(cmdstring == NULL){     
         return (1);
    }


    if((pid = fork())<0){
            status = -1;
    }

    else if(pid = 0){
        execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
        -exit(127); //子程式正常執行則不會執行此語句
        }

    else{
           while(waitpid(pid, &status, 0) < 0){
                if(errno != EINTER){
                    status = -1;
                    break;
                }
            }
        }

        return status;
}


分析一下原理估計就能看懂了:  

當system接受的命令為NULL時直接返回,否則fork出一個子程式,因為fork在兩個程式:父程式和子程式中都返回,這裡要檢查返回的pid,fork在子程式中返回0,在父程式中返回子程式的pid,父程式使用waitpid等待子程式結束,子程式則是呼叫execl來啟動一個程式代替自己,execl("/bin/sh", "sh", "-c", cmdstring, (char*)0)是呼叫shell,這個shell的路徑是/bin/sh,後面的字串都是引數,然後子程式就變成了一個shell程式,這個shell的引數是cmdstring,就是system接受的引數。在windows中的shell是command,想必大家很熟悉shell接受命令之後做的事了。

再解釋下fork的原理:當一個程式A呼叫fork時,系統核心建立一個新的程式B,並將A的記憶體映像複製到B的程式空間中,因為A和B是一樣的,那麼他們怎麼知道自己是父程式還是子程式呢,看fork的返回值就知道,上面也說了fork在子程式中返回0,在父程式中返回子程式的pid。

windows中的情況也類似,就是execl換了個又臭又長的名字,引數名也換的看了讓人發暈的,我在MSDN中找到了原型,給大家看看:

複製程式碼 程式碼如下:

HINSTANCE   ShellExecute(
               HWND   hwnd,
               LPCTSTR   lpVerb,
               LPCTSTR   lpFile,
               LPCTSTR   lpParameters,
               LPCTSTR   lpDirectory,
               INT   nShowCmd
   );  


用法見下:
ShellExecute(NULL,   "open",   "c:\\a.reg",   NULL,   NULL,   SW_SHOWNORMAL);  

你也許會奇怪 ShellExecute中有個用來傳遞父程式環境變數的引數 lpDirectory,linux中的execl卻沒有,這是因為execl是編譯器的函式(在一定程度上隱藏具體系統實現),在linux中它會接著產生一個linux系統的呼叫 execve, 原型見下:
int execve(const char * file,const char **argv,const char **envp);

看到這裡就會明白為什麼system()會接受父程式的環境變數,但是用system改變環境變數後,system一返回主函式還是沒變。原因從system的實現可以看到,它是通過產生新程式實現的,從我的分析中可以看到父程式和子程式間沒有程式通訊,子程式自然改變不了父程式的環境變數。

使用了system函式就能執行dos指令。

複製程式碼 程式碼如下:

#include <stdio.h>
#include <stdlib.h>
xiaoyu()
{
char *a;
int n=0;
FILE *f;
f=fopen("file.bat","w+");/*新建一個批處理*/
if(f==NULL)
exit(1);
    a="echo"; /*DOS命令*/
    for(n=65;n<=90;n++)/*大寫A-Z*/
    fprintf(f,"%s %c\n",a,n);/*利用ASCII碼輸出A-Z,寫出批處理*/
    fclose(f);
    system("file.bat");/*執行批處理*/
}
main()
{
    char *string;
    xiaoyu();
    string="echo C語言的system函式\n";/*輸出中文*/
    system(string);
    system("pause");/*程式暫停*/
}


C中可以使用DOS命令,以後程式設計通過呼叫DOS命令很多操作就簡單多了。

 

基於C語言sprintf函式的深入理解

 

printf 可能是許多程式設計師在開始學習C語言時接觸到的 第二個函式(我猜第一個是main),說起來,自然是老朋友了,可是,你對這個老朋友瞭解多嗎?你對它的那個孿生兄弟sprintf瞭解多嗎?在將各種類 型的資料構造成字串時,sprintf的強大功能很少會讓你失望。
由於sprintf跟printf在用法上幾乎一樣,只是列印的目的地不同而已,前者列印到字串中,後者則直接在命令列上輸出。這也導致sprintf比printf有用得多。所以本文著重介紹sprintf,有時也穿插著用用 pritnf。
sprintf是個變參函式,定義如下:
int sprintf( char *buffer, const char *format [, argument] … );
除了前兩個引數型別固定外,後面可以接任意多個引數。而它的精華,顯然就在第二個引數:格式化字串上。
printf 和sprintf都使用格式化字串來指 定串的格式,在格式串內部使用一些以“%”開頭的格式說明符(format specifications)來佔據一個位置,在後邊的變參列表中提供相應的變數,最終函式就會用相應位置的變數來替代那個說明符,產生一個呼叫者想要 的字串。
1. 格式化數字字串
sprintf最常見的應用之一莫過於把整數列印到字串中,所以,spritnf在大多數場合可以替代itoa。如:
//把整數123列印成一個字串儲存在s中。
sprintf(s, “%d”, 123);   //產生“123″
可以指定寬度,不足的左邊補空格:
sprintf(s, “%8d%8d”, 123, 4567); //產生:“    123    4567″
當然也可以左對齊:
sprintf(s, “%-8d%8d”, 123, 4567); //產生:“123         4567″
也可以按照16進位制列印:
sprintf(s, “%8x”, 4567); //小寫16進位制,寬度佔8個位置,右對齊
sprintf(s, “%-8X”, 4568); //大寫16進位制,寬度佔8個位置,左對齊
這樣,一個整數的16進位制字串就很容易得到,但我們在列印16進位制內容時,通常想要一種左邊補0的等寬格式,那該怎麼做呢?很簡單,在表示寬度的數字前面加個0就可以了。
sprintf(s, “%08X”, 4567); //產生:“000011D7″
上面以”%d”進行的10進位制列印同樣也可以使用這種左邊補0的方式。
這裡要注意一個符號擴充套件的問題:比如,假如我們想列印短整數(short)-1的記憶體16進製表示形式,在Win32平臺上,一個 short型佔2個位元組,所以我們自然希望用4個16進位制數字來列印它:
short si = -1;
sprintf(s, “%04X”, si);
產生“FFFFFFFF”,怎麼回事?因為 spritnf是個變參函式,除了前面兩個引數之外,後面的引數都不是型別安全的,函式更沒有辦法僅僅通過一個“%X”就能得知當初函式呼叫前引數壓棧時 被壓進來的到底是個4位元組的整數還是個2位元組的短整數,所以採取了統一4位元組的處理方式,導致引數壓棧時做了符號擴充套件,擴充套件成了32位的整數-1,列印時 4個位置不夠了,就把32位整數-1的8位16進位制都列印出來了。如果你想看si的本來面目,那麼就應該讓編譯器做0擴充套件而不是符號擴充套件(擴充套件時二進位制左 邊補0而不是補符號位):
sprintf(s, “%04X”, (unsigned short)si);
就可以了。或者:
unsigned short si = -1;
sprintf(s, “%04X”, si);
sprintf和printf還可以按8進位制列印整數字符串,使用”%o”。注意8進位制和16進位制都不會列印出負數,都是無符號的,實際上也就是變數的內部編碼的直接的16進位制或8進製表示。
2. 控制浮點數列印格式
浮點數的列印和格式控制是sprintf的又一大常用功能,浮點數使用格式符”%f”控制,預設保留小數點後6位數字,比如:
sprintf(s, “%f”, 3.1415926);    //產生“3.141593″
但有時我們希望自己控制列印的寬度和小數位數,這時就應該使用:”%m.nf”格式,其中m表示列印的寬度,n表示小數點後的位數。比如:
sprintf(s, “%10.3f”, 3.1415626);   //產生:“     3.142″
sprintf(s, “%-10.3f”, 3.1415626); //產生:“3.142     ”
sprintf(s, “%.3f”, 3.1415626); //不指定總寬度,產生:“3.142″
注意一個問題,你猜
int i = 100;
sprintf(s, “%.2f”, i);
會打出什麼東東來?“100.00”?對嗎?自己試試就知道了,同時也試試下面這個:
sprintf(s, “%.2f”, (double)i);
第一個打出來的肯定不是正確結果,原因跟前面提到的一樣,引數壓棧時呼叫者並不知道跟i相對應的格式控制符是個”%f”。而函式執行時函式本身則並不知道當年被壓入棧裡的是個整數,於是可憐的儲存整數i的那4個位元組就被不由分說地強行作為浮點數格式來解釋了,整個亂套了。
不過,如果有人有興趣使用手工編碼一個浮點數,那麼倒可以使用這種方法來檢驗一下你手工編排的結果是否正確。J
字元/Ascii碼對照
我們知道,在C/C++語言中,char也是一種普通 的scalable型別,除了字長之外,它與short,int,long這些型別沒有本質區別,只不過被大家習慣用來表示字元和字串而已。(或許當年 該把這個型別叫做“byte”,然後現在就可以根據實際情況,使用byte或short來把char通過typedef定義出來,這樣更合適些)
於是,使用”%d”或者”%x”列印一個字元,便能得 出它的10進位制或16進位制的ASCII碼;反過來,使用”%c”列印一個整數,便可以看到它所對應的ASCII字元。以下程式段把所有可見字元的 ASCII碼對照表列印到螢幕上(這裡採用printf,注意”#”與”%X”合用時自動為16進位制數增加”0X”字首):
for(int i = 32; i < 127; i++) {
printf(”[ %c ]: %3d 0x%#04X/n”, i, i, i);
}
3. 連線字串
sprintf的格式控制串中既然可以插入各種東西,並最終把它們“連成一串”,自然也就能夠連線字串,從而在許多場合可以替代strcat,但sprintf能夠一次連線多個字串(自然也可以同時在它們中間插入別的內容,總之非常靈活)。比如:
char* who = “I”;
char* whom = “CSDN”;
sprintf(s, “%s love %s.”, who, whom); //產生:“I love CSDN. ”
strcat 只能連線字串(一段以'/0'結尾的字 符陣列或叫做字元緩衝,null-terminated-string),但有時我們有兩段字元緩衝區,他們並不是以'/0'結尾。比如許多從第三方庫函 數中返回的字元陣列,從硬體或者網路傳輸中讀進來的字元流,它們未必每一段字元序列後面都有個相應的'/0'來結尾。如果直接連線,不管是sprintf 還是strcat肯定會導致非法記憶體操作,而strncat也至少要求第一個引數是個null-terminated-string,那該怎麼辦呢?我們 自然會想起前面介紹列印整數和浮點數時可以指定寬度,字串也一樣的。比如:
char a1[] = {'A', ‘B', ‘C', ‘D', ‘E', ‘F', ‘G'};
char a2[] = {'H', ‘I', ‘J', ‘K', ‘L', ‘M', ‘N'};
如果:
sprintf(s, “%s%s”, a1, a2); //Don't do that!
十有八九要出問題了。是否可以改成:
sprintf(s, “%7s%7s”, a1, a2);
也沒好到哪兒去,正確的應該是:
sprintf(s, “%.7s%.7s”, a1, a2);//產生:“ABCDEFGHIJKLMN”
這可以類比列印浮點數的”%m.nf”,在”%m.ns”中,m表示佔用寬度(字串長度不足時補空格,超出了則按照實際寬度列印),n才表示從相應的字串中最多取用的字元數。通常在列印字串時m沒什麼大用,還是點號後面的n用的多。自然,也可以前後都只取部分字元:
sprintf(s, “%.6s%.5s”, a1, a2);//產生:“ABCDEFHIJKL”
在許多時候,我們或許還希望這些格式控制符中用以指定 長度資訊的數字是動態的,而不是靜態指定的,因為許多時候,程式要到執行時才會清楚到底需要取字元陣列中的幾個字元,這種動態的寬度/精度設定功能在 sprintf的實現中也被考慮到了,sprintf採用”*”來佔用一個本來需要一個指定寬度或精度的常數數字的位置,同樣,而實際的寬度或精度就可以 和其它被列印的變數一樣被提供出來,於是,上面的例子可以變成:
sprintf(s, “%.*s%.*s”, 7, a1, 7, a2);
或者:
sprintf(s, “%.*s%.*s”, sizeof(a1), a1, sizeof(a2), a2);
實際上,前面介紹的列印字元、整數、浮點數等都可以動態指定那些常量值,比如:
sprintf(s, “%-*d”, 4, ‘A'); //產生“65 ”
sprintf(s, “%#0*X”, 8, 128);    //產生“0X000080″,“#”產生0X
sprintf(s, “%*.*f”, 10, 2, 3.1415926); //產生“      3.14″
4. 列印地址資訊
有時除錯程式時,我們可能想檢視某些變數或者成員的地址,由於地址或者指標也不過是個32位的數,你完全可以使用列印無符號整數的”%u”把他們列印出來:
sprintf(s, “%u”, &i);
不過通常人們還是喜歡使用16進位制而不是10進位制來顯示一個地址:
sprintf(s, “%08X”, &i);
然而,這些都是間接的方法,對於地址列印,sprintf 提供了專門的”%p”:
sprintf(s, “%p”, &i);
我覺得它實際上就相當於:
sprintf(s, “%0*x”, 2 * sizeof(void *), &i);
5. 利用sprintf的返回值
較少有人注意printf/sprintf函式的返回值,但有時它卻是有用的,spritnf返回了本次函式呼叫最終列印到字元緩衝區中的字元數目。也就是說每當一次sprinf呼叫結束以後,你無須再呼叫一次strlen便已經知道了結果字串的長度。如:
int len = sprintf(s, “%d”, i);
對於正整數來說,len便等於整數i的10進位制位數。
下面的是個完整的例子,產生10個[0, 100)之間的隨機數,並將他們列印到一個字元陣列s中,以逗號分隔開。

複製程式碼 程式碼如下:

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
int main() {
srand(time(0));
char s[64];
int offset = 0;
for(int i = 0; i < 10; i++) {
offset += sprintf(s + offset, “%d,”, rand() % 100);
}
s[offset - 1] = ‘/n';//將最後一個逗號換成換行符。
printf(s);
return 0;
}


設想當你從資料庫中取出一條記錄,然後希望把他們的各 個欄位按照某種規則連線成一個字串時,就可以使用這種方法,從理論上講,他應該比不斷的strcat效率高,因為strcat每次呼叫都需要先找到最後 的那個'/0'的位置,而在上面給出的例子中,我們每次都利用sprintf返回值把這個位置直接記下來了。
6. 使用sprintf的常見問題
sprintf是個變參函式,使用時經常出問題,而且只要出問題通常就是能導致程式崩潰的記憶體訪問錯誤,但好在由sprintf誤用導致的問題雖然嚴重,卻很容易找出,無非就是那麼幾種情況,通常用眼睛再把出錯的程式碼多看幾眼就看出來了。
?? 緩衝區溢位
第一個引數的長度太短了,沒的說,給個大點的地方吧。當然也可能是後面的引數的問題,建議變參對應一定要細心,而列印字串時,儘量使用”%.ns”的形式指定最大字元數。
?? 忘記了第一個引數
低階得不能再低階問題,用printf用得太慣了。//偶就常犯。:。(
?? 變參對應出問題
通常是忘記了提供對應某個格式符的變參,導致以後的引數統統錯位,檢查檢查吧。尤其是對應”*”的那些引數,都提供了嗎?不要把一個整數對應一個”%s”,編譯器會覺得你欺她太甚了(編譯器是obj和exe的媽媽,應該是個女的,:P)。
7. strftime
sprintf還有個不錯的表妹:strftime,專門用於格式化時間字串的,用法跟她表哥很像,也是一大堆格式控制符,只是畢竟小姑娘家心細,她還要呼叫者指定緩衝區的最大長度,可能是為了在出現問題時可以推卸責任吧。這裡舉個例子:

複製程式碼 程式碼如下:

time_t t = time(0);
//產生“YYYY-MM-DD hh:mm:ss”格式的字串。
char s[32];
strftime(s, sizeof(s), “%Y-%m-%d %H:%M:%S”, localtime(&t));


sprintf在MFC中也能找到他的知音:CString::Format,strftime在MFC中自然也有她的同道:CTime::Format,這一對由於從物件導向哪裡得到了贊助,用以寫出的程式碼更覺優雅。
8. 後記
本文介紹的所有這些功能,在MSDN中都可以很容易地查到,筆者只是根據自己的使用經驗,結合一些例子,把一些常用的,有用的,而可能為許多初學者所不知的用法介紹了一點,希望大家不要笑話,也希望大家批評指正。
有人認為這種帶變參的函式會引起各種問題,因而不提倡使用。但筆者本人每每還是抵擋不了它們強大功能的誘惑,在實際工作中一直在使用。實際上,C#.NET 從開始就支援變參,剛釋出不久的Java5.0也支援變參了。
①獲取System時間: void GetSystemTime(LPSYSTEMTIME lpSystemTime); 下面是例子:

複製程式碼 程式碼如下:

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
void main() {
SYSTEMTIME st; //定義存放時間的結構體
char strTime[256];
int n=0;
GetSystemTime(&st);
n = sprintf(strTime,”Year:/t%d/n”,st.wYear);
n += sprintf(strTime+n,”Month:/t%d/n”,st.wMonth);
n += sprintf(strTime+n,”Day:/t%d/n”,st.wDay);
n += sprintf(strTime+n,”Date:/t%d/n”,st.wDayOfWeek);
n += sprintf(strTime+n,”Hour:/t%d/n”,st.wHour);
n += sprintf(strTime+n,”Minute:/t%d/n”,st.wMinute);
n += sprintf(strTime+n,”Second:/t%d/n”,st.wSecond);
n += sprintf(strTime+n,”MilliSecond:/t%d/n”,st.wMilliseconds);
printf(”%s”,strTime);
system(”pause”);
}


 

相關文章