用 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. 《Java程序设计》 第四周学习总结

    学号 20175313 <Java程序设计>第四周学习总结 教材学习内容总结 第五章主要内容 了解子类的继承性 子类和父类在同一包中的继承性(除private外其余都继承) 子类和父类不在 ...

  2. Python WebSocket长连接心跳与短连接

    python websocket 安装 pip install websocket-client 先来看一下,长连接调用方式: ws = websocket.WebSocketApp("ws ...

  3. 探讨JS合并两个数组的方法

    我们在项目过程中,有时候会遇到需要将两个数组合并成为一个的情况. 比如: var a = [1,2,3]; var b = [4,5,6]; 有两个数组a.b,需求是将两个数组合并成一个.方法如下: ...

  4. 记使用talend从oracle抽取数据时,数字变为0的问题

    数据源为oracle,字段类型为number. 发现通过mainline连接到一个logrow控件,输入的该字段的值为0 经过多次测试还是没发现有什么规律. 通过查看代码发现有这一句内容. if (r ...

  5. Poj3624 Charm Bracelet (01背包)

    题目链接:http://poj.org/problem?id=3624 Description Bessie has gone to the mall's jewelry store and spie ...

  6. java面试题汇总(有的题无视即可,没什么实际用途)

    相关概念 面向对象的三个特征 封装,继承,多态,这个应该是人人皆知,有时候也会加上抽象. 多态的好处 允许不同类对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用多种不同的行为方式(发送消 ...

  7. JavaScript 字典

    JavaScript 字典 字典以 key value 形式出现 使用: a = {'k1':'v1,''k2':'v2'} 获取值: a['k1'] 获取值:v1

  8. tp剩余未验证内容-7

    bash脚本中 的 set -e表示 exit immediately if a simple command returns a non-zero value.主要是为了防止错误被忽略.会被立即退出 ...

  9. springboot使用@Schednled 注解实现定时任务

    part 1: @Component public class Scheduled { SimpleDateFormat dateFormat = new SimpleDateFormat(" ...

  10. QThread使用——关于run和movetoThread的区别

    QThread 使用探讨 2010-10-23 00:30 注意:本文停止更新,请优先考虑 Qt 线程基础(QThread.QtConcurrent等) dbzhang800 2011.06.18 Q ...