0
点赞
收藏
分享

微信扫一扫

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 intoptind; //(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;
}
}

}


举报

相关推荐

0 条评论