19

我有一个简单的程序。请注意,我使用了一个大小为 1 字节的无符号固定宽度整数。

#include <cstdint>
#include <iostream>
#include <limits>

int main()
{
    uint8_t x = 12;
    std::cout << (x << 1) << '\n';
    std::cout << ~x;

    std::cin.clear();
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    std::cin.get();

    return 0;
}

我的输出如下。

24
-13

我测试了更大的数字,操作员<<总是给我正数,而操作员~总是给我负数。然后我用sizeof()了,发现...

当我使用左移位运算符(<<)时,我收到一个无符号的 4 字节整数。

当我使用按位非运算符(~)时,我收到一个有符号的 4 字节整数。

似乎按位非运算符(~)像算术运算符一样进行有符号整数提升。然而,左移运算符 ( <<) 似乎提升为无符号积分。

我觉得有义务知道编译器何时在我背后改变了一些东西。如果我的分析是正确的,所有按位运算符是否都提升为 4 字节整数?为什么有些签名有些未签名?我很混乱!

编辑:我总是得到积极或总是得到负值的假设是错误的。但是由于错误,我了解了真正发生的事情,这要归功于下面的精彩答案。

4

4 回答 4

13

[expr.unary.op]

的操作数~应具有整数或非范围枚举类型;结果是其操作数的反码。进行整体促销。

[expr.shift]

移位运算符<<>>组从左到右。[...] 操作数应为整数或非范围枚举类型,并执行整数提升

什么是整体推广uint8_t(通常会unsigned_char在幕后)?

[conv.prom]

如果可以表示源类型的所有值,则可以将除bool, char16_t,之外的整数类型的纯右值char32_t,或者 wchar_t其整数转换等级(4.13)小于等级的 int纯右值转换为类型的纯右值;否则,源纯右值可以转换为类型的纯右值。intintunsigned int

所以int,因为 a 的所有值都uint8_t可以用 来表示int

是什么int(12) << 1int(24).

是什么~int(12)int(-13).

于 2015-05-27T05:51:16.870 回答
5

出于性能原因,C 和 C++ 语言认为int是“最自然的”整数类型,而比 an“更小”的类型int被认为是一种“存储”类型。

当您在表达式中使用存储类型时,它会自动转换为 anintunsigned int隐式转换为 an。例如:

// Assume a char is 8 bit
unsigned char x = 255;
unsigned char one = 1;

int y = x + one; // result will be 256 (too large for a byte!)
++x;             // x is now 0

发生的事情是,xone第一个表达式中已隐式转换为整数,已计算加法并将结果存储回整数中。换句话说,没有使用两个无符号字符执行计算。

同样,如果您float在表达式中有一个值,编译器会做的第一件事就是将其提升为 a double(换句话说,float它是一种存储类型,double而是浮点数的自然大小)。这就是如果你printf用来打印浮点数的原因,你不需要说%lfint 格式字符串并且%f就足够了(但是%lf需要,scanf因为该函数存储结果并且 afloat可以小于 a double)。

C++ 使事情变得相当复杂,因为在将参数传递给函数时,您可以区分ints 和更小的类型。因此,在每个表达式中执行转换并不总是正确的......例如,您可以:

void foo(unsigned char x);
void foo(int x);

在哪里

unsigned char x = 255, one = 1;
foo(x);       // Calls foo(unsigned char), no promotion
foo(x + one); // Calls foo(int), promotion of both x and one to int
于 2015-05-27T06:08:02.367 回答
4

我测试了更大的数字,运算符 << 总是给我正数,而 operator ~ 总是给我负数。然后我使用 sizeof() 并发现...

错了,测试一下:

uint8_t v = 1;
for (int i=0; i<32; i++) cout << (v<<i) << endl;

给出:

1
2
4
8
16
32
64
128
256
512
1024
2048
4096
8192
16384
32768
65536
131072
262144
524288
1048576
2097152
4194304
8388608
16777216
33554432
67108864
134217728
268435456
536870912
1073741824
-2147483648

uint8_t是一个 8 位长的无符号整数类型,它可以表示 [0,255] 范围内的值,因为包含在它的范围内的那个范围int被提升为int(not unsigned int)。提升至int优先于提升至unsigned

于 2015-05-27T06:05:07.823 回答
3

研究二进制补码以及计算机如何存储负整数。
尝试这个

#include <cstdint>
#include <iostream>
#include <limits>
int main()
{
uint8_t x = 1;
int shiftby=0;
shiftby=8*sizeof(int)-1;
std::cout << (x << shiftby) << '\n'; // or std::cout << (x << 31) << '\n';

std::cout << ~x;

std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cin.get();
return 0;
}

输出为-2147483648

一般来说,如果有符号数的第一位是 1,则认为它是负数。当您取大量并转移它时。如果您将其移位以使第一位为1,则它将为负

** 编辑 **
好吧,我可以想到移位运算符使用 unsigned int 的原因。考虑右移操作>>,如果你右移 -12 你会得到122而不是 -6。这是因为它在开头添加了一个零而不考虑符号

于 2015-05-27T05:39:58.217 回答