首页 > 代码库 > 学习C的到此一游小节
学习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‘)
;
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();
在上面的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(......)!=......||......)
{
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.h和stdlib.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
举例