Ruby的双冒号`::`是什么?

487
这个双冒号::是什么?例如,Foo::Bar
我找到了一个定义
::是一元操作符,允许从类或模块内定义的常量、实例方法和类方法在类或模块外部被访问。
如果可以使用::来公开任何内容,那么范围(私有、受保护)有何用处?

202
为了方便未来的谷歌搜索用户,如果您想要搜索一个符号,请尝试使用http://symbolhound.com/。 - Andrew Grimm
1
可能是重复问题?https://dev59.com/i3E95IYBdhLWcg3wheNR - jacobq
以及https://dev59.com/sHA65IYBdhLWcg3w2iip - 1dolinski
9
谢谢你,@AndrewGrimm。这是我这周看到的最棒的东西。 - abeger
2
未来已经到来,我很遗憾地通知我的谷歌同事们,symbolhound.com现已关闭 - Erdős-Bacon
12个回答

446

::基本上是一个命名空间解析运算符。它允许您访问模块中的项目,或类中级别的项目。例如,假设您有以下设置:

module SomeModule
    module InnerModule
        class MyClass
            CONSTANT = 4
        end
    end
end

您可以通过以下方式从模块外部访问CONSTANT: SomeModule::InnerModule::MyClass::CONSTANT

这不影响在类中定义的实例方法,因为您使用不同的语法(点.)来访问它们。

相关说明:如果您想返回到顶级命名空间,请执行此操作:::SomeModule - Benjamin Oakes


6
比如在 C# 中是这样的,但是在 C++(以及 Ruby)中则使用 :: 来解决命名空间问题,例如 std::cout << "Hello World!"; - Jerry Fernholz
171
相关说明:如果你想返回到顶层命名空间,请执行以下操作:::SomeModule - Benjamin Oakes
7
领导冒号是隐含的,除非我恰好在另一个模块中有SomeModule并且希望获取顶层模块的SomeModule,这是正确的吗? - Jo Liss
8
是的。如果您想确保引用顶层命名空间中的常量或另一个模块中同名的常量(例如::SomeOtherModule::ClassMethods),这是有帮助的。 - Benjamin Oakes
2
这非常类似于C++的作用域操作符。 - lkahtz
显示剩余8条评论

136

这个简单的例子说明了它:

MR_COUNT = 0        # constant defined on main Object class
module Foo
  MR_COUNT = 0
  ::MR_COUNT = 1    # set global count to 1
  MR_COUNT = 2      # set local count to 2
end

puts MR_COUNT       # this is the global constant: 1
puts Foo::MR_COUNT  # this is the local constant: 2

选自http://www.tutorialspoint.com/ruby/ruby_operators.htm


这就是导致警告的原因。有没有什么方法可以避免这个警告? - NullVoxPopuli
3
通常来说改变常量的值是一种极不好的做法,但是如果你想修改一个糟糕编写的 gem 中的常量而又不想 fork 它,可以通过使用 .send(:remove_const) 方法将其从定义常量的模块中移除,然后重新定义该常量。 - BookOfGreg

86

::允许您访问另一个类或模块中定义的常量、模块或类。它用于提供命名空间,以便方法和类名称不会与其他作者创建的类产生冲突。

当您在Rails中看到ActiveRecord::Base时,意味着Rails具有类似以下结构:

module ActiveRecord
  class Base
  end
end
即在模块 ActiveRecord 内部有一个名为 Base 的类,可以通过 ActiveRecord::Base 引用它(你可以在 Rails 源码的 activerecord-n.n.n/lib/active_record/base.rb 中找到它)。
另一个常见用法是使用 :: 访问模块中定义的常量,例如:
module Math
  PI = 3.141 # ...
end

puts Math::PI

:: 运算符不能绕过被标记为私有或受保护的方法的可见性。


10
如果一个类写成class MyClass < ActiveRecord::Base,那么这意味着MyClass只会继承基础类中的方法,而不会继承ActiveRecord模块中的任何内容吗? - Charlie Parker
3
为什么在这个命名空间解析中要使用特殊的双冒号,而不是也使用“.”?即使我们使用“.”,上下文和大小写也可以防止混淆意义,不是吗? - Jonah
5
@Jonah 在某些情况下可能存在歧义,例如考虑 class Foo; Baz = 42; def self.Baz; "Baz method!"; end; end(完全有效),Foo::Baz # => 42Foo.Baz # => "Baz method!"。请注意,Foo::Baz()(带有括号)也会调用该方法。 - mikej
4
那么它解决的使用场景是具有完全相同名称的类常量和类方法的能力?这似乎并不是支持该功能的有力论点。就个人而言,我宁愿放弃那种能力(看起来很麻烦),放弃双冒号,也使用“.”进行命名空间。也许它还可以解决其他用例吗? - Jonah

34

如果你可以使用 :: 暴露任何东西,那么作用域(private,protected)有何用处?

在Ruby中,一切都是暴露的,任何地方都可以修改。

如果你担心类可以从“类定义”之外被修改,那么Ruby可能不适合你。

另一方面,如果你对Java的类被锁定感到沮丧,那么Ruby可能是你要找的。


1
我听一些 Ruby 程序员说过实例变量并不是公开的,即使 attr_accessor 只是创建修改变量的方法。(当然还有 instance_eval - Andrew Grimm
4
没错,Ruby中有instance_eval方法。但也有instance_variable_getinstance_variable_set方法。Ruby太过动态以致于无法限制。 - yfeldblum

28

出人意料的是,这里的10个答案都说同样的话。'::' 是一个命名空间解析运算符,而且确实如此。但是,在涉及到常量查找算法时,你必须意识到命名空间解析运算符有一个需要注意的地方。正如Matz在他的书《Ruby编程语言》中所阐述的那样,常量查找有多个步骤。首先,它会在引用常量的词法作用域内搜索常量。如果在词法作用域内找不到该常量,则会在继承层次结构中进行搜索。由于这种常量查找算法,因此我们会得到以下预期的结果:

module A
  module B
      PI = 3.14
      module C
        class E
          PI = 3.15
        end
        class F < E
          def get_pi
            puts PI
          end
        end
      end
  end
end
f = A::B::C::F.new
f.get_pi
> 3.14

在F继承自E的情况下,B模块处于F的词法作用域内。因此,F实例将引用模块B中定义的常量PI。现在,如果模块B没有定义PI,则F实例将引用超类E中定义的PI常量。

但是,如果我们使用'::'而不是嵌套模块,我们会得到相同的结果吗?不!

通过在定义嵌套模块时使用命名空间解析运算符,嵌套的模块和类不再位于其外部模块的词法作用域内。如下所示,在A::B中定义的PI不在A::B::C::D的词法作用域内,因此在获取get_pi实例方法中引用PI时得到未初始化的常量:

module A
end

module A::B
  PI = 3.14
end

module A::B::C
  class D
    def get_pi
      puts PI
    end
  end
end
d = A::B::C::D.new
d.get_pi
NameError: uninitialized constant A::B::C::D::PI
Did you mean?  A::B::PI

4
这个回答应该排在更前面。 - Burak Kaymakci
是的,最后一部分也可以用作面试问题。不错的选择。 - undefined

22

在之前的回答中,提到了可以使用::来访问实例方法。以下所有方式都是有效的:

MyClass::new::instance_method
MyClass::new.instance_method
MyClass.new::instance_method
MyClass.new.instance_method

根据最佳实践,我认为只有最后一个被推荐。


12
不,它不是用来访问每个方法的,它是一个“解析”运算符,也就是说,你使用它来解决常量/静态符号的作用域(或者可以说是位置)。
例如,在您的第一行中,Rails使用它来在ActiveRecord.Module中查找Base类,而在第二行中,它用于定位Routes类的类方法(静态),等等。
它不是用来暴露任何东西的,它被用来“定位”您的范围周围的东西。 http://en.wikipedia.org/wiki/Scope_resolution_operator

“(static)” 你的意思是 “(静态)” 还是 “(绘制)”?!? - Meltemi

6
Ruby on Rails 使用::进行命名空间解析。
class User < ActiveRecord::Base

  VIDEOS_COUNT = 10
  Languages = { "English" => "en", "Spanish" => "es", "Mandarin Chinese" => "cn"}

end

使用它的方法如下:
User::VIDEOS_COUNT
User::Languages
User::Languages.values_at("Spanish") => "en"

此外,另一种用法是:在使用嵌套路由时。 OmniauthCallbacksController 是在用户下定义的。
并且路由如下:
devise_for :users, controllers: {omniauth_callbacks: "users/omniauth_callbacks"}


class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController

end

5

这是防止定义与项目中链接的其他代码发生冲突。这意味着您可以保持分离。

例如,您可以在代码中有一个名为“run”的方法,并且仍然能够调用您的方法,而不是已经在某个其他库中定义的“run”方法。


4
module Amimal
      module Herbivorous
            EATER="plants" 
      end
end

Amimal::Herbivorous::EATER => "plants"

::用于创建作用域。为了从两个模块访问常量EATER,我们需要将模块的作用域限定到常量所在的范围。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接