1、技巧类
1.1 宏
#include <stdio.h>
#define SWAP(t, a, b) \
do \
{ \
t c = a; \
a = b; \
b = c; \
}while(0)
int main( )
{
int a=0,b=1;
SWAP(int,a,b);
printf("%d,%d\r\n",a,b);
return 0;
}
#if defined(DEBUG_U3)
#define debug u3_printf
#elif defined(DEBUG_U2)
#define debug u2_printf
#elif defined(DEBUG_U1)
#define debug u1_printf
#else
#define debug(...)
#endif
#if defined(CONFIG_XXX)
...
#endif
判断宏的值时,需要提前判断是否被定义!因为未定义的宏值默认为0,如果未定义,则要使用#error来提示编译报错。
typedef unsigned char UINT8;
#define SIZE_FONT_100X100 0
#define SIZE_FONT_32X32 1
#ifndef CONFIG_LCD_HIDE_OFF
#error ERROR CONFIG_LCD_HIDE_OFF Undefined
#endif
#if ( CONFIG_LCD_HIDE_OFF == 0 )
UINT8 MaxFont = SIZE_FONT_100X100;
#else
UINT8 MaxFont = SIZE_FONT_32X32;
#endif
1.2 最小二乘法拟合曲线代码
#define N 1e-13
void analyzeCurve(double *x,double *y,double *a,double*b,double *c,int count)
{
double m1,m2,m3,z1,z2,z3;
double sumx=0,sumx2=0,sumx3=0,sumx4=0,sumy=0,sumxy=0,sumx2y=0;
int i;
*a=*b=*c=0;
z1=z2=z3=999;
for(i=0;i<count;i++)
{
sumx+=x[i];sumy+=y[i];
sumx2+=pow (x[i],2); sumxy+=x[i]*y[i];
sumx3+=pow(x[i],3); sumx2y+=pow(x[i],2)*y[i];
sumx4+=pow(x[i],4);
}
while((z1>N)||(z2>N)||(z3>N))
{
m1=*a; *a=(sumx2y-sumx3*(*b)-sumx2*(*c))/sumx4; z1=(*a-m1)*(*a-m1);
m2=*b; *b=(sumxy-sumx*(*c)-sumx3*(*a))/sumx2; z2=(*b-m2)*(*b-m2);
m3=*c; *c=(sumy-sumx2*(*a)-sumx*(*b))/count; z3=(*c-m3)*(*c-m3);
}
printf (" y=%9.6fx*x+%9.6fx+%9.6f",*a,*b,*c);
}
int main()
{
double x[21]={0.00,20,40,60,80};
double y[21]={0.00,23,60,77.69,79.64};
double a,b,c ;
analyzeCurve(x,y,&a,&b,&c,5);
return 0;
}
1.3 产生秒级或毫秒级随机数
1.3.1 秒级随机数
为了使用rand() 产生秒级随机数,一般会先调用srand((unsigned)time(NULL)); 以利用当前时间(1970年1月1日以来走过的秒数)作为随机数的种子。所以,如果你的程序不是连续执行的那种(不在1秒内可以执行多次),则可以得到数值不同的随机值。但若在1秒内执行多次rand()函数,则这几次的随机值将会是一样的,因为同一秒的随机值种子是一样的。
#include <stdlib.h>
#include <stdio.h>
#include <stdio.h>
#include <time.h>
int main(void)
{
int buf[10],i,j;
srand((unsigned)time(NULL));
for(i=0; i<10; i++)
{
buf[i]=rand()%100;
printf("%d ",buf[i]);
}
printf("\n");
return 0;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mu6KBsJu-1628341466015)(C:\Users\liang\AppData\Roaming\Typora\typora-user-images\image-20210807105312107.png)]
1.3.2 豪秒级随机数
方法:获取以毫秒为单位的时间值作为随机函数种子。即利用ftime() 函数获取timeb 结构体,其里面的时间变量包含秒和毫秒:
struct timeb{
time_t time;
unsigned short millitm;
short timezonel;
short dstflag;
}
具体实现代码,参考如下:
#include <stdlib.h>
#include <stdio.h>
#include <stdio.h>
#include <time.h>
#include <sys/timeb.h>
int main(void)
{
int buf[10],i,j;
struct timeb timer;
ftime(&timer);
srand(timer.time * 1000 + timer.millitm);
for(i=0; i<10; i++)
{
buf[i]=rand()%100;
printf("%d ",buf[i]);
}
printf("\n");
return 0;
}
1.4 定时器的使用
#include <stdlib.h>
#include <signal.h>
static struct itimerval oldtv;
struct itimerval itv;
void set_timer()
{
itv.it_interval.tv_sec = 0;
itv.it_interval.tv_usec = 990000;
itv.it_value.tv_sec = 0;
itv.it_value.tv_usec = 50000;
setitimer(ITIMER_REAL, &itv, &oldtv);
}
void shut_timer()
{
itv.it_value.tv_sec = 0;
itv.it_value.tv_usec = 0;
setitimer(ITIMER_REAL, &itv, &oldtv);
}
void signal_handler(void)
{
...
}
int main()
{
signal(SIGALRM, signal_handler);
set_timer();
while(1);
return 0;
}
1.5 位的使用
做低层时,经常会读写寄存器,比如操作某位,设置为0或1,而在C语言中便为我们提供一种数据结构”位域”,使得我们通过读写”位域”来实现操作某位:
#include <stdio.h>
struct {
unsigned mode:8;
unsigned en:1;
unsigned reserved:1;
unsigned clk_select:4;
unsigned ch_select:3;
}reg11;
int main()
{
reg11.en =1;
printf("reg11=%d\n",reg11);
reg11.mode =50;
printf("reg11=%d\n",reg11);
return 0;
}
#include <stdio.h>
typedef union{
unsigned char val;
struct {
unsigned a:4;
unsigned b:1;
unsigned c:2;
unsigned d:1;
}bit;
}reg11;
struct reg{
unsigned char a:4;
unsigned char b:2;
unsigned char c:1;
};
int main()
{
reg11 reg;
reg.val=0;
reg.bit.b = 1;
printf("val = %d\n",reg.val);
return 0;
}
1.6 时间相关函数
1.6.1 clock
printf("%d",clock()/CLOCKS_PER_SEC);
1.7 字符串函数
1.7.1 sprintf、sscanf
sprintf(s, "%d", 123); //输出到字符串
sscanf("12345","%d",&data); //从字符串输入,若为浮点数的话只支持float型
1.7.2 strstr
- 从字符串查找子串是否存在,若存在则返回所在位置的char *指针,不存在返回NULL。
char str[]="ABCDEFG";
char *presult;
presult= strstr(str,"BCD"); //查找到有BCD子串,所以*presult="BCDEFG";
1.7.3 strchr
- 从字符串查找字符是否存在,若存在则返回所在位置的char *指针,不存在返回NULL。
char *strchrnul(const char *s, int c);
1.7.4 perror
iconv_t cd = iconv_open ("GBK", "UTF-8");
if ( cd == (iconv_t)-1 )
{
perror ("iconv_open");
}
打印:iconv_open: Invalid argument
1.7.5 strtoul、strtod、strtol
参数含义:
*nptr:待转换的字符串
endptr:存放出错的字符串地址,默认为NULL。
base:要转换的数据类型,范围从2至36(2进制、10进制、16进制等)。也可以取0(当base==0,表示自动识别:0x/0X开头的为16进制,0开头的为8进制,其余为10进制)
#include<stdlib.h>
#include<stdio.h>
void main()
{
char *endptr;
char a[] = "12345.6789";
char b[] = "1234.567qwer";
char c[] = "-232.23e4";
printf( "a=%lf\n", strtod(a,NULL) );
printf( "b=%lf\n", strtod(b,&endptr) );
printf( "endptr=%s\n", endptr );
printf( "c=%lf\n", strtod(c,NULL) );
}
执行结果:
a=12345.678900
b=1234.567000
endptr=qwer
c=-2322300.000000
1.7.6 atoi、atol、atof
-
头文件:#include <stdlib.h> -
作用:扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,到出现非数字或字符串结束时(’\0’)才结束转换,并将对应的结果(int、long、double)返回。参数nptr字符串可包含正负号、小数点或E(e)来表示指数部分,如123.456或123e-2。 -
函数原型 :
int atof(const char *nptr); long atof(const char *nptr); double atof(const char *nptr); -
注意:因为ato系列函数没有出错处理,所以它是需要被转换字符串是纯数字类型的。 -
详情参考:C 标准库 – | 菜鸟教程 (runoob.com)
1.8 iconv
-
头文件: #include <iconv.h> -
有3个常用函数:
1.9 命令行参数解析(getopt)
-
头文件:#include <unistd.h> -
原型:int getopt(int argc, char *const argv[], const char *optstring); -
全局变量:
- extern char *optarg; //指向当前选项参数的指针
- extern int optind; //(option index)下一次调用getopt时,从optind存储的位置处重新开始检查选项。
-
参数解析:optstring选项在getopt定义里分为三种:
- 不带参数的选项
- 命令:
rm work/ -rf //r和f选项后面就没跟参数 - 对应的getopt函数:
ch = getopt(argc, argv, "rf"); - 必须带参数的选项,在单个字符后加一个冒号,然后跟的参数可以紧跟或者空格隔开
- 命令:
tar -xjf 1. tar.bz2 -C ./tmp //-C 后面跟着一个参数 - 对应的getopt函数:
ch=getopt(argc, argv,"xjf:C:"); //xj后面没有冒号,所以不跟参数,而f和C后面有冒号,所以必须加参数 - 可带参数的选项,在单个字符后加两个冒号 ,该选项后面,如果跟上参数,则必须紧跟,不能以空格隔开
-
返回值:
- 若选项被成功找到,则返回选项字符值;
- 如果所有命令行成功解析完,则返回-1;
- 如果解析到不正确的选项或者没有跟选项的参数时,则返回?
-
示例:
#include "stdio.h"
#include <unistd.h>
extern char *optarg;
extern int optind;
int main(int argc,char *argv[])
{
char ch;
while(( ch=getopt(argc, argv,"abc:d::"))!= -1)
{
switch(ch) {
case 'a':
{
printf("a:\n");
printf("optarg: %s\n",optarg);
printf("optind: %d\n",optind);
break;
}
case 'b':
{
printf("b:\n");
printf("optarg: %s\n",optarg);
printf("optind: %d\n",optind);
break;
}
case 'c':
{
printf("c:\n");
printf("optarg: %s\n",optarg);
printf("optind: %d\n",optind);
break;
}
case 'd':
{
printf("d:\n");
printf("optarg: %s\n",optarg);
printf("optind: %d\n",optind);
break;
}
default :
{
printf("%c:\n",ch);
printf("optarg: %s\n",optarg);
printf("optind: %d\n",optind);
break;
}
}
}
return 0;
}
运行结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CqZNp83X-1628341466019)(C:\Users\liang\AppData\Roaming\Typora\typora-user-images\image-20210807204846610.png)]
2、易错类
2.1 文件操作
fopen、fwrite、fread、fseek、fgets、popen、access
2.1.1 fopen()
-
r 以只读方式打开文件,该文件必须存在。 -
r+ 以读/写方式打开文件,该文件必须存在。 -
rb+ 以读/写方式打开一个二进制文件 -
rt+ 以读/写方式打开一个文本文件 -
w 打开只写文件,若文件存在则清零重写,不存在则新建。 -
w+ 打开可读/写文件,若文件存在则清零重写,不存在则新建。 -
wb 以只写方式打开或新建一个二进制文件 -
wb+ 以读/写方式打开或建立一个二进制文件 -
wt+ 以读/写方式打开或建立一个文本文件 -
a 以附加的方式打开只写文件。若文件不存在,则会新建;存在,则文件原内容保留(EOF 符保留)。 -
a+ 以附加方式打开可读/写的文件。若文件不存在,则会新建;存在,则文件原内容保留(原来的 EOF 符不保留)。 -
at+ 以读/写方式打开一个文本文件,允许读或在文本末追加数据。 -
ab+ 以读/写方式打开一个二进制文件,允许读或在文件末追加数据。
2.1.2 fread、fwrite
-
原型 :size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); -
原型:size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream); -
返回值:fread()和fwrite()返回的是成功读取/写入的条目数(字节数,也即nmemb大小)。如果到达文件末尾或读写错误,则返回0 -
size取值尽量为1,即以字节为单位。
2.1.3 fprintf、fclose
-
原型:int fprintf(FILE *stream, const char *format, ...) -
原型:int fclose(FILE *stream); //关闭文件流,刷新缓冲区,更新磁盘文件 -
示例
#include<string.h>
#include<stdio.h>
int main(void)
{
FILE *fp = NULL;
const char *buf = "0123456789";
fp = fopen("DUMMY.FIL","w");
fwrite(buf,strlen(buf),1,fp);
fclose(fp);return 0;
}
2.1.4 fseek
-
原型:int fseek(FILE *stream, long offset, int fromwhere); -
offset:正数表示正向偏移,负数表示负向偏移,单位为字节 -
fromwhere:
- SEEK_SET(==0): 文件开头
- SEEK_CUR(==1): 当前位置
- SEEK_END(==2): 文件结尾
-
示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char* argv[])
{
FILE *fp = NULL;
int readbuf;
int readEnd;
int writebuf=100;
int len;
fp = fopen("./1.txt","rb+");
printf("read 1.txt: fp==NULL=%d size=%d\n",fp == NULL,sizeof(readbuf));
if(fp!=NULL)
{
len=fread(&readbuf,sizeof(int),1,fp);
printf("read len=%d data=%d\n",len,readbuf);
fseek(fp,-sizeof(int),SEEK_END);
fread(&readEnd,sizeof(int),1,fp);
printf("read file end =%d\n",readEnd);
fclose(fp);
}
else
{
fp = fopen("./1.txt","wb+");
printf("write 1.txt: fp==NULL=%d size=%d\n",fp == NULL,sizeof(readbuf));
if(fp!=NULL)
{
len=fwrite(&writebuf,sizeof(int),1,fp);
printf("write len=%d \n",len);
fclose(fp);
}
}
return 0;
}
2.1.5 fgets
从文件结构体指针stream中读取数据,每次读取一行。读取的数据保存在buf指向的字符数组中,每次最多读取bufsize-1个字符(第bufsize个字符赋’\0’),如果文件中的该行,不足bufsize-1个字符,则读完该行就结束。如若该行(包括最后一个换行符)的字符数超过bufsize-1,则fgets只返回一个不完整的行
-
原型:char *fgets(char *buf, int bufsize, FILE *stream); -
返回值:返回读成功的缓存区地址,读到文件结尾或者出错返回NULL。 -
bufsize:缓存区大小,包含字符串结束符\0 。 -
示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char* argv[])
{
FILE *fp = NULL;
char readbuf[5];
fp = fopen("./1.txt","r+");
printf("(DEBUG)read 1.txt: fp==NULL=%d\n",fp == NULL);
if(fp!=NULL)
{
while(fgets(readbuf, sizeof(readbuf),fp))
{
printf("%s",readbuf);
}
fclose(fp);
}
return 0;
}
2.1.6 popen、pclose
命令输出到文件或文件内容作为命令输入。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int run_command(const char *cmd, const char *type)
{
FILE *fp;
int res; char buf[1024];
if ((fp = popen(cmd, type)) ==NULL)
{
printf("popen err \n");
return -1;
}
if(type[0]=='r')
{
while(fgets(buf, sizeof(buf),fp))
{
printf("%s",buf);
}
}
pclose(fp);
return 0;
}
int main(int argc,char* argv[])
{
run_command("vi 1.txt","r");
return 0;
}
2.1.7 access
头文件:#include <unistd.h>。用来检测访问的文件属性,是否可以读写,存在,执行
#include <stdio.h>
#include <unistd.h>
int file_exists(char *filename);
int main(void)
{
printf("Does NOTEXIST.FIL exist: %s\n",file_exists("./1.txt") ? "YES" : "NO");
return 0;
}
int file_exists(char *filename)
{
return (access(filename, F_OK) == 0);
}
2.1.8 fsync、fflush
如果在嵌入式linux中,则有可能在写数据后强制关电,此时数据还在缓冲区,并没写到flash中,所以需要在fclose()前面加上:
fflush(fp);
fsync(fileno(fp));
fclose(fp);
2.2 setjmp、longjmp(除零问题)
一般在处理除0问题时,都会先定义一个极小的浮点数,然后判断除数的绝对值是否小于该数,如果小于就当作除数是0。但该方法不容易辨别当被除数为0时的情况:
double divide(doublea,double b)
{
const double delta = 0.00000000001;
if(!((-delta<b)&&(b<delta)))
{
return a/b ;
}
else
{
return 0;
}
}
可以通过setjmp()和longjmp()解决以上问题:
-
setjmp/longjmp描述:
- 和goto很相似,但是可以从一个函数到另外一个函数的跳转,常常用在异常处理上面。
- 这两个函数需要正确使用,否则会破坏程序顺序执行方式
- 属于头文件 #include <setjmp.h>
-
原型:
int setjmp(jmp_buf env);
void longjmp(jmp_buf env,int val);
mian()函数 调用 setjmp(env) 将上下文(入栈)保存在env中,并返回0。
接着调用 divide()函数 进行除法操作。进入 divide()函数 后,由于发现除法为0,所以使用 longjmp(env,1)函数 恢复 setjmp() 保存的上下文,也就是直接返回到了 main()函数 处理 setjmp(env) 的时候,并返回异常值1。
#include <stdio.h>
#include <setjmp.h>
jmp_buf env;
double divide(double a,double b)
{
const double delta = 0.00000000001;
if(!((-delta<b)&&(b<delta)))
{
return a/b ;
}
else
{
longjmp(env,1);
return 0;
}
}
int main( )
{
int ret;
ret=setjmp(env);
if(!ret)
{
printf("5/0=%lf\n",divide(5,0));
}
else if(ret==1)
{
printf("ERR\n");
}
return 0;
}
2.3 双向链表
#include "stdio.h"
#include <stdlib.h>
#include "string.h"
typedef struct NAME{
char *name;
struct NAME *preced;
struct NAME *next;
}T_NAME, *PT_NAME;
PT_NAME g_ptNameListHead=NULL;
void add_LinkList(PT_NAME nameNew)
{
PT_NAME ptTamp;
int i=0;
if(g_ptNameListHead==NULL)
{
g_ptNameListHead= nameNew;
return ;
}
else
{
ptTamp=g_ptNameListHead;
while(ptTamp->next)
{
ptTamp=ptTamp->next;
}
ptTamp->next=nameNew;
nameNew-> preced= ptTamp;
return ;
}
}
void add_name(char str[])
{
PT_NAME ptTamp;
char *p;
p=(char *)malloc(strlen(str));
strcpy(p,str);
ptTamp=(PT_NAME)malloc(sizeof(T_NAME));
ptTamp->name=p;
ptTamp-> preced=NULL;
ptTamp-> next= NULL;
add_LinkList(ptTamp);
}
PT_NAME find_name(char str[])
{
PT_NAME ptTamp;
if(g_ptNameListHead==NULL)
return NULL;
ptTamp=g_ptNameListHead;
while(ptTamp)
{
if(strcmp(ptTamp->name,str)==0)
{
return ptTamp;
}
else
ptTamp=ptTamp->next;
}
return NULL;
}
int del_name(char str[])
{
PT_NAME ptTamp;
PT_NAME ptLast;
PT_NAME ptNext;
ptTamp=find_name(str);
if(!ptTamp)
return -1;
if(ptTamp==g_ptNameListHead)
{
g_ptNameListHead=ptTamp->next;
}
else
{
ptLast=ptTamp->preced;
ptNext=ptTamp->next ;
ptLast->next= ptNext;
ptNext->preced=ptLast;
}
free(ptTamp->name);
free(ptTamp);
return 0;
}
void List_name(void)
{
int i=0;
PT_NAME ptTamp=g_ptNameListHead;
while(ptTamp)
{
printf("<%d> %s\n",i,ptTamp->name);
ptTamp=ptTamp->next; i++;
}
}
void scanf_name(char cmd)
{
char name[128];
switch(cmd)
{
case 'a':
{
printf("please enter name:\n");
scanf("%s",name);
add_name(name);
printf("add %s OK\n",name);
break;
}
case 'd':
{
printf("please enter name:\n");
scanf("%s",name);
if(del_name(name)<0)
{
printf("del %s error\n",name);
}
else
printf("del %s OK\n",name);
break;
}
case 'l':
List_name(); break;
}
}
int main(int argc, char **argv)
{
char c;
while(1)
{
printf("**********************\n");
printf("<l> List all the names\n");
printf("<a> add one name\n");
printf("<d> del one name\n");
printf("<q> quit\n");
printf("**********************\n");
do{
scanf("%c",&c);
}while(c!='a'&&c!='d'&&c!='q'&&c!='l');
switch(c)
{
case 'a':
case 'l':
case 'd':scanf_name(c); break;
case 'q':return 0;
default : break;
}
}
}
“add %s OK\n”,name); break; } case ‘d’: //del { printf(“please enter name:\n”); scanf("%s",name); if(del_name(name)<0) { printf(“del %s error\n”,name); } else printf(“del %s OK\n”,name); break; } case ‘l’: //list List_name(); break; } }
int main(int argc, char **argv) { char c;
while(1)
{
printf("**********************\n");
printf("<l> List all the names\n");
printf("<a> add one name\n");
printf("<d> del one name\n");
printf("<q> quit\n");
printf("**********************\n");
do{
scanf("%c",&c);
}while(c!='a'&&c!='d'&&c!='q'&&c!='l');
switch(c)
{
case 'a':
case 'l':
case 'd':scanf_name(c); break;
case 'q':return 0;
default : break;
}
}
}
|