文章目录
1. 几个问题
1.1 结构体类型定义的位置
看下面这段代码代码:
#include<stdio.h>
int fun(struct a y)
{
return 0;
}
int fun1(int a)
{
return 0;
}
int main()
{
int g;
struct a
{
int b;
int c;
int d;
};
fun1(g);
fun(xx);
struct a xx;
return 0;
}
这段代码编译的话会出现报错:
warning: ‘struct a’ declared inside parameter list will not be visible outside of this definition or declaration
error: parameter 1 (‘y’) has incomplete type
报错原因是未声明结构体。于是我有了下面的疑问:
为什么fun1的形参int不用提前声明,而fun的结构体形参必须提前声明?
我暂时得到了一个不是很满意的答案:因为int为基本数据类型,能被编译器自动识别,而结构体为构造数据类型。只有提前声明结构体类型,做函数形参时才能被编译器识别。
1.2 编译器对构造数据类型的识别
那么,数组也为构造数据类型,它做函数形参是为何不用提前声明?
同样,我也有一个不是很满意的答案:因为数组的类型是确定的,它包含的元素都为基本数据类型。
1.3 编译器对源文件的编译过程
既然全局函数在main函数中调用的时候才会开辟相应的空间,那为什么在编译阶段,就要对传入的形参进行识别呢?
同样暂时有个不满意的答案:编译器解析源码的过程涉及到词法分析、语法分析、语义分析,是个十分复杂的过程。在处理过程中,编译器需要“看到”所有相关的定义和声明。
2. 静态链表
看下面这段代码:
#include<stdio.h>
struct student
{
long num;
struct student *next;
};
int main(void)
{
struct student a,b,c,*h,*p;
h=&a;
a.num=10101;
a.next=&b;
b.num=10103;
b.next=&c;
c.num=10107;
c.next=NULL;
p=h;
while(p!=NULL)
{
printf("%ld\n",p->num);
p=p->next;
}
return 0;
}
在此例中,链表的每个结点都是在程序中定义的,由系统在内存中分配固定的存储单元。每个结点不是临时开辟的,也不能用完后释放,而是在程序结束后释放。
从这一点来讲,这种链表被称为“静态链表”。
实际应用中使用更广泛的是“动态链表”。
3. 动态链表
3.2 库函数
先介能够动态申请和释放内存的库函数。
1. malloc
原型为:
void *malloc(unsigned int a);
作用是在内存的堆区中申请 a 字节的连续空间,并返回此空间的起始地址。
由于返回的地址的类型为 void,所以将其赋值给其他类型的指针变量时,需要进行强制类型转换,如:
p=(long *)malloc(8);
2. free
用法如下:
free(p);
3.2 内存空间分类
见下图:
栈区空间由计算机分配,而堆区空间由程序员手动管理。
静态链表的内存空间由计算机从栈区分配。而 malloc 从堆区申请存储空间。
4. 链表的基本操作
链表的基本操作包含建立链表、插入链表、删除链表、输出链表、查找链表。
4.1 建立链表
看下面这段代码:
#include<stdio.h>
#include<stdlib.h>
struct student
{
long num;
struct student *next;
};
int main()
{
struct student *h,*p;
h=(struct student *)malloc(sizeof(struct student));
h->next=NULL;
h->num=1;
p=(struct student *)malloc(sizeof(struct student));
p->next=h;
h=p;
return 0;
}
这段代码先创建了一个结构体,指针 h 指向整个结构体的首地址,这个结构体的尾部指向空地址NULL。
然后又创建了了一个同类型的结构体,指针 p 指向新创建的结构体。新创建的结构体的尾部指向之前创建的结构体。指针 h 指向新创建的结构体。
可以将这个过程封装为函数,重复调用以达到不断扩充链表的效果。
4.2 输出链表
将链表中各节点的值依次输出,需要对链表进行遍历。核心代码如下:
whlie(p!=NULL)
{
printf("%d\n",p->num);
p=p->next;
}
4.3 插入链表
要找到链表的插入点,可以使用循环。
4.4 链表的删除
修改前一个共用体的尾部指针,然后释放掉该结构体的内存空间。