模版类即使用模版的泛型方式来编写类似链表的链式物理结构,这篇文章我是以动态数组的方式实现的,所以是List线性物理结构!
1.包含头文件
定义函数执行的正确与错误宏用来判断函数执行,其次在定义类型别名(个人的一个小习惯)
头文件
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
宏与类型别名
typedef int STORAGE_FUNC_I;
typedef void STORAGE_FUNC_V;
typedef bool STORAGE_FUNC_BL;
typedef char* STORAGE_FUNC_CP;
//宏定义
#define STORAGE_ERROR 0
#define STORAGE_SUCCESS 1
2.声明一个模版类
template<typename T>
class List{
};
3.增加私有模版变量
这里是用于类型萃取,其次在增加一个用于保存大小,这样获取时就不用每次都循环遍历了,其次我们使用的是线性数组,所以大小一般是不能直接遍历的,这里保存是最好的方法!
private:
T *Data; //数据
int ListSize; //大小
5.增加构造函数
这里增加两个构造函数,一个是无参,一个是用于长度构造,分配线性动态数组(这里说一下new确实要方便许多)
析构函数我们后面增加
//默认构造
List() :Data(NULL), ListSize(0){
}
//长度构造
List(int Len){
Data = new T[Len]; //分配数组
ListSize = Len;
}
6.编写一个函数:修改数据
//修改数据
STORAGE_FUNC_I Modify(int ArrIndex, T IutPut_Data){
//比对要修改的数据索引是否越界
if (ArrIndex > ListSize){
return STORAGE_ERROR;
}
//因为类型萃取原因,这里我们可以直接修改值,c++已经为我们做了很多繁琐的工作
Data[ArrIndex - 1] = IutPut_Data;
return STORAGE_SUCCESS;
}
这里我来简单说一下这行代码:
Data[ArrIndex - 1] = IutPut_Data;
上面的-1是要求用户从1为下标,而编译器是0
有些类型萃取不是特别懂的同学对这行代码可能有点不太理解
比如当模版变量Data是char*类型时,这样直接赋值是不对的
但是模版类在一开始即声明的时候其实类型就已经被显示的确定了
比如:
List<char*> string;
当我们使用模版类时,声明必须是这样,因为模版类要求使用<>显示指定当前类所使用的模版到底是什么类型
当指定以后,模版类里凡事模版函数还是模版变量都会被c++隐式的定义为指定的变量类型
这样Data也就变成了char*
上面的赋值其实c++在内部并不是直接敷值,而是根据传递进来的类型数据大小,而进而分配一个内存并指向它
在一开始的构造函数里的new数组其实new的是一个指针数组,而c++则会在编译期间给模版类进行繁琐的类型判断和额外的分配内存代码并指向它!
如果不是指针的话,那么c++不会进行分配内存,只是进行内存copy(这里不是引用,除非使用过&)
虽然说这样方便了我们开发不同类型的代码,但是编译也变的慢了,其次运行效率上也不一定会比c语言快
7.获取数据
这里就比较简单了,根据上面说的,这里其实是返回模版指针返回的地址里的值(因为这里模版返回是T而不是T*)
//获取数据
T GetData(int ArrIndex){
//获取数据大小
if (ArrIndex > ListSize){
return STORAGE_ERROR;
}
return Data[ArrIndex - 1];
}
8.获取大小
也是非常简单直接返回我们用于保存大小的变量即可
//获取大小
STORAGE_FUNC_I GetSize(){
return ListSize;//返回大小
}
9.释放
这里将开辟的动态内存释放掉,类本身是存在于栈中的,剩余的空间可以让编译器为我们释放(类指针也是栈中的,只是指向了动态内存而已,释放方式需要外部显示调用而已)
同时别忘记将栈数据清0
最后当调用者线程结束,这个类才真正的被释放掉,这个函数只是用于释放动态开辟的内存
//释放
STORAGE_FUNC_V Relete(){
//析构
if (Data != NULL){
delete[ListSize] Data;
Data = NULL;
}
ListSize = 0;
}
9.1 析构函数
有了Relete之后直接在析构里调用即可,防止在线程结束时程序员没有显示调用Relete,这样其实就实现了智能指针的概念
~List(){
Relete();
}
10.小插曲实验Demo
上面编写了List的基本函数,同时也说了几个知识点,这里来做一个Demo运行看看:
int:
List<int> ITest(2);
ITest.Modify(1, 17);
ITest.Modify(2, 18);
printf("%d,%d", ITest.GetData(1),ITest.GetData(2));
运行:
17,18
char*
List<char*> ITest(2);
ITest.Modify(1, "hello");
ITest.Modify(2, "word!");
printf("%s,%s", ITest.GetData(1),ITest.GetData(2));
运行:
hello, word!
这里为了证明上面第6小节说的理论知识,来给大家证明一下:
List<char*> ITest(2);
ITest.Modify(1, 1);
ITest.Modify(2, 2);
给char*类型传递int数据看看会报什么错误:
error C2664: “STORAGE_FUNC_I List<char *>::Modify(int,T)”: 无法将参数 2 从“int”转换为“char *”
这个是我vs2017上报的错误,可以看到证实了上面所说的(其它编译器可能存在隐式转换一些不严谨的编译器)
11.动态扩充
由于此list在一开始大小就通过构造固定了,所以我们要让它实现动态扩充
实现思路:
申请一个新的Data1然后new原大小+Len
最后在进行复制将旧的复制到新的,然后在释放旧的,再让旧的指向新的即可
//扩充增加
STORAGE_FUNC_I ExpanSion(int Len){
T *Data1 = new T[ListSize + Len];
int i = 0;
for (i = 0; i <= (ListSize + Len) - 1; ++i){
if (i <= ListSize && ListSize != 0){
Data1[i] = Data[i];
}
else{
Data1[i] = 0;
}
}
Relete();
Data = Data1;
ListSize += Len;
return STORAGE_SUCCESS;
}
11.1动态变更
和扩充增加一样,动态变更其实是直接修改大小
比如动态扩充是在原大小上,而动态变更是直接修改大小可增可删
其实现思路一样
//动态变更
STORAGE_FUNC_I ExpanSionEx(int Len){
if (Len <= ListSize){
return STORAGE_ERROR;
}
T *Data1 = new T[Len];
int i = 0;
for (i = 0; i <= Len - 1; ++i){
if (i <= ListSize && ListSize != 0){
Data1[i] = Data[i];
}
else{
Data1[i] = 0;
}
}
Relete();
Data = Data1;
ListSize = Len;
return STORAGE_SUCCESS;
}
这样就达到了动态List的功能,可增加了,但是删除呢?
下面在实现一个删除
12.删除
这个函数只能删除从后开始的索引数据
其实现思路就是循环赋予值,然后省略掉原大小+Len即可
//删除
STORAGE_FUNC_I Delete(int Len){
if (Len >= ListSize){
return STORAGE_ERROR;
}
//分配大小-Len是去掉尾巴
T *Data1 = new T[ListSize - Len];
int LinShi = 0;
//循环到要删除的索引行,-1是因为i从0开始
for (int i = 0; i <= (ListSize - Len) - 1; ++i){
Data1[LinShi++] = Data[i];
}
Relete();
Data = Data1;
ListSize -= 1;
return STORAGE_SUCCESS;
}
其次我们在实现一个根据索引删除的函数
同样都是使用副本进行Copy的,当到达要删除的那一个索引数据时我们不copy就可以了,并且在副本分配大小时少分配一个就可以了,即达到了自动对齐的功能
//删除指定位置上的数据
STORAGE_FUNC_I DeleteIndex(int Index){
if (Index - 1 > ListSize){
return STORAGE_ERROR;
}
T *Data1 = new T[ListSize - 1];
int LinShi = 0;
for (int i = 0; i <= ListSize - 1; ++i){
//判断是否到达索引,到达跳过不赋予值
if (i == Index - 1){
continue;
}
else{
Data1[LinShi++] = Data[i];
}
}
Relete();
Data = Data1;
ListSize -= 1;
return STORAGE_SUCCESS;
}
13.数据清0
注意当指针数组分配完成之后,其会随便指向任意地址,所以我们写这个函数先让它们指向0(然后放到构造里去
这里在说一个小知识点
有很多人好奇,既然指针分配好内存了,讲道理就已经可以直接赋值了,如果在指向其它的那原本分配的内存哪去了?
不就造成了内存泄露?
答:
博主一开始也是这样认为的,但是后来发现,其分配内存的过程中
c++是确定了类型之后,new一个新的类型指针
比如
T是int
c++new了一个int的指针
从而模版变量指向了它
然后在赋予值的时候为其开辟空间,然后让int*的指向这个空间
也就是 T*->int*->Data这样的一个关系
这个是博主做实验(通过一些调用来判断)得出的(并非通过汇编)
如果觉得不对,可以在下方留言
当我们直接让其=0的时候
上面说了c++会做类型萃取其中还会进行很多复杂的工作
其实是T*->然后找到int*然后在讲将int*里的值给0
char*的话也是一样
博主做实验时给char*赋0会弹出NULL,从而不是\0
如果非指针类型的情况下比如int
那么顺序是T*->int
给0则直接复制到T指向的内存空间里,而非二次指向
//数据置0
STORAGE_FUNC_V DataMet(){
if(ListSize <= 0){
return;
}
for (int i = 0; i <= ListSize - 1; ++i){
Data[i] = 0;
}
}
//长度构造
List(int Len){
Data = new T[Len]; //分配数组
ListSize = Len;
DataMet();
}
14. 获取索引
用于获取首尾中的索引
//获取头索引
STORAGE_FUNC_I Hand(){
if (ListSize <= 0){
return STORAGE_ERROR;
}
return 1;
}
//获取尾索引
STORAGE_FUNC_I End(){
if (ListSize <= 0){
return STORAGE_ERROR;
}
return ListSize;
}
//获取中间索引
STORAGE_FUNC_I In(){
if (ListSize <= 0){
return STORAGE_ERROR;
}
return ListSize / 2;
}
15.寻找数据
这里无论任何类型都用=可以直接判断包括字符串,c++会为我们做额外的类型萃取
//寻找数据
STORAGE_FUNC_BL SeekData(T SeekData){
if (Hand() == 0){
return STORAGE_ERROR;
}
for (int i = 0; i <= ListSize-1; ++i){
if (Data[i] == SeekData){
return true;
}
}
return false;
}
STORAGE_FUNC_BL SeekDataToIndex(T SeekData){
if (Hand() == 0){
return STORAGE_ERROR;
}
for (int i = 0; i <= ListSize - 1; ++i){
if (Data[i] == SeekData){
return i + 1;
}
}
return false;
}
好了基本上到这里一个完整的List已经实现了
我们在增加几个操作符吧,让List更加富有生动性
16.操作符
都是调用已经写好的函数
//+
List & operator + (T AddData){
Add(AddData);
}
//-
List & operator - (int Len){
Delete(Len)
}
//[]
List & operator [] (int Len){
return GetData(Len);
}
完整代码:
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
//类型定义
typedef int STORAGE_FUNC_I;
typedef void STORAGE_FUNC_V;
typedef bool STORAGE_FUNC_BL;
typedef char* STORAGE_FUNC_CP;
//宏定义
#define STORAGE_ERROR 0
#define STORAGE_SUCCESS 1
//列表类
template<typename T>
class List{
public:
//默认构造
List() :Data(NULL), ListSize(0), IdStatic(0){
}
//长度构造
List(int Len){
Data = new T[Len]; //分配数组
ListSize = Len;
DataMet();
//id构建
IdStatic = NULL;
}
//修改数据
STORAGE_FUNC_I Modify(int ArrIndex, T IutPut_Data){
//比对要修改的数据索引是否越界
if (ArrIndex > ListSize){
return STORAGE_ERROR;
}
//因为类型萃取原因,这里我们可以直接修改值,c++已经为我们做了很多繁琐的工作
Data[ArrIndex - 1] = IutPut_Data;
return STORAGE_SUCCESS;
}
//获取数据
T GetData(int ArrIndex){
//获取数据大小
if (ArrIndex > ListSize){
return STORAGE_ERROR;
}
return Data[ArrIndex - 1];
}
//扩充增加
STORAGE_FUNC_I ExpanSion(int Len){
T *Data1 = new T[ListSize + Len];
int i = 0;
for (i = 0; i <= (ListSize + Len) - 1; ++i){
if (i <= ListSize && ListSize != 0){
Data1[i] = Data[i];
}
else{
Data1[i] = 0;
}
}
Relete();
Data = Data1;
ListSize += Len;
return STORAGE_SUCCESS;
}
//动态变更
STORAGE_FUNC_I ExpanSionEx(int Len){
if (Len <= ListSize){
return STORAGE_ERROR;
}
T *Data1 = new T[Len];
int i = 0;
for (i = 0; i <= Len - 1; ++i){
if (i <= ListSize && ListSize != 0){
Data1[i] = Data[i];
}
else{
Data1[i] = 0;
}
}
Relete();
Data = Data1;
ListSize = Len;
return STORAGE_SUCCESS;
}
//Add增加
STORAGE_FUNC_I Add(T ADD_Data){
T *Data1 = new T[ListSize + 1];
for (int i = 0; i <= ListSize - 1; ++i){
Data1[i] = Data[i];
}
Data1[ListSize] = ADD_Data;
int Fbl = ListSize;
Relete();
Data = Data1;
ListSize = Fbl + 1;
return STORAGE_SUCCESS;
}
//获取大小
STORAGE_FUNC_I GetSize(){
return ListSize;//返回大小
}
//重新增长
STORAGE_FUNC_V ReGrow(int Len){
Relete();
Data = new T[Len]; //分配数组
ListSize = Len;
//id构建
IdStatic = NULL;
}
//删除
STORAGE_FUNC_I Delete(int Len){
if (Len >= ListSize){
return STORAGE_ERROR;
}
//分配大小-Len是去掉尾巴
T *Data1 = new T[ListSize - Len];
int LinShi = 0;
//循环到要删除的索引行,-1是因为i从0开始
for (int i = 0; i <= (ListSize - Len) - 1; ++i){
Data1[LinShi++] = Data[i];
}
Relete();
Data = Data1;
ListSize -= 1;
return STORAGE_SUCCESS;
}
//删除指定位置上的数据
STORAGE_FUNC_I DeleteIndex(int Index){
if (Index - 1 > ListSize){
return STORAGE_ERROR;
}
T *Data1 = new T[ListSize - 1];
int LinShi = 0;
for (int i = 0; i <= ListSize - 1; ++i){
//判断是否到达索引,到达跳过不赋予值
if (i == Index - 1){
continue;
}
else{
Data1[LinShi++] = Data[i];
}
}
Relete();
Data = Data1;
ListSize -= 1;
return STORAGE_SUCCESS;
}
//数据置0
STORAGE_FUNC_V DataMet(){
if (ListSize <= 0){
return;
}
for (int i = 0; i <= ListSize - 1; ++i){
Data[i] = 0;
}
}
//获取头索引
STORAGE_FUNC_I Hand(){
if (ListSize <= 0){
return STORAGE_ERROR;
}
return 1;
}
//获取尾索引
STORAGE_FUNC_I End(){
if (ListSize <= 0){
return STORAGE_ERROR;
}
return ListSize;
}
//获取中间索引
STORAGE_FUNC_I In(){
if (ListSize <= 0){
return STORAGE_ERROR;
}
return ListSize / 2;
}
//寻找数据
STORAGE_FUNC_BL SeekData(T SeekData){
if (Hand() == 0){
return STORAGE_ERROR;
}
for (int i = 0; i <= ListSize-1; ++i){
if (Data[i] == SeekData){
return true;
}
}
return false;
}
STORAGE_FUNC_BL SeekDataToIndex(T SeekData){
if (Hand() == 0){
return STORAGE_ERROR;
}
for (int i = 0; i <= ListSize - 1; ++i){
if (Data[i] == SeekData){
return i + 1;
}
}
return false;
}
//释放
STORAGE_FUNC_V Relete(){
//析构
if (Data != NULL){
delete[ListSize] Data;
Data = NULL;
}
if (IdStatic != NULL){
free(IdStatic);
IdStatic = NULL;
}
}
~List(){
Relete();
}
//运算符
//+
List & operator + (T AddData){
Add(AddData);
}
//-
List & operator - (int Len){
Delete(Len)
}
//[]
List & operator [] (int Len){
return GetData(Len);
}
private:
T *Data; //数据
char *IdStatic; //关联数据
int ListSize; //大小
};
int main(){
List<int> ITest(2);
ITest.Modify(1, 1);
ITest.Modify(2, 2);
printf("%d,%d", ITest.GetData(1),ITest.GetData(2));
getchar();
return 0;
}
本文以动态数组方式实现模版类List线性物理结构。介绍了包含头文件、声明模版类、增加私有变量、构造函数等步骤,还涉及修改数据、获取数据和大小、释放内存等操作,实现了动态扩充、删除、数据清0等功能,最后增加操作符并给出完整代码。
1369

被折叠的 条评论
为什么被折叠?



