首页 > 代码库 > 学习C的到此一游小节

学习C的到此一游小节

  1. 获取指定长度的字符串,或者说为字符串数组获取用户输入的字符

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‘)

;

str[i] = ‘\0‘;

}

}

使用举例:

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

使用举例:

在这种场景下

#define NAME_CUT 50

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;/*数据类型*/

typedef

struct list

{

NODE data;

struct NODE *next;

}

LIST;/*链表类型*/

使用举例

#define NUM_CUT 10  /*数据中编号最大长度*/

#define NAME_CUT 20 /*数据中姓名最大字符数*/

typedef

struct

{

char num[NUM_CUT + 1];

char name[NAME_CUT + 1];

char sex;

char age;

}

STU;/*数据类型*/

typedef 

struct NODE

{

STU data;

struct NODE *next;

}

LIST;/*链表类型*/

这种写法非常漂亮,容易扩展

 

4.简单的malloc宏,添加了’日志’记录

#define MALLOC(mp) \

{\

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

{\

fputs("内存分配失败,程序退出......", stderr);\

exit(EXIT_FAILURE); \

}\

}

使用举例

假设存在如下环境

#define NUM_CUT 10  /*数据中编号最大长度*/

#define NAME_CUT 20 /*数据中姓名最大字符数*/

typedef

struct

{

char num[NUM_CUT + 1];

char name[NAME_CUT + 1];

char sex;

char age;

}

STU;/*数据类型*/

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.在控制台上清除输入缓存

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

;

举例

譬如存在如下语句

int c,num;

printf(“请输入一个整数:”);

scanf(“%d”,&num);

printf(“请再输入一个字符:”);

c=getcahr();

在上面的scanfgetchar之间就存在输入缓存问题,默认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(......)!=......||......)

{

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

;

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("输入出错,请按照提示重新操作!");}

使用的话就可以写成

int num;

while(printf(“请输入一个大于0小于250的整数:”),

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

      CLEARANDMSG;

但是上面的代码很丑,关键看自己怎么看了.

 

8.关于printf输出格式串%*的用法

#define NAME_CUT 50

char name[NAME_CUT+1];

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

举例

对于这个技巧,是为了避免一些错误,例如如果name数组中没有’\0’,输出采用%s则会使输出内容不可控.还有一点就是它也统一控制了姓名打印对齐的格式.

 

9.比较古老的关于结构体空间声明技巧

typedef 

struct node

{

int num;

char name[];

}

NODE;

举例

上面是C99定义包含空数组的结构体,在比较老的时候还存在

typedef

struct node

{

int num;

char name[1]; 

}

NODE;

typedef

struct node

{

int num;

char name[0]; 

}

NODE;

上面两种模式,思路都一致.声明的时候可以写成

int n=10;

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

达到可变数组的目的.(注结构体中 char name[]char *name,是不同的)

 

10.高级简单一点的FOPEN宏的实现

#define FOPEN(file,path,type) \

FILE *##file;\

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

{\

fputs("内存不足程序退出中", stderr);\

exit(EXIT_SUCCESS);\

}

举例

如果想将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) \

{\

FILE *##file;\

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

{\

fputs("内存不足程序退出中", stderr);\

exit(EXIT_FAILURE);\

}\

##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”,

{

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

;

});

});

推荐加上括号美观(也推荐括号另起一行),是不是有一种函数式编程的感觉呢.

哈哈,也许你也被我坑了,其实上面的代码只是一厢情愿.对于第二个参数,宏只当

字符串处理.不会再替换了,把它当成宏.正确的做法还需要求助FOPEN这个宏.混搭.

#define FOPEN(file,path,type) \

FILE *##file;\

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

{\

fputs("内存不足程序退出中", stderr);\

exit(EXIT_SUCCESS);\

}

正确的代码为:

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

{

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

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

;

    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);\

exit(EXIT_FAILURE);\

}

举例

如果你想声明一个int *hoge;

那么可以写成 TYPE_MALLOC(int,hoge);

同样如果你有一个这样一个结构体

typedef 

struct node

{

int num;

char *name;

}

NODE;

如果你想声明一个结点结构体指针变量,可以写成

TYPE_MALLOC(NODE,pnode);

对于这个宏适用于第一次声明指针变量并想分配空间的情况.上面的宏不知道有没有人好奇,为什么开始有个;.其实这个;是一个空语句.主要是为了解决,这样的错误情况”宏定义不能以##开头”.当然也可以用其它方式解决这个错误,例如添加”{}”,等等.宏很吊,有点像C中的第二种语法,生成代码的代码.上面的宏推荐写成这样:

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

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

fputs("内存申请失败,程序退出中...",stderr);\

exit(EXIT_FAILURE);\

}

再次扩展一下,其实下面的宏用法最多:

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

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

fputs("内存申请失败,程序退出中...",stderr);\

exit(EXIT_FAILURE);\

}

上面那个宏现在也不是很标准,采用下面这个宏,但是这个宏目前依赖stdio.hstdlib.h文件.代码如下:

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

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

fputs("内存申请失败,程序退出中...\n",stderr);\

exit(EXIT_FAILURE);\

}

外加一个宏吧:

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

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

fputs("内存申请失败,程序退出中...\n",stderr);\

exit(EXIT_FAILURE);\

}

 

 

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)

{

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

;

puts(“输入错误,请按照提示重新操作!”);

}

现在使用SAFETY_SCANF宏的写法就是这样了

SAFETY_SCANF(“请输入购票的月数:

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

感觉不好,上面宏应该改写成这样

#define SAFETY_SCANF(print_code,scanf_code) \

while(##print_code,##scanf_code)\

{\

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

;\

puts("输入错误,请按照提示重新操作!");\

}

这样处理这种情况就容易了:

#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)\

{\

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

;\

puts("输入错误,请按照提示重新操作!");\

}

这样上面调用的方式就变成:

#define N 10

float score[N];

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

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‘);\

puts("输入出错,请按照提示重新操作!");\

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

要不再扩展一下,如何用函数来模拟.例如模拟下面这段代码

int mouth;

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)

{

int mouth;

int cut = 3;

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))

{

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

;

puts("输入出错,请按照提示重新操作!");

va_start(var, fmt);

vprintf(fmt, var);

}

va_end(var);

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

;

}

上面只是最符合上面情况的模拟,想完全做到,我感觉需要做很多工作.就从这里打住吧!

 

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

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

FILE *file;\

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

{\

fputs("内存不足程序退出中", stderr);\

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);

});

});

return 0;

什么都不说了,扩展一下,如果宏中存在#,##,那么宏就不可以嵌套展开.现在推荐大家采用如上写法格式,用以区别同正规代码格式.

 

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)))

break;

cut += idx+1;

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

} while (cut<len);

putchar(‘\n‘);

//这个函数用只能是凑合着用

17

举例