擷取指定長度的字元串,或者說為字元串數組擷取使用者輸入的字元
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