1. 程式人生 > >C語言實現動態陣列 C語言函式指標和回撥函式

C語言實現動態陣列 C語言函式指標和回撥函式

實現任意資料型別的動態陣列的初始化,插入,刪除(按值刪除;按位置刪除),銷燬功能。、

動態陣列結構體

  實現動態陣列結構體的三個要素:(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 }