如果一个函数有错误的参数,如何防止它返回任何值?
Posted
技术标签:
【中文标题】如果一个函数有错误的参数,如何防止它返回任何值?【英文标题】:If a function has wrong parameters, how to prevent it from returning any value? 【发布时间】:2021-03-25 07:04:53 【问题描述】:我确定已经有人问过这个问题,但我找不到答案。
如果我有一个函数,比如说:
int Power(int number, int degree)
if(degree==0)
return 1;
return number*Power(number, degree-1);
仅当degree
为非负int
时才有效。如何防止这个函数被错误的参数调用?
例如,如果程序员写cout<<Power(2, -1);
,我希望编译器拒绝编译代码并返回某种错误消息(例如“函数 Power 只接受非负整数”)。
在这种情况下,另一种选择是函数不返回任何值。例如:
int Power(int number, unsigned int degree)
if(degree<0)
//return nothing
if(degree==0)
return 1;
return number*Power(number, degree-1);
【问题讨论】:
@DanielLangr 我见过“参数”和“参数”可以互换使用。 您在第二个示例中将degree
声明为unsigned int
,因此degree < 0
不可能为真。
明显的方法是throw
一个例外。由于某种原因这是不可接受的吗?
@EtiennedeMartel 见***.com/questions/1788923/parameter-vs-argument
@EtiennedeMartel 这是错误的,有时会导致误解。
【参考方案1】:
返回值还有另一种方法:抛出一个值。一个典型的例子:
if(degree<0)
throw std::invalid_argument("degree may not be negative!");
我希望编译器拒绝编译代码
通常,参数在运行时之前是未知的,因此这通常是不可能的。
您的回答很友好。但我很好奇:'throw' 会终止程序并阻止执行 Power() 之后的任何操作。
如果你捕获了抛出的对象,那么你可以在抛出该对象的函数之后立即继续。
【讨论】:
您的回答很友好。但我很好奇:“抛出”会终止程序并阻止执行 Power() 之后的任何操作。如果案例只是简单的“跳过”函数,让编译后的代码,那将如何实现呢? @eerorika 谢谢!【参考方案2】:C++ 进行隐式类型转换这一事实,让您无法摆脱困境,如果您编写 unsigned int x = -1;
,无论您使用编译器打开哪些警告,您都不会看到任何问题那个。
唯一想到的可能对您有所帮助的规则是臭名昭著的"max zero or one implicit conversions" 规则。但我怀疑在这种情况下可以利用它。 (-1 需要转换为 unsigned int,然后隐式转换为另一种类型)。但我认为从我在上面链接的页面上阅读的内容来看,数字隐式转换在某些情况下并不算数。
这给你留下了另一个,也是不完美的选择。在下面的代码中,我概述了基本思想。但是有无限的空间来完善这个想法(稍后会详细介绍)。此选项是结合您自己的整数类型使用可选类型。下面的代码也仅暗示了可能的情况。所有这些都可以在一些花哨的单子框架或诸如此类的东西中完成......
显然,在问题中发布的代码中,将参数degree
作为unsigned int
是一个坏主意,因为这样会隐式转换负值并且函数无法保护自己免受敌对程度0xFFFFFFFF
(无符号整数的最大值)。如果它想检查,最好选择int
。然后它可以检查负值。
问题中的代码还要求堆栈溢出,因为它没有以尾递归方式实现电源。但这只是一个旁白,不受手头问题的影响。让我们快点把它弄走。
// This version at least has a chance to benefit from tail call optimizations.
int internalPower_1 (int acc, int number, int degree)
if (1 == degree)
return acc * number;
return internalPower_1(acc*number, number, degree - 1);
int Power_1 (int number, int degree)
if (degree < 0)
throw std::invalid_argument("degree < 0");
return internalPower_1( 1, number, degree);
现在,如果我们可以有整数类型,这不是很好吗,这取决于有效的值范围?其他语言也有(例如 Common Lisp)。除非在 boost 中已经有一些东西(我没有检查),否则我们必须自己滚动这样的东西。
先写代码,再找借口:
#include <iostream>
#include <stdexcept>
#include <limits>
#include <optional>
#include <string>
template <int MINVAL= std::numeric_limits<int>::min(),
int MAXVAL = std::numeric_limits<int>::max()>
struct Integer
int value;
static constexpr int MinValue()
return MINVAL;
static constexpr int MaxValue()
return MAXVAL;
using Class_t = Integer<MINVAL,MAXVAL>;
using Maybe_t = std::optional<Class_t>;
// Values passed in during run time get handled
// and punished at run time.
// No way to work around this, because we are
// feeding our thing of beauty from the nasty
// outside world.
explicit Integer (int v)
: valuev
if (v < MINVAL || v > MAXVAL)
throw std::invalid_argument("Value out of range.");
static Maybe_t Init (int v)
if (v < MINVAL || v > MAXVAL)
return std::nullopt;
return Maybe_t(v);
;
using UInt = Integer<0>;
using Int = Integer<>;
std::ostream& operator<< (std::ostream& os,
const typename Int::Maybe_t & v)
if (v)
os << v->value;
else
os << std::string("NIL");
return os;
template <class T>
auto operator* (const T& x,
const T& y)
-> T
if (x && y)
return T::value_type::Init(x->value * y->value);
return std::nullopt;
Int::Maybe_t internalPower_3 (const Int::Maybe_t& acc,
const Int::Maybe_t& number,
const UInt::Maybe_t& degree)
if (!acc) return std::nullopt;
if (!degree) return std::nullopt;
if (1 == degree->value)
return Int::Init(acc->value * number->value);
return internalPower_3(acc * number,
number,
UInt::Init(degree->value - 1));
Int::Maybe_t Power_3 (const Int::Maybe_t& number,
const UInt::Maybe_t& degree)
if (!number) return std::nullopt;
return internalPower_3 (Int::Init(1),
number,
degree);
int main (int argc, const char* argv[])
std::cout << Power_1 (2, 3) << std::endl;
std::cout << Power_3 (Int::Init(2),
UInt::Init(3)) << std::endl;
std::cout << Power_3 (Int::Init(2),
UInt::Init(-2)) << std::endl;
std::cout << "UInt min value = "
<< UInt::MinValue() << std::endl
<< "Uint max value = "
<< UInt::MaxValue() << std::endl;
return 0;
这里的关键是,函数Int::Init()
返回Int::Maybe_t
。因此,在错误传播之前,如果用户尝试使用超出范围的值进行初始化,用户会很早就收到std::nullopt
。使用Int
的构造函数,反而会导致异常。
为了让代码能够检查,模板的有符号和无符号实例(例如Integer<-10,10>
或Integer<0,20>
)都使用有符号整数作为存储,因此能够检查无效值,潜入通过隐式类型转换。代价是,我们在 32 位系统上的 unsigned 将只有 31 位......
这段代码没有显示但可能很好的想法是,具有两个(不同实例)整数的操作的结果类型可能是整数的另一个不同实例。示例:auto x = Integer<0,5>::Init(3) - Integer<0,5>::Init(5)
在我们当前的实现中,这将导致 nullopt,保留类型 Integer<0,5>
。在一个可能更好的世界中,尽管也有可能,结果将是 Integer<-2,5>
。
不管怎样,有些人可能会觉得我的小Integer<,>
实验很有趣。毕竟,使用类型来更有表现力是好的,对吧?如果您编写像 typename Integer<-10,0>::Maybe_t foo(Integer<0,5>::Maybe_t x)
这样的函数,那么对于 x 有效的值范围是什么。
【讨论】:
哇。谢谢你这么长的回答!我学到了很多。以上是关于如果一个函数有错误的参数,如何防止它返回任何值?的主要内容,如果未能解决你的问题,请参考以下文章