这对我来说似乎是一个奇怪的疏漏,因为它允许编写的代码更不容易在意外大输入时产生溢出。
此外,使用无符号整数可以作为自我说明的形式,因为它们表明无符号整数所表示的值不应该是负数。
最后,在某些情况下,无符号整数可以更有效地进行某些操作,例如除法。
包括无符号整数有何不利之处?
我听说过一些故事,说它们应该被包含在原始的Java发布中。Oak是Java的前身,在一些规范文档中提到了无符号值。不幸的是,这些从未出现在Java语言中。据大家所知,它们只是由于时间限制而没有被实现。
char
)被遗漏掉,是因为设计者认为它们与语言目标不相符,是个坏主意。 - Stephen C在“C”规范中有一些宝石,由于实用原因而被Java放弃,但随着开发者的需求,这些宝石正在慢慢回归(如闭包等)。
我提到第一个是因为它与本讨论相关;指针值对无符号整数算术的遵循性。并且,与此主题相关的是,在Java的有符号世界中维护无符号语义的困难。
我猜想,如果有人请Dennis Ritchie的替身来建议Gosling的设计团队,它会建议给Signed一个“无限的零”,这样所有地址偏移请求都会首先添加它们的代数环大小以消除负值。
这样,任何抛出到数组的偏移量都不会产生SEGFAULT。例如,在我称之为RingArray的双精度封装类中,需要无符号行为-在“自旋转循环”上下文中:
// ...
// Housekeeping state variable
long entrycount; // A sequence number
int cycle; // Number of loops cycled
int size; // Active size of the array because size<modulus during cycle 0
int modulus; // Maximal size of the array
// Ring state variables
private int head; // The 'head' of the Ring
private int tail; // The ring iterator 'cursor'
// tail may get the current cursor position
// and head gets the old tail value
// there are other semantic variations possible
// The Array state variable
double [] darray; // The array of doubles
// somewhere in constructor
public RingArray(int modulus) {
super();
this.modulus = modulus;
tail = head = cycle = 0;
darray = new double[modulus];
// ...
}
// ...
double getElementAt(int offset){
return darray[(tail+modulus+offset%modulus)%modulus];
}
// remember, the above is treating steady-state where size==modulus
// ...
void addElement(long entrycount,double value){ // to be called only by the keeper of entrycount
this.entrycount= entrycount;
cycle = (int)entrycount/modulus;
if(cycle==0){ // start-up is when the ring is being populated the first time around
size = (int)entrycount; // during start-up, size is less than modulus so use modulo size arithmetic
tail = (int)entrycount%size; // during start-up
}
else {
size = modulus;
head = tail;
tail = (int)entrycount%modulus; // after start-up
}
darray[head] = value; // always overwrite old tail
}
因为unsigned
类型是纯恶魔。
在C语言中,unsigned - int
会产生unsigned
类型,这一事实更加恶魔。
以下是这个问题的一个快照,它曾经让我受过不止一次的伤害:
// We have odd positive number of rays,
// consecutive ones at angle delta from each other.
assert( rays.size() > 0 && rays.size() % 2 == 1 );
// Get a set of ray at delta angle between them.
for( size_t n = 0; n < rays.size(); ++n )
{
// Compute the angle between nth ray and the middle one.
// The index of the middle one is (rays.size() - 1) / 2,
// the rays are evenly spaced at angle delta, therefore
// the magnitude of the angle between nth ray and the
// middle one is:
double angle = delta * fabs( n - (rays.size() - 1) / 2 );
// Do something else ...
}
你注意到这个 bug 了吗?我承认只有在使用调试器后才看到它。
因为 n
是无符号类型的 size_t
,整个表达式 n - (rays.size() - 1) / 2
的计算结果是 unsigned
。该表达式旨在成为从中间射线开始的第 n
条射线的有符号位置:左侧第一条射线的位置为-1,右侧第一条射线的位置为+1,以此类推。取绝对值并乘以 delta
角度后,我将得到第 n
条射线与中间射线之间的角度。
不幸的是,上述表达式包含了邪恶的无符号数,而不是像-1这样的值,它的计算结果是 2^32-1。随后的转换为 double
密封了这个 bug。
由于滥用unsigned
算术操作导致了一两个错误,我们不得不开始思考是否值得这多出来的一位所带来的额外麻烦。我正在尽可能地避免在算术运算中使用unsigned
类型,尽管仍然会在非算术操作(如二进制掩码)中使用它。
exit(1)
真的“值得额外的麻烦”吗?不能打开大文件真的值得保证不熟悉 Java 的程序员不会在使用“unsigned”时出错吗? - yynyn - (rays.size() - 1) / 2
。你应该总是给二元运算符加上括号,因为代码的读者不应该假设计算机程序中的操作顺序。即使我们惯例上说 a+bc = a+(bc),但这并不意味着在阅读代码时可以假设这一点。此外,计算应该在循环之外定义,以便可以在没有循环的情况下进行测试。这是一个类型不匹配的错误,而不是无符号整数的问题。在 C 中,你需要确保你的类型匹配。 - Dmytroclass Point { float x, y; public: virtual ~Point() {} };
你能发现商业软件使用该类时为什么会比具有完全相同功能的竞争对手多用2倍的内存吗?没错:将 Point::~Point()
设为虚函数使其更符合面向对象编程(在那种情况下是不必要的),但这也为8字节的结构体添加了额外的8个字节(这对于内存占用来说至关重要)。 - Michaelunsigned int num = -7;
printf("%d", num);
你能猜出打印的内容吗?
-7
哇!一个无符号整数是负数!确实如此。真正的正整数并不存在。无符号整数只是一个n字节(取决于C语言架构)的值,它不为符号分配MSB。它不检查所分配或读取的数字的实际符号。
char
数据类型。虽然听起来有些愚蠢,但是char
允许您存储0到255范围内的数字。不过,打印可能会有问题。您还可以使用byte
通过一些技巧来存储0到255。由于Java本身就是面向对象的,因此在其中使用一些对象魔法也没有问题。 - Thorfinn你的问题是“为什么Java不支持无符号整数”?
我的回答是,Java希望它所有的原始类型:byte、char、short、int和long都应该像汇编一样被视为byte、word、dword和qword,而Java运算符是对所有原始类型进行有符号操作,除了char之外,但只有在char上它们才是无符号的16位。
因此,静态方法应该是32位和64位的无符号操作。
你需要一个最终类,它的静态方法可以用于无符号操作。
你可以创建这个最终类,给它起任何你想要的名字,并实现它的静态方法。
如果你不知道如何实现静态方法,那么这个link可能会帮助你。
在我看来,Java与C++完全不同,如果它既不支持无符号类型也不支持运算符重载,因此我认为Java应该被视为与C++和C完全不同的语言。我建议避免使用 short、int 和 long 原始类型,而是分别使用 word、dword 和 qword,并调用无符号操作和/或有符号操作的静态方法,而不是使用运算符。
如果你只进行有符号操作,并且在代码中仅使用运算符,则可以使用这些原始类型 short、int 和 long。
实际上,word、dword 和 qword 在语言中并不存在,但你可以为每个创建新的类,它们的实现应该非常容易:
类word仅包含原始类型short,类dword仅包含原始类型int,类qword仅包含原始类型long。现在,所有无符号和有符号的方法都可以作为静态或非静态方法实现在每个类中,即通过在word类上赋予有意义的名称来实现所有16位操作(无符号和有符号),通过在dword类上赋予有意义的名称来实现所有32位操作(无符号和有符号),通过在qword类上赋予有意义的名称来实现所有64位操作(无符号和有符号)。
如果您不喜欢为每个方法提供太多不同的名称,则可以在Java中使用重载,很高兴看到Java没有删除这个功能!
如果您想要8位有符号操作的方法而不是运算符,并且没有任何运算符的8位无符号操作的方法,则可以创建Byte类(请注意,第一个字母'B'是大写的,因此这不是原始类型byte),并在该类中实现这些方法。
关于按值传递和按引用传递:那是我能想到的唯一解决方案。我只希望我可以将原始类型typedef为word、dword和qword,但Java不支持typedef或者using,不像C#支持using,这与C的typedef等效。
关于输出:
对于相同的比特序列,你可以用很多种方式来打印它们:二进制、十进制(例如C printf中%u的含义)、八进制(例如C printf中%o的含义)、十六进制(例如C printf中%x的含义)和整数(例如C printf中%d的含义)。
注意,C printf不知道传递给函数作为参数的变量的类型,因此printf仅从传递给函数第一个参数的char *对象中知道每个变量的类型。
因此,在每个类中:Byte、word、dword和qword,您都可以实现打印方法并获得printf的功能,即使类的基本类型是有符号的,您仍然可以按照某些涉及逻辑和移位操作的算法将其打印为无符号数以获取要打印到输出的数字。
很遗憾,我给你的链接并没有展示如何实现这些打印方法,但我相信你可以通过谷歌搜索需要实现这些打印方法的算法。
这就是我能回答你问题和建议的全部了。
我能想到一个不幸的副作用。在Java嵌入式数据库中,使用32位ID字段可以拥有的ID数量为2^31,而不是2^32(约20亿,而不是约40亿)。
我认为的原因是因为他们太懒了,不想去实现/修正那个错误。 暗示C/C++程序员不理解无符号、结构体、联合体、位标志...这简直荒谬。
要么你正在与一个基本的/bash/java程序员交谈,即将开始学习C语言,没有任何真正的知识,要么你只是在胡说八道。 ;)
当你每天处理来自文件或硬件的格式时,你会开始质疑,他们到底在想什么。
一个很好的例子是尝试使用无符号字节作为自旋转循环。 对于那些不理解最后一句话的人,你怎么能称自己为程序员呢。
DC