IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言常见错误和技巧一文打尽 -> 正文阅读

[C++知识库]C语言常见错误和技巧一文打尽

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(...)   //表示预处理器遇到debug(...)时,会省略掉该语句,从而不打印输出
#endif
  • 判断一个宏是否存在
#if defined(CONFIG_XXX)		//不要使用 #ifdef 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 最小二乘法拟合曲线代码

  • 给定一组坐标值,得到这些点的拟合曲线函数:
/***************************************************************************
 * count: 表示XY坐标点数 
 *
 * a b c: y=ax*x+bx+c;
 *      
***************************************************************************/
#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;	//1970年1月1日以来走过的秒数
	unsigned short millitm;	//毫秒数
	short timezonel;	/* 当前时区和Greenwich时区的差值,单位为分 */
	short dstflag;		/* 日光节约时间的修正状态,非0代表启用日光节约时间修正 */
}

具体实现代码,参考如下:

#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;  //启动后的定时器每隔990ms唤醒一次
        itv.it_value.tv_sec = 0;
        itv.it_value.tv_usec = 50000;   //定时器在50ms后启动
        setitimer(ITIMER_REAL, &itv, &oldtv); 
           //ITIMER_REAL表示每次定时器唤醒时将会触发SIGALRM信号
}

/*关闭定时器*/
void shut_timer()
{
        itv.it_value.tv_sec = 0;        //将启动参数设置为0,表示定时器不启动
        itv.it_value.tv_usec = 0;
        setitimer(ITIMER_REAL, &itv, &oldtv);
}

/*定时器中断处理函数*/
void signal_handler(void)
{
        ... 
}

int main()
{
        signal(SIGALRM, signal_handler);  //将SIGALRM信号与signal_handler函数建立关系,当信号触发时便会调用该函数.
        set_timer();			//启动定时器
        while(1);	//等待中断
        return 0;
}

1.5 位的使用

做低层时,经常会读写寄存器,比如操作某位,设置为0或1,而在C语言中便为我们提供一种数据结构”位域”,使得我们通过读写”位域”来实现操作某位:

#include <stdio.h>

struct {                                                  
         unsigned mode:8;          //bit[0,7]:模式选择
         unsigned en:1;            //bit[8]   :使能选择
         unsigned reserved:1;      //bit[9]    :保留reserved  (也可以写成unsigned reserved:1;)
         unsigned clk_select:4;    //bit[10,13]:时钟选择
         unsigned ch_select:3;     //bit[14,15]:通道选择
}reg11;            //定义一个reg11变量,不声明结构体的好处在于确保变量唯一性

int main()
{      
         reg11.en =1;                     //bit8=1 --> 256
         printf("reg11=%d\n",reg11);         //打印 256

         reg11.mode =50;
         printf("reg11=%d\n",reg11);         //打印 256+50
         return 0;
}
  • 越界处理:若向某个位域的赋值超过其范围,则向低位保留该位域需要的位数,其它相邻位域不受影响。即若执行reg11.en =5;,则reg11=256。

  • 位域结构体大小:位域的结构体的长度默认是int型的倍数(4字节、8字节、12字节等)。但可以通过联合体或明确类型定义使之大小为单字节。

#include <stdio.h>

typedef union{
	unsigned char val;

    struct {                                                  
             unsigned a:4; 
             unsigned b:1; 
             unsigned c:2;
             unsigned d:1;
    }bit;   
    
}reg11;             //使用typedef ,告诉编译器,reg11是个声明类型


/*或明确类型定义,位域总长度只有8位*/
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;            //bit[4]=1
        printf("val = %d\n",reg.val);

        return 0;
}

1.6 时间相关函数

1.6.1 clock

  • 所属头文件#include <ctime.h>

  • 作用:获取当前时钟计数,一般都是ms为单位,也可以通过CLOCKS_PER_SEC宏计算以秒为单位的时间:

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

  • 打印错误信息,例如在arm机上运行:
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

  • 头文件:#include <stdlib.h>

  • 作用:扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,到出现非数字或字符串结束时(’\0’)才结束转换,并将对应的结果(unsigned long、double、long)返回。若endptr不为NULL,则会将遇到不合条件而终止的nptr中的字符指针由endptr传回。

  • 函数原型

    • unsigned long strtoul(const char *nptr, char **endptr, int base );
    • double strtod(const char *nptr, char **endptr);
    • long strtol(const char *nptr, char **endptr);

参数含义

*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个常用函数

    • iconv_open

      • 原型:iconv_t iconv_open (const char* tocode, const char* fromcode);
      • 作用:打开转换编码的句柄(类似文件描述符)
      • 返回值:返回一个句柄iconv_t 格式,若打开失败,返回-1
      • 示例:
      iconv_t cd = iconv_open ("GBK//IGNORE", "UTF-8");  //utf-8转GBK, IGNORE表示遇到无法转换字符跳过
      
    • iconv

      • 原型:size_t iconv (iconv_t cd, const char** inbuf, size_t * inbytesleft, char** outbuf, size_t *outbytesleft);
      • 作用:根据第一个参数iconv_t cd句柄来转换格式。(调用iconv_open 成功后,才能调用iconv )
      • 参数:
        • inbuf :需要转换的字符串
        • inbytesleft :存放还有多少字符没有转换
        • outbuf:存放转换后的字符串
        • outbytesleft :存放转换后,tempoutbuf剩余的空间
    • iconv_close

      • 原型:iconv_close (iconv_t cd);
      • 作用:转换成功后,关闭iconv_t cd句柄

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; //(option index)下一次调用getopt时,从optind存储的位置处重新开始检查选项。 
int main(int argc,char *argv[])
{ 
  char ch;

  //ab:不带参数的选项,c:必须带参数的选项 d:可带参数的选项
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()

  • 原型FILE* fopen(const char* path, const char* mode);

  • mode类型

  • 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");/*创建一个包含10个字节的文件*/
fwrite(buf,strlen(buf),1,fp);/*将buf内容写入到文件中*/
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);      //读写开头的第一个int型数据

              printf("read len=%d  data=%d\n",len,readbuf);

              fseek(fp,-sizeof(int),SEEK_END);          //将fp指向文件末尾的最后一个int型数据处

              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);   //写入一个int型数据
                 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

命令输出到文件或文件内容作为命令输入。

  • 原型FILE *popen(const char *command, const char *type);

  • 原型int pclose(FILE *stream);

  • type

    • 若popen ()的type是”r”,则文件指针是连接到子进程执行command命令的标准输出
    • 若popen ()的type是”w”,则文件指针连接到子进程执行command命令的标准输入
  • 示例

#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>。用来检测访问的文件属性,是否可以读写,存在,执行

  • 原型int access(const char *pathname, int mode);

  • mode

    • #define  F_OK      0     /* Check for file existence */
      #define  X_OK      1     /* Check for execute permission. */
      #define  W_OK     2     /* Check for write permission */
      #define  R_OK      4     /* Check for read permission */
      
  • 示例

#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));          //同步数据到flash。fileno用来获取fp的文件描述符
fclose(fp); 

2.2 setjmp、longjmp(除零问题)

一般在处理除0问题时,都会先定义一个极小的浮点数,然后判断除数的绝对值是否小于该数,如果小于就当作除数是0。但该方法不容易辨别当被除数为0时的情况:

/*当我们调用divide(0,1)时,返回值也是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);	//将当前上下文保存在jmp_buf结构体env中(入栈),并返回0

void longjmp(jmp_buf env,int val);	//从env变量中恢复上下文,并从setjmp函数调用点返回,返回值为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;	//因为需要在两个函数之间跳转,必然要使用全局变量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);      //直接跳转到22行,ret=setjmp(env)代码处,并返回异常值(1)
              return 0;
       }
}
 
int main( )
{
    int ret;
 
    ret=setjmp(env); //手动调用 setjmp(),将返回正常值(0),
 
       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;   //上个name链表成员
       struct NAME *next;     //下个name链表成员
}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);   //添加链表
}


/*从链表里找到name位置*/
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)             //find
              {
                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':               //add
        {
         printf("please enter name:\n");
         scanf("%s",name);
         add_name(name);
         printf("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;
        }
    }
}

“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;
    }
}

}




 
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-08 11:03:10  更:2021-08-08 11:05:18 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/8 15:22:31-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码