我只是想知道我应该使用 std::size_t
来代替 int
吗?例如:
#include <cstdint>
int main()
{
for (std::size_t i = 0; i < 10; ++i) {
// std::size_t OK here? Or should I use, say, unsigned int instead?
}
}
一般来说,关于何时使用 std::size_t
的最佳做法是什么?
一个好的经验法则是,您需要在循环条件中与自然是 std::size_t
本身的事物进行比较。
std::size_t
是任何 sizeof
表达式的类型,并且保证能够表示 C++ 中任何对象(包括任何数组)的最大大小。通过扩展,它也保证对于任何数组索引都足够大,因此它是数组索引循环的自然类型。
如果您只是计算一个数字,那么使用包含该数字的变量类型或 int
或 unsigned int
(如果足够大)可能更自然,因为这些应该是机器。
size_t
是 sizeof
运算符的结果类型。
将 size_t
用于对数组中的大小或索引进行建模的变量。 size_t
传达语义:您立即知道它表示以字节或索引为单位的大小,而不仅仅是另一个整数。
此外,使用 size_t
表示字节大小有助于使代码具有可移植性。
size_t
类型用于指定某事物的 size,因此使用它是很自然的,例如,获取字符串的长度,然后处理每个字符:
for (size_t i = 0, max = strlen (str); i < max; i++)
doSomethingWith (str[i]);
当然,您确实必须注意边界条件,因为它是无符号类型。顶端的边界通常不是那么重要,因为最大值通常很大(尽管 有可能到达那里)。大多数人只是将 int
用于此类事情,因为他们很少有结构或数组大到足以超过 int
的容量。
但请注意以下事项:
for (size_t i = strlen (str) - 1; i >= 0; i--)
由于无符号值的包装行为,这将导致无限循环(尽管我已经看到编译器对此提出警告)。这也可以通过(稍微难以理解但至少不受包装问题的影响)来缓解:
for (size_t i = strlen (str); i-- > 0; )
通过将减量转换为继续条件的检查后副作用,这会检查值 before 减量的继续,但仍使用循环内的减量值(这就是为什么循环从 len .. 1
而不是 len-1 .. 0
运行)。
strlen
是一种不好的做法。 :) 你可以这样做:for (size_t i = 0, len = strlen(str); i < len; i++) ...
for (size_t i = strlen (str); i --> 0;)
-->
“转到”运算符(请参阅 stackoverflow.com/questions/1642028/…)。已将您的建议纳入答案。
if (i == 0) break;
吗(例如,for (size_t i = strlen(str) - 1; ; --i)
。(虽然我更喜欢你的,但只是想知道这是否也能正常工作)。
根据定义,size_t
是 sizeof
运算符的结果。创建 size_t
是为了指代尺寸。
您做某事的次数(在您的示例中为 10 次)与大小无关,那么为什么要使用 size_t
? int
或 unsigned int
应该没问题。
当然,您在循环中使用 i
所做的事情也很重要。例如,如果将它传递给采用 unsigned int
的函数,则选择 unsigned int
。
无论如何,我建议避免隐式类型转换。 Make all type conversions explicit.
简短的回答:
几乎从不
长答案:
每当您需要在 32 位系统上拥有大于 2gb 的 char 向量时。在所有其他用例中,使用有符号类型比使用无符号类型更安全。
例子:
std::vector<A> data;
[...]
// calculate the index that should be used;
size_t i = calc_index(param1, param2);
// doing calculations close to the underflow of an integer is already dangerous
// do some bounds checking
if( i - 1 < 0 ) {
// always false, because 0-1 on unsigned creates an underflow
return LEFT_BORDER;
} else if( i >= data.size() - 1 ) {
// if i already had an underflow, this becomes true
return RIGHT_BORDER;
}
// now you have a bug that is very hard to track, because you never
// get an exception or anything anymore, to detect that you actually
// return the false border case.
return calc_something(data[i-1], data[i], data[i+1]);
size_t
的签名等效项是 ptrdiff_t
,而不是 int
。但在大多数情况下,使用 int
仍然比 size_t 好得多。 ptrdiff_t
在 32 位和 64 位系统上是 long
。
这意味着每当您与 std::containers 交互时,您总是必须在 size_t 之间进行转换,这不是很漂亮。但是在一个正在进行的本地会议上,c++ 的作者提到用无符号 size_t 设计 std::vector 是一个错误。
如果您的编译器在从 ptrdiff_t 到 size_t 的隐式转换时向您发出警告,您可以使用构造函数语法使其显式:
calc_something(data[size_t(i-1)], data[size_t(i)], data[size_t(i+1)]);
如果只是想迭代一个集合,没有边界检查,使用基于范围的:
for(const auto& d : data) {
[...]
}
这里是 Bjarne Stroustrup(C++ 作者)在 going native 的一些话
对于某些人来说,STL 中的这种有符号/无符号设计错误是足够的理由,不使用 std::vector,而是使用自己的实现。
for(int i = 0; i < get_size_of_stuff(); i++)
很奇怪。现在,当然,你可能不想做很多原始循环,但是 - 来吧,你也使用它们。
x + 1 < y
等价于 x < y - 1
,但它们不是无符号整数。当事物被假定为等效的东西被转换时,这很容易引入错误。
size_t
是一种非常易读的方式来指定项目的大小维度 - 字符串的长度、指针占用的字节数等。它还可以跨平台移植 - 您会发现 64 位和 32 位都可以很好地与系统配合使用函数和 size_t
- unsigned int
可能不会做的事情(例如,您何时应该使用 unsigned long
使用 std::size_t 对 C 样式数组进行索引/计数。
对于 STL 容器,您将拥有(例如)vector<int>::size_type
,它应该用于索引和计数向量元素。
在实践中,它们通常都是无符号整数,但不能保证,尤其是在使用自定义分配器时。
std::size_t
通常是 unsigned long
(在 64 位系统上为 8 个字节)而不是 unisgned int
(4 个字节)。
size_t
索引,因为索引可以是负数。但是,如果不想变成负数,可以将 size_t
用于自己的此类数组实例。
+
,因此似乎 ptrdiff_t
是用于索引的那个。
vector<T>::size_type
(对于所有其他容器也是如此),它实际上是相当无用的,因为它被有效地保证为 size_t
- 它的 typedef'd 为 Allocator::size_type
,关于容器的限制参见 20.1 .5/4 - 特别是,size_type
必须是 size_t
,而 difference_type
必须是 ptrdiff_t
。当然,默认的 std::allocator<T>
满足这些要求。所以只需使用较短的 size_t
并且不要打扰其余的部分 :)
很快,大多数计算机将采用 64 位体系结构和 64 位操作系统:运行在包含数十亿个元素的容器上运行的程序。然后您必须使用 size_t
而不是 int
作为循环索引,否则您的索引将在第 2^32:th 元素处环绕,在 32- 和64 位系统。
为未来做准备!
long int
而不是 int
。如果 size_t
与 64 位操作系统相关,则它与 32 位操作系统同样相关。
size_t 由各种库返回以指示该容器的大小非零。当你回来时使用它:0
但是,在上面的示例中,在 size_t 上循环是一个潜在的错误。考虑以下:
for (size_t i = thing.size(); i >= 0; --i) {
// this will never terminate because size_t is a typedef for
// unsigned int which can not be negative by definition
// therefore i will always be >= 0
printf("the never ending story. la la la la");
}
使用无符号整数有可能产生这些类型的微妙问题。因此恕我直言,我更喜欢仅在与需要它的容器/类型交互时才使用 size_t 。
使用 size_t 时请注意以下表达式
size_t i = containner.find("mytoken");
size_t x = 99;
if (i-x>-1 && i+x < containner.size()) {
cout << containner[i-x] << " " << containner[i+x] << endl;
}
无论您对 x 有什么值,您都将在 if 表达式中得到错误。我花了几天的时间才意识到这一点(代码太简单了,我没有做单元测试),尽管只需要几分钟就可以找出问题的根源。不确定进行强制转换或使用零会更好。
if ((int)(i-x) > -1 or (i-x) >= 0)
两种方式都应该有效。这是我的测试运行
size_t i = 5;
cerr << "i-7=" << i-7 << " (int)(i-7)=" << (int)(i-7) << endl;
输出:i-7=18446744073709551614 (int)(i-7)=-2
我想听听别人的意见。
(int)(i - 7)
是一个下溢,然后转换为 int
,而 int(i) - 7
不是下溢,因为您首先将 i
转换为 int
,然后减去 7
。此外,我发现您的示例令人困惑。
通常最好不要在循环中使用 size_t。例如,
vector<int> a = {1,2,3,4};
for (size_t i=0; i<a.size(); i++) {
std::cout << a[i] << std::endl;
}
size_t n = a.size();
for (size_t i=n-1; i>=0; i--) {
std::cout << a[i] << std::endl;
}
第一个循环没问题。但是对于第二个循环:当 i=0 时,i-- 的结果将是 ULLONG_MAX(假设 size_t = unsigned long long),这不是您在循环中想要的。此外,如果 a 为空,则 n=0 且 n-1=ULLONG_MAX 也不好。
size_t
是一种无符号类型,可以为您的体系结构保存最大整数值,因此可以防止因符号(有符号 int 0x7FFFFFFF
递增 1 将给您 -1)或短尺寸(无符号短整数 0xFFFF)引起的整数溢出增加1会给你0)。
主要用于数组索引/循环/地址运算等。 memset()
之类的函数只接受 size_t
,因为理论上您可能有一块大小为 2^32-1
的内存(在 32 位平台上)。
对于这样简单的循环,不要打扰,只需使用 int。
我一直在努力理解什么以及何时使用它。但是 size_t 只是一个无符号整数数据类型,它在各种头文件中定义,例如 <stddef.h>, <stdio.h>, <stdlib.h>, <string.h>, <time.h>, <wchar.h>
等。
它用于以字节为单位表示对象的大小,因此它被 sizeof 运算符用作返回类型。最大允许大小取决于编译器;如果编译器是 32 位,那么它只是 unsigned int 的 typedef(别名),但如果编译器是 64 位,那么它将是 unsigned long long 的 typedef。 size_t 数据类型永远不会是负数(不包括 ssize_t)。因此,许多 C 库函数(如 malloc, memcpy and strlen
)声明其参数并返回类型为 size_t
。
/ Declaration of various standard library functions.
// Here argument of 'n' refers to maximum blocks that can be
// allocated which is guaranteed to be non-negative.
void *malloc(size_t n);
// While copying 'n' bytes from 's2' to 's1'
// n must be non-negative integer.
void *memcpy(void *s1, void const *s2, size_t n);
// the size of any string or `std::vector<char> st;` will always be at least 0.
size_t strlen(char const *s);
size_t
或任何无符号类型可能被视为循环变量,因为循环变量通常大于或等于 0。
malloc
/free
,即使是 new
/delete
在 C++ 中的有效用例也很少。对于动态内存管理,我们使用智能指针(例如 std::unique_ptr
)代替(如果甚至需要,因为通常可以使用标准容器完成常规操作,例如 std::vector
)。此外,在 C++ 中,我们没有 #include <stddef.h>
也没有 #include <string.h>
。相反,我们使用 #include <string>
和 #include <cstddef>
,并使用 std::string
。 C 和 C++ 是不同的语言。
size_t 是无符号整数类型,可以表示系统上的最大整数。仅当您需要非常大的数组、矩阵等时才使用它。
一些函数返回一个 size_t,如果您尝试进行比较,您的编译器会警告您。
通过使用适当的有符号/无符号数据类型或简单地进行类型转换以进行快速破解来避免这种情况。
size_t 是无符号整数。所以每当你想要 unsigned int 时,你都可以使用它。
当我想指定数组的大小时使用它,计数器等...
void * operator new (size_t size); is a good use of it.
size_t
可能是一个无符号的 64 位整数,而在 32 位机器上它只是一个 32 位无符号整数。
size_t
。ssize_t
用于签名值。ssize_t
没有size_t
的完整范围。它只是size_t
将转换为的任何签名的变体。这意味着,整个内存范围不能与ssize_t
一起使用,并且在依赖于size_t
类型的变量时可能会发生整数溢出。int
的替代品,它更符合语义。您对ssize_t
不提供全部范围的评论是正确的,但对于int
也是正确的。真正重要的是为应用程序使用适当的类型。