代码之家  ›  专栏  ›  技术社区  ›  Andreas Grech

在C中推断常量的类型#

  •  37
  • Andreas Grech  · 技术社区  · 16 年前

    在C中,以下类型推断有效:

    var s = "abcd";
    

    但是当变量是常量时,为什么不能推断类型呢?

    以下引发编译时异常:

    const var s = "abcd"; // <= Compile time error: 
                          //    Implicitly-typed local variables cannot be constant
    
    11 回复  |  直到 9 年前
        1
  •  30
  •   Eric Lippert    11 年前

    我真的希望利珀特能过来看看这个问题

    如果有什么事你想引起我的注意,你可以把我的名字写在正文里,而不是评论里,我最终会找到的。或者,更好的是,你可以“推”到 @ericlippert .请注意,这不构成服务级别协议;我在业余时间这样做。

    当变量是常量时,为什么不能推断类型?

    “常量”和“变量”是 对立 . const var 给我打字时的颤栗。常量是一个永不更改且没有存储位置的值;变量是一个内容发生更改的存储位置。它们完全不同,所以不要试图将它们组合在一起。这个 var 语法被选择来调用“这是一个变量”,我们坚持使用它。

    变量 可以代表特定的类型声明,但将其与 const 严重混淆了编译器的画面 值。因此 常数VaR 不允许这样做以避免这种混淆,您必须显式地键入常量。

    我完全可以使用不使用的推断常量 var :

    const Pi = 3.14159;
    

    我觉得不错。但是,我知道没有计划将此添加到c。

        2
  •  12
  •   Kevin Won    16 年前

    这只是一个猜测,但我认为原因可能与这样一个事实有关:常量值在编译时被放入元数据中(这有其自身的微妙后果)。我想知道编译器是否有一些问题来解决如何将var转换为元数据。

    在李希特 通过C语言实现CLR (第177页)

    定义常量会导致创建 元数据的。当代码引用 常量符号,编译器查找 在 定义该常量的程序集, 提取常量的值,以及 在发出的IL中嵌入值 代码。

    他接着指出,这意味着,由于这个原因,你不能得到一个常数的记忆。在psuedo c中,如果程序集a定义了一个常量,则要更明确一点:

    //Assembly A, Class Widget defines this:
    public static const System.Decimal Pi = 3.14
    

    那么你的消费者是:

    //somewhere in the Program.exe assembly
    decimal myCircleCurcum = 2 * Widget.pi
    

    program.exe的结果编译IL将执行以下伪代码:

    // pseudo-IL just to illustrate what would happen to the const
    myCircleCurcum = 2*3.14
    

    请注意 使用程序集完全不知道十进制3.14与程序集A有任何关系——它是对program.exe的一个文本值 . 对我来说,这是C编译器合理的行为方式——毕竟,汇编A 明确声明 该pi是一个常数(意味着该值是一次且对于所有pi=3.14)。但是,我冒昧地猜测,99%的C开发人员不理解这一点的后果,他们可能会一时心血来潮将PI改为3.1415。

    常量有一个非常糟糕的跨程序集版本故事(同样,它来自Richter),因为如果程序集A的常量发生更改(即重新编译),则具有常量的程序集A的使用者将看不到更改。这可能导致程序集A的使用者很难找出错误。所以我禁止我的团队使用常量。他们轻微的性能提升不值得他们引起的细微错误。

    只有当你知道一个常数永远不会改变时,你才能真正地使用它。 --即使有了像π这样的常数,你也不能肯定地说你不希望你的决定在未来发生改变。

    如果组件A定义:

    decimal const pi = 3.14
    

    然后构建它,然后其他程序集使用它,如果随后更改程序集A:

    decimal const pi = 3.1415
    

    并重新组装组件A,组件A的消费者仍将拥有旧值3.14!为什么?因为最初的3.14被定义为一个常量,这意味着程序集A的消费者被告知该值不会改变——所以他们可以将该值的pi烘焙到自己的元数据中(如果重新构建程序集A的消费者,那么它将在其元数据中获得pi的新值)。同样,我不认为这是CSC处理常量的方式的问题——只是开发人员可能不希望常量 无法安全更改 在某些情况下,可以在其他情况下安全地进行更改。安全:没有一个使用者只有.dll引用(即每次都从源代码生成),不安全:使用者不知道您的程序集的源代码何时更改,而常量定义了它。在.NET文档中应该更清楚地说明 常量表示不能更改源代码中的值

    出于这个原因,我强烈建议不要使用常量,而只将小部件设置为只读。你能确定有多少价值观会永远不变?

    在我看来,使用const over readonly的唯一真正原因是,某些东西是否可能具有性能影响…但是如果你遇到了这个问题,我想知道C语言是否真的是解决你问题的正确语言。简而言之,对我来说,使用常量从来都不是一个好主意。很少有时候微小的性能改进值得潜在的问题。

        3
  •  11
  •   Keith Sparkjoy - Pluralsight    15 年前

    我同意埃里克的观点,这是一种丑陋的罪恶:

    const var s = "abcd"

    但为什么不简单地说呢?

    const s = "abcd"

    在我看来是一个合理的语法。

        4
  •  7
  •   Mark Byers    16 年前

    简短的答案是因为语言设计者(微软)这么说。

    MSDN :

    编译器错误CS0822

    错误信息 :隐式类型化局部变量 不能是常数

    隐式类型的局部变量是 仅用于存储匿名 类型。在所有其他情况下 只是个方便。如果值 变量永远不会改变,只要给出 它是显式类型。正在尝试使用 带有 隐式类型化的本地将生成 CS0106。

    更正此错误

    如果需要变量为常量或只读,请将其 显式类型。

        5
  •  3
  •   Fernando Pelliccioni    14 年前

    我不同意@eric。

    这个 var 关键字不表示“这是一个变量”,而是表示“要推断类型”。

    int、long等是否是识别变量的“关键字”?不,它们只是数据类型,可以用于变量或常量。

    我想关键字的名字 var 被认为类似于javascript,我认为不合适。

    怎么样 汽车 ?( http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1705.pdf )

        6
  •  3
  •   user1179071    13 年前

    我的答案是什么?因为目前不可能使用“const var”,所以不要担心它。这种限制,毫无理由,使C在处理常数与变量的方式上不平衡,从而产生了一种不平衡。你会过得更好的

    选择了“var”语法来调用“this is a variable”,我们将继续使用它。

    我发现埃里克·利珀特的论点在多个层面上都很不可信。

    埃里克,我不知道“我们”是谁,我真的不想听起来粗鲁,但是 使用 (因为存在的原因)和 意思 (正如为什么var是合适的名称一样)与您试图附加到它上的含义无关。”VAR是 习惯于 它取代了显式类型声明,并表示它的类型在当时可以是许多类型中的一个。

    综上所述,var替换了类型声明。我们不要假装它还做了什么,因为类型和值(以及这个值是否可以更改)是两个不同的东西。Occum的Razor应用于此,无需将var的含义扩展到它所做的之外。

    更重要的是,即使在隐式声明不是一个选项并且var关键字正在使用的时候,人们仍然认为他们的对象是变量,将变量声明为常量没有问题。

    引入“VaR”是因为需要它。这不需要让变量安全地变为常量。这种有限的介入创造了另一种需求,目前还没有满足。

    你的整个立场可以归结为一个符号论点——我们只是不喜欢“const var sounds”(例如,“gives me the shudders to type.”)的方式,考虑到一个人可以在没有编译错误的情况下键入类似“dynamic static”的东西,这是很奇怪的。 声音 也很尴尬。

    为什么要强调那些一开始就完全没有矛盾风险的东西?“const var=”Hello World“”或其变化是否真的会让人困惑,不管它是不是一个常数。我认为人们能够确切地理解这意味着什么,就像他们理解“动态静态”的含义一样。

    真正的底线是,能够隐式地声明常量既有意义,也实际上是有用的。目前没有办法做到这一点,似乎没有理由。能够声明“const var”比引入另一个关键字来服务隐式声明的常量更有意义。

    如果你不认为埃里克的论点完全基于对语义学不必要的复杂解释,试着围绕 意思 如果用其他名称调用,则为“var”。说吧,IMPL。IMPL不能与const一起使用有什么原因吗?我很难想出一个理由。因此,归根结底就是不喜欢“const var”的发音,而不喜欢其他任何东西。我想我们中的大多数人都能很容易地克服这一点。

        7
  •  2
  •   Bobby    16 年前

    在这种情况下,很明显您知道引用类型是常量,并且是相当原始的类型(常量只能是值类型或字符串等),所以您应该声明该类型,而不是使用隐式类型。

    换句话说,因为类型显然是常量和已知的,所以绝对没有理由使用var。

    隐式类型的局部变量是 仅用于存储匿名 类型。在所有其他情况下 只是个方便。如果值 变量永远不会改变,只要给出 它是显式类型。正在尝试使用 带有 隐式类型化的本地将生成 CS0106。

    http://msdn.microsoft.com/en-us/library/bb310881.aspx

    编译器错误CS0822

    如果需要,请更正此错误 变量为常量或 只读,给它一个显式类型。

        8
  •  2
  •   supercat    13 年前

    虽然我不同意Lippert先生的推理,但有一个很好的理由不允许隐式键入命名常量:如果键入的常量不必显式指定其类型,请考虑以下代码的含义:

    const var ScaleFactor = 2500000000; // Type 'Int64'
    
    ...
    int thisValue = getNextInt();
    total += thisValue * ScaleFactor;
    

    现在假设比例因子需要削减20%。将值更改为2000000000会有什么影响?即使在代码中指定了值[例如,更改时],也会出现将Int64变为Int32的问题。 total += thisValue * 2500000000; total += thisValue * 2000000000; 更改将与要求值为 Int64 . 相比之下,A const 声明很可能与它所影响的代码相差甚远,因此没有任何可见的方法来知道某个地方的代码是否依赖于一个长类型的常量。

        9
  •  1
  •   Jonathan Allen    16 年前

    有趣。我不知道它是C编译器的一个限制,还是语言本身的基本限制。

    为了解释我的意思,考虑一下vb。

    在Vb9中,您也不能推断常量,但这只是编译器的一个限制。 在Vb10中,他们能够添加常量类型推理,而不会对语言进行任何显著的更改。

        10
  •  0
  •   Beku    16 年前

    imo var的主要目的是允许匿名类型(类型未知,通过var可以声明一个变量来存储它)。现在更常见的用法是写更少的代码;)。 正如他们解释的 here 如果知道类型和值(不会改变),只需编写类型。

        11
  •  0
  •   binki    9 年前

    我意识到我(我们)真正想要的是 const 在javascript或c中定义的关键字。也就是说,在运行时计算它的能力,但不允许在后续代码中更新它。这对于强制约束很有用,并且当您只希望计算一次值时,它是显式的。

    换句话说,这个问题真正需要的是能够使用 readonly 局部变量(变量/方法参数)的关键字。例如,此语法可能有用:

    // This variable should never be overwritten!
    readonly var target = 4;
    

    这并不是说在C中没有先例。 using() 迭代 foreach )变量的行为方式已经如下:

    class Program
    {
        static void Main(string[] args)
        {
            foreach (var x in new[] { "asdf", })
            {
                System.Console.WriteLine(x);
                // error CS1656: Cannot assign to 'x' because it is a 'foreach iteration variable'
                x = "food";
            }
        }
    }
    

    哦,看,我有类型推断 只读 行为!哎呀!但是,使用 前额 关键字太笨重了,无法在真正的代码中实现这一点。一点也不明显,你试图保护自己不受,呃,你自己或你的同事添加的代码会发生变化 x 稍后,不考虑影响(在编译器错误的帮助下)。这就是为什么 it would be great if it became a language feature .