为什么我们必须提到C中变量的数据类型


19

通常在C语言中,我们必须在变量声明中告诉计算机数据的类型。例如,在下面的程序中,我要打印两个浮点数X和Y的总和。

#include<stdio.h>
main()
{
  float X=5.2;
  float Y=5.1;
  float Z;
  Z=Y+X;
  printf("%f",Z);

}

我不得不告诉编译器变量X的类型。

  • 编译器不能自行确定其类型X吗?

是的,我可以这样做:

#define X 5.2

现在,我可以编写程序而无需告诉编译器Xas 的类型:

#include<stdio.h>
#define X 5.2
main()
{
  float Y=5.1;
  float Z;
  Z=Y+X;
  printf("%f",Z);

}  

因此,我们看到C语言具有某种功能,通过它可以自行确定数据类型。就我而言,它确定X为float类型。

  • 在main()中声明某些内容时,为什么还要提及数据类型?为何编译器无法main()像in中那样自行确定变量的数据类型#define

14
实际上,这两个程序并不等效,因为它们可能会给出稍微不同的输出!5.2是a double,因此第一个程序将双float精度字面量四舍五入为精度,然后将它们作为浮点数相加,而第二个程序将5.1的双精度表示形式四舍五入doubledouble使用double加法将其添加到值5.2中,然后将计算结果四舍五入为float精度。由于四舍五入发生在不同的位置,因此结果可能会有所不同。这只是影响其他程序相同行为的变量类型的一个示例。

12
当您这样做时#define X 5.2X它不是一个变量,而是一个常量,因此可以用5.2您提到的任何地方从字面上替换它为预处理器X。您不能重新分配X
scriptin 2014年

16
请注意:这是一种祝福和诅咒。一方面,当编译器确实可以为您完成一些操作时,您必须键入几个字符(C ++ auto实际上可以完成您想要的操作)。另一方面,如果您认为自己知道代码在做什么,并且实际上键入了其他内容,则像这样的静态类型将在它成为大问题之前更早地捕获错误。每种语言都达到平衡:静态打字,类型推论,动态打字。对于某些任务,额外的输入实际上是值得的。对于其他人,这是浪费。
Cort Ammon-恢复莫妮卡2014年

了解Ocaml和/或Haskell。...您将为他们的类型推断能力感到高兴。
Basile Starynkevitch 2015年

Answers:


46

您正在将变量声明与#defines 进行比较,这是不正确的。使用#define,您可以在标识符和源代码片段之间创建映射。然后,C预处理器将使用所提供的代码段从字面上替换该标识符的任何出现。写作

#define FOO 40 + 2
int foos = FOO + FOO * FOO;

最终对于编译器而言与编写相同

int foos = 40 + 2 + 40 + 2 * 40 + 2;

将其视为自动复制和粘贴。

同样,可以重新分配普通变量,而用创建的宏#define则不能(尽管您可以重新分配#define)。该表达式FOO = 7将是编译器错误,因为我们无法将其分配给“ rvalues”:40 + 2 = 7是非法的。

那么,为什么我们根本需要类型?有些语言显然摆脱了类型,这在脚本语言中尤为常见。但是,它们通常具有一种称为“动态类型化”的功能,其中变量没有固定类型,而值却具有固定类型。尽管这要灵活得多,但性能也较差。C喜欢性能,因此它具有非常简单有效的变量概念:

有一段记忆称为“堆栈”。每个局部变量对应于堆栈上的一个区域。现在的问题是该区域必须有多少个字节长?在C语言中,每种类型都有明确定义的大小,您可以通过进行查询sizeof(type)。编译器需要知道每个变量的类型,以便可以在堆栈上保留正确的空间量。

为什么用创建的常量#define不需要类型注释?它们不存储在堆栈中。取而代之的是,#define以比复制和粘贴更为可维护的方式创建可重用的源代码片段。源代码中的文字(例如"foo"或)42.87由编译器内联存储为特殊指令,也可以存储在所得二进制文件的单独数据部分中。

但是,文字确实具有类型。字符串文字是char *42是一个,int但也可以用于较短的类型(缩小转换)。42.8将是一个double。如果你有一个文本,并希望它有不同的类型(例如做42.8一个float,或42一个unsigned long int),那么你可以使用后缀-字面后的信是如何变化的,编译器将是文字。就我们而言,我们可以说42.8f42ul

某些语言与C中一样具有静态类型,但是类型注释是可选的。示例包括ML,Haskell,Scala,C#,C ++ 11和Go。这是如何运作的?魔法?不,这称为“类型推断”。在C#和Go中,编译器查看赋值的右侧,并推导出赋值的类型。如果右侧是诸如的文字,则这非常简单42ul。然后很明显,变量的类型应该是什么。其他语言也具有更复杂的算法,这些算法考虑了如何使用变量。例如,如果这样做x/2,则x不能是字符串,但必须具有某种数字类型。


感谢您的解释。我了解的是,当我们声明变量的类型(局部或全局)时,我们实际上是在告诉编译器,它应该在堆栈中为该变量保留多少空间。另一方面,#define我们有一个常数,可以将其直接转换为二进制代码(可能会持续多长时间),并将其原样存储在内存中。
user106313 2014年

2
@ user31782-不完全是。声明变量时,类型告诉编译器该变量具有哪些属性。这些属性之一是大小。其他属性包括它如何表示值以及可以对这些值执行哪些操作。
Pete Becker 2014年

@PeteBecker然后,编译器如何知道这些其他属性#define X 5.2
user106313 2014年

1
这是因为将错误的类型传递给printf您会导致未定义的行为。在我的计算机上,该代码段每次都会打印一个不同的值,在Ideone上,它在打印零后崩溃。
Matteo Italia

4
@ user31782-“似乎我可以对任何数据类型执行任何操作” X*Y如果XY是指针,则编号无效,但是如果它们是ints,则可以;*X如果Xint,则无效;但如果是指针,则可以。
皮特·贝克尔

4

第二个示例中的X绝不是浮点数。它称为宏,它将源中定义的宏值“ X”替换为该值。此处是有关#define的可读文章。

对于提供的代码,在编译之前预处理器会更改代码

Z=Y+X;

Z=Y+5.2;

这就是被编译的内容。

这意味着您还可以使用以下代码替换这些“值”

#define X sqrt(Y)

甚至

#define X Y

3
它仅称为宏,而不是可变参数宏。可变参数宏是一个带有可变数量参数的宏,例如#define FOO(...) { __VA_ARGS__ }
hvd 2014年

2
我的坏消息会解决:)
James Snell 2014年

1

简短的答案是由于历史/代表硬件,C需要类型。

历史:C于1970年代初开发,旨在用作系统编程语言。理想情况下,代码速度很快,并且可以充分利用硬件的功能。

在编译时推断类型是可能的,但是已经很慢的编译时间会增加(请参阅XKCD的“编译”卡通。这在C发布后至少十年内适用于“ hello world”)。在运行时推断类型可能不符合系统编程的目的。运行时推断需要其他运行时库。C出现在第一台PC之前。里面有256个RAM。不是千兆字节或兆字节,而是千字节。

在您的示例中,如果省略类型

   X=5.2;
   Y=5.1;

   Z=Y+X;

然后,编译器可能会很高兴地得出X和Y为浮点数并使Z相同。实际上,现代编译器还可以计算出不需要X&Y,只需将Z设置为10.3即可。

假定计算嵌入在函数内。函数编写者可能想使用他们的硬件知识或要解决的问题。

比浮点数更合适吗?占用更多内存并且速度较慢,但​​结果的准确性会更高。

函数的返回值可能是int(或long),因为小数并不重要,尽管从float到int的转换并非没有代价。

还可以将返回值设为两倍,以确保float + float不会溢出。

所有这些问题对于当今编写的绝大多数代码似乎毫无意义,但在生成C时至关重要。


1
这并不能解释例如为什么类型声明不是可选的,从而允许程序员选择显式声明它们还是依靠编译器来推断
gna

1
确实不是@gnat。我已经对文本进行了调整,但是当时这样做没有任何意义。域C是为实际想要决定以1字节,2字节或4字节或字符串或单词中的5位存储而设计的。
itj

0

C没有类型推断(当编译器为您猜测变量的类型时即称为该推断),因为它很旧。它是在1970年代初期开发

许多较新的语言都有允许您使用变量而不指定其类型的系统(红宝石,javascript,python等)。


12
您提到的所有语言(Ruby,JS,Python)都没有类型推断作为语言功能,尽管实现可以使用它来提高效率。相反,它们使用动态类型,其中值具有类型,而变量或其他表达式则没有。
阿蒙2014年

2
JS 不允许省略类型-只是不允许您声明任何类型。它使用动态类型,其中值具有类型(例如trueboolean),而不是变量(例如var x可以包含任何类型的值)。同样,对于类似问题这样的简单案例,类型推断可能早在C发布之前就已经知道了。
scriptin

2
这不会使该语句为假(为了强制执行某些操作,您还必须允许它)。现有的类型推断不会改变C的类型系统是其历史背景的结果(与专门说明的哲学推理或技术限制相对)的事实
Tristan Burnside 2014年

2
考虑到ML(几乎与C一样古老)具有类型推断,因此“它很旧”不是一个很好的解释。使用和开发C的环境(小型机器要求编译器占用很小的空间)似乎更有可能。不知道为什么要提到动态类型化语言,而不是提及带有类型推断的语言的一些示例-Haskell,ML,Hck C#拥有它-几乎不再是晦涩的功能了。
Voo

2
@BradS。Fortran并不是一个好例子,因为变量名的第一个字母类型声明,除非implicit none在这种情况下必须声明类型。
dmckee 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.