c语言课程以外需补充的内容.docx
《c语言课程以外需补充的内容.docx》由会员分享,可在线阅读,更多相关《c语言课程以外需补充的内容.docx(21页珍藏版)》请在冰豆网上搜索。
c语言课程以外需补充的内容
一、以下内容是课程中未涉及的部分,但在等级考试中经常出现的
1、关于有参宏的内容及其与函数的区别
(1)宏定义和宏替换的定义
宏定义是将一个标识符(宏名)定义为某个字符串(宏体)。
宏定义有不带参数和带参数两种形式:
#define标识符字符串
#define标识符(参数表)字符串
宏替换是用已定义的宏体(字符串)来替换宏名(标识符)。
其形式:
宏名
宏名(参数表)
宏定义和宏替换两者的关系:
必须先有宏定义,才能再有宏替换,后者是前者的逆操作。
例:
#defineA(x)x*x
main()
{inta=5;
printf(“%d\n”,A(a-1));}
程序经替换后的printf语句为:
printf(“%d\n”,a-1*a-1);替换后再编译运行,程序的结果应为-1。
注意事项:
宏定义不是C语句,行末不能有分号。
进行宏替换时,要记住只是简单的代换,不能随意增加或减少内容。
如上例,程序经替换后的printf语句不应该替换为:
printf(%d\n,(a-1)*(a-1));
如果需要上述的替换,则应将宏定义修改为:
#defineA(x)(x)*(x)
定义带参数的宏定义时,要特别注意括号的正确使用。
(2).带参数的宏替换与函数调用的区别
●实现的时间不同。
宏替换的实现是在编译前完成;而函数调用是在运行时处理的。
●实现的方法不同。
宏替换只是简单的代换,即用已定义的宏体来替换宏名,它没有控制的转移。
而函数调用是将实参传递给形参,然后转去执行调用的函数体,执行完后,返回到调用的地方继续往下执行。
●宏定义不存在类型问题,即宏名和参数都是无类型的;而函数的形参和实参必须指出类型,且要求形参和实参的类型要相一致。
例:
运行结果:
F1(1+1)=3
F2(1+1)=4
#defineF1(x)x*x
intf2(intx)
{returnx*x;}
main()
{intx=1;
printf("F1(%d+1)=%d\n",x,F1(x+1));
printf("f2(%d+1)=%d\n",x,f2(x+1));
}
2、有关链表的操作
准备:
动态内存分配
一、为什么用动态内存分配
但我们未学习链表的时候,如果要存储数量比较多的同类型或同结构的数据的时候,总是使用一个数组。
比如说我们要存储一个班级学生的某科分数,总是定义一个float型(存在0.5分)数组:
floatscore[30];
但是,在使用数组的时候,总有一个问题困扰着我们:
数组应该有多大?
在很多的情况下,你并不能确定要使用多大的数组,比如上例,你可能并不知道该班级的学生的人数,那么你就要把数组定义得足够大。
这样,你的程序在运行时就申请了固定大小的你认为足够大的内存空间。
即使你知道该班级的学生数,但是如果因为某种特殊原因人数有增加或者减少,你又必须重新去修改程序,扩大数组的存储范围。
这种分配固定大小的内存分配方法称之为静态内存分配。
但是这种内存分配的方法存在比较严重的缺陷,特别是处理某些问题时:
在大多数情况下会浪费大量的内存空间,在少数情况下,当你定义的数组不够大时,可能引起下标越界错误,甚至导致严重后果。
那么有没有其它的方法来解决这样的外呢体呢?
有,那就是动态内存分配。
所谓动态内存分配就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。
动态内存分配不象数组等静态内存分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小。
从以上动、静态内存分配比较可以知道动态内存分配相对于景泰内存分配的特点:
1、不需要预先分配存储空间;
2、分配的空间可以根据程序的需要扩大或缩小。
二、如何实现动态内存分配及其管理
要实现根据程序的需要动态分配存储空间,就必须用到以下几个函数
1、malloc函数
malloc函数的原型为:
void*malloc(unsignedintsize)
其作用是在内存的动态存储区中分配一个长度为size的连续空间。
其参数是一个无符号整形数,返回值是一个指向所分配的连续存储域的起始地址的指针。
还有一点必须注意的是,当函数未能成功分配存储空间(如内存不足)就会返回一个NULL指针。
所以在调用该函数时应该检测返回值是否为NULL并执行相应的操作。
下例是一个动态分配的程序:
#include
#include
main()
{
intcount,*array;/*count是一个计数器,array是一个整型指针,也可以理解为指向一个整型数组的首地址*/
if((array(int*)malloc(10*sizeof(int)))==NULL)
{
printf("不能成功分配存储空间。
");
exit
(1);
}
for(count=0;count〈10;count++)/*给数组赋值*/
array[count]=count;
for(count=0;count〈10;count++)/*打印数组元素*/
printf("%2d",array[count]);
}
上例中动态分配了10个整型存储区域,然后进行赋值并打印。
例中if((array(int*)malloc(10*sizeof(int)))==NULL)语句可以分为以下几步:
1)分配10个整型的连续存储空间,并返回一个指向其起始地址的整型指针
2)把此整型指针地址赋给array
3)检测返回值是否为NULL
2、free函数
由于内存区域总是有限的,不能不限制地分配下去,而且一个程序要尽量节省资源,所以当所分配的内存区域不用时,就要释放它,以便其它的变量或者程序使用。
这时我们就要用到free函数。
其函数原型是:
voidfree(void*p)
作用是释放指针p所指向的内存区。
其参数p必须是先前调用malloc函数或calloc函数(另一个动态分配存储区域的函数)时返回的指针。
给free函数传递其它的值很可能造成死机或其它灾难性的后果。
注意:
这里重要的是指针的值,而不是用来申请动态内存的指针本身。
例:
int*p1,*p2;
p1=malloc(10*sizeof(int));
p2=p1;
……
free(p2)/*或者free(p2)*/
malloc返回值赋给p1,又把p1的值赋给p2,所以此时p1,p2都可作为free函数的参数。
malloc函数是对存储区域进行分配的。
free函数是释放已经不用的内存区域的。
所以由这两个函数就可以实现对内存区域进行动态分配并进行简单的管理了。
一、单链表的建立
有了动态内存分配的基础,要实现链表就不难了。
所谓链表,就是用一组任意的存储单元存储线性表元素的一种数据结构。
链表又分为单链表、双向链表和循环链表等。
我们先讲讲单链表。
所谓单链表,是指数据接点是单向排列的。
一个单链表结点,其结构类型分为两部分:
1、数据域:
用来存储本身数据
2、链域或称为指针域:
用来存储下一个结点地址或者说指向其直接后继的指针。
例:
typedefstructnode
{
charname[20];
structnode*link;
}stud;
这样就定义了一个单链表的结构,其中charname[20]是一个用来存储姓名的字符型数组,指针*link是一个用来存储其直接后继的指针。
定义好了链表的结构之后,只要在程序运行的时候爱数据域中存储适当的数据,如有后继结点,则把链域指向其直接后继,若没有,则置为NULL。
下面就来看一个建立带表头(若未说明,以下所指链表均带表头)的单链表的完整程序。
#include
#include/*包含动态内存分配函数的头文件*/
#defineN10/*N为人数*/
typedefstructnode
{
charname[20];
structnode*link;
}stud;
stud*creat(intn)/*建立单链表的函数,形参n为人数*/
{
stud*p,*h,*s;/**h保存表头结点的指针,*p指向当前结点的前一个结点,*s指向当前结点*/
inti;/*计数器*/
if((h=(stud*)malloc(sizeof(stud)))==NULL)/*分配空间并检测*/
{
printf("不能分配内存空间!
");
exit(0);
}
h->name[0]='\0';/*把表头结点的数据域置空*/
h->link=NULL;/*把表头结点的链域置空*/
p=h;/*p指向表头结点*/
for(i=0;i{
if((s=(stud*)malloc(sizeof(stud)))==NULL)/*分配新存储空间并检测*/
{
printf("不能分配内存空间!
");
exit(0);
}
p->link=s;/*把s的地址赋给p所指向的结点的链域,这样就把p和s所指向的结点连接起来了*/
printf("请输入第%d个人的姓名",i+1);
scanf("%s",s->name);/*在当前结点s的数据域中存储姓名*/
s->link=NULL;
p=s;
}
return(h);
}
main()
{
intnumber;/*保存人数的变量*/
stud*head;/*head是保存单链表的表头结点地址的指针*/
number=N;
head=creat(number);/*把所新建的单链表表头地址赋给head*/
}
这样就写好了一个可以建立包含N个人姓名的单链表了。
写动态内存分配的程序应注意,请尽量对分配是否成功进行检测。
二、单链表的基本运算
建立了一个单链表之后,如果要进行一些如插入、删除等操作该怎么办?
所以还须掌握一些单链表的基本算法,来实现这些操作。
单链表的基本运算包括:
查找、插入和删除。
下面我们就一一介绍这三种基本运算的算法,并结合我们建立单链表的例子写出相应的程序。
1、查找
对单链表进行查找的思路为:
对单链表的结点依次扫描,检测其数据域是否是我们所要查好的值,若是返回该结点的指针,否则返回NULL。
因为在单链表的链域中包含了后继结点的存储地址,所以当我们实现的时候,只要知道该单链表的头指针,即可依次对每个结点的数据域进行检测。
以下是应用查找算法的一个例子:
#include
#include
#include/*包含一些字符串处理函数的头文件*/
#defineN10
typedefstructnode
{
charname[20];
structnode*link;
}stud;
stud*creat(intn)/*建立链表的函数*/
{
stud*p,*h,*s;
inti;
if((h=(stud*)malloc(sizeof(stud)))==NULL)
{
printf("不能分配内存空间!
");
exit(0);
}
h->name[0]='\0';
h->link=NULL;
p=h;
for(i=0;i{
if((s=(stud*)malloc(sizeof(stud)))==NULL)
{
printf("不能分配内存空间!
");
exit(0);
}
p->link=s;
printf("请输入第%d个人的姓名",i+1);
scanf("%s",s->name);
s->link=NULL;
p=s;
}
return(h);
}
stud*search(stud*h,char*x)/*查找链表的函数,其中h指针是链表的表头指针,x指针是要查找的人的姓名*/
{
stud*p;/*当前指针,指向要与所查找的姓名比较的结点*/
char*y;/*保存结点数据域内姓名的指针*/
p=h->link;
while(p!
=NULL)
{
y=p->name;
if(strcmp(y,x)==0)/*把数据域里的姓名与所要查找的姓名比较,若相同则返回0,即条件成立*/
return(p);/*返回与所要查找结点的地址*/
elsep=p->link;
}
if(p==NULL)
printf("没有查找到该数据!
");
}
main()
{
intnumber;
charfullname[20];
stud*head,*searchpoint;/*head是表头指针,searchpoint是保存符合条件的结点地址的指针*/
number=N;
head=creat(number);
printf("请输入你要查找的人的姓名:
");
scanf("%s",fullname);
searchpoint=search(head,fullname);/*调用查找函数,并把结果赋给searchpoint指针*/
}
2、插入(后插)
假设在一个单链表中存在2个连续结点p、q(其中p为q的直接前驱),若我们需要在p、q之间插入一个新结点s,那么我们必须先为s分配空间并赋值,然后使p的链域存储s的地址,s的链域存储q的地址即可。
(p->link=s;s->link=q),这样就完成了插入操作。
下例是应用插入算法的一个例子:
#include
#include
#include
#defineN10
typedefstructnode
{
charname[20];
structnode*link;
}stud;
stud*creat(intn)/*建立单链表的函数*/
{
stud*p,*h,*s;
inti;
if((h=(stud*)malloc(sizeof(stud)))==NULL)
{
printf("不能分配内存空间!
");
exit(0);
}
h->name[0]='\0';
h->link=NULL;
p=h;
for(i=0;i{
if((s=(stud*)malloc(sizeof(stud)))==NULL)
{
printf("不能分配内存空间!
");
exit(0);
}
p->link=s;
printf("请输入第%d个人的姓名:
",i+1);
scanf("%s",s->name);
s->link=NULL;
p=s;
}
return(h);
}
stud*search(stud*h,char*x)/*查找函数*/
{
stud*p;
char*y;
p=h->link;
while(p!
=NULL)
{
y=p->name;
if(strcmp(y,x)==0)
return(p);
elsep=p->link;
}
if(p==NULL)
printf("没有查找到该数据!
");
}
voidinsert(stud*p)/*插入函数,在指针p后插入*/
{
charstuname[20];
stud*s;/*指针s是保存新结点地址的*/
if((s=(stud*)malloc(sizeof(stud)))==NULL)
{
printf("不能分配内存空间!
");
exit(0);
}
printf("请输入你要插入的人的姓名:
");
scanf("%s",stuname);
strcpy(s->name,stuname);/*把指针stuname所指向的数组元素拷贝给新结点的数据域*/
s->link=p->link;/*把新结点的链域指向原来p结点的后继结点*/
p->link=s;/*p结点的链域指向新结点*/
}
main()
{
intnumber;
charfullname[20];/*保存输入的要查找的人的姓名*/
stud*head,*searchpoint;
number=N;
head=creat(number);/*建立新链表并返回表头指针*/
printf("请输入你要查找的人的姓名:
");
scanf("%s",fullname);
searchpoint=search(head,fullname);/*查找并返回查找到的结点指针*/
insert(searchpoint);/*调用插入函数*/
}
3、删除
假如我们已经知道了要删除的结点p的位置,那么要删除p结点时只要令p结点的前驱结点的链域由存储p结点的地址该为存储p的后继结点的地址,并回收p结点即可。
以下便是应用删除算法的实例:
#include
#include
#include
#defineN10
typedefstructnode
{
charname[20];
structnode*link;
}stud;
stud*creat(intn)/*建立新的链表的函数*/
{
stud*p,*h,*s;
inti;
if((h=(stud*)malloc(sizeof(stud)))==NULL)
{
printf("不能分配内存空间!
");
exit(0);
}
h->name[0]='\0';
h->link=NULL;
p=h;
for(i=0;i{
if((s=(stud*)malloc(sizeof(stud)))==NULL)
{
printf("不能分配内存空间!
");
exit(0);
}
p->link=s;
printf("请输入第%d个人的姓名",i+1);
scanf("%s",s->name);
s->link=NULL;
p=s;
}
return(h);
}
stud*search(stud*h,char*x)/*查找函数*/
{
stud*p;
char*y;
p=h->link;
while(p!
=NULL)
{
y=p->name;
if(strcmp(y,x)==0)
return(p);
elsep=p->link;
}
if(p==NULL)
printf("没有查找到该数据!
");
}
stud*search2(stud*h,char*x)/*另一个查找函数,返回的是上一个查找函数的直接前驱结点的指针,*/
/*h为表头指针,x为指向要查找的姓名的指针*/
/*其实此函数的算法与上面的查找算法是一样的,只是多了一个指针s,并且s总是指向指针p所指向的结点的直接前驱,*/
/*结果返回s即是要查找的结点的前一个结点*/
{
stud*p,*s;
char*y;
p=h->link;
s=h;
while(p!
=NULL)
{
y=p->name;
if(strcmp(y,x)==0)
return(s);
else
{
p=p->link;
s=s->link;
}
}
if(p==NULL)
printf("没有查找到该数据!
");
}
voiddel(stud*x,stud*y)/*删除函数,其中y为要删除的结点的指针,x为要删除的结点的前一个结点的指针*/
{
stud*s;
s=y;
x->link=y->link;
free(s);
}
main()
{
intnumber;
charfullname[20];
stud*head,*searchpoint,*forepoint;
number=N;
head=creat(number);
printf("请输入你要删除的人的姓名:
");
scanf("%s",fullname);
searchpoint=search(head,fullname);
forepoint=search2(head,fullname);
del(forepoint,searchpoint);
3、有关结构体变量的引用方法(请参考教材的相关部分)
(1).结构类型和结构变量的定义形式:
struct结构名
{
<结构成员说明>
};
struct结构名结构变量名表;或在定义结构类型的同时定义结构变量,也可以定义无类型名称的结构变量。
(2).结构成员表示和结构变量的赋值
<结构变量名>.<成员名>
<指向结构变量指针名>-><成员名>
结构变量的初始化:
结构变量可用初始值表的方法进行初始化。
结构数组也可用初始值表的方法进行初始化。
结构变量的赋值:
结构变量的赋值须对结构变量的各个成员进行赋值。
(3).结构与数组
结构变量可以作为数组元素,数组也可以作为结构的成员。
数组元素是同一结构类型的数组称为“结构数组”。
(4).结构与函数
结构变量和指向结构的指针可以作为函数的参数,也可以作为函数的返回值。
返回值为结构变量的函数称为“结构函数”。
注意事项:
·结构类型定义后,系统并不为其分配存储空间,只有定义了此结构类型的变量、数组和指针等,才为变量、数组和指针分配存储单元。
·结构类型所占的字节数可由sizeof(struct结构名)算出,即各成员所占的字节数总和。
如:
structstudent{
longnum;
charname[20];
floatscore;
}st,stclass[30],*p;
结构类型structstudent占28个字节,系统为结构变量st分配28个字节,为结构数组stclass分配28×30个字节,为指向结构的指针p分配2个字节。
·结构变量和指向结构变量的指针在其成员的表示上是不同的,前者用运算符·,而后者用运算符->。
如st.num和p->num。
两者只有当p=&st时,它们才等价。
二、以下部分内容课程中有所涉及但不详尽,需花时间熟悉。
1、C中的输入输出(请在做历年考题过程中熟练这种表达和注意事项)
(1)格式输出函数printf:
其作用是按各种不同的格式要求向显示屏输出若干个数据,形式为:
printf(控制字符串,[输出表列]);
其中,“输出表列”是可以缺省的,当缺省时,只输出一些提示信息。
如:
printf(“inputa&b:
”);。
“输出表列”不缺省时,则将输出表列中的各个表达式的值按双引号内的格式要求输出。
该函数的使用说明如下:
双引号内除“%格式符”以外,皆原样输出。
输出表列中的各个表达式的值若要正确输出,则要求双引号内必须有一个类型一致的格式符与之对应;若类型不对,则输出无定值;若格式符少,则列在后面的输出项不被输出。
函数的返回值为正常输出的输出项的个数。
(2).格式输入函数scanf:
形式为:
scanf(控制字符串,地址表列);
与输出函数不同,该函数中的输入项要求的是地址表列。
使用该函