char!=(signed char), char!=(unsigned char)

48
下面的代码可以编译,但 char 类型的行为与 int 类型不同。
特别地,
   cout << getIsTrue< isX<int8>::ikIsX  >() << endl;
   cout << getIsTrue< isX<uint8>::ikIsX  >() << endl;
   cout << getIsTrue< isX<char>::ikIsX  >() << endl;

这将导致三个类型的模板被实例化:int8、uint8和char。这是什么意思?

但对于int型,情况并非如此:int和uint32会导致相同的模板实例化,signed int则另外一种。

原因似乎在于C++将char、signed char和unsigned char视为三种不同类型,而int则等同于signed int。我理解的对吗?或者我漏掉了些什么?

#include <iostream>

using namespace std;

typedef   signed char       int8;
typedef unsigned char      uint8;
typedef   signed short      int16;
typedef unsigned short     uint16;
typedef   signed int        int32;
typedef unsigned int       uint32;
typedef   signed long long  int64;
typedef unsigned long long uint64;

struct TrueType {};
struct FalseType {};

template <typename T>
struct isX
{
   typedef typename T::ikIsX ikIsX;
};


// This  int==int32 is ambiguous
//template <>            struct isX<int  >    { typedef FalseType ikIsX; };  // Fails
template <>            struct isX<int32  >  { typedef FalseType ikIsX; };
template <>            struct isX<uint32 >  { typedef FalseType ikIsX; };


// Whay isn't this ambiguous? char==int8
template <>            struct isX<char  >  { typedef FalseType ikIsX; };
template <>            struct isX<int8  >  { typedef FalseType ikIsX; };
template <>            struct isX<uint8 >  { typedef FalseType ikIsX; };


template <typename T> bool getIsTrue();
template <>           bool getIsTrue<TrueType>() { return true; }
template <>           bool getIsTrue<FalseType>() { return false; }

int main(int, char **t )
{
   cout << sizeof(int8) << endl;  // 1
   cout << sizeof(uint8) << endl; // 1
   cout << sizeof(char) << endl;  // 1

   cout << getIsTrue< isX<int8>::ikIsX  >() << endl;
   cout << getIsTrue< isX<uint8>::ikIsX  >() << endl;
   cout << getIsTrue< isX<char>::ikIsX  >() << endl;

   cout << getIsTrue< isX<int32>::ikIsX  >() << endl;
   cout << getIsTrue< isX<uint32>::ikIsX  >() << endl;
   cout << getIsTrue< isX<int>::ikIsX  >() << endl;

}

我正在使用 g++ 4.x 版本。

1
您还应该注意,int8_t 不一定是 signed char,而 uint8_t 也不一定是 unsigned char。特别是在 Solaris 上,如果 char 是有符号的,则 int8_t 就是 char。换句话说,在那里编写的代码将无法编译通过。 - Michał Górny
2
int和uint32会导致相同的模板实例化,而signed int则不同,这肯定是错误的,因为int是有符号的。 - Felix Dombek
4个回答

71

以下是标准的回答:

3.9.1 基本类型 [basic.fundamental]

被声明为字符型(char)的对象应当足够大,能够存储实现基础字符集中的任何一个成员。如果将该字符集中的字符存储在字符对象中,则该字符对象的整数值等于该字符的单个字符文字形式的值。是否可以将负值存储在char对象中是由实现定义的。字符可以显式声明为unsignedsigned简单的charsigned charunsigned char是三种不同的类型。一个char、一个signed char和一个unsigned char占用相同的存储空间并具有相同的对齐要求(basic.types);也就是说,它们具有相同的对象表示形式。对于字符类型,对象表示的所有位都参与值表示。对于无符号字符类型,值表示中所有可能的位模式都表示数字。这些要求不适用于其他类型。在任何特定的实现中,普通的char对象可以具有与signed charunsigned char相同的值;哪一个是实现定义的。


总之,声明确定如何解释任何一个位模式,而实现确定它们之间的转换方法。因此,虽然它们都是相等数量的位,但char并不代表一个数字,任何执行的数学运算都涉及到隐式转换为有符号/无符号或一些算法行为,这些行为只表面上看起来像数学运算。(类比可能是使用重载运算符+修改结构体。)char不需要是8位或一个字节(某些机器不使用8位字节),在这种情况下,8位数学将需要类型int8或uint8。 - Max Power
1
特别是当你涉及到像utf和上层unicode集这样的编码时,你希望基于所表示字符而不是底层字符集编码的一致行为。因此,将char类型隐式地视为数值int是一个糟糕的设计,会产生歧义,即“char”的多重用途。unsigned charsigned char应该被弃用,而采用明确的int8/uint8来进行数值处理,而普通的char则明确保留为字符枚举。特别是考虑到char的大小可以根据各种字符集进行更改。 - Max Power

32

尽管大多数整型(例如shortint)默认为signed,但在C ++中,char没有默认的符号属性。

它既不是类型signed char也不是unsigned char,因此实现可以决定其是否有符号。

当C++程序员将char用作8位整数类型时,这是一个常见错误。


3
因为你非常简明地解释了数据类型之间的差异,并通过比较暗示了它们应该如何使用,所以我给你点赞。 - user7851115
2
历史注释:据说这是因为早期版本的 C 没有指定 char 的符号,所以不同的编译器做了不同的事情,然后标准保留了这种行为,以使旧代码在它们相同的编译器上继续工作。 - Mooing Duck
实现并不决定char是有符号还是无符号。Char既不是有符号的,也不是无符号的,因为它不是数字表示,只是字符枚举,被解释为某些任意字符代码集。实现定义的是如何将char原语隐式或显式地转换为和从数字数据表示中转换的。使用无符号和有符号char是数值的,但应该弃用int8和uint8来避免类型名称不明确的混淆,而char不需要是8位字节,进一步削弱了数值效用。 - Max Power

30

对于这样的问题,我喜欢查看C语言的Rationale文档,它通常也会提供C++中的答案,有时当我阅读标准时会出现一些疑问。关于这个问题,它有如下解释:

指定了三种char类型:signed、plain和unsigned。一个plain char类型可以根据实现而变成signed或unsigned类型,就像以前的做法一样。引入了signed char类型是为了在那些将plain char类型实现为unsigned类型的系统上提供一个一字节有符号整数类型。出于对称性的原因,关键字signed可以作为其他整数类型的类型名称的一部分。

C语言的Rationale文档


那么,我们为什么需要 signed char?只是为了用它来表示一个有符号的一字节整数吗? - Alcott
3
@Alcott,我认为char可能是有符号的,也可能是无符号的,这是由具体实现决定的,但是signed char始终是有符号的,而unsigned char始终是无符号的,如果你想确保/明确类型。 - hanshenrik

20

没错,charunsigned charsigned char是不同的类型。如果char只是一个同义词,根据编译器的实现可以对应于signed charunsigned char中的一个,那就太好了,但标准规定它们是不同的类型。


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