C语言利用动态数组实现顺序表(不限数据类型)

Wesley13
• 阅读 380

实现任意数据类型的顺序表的初始化,插入,删除(按值删除;按位置删除),销毁功能。、

顺序表结构体

  实现顺序表结构体的三个要素:(1)数组首地址;(2)数组的大小;(3)当前数组元素的个数。

1 //顺序表结构体
2 struct DynamicArray{
3     void **addr; //指向数组的首地址(指向数组的指针)
4     int capacity; //数组的大小
5     int size; //当前数组元素的个数
6 };

注意事项:void **addr为二级指针,即数组的元素也为指针,因为我们并不知道用户的输入数据是什么类型,操作数据的地址是最安全的方法。

初始化

  对顺序表进行初始化,实际上为初始化顺序表内的各个成员,另外对输入的参数做边界处理。

 1 //初始化数组,初始化结构体和里面的元素。初始化之后返回该数组,写为void*
 2 void *Init(int capacity_){
 3     if (capacity_ <= 0){
 4         return NULL;
 5     }
 6     
 7     struct DynamicArray *arr = malloc(sizeof(struct DynamicArray));//开辟一个结构体就可以了
 8     if (NULL == arr){
 9         return NULL;
10     }
11 
12     arr->capacity = capacity_;
13     arr->addr = malloc(sizeof(void*)*arr->capacity);//对数组开辟内存
14     arr->size = 0;
15 
16     return arr;
17 }

插入操作

  对于顺序表的插入操作,需要在pos位置处开始的元素统一往后移动一个位置,处理方式为:从后往前挨个移动,从前往后会覆盖。

  注意:(1)考虑顺序表的顺序插入和乱序插入,12行的代码;(2)若顺序表被填满,则需要对现有数组进行扩大空间,这里涉及到四步操作:开辟内存、拷贝数据(尽量使用memcpy)、释放原内存、修改指针指向。(3)对输入参数做边界处理。

 1 //插入值,从后往前挨个移动一位,如果插入的值过大,则扩大数组
 2 void Insert(void *arr_, int pos, void *data){
 3 
 4     struct DynamicArray *arr = (struct DynamicArray *)arr_;
 5 
 6     if (NULL == arr || NULL == data){
 7         return;
 8     }
 9 
10     //对于无效的pos,默认插入到现有元素的后面一个
11     //if (pos < 0 || pos > arr->capacity)
12     if (pos < 0 || pos > arr->size)
13     {
14         pos = arr->size;
15     }
16 
17     //每次调用插入函数都会插入值,因此arr->size++,size一直增长,且没有限制
18     if (arr->size >= arr->capacity)
19     //if (arr->size > arr->capacity)
20     {
21 
22         //开辟新内存
23         int newcapacity = arr->capacity * 2;
24         void **newaddr = malloc(sizeof(void *)*newcapacity);
25 
26         //拷贝数据,尽量使用内存拷贝函数
27         memcpy(newaddr, arr->addr, sizeof(void *) * arr->capacity);
28 
29         //释放原空间
30         if (arr->addr != NULL){
31             free(arr->addr);
32             arr->addr = NULL;
33         }
34 
35         //修改指针指向
36         arr->addr = newaddr;
37         arr->capacity = newcapacity;
38     }
39 
40     for (int i = arr->size - 1; i >= pos; --i){
41         arr->addr[i + 1] = arr->addr[i];
42     }
43     arr->addr[pos] = data; //添加过后,size变化
44     arr->size++;
45 }

遍历操作

  遍历一般的作用为打印数据,但这里并不知道用户的是什么数据,这里由回调函数进行打印(C语言函数指针和回调函数)。

 1 //遍历
 2 void Foreach(void *arr_, void(*_callback)(void *)){
 3     struct DynamicArray * arr = (struct DynamicArray *)arr_;
 4     if (NULL == arr || NULL == _callback){
 5         return;
 6     }
 7     for (int i = 0; i < arr->size; ++i){
 8         _callback(arr->addr[i]);
 9     }
10 }

删除操作

  这里分为按值删除和按位置删除,其中按值操作调用了按位置操作的代码,因此需要注意size--的问题。另外,按值操作也使用了回调函数。

 1 //删除(按值删除,按位置删除)
 2 void DeletePos(void *arr_, int pos){
 3     struct DynamicArray *arr = arr_;
 4     if (NULL == arr){
 5         return;
 6     }
 7 
 8     for (int i = pos; i < arr->size - 1; ++i){
 9         arr->addr[i] = arr->addr[i + 1];
10     }
11 
12     arr->size--;//size会减小
13 }
14 void DeleteValue(void *arr_, void * data, int(*_compare)(void *, void*)){
15     struct DynamicArray *arr = arr_;
16     if (NULL == arr || NULL == data || NULL == _compare){
17         return;
18     }
19     for (int i = 0; i < arr->size; i++){
20         if (_compare(arr->addr[i], data)){
21             DeletePos(arr, i);
22             break; 
23         }
24     }
25     //arr->size--; DeletePos里面已经有size--了
26 }

销毁操作

  注意事项:需要先释放成员函数,再释放结构体。

 1 //销毁
 2 void Destory(void * arr_){
 3     struct DynamicArray *arr = arr_;
 4     if (NULL == arr){
 5         return;
 6     }
 7     if (arr->addr != NULL){
 8         free(arr->addr);
 9         arr->addr = NULL;
10     }
11     if (arr != NULL){
12         free(arr);
13         arr = NULL;
14     }
15 }

元素个数和数组大小函数

  之所以提供这两个函数,是因为不希望用户能直接看到我们定义的结构体内部,也不希望用户可以随便更改,因此我们各个函数的返回值都是void类型,这里提供两个函数,以便用户可以查看元素的个数和数组的大小。

1 int capaArray(void *arr_){
2     struct DynamicArray *arr = (struct DynamicArray *)arr_;
3     return arr->capacity;
4 }
5 
6 int sizeArray(void *arr_){
7     struct DynamicArray *arr = (struct DynamicArray *)arr_;
8     return arr->size;
9 }

测试

  进行测试时,需要自定义打印和比较回调函数,不需要关心void*data是什么,仅仅实现自定义数据的比较和打印即可。另外回调函数使用时不需要任何参数,只需要函数名;另外,测试了结构体和整型顺序表,可以对顺序表结构体中的void **addr为二级指针有更好的理解。

 1 struct Person{
 2     char name[100];
 3     int age;
 4 };
 5 
 6 // 自定义输出函数
 7 void print(void *data_){
 8     if (NULL == data_){
 9         return;
10     }
11     struct Person *data = (struct Person *)data_;
12     printf("name:%s, age:%d\n", data->name, data->age);
13 }
14  //整型自定义输出函数,访问时需要解引用
15 void printInt(void *data_){
16     if (NULL == data_){
17         return;
18     }
19     int *data = (int *)data_;
20     printf("age:%d\n", *data);
21 }
22 
23 //自定义比较函数
24 int compare(void *d1, void *d2){
25     if (NULL == d1|| NULL == d2){
26         return 0;
27     }
28     struct Person *p1 = d1;
29     struct Person *p2 = d2;
30 
31     return (strcmp(p1->name, p2->name) == 0 && (p1->age == p2->age));
32 }
33 
34 void test(){
35     struct Person p1 = {"aaa", 10};
36     struct Person p2 = { "bbb", 20 };
37     struct Person p3 = { "ccc", 30 };
38     struct Person p4 = { "ddd", 40 };
39     struct Person p5 = { "eee", 50 };
40     struct Person p6 = { "fff", 60 };
41     //int p1 = 1;
42     //int p2 = 2;
43     //int p3 = 3;
44     //int p4 = 4;
45     //int p5 = 5;
46 
47 
48     void * arr = Init(4);
49     Insert(arr, 1, &p1);
50     Insert(arr, 2, &p2);
51     Insert(arr, 3, &p3);
52     Insert(arr, 4, &p4);
53     printf("%d\n", capaArray(arr));
54     Insert(arr, 100, &p5);
55     printf("%d\n", capaArray(arr));
56     Insert(arr, 1, &p6);
57     Foreach(arr, print);
58     printf("-----------------\n");
59     DeleteValue(arr, &p2, compare);
60     Foreach(arr, print);
61     Destory(arr);
62 
63 }
64 
65 
66 int main(){
67 
68     test();
69     system("pause");
70     return 0;
71 }
点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
似梦清欢 似梦清欢
1年前
查找算法
顺序查找顺序查找又称为线性查找,对线性表和链表都适用。线性表可以通过数组下标递增来顺序扫描每个元素,链表可以通过next指针依次扫描每一个元素。:::tip指针实现顺序表时,顺序表中是指针时,在定义顺序表的结构体后,需要对顺序表初始化,初始化时为指针申请堆
似梦清欢 似梦清欢
1年前
线性表
线性表的顺序存储实现(数组形式)称为顺序表。线性表顺序表示原理解析这里描述的线性表是逻辑结构的,独立于存储结构。线性表的顺序表示简称顺序表。顺序表实现线性表的方式是使用数组。线性表第一个元素的数组下标是0。另外一种实现顺序表的方法:使用数组方式比动态分配更
Stella981 Stella981
2年前
Hive 删除行, 表 ,清空表
删除行A表数据如下id(String)       name(String)\1                       aaa2                      bbb3                      ccc\
Wesley13 Wesley13
2年前
Java开发者容易犯的十个错误
!(https://oscimg.oschina.net/oscnet/c9f00cc918684fbe8a865119d104090b.gif)Top1.数组转换为数组列表将数组转换为数组列表,开发者经常会这样做:\java\List<StringlistArrays.asList(arr);Arr
Wesley13 Wesley13
2年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Stella981 Stella981
2年前
JS 对象数组Array 根据对象object key的值排序sort,很风骚哦
有个js对象数组varary\{id:1,name:"b"},{id:2,name:"b"}\需求是根据name或者id的值来排序,这里有个风骚的函数函数定义:function keysrt(key,desc) {  return function(a,b){    return desc ? ~~(ak
Wesley13 Wesley13
2年前
Oracle一张表中实现对一个字段不同值和总值的统计(多个count)
需求:统计WAIT\_ORDER表中的工单总数、未处理工单总数、已完成工单总数、未完成工单总数。表结构:为了举例子方便,WAIT\_ORDER表只有两个字段,分别是ID、STATUS,其中STATUS为工单的状态。1表示未处理,2表示已完成,3表示未完成总数。 SQL:  1.SELECT   2
Wesley13 Wesley13
2年前
ES6 新增的数组的方法
给定一个数组letlist\//wu:武力zhi:智力{id:1,name:'张飞',wu:97,zhi:10},{id:2,name:'诸葛亮',wu:55,zhi:99},{id:3,name:'赵云',wu:97,zhi:66},{id:4,na