如果一个函数有错误的参数,如何防止它返回任何值?

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 &lt; 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&lt;-10,10&gt;Integer&lt;0,20&gt;)都使用有符号整数作为存储,因此能够检查无效值,潜入通过隐式类型转换。代价是,我们在 32 位系统上的 unsigned 将只有 31 位......

这段代码没有显示但可能很好的想法是,具有两个(不同实例)整数的操作的结果类型可能是整数的另一个不同实例。示例:auto x = Integer&lt;0,5&gt;::Init(3) - Integer&lt;0,5&gt;::Init(5) 在我们当前的实现中,这将导致 nullopt,保留类型 Integer&lt;0,5&gt;。在一个可能更好的世界中,尽管也有可能,结果将是 Integer&lt;-2,5&gt;

不管怎样,有些人可能会觉得我的小Integer&lt;,&gt; 实验很有趣。毕竟,使用类型来更有表现力是好的,对吧?如果您编写像 typename Integer&lt;-10,0&gt;::Maybe_t foo(Integer&lt;0,5&gt;::Maybe_t x) 这样的函数,那么对于 x 有效的值范围是什么。

【讨论】:

哇。谢谢你这么长的回答!我学到了很多。

以上是关于如果一个函数有错误的参数,如何防止它返回任何值?的主要内容,如果未能解决你的问题,请参考以下文章

编写一个sort函数,它用于对任何类型的数组进行排序

Oracle数据库Decode()函数的使用方法有哪些?

如何防止在表单提交中传递空白UTM值?

const

MySQL NULLIF函数用法

linux 下socket的recv函数返回值问题