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

时间:2015-07-01 09:12:10

标签: c type-conversion

我写了一个简单的计时器函数来计算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

7 个答案:

答案 0 :(得分:7)

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

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

答案 1 :(得分:4)

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

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

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

这样做的结果是正确编写的c ++代码可以被证明是正确的'在运行时之前,这意味着你花费很少的时间来追踪你本来会遇到的晦涩的错误。

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;
}

答案 2 :(得分:2)

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

有道理,不是吗?

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

答案 3 :(得分:2)

避免警告

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;
}

如果您看到<sys/types.h>中定义的数据类型,那么您肯定会找到

typedef long time_t;
...
typedef long suseconds_t;

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

答案 4 :(得分:2)

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

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

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

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

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

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

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

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

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

      但当然最重要的问题是, sometype 应该是什么?

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

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

      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具有类似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;
      

      虽然当然还存在亚秒的问题。

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

答案 5 :(得分:0)

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

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

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

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

答案 6 :(得分:0)

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