概述
最近了解到了一点数据结构入门的东西, 在学习后, 根据思路, 设计一个C语言
实现的简单的动态数组的实现和相关实现思想
,方法
的讨论, 以及详细的讲解过程步骤。运用 C++ 的面向对象思想 , 依托C语言结构体实现。
本文章使用C++/C语言的命名方式,并且命名规范, 严格遵循C语言程序设计的思想
1. 准备知识
malloc() 函数 , relloc() 函数, 指针, struct 结构体 , 一个脑袋, 一双手
本教程适应的C语言版本为C89 以上, 适用的编译器为 VS, VSCode , CLion , CodeBlocks
等除了VC++6.0 之外的C++编译器- 使用C++面向对象/面向对象的思想,设计思想,留有可扩增接口,函数,使用
.cpp
文件
3. 步骤分析
- 需求分析:实现什么功能
- 声明对应函数,确定正确形式,函数参数,返回值的合理性
- 设计函数体, 考量, 优化设计
- 测试,优化
需求分析
1, 动态数组的结构体: 指针(数组), 长度length: 指针指向内存(数组)含有的节点个数; Capacity: 容量, 表示当前指针所指向的线性内存的容量
2. 初始化结构体(动态数组)函数:动态申请内存空间,初始化长度和容量
3.声明增删改查插,统计,替换,转换,排序等函数
4.实现对应的函数,并且协调调度
5.运行测试/优化
1.结构体的设计
为了更加便捷的更改和控制动态数组的类型和初始化长度, 我们使用 #define 和 tepydef 进行宏定义 SIZE = 16 动态数组的容量为16 C++, Java中动态数组的底层默认定义长度为16
。typedef 进行类型定义(泛型) T 为 你想要定义的类型 (int ,double , char , float 等) 。
#define SIZE 16 //定义默认长度
typedef int T; //定义可更改的类型, 泛型编程思想
结构体成员属性(C++面向对象思想) 及结构体中的变量 , 一个类型定义的 T 的指针变量,用来指向连续的内存空间 及数组 T* matrix
; 数组的长度 length = 0
默认为 0 ; 数组的容量默认为16 -> capacity = 16
;
typedef struct Array
{
//动态指针
T* matrix = NULL;
//默认值: 0
int length = 0 ;
//初始化容量: 16
int capacity = 16;
}array;
2.初始化/构造动态数组
~~初始化结构体的函数 init_array()
, 称为构造函数, 构造函数使用其类或者结构体 struct
的名字来直接进行声明,而且没有返回值, 所以这里我们直接使用 Array()
来作为构造函数 ; 用来给结构体的成员属性,成员函数做初始化: 动态数组申请 16 个空间的数组, 长度len = 0 , capacity = 16
; 并且可以使用重载函数(指函数参数类型不同或者参数个数不同或者参数顺序不同的同名函数), 实现 指定参数长度 len
的动态内存分配(数组初始化) 。 以上这些函数和接下来的函数都是属于结构体的成员(成员函数)所以都是声明结构体内部,
但是声明在结构体外部: 使用作用域:: 进行定位(C++类的设计思想–类内声明,类外实现,有助于简化类)
typedef struct Array
{
//动态指针
T* matrix = NULL;
//默认值: 0
int length = 0 ;
//初始化容量: 16
int capacity = 16;
//构造函数:默认初始化为 0
Array() ;
//有参构造: 传入指定是数组长度的构造初始化
Array(int init_len) ;
//初始化函数: C语言风格
bool init_array() ;
//C语言风格: 指定长度的初始化函数
bool init_array(int init_len);
}array;
//默认构造函数:C++风格
Array::Array()
{
//调用指定长度的构造函数
Array(SIZE) ;
}
Array::Array(int init_len)
{
matrix = (T*)malloc(init_len * sizeof(T)) ;
length = 0 ;
capacity = init_len ;
}
//C语言风格的初始化函数: 函数体与构造函数相同
bool Array::init_array()
{
return init_array(SIZE) ;
}
bool Array::init_array(int init_len)
{
matrix = (T*)malloc(init_len * sizeof(T)) ;
length = 0 ;
capacity = init_len ;
return this->matrix != NULL ;
}
3.析构函数
大家不要被名字吓到了 , 析构函数: 作用就是拿来释放动态申请的内存, 和关闭一些流资源, 大家所熟悉的 文件指针最后需要调用函数 free ( fp)
就是用来释放 文件指针File* fp
的所以析构函数 只是做一个将我们申请到的动态内存的释放,销毁我们的对象(C++面向对象思想) 。析构函数的写法我们提供两种:
- C语言风格:
free_array() ;
释放指针 - C++风格:
~Array()
释放对象 , 在C++面向对象思想中 , 析构函数声明形式为: 一个波浪号’~’ 加上 结构体/类的名字即可: 我们这里可以在 ~Array() 中调用 free() 函数
在析构函数中 , 我们释放动态申请得到的内存和指针, 避免造成野指针和内存泄露(OOM , Java虚拟机概念)
typedef struct Array
{
//动态指针
T* matrix = NULL;
//默认值: 0
int length = 0 ;
//初始化容量: 16
int capacity = 16;
//构造函数:默认初始化为 0
Array() ;
//初始化函数: C语言风格
bool init_array() ;
//C语言风格: 指定长度的初始化函数
bool init_array(int init_len);
//有参构造: 传入指定是数组长度的构造初始化
Array(int init_len) ;
//析构函数: C++思想风格
~Array() ;
//析构函数: C语言风格
bool free_array();
}array;
Array::~Array()
{
length = 0;
capacity = 0 ;
//释放动态数组指针所指向的那块内存空间
delete matrix ; //delte: C++思想: 释放堆栈空间,释放指针指向的内存空间
matrix = NULL ;
}
bool Array::free_array()
{
//释放动态数组指针
free(matrix) ;
matrix == NULL ;
if (this->matrix == NULL)
{
//数组指针指向内存为 NULL ,释放成功
printf("success to free pointer_array\n") ;
length = 0;
capacity = 0 ;
return true ;
} else
{
printf("fail to free pointer_array\n") ;
return false;
}
}
4.函数声明
声明结构体的成员操作函数: 对结构体进行以下功能的函数声明
- 增删改查插
- 统计, 定位 , 比较
- 获取 , 替换, 清除
- 拷贝(复制) , 获取子数组 , 排序
- to_char_array 转换为字符串, 遍历输出数组
- 运算符重载等
根据以上功能模块设计更加详细的更加细致的函数:
- 增加,插入动态数组元素/节点, 扩容:
//增加节点函数 插入函数 , 扩容函数,
//增加一个节点
int push_front(T node); //头插
int push_back(T node); //尾插
int insert(T node, int index); //插入指定位置
//插入数组
int insert(T* node) ; //插入一个对应数组元素类型的数组: 这里可以拓展区间插入,指定个数插入,读者感兴趣可以自己设计
int insert(T* node , int brgin_index ,int end_index, int start_pos) ; //插入自指定区间的
int insert_array(Array src); //插入一个动态数组中的数组
int insert_array(Array src , int start ); //插入指定区间的动态数组到指定位置
int insert_array(Array src , int start , int len ); //插入指定区间和长度的动态数组到指定位置
int insert_array(Array src , int start , int start_index, int end_index) ; //选择指定源数组
//扩容函数
int add_capacity();
int add_capacity(int new_capacity); //扩充指定大小的空间
- 删除节点元素
//删除节点: 头,尾,指定位置,元素
int pop_front();
int pop_back();
int erase(double index); //这里因为T的宏定义存在冲突问题, 必须把index参数设置为doule 然后取int 就行了
int erase(T node ) ;
//删除动态数组中存在的所有函数参数数组中的节点元素
int erase(T* arr) ;
int erase(Array arr) ;
//删除指定区间的元素
int erase(int start , int end) ;
- 修改节点元素
//修改指定位置的节点
int update(T new_node , int index );
int set(T old_node , T new_node) ;
int set(T* src , int start_pos) ;
//插入指定了区间的源数组片段到目标数组的指定起始位置处
int update(T* src , int start_pos, int begin_index, int end_index) ;
- 查找节点元素
//查找节点
int index(T node);
int last_index(T node) ;
//查找是否含有指定数组
//查找第一次出现数组的下标
int find_array(T* arr) ;
int find_array(Array arr);
int find(T node);
int find_last(T node);
bool contains(T node) ;
bool contains_array(T* node) ;
bool contains_array(Array arr);
//是否含有指定数组,忽略数组的顺序
bool contains_array_ignore_order(T* node) ;
bool contains_array_ignore_order(Array arr);
- 统计动态数组节点
//统计节点数
int count(T node);
- 定位节点
//定位节点
int index(T node);
int index_last(T node) ;
//查找第一次出现数组的下标
int index_array(T* arr) ;
int index_array(Array arr);
- 比较两个动态数组的是否相等
//判断动态数组是否是相等
bool equals(Array arr);
bool equals(T* arr) ;
- 比较两个数组的大小
//返回数组的差值:基本数据类型
T compare(T* arr);
T compare(Array arr);
//运算符重载 > , == : 直接通过运算符进行判断数组之间的大小
int operator> (Array arr);
int operator== (Array arr);
int operator< (Array arr) ;
- 获取动态数组的元素.节点
//获取数组的元素
T get(int index) ;
T* get_nodes(int start_index , int end_index) ;
Array get_array(int start_index , int end_index);
- 替换指定区间,位置,元素处的节点
//替换指定区间,位置,元素处节点
bool replace(int index , T new_node) ;
//用指定元素替换需要被替换的元素
bool repalce(T new_node ,T old_node) ;
//使用 node 替换指定区间的元素
bool replace(int start, int end , T replacement) ;
- 清除,清空数组
//清空,清除整个数组
bool clear();
bool clear(int start , int end) ;
- 拷贝,复制动态数组: 拷贝函数, 传入一个src 源数组, 返回一个他的深拷贝数组/动态数组
//拷贝,复制动态数组: 拷贝构造函数
bool copy(T* arr);
bool copy(Array arr) ;
//拷贝指定了起始位置, 拷贝长度的数组
bool copy(T* arr , int start , int len) ;
bool copy(Array arr , int start , int len ) ;
- 获取到数组的子区间
这个作为拓展,留个作者或者, 读者下来自己慢慢搞 ,( 哈哈哈 , 主要是不会 )
- 根据排序规则排序数组: 因为C语言没有提供排序函数所以这里需要自己重写一些排序规则, 或者自定义仿函数进行自定义排序; 此处使用函数指针的方式来传递自定义的函数 因为C语言没有 仿函数的概念, 而又要思想函数作为形式参数传递, 那么只能使用
函数指针
来传递:
格式如下:
double cal(int); // prototype
double (*pf)(int); // 指针pf指向的函数, 输入参数为int,返回值为double
pf = cal; // 指针赋值
void estimate(int lines, double (*pf)(int)); // 函数指针作为参数传递
double y = cal(5); // 通过函数调用
double y = (*pf)(5); // 通过指针调用 推荐的写法
double y = pf(5); // 这样也对, 但是不推荐这样写
//这里根据Java和 C++ 中 sort() 函数的设计思想采用:双向快排
void sort(); //默认升序
void sort_desc() ; //降序排列
//通过自定义函数来进行排序
void sort(T (*fp)(T , T)) ;
- 遍历数组:
//打印输出函数
void display();
- 运算符重载等: 通过对 + , - , * , / , > < == , ++ , – , [ ] 等运算符的重载增加动态数组访问和使用的高效与便捷
https://www.runoob.com/cplusplus/binary-operators-overloading.html
//运算符重载
int operator== (T elm);
int operator<(T elm) ;
int operator>(T elm) ;
Array operator--() ;
//虚拟参数, 后置--
Array operator--(int );
Array operator++() ;
//后置++
Array operator++(int ) ;
//四则运算 ,将两个数组进行四则运算
T operator+(Array arr) ;
T operator-(Array arr);
T operator*(Array arr);
T operator/(Array arr);
T operator%(Array arr) ;
//重载下标运算, 可以直接通过下标访问
T operator[](int index) ;
//重载 << 运算符 , 对动态数组直接进行赋值和拷贝
Array* operator<<(Array arr);
Array* operator>>(Array arr);
___________________________________________________________________________
贴一个总的集合:
typedef struct Array
{
//动态指针
T* matrix = NULL;
//默认值: 0
int length = 0 ;
//初始化容量: 16
int capacity = 16;
//构造函数:默认初始化为 0
Array() ;
//初始化函数: C语言风格
bool init_array() ;
//C语言风格: 指定长度的初始化函数
bool init_array(int init_len);
//有参构造: 传入指定是数组长度的构造初始化
Array(int init_len) ;
//析构函数: C++思想风格
~Array() ;
//析构函数: C语言风格
bool free_array();
//成员函数
//增加节点函数 插入函数 , 扩容函数,
//增加一个节点
int push_front(T node);
int push_back(T node);
int insert(T node, int index);
//插入数组
int insert(T* node) ;
//插入指定区间的数组到指定的目标数组的指定开始位置 ;
int insert(T* node , int begin_index ,int end_index, int start_pos) ;
int insert_array(Array src);
//插入指定区间的动态数组到动态数组的指定区间
int insert_array(Array src , int start );
//插入指定长度的的数组到指定区间的目标数组中
int insert_array(Array src , int start, int len );
//插入指定起始,结束下标的动态数组元素到指定起始坐标的目标数组中
int insert_array(Array src , int start_index, int end_index ,int start ,) ;
//扩容函数
int add_capacity();
int add_capacity(int new_capacity);
//删除节点: 头,尾,指定位置,元素
int pop_front();
int pop_back();
int erase(double index); //这里因为T的宏定义存在冲突问题, 必须把index参数设置为doule 然后取int 就行了
int erase(T node ) ;
//删除动态数组中存在的所有函数参数数组中的节点元素
int erase(T* arr) ;
int erase(Array arr) ;
//删除指定区间的元素
int erase(int start , int end) ;
//修改节点
//修改指定位置的节点
int update(T new_node , int index );
int set(T old_node , T new_node) ;
int set(T* src , int start_pos) ;
//插入指定了区间的源数组片段到目标数组的指定起始位置处
int update(T* src , int start_pos, int begin_index, int end_index) ;
//查找节点
int find(T node);
int find_last(T node);
//查找是否含有指定数组
//查找第一次出现数组的下标
int find_array(T* arr) ;
int find_array(Array arr);
bool contains(T node) ;
bool contains_array(T* node) ;
bool contains_array(Array arr);
//是否含有指定数组,忽略数组的顺序
bool contains_array_ignore_order(T* node) ;
bool contains_array_ignore_order(Array arr);
//统计节点数
int count(T node);
//定位节点
int index(T node);
int index_last(T node) ;
//查找第一次出现数组的下标
int index_array(T* arr) ;
int index_array(Array arr);
//判断动态数组是否是相等
bool equals(Array arr);
bool equals(T* arr) ;
//返回数组的差值:基本数据类型
T compare(T* arr);
T compare(Array arr);
//运算符重载 > , == : 直接通过运算符进行判断数组之间的大小
int operator> (Array arr);
int operator== (Array arr);
int operator< (Array arr) ;
//获取数组的元素
T get(int index) ;
//获取指定区间的基本数据类型数组
T* get_nodes(int start_index , int end_index) ;
//获取指定区间的动态数组
Array get_array(int start_index , int end_index);
//替换指定区间,位置,元素处节点
bool replace(int index , T new_node) ;
//用指定元素替换需要被替换的元素
bool repalce(T new_node ,T old_node) ;
//使用 node 替换指定区间的元素
bool replace(int start, int end , T replacement) ;
//清空,清除整个数组
bool clear();
bool clear(int start , int end) ;
//拷贝,复制动态数组: 拷贝构造函数
bool copy(T* arr);
bool copy(Array arr) ;
//拷贝指定了起始位置, 拷贝长度的数组
bool copy(T* arr , int start , int len) ;
bool copy(Array arr , int start , int len ) ;
//这里根据Java和 C++ 中 sort() 函数的设计思想采用:双向快排
void sort(); //默认升序
void sort_desc() ; //降序排列
//通过自定义函数来进行排序
void sort(T (*fp)(T , T)) ;
//打印输出函数
void display();
//运算符重载
int operator== (T elm);
int operator<(T elm) ;
int operator>(T elm) ;
Array operator--() ;
//虚拟参数, 后置--
Array operator--(int );
Array operator++() ;
//后置++
Array operator++(int ) ;
//四则运算 ,将两个数组进行四则运算
T operator+(Array arr) ;
T operator-(Array arr);
T operator*(Array arr);
T operator/(Array arr);
T operator%(Array arr) ;
//重载下标运算, 可以直接通过下标访问
T operator[](int index) ;
//重载 << 运算符 , 对动态数组直接进行赋值和拷贝
Array* operator<<(Array arr);
Array* operator>>(Array arr);
}array;
//默认构造函数:C++风格
Array::Array()
{
//调用指定长度的构造函数
Array(SIZE) ;
}
Array::Array(int init_len)
{
matrix = (T*)malloc(init_len * sizeof(T)) ;
length = 0 ;
capacity = init_len ;
}
//C语言风格的初始化函数: 函数体与构造函数相同
bool Array::init_array()
{
return init_array(SIZE) ;
}
bool Array::init_array(int init_len)
{
matrix = (T*)malloc(init_len * sizeof(T)) ;
length = 0 ;
capacity = init_len ;
return this->matrix != NULL ;
}
Array::~Array()
{
length = 0;
capacity = 0 ;
//释放动态数组指针所指向的那块内存空间
delete matrix ; //delte: C++思想: 释放堆栈空间,释放指针指向的内存空间
matrix = NULL ;
}
bool Array::free_array()
{
//释放动态数组指针
free(matrix) ;
matrix == NULL ;
if (this->matrix == NULL)
{
//数组指针指向内存为 NULL ,释放成功
printf("success to free pointer_array\n") ;
length = 0;
capacity = 0 ;
return true ;
} else
{
printf("fail to free pointer_array\n") ;
return false;
}
}
5.函数实现
等待更新: 3161880795
6.总结/拓展
繁琐, 不好理解,代码冗余, 复用性不高,由于C语言的局限性,不能使用C++的特性