一、可变数组
编写可变数组函数库
- 创建数组
 
- 回收数组
 
- 获取数组中单元个数
 
- 访问数组中的某个单元
 
- 增长数组长度
 
定义Array结构体:
定义为指针类型的结构体,似乎可以简化某些函数的调用传递参数,节省内存空间?
1 2 3 4 5 6 7 8 9
   | #ifndef ARRAY_H #define ARRAY_H
  typedef struct{     int *array;     int size; }*Array;
  #endif
   | 
 
但是,当我们在函数内部创建变量的时候,这个时候a指向的是已经存在的内存空间。
1 2 3 4
   | void test() { 	Array a; }
   | 
 
所以,一般情况下,定义结构体变量不要加*,不要定义指针类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | #ifndef ARRAY_H #define ARRAY_H
  typedef struct{     int *array;     int size; }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);
  #endif
   | 
 
函数实现:
创建可变数组
思考:为什么不返回指针?
1 2 3 4 5 6 7
   | Array array_create(int init_size) {     Array a;     a.size = init_size;     a.array = (int*)malloc(sizeof(int)*a.size);     return a; }
   | 
 
释放数组空间
1 2 3 4 5 6 7
   | void array_free(Array *a) { 	free(a->array); 	a->array = Null; 	a->size = 0; 	 }
   | 
 
获取数组大小
思考:为什么不直接a.size获取大小?
- 目的:封装,保护了a的size,防止被修改,也利于后期维护
 
1 2 3 4
   | int array_size(const Array *a) {    return a->size;  }
   | 
 
获取数组中对应元素的位置
思考:为什么要返回int类型指针而不直接返回int
- 使用
array_at(&a,0)取地址 
- 使用
*array_at(&a,0)=10;可以直接进行赋值操作 
当然,也可以重新编写array_set,array_get两个函数,分别用于赋值和取值
1 2 3 4
   | int* array_at(Array *a,int index) {     return &(a->array[index]); }
   | 
 
增长数组空间
1 2 3 4 5 6 7 8 9 10 11
   | void array_inflate(Array *a,int more_size) {     int*p = (int*)malloc(sizeof((int)*(a->size+more_size));     int i;     for(i=0;i<a->size;i++){         p[i]=a->array[i];     }     free(a->array);     a->array = p;     a->size += more_size; }
   | 
 
不断写入数组内容
一直循环下去,总会出现数组越界的情况。
于是,继续修改array_at
引入block概念
 二、 可变数组的缺陷
- 拷贝时间开销大
 
- 在内存受限的情况下,可能难以申请到更大的内存空间
 
- 
 
解决方案:
不去修改原来的内存空间,链接1个block即可拓展数组长度
 三、链表
链表中的元素簇称为“节点”
每个节点仅包含两个部分:
1 2 3 4
   | typedef struct _node{     int value;     struct _node next; }Node;
  | 
 
思考:为什么不用:
因为在这一行还没有执行Node的类型重定义,编译器无法识别。
使用struct _node{
}定义了struct _node类型的结构体
使用typedef … Node重定义了struct _node类型
程序:循环输入数字,如果为-1则退出,
 1. 定义head
 2.定义第一个节点
1 2 3
   | Node *p = (Node*)malloc(sizeof(Node)); p->value = number; p->next = Null;
   | 
 
 3. 定义last
1 2 3 4 5 6
   | Node *last = head; while(last -> next) {     last = last -> next; } last->next = p;
   | 
 
 4. 链表函数——添加节点
实现一个创建节点的函数:
缺点:函数内部对head进行改变,但是并不影响外部的head(PS:这里的head为指针)
tips:尽量少用全局变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | void add(Node* head,int number) {          Node *p = (Node*)malloc(sizeof(Node));     p->value = number;     p->next = NULL;          Node *last = head;     if(last){         while(last->next){             last = last->next;         }                  last->next = p;     }else{         head = p;     } }
   | 
 
| |
| |
/
修改程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | Node* add(Node* head,int number) {          Node *p = (Node*)malloc(sizeof(Node));     p->value = number;     p->next = NULL;          Node *last = head;     if(last){         while(last->next){             last = last->next;         }                  last->next = p;     }else{         head = p;     }     return head;  }
   | 
 
在主程序中,即可实现改变head的操作,但是这也存在一个缺点,如果忘记调用head = …,程序会报错,封装还不够完美
| |
| |
/
第三种方案,传head改为传递head的指针(实际上是指针的指针)
tips:数据结构
实际上,现在这个函数,return返回值,以及主程序中的赋值都可以不进行,提高了程序的易用性,根本原因是因为pHead是head的指针,即指针的指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   | Node* add(Node** pHead,int number) {          Node *p = (Node*)malloc(sizeof(Node));     p->value = number;     p->next = NULL;          Node *last = *pHead;     if(last){         while(last->next){             last = last->next;         }                  last->next = p;     }else{         *pHead = p;     }     return *pHead;  }
   | 
 
| |
| |
/
第四种方案 ,添加一个List类型结构体
1 2 3
   | typedef struct _list{     Node *head; }List;
  | 
 
这样,函数可以改写为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | void add(List *plist,int number) {          Node *p = (Node*)malloc(sizeof(Node));     p->value = number;     p->next = NULL;          Node *last = plist->head;     if(last){         while(last->next){             last = last->next;         }                  last->next = p;     }else{         plist->head = p;     } }
   | 
 
思考:调用这个函数,last每次都要从head开始指向,执行了没有必要的操作,如何让函数执行完之后,last指向保持上一次的指向?
| |
| |
/
1 2 3 4
   | typedef struct _list{     Node *head;     Node *tail; }List;
  | 
 
实现:
在 List 结构体中添加一个指向链表尾节点的指针 tail,然后每次添加节点时,直接更新 tail 的指向。这样可以避免每次都从头开始遍历链表,优化了插入操作的效率。
 修改后的代码示例
- 修改 
List 结构体:添加一个 tail 指针,指向链表的最后一个节点。 
- 修改 
add 函数:在每次添加新节点时,直接更新 tail 指针。 
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | void add(List *plist,int number) {          Node *p = (Node*)malloc(sizeof(Node));     p->value = number;     p->next = NULL;          if(plist->tail){             plist->tail->next = p;     }else{         plist->head = p;     }     plist->tail = p;   }
   | 
 
这种方式通过维护一个指向尾节点的指针,可以避免每次添加节点都从头遍历,优化了链表操作的效率。
 5. 链表函数——打印
1 2 3 4 5 6 7
   | void print(List *list){     Node *p;     for(p=list->head;p;p=p->next){         printf("%d\t",p->value);         printf("\n");     } }
  | 
 
 6. 链表函数——删除
 7. 链表函数——清除
1 2 3 4 5 6 7 8 9 10 11 12 13
   | void clear(List *plist) {     Node *current = plist->head;     Node *nextNode;
      while (current != NULL) {         nextNode = current->next;           free(current);                      current = nextNode;             }
      plist->head = NULL;       plist->tail = NULL;   }
  |