为什么C有这么多不同的类型?

我写了一个简单的计时器函数来计算startend之间经过的时间

 double mytimer(struct timeval *start, struct timeval *end) { return (end->tv_sec - start->tv_sec) + (end->tv_usec - start->tv_usec)*1e-6; } 

gcc给出以下警告:

警告:从’__suseconds_t’转换为’double’可能会改变其值
警告:从’__time_t’转换为’double’可能会改变其值

以下是timeval的定义:

 struct timeval { time_t tv_sec; /* seconds */ suseconds_t tv_usec; /* microseconds */ }; 

所以我的问题是为什么C定义了这么多不兼容的类型,而不是简单地使用原始类型,如int short …? 它根本不是用户友好的。
我怎样才能对这些类型进行算术运算?

更新

大多数人似乎忽略了我的第二个问题。 添加两种不同类型的标准方法是什么,例如time_tsuseconds_t

因为time_t等包含的是实现定义的,所以没有什么可以说它们应该包含一个整数秒,就像代码中的注释所暗示的那样。 原因是他们希望这些类型可以在不同系统之间移植。

在实践中, time.h确实相当麻烦,因此大多数时候程序最终会调用特定于系统的函数。

正如buttiful butterfly正确指出的那样,c ++语言和标准库设计的目的是在编译时强制执行逻辑正确性。

这意味着我们的目标是在某些模糊环境下做错事的程序根本不会编译(或者编译,但会警告你)。

这意味着您可以在软件甚至在测试工具中运行之前修复这些逻辑错误,更不用说在客户面前了。

这样做的结果是,正确编写的c ++代码在运行时可以被certificate是“正确的”,这意味着您可以花费更少的时间来追踪您可能会遇到的模糊错误。

c ++的神奇之处在于它提供了令人难以置信的壮举,同时提供了极好的效率和代码优化。

注意:’正确’我的意思是它会可靠地做你认为你告诉它的事情。 你仍然需要写出正确的逻辑!

至于问题:

所以我的问题是为什么C定义了这么多不兼容的类型,而不是简单地使用原始类型,如int short …? 它根本不是用户友好的。

它们是不兼容的,以便故意阻止您将它们相互转换。 它们代表不同的概念,就像速度和距离是不同的概念一样。 他们之间没有直接的转换。

我怎样才能对这些类型进行算术运算?

使算术的中间结果成为这些类型可以安全地转换为不失精度的东西。 在这种情况下,tv_sec和tv_usec是整数类型,因此即使它们彼此不兼容,它们也可以单独转换为double。

例如:

 double mytimer(struct timeval *start, struct timeval *end) { return double(end->tv_sec - start->tv_sec) + double(end->tv_usec - start->tv_usec) * 1e-6; } 

原因是像int这样的内置类型是依赖于平台的。 所以在一台计算机上, int可能足以存储时间值,而在另一台计算机上则需要很长时间 。 为了允许人们编写在所有平台上运行的程序,引入了类似time_t的类型,这些类型通常只是适用于该特定平台的某些原始类型的别名定义。 事实上,这在开始时需要更多的学习努力,但从长远来看,这项努力将带来巨大的回报。

有道理,不是吗?

[编辑]:至于奇怪的警告:编译器警告说,将time_t和suseconds_t转换为double可能会丢失一些信息。 这是因为两种类型都是整数类型,其位数多于double的尾数部分。 在您的情况下,仅适用于非常大的时间值,因此您可以简单地忽略这些警告。 (但是编译器应该如何知道time_t值通常适合双倍?所以他会发出这个警告。)实际上,如果不依赖于代码平台,你就无法做到这一点。

[自从我第一次发布以来,我几乎完全重写了这个答案。]

第一个问题的答案是,C有很多类型,试图平衡所有不同字大小的支持机器的需求,并具有合理的可移植性。 事情变得更加复杂,因为它们也希望能够合理地支持诸如“数据结构的大小”,“文件中的偏移”和“现实世界中的时间”之类的专门数量,并且尽管有时这些专门的数量结束了不是由语言规范或编译器决定,而是由底层操作系统决定。

通常,从大型整型转换为浮点型时有两个问题:

  1. 浮点类型可能无法准确表示积分类型的所有有效数字

  2. 浮点类型甚至可能无法处理整数类型的范围

time_t的情况下,还有一个问题:

  1. 类型time_t可能不是您可以有意义地减去的整数秒数

(但是,现在,响应这些问题的“有用”编译器警告有时似乎与保姆有关,我也有同感。很难理解编译器实际上担心的模糊情况,以及它可能很难看到如何在没有警告的情况下重写代码,并且很难确定您最终必须插入的任何强制转换都不会导致代码更不安全。)

如果你不担心关注#3(如果你愿意假设time_t是一个整数秒),你可以通过先减法(和整数类型)来减少数据丢失的可能性,并且然后转换:

 return (sometype)(end->tv_sec - start->tv_sec) + (sometype)(end->tv_usec - start->tv_usec) / 1e6; 

但当然最重要的问题是, 某些类型应该是什么?

我相信你最好的选择是在每个地方double 。 C中double类型的保证范围和精度都非常大。 因此,除非你操纵大于1e50年的时间差(并且除非有人将类型subsec_t实现为266位类型或其他东西),否则即使使用警告抑制强制转换,您的代码也应该是安全的,并且您可以插入注释为此。

如果您想了解这些问题在实践中是如何实际体现的,那么很容易certificate这些问题。 试试这个:

 float f = (float)2000000123L - (float)2000000000L; printf("%f\n", f); 

如果您有64位编译器,即使使用双精度,也可以观察到精度损失:

 double d = (double)9000000000000001234LL - (double)9000000000000000000LL; printf("%f\n", d); 

在我的机器上,这两个片段分别打印1281024

我不确定你的编译器试图警告你的三个问题中的哪一个。 #1是最有可能的。 如果你在减去之后进行转换,你可以看到精度损失如何消失,而不是之前:

 f = 2000000123L - 2000000000L; d = 9000000000000001234LL - 9000000000000000000LL; 

要么

 f = (float)(2000000123L - 2000000000L); d = (double)(9000000000000001234LL - 9000000000000000000LL); 

当我们所拥有的只有32位长和64位双精度时,这在实践中并不是很重要(因为IEEE 754 double s具有52位精度)。 现在,64位类型正变得司空见惯,这些类型的警告正变得越来越普遍。 如果您对double类型具有足够的精度以满足所有时间减去需求感到满意,则可以使用适当的强制转换来double警告。 (再说一遍,这里的“适当”是指“减法后”。)如果你想更安全,你可以改为输入long double 。 如果你的编译器支持它,并且它确实比常规double “更长”,它可以真正减少精度损失问题。 (这是前一个使用long double例子:

 long double ld = (long double)9000000000000001234LL - (long double)9000000000000000000LL; printf("%Lf\n", ld); 

在我的系统上,这个打印1234

但是,尽管如此,在这种情况下,如果你想真正让你的生活更轻松 – 并且顺便提一下,同时解决问题#3 – 你可以并且可以说应该使用标准函数来计算差异。 减去两个time_t值的标准函数是difftime 。 (担心所有这些事情是difftime的工作,包括time_t不直接代表秒数的可能性。)所以你可以写

 return difftime(end->tv_sec - start->tv_sec) + (double)(end->tv_usec - start->tv_usec) / 1e6; 

虽然当然还有亚秒的问题。

最好的解决方案是使用预先编写的库函数来减去两个时间值,您可能需要花一些时间来寻找其中一个。

避免警告

 double mytimer(struct timeval * start, struct timeval * end) { long usec = end->tv_usec - start->tv_usec; long sec = end->tv_sec - start->tv_sec; return 1.0 * sec + 1e-6 * usec; } 

如果你看到定义的数据类型,你会发现它本身

 typedef long time_t; ... typedef long suseconds_t; 

struct timeval没有特定的格式说明符,但结构成员的类型为long。 希望这能解决你的问题。

原因称为类型安全。 并非所有类型的表达在工作程序中都有意义。 类型安全意味着编译器将拒绝允许不安全,无效或不适当的操作。

从长远来看,拒绝这些错误代码的编译器会节省程序员的工作量,因为程序员在没有帮助的情况下检测问题所需的时间比编译器要多。

C和C ++具有许多类型安全function。 其他编程语言有更多,有些则更少。 这仅仅意味着某些编程风格在不同的编程语言中更有效。

警告类型安全性的规范性较小 – 它们会导致编译器警告可疑事物,而不是完全拒绝它们。

虽然这些类型通常使用标准整数类型实现,但您不能将其视为可移植性。 标准库为转换等提供了一些方便的function。例如,如果要计算两次之间的延迟,可以使用返回double difftime