用 C 语言实现泛型栈

mystack.h

 #ifndef __MYSTACK_H__
#define __MYSTACK_H__ #include <assert.h> // C style,不使用C++的class
typedef struct {
void *elems;
int elemSize; // 元素大小
int logicLen; // 逻辑长度,栈中当前元素个数,也是下个进栈元素的下标
int allocLen; // 分配的最大内存长度
void (*freefn)(void *); // Free函数指针,用于释放栈元素的内存
}stack; // 构造函数
void StackNew(stack *s, int elemSize, void (*freefn)(void *) = NULL) {
s->elemSize = elemSize;
s->logicLen = ;
s->allocLen = ; // 初始为栈开辟4个元素的内存
s->elems = malloc(s->allocLen * elemSize);
assert(s->elems); // 表达式为0则报错
s->freefn = freefn;
} // 析构函数
void StackDispose(stack *s) {
if (s->freefn) {
for (int i = ; i < s->logicLen; i++) {
s->freefn((char *)s->elems + i * s->elemSize);
}
}
free(s->elems);
} void StackPush(stack *s, void *elemAddr) {
if (s->logicLen == s->allocLen) {
s->allocLen <<= ;
s->elems = realloc(s->elems, s->allocLen * s->elemSize);
assert(s->elems); // 如果realloc失败,s->elem还会是原来那片内存,并不是NULL,但realloc函数会返回NULL
}
void *target = (char *)s->elems + s->logicLen * s->elemSize; // 目的地址
memcpy(target, elemAddr, s->elemSize);
s->logicLen++;
} void StackPop(stack *s, void *elemAddr) {
assert(s->logicLen > );
s->logicLen--;
void *source = (char *)s->elems + s->logicLen * s->elemSize; // 栈顶元素地址
memcpy(elemAddr, source, s->elemSize);
} #endif

分析:

1、为了实现存放 int 型、double 型、char * 型、自定义类型元素的栈(泛型栈),需要定义一个指明元素大小的变量 elemSize,在栈初始化时传入以开辟足够大小的空间。注意 malloc() 后 assert() 的运用,在内存申请失败时直接退出。

2、如果存储的是基本数据类型,如 char、int 等或者是成员不包含指针的结构体类型,析构函数只需要 free(elems),而对栈中每个元素则不需要手动 free;如果存储的元素是 char * ,或是指向结构体的指针,或是指向动态申请内存的指针等等,就需要在调用 StackDispose() 之前,将清理栈中元素的方式的信息传给构造函数,即函数指针(指向一段代码)。作为栈结构体的第 5 个成员,它默认是空指针(对于基本数据类型),要么是某个合法的 freefn() 指针(对于用户指定数据结构)。

3、入栈函数 StackPush() 使用 realloc 不断地增长栈的内存空间。对于基础数据类型,elems[logicLen] 即为栈顶元素;而泛型栈中,则需要从具体的内存地址中找到相应的栈顶位置 target,这里同样使用了 void * → char * 的小技巧,并使用 memcpy() 将要入栈的元素拷贝到栈顶地址中去。

4、出栈函数 StackPop() 将出栈元素放在函数的参数列表中,由用户提供一个地址,栈获取栈顶地址指向的元素并将其拷贝到该地址指向的内存中。

main() 函数

 void StrFree(void *vp) {
free(*(char **)vp);
} int main() {
/* int栈 */
stack intStack;
StackNew(&intStack, sizeof(int));
for (int i = ; i <= ; i++) {
StackPush(&intStack, &i);
}
int top;
for (int i = ; i < ; i++) {
StackPop(&intStack, &top);
cout << top << endl;
}
StackDispose(&intStack);
/* string栈 */
const char *players[] = {"Niko", "Miracle-" ,"Sky" ,"pigff" ,"Yaphets"};
stack strStack;
StackNew(&strStack, sizeof(char *), StrFree);
for (int i = ; i < ; i++) {
char *copy = strdup(players[i]);
StackPush(&strStack, &copy);
}
char *player;
for (int i = ; i < ; i++) {
StackPop(&strStack, &player);
cout << player << endl;
free(player);
}
StackDispose(&strStack);
return ;
}

分析:

1、strFree() 函数,虽然接受的参数 vp 是 void *(为了类型通用),但我们编写该函数时逻辑上知道 vp 是 char ** 类型

2、WHY 不在 StackPop()(弹出栈顶元素)之前,释放栈顶字符串的内存空间???

因为 StackPop() 函数实际上并没有将一份栈顶字符串的拷贝返回给用户,而是获取栈顶字符串所在内存的地址,并用 memcpy() 复制到用户提供的空间(player),即用户获取的是一个指向该字符串的指针,该字符串的所有权由栈交给用户,因此弹出来的 player 应当由用户需要自行 free()。并且字符串拷贝函数 strdup() 在内部调用了 malloc() 动态分配内存,应该与 free() 成对出现。

输出结果:

以上。

【C/C++】泛型栈的更多相关文章

  1. C#图解教程 第十七章 泛型

    泛型 什么是泛型 一个栈的示例 C#中的泛型 继续栈示例 泛型类声明泛型类创建构造类型创建变量和实例 使用泛型的栈的示例比较泛型和非泛型栈 类型参数的约束 Where子句约束类型和次序 泛型方法 声明 ...

  2. .net框架-栈(Stack)

    栈(Stack) 栈代表一个后进先出的集合 栈元素为Object类型 .net框架提供Stack<T>泛型栈类 压栈(Push)和出栈(Pop)是栈的基本操作,压栈入栈顶,出栈也出栈顶. ...

  3. 从一知半解到揭晓Java高级语法—泛型

    目录 前言 探讨 泛型解决了什么问题? 扩展 引入泛型 什么是泛型? 泛型类 泛型接口 泛型方法 类型擦除 擦除的问题 边界 通配符 上界通配符 下界通配符 通配符和向上转型 泛型约束 实践总结 泛型 ...

  4. 如果你也会C#,那不妨了解下F#(5):模块、与C#互相调用

    F# 项目 在之前的几篇文章介绍的代码都在交互窗口(fsi.exe)里运行,但平常开发的软件程序可能含有大类类型和函数定义,代码不可能都在一个文件里.下面我们来看VS里提供的F#项目模板. F#项目模 ...

  5. java泛型编程

    一般的类和方法都是针对特定数据类型的,当写一个对多种数据类型都适用的类和方法时就需要使用泛型编程,java的泛型编程类似于C++中的模板,即一种参数化类型的编程方法,具体地说就是将和数据类型相关的信息 ...

  6. 【python】列出http://www.cnblogs.com/xiandedanteng中所有博文的标题

    代码: # 列出http://www.cnblogs.com/xiandedanteng中所有博文的标题 from bs4 import BeautifulSoup import requests u ...

  7. Node.js 网页爬虫再进阶,cheerio助力

    任务还是读取博文标题. 读取app2.js // 内置http模块,提供了http服务器和客户端功能 var http=require("http"); // cheerio模块, ...

  8. Node.js 网页瘸腿稍强点爬虫再体验

    这回爬虫走得好点了,每次正常读取文章数目总是一样的,但是有程序僵住了情况,不知什么原因. 代码如下: // 内置http模块,提供了http服务器和客户端功能 var http=require(&qu ...

  9. Node.js 网页瘸腿爬虫初体验

    延续上一篇,想把自己博客的文档标题利用Node.js的request全提取出来,于是有了下面的初哥爬虫,水平有限,这只爬虫目前还有点瘸腿,请看官你指正了. // 内置http模块,提供了http服务器 ...

随机推荐

  1. BN多卡同步进行

    为什么不进行多卡同步? BatchNorm的实现都是只考虑了single gpu.也就是说BN使用的均值和标准差是单个gpu算的,相当于缩小了mini-batch size.至于为什么这样实现,1)因 ...

  2. nginx----------nginx日志详细分解

    1.客户端(用户)IP地址.如:上例中的 47.52.45.228 2.访问时间.如:上例中的 [03/Jan/2013:21:17:20 -0600] 3.请求方式(GET或者POST等).如:上例 ...

  3. 记oracle使用expdp将数据导出到asm报错

    报错信息如下: ORA-39002: invalid operationORA-39070: Unable to open the log file.ORA-29283: invalid file o ...

  4. PHP 批量操作删除,支持单个删除

    PHP  执行部分: <?php include('checkadmin.php'); header('Content-Type: text/html; charset=utf-8'); if( ...

  5. GreenDao 使用和数据库升级

    1使用方法 一.添加依赖 在bulid.gradle文件下的dependencies下添加所需依赖   compile 'org.greenrobot:greendao:3.2.2' // add l ...

  6. PTA第一个编程题总结

    7-1 打印沙漏 (20 分) 本题要求你写个程序把给定的符号打印成沙漏的形状.例如给定17个“*”,要求按下列格式打印 ***** *** * *** ***** 所谓“沙漏形状”,是指每行输出奇数 ...

  7. 键盘录入一个文件夹路径,统计该文件夹(包含子文件夹)中每种类型的文件及个数,注意:用文件类型(后缀名,不包含.(点),如:"java","txt")作为key, 用个数作为value,放入到map集合中,遍历map集合

    package cn.it.zuoye5; import java.io.File;import java.util.HashMap;import java.util.Iterator;import ...

  8. Spring笔记 #01# 一个小而生动的IOC例子代码

    索引 Spring容器的最小可用依赖 用XML定义元数据 实例化容器&使用容器 例子中仅包含两种类:英雄类Hero和武器类Weapon. 演示DI:给Hero初始化Weapon 演示AOP:法 ...

  9. Python Kivy writes / read the file on the SD card

    Path to SD card from jnius import autoclass # SDcard Android # Get path to SD card Android try: Envi ...

  10. K8S学习笔记之k8s日志收集实战

    0x00 简介 本文主要介绍在k8s中收集应用的日志方案,应用运行中日志,一般情况下都需要收集存储到一个集中的日志管理系统中,可以方便对日志进行分析统计,监控,甚至用于机器学习,智能分析应用系统问题, ...