天天看點

學習C的到此一遊小節

擷取指定長度的字元串,或者說為字元串數組擷取使用者輸入的字元

void get_str(char str[], int len)

{

int i=0, c;

while (i<len)

c = getchar();/*這裡是防止開始之前使用者輸入過回車,我們就跳過不處理*/

if (c == ‘\n‘)

if (!i)

continue;

str[i] = ‘\0‘;

break;

}

str[i++] = c;

if (i == len)

if (c != ‘\n‘)

while (getchar() != ‘\n‘)

;

使用舉例:

char str[11];//結尾儲存’\0’

get_str(str,sizeof(str)-1);

或者使用如下格式

#define NAME_CUT 50

char name[NAME_CUT+1];

get_str(str,NAME_CUT);

2.添加雙引号的宏

#define F(x) _F(x) 

#define _F(x) #x

在這種場景下

char name[NAME_CUT+1]

我們需要列印name,就可以寫成

printf(“%” F(NAME_CUT) ”s\n”,name);

當然真如果出現這種情況,我推薦使用如下宏

#define CUT_FORMAT(x)  _CUT_FORMAT(x)

#define _CUT_FORMAT(x) “%”#x”s”

使用就改為

printf(CUT_FORMAT(NAME_CUT) “\n”,name);

3.推薦的連結清單的聲明格式

typedef

struct node

......

NODE;/*資料類型*/

struct list

NODE data;

struct NODE *next;

LIST;/*連結清單類型*/

使用舉例

#define NUM_CUT 10  /*資料中編号最大長度*/

#define NAME_CUT 20 /*資料中姓名最大字元數*/

struct

char num[NUM_CUT + 1];

char name[NAME_CUT + 1];

char sex;

char age;

STU;/*資料類型*/

typedef 

struct NODE

STU data;

這種寫法非常漂亮,容易擴充

4.簡單的malloc宏,添加了’日志’記錄

#define MALLOC(mp) \

{\

if (NULL == (mp = malloc(sizeof(*mp)))) \

fputs("記憶體配置設定失敗,程式退出......", stderr);\

exit(EXIT_FAILURE); \

}\

假設存在如下環境

STU *stu;

那麼為stu開辟一個空間隻需要添加下面的宏語句.

MALLOC(stu);//顯然它也存在一個C關于malloc的通病,就是記憶體不足時,再列印文本也是無法執行的.但是它的原意是日志記錄,雖然不一定會記錄成功.

再擴充一下MALLOC如下:

#define MALLOC(var) if(!(var=malloc(sizeof(*var)))){\

fputs("記憶體申請失敗,程式退出中...\n",stderr);\

exit(EXIT_FAILURE);\

5.檔案打開的簡單宏,添加了’日志’記錄

#define FOPEN(file,path,type) \

if (NULL == (file = fopen(path, type)))\

fputs("記憶體不足程式退出中", stderr);\

exit(EXIT_SUCCESS);\

舉例

FILE *file;

FOPEN(file,”work_log.rec”,”rb”);

6.在控制台上清除輸入緩存

譬如存在如下語句

int c,num;

printf(“請輸入一個整數:”);

scanf(“%d”,&num);

printf(“請再輸入一個字元:”);

c=getcahr();

在上面的scanf和getchar之間就存在輸入緩存問題,預設getchar()接收的值為’\n’.

這樣c擷取的值不是我們事先預估的

解決辦法就是在二者之間加上

While(getchar()!=’\n’)

擴充一下,更加嚴格的應該是這段代碼

int ch;

While((ch=getchar())!=EOF&&ch!=’\n’)

上面的情況是允許使用者使用退出getchar()的操作,getchar這個函數在接收錯誤的時候傳回-1,其實就是EOF檔案結束标志宏.在window上可以按下Ctrl+Z,Linux上可以按下Ctrl+D達到讓getchar函數傳回-1的效果,結束目前的輸入.

在這裡,我采用的設計思路是,要麼關閉目前程式,要麼就必須輸入完整.關鍵看程式員的想法吧!

7.關于scanf函數擷取使用者輸入值的一種簡便用法

while (printf(......)

,scanf(......)!=......||......)

puts(......);

例如存在如下要求,需要接收使用者輸入的一個數,這個數必須小于250,大于0

代碼如下:

int num;

while(printf(“請輸入一個大于0小于250的整數:”),

scanf(“%d”,&num)!=1||num<=0||num>=250)

while(getchar()!=’\n’)

    puts(“輸入錯誤,請按照提示重新操作!”);

當然有時嫌它代碼重複,可以定義如下簡單宏

#define CLEAR 

while (getchar() != ‘\n‘)/*清除緩存*/

#define CLEARANDMSG {CLEAR;puts("輸入出錯,請按照提示重新操作!");}

使用的話就可以寫成

      CLEARANDMSG;

但是上面的代碼很醜,關鍵看自己怎麼看了.

8.關于printf輸出格式串%*的用法

printf("%*s\n",NAME_CUT,name);

對于這個技巧,是為了避免一些錯誤,例如如果name數組中沒有’\0’,輸出采用%s則會使輸出内容不可控.還有一點就是它也統一控制了姓名列印對齊的格式.

9.比較古老的關于結構體空間聲明技巧

char name[];

NODE;

上面是C99定義包含空數組的結構體,在比較老的時候還存在

char name[1]; 

char name[0]; 

上面兩種模式,思路都一緻.聲明的時候可以寫成

int n=10;

NODE node=malloc(sizeof(NODE)+n);

達到可變數組的目的.(注結構體中 char name[]和char *name,是不同的)

10.進階簡單一點的FOPEN宏的實作

FILE *##file;\

if (NULL == (##file = fopen(path, type)))\

如果想将worker_one.rec檔案内容複制到worker_two.rec檔案中,

建立并打開檔案對象的代碼就是:

FOPEN(worker_one_file,”worker_one.rec”,”rb”);

FOPEN(worker_two_file,”worker_one.rec”,”wb”);

詳細完整的代碼如下:

FOPEN(wo_file,”worker_one.rec”,”rb”);

FOPEN(wt_file,”worker_one.rec”,”wb”);

for(int c;(c=fgetc(wo_file))!=EOF;fputc(c,wt_file))

fclose(wo_file);

fclose(wt_file);

12.比最牛逼的FOPEN宏更牛逼的USING_FILE宏

#define USING_FILE(file,path,type,code) \

##code;\

fclose(##file);\

仍然處理”将worker_one.rec檔案内容複制到worker_two.rec檔案中”,

這個問題,代碼如下

USING_FILE(wo_file,”worker_one.rec”,”rb”,

USING_FILE(wt_file,”worker_two.rec”,”wb”,

});

推薦加上括号美觀(也推薦括号另起一行),是不是有一種函數式程式設計的感覺呢.

哈哈,也許你也被我坑了,其實上面的代碼隻是一廂情願.對于第二個參數,宏隻當

字元串處理.不會再替換了,把它當成宏.正确的做法還需要求助FOPEN這個宏.混搭.

正确的代碼為:

FOPEN(wt_file,”worker_two.rec”,”wb”);

    fclose(wt_file);

這裡也看的出來USING_FILE這個宏使用時不能嵌套,一個優美的雞肋.表達了某個C程式

員的美好想法吧.(宏還存在一個問題,宏參不能太長,否則有的編譯器會出現警告.後導緻錯誤,再扯一下,解決這個錯誤的方法就是将代碼塊封裝為函數.)

13 比牛逼的MALLOC宏更徹底的TYPE_MALLOC宏

#define TYPE_MALLOC(type,var) ;\

##type *##var;\

if(NULL==(##var=malloc(sizeof(##type))))\

fputs("記憶體申請失敗,程式退出中...",stderr);\

如果你想聲明一個int *hoge;

那麼可以寫成 TYPE_MALLOC(int,hoge);

同樣如果你有一個這樣一個結構體

char *name;

如果你想聲明一個結點結構體指針變量,可以寫成

TYPE_MALLOC(NODE,pnode);

對于這個宏适用于第一次聲明指針變量并想配置設定空間的情況.上面的宏不知道有沒有人好奇,為什麼開始有個;.其實這個;是一個空語句.主要是為了解決,這樣的錯誤情況”宏定義不能以##開頭”.當然也可以用其它方式解決這個錯誤,例如添加”{}”,等等.宏很吊,有點像C中的第二種文法,生成代碼的代碼.上面的宏推薦寫成這樣:

#define POINTER_MALLOC(type,var) type *var;\

if(NULL==(var=malloc(sizeof(type)))){\

再次擴充一下,其實下面的宏用法最多:

#define POINTER_MALLOC(type,var,num) type *var;\

if(NULL==(var=malloc(sizeof(type)*num))){\

上面那個宏現在也不是很标準,采用下面這個宏,但是這個宏目前依賴stdio.h和stdlib.h檔案.代碼如下:

if(!(var=malloc(sizeof(type)*(num)))){\

外加一個宏吧:

#define TYPE_MALLOC(type,var) type *var;\

if(!(var=malloc(sizeof(type)))){\

14.簡化scanf函數的宏

#define SAFETY_SCANF(print_code,scanf_code) \

while(printf(##print_code),##scanf_code)\

while(getchar()!=‘\n‘)\

;\

puts("輸入錯誤,請按照提示重新操作!");\

有時上面代碼就是重複勞動,而且不好封裝成函數.例如原先

想寫成這樣代碼:

int mouth;

while(printf(“請輸入購票的月數:”),

scanf(“%d”,mouth)!=1||mouth<1||mouth>12)

puts(“輸入錯誤,請按照提示重新操作!”);

現在使用SAFETY_SCANF宏的寫法就是這樣了

SAFETY_SCANF(“請輸入購票的月數:”

,scanf(“%d”,mouth)!=1||mouth<1||mouth>12);

感覺不好,上面宏應該改寫成這樣

while(##print_code,##scanf_code)\

這樣處理這種情況就容易了:

#define N 10

float score[N];

for(int i=0;i<N;i++)

SAFETY_SCANF(printf(“請輸入第%d個學生總分:”,i+1)

,scanf(“%f”,score+i)!=1||score[i]<0); 

這樣幾乎朗闊了各種情況

再擴充一下,如果覺得printf沒必要,還是可以擴充成如下形式:

#define SAFETY_SCANF(scanf_code,...) \

while(printf(__VA_ARGS__),##scanf_code)\

這樣上面調用的方式就變成:

SAFETY_SCANF(scanf(“%f”,score+i)!=1||score[i]<0,“請輸入第%分:”,i+1); 

再擴充一下,如果想寫成函數如何寫,不好意思,就算用上更進階特性都無法做的比宏好.

再次擴充一下,上面宏改成這樣,會更好.

#define SAFETY_SCANF(scanf_code,...) while(printf(__VA_ARGS__),scanf_code)\

while(getchar()!=‘\n‘);\

puts("輸入出錯,請按照提示重新操作!");\

while(getchar()!=‘\n‘)

這樣的話,更簡單而且也不會給下一次輸入帶來輸入緩存問題.強力推薦最後一個最安全的寫法.(其實上面宏能跑起來,編譯器的貪婪模式幫了很大忙.)實戰的時候推薦這麼寫:

#define SAFETY_SCANF(scanf_code,...) {while(printf(__VA_ARGS__),scanf_code){\

}while(getchar()!=‘\n‘);}

要不再擴充一下,如何用函數來模拟.例如模拟下面這段代碼

int cut = 3;

SAFETY_SCANF(scanf("%d",&mouth)!=1||mouth<1||mouth>12,"請輸入購買的第%d張票的月份:",cut+1);

如果用函數來模拟,需要做的東西有點多.完整的code如下,

#include <stdio.h>

#include <stdarg.h>

#include <stdbool.h>

typedef bool(*judge_scanf)(const char *fmt, ...);

bool judge_mouth(const char *fmt_scf, int *pm);

void safety_scanf(judge_scanf judge, const char *fmt_scf, int *pm, const char *fmt, ...);

int main(void)

safety_scanf(judge_mouth, "%d", &mouth, "請輸入購買的第%d張票的月份:", cut + 1);

return 0;

bool judge_mouth(const char *fmt, int *pm)

return scanf(fmt, pm) != 1 || *pm<1 || *pm>12;

void safety_scanf(judge_scanf judge, const char *fmt_scf, int *pm, const char *fmt, ...)

va_list var;

va_start(var, fmt);

vprintf(fmt, var);

while (judge(fmt_scf, pm))

puts("輸入出錯,請按照提示重新操作!");

va_end(var);

while (getchar()!=‘\n‘)

上面隻是最符合上面情況的模拟,想完全做到,我感覺需要做很多工作.就從這裡打住吧!

15.USING_FILE宏的再次重生,更加屌爆了

#define USING_FILE(file,path,type,code) {\

FILE *file;\

exit(1);\

code;\

fclose(file);\

例如我們需要建立一個output.txt檔案,并寫入”Hello World!”字元,并換行,之後讀取output.txt檔案複制到new_output.txt檔案中.

USING_FILE(file, "output.txt", "wb+", {

fprintf(file,"Hello World!\r\n");

fseek(file, 0l,SEEK_SET);

USING_FILE(new_file, "new_output.txt", "wb", {

int c;

while (EOF != (c = fgetc(file)))

fputc(c, new_file);

什麼都不說了,擴充一下,如果宏中存在#,##,那麼宏就不可以嵌套展開.現在推薦大家采用如上寫法格式,用以差別同正規代碼格式.

16 字元串查找字串,傳回第一次查找到的索引

int str_index(const char *source,const char *target)

const char *cpy, *tar,*st=source;

do

while (*st&&*st != *target)/*先過濾一些完全不比對的字元*/

st++;

for (cpy = st, tar = target; *cpy&&*cpy == *tar; cpy++, tar++)

;/*查找看是否完全比對*/

if (!*tar)

return st - source;

} while (*st++);

return -1;

使用起來比較簡單,簡單代碼如下:

//查找字元串中所有字串出現的位置

const char *s = "I love my wife", *t = " ";

int idx,len=strlen(s),cut=0;

printf("源串[%s]中出現字串[%s]的位置是:",s,t);

do/*下面列印所有空格出現的位置*/

if (-1 == (idx = str_index(s + cut, t)))

cut += idx+1;

printf("%d ", cut - 1);

} while (cut<len);

putchar(‘\n‘);

//這個函數用隻能是湊合着用

17