目录
14.1.1 可变数组
array.h
array.c
array.c main
14.2.1 可变数组的缺陷(2个)
14.2.2 链表
Node.h
linked-list.c
14.2.3 链表的函数
方法一:错误的示例
方法二:有关最后的head = p;
方法三:传head的指针进去
方法四:创建一个List结构体代表整个链表
14.2.4 链表的一些操作
遍历链表
搜索链表(在搜索基础上更进一步)
链表删除(搜索到了才能删除)
清空链表
首先我们想一想这个问题需要的特点(功能):
- 可增长
- 能获得当前大小
- 可以访问其中的单元
解决方法:设立函数库,几个函数分别实现不同功能
#ifndef _ARRAY_H
#define _ARRAY_H
typedef struct{
int *array;
int size;
}Array;//重命名结构为ARRAY
}*Array
很多人第三方库会设定成一个指针,下方的参数表里经常用到,
可以将参数表里的*去掉了,但是这样的坏处是你之后就不能访问
本地变量array(会动态创建一个),而且可读性也不好
Array array_create(int init_size);//创建数组
void array_free(Array *a);//释放数组的内存空间
int array_size(const Array *a);//获得数组空闲大小
int* array_at(Array *a, int index);//访问数组某个单元
void array_inflate(Array *a,int more_size);//让数组变大
#include"array.h"
//typedef struct{
// int *array;
// int size;
//}Array;
const BLOCK_SIZE = 20;//设自己觉得合适的大小
Array array_create(int init_size)
{
Array a;//创建结构变量a
a.size = init_size;
a.array = (int*)malloc(sizeof(int)*a.size);
//初始化大小和指针
return a;
//返回a本身而不是指针,此时制作的是本地变量,因此可以在main函数中灵活使用
}
void array_free(Array *a)//上个函数中的结构里的*array需要释放
{
free(a->array);
//为了保险起见,防止free两次(freeNULL是无害的),把这两个也初始化为0
a->array = NULL;
a->size = 0;
}
//既然只是想获得size,为什么不干脆printf里return a.size?还写函数干什么
//实际上,我们用函数将内部的实现细节封装起来,防止在大体量程序里直接调用带来的问题
//因为可能根本无法调用
int array_size(const Array *a)
{
return a->size;
}
//为什么返回指针?因为这样做可以同时做到对指针的读和写
eg:*array_at(&a,0) = 10;//写入
printf("%d\n",*array_at(&a,0));//读取
当然,写两个分开的函数也是可以的
int* array_at(Array *a, int index)
{
return a->array[index];
这样对吗?并不,注意这个函数的返回类型是个int指针,我们这样写返回的是int
return &(a->array[index]);
}
//我们动态分配的内存空间实际上是不能长大的,是固定的
//所以思路是:重新分配一块空间,把原来空间上的东西复制到新的大空间
void array_inflate(Array *a,int more_size)
{
int *p = (int*)malloc(sizeof(int)*(a->size + more_size));
int i;
for(i=0;isize;i++){
p[i] = a->array[i];
}
//这个循环可以用库函数里的memcpy代替,理论效率更高
free(a->array);
a->array = p;
a->size += more_size;
}
int main()
{
Array a = array_create(100);//创建结构a,数组大小为100
printf("%d\n",array_size(&a));//打印数组大小
*array_at(&a,0) = 10;//设置a[0]=10
printf("%d\n",*array_at(&a,0));//打印a[0]
int number = 0;
int cnt = 0;
//设置输入-1为退出条件
while(number != -1){
scanf("%d",&number);
if(number != -1){
*array_at(&a,cnt++) = number;
}
}
array_free(&a);
return 0;
}
此时可以实现不断输入数值填满数组,但是当数组越界时,还需要对程序进行一些修改
int* array_at(Array *a, int index)
{
if(index >= a->size){
array_inflate(a, index - a->size + 1);
//因为每次读入一个数,所以就让数组上标增长1,
//然而每次增长1都要重新申请,然后完整复制之前的数据,
//因此我们定一个单位,每次越界都增长一个单位的大小,这样更加高效
array_inflate(a, (index/BLOCK_SIZE + 1)*BLOCK_SIZE - a->size);
//比如我要访问102,block大小为20,现在只有100,也就是5个block,
//那么102/20=5,5+1=6,6*20=120,120-100=20,于是函数知道要增长20
//如果越界更多,用这个公式也可以一次性算好
}
return &(a->array[index]);
}
所以有什么更好的方式呢?
我们可以从申请一块新的内存,转为申请一块block(固定大小)的内存,然后再将它们连接起来,越界后告诉你该去哪里存储或读取,这样不需要拷贝数据额外增加时间,也解决了内存问题。
以int类型的数组为例,如何实现多个数组的连接呢?
一种思路是这样的:将每个单元分成两部分,前面是数据,后面是一个指针,指向下一个单元的数据。在开始和末尾的单元还要做个标记来确定整个数组的大小。
就有这样的东西
那么在程序中如何实现呢?首先是结点
#ifndef _NODE_H_
#define _NODE_H_
//结点,并重命名为Node
typedef struct _node{
int value;
struct _node *next;//指向下一个的指针(因为下一个是同类型)
//为什么不直接Node *next呢?因为这时候Node还没生成呢
}Node;
#endif
现在有一个使用场景,就是我不知道有多少份数据,但是我要一个一个录入,那么我读到第一个数据,怎么能让它出现在内存的第一部分,更进一步说,怎么能存放到链表头?
#include"node.h"
#include
#include
//typedef struct _node{
// int value;
// struct _node *next;
//}Node;
int main()
{
//创建一个指针作为head,指向NULL(啥也不指)
Node *head = NULL;
int number;
do{
scanf("%d",&number);
if(number != -1);
//先创建一个Node结构体,add to linked-list
Node *p = (Node*)malloc(sizeof(Node));
p->value = number;
p->next = NULL;
//首先给*p分配内存,然后p所指的value接收传入的number,p所指的next指向NULL
//然后找到最后的单元,find the list
Node *last = head;//先指向头,从头开始遍历找尾
//这里的循环,当数组只有头时,last=head=NULL,结束条件是无效的,
//所以要判断一下last是不是NULL
if(last){
while( last->next ){//如果last还指向一个后继
last = last->list;//让被指的元素成为last
}
//链接新增单元和原来的最后一段单元,attach
last->next = p;//让新增的p成为尾
}else{
head = p;
//如果条件不成立,last指向NULL,说明现在链表是空的,
//就让head指向第一个单元
}
} while (number != -1);
return 0;
}
知道了如何实现链表,我们在更多使用场景是将其封装成一个函数来使用,这里面也有一些问题需要注意
#include"node.h"
#include
#include
//typedef struct _node{
// int value;
// struct _node *next;
//}Node;
void add(Node* head,int number);
int main()
{
Node *head = NULL;
int number;
do{
scanf("%d",&number);
if(number != -1);
add(head,number);//调用函数
}
} while (number != -1);
return 0;
}
void add(Node* head,int number)
{
// add to linked-list
Node *p = (Node*)malloc(sizeof(Node));
p->value = number;
p->next = NULL;
// find the list
Node *last = head;
if(last){
while( last->next ){
last = last->list;
}
// attach
last->next = p;
}else{
head = p;
}
在函数里会对head进行修改,但是属于本地变量,因此传进来的head不会有变动
一个解决方法是,把头指针Node* head定义成全局变量
然而全局变量导致程序只能处理本次的数据
那我返回一个head,并且调用函数时候写为head = add(head,number),可行吗?这当然是有进步的
然而,如果忘记写head=,对一个空链表使用原来的函数就会导致程序出错,于是我们有第三种方案:
传head的指针进去!
#include"node.h"
#include
#include
//typedef struct _node{
// int value;
// struct _node *next;
//}Node;
Node* add(Node* head,int number);
int main()
{
Node *head = NULL;
int number;
do{
scanf("%d",&number);
if(number != -1);
add(head,number);
}
} while (number != -1);
return 0;
}
Node* add(Node** pHead,int number)//这里用Node** pHead
{
// add to linked-list
Node *p = (Node*)malloc(sizeof(Node));
p->value = number;
p->next = NULL;
// find the list
Node *last = *pHead;//这里*pHead
if(last){
while( last->next ){
last = last->list;
}
// attach
last->next = p;
}else{
head = p;
}
}
好处:给改进程序带来很多可能
#include"node.h"
#include
#include
//typedef struct _node{
// int value;
// struct _node *next;
//}Node;
typedef struct _list{
Node* head;
//在这个基础上可以扩充功能,比如加一个尾指针代替遍历的部分
}List;
void add(Node* head, int number);
int main()
{
List list1;//创建一个结构体并初始化
list1.head = NULL;
int number;
do{
scanf("%d",&number);
if(number != -1);
add(&list1,number);
}
} while (number != -1);
return 0;
}
void add(List* pList,int number)//传入的是List指针,且函数返回类型void
{
// add to linked-list
Node *p = (Node*)malloc(sizeof(Node));
p->value = number;
p->next = NULL;
// find the list
Node *last = pList->head;//list里的head
if(last){
while( last->next ){
last = last->list;
}
// attach
last->next = p;
}else{
pList->head = p;//p=plist指向的head
}
}
想要输出遍历进去的数据,怎么做呢?
int main()
{
List list1;
list1.head = NULL;
int number;
do{
scanf("%d",&number);
if(number != -1);
add(&list1,number);
}
} while (number != -1);
//创建指针
Node *p;
//遍历输出
for(p=list.head; p ;p=p->next)
{
printf("%d\t",p->value);
}
printf("\n");
return 0;
}
然后将其也封装成一个函数
void print(List *pList);
int main()
{
List list1;
list1.head = NULL;
int number;
do{
scanf("%d",&number);
if(number != -1);
add(&list1,number);
}
} while (number != -1);
print(&list);
return 0;
}
void print(List *pList){
Node *p;
for(p=list.head; p ;p=p->next)
{
printf("%d\t",p->value);
}
printf("\n");
}
void found(List *pList);
int main()
{
List list1;
list1.head = NULL;
int number;
do{
scanf("%d",&number);
if(number != -1);
add(&list1,number);
}
} while (number != -1);
scanf("%d",&number);
found(&list,number);
return 0;
}
void print(List *pList,int number){
Node *p;
int isFound = 0;
for(p=list.head; p ;p=p->next)
{
if(p->value == number){
printf("找到了!\n");
isFound = 1;
break;
}
}
if(!isFound){
printf("没找到!\n");
}
}
如果要删除的链表不是头也不是尾:
- 要将删除部分前一个单元的指针指向后一个单元
- 释放掉删除的链表
问题来了,我们有指针指向下一个,但没有指向前面的,如何获得前面单元的指针呢?
如果是双向链表,这个问题就迎刃而解,所以我们仿照它做一个吧!
步骤:
int main()
{
List list1;
list1.head = NULL;
int number;
do{
scanf("%d",&number);
if(number != -1);
add(&list1,number);
}
} while (number != -1);
scanf("%d",&number);
Node *q;
for(q=NULL,p=list.head; p ;q-p,p=p->next)//两个指针同时往下移一位
{
if(p->value == number){
if(q){
q->next = p->next;
}else{
list.head = p->next;
}
free(p);
break;
}
}
return 0;
}
在使用指针的时候,出现在->左侧的指针代表你要访问指针所指的值了,所以这个指针不能指向NULL,这时候就要仔细检查下它是不是NULL,有没有足够的代码检查。
像这个程序中,for循环的条件里就是p,如果是NULL则会退出循环,然而q没有保证,这就是程序的边界条件。
如果p在第一个就找到了,q指向的是NULL,要执行删除时,我们不是让q的next指向p的next,而是让head指向p的next。所以这里需要一个额外的判断条件,当q存在,也就是不指向NULL时才让q->next=p->next;否则的话让head指向p->next
链表中所有的结点都是malloc分配的,当你想要删掉整个链表,要怎么做?
int main()
{
List list1;
list1.head = NULL;
int number;
do{
scanf("%d",&number);
if(number != -1);
add(&list1,number);
}
} while (number != -1);
Node *q;
for(p=head; p ;p=q){
q = p->next;
free(p);
}
return 0;
}