什么是“第一类”对象?

261 投票
6 回答
84896 浏览
提问于 2025-04-11 09:30

在某种编程语言中,当我们说某些东西是“第一类”的时候,这是什么意思呢?为什么会这样说?它们和那些不是第一类的语言有什么不同呢?

当有人说“万物皆对象”(比如在Python中),这是不是意味着“万物都是第一类的”呢?

6 个回答

20

“一等公民”这个说法的意思是你可以像平常一样对待它们。大多数情况下,这意味着你可以把这些一等公民当作参数传递给函数,或者从函数中返回它们。

对于对象来说,这一点很明显,但对于函数甚至类来说,就不一定那么明显了:

void f(int n) { return n * 2; }

void g(Action<int> a, int n) { return a(n); }

// Now call g and pass f:

g(f, 10); // = 20

这是一个C#的例子,在这个语言里,函数实际上并不是一等公民。因此,上面的代码使用了一个小技巧(也就是一个叫做Action<>的泛型委托)来把函数当作参数传递。其他一些语言,比如Ruby或Python,允许把类和代码块也当作普通变量来处理(在Ruby中,甚至可以把它们当作常量)。

29

当有人说“在Python中一切都是对象”时,他的意思是“一切都是一等公民”吗?

没错。

在Python中,所有东西都是完整的对象。即使是其他语言中的“基本类型”,在Python里也是对象。

你会发现像2这样的对象实际上有很多丰富而复杂的功能。

>>> dir(2)
['__abs__', '__add__', '__and__', '__class__', '__cmp__', '__coerce__', '__delattr__', '__div__', '__divmod__', '__doc__', '__float__', '__floordiv__', '__getattribute__', '__getnewargs__', '__hash__', '__hex__', '__index__', '__init__', '__int__', '__invert__', '__long__', '__lshift__', '__mod__', '__mul__', '__neg__', '__new__', '__nonzero__', '__oct__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdiv__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__str__', '__sub__', '__truediv__', '__xor__']

因为在Python中一切都是一等对象,所以特殊情况相对较少。

比如在Java中,有一些基本类型(如int、bool、double、char)并不是完整的对象。这就是为什么Java需要引入Integer、Boolean、Double和Character作为一等类型。这对初学者来说可能很难理解——为什么基本类型和类要并存。

这也意味着一个对象的类本身也是一个对象。这和C++不同,在C++中类在运行时不一定有独立的存在。

比如2的类型是type 'int'对象,它有方法、属性和类型。

>>> type(2)
<class 'int'>

int这样的内置类型的类型是type 'type'对象。这个对象也有方法和属性。

>>> type(type(2))
<class 'type'>
237

简单来说,这意味着对这个对象的使用没有限制。它和其他任何对象都是一样的。

所谓的“第一类对象”,就是一种可以动态创建、销毁、传递给函数、作为返回值的实体,并且在编程语言中享有和其他变量一样的权利。

根据不同的编程语言,这可能意味着:

  • 可以用匿名的字面值表示
  • 可以存储在变量中
  • 可以存储在数据结构中
  • 有独立的身份(不依赖于任何特定的名称)
  • 可以和其他实体进行相等比较
  • 可以作为参数传递给过程/函数
  • 可以作为过程/函数的结果返回
  • 可以在运行时构造
  • 可以被打印
  • 可以被读取
  • 可以在分布式进程之间传输
  • 可以存储在运行进程之外

来源

在C++中,函数本身不是第一类对象,但:

  • 你可以重载'()'操作符,这样就可以有一个对象函数,它是第一类的。
  • 函数指针是第一类对象。
  • boost bind、lambda和function都提供第一类函数。

在C++中,类不是第一类对象,但这些类的实例是。而在Python中,类和对象都是第一类对象。(想了解更多关于类作为对象的内容,可以查看这个回答)。

下面是一个JavaScript第一类函数的例子:

// f: function that takes a number and returns a number
// deltaX: small positive number
// returns a function that is an approximate derivative of f
function makeDerivative( f, deltaX )
{
    var deriv = function(x)
    { 
       return ( f(x + deltaX) - f(x) )/ deltaX;
    }
    return deriv;
}
var cos = makeDerivative( Math.sin, 0.000001);
// cos(0)     ~> 1
// cos(pi/2)  ~> 0

来源

那些不是第一类对象的实体被称为第二类对象。在C++中,函数是第二类对象,因为它们不能动态创建。

关于编辑:

编辑。当有人说“所有东西都是对象”(比如在Python中),他是否真的意味着“所有东西都是第一类的”?

这个“对象”一词可以宽泛使用,并不一定意味着是第一类对象。可能更合理的说法是把整个概念称为“第一类实体”。但在Python中,他们确实希望让所有东西都是第一类的。我相信说这句话的人是想表达第一类的意思。

撰写回答