首先,我们需要在栈中设置一个抽象的存储结构, void *elem, 其需要动态分配堆内存, 声明如下所示:
typedef struct { void *elem; int elem_size; int length; int position; void (*freefn)(void *elem); }Stack; void stackNew(Stack *s, int elem_size, void (*freefn)(void *elem)); void push(Stack *s, void *elem); void pop(Stack *s, void *result); void stackDestroy(Stack *s);
void (*freefn)(void *elem) 为函数指针,其表示用于回收抽象指针(void *elem)中,需要回收的数据,比如动态分配的字符串和结构体中动态分配的内容,我们可以通过传递函数指针,使得在回收栈时,不必弹出所有栈中数据进行回收,而是通过stackDestroy调用我们定义的回收方法,进行数据回收。
具体实现如下所示:
#include <stdlib.h> #include <string.h> #include <iostream> void stackNew(Stack *s, int elem_size, void (*freefn)(void *elem)) { s->length = 4; s->position = 0; s->elem_size = elem_size; s->freefn = freefn; s->elem = malloc(s->length * s->elem_size); } static void stackGrow(Stack *s) { s->length = s->length * 2; s->elem = realloc(s->elem, s->length * s->elem_size); } void push(Stack *s, void *elem) { if(s->position == s->length) { stackGrow(s); } void *address; address = (char *)s->elem + s->position * s->elem_size; memcpy(address, elem, s->elem_size); s->position++; } void pop(Stack *s, void *result) { s->position--; memcpy(result, (char *)s->elem + s->position * s->elem_size, s->elem_size); } void stackDestroy(Stack *s) { int i = 0; for(; i < s->position; i++) { s->freefn((char *)s->elem + i * s->elem_size); } free(s->elem); }
由于我们不知道数据类型是什么,所以在初始化栈时,我们需要传递数据类型的大小,在进行数据弹出和压入的时候,我们可以对内存直接进行拷贝工作,这样做的优点是显而易见的。不要忘记,elem_size*length才是需要分配堆内存的大小。
调用时,如下所示:
#include <iostream> #include <string.h> #include <stdlib.h> #include "stack4.h" using namespace std; static char * Strdup(const char * source) { char * temp = (char *)malloc(strlen(source) * sizeof(char) + 1); strcpy(temp, source); return temp; } void freefn(void *element) { cout << "data free..." << endl; char *temp = *((char **) element); free(temp); } int main() { char *str[] = {"AAAAAAAA", "BBBBBBBB", "CCCCCCCC", "DDDDDDDDD"}; Stack *s = (Stack *) malloc(sizeof(Stack)); stackNew(s, sizeof(char *), freefn); int i=0; for(;i < 4; i++) { char *sc = Strdup(str[i]); push(s, &sc); } char *result; pop(s, &result); cout << result << endl; //回收pop的数据 free(result); //有了固定的回收函数后,不必弹出所有的栈中数据,即可回收内存 //若没有,回收内存时,必须弹出所有栈中数据,手动进行回收,然后回收栈内存 stackDestroy(s); free(s); getchar(); return 0; }
使用深度拷贝字符串,是考虑到了实际应用中,动态分配内容的生命周期。
还需注意的是,在压栈时,必须对指向动态分配字符串的指针地址进行操作,因为我们在栈的初始化时,传递的数据大小是sizeof(char *),另一方面,也有助于栈的高效操作。
相关推荐
了解不同的编程范式能帮助开发者选择最适合项目的技术栈,提高代码的可读性、可维护性和可扩展性。 首先,我们来探讨编程范式。编程范式主要有以下几种: 1. **过程式编程**:这是一种早期的编程方法,强调通过一...
面向对象编程(OOP)是C++的主要编程范式,它包括封装、继承和多态三个主要特征。封装确保数据和操作数据的方法紧密结合,防止外部直接访问数据。继承允许子类从父类继承属性和行为,实现代码重用。多态则允许不同类...
面向对象编程(Object-Oriented Programming,OOP)是一种编程范式,它基于“对象”的概念,这些对象封装了数据和操作这些数据的方法。C++是支持面向对象编程的多范式语言,它允许开发者通过类和对象来组织代码,...
泛型编程是一种编程范式,它允许程序员编写不依赖于特定数据类型的代码,从而提高了代码的复用性和灵活性。在现代编程语言中,如C++、Java、Python和C#,泛型编程得到了广泛支持。然而,C语言作为较早出现的语言,并...
Rust是一门系统编程语言,专注于安全,尤其是并发安全,支持函数式和命令式以及泛型等编程范式的多范式语言。以下是关于Rust语言的详细介绍: 起源:Rust最初是由Mozilla研究院的Graydon Hoare设计创造,然后在Dave...
函数式编程是一种编程范式,它强调通过使用数学函数来构造程序,并且尽可能避免改变状态和可变数据。在函数式编程中,程序是由纯函数组成的,纯函数是指给定相同的输入,总是返回相同输出的函数,而且不产生任何副...
Java面向对象编程是一种核心的编程范式,它在软件开发中占据着主导地位。本指南将深入探讨Java语言中的面向对象特性,帮助开发者理解和掌握如何有效地利用这些特性进行编程。 一、面向对象基础 1. 类与对象:Java...
此外,随着技术的发展,UNIX编程也在不断进化,本书所教授的基础概念和编程技术,虽然有着深厚的历史根基,但在应用到现代的系统编程时,还需要结合当前的操作系统特性以及新的编程范式进行相应的调整和优化。...
C语言和Java是两种广泛应用的编程语言,它们在很多方面有着显著的区别,这些差异主要体现在语法、内存管理、平台依赖性、应用领域以及编程范式等多个方面。 首先,C语言是一种面向过程的语言,它的核心在于函数,...
数据结构是指在计算机中组织、存储和处理数据的方式,而面向对象编程(Object-Oriented Programming,OOP)是一种编程范式,它基于“对象”的概念,使得代码更加模块化、易于理解和维护。 首先,我们来深入了解数据...
STL,全称Standard Template Library,是C++标准库中的一个重要组成部分,由Alexander Stepanov创建,它提供了泛型编程的工具,包括...STL的出现极大地提高了C++程序员的工作效率,并对后来的编程范式产生了深远影响。
面向对象程序设计是一种重要的编程范式,它基于“对象”的概念,通过封装、继承和多态等特性,使得代码更加模块化,易于理解和维护。在本实验报告中,重点探讨了类属机制(也称为“类的嵌套类”)在C++中的应用。 ...
C++不仅是一种面向对象的编程语言,还包含了泛型编程、元编程等多种编程范式,使得它在软件工程中具有极高的灵活性和表现力。 C++的基本概念包括: 1. **基础语法**:C++起源于C语言,因此它保留了C的大部分语法...
标题中提到了“Java编程思想及Struts2”,这意味着文档将会包含关于Java编程语言的基础知识和面向对象编程范式的深入理解,同时也会覆盖Struts2这一基于MVC模式的Web应用框架的架构设计、实现原理。 描述中提到的...
在现代软件开发中,**泛型编程**是一种重要的编程范式,它允许程序员编写代码时使用未指定的数据类型,从而提高代码的复用性和灵活性。C++通过模板支持泛型编程,使开发者能够创建通用算法和数据结构,而无需为每种...
Java语言是现代计算机编程语言中极为重要的一门语言,其具有跨平台特性、面向对象的编程范式、丰富的数据类型、灵活的访问修饰符以及抽象和静态的概念,这些都是Java语言的鲜明特点和知识点。 首先,Java语言之所以...
JavaScript中的函数式编程是一种强大的编程范式,它源自数学中的函数理论,强调将计算视为函数的求值,而不是状态的变化或指令的序列。在JavaScript中,函数式编程可以帮助我们写出更简洁、可读性强且易于测试的代码...
函数式编程是一种编程范式,它强调将计算视为数学函数的求值过程,避免了状态变化和可变数据,从而减少了错误的可能性,提高了代码的可读性和可维护性。主要特点包括: 1. **纯函数**:函数的输出只依赖于输入参数...
面向过程和面向对象是两种不同的编程范式。面向过程性能比面向对象高,因为对象调用需要实例化,开销比较大,较消耗资源。但是,面向过程没有面向对象易维护、易复用、易扩展的特点。 面向对象易维护、易复用、易...