我目前正在阅读Lahoie, Lippman和Moo编写的第5版c++ Primer,并一直在努力解决一些问题。
首先,我只是想确认,当使用任何cctype
函数时,我必须确保我包括标题,对吗?因为一开始,我忘了包含它,但它仍然运行。这让我很困惑。
另外,我正在浏览一个不同的问题(我将得到),并发现了另一个问题哈哈!当使用来自cctype
的任何东西时,我应该把它写为std::/write using std::
,例如,如果我使用tolower
,要么在每个实例上写std::tolower
/为它写一个using语句。这是有意义的,因为它确实说它们是"在std
命名空间中定义的",但我没有意识到,并且一直在写它,没有问题。我猜size_t
也差不多,对吧?
size_t
,我有一个问题。这是我的代码:
// Exercise Section 3.5.2., Exercise 3.30
#include <iostream>
#include <cstddef>
using std::cout; using std::endl;
int main()
{
constexpr size_t num_size = 10;
int num[num_size] = {};
for (size_t n = 0; n < num_size; ++n) {
num[n] = n;
cout << num[n] << endl;
}
return 0;
}
因此,代码应该定义一个包含10个int
的数组,并为每个元素赋予与其在数组中的位置相同的值。
它运行正确,但我在num[n]=n
部分收到错误。上面写着Implicit conversion loses integer precision: size_t (aka 'unsigned long') to int
我明白这意味着什么,但我的问题是,这本书说"当我们使用一个变量下标数组,我们通常应该定义该变量类型size_t
"。我做过这个,它给出了这个错误。它确实运行得很好,但似乎这种事情可能会导致错误。
注:在这段代码中,就像我上面问的,我应该有using std::size_t
吗?
我必须包括头,即使它没有工作吗?
是的,你必须总是包含至少一个头文件来提供你需要的每一个定义/声明,除非确切的原型/类型定义是有保证的,并且你直接把它放到你的源代码中。
一些标准头文件可能包含其他标准头文件,这可能会让你在某些时候侥幸逃脱,但当你升级/移植到不同的实现时,你会后悔的。
我读到所有的声明和定义都是"在std命名空间中定义的",但我没有意识到,并且一直在写它,没有问题。我猜size_t也是类似的,对吧?
是的,是一样的。出于兼容性的考虑,C/Unicode所采用的<c...>
头也可以在全局命名空间中提供它们的符号。
17.6.1.2标头
[headers]
1 c++标准库的每个元素(视情况而定)在头文件中声明或定义。175
c++标准库提供了55个c++库头,如表14所示。
C标准库的功能在26个附加头中提供,如表15所示。
除第18条至第30条和附件D中指出的情况外,每个头文件cname的内容应与相应的头文件名称。h的内容相同,如C标准库(1.2)或C Unicode TR(视情况而定)中规定的,就像通过包含一样。然而,在c++标准库中,这些声明(在C中定义为宏的名字除外)在命名空间std的命名空间范围(3.3.6)内。这些名字是否首先在全局命名空间范围内声明,然后通过显式using-declarations(7.3.3)注入命名空间std,这是未指定的。
在C中被定义为宏的名字在c++标准库中也应该被定义为宏,即使C授予作为函数实现的许可。[注:C语言中定义为宏的名称包括:assert、offsetof、setjmp、va_arg、va_end和va_start。]-end note]
在C中定义为函数的名称,在c++标准库中也应定义为函数。176
在c++中作为关键字或操作符的标识符不能在c++标准库头文件中定义为宏。177
C标准库头文件描述了在c++程序中使用name.h (C头文件)格式的效果。178
在我的示例程序中,我使用
size_t
作为数组索引。这招管用,不过我得到了警告。我应该这样做吗?它通常会导致错误吗?
当然,正如你所猜测的,名称空间std
也同样适用。
至于其他的,有一个很好的短语:"好的建议是有理由的"。
你应该使用std::size_t
作为索引的原因是,这个类型a)向读者表明它是一个大小或索引,b)保证它足够大。在您的例子中,一个较低的int
,保证其最小最大值为215-1,就可以了。
另一种方法是在赋值时直接转换为正确的类型。
你问:
首先,我只是想确认,当使用任何cctype函数时,我必须确保我包括标题,对吗?
是的,是这样的。您可能会间接获得一些函数声明或其他声明,但这不是可移植代码。您应该理解标准中声明在哪里可用,并在使用函数、类型等之前包含该头文件。
你问:
我猜
size_t
也类似,对吧?
是的,你应该使用std::size_t
。
有一个关于这个话题的帖子。浏览size_t和std::size_t的区别
你问:
在这段代码中,就像我上面问的,我应该使用std::size_t吗?
在循环中,也可以使用int
。使用std::size_t
索引数组的建议是一个很好的建议,但并不是不可违背的。
如果您选择使用size_t
的n
,它是可以使用static_cast
转换为int
,以摆脱编译器的警告/错误。
num[n] = static_cast<int>(n);
你声明你的数组为
int num[num_size] = {};
这意味着它是一个包含10
元素的int
数组。
然后你说
for (size_t n = 0; n < num_size; ++n)
num[n] = n;
注意n
的类型是size_t
,也就是unsigned long
。因此,您将unsigned long
值放入int
数组中,因此它们被隐式转换为int
值。
标准头文件在全局命名空间中放置了一堆东西。理想情况下,他们不会,但他们确实会。通常这是因为有些东西实际上是一个宏,而不是一个类型定义函数。
有时可以不包含头文件,因为您包含的其他头文件包含了缺失的头文件。
我必须确保包含标题,对吧?因为,一开始,我忘了包含它,但它仍然运行。
一些标准标头可以包含其他标头。但是,如果程序中使用了头文件中的一些声明,则显式地包含头文件是一个好主意。在包含的头文件的其他实现中,可能会出现这样的情况:所需的头文件不包含在内,编译器将发出错误。
。这将是有意义的,因为它确实说它们是"定义在std命名空间",但我没有意识到,并且一直在写它还没出过问题。
c++标准允许编译器将C标准函数放在全局命名空间中。尽管在这种情况下,最好显式地指定在任何情况下都要声明函数的命名空间std。
对于最后一个问题,那么数组的元素具有类型int
,而你分配给它们类型size_t
的值,问题是类型int
不能容纳类型size_t
的所有值,编译器会警告你这一点。你可以显式地指定强制转换,告诉编译器你知道你在做什么。
num[n] = ( int )n;
或
num[n] = static_cast<int>( n );