总所周知,Python语言当中的list是可以存储不同类型的元素的,对应到现代C++当中,可以用std::variant或者std::any实现类似的功能。而Python官方的实现当中用到了二级指针,不过抛开这些,我们也可以自己设计一个list的架构,实现多类型值的存储容器。

  下图是自己实现的list的架构,按照这个架构,我们来逐步分析代码。不过为了节省篇幅,我仅仅只实现了一部分的方法,比如append,但是这里我们着重的是容器的设计。

  我们自顶向下分析。list这个结构体是最终要实现的容器,里面包含了一个指向__list的指针,__list里面存着一系列的Node节点。除了指针,还有offset偏移量,记录当前__list指针ptr的偏移量,size是list的元素大小,而最后一个联合体u则为了实现多值存储而塞的一个成员。Node这边,含有一个void类型的指针,它可以指向任意元素的地址,待会我们会将它转换回对应的元素类型,从而获取其指向的值。type记录该指针指向的具体类型。

  以下对应了这三个结构体的实现。

struct Node {
void *data = nullptr;
int type;
}; struct __list {
Node node;
}; struct list {
__list *ptr;
int offset{};
int size; U u; list(int size) : size(size) {
ptr = static_cast<__list *>(malloc(sizeof(__list) * (size + 1)));
} list(const list& other) = default; ~list() {
ptr -= offset;
free(ptr);
}
}

  在分配内存的时候,要注意额外分配多一个空位,因为ptr是指向list最后元素的下一个位置。析构函数的时候也要记得将ptr回退到最开始的位置,不然会出现内存方面的问题。

  在类型方面,这里仅写了几种常用的类型,可以按照实际需要补充更多的类型上去。

enum {
INT,
UINT,
CHAR,
UCHAR,
FLOAT,
DOUBLE
};

  append函数,这里我没有使用泛型实现,而是使用了函数重载,觉得比较好写,以下是int类型的实现,其它类型同理,只需要稍微改改。

void append(uint& __data) {
ptr->node.data = static_cast<void *>(&__data);
ptr->node.type = UINT; if (offset + 1 <= size) {
++ptr;
++offset;
}
else
std::cout << "The list has achived it's capacity\n";
}

  另外,还重载了[]运算符,这里就用到了前面所提到的union了,这里设定了返回值为union,这样可以比较巧妙的处理不同返回值的情况。

U operator[](int index) {
auto it = ptr - offset + index;
auto __data = it->node.data;
int type = it->node.type; switch (type) {
case INT: {
u.intData = *(static_cast<int *>(__data));
u.type = INT;
break;
}
case UINT: {
u.uintData = *static_cast<uint *>(__data);
u.type = UINT;
break;
}
case CHAR: {
u.charData = *static_cast<char *>(__data);
u.type = CHAR;
break;
}
case UCHAR: {
u.ucharData = *static_cast<u_char *>(__data);
u.type = UCHAR;
break;
}
case FLOAT: {
u.floatData = *static_cast<float *>(__data);
u.type = FLOAT;
break;
}
case DOUBLE: {
u.doubleData = *static_cast<double *>(__data);
u.type = DOUBLE;
break;
}
default: {
assert(0);
}
} return u;
}

  为了最终可以遍历元素并且输出出来,还需要对union进行重载一下。

struct U {
union {
int intData;
uint uintData;
char charData;
u_char ucharData;
float floatData;
double doubleData;
}; // To figure out which type we're using
int type; friend std::ostream& operator<<(std::ostream& os, const U& u) {
int type = u.type; switch (type) {
case INT: {
os << u.intData;
break;
}
case UINT: {
os << u.uintData;
break;
}
case CHAR: {
os << u.charData;
break;
}
case UCHAR: {
os << u.ucharData;
break;
}
case FLOAT: {
os << u.floatData;
break;
}
case DOUBLE: {
os << u.doubleData;
break;
}
default: {
assert(0);
}
} return os;
}
};

  (能用switch代替if else就尽量代替)

  到这里,所设计的list就差不多了,剩下的函数可以由读者来拓展。不过还有局限性,可以看看它怎么使用。

int main() {
list lst{3}; std::vector v{1, 2, 3}; for (int i{}; i < v.size(); ++i)
lst.append(v[i]); for (int i{}; i < lst.size; ++i)
std::cout << lst[i] << ' ';
}

  由于没有写对右值数据的处理,所以只能先将想要存的数据存入另一个容器当中。我们再来测试一下。

int main() {
list lst{3}; int a = 1;
double b = 1.1;
char c = 'c'; lst.append(a);
lst.append(b);
lst.append(c); for (int i{}; i < lst.size; ++i)
std::cout << lst[i] << ' ';
}

  运行结果是1, 1.1, c,符合预期。

  以下是完整代码

#include <iostream>
#include <cstdlib>
#include <cassert>
#include <vector>
#include <type_traits> enum {
INT,
UINT,
CHAR,
UCHAR,
FLOAT,
DOUBLE
}; struct U {
union {
int intData;
uint uintData;
char charData;
u_char ucharData;
float floatData;
double doubleData;
}; // To figure out which type we're using
int type; friend std::ostream& operator<<(std::ostream& os, const U& u) {
int type = u.type; switch (type) {
case INT: {
os << u.intData;
break;
}
case UINT: {
os << u.uintData;
break;
}
case CHAR: {
os << u.charData;
break;
}
case UCHAR: {
os << u.ucharData;
break;
}
case FLOAT: {
os << u.floatData;
break;
}
case DOUBLE: {
os << u.doubleData;
break;
}
default: {
assert(0);
}
} return os;
}
}; struct Node {
void *data = nullptr;
int type;
}; struct __list {
Node node;
}; struct list {
__list *ptr;
int offset{};
int size; U u; list(int size) : size(size) {
ptr = static_cast<__list *>(malloc(sizeof(__list) * (size + 1)));
} list(const list& other) = default;
list& operator=(const list& other) = default; ~list() {
ptr -= offset;
free(ptr);
} void append(int& __data) {
if (offset + 1 <= size) {
ptr->node.data = static_cast<void *>(&__data);
ptr->node.type = INT;
++ptr;
++offset;
}
else
std::cout << "The list has achived it's capacity\n";
} void append(float& __data) {
ptr->node.data = static_cast<void *>(&__data);
ptr->node.type = FLOAT; if (offset + 1 <= size) {
++ptr;
++offset;
}
else
std::cout << "The list has achived it's capacity\n";
} void append(double& __data) {
ptr->node.data = static_cast<void *>(&__data);
ptr->node.type = DOUBLE; if (offset + 1 <= size) {
++ptr;
++offset;
}
else
std::cout << "The list has achived it's capacity\n";
} void append(char& __data) {
ptr->node.data = static_cast<void *>(&__data);
ptr->node.type = CHAR; if (offset + 1 <= size) {
++ptr;
++offset;
}
else
std::cout << "The list has achived it's capacity\n";
} void append(u_char& __data) {
ptr->node.data = static_cast<void *>(&__data);
ptr->node.type = UCHAR; if (offset + 1 <= size) {
++ptr;
++offset;
}
else
std::cout << "The list has achived it's capacity\n";
} void append(uint& __data) {
ptr->node.data = static_cast<void *>(&__data);
ptr->node.type = UINT; if (offset + 1 <= size) {
++ptr;
++offset;
}
else
std::cout << "The list has achived it's capacity\n";
} U operator[](int index) {
auto it = ptr - offset + index;
auto __data = it->node.data;
int type = it->node.type; switch (type) {
case INT: {
u.intData = *(static_cast<int *>(__data));
u.type = INT;
break;
}
case UINT: {
u.uintData = *static_cast<uint *>(__data);
u.type = UINT;
break;
}
case CHAR: {
u.charData = *static_cast<char *>(__data);
u.type = CHAR;
break;
}
case UCHAR: {
u.ucharData = *static_cast<u_char *>(__data);
u.type = UCHAR;
break;
}
case FLOAT: {
u.floatData = *static_cast<float *>(__data);
u.type = FLOAT;
break;
}
case DOUBLE: {
u.doubleData = *static_cast<double *>(__data);
u.type = DOUBLE;
break;
}
default: {
assert(0);
}
} return u;
} };

  到这里,一个Pythonic的list就成型了,剩下的其它函数实现方式也就大同小异。在设计list的时候,由于设计到指针,因此对于内存泄露方面需要比较谨慎。以上的实现仅仅涉及到了一级指针,Python官方实现是采用二级指针,感兴趣的话可以去学习学习别人是怎么实现的~

搓一个Pythonic list的更多相关文章

  1. 手搓一个“七夕限定”,用3D Engine 5分钟实现烟花绽放效果

    七夕来咯!又到了给重要的人送惊喜的时刻. 今年,除了将心意融入花和礼物,作为程序员,用自己的代码本事手搓一个技术感十足"七夕限定"惊喜,我觉得,这是不亚于车马慢时代手写信的古典主义 ...

  2. 手搓一个兔子问题(分享一个C语言问题,持续更新...)

    大家好,我是小七夜,今天就不分享C语言的基础知识了,分享一个比较好玩的C语言经典例题:兔子问题 题目是这样的:说有一个穷苦人这天捉到了一只公兔子,为了能繁衍后代他又买了一只母兔子,后来兔子开始生小兔子 ...

  3. 手搓一个C语言简单计算器。

    #include <stdio.h> void xing(int shu); void biaoti(int kong,char * title); void zhuyemian(char ...

  4. Pythonic到底是什么玩意儿?

    http://blog.csdn.net/gzlaiyonghao/article/details/2762251 作者:Martijn Faassen 译者:赖勇浩(http://blog.csdn ...

  5. python pythonic是什么?

    原文地址:http://faassen.n--tree.net/blog/view/weblog/2005/08/06/0 注:Martijn 是 Zope 领域的专家,他为 Zope 系列产品做了许 ...

  6. GeoPackage - 一个简便轻量的本地地理数据库

    GeoPackage(以下简称gpkg),内部使用SQLite实现的一种单文件.与操作系统无关的地理数据库. 当前标准是1.2.1,该版本的html版说明书:https://www.geopackag ...

  7. [LeetCode] Spiral Matrix 螺旋矩阵

    Given a matrix of m x n elements (m rows, n columns), return all elements of the matrix in spiral or ...

  8. Visual Studio 2013常用快捷键

    ---恢复内容开始--- 代码选择 1  区域代码选择 按Shift选择整(行)块代码,可配合四个方向键(左右键:选择单个字符,上下键:上下行的当前列).Home(当前行首).End(当前行尾).Pg ...

  9. Redis资料汇总专题

    1.Redis是什么? 十五分钟介绍 Redis数据结构 Redis系统性介绍 一个很棒的Redis介绍PPT 强烈推荐!非同一般的Redis介绍 Redis之七种武器 锋利的Redis redis ...

  10. redis资料汇总

    redis资源比较零散,引用nosqlfan上的文章,方便大家需要时翻阅.大家看完所有的,如果整理出文章的,麻烦知会一下,方便学习. 1.Redis是什么? 十五分钟介绍 Redis数据结构 Redi ...

随机推荐

  1. 详解同为4800W像素的相机传感器,三星GM1和索尼IMX586区别在哪里?

    数字影像之父Bryce Bayer基于RGB模式,通过在感光元件前加上一个滤镜的方法终于实现了彩色照片.Bayer滤镜跨出了照片从黑白到彩色的一大步,但是对于挑剔的人眼来说,每个像素只有一个颜色是远远 ...

  2. 使用官方推荐的库来测react hook组件

    最近写单元测试的时候遇见了一些问题,当我使用使用jest测React. useRef,  React. useEffect时,总是测不到, 然后我去查阅了一下官方文档,它推荐了使用下面这个库 @tes ...

  3. 反汇编分析C语言

    环境 VC6.0环境 空函数反汇编 #include "stdafx.h" void function(){ } int main(int argc, char* argv[]) ...

  4. PostgreSQL 10 文档: SQL 语法

    SQL 命令   这部分包含PostgreSQL支持的SQL命令的参考信息.每条命令的标准符合和兼容的信息可以在相关的参考页中找到. 目录 ABORT - 中止当前事务 ALTER AGGREGATE ...

  5. 2021-7-12 VUE的增删改查功能简单运用

    Vue增删改查简易实例 <!DOCTYPE html> <html> <head> <title> </title> <style t ...

  6. C++ 核心指南之 C++ 哲学/基本理念(下)

    C++ 核心指南(C++ Core Guidelines)是由 Bjarne Stroustrup.Herb Sutter 等顶尖 C+ 专家创建的一份 C++ 指南.规则及最佳实践.旨在帮助大家正确 ...

  7. 如何提升 API-First 设计流程

    一个 API-First 设计应该具有可复用性.互操作性.可修改性.用户友好性.安全性.高效性.务实性,并且重要的是,与组织目标保持一致.这些基本特征将确保 API 能够有效地为 API- First ...

  8. [linux]frp内网穿透

    前言 假设有如下网络拓扑 A可以访问B,但B无法访问A.A和B都能访问C.如果B需要访问A的8000端口,一般有如下方法: 网络管理员做路由转发.硬件层面网络转发,性能一般来说更好,但需要熟悉路由配置 ...

  9. django配置swagger自动生成接口文档以及自定义参数设置

    首先安装swagger所用的包 pip install drf-yasg 然后再settings.py 中注册app 接口采用的token认证,在settings.py配置认证方式 SWAGGER_S ...

  10. MySQL实战实战系列 03 事务隔离:为什么你改了我还看不见?

    提到事务,你肯定不陌生,和数据库打交道的时候,我们总是会用到事务.最经典的例子就是转账,你要给朋友小王转 100 块钱,而此时你的银行卡只有 100 块钱. 转账过程具体到程序里会有一系列的操作,比如 ...