为什么string::compare返回一个int?

103
为什么 string::compare 返回一个整型而不是一个小一些的类型,例如shortchar?我的理解是这个方法只返回-1、0或1。
第二部分,如果我想设计一个比较两个类型为Foo的对象的比较方法,并且我只想返回-1、0或1,那么使用shortchar是否通常是一个好主意?
编辑:我已经被纠正了,string::compare 实际上返回大于0、小于0或0。谢谢大家的指正。
答案似乎是,没有理由返回比int类型更小的类型,因为返回值是“右值”,这些“右值”并不会从比int(4字节)类型更小的类型中受益。此外,许多人指出,大多数系统的寄存器可能会是int类型的大小,因为无论您给它们一个1、2或4字节的值,这些寄存器都将被填充,因此返回更小的值没有真正的好处。
编辑2:实际上,在使用诸如对齐、掩码等较小的数据类型时可能会有额外的处理开销。一般的共识是,较小的数据类型存在于处理大量数据时节省内存的情况下,例如数组。
今天学到了东西,再次感谢大家!

我认为更好的做法是使用一个更具体的类型。类似于Ada95中只包含-1、0和1的类型。 - Sachin Kainth
24
你提供的string::compare()文档明确说明它的返回值是<0, 0和>0,而不是-1,0和1。我会保持原意并使翻译更易懂。 - Captain Obvlious
6
使用 shortchar 而不是 int 的优点是什么?大多数计算机架构将函数的返回值存储在寄存器中,而一个 int 同样适合于存储在寄存器中。并且当你需要确保有符号值被正确处理时,使用 char 作为数字类型总是一个坏主意。 - Cody Gray
7
明显船长,你的名字和评论... 简直无价。 - Cody Smith
2
使用char会是个糟糕的选择,因为在char被视为无符号数的平台上,检查返回值是否小于零的代码将失效。 - milleniumbug
9个回答

113

首先,规范指定返回值小于、等于或大于0,不一定是-11。 其次,返回值是rvalues,需要整数提升,因此返回任何更小的值都没有意义。

在C++(以及C)中,每个表达式都是rvalue或lvalue。历史上,这些术语是指lvalues出现在赋值的左侧,而rvalues只能出现在右侧。今天,对于非类类型的简单近似是,lvalue具有内存中的地址,而rvalue则没有。因此,您不能获取rvalue的地址,并且cv限定符(用于“访问”)不适用。在C ++术语中,没有类类型的rvalue是一个纯值,而不是一个对象。函数的返回值是一个rvalue,除非它具有引用类型。(适合寄存器的非类类型通常会在寄存器中返回,例如,而不是在内存中返回。)

对于类类型,问题要复杂一些,这是由于您可以在rvalue上调用成员函数。这意味着rvalue实际上必须具有地址,用于this指针,并且可以是cv-qualified,因为cv限定符在重载分辨率中起作用。最后,C ++11引入了几个新的区分,以支持rvalue引用;这些也主要适用于类类型。
整数提升是指当比int小的整数类型在表达式中作为rvalue使用时,在大多数上下文中,它们将被提升为int。因此,即使我声明了一个变量short a,b;,在表达式a + b中,ab都会在加法发生之前提升为int。类似地,如果我写a < 0,则比较是基于a的值,转换为int。实际上,几乎没有任何情况会有所不同,至少在2补码机器上,其中整数算术包装(即除了极少数异类之外的所有机器,今天-我认为Unisys大型计算机仍然是唯一的例外)。尽管如此,在更常见的机器上:

short a = 1;
std::cout << sizeof( a ) << std::endl;
std::cout << sizeof( a + 0 ) << std::endl;

应当产生不同的结果:第一个等价于sizeof( short ), 第二个等价于sizeof( int )(由于整数提升)。

这两个问题在形式上是正交的;rvalue和lvalue与整数提升无关。但是...... 整数提升仅适用于rvalue,而大多数(但并非全部)使用rvalue的情况将导致 整数提升。因此,在返回比int更小的数字值时真的没有理由。 甚至有一个非常好的理由不要将其作为字符类型返回。 像<<这样的重载运算符通常对字符类型有不同的行为, 因此您只希望将字符作为字符类型返回。(您可以比较一下区别:

char f() { return 'a'; }
std::cout << f() << std::endl;      //  displays "a"
std::cout << f() + 0 << std::endl;  //  displays "97" on my machine

不同之处在于第二种情况中发生了整数提升,导致选择了不同的<<重载。


46
在你的答案中,如果你能更详细地解释一下“返回值是右值,受整数提升的影响”,那就太好了。 - Alvin Wong
2
@AlvinWong:查看为什么C语言中的字符字面量是int而不是char?的答案,获取更多背景信息。 - Jesse Good
如果它是“signed char”,它会像有符号的“char”一样表现吗?还是它会成为不同的类型? - user541686
@Mehrdad 这是不同类型,但行为相同。 - James Kanze
@Mehrdad 很好的问题。事实上,标准iostreams几乎在所有它有char重载的地方都包含了对signed charunsigned char的重载。(对于sizeof的行为,三种类型表现相同是正常的。) - James Kanze
显示剩余3条评论

41

故意不返回-1、0或1是有意义的。

它允许(请注意,这不是针对字符串的,但同样适用于字符串)。

int compare(int *a, int *b)
{
   return *a - *b;
}

这比以下代码要简洁得多:

int compare(int *a, int *b)
{
   if (*a == *b) return 0;
   if (*a > *b) return 1;
   return -1;
}

如果你需要返回 -1、0 或 1,那么你就需要执行类似于以下的操作。

对于更复杂的类型,同样适用:

class Date
{
    int year;
    int month;
    int day;
}

int compare(const Date &a, const Date &b)
{
   if (a.year != b.year) return a.year - b.year;
   if (a.month != b.month) return a.month - b.month;
   return a.day - b.day;
}

在字符串的情况下,我们可以这样做:
int compare(const std::string& a, const std::string& b)
{
   int len = min(a.length(), b.length());

   for(int i = 0; i < len; i++)
   {
      if (a[i] != b[i]) return a[i] - b[i];
   }
   // We only get here if the string is equal all the way to one of them
   // ends. If the length isn't equal, "longest" wins. 
   return a.length() - b.length();
}

8
你的第一个“compare”函数存在溢出问题,但如果使用“char”,而且“char”比“int”小,则问题不会同样存在。例如,如果“a”是“MAX_INT”,而“b”是“-1”,则“a - *b”会导致未定义行为(UB),但如果实现选择定义它的行为,则结果几乎肯定为负数。 - Steve Jessop
1
你前面的示例有问题:length() 返回的是 size_t,可能比 int 更大... - F'x
是的,如果你的字符串超过2GB长度,那可能会成为一个问题。作为一种测试用例,我曾经做过1GB长的字符串来存储FIFO中的数据。但确实,处理包含以Base64方式编码的MPEG的字符串的人很可能会遇到这个问题... - Mats Petersson
@MatsPetersson 这更像是一个基本问题,因为问题是“为什么它返回一个int?” - F'x
嗯,我相信这是出于历史原因,可能是为了与strcmp/memcmp和其他比较类型操作兼容。 - Mats Petersson

25

int通常(在大多数现代硬件上)是与系统总线和/或CPU寄存器相同大小的整数,即所谓的机器字。因此,int通常比较小的类型传递更快,因为它不需要对齐、掩码和其他操作。

更小的类型主要存在于允许对数组和结构进行RAM使用优化的情况下。在大多数情况下,它们会牺牲一些CPU周期(以对齐操作的形式)来获得更好的RAM使用。

除非您需要强制返回一个特定大小的有符号或无符号数字(char、short...),否则最好使用int,这也是标准库所做的。


以一种易于理解的方式解释硬件方面的事情,这是一个很好的方法。 - Ogre Psalm33

10

这是C语言的惯用法。

当C语言需要compare类型函数时,它们总是返回一个int。不幸的是,C++也延续了这种做法。

然而,返回int实际上可能是最快的方式,因为它通常与系统中使用的寄存器的大小相同。 (有意模糊.)


1
实际上,shortchar可能会对性能造成影响,例如255+7charint中具有不同的值,因此正确的实现不能仅仅将char存储在int可以存储的位置而不考虑其语义。编译器不一定会优化掉这种效率低下的情况。 - Jack Aidley

10

这个方法实际上并没有返回集合{ -1, 0, 1 }中的整数;它可以是任何整数值。

为什么?我能想到的主要原因是,int应该是体系结构的“自然大小”值;对这个大小的值进行操作通常至少和在更小或更大的值上进行的操作一样快(在许多情况下甚至更快)。所以这是允许实现使用最快速度的情况。


5
如果我要设计一个比较方法,用于比较两个类型为Foo的对象,且只想返回-1、0或1,是否使用short或char是一个好主意呢?
这是一个可行的主意。更好的方式是返回bool(如果只想比较相等)或枚举(提供更多信息):
enum class MyResult
{
  EQUAL,
  LESS,
  GREATER
};

MyResult AreEqual( const Foo &foo1, const Foo & foo2 )
{
  // calculate and return result
}

3
“这个想法还可以。”你有理由支持这个想法吗? - jrok

4
假设一些人正在将代码从C改为C ++。他们决定将strcmp替换为string :: compare 。
由于strcmp返回int,因此更容易让string :: compare 返回int作为礼物。

2
可能是为了让它更像 strcmp,后者也有这个一组返回值。如果你想移植代码,最好有尽可能接近的替代品。

此外,返回值不仅仅是 -101,而是 <00>0

另外,正如提到的,由于返回值受到整数提升的影响,所以将其变小是没有意义的。


-1

因为布尔返回值只能有两个可能的值(true,false),而比较函数可以返回三个可能的值(小于,等于,大于)。

更新

虽然可以返回有符号短整型,但如果您真的想要实现自己的比较函数,您可以返回一个带有两个布尔值的半字节或结构体值。


7
问题中并没有提到要返回布尔类型。事实上,他特别提出了shortchar作为int的替代方案。 - Cody Gray

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