3.2线性表的定义:
线性表(List):零个或多个数据元素的有限序列。(是具有像线一样性质的表)
首先,它是一个序列。也就是说,元素之间是有顺序的,若元素存在多个,则第一个元素无前驱,最后一个元素无后继,其他每个元素有且只有一个前驱和后继。
然后,线性表强调是有限的。元素的个数当然也是有限的,事实上,在计算机中处理的对象都是有限的。
线性表长度的定义:线性表元素个数n(n>=0)定义为线性表的长度。当n=0时,称为空表。
3.3线性表的抽象数据类型:即线性表应该有一些什么样的操作呢?
线性表的抽象数据类型定义如下:
基本操作:
1.线性表的创建和初始化
2.线性表重置为空表
3.根据位序得到数据元素
4.查找某个元素是否存在
5.获取线性表长度的问题
6.插入数据和删除数据
复杂操作:可以由基本操作的组合来实现(并集操作)
3.4线性表的顺序存储结构:
线性表顺序存储的定义(两种物理结构的第一种):指的是用一段地址连续的存储单元依次存储线性表的数据元素。
顺序存储方式:说白了,就是在内存中找了块地儿,通过占位的形式,把一定内存空间给占了,然后把相同数据类型的数据元素依次存放在这块空地中。
既然线性表的每个数据元素的类型都相同,所以可以用C语言的一维数组来实现顺序存储结构,即把第一个数据元素存到数组下标为0的位置中,接着把线性表相邻的元素存储在数组中相邻的位置。
来看看线性表的顺序存储的结构代码。
#define MAXSIZE 20 //存储空间初始分配量
typedef int ElemType; //ElemType 类型根据实际情况而定,这里假设为int
typedef struct
{
ElemType date[MAXSIZE];//数组存储数据元素,最大值为MAXSIZE
int length;//线性表当前长度
}sqList;
这里,我们就发现描述顺序存储结构需要三个属性:
1.存储空间的起始位置:数组data,它的存储位置就是存储空间的存储位置。
2.线性表的最大存储容量:数组长度MaxSize.
3.线性表的当前长度:length.
数据长度与线性表长度区别:
两个概念:“数组的长度”和“线性表的长度”。数组的长度是存放线性表的存储空间长度,存储分配后这个量一般是不变的。
线性表的长度时线性表中数据元素的个数,随着线性表差如何删除的操作进行,这个量是变化的。
在任意时刻,线性表的长度应该小于等于数组的长度。
地址的计算方法:线性表的第i个元素是要存储在数组下标为i-1的位置。
用数组存储顺序表意味着要分配固定长度的数组空间,由于线性表中可以进行查如何删除操作,因此分配的数组空间要大于等于当前线性表的长度。
存储器中每个存储单元都有自己的编号,这个编号称为地址。。。。。。。。
3.5顺序存储结构的插入与删除
获得元素操作:
#define OK 1
#define ERROR 0
#define TURE 1
#define FALSE 0
typedef int Status;
Status GetElem(Sqlist L,int i,ElemType *e)
{
if(L.length==0 || i<l ||i>L.length)
return ERROR;
*e=L.data[i-1];
return OK;
}
插入操作:
插入算法的思路:1.如果插入位置不合理,抛出异常;
2.如果线性表的长度大于等于数组长度,则抛出异常或动态增加容量;
3.从最后一个元素开始向前遍历到第i个位置,分别将它们都向后移动一个位置;
4.将要插入的元素填入位置i处;
5.表长加1。
实现代码如下:
//初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
//操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1。
Status GetElem(Sqlist *L,int i,ElemType e)
{
int k;
if(L->length==MAXSIZE)//顺序线性表已经满
return ERROR;
if(i<1 || i>L->length+1)//当i不在范围内时
return ERROR;
if(i<=L->length)//若插入数据位置不在表尾
{
for(k=L->length-1;k>=i-1;k--)//将要插入位置后数据元素向后移动一位
L->data[k+1]=L->data[k];
}
L->data[i-1]=e;//将新元素插入
L->length++;
return OK;
}
删除操作:
删除算法的思路:
1.如果删除位置不合理,则抛出异常;
2.取出删除元素;
3.从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置;
4。表长减1。
实现代码如下:
//初始条件:顺序线性表L已存在,1<=i<=ListLength(L)
//操作结果:删除L中的第i个数据元素,并用e返回其值,L的长度减1。
Status GetElem(Sqlist *L,int i,ElemType e)
{
int k;
if(L->length==0)//线性表为空
return ERROR;
if(i<1 || i>L->length+1)//删除位置不正确
return ERROR;
if(i<L->length)//如果删除不是最后位置
{
for(k=i;k<L->length;k++)//将删除位置后继元素前移
L->data[k-1]=L->data[k];
}
L->length--;
return OK;
}
线性表顺序存储结构的优缺点:优点:1.无需为表中元素之间的逻辑关系而增加额外的存储空间
2.可以快速的存取表中任意位置的元素
缺点:1.插入何删除操作需要移动大量元素
2.当线性表长度变化较大时,难以确定存储空间的容量
3.造成存储空的“碎片”。
3.6线性表的链式存储结构:
顺序存储结构不足的解决办法。
线性表链式存储结构定义:为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对数据元素ai来说,除了存储其本身的信息之外,还需存储一个指示其直接后继的信息(即直接后继的存储位置)。
我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。指针域中存储的信息称做指针或链。这两部分信息组成数据元素ai的存储映像,称为节点(Node)。
n个节点(ai的存储映像)链接成一个链表,即为线性表的链式存储结构,因为此链表的每个节点中只包含一个指针域,所以叫做单链表。单链表正是通过每个节点的指针域将线性表的数据元素按其逻辑次序链接在一起。
对于线性表来说,总得有个头有个尾,链表也不例外。我们把链表中第一个节点的存储位置叫头指针,那么整个链表的存取就必须是从头指针开始进行了。之后的每一个节点,其实就是上一个的后继指针指向的位置。
最后一个节点,他的指针指向NULL。
有时,我们为了更加方便的对量表进行操作,会在单链表的第一个节点前附近设一个节点,称为头结点。
头结点与头指针的异同:
头指针:1头指针是指链表指向第一个节点的指针,若链表有头结点,则是指向头结点的指针
2.头指针具有标识作用,所以常用头指针冠以链表的名字。
3.无论链表是否为空,头指针均不为空。头指针是链表的必要元素。
头结点:1头结点是为了操作的统一和方便而设立的,放在第一个元素的节点之前,其数据域一般无意义(也可存放链表的长度)
2.有了头结点,对在第一元素节点前插入节点和删除第一节点,其操作与其它节点的操作就统一了。
3.头结点不一定是链表的必要元素。
#include <stdlib.h> /*含ma l l o c ( ) 的头文件*/
#include <stdio.h> //定义链表数据结构
struct node { int num; struct node *next; };
//函数声明 struct node *creat();
void print();
int main(void)
{
struct node *head; head=NULL; //建一个空表
head=creat(head);/*创建单链表*/
print(head);/*打印单链表*/
}
/******************************************/
struct node*creat(struct node *head)/*返回的是与节点相同类型的指针*/
{
struct node*p1,*p2; int i=1;
//利用malloc ( )函数向系统申请分配一个节点
p1=p2=(struct node*)malloc(sizeof(struct node));/*新节点*/
printf("请输入值,值小于等于0结束,值存放地址为:p1_ADDR= %d\n",p1);
scanf("%d",&p1->num);/*输入节点的值*/
p1->next=NULL;/*将新节点的指针置为空*/
while(p1->num>0)/*输入节点的数值大于0*/
{
//将新节点的指针成员赋值为空。若是空表,将新节点连接到表头;若是非空表,将新节点接到表尾;
if(head==NULL)
head=p1;/*空表,接入表头*/
else
p2->next=p1;/*非空表,接到表尾*/ p2=p1;
p1=(struct node*)malloc(sizeof(struct node));/*下一个新节点*/
i=i+1;
printf("请输入值,值小于等于0结束,值存放地址为:p%d_ADDR= %d\n",i,p2);
scanf("%d",&p1->num);/*输入节点的值*/
//判断一下是否有后续节点要接入链表,若有转到3 ),否则结束;
}
//==============原来程序更正部分:================================
free(p1); //申请到的没录入,所以释放掉 p1=NULL; //使指向空
p2->next = NULL; //到表尾了,指向空 printf("链表输入结束(END)\n");
//==============================================
return head;/*返回链表的头指针*/
}
/*******************************************/
void print(struct node*head)/*出以head为头的链表各节点的值*/
{
struct node *temp; temp=head;/*取得链表的头指针*/
printf("\n\n\n链表存入的值为:\n");
while(temp!=NULL)/*只要是非空表*/
{
printf("m\n",temp->num);/*输出链表节点的值*/
temp=temp->next;/*跟踪链表增长*/
}
printf("链表打印结束!!");
}
c语言 malloc函数
extern void *malloc(unsigned int num_bytes);
头文件:
#include<malloc.h>或者#include<alloc.h>两者的内容是完全一样的
如果分配成功:则返回指向被分配内存空间的指针
不然返回指针NULL
同时,当内存不再使用的时候,应使用free()函数将内存块释放掉。
关于:void*,表示未确定类型的指针,c,c++规定void*可以强转为任何其他类型的指针,关于void还有一种说法就是其他任何类型都可以直接赋值给它,无需进行强转,但是反过来不可以.
malloc:
malloc分配的内存大小至少为参数所指定的字节数
malloc的返回值是一个指针,指向一段可用内存的起始位置,指向一段可用内存的起始地址,多次调用malloc所分配的地址不能有重叠部分,除非某次malloc所分配的地址被释放掉malloc应该尽快完成内存分配并返回(不能使用NP-hard的内存分配算法)实现malloc时应同时实现内存大小调整和内存释放函数(realloc和free)
malloc和free是配对的,如果申请后不释放就是内存泄露,如果无故释放那就是什么也没做,释放只能释放一次,如果一块空间释放两次或者两次以上会出现错误(但是释放空指针例外,释放空指针也等于什么也没做,所以释放多少次都是可以的。)
malloc和new
new返回指定类型的指针,并且可以自动计算所需要的大小。
int p;
p = new int;//返回类型为int ,分配的大小是sizeof(int)
p = new int[100];//返回类型是int*类型,分配的大小为sizeof(int)*100
而malloc需要我们自己计算字节数,并且返回的时候要强转成指定类型的指针。
int p;
p = (int )malloc(sizeof(int));
(1)malloc的返回是void,如果我们写成了:p=malloc(sizeof(int));间接的说明了(将void转化给了int,这不合理)
(2)malloc的实参是sizeof(int),用于指明一个整型数据需要的大小,如果我们写成p=(int*)malloc(1),那么可以看出:只是申请了一个一个字节大小的空间。
(3)malloc只管分配内存,并不能对其进行初始化,所以得到的一片新内存中,其值将是随机的。一般意义上:我们习惯性的将其初始化为NULL,当然也可以使用memset函数。
简单的说:
malloc函数其实就是在内存中找一片指定大小的空间,然后将这个空间的首地址给一个指针变量,这里的指针变量可以是一个单独的指针,也可以是一个数组的首地址,这要看malloc函数中参数size的具体内容。我们这里malloc分配的内存空间在逻辑上是连续的,而在物理上可以不连续。我们作为程序员,关注的是逻辑上的连续,其他的操作系统会帮着我们处理。
C语言中局部变量和全局变量变量的存储类别(static,extern,auto,register)
1.局部变量和全局变量
在讨论函数的形参变量时曾经提到,形参变量只在被调用期间才分配内存单元,调用结束立即释放。这一点表明形参变量只有在函数内才是有效的,离开该函数就不能再使用了。这种变量有效性的范围称变量的作用域。不仅对于形参变量,C语言中所有的量都有自己的作用域。变量说明的方式不同,其作用域也不同。C语言中的变量,按作用域范围可分为两种,即局部变量和全局变量。
1.1局部变量
局部变量也称为内部变量。局部变量是在函数内作定义说明的。其作用域仅限于函数内,离开该函数后再使用这种变量是非法的。
代码如下:
int f1(int a) /*函数f1*/
{
int b,c;
}
a,b,c有效
int f2(int x) /*函数f2*/
{
int y,z;
}
x,y,z有效
int main(void)
{
int m,n;
}
m,n有效
在函数f1内定义了三个变量,a为形参,b,c为一般变量。在 f1的范围内a,b,c有效,或者说a,b,c变量的作用域限于f1内。同理,x,y,z的作用域限于f2内。m,n的作用域限于main函数内。关于局部变量的作用域还要说明以下几点:
1)主函数中定义的变量也只能在主函数中使用,不能在其它函数中使用。同时,主函数中也不能使用其它函数中定义的变量。因为主函数也是一个函数,它与其它函数是平行关系。这一点是与其它语言不同的,应予以注意。
2)形参变量是属于被调函数的局部变量,实参变量是属于主调函数的局部变量。
3)允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰,也不会发生混淆。如在前例中,形参和实参的变量名都为n,是完全允许的。
4)在复合语句中也可定义变量,其作用域只在复合语句范围内。
代码如下:
int main(void)
{
int s,a;
{
int b;
s=a+b;
/*b作用域*/
}
/*s,a作用域*/
}
代码如下:
int main(void)
{
int i=2,j=3,k;
k=i+j;
{
int k=8;
printf("%d\n",k);
}
printf("%d\n",k);
}
本程序在main中定义了i,j,k三个变量,其中k未赋初值。而在复合语句内又定义了一个变量k,并赋初值为8。应该注意这两个k不是同一个变量。在复合语句外由main定义的k起作用,而在复合语句内则由在复合语句内定义的k起作用。因此程序第4行的k为main所定义,其值应为5。第7行输出k值,该行在复合语句内,由复合语句内定义的k起作用,其初值为8,故输出值为8,第9行输出i,k值。i是在整个程序中有效的,第7行对i赋值为3,故以输出也为3。而第9行已在复合语句之外,输出的k应为main所定义的k,此k值由第4行已获得为5,故输出也为5。
1.2全局变量
全局变量也称为外部变量,它是在函数外部定义的变量。它不属于哪一个函数,它属于一个源程序文件。其作用域是整个源程序。在函数中使用全局变量,一般应作全局变量说明。只有在函数内经过说明的全局变量才能使用。全局变量的说明符为extern。但在一个函数之前定义的全局变量,在该函数内使用可不再加以说明。
代码如下:
int a,b; /*外部变量*/
void f1() /*函数f1*/
{
……
}
float x,y; /*外部变量*/
int fz() /*函数fz*/
{
……
}
Int main(void) /*主函数*/
{
……
}
从上例可以看出a、b、x、y都是在函数外部定义的外部变量,都是全局变量。但x,y定义在函数f1之后,而在f1内又无对x,y的说明,所以它们在f1内无效。a,b定义在源程序最前面,因此在f1,f2及main内不加说明也可使用。
输入正方体的长宽高l,w,h。求体积及三个面xy,xz,y*z的面积。
代码如下:
int s1,s2,s3;
int vs( int a,int b,int c)
{
int v;
v=a*b*c;
s1=a*b;
s2=b*c;
s3=a*c;
return v;
}
int main(void)
{
int v,l,w,h;
printf("\ninput length,width and height\n");
scanf("%d%d%d",&l,&w,&h);
v=vs(l,w,h);
printf("\nv=%d,s1=%d,s2=%d,s3=%d\n",v,s1,s2,s3);
}
外部变量与局部变量同名。
代码如下:
int a=3,b=5; /*a,b为外部变量*/
max(int a,int b) /*a,b为外部变量*/
{
int c;
c=a>b?a:b;
return(c);
}
int main(void)
{
int a=8;
printf("%d\n",max(a,b));
}
如果同一个源文件中,外部变量与局部变量同名,则在局部变量的作用范围内,外部变量被“屏蔽”,即它不起作用。
2.变量的存储类别:
1 动态存储方式与静态动态存储方式
前面已经介绍了,从变量的作用域(即从空间)角度来分,可以分为全局变量和局部变量。
从另一个角度,从变量值存在的作时间(即生存期)角度来分,可以分为静态存储方式和动态存储方式。
静态存储方式:是指在程序运行期间分配固定的存储空间的方式。
动态存储方式:是在程序运行期间根据需要进行动态的分配存储空间的方式。
用户存储空间可以分为三个部分:
1)程序区;
2)静态存储区;
3)动态存储区;
全局变量全部存放在静态存储区,在程序开始执行时给全局变量分配存储区,程序行完毕就释放。在程序执行过程中它们占据固定的存储单元,而不动态地进行分配和释放;
动态存储区存放以下数据:
1)函数形式参数;
2)自动变量(未加static声明的局部变量);
3) 函数调用实的现场保护和返回地址;
对以上这些数据,在函数开始调用时分配动态存储空间,函数结束时释放这些空间。
在c语言中,每个变量和函数有两个属性:数据类型和数据的存储类别。
2 auto变量
函数中的局部变量,如不专门声明为static存储类别,都是动态地分配存储空间的,数据存储在动态存储区中。函数中的形参和在函数中定义的变量(包括在复合语句中定义的变量),都属此类,在调用该函数时系统会给它们分配存储空间,在函数调用结束时就自动释放这些存储空间。这类局部变量称为自动变量。自动变量用关键字auto作存储类别的声明。
代码如下:
int f(int a) /*定义f函数,a为参数*/
{
auto int b,c=3; /*定义b,c自动变量*/
}
a是形参,b,c是自动变量,对c赋初值3。执行完f函数后,自动释放a,b,c所占的存储单元。
关键字auto可以省略,auto不写则隐含定为“自动存储类别”,属于动态存储方式。
3 用static声明局部变量
有时希望函数中的局部变量的值在函数调用结束后不消失而保留原值,这时就应该指定局部变量为“静态局部变量”,用关键字static进行声明。
考察静态局部变量的值。
代码如下:
f(int a)
{
auto b=0;
static c=3;
b=b+1;
c=c+1;
return(a+b+c);
}
int main(void)
{
int a=2,i;
for(i=0;i<3;i++)
printf("%d",f(a));
}
对静态局部变量的说明:
1)静态局部变量属于静态存储类别,在静态存储区内分配存储单元。在程序整个运行期间都不释放。而自动变量(即动态局部变量)属于动态存储类别,占动态存储空间,函数调用结束后即释放。
2)静态局部变量在编译时赋初值,即只赋初值一次;而对自动变量赋初值是在函数调用时进行,每调用一次函数重新给一次初值,相当于执行一次赋值语句。
3)如果在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值0(对数值型变量)或空字符(对字符变量)。而对自动变量来说,如果不赋初值则它的值是一个不确定的值。
打印1到5的阶乘值。
代码如下:
int fac(int n)
{
static int f=1;
f=f*n;
return(f);
}
int main(void)
{
int i;
for(i=1;i<=5;i++)
printf("%d!=%d\n",i,fac(i));
}
4 register变量
为了提高效率,C语言允许将局部变量得值放在CPU中的寄存器中,这种变量叫“寄存器变量”,用关键字register作声明。
使用寄存器变量。
代码如下:
int fac(int n)
{
register int i,f=1;
for(i=1;i<=n;i++)
f=f*I;
return(f);
}
int main(void)
{
int i;
for(i=0;i<=5;i++)
printf("%d!=%d\n",i,fac(i));
}
说明:
- 只有局部自动变量和形式参数可以作为寄存器变量;
2)一个计算机系统中的寄存器数目有限,不能定义任意多个寄存器变量;
3)局部静态变量不能定义为寄存器变量。
2.5用extern声明外部变量
外部变量(即全局变量)是在函数的外部定义的,它的作用域为从变量定义处开始,到本程序文件的末尾。如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件终了。如果在定义点之前的函数想引用该外部变量,则应该在引用之前用关键字extern对该变量作“外部变量声明”。表示该变量是一个已经定义的外部变量。有了此声明,就可以从“声明”处起,合法地使用该外部变量。
用extern声明外部变量,扩展程序文件中的作用域。
代码如下:
int max(int x,int y)
{
int z;
z=x>y?x:y;
return(z);
}
int main(void)
{
extern A,B;
printf("%d\n",max(A,B));
}
int A=13,B=-8;
说明:在本程序文件的最后1行定义了外部变量A,B,但由于外部变量定义的位置在函数main之后,因此本来在main函数中不能引用外部变量A,B。现在我们在main函数中用extern对A和B进行“外部变量声明”,就可以从“声明”处起,合法地使用该外部变量A和B。