文章目录
pop()函数
在经典数据结构,
stack
和
queue
中有一个重要的函数那就是
pop()
表示弹出线性顶部的一个元素。
而在各种语言的标准数据结构中也自然有这些数据结构和对应的函数。
在C++中,pop()无返回,且对空对象pop()行为未定义。
空对象未定义可以理解,但是为什么不返回顶部元素呢?这涉及到异常安全的问题:Exception-Safe Coding in C++ (exceptionsafecode.com)
在C++早期由于没有移动语义和其他各种原因,无法做到安全的返回。
本文就主要来处理这两个问题。
其他语言的示例
python:
使用
list()
当作栈。虽然在空栈时pop也会出异常,但是py的pop可以返回被pop出的数据。
stk =[]
n =3for i inrange(n):
stk.append(i)for i inrange(n):# 获取pop的数据print(stk.pop())# IndexError: pop from empty list# stk.pop()print(len(stk))
C++示例
pop()
函数在空栈时是未定义行为。
楼主本地测试下,msvc直接崩,gcc可以往下运行,但是若访问空栈的top()也会崩。这只是玩个乐呵,别做任何参考。
但C++的一个特点是pop()函数
返回是void
!
#include<iostream>#include<stack>
std::stack<int> stk;autotest_pop(){// printf("Before pop size=%d top=%d\n", stk.size(), stk.top());
stk.pop();// printf("After pop size=%d top=%d\n", stk.size(), stk.top());}intmain(){
std::cout <<"Main Start\n";constexprint n =3;for(int i =1; i <= n; i +=1){
stk.push(i);}for(int i =0; i < n; i +=1){test_pop();}// 空栈pop是未定义test_pop();
std::cout <<"Main End\n";}
自定义pop()
下面为了方便,采用继承而不是组合的方式来处理。请注意在调用模板基类内容时候的一些注意点,本文不会讲解这块基础。
有一些激进派认为,空栈的pop直接抛出一个确定的异常,但本文没那么粗暴。
且默认采用移动语义,缺点是对于一些确定删除移动语义的对象会报错,当然这类对象比较少。
bool + 引用
这是一种非常朴素和经典的方案。在C语言时代,没有引用语义,因此采用指针的方式处理。
因此可以写出两种接口:
bool pop(Type &val)
Type pop(bool &ok)
其中return bool比较常见。而第二种在Qt库中出现的也比较多。
方案1
如果是空栈一般就不要对引用的对象做多余操作了。
这样的话,对于外部传入的对象的创建是多余的性能开销。
方案2
当失败时,由于返回值的存在,需要有一个默认的返回。但是不是所有对象的构造方式都一致。
因此这也是这种方式的一个缺点。
#include<iostream>#include<stack>namespace my {template<typenameType>classstack:public std::stack<Type>{public:boolpop(Type &val){if(this->empty()){// 一般来说直接false// 不对&进行多余操作returnfalse;}
val = std::move(this->top());
std::stack<Type>::pop();returntrue;}
Type pop(bool&ok){if(this->empty()){
ok =false;return{};}
Type ret = std::move(this->top());
std::stack<Type>::pop();
ok =true;return ret;}};}// namespace my
my::stack<int> stk;voidtest_pop_1(){bool ok;int x = stk.pop(ok);printf("pop=[%d]{%s}\n", x, ok ?"true":"false");}voidtest_pop_2(){int x =-1;if(stk.pop(x)){printf("pop=[%d]{true}\n", x);}else{// false时候的val一般视为随机值,垃圾值printf("pop=[%d]{false}\n", x);}}intmain(){
std::cout <<"Main Start\n";constexprint n =3;for(int i =1; i <= n; i +=1){
stk.push(i);}// auto test_pop = test_pop_1;auto test_pop = test_pop_2;for(int i =0; i < n; i +=1){test_pop();}// 空栈pop是未定义test_pop();
std::cout <<"Main End\n";}
智能指针
这里采用
shart_ptr<>
却使用
make_share<>()
。
智能指针在栈内的开销是常量级的。且可以直接作为空指针来进行bool判断。
#include<iostream>#include<memory>#include<stack>namespace my {template<typenameType>classstack:public std::stack<Type>{public:
std::shared_ptr<Type>pop(){if(this->empty()){returnnullptr;}
std::shared_ptr<Type> ret =
std::make_shared<Type>(std::move(this->top()));
std::stack<Type>::pop();return ret;}};}// namespace my
my::stack<int> stk;voidtest_pop(){auto sp = stk.pop();if(sp){printf("pop=[%d]{true}\n",*sp);}else{printf("pop=[nullptr]{false}\n");}}intmain(){
std::cout <<"Main Start\n";constexprint n =3;for(int i =1; i <= n; i +=1){
stk.push(i);}for(int i =0; i < n; i +=1){test_pop();}// 空栈pop是未定义test_pop();
std::cout <<"Main End\n";}
optional
(C++17) optional的使用-CSDN博客
在C++17中为了处理对象是否有效的这样方案,直接引入了一个新的对象
std::optional
。
std::optional
可以直接作为bool的判断。且同样保证了已知的构造方式。
#include<iostream>#include<optional>#include<stack>namespace my {template<typenameType>classstack:public std::stack<Type>{public:
std::optional<Type>pop(){if(this->empty()){return{};}auto ret = std::move(this->top());
std::stack<Type>::pop();return std::move(ret);}};}// namespace my
my::stack<int> stk;voidtest_pop(){auto opt = stk.pop();if(opt){printf("pop=[%d]{true}\n", opt);}else{printf("pop=[%d]{false}\n", opt);}}intmain(){
std::cout <<"Main Start\n";constexprint n =3;for(int i =1; i <= n; i +=1){
stk.push(i);}for(int i =0; i < n; i +=1){test_pop();}// 空栈pop是未定义test_pop();
std::cout <<"Main End\n";}
END
版权归原作者 天赐细莲 所有, 如有侵权,请联系我们删除。