将数组的所有元素初始化为相同的数字
Initialize all the elements of an array to the same number
前段时间,我的老老师发布了这段代码,说这是将数组初始化为相同数字的另一种方法(当然不是零)。
在这种情况下是三个。
他说,这种方式比for
循环略好。为什么需要左移运算符?为什么我需要另一个长数组? 我不明白这里发生了什么。
int main() {
short int A[100];
long int v = 3;
v = (v << 16) + 3;
v = (v << 16) + 3;
v = (v << 16) + 3;
long *B = (long*)A;
for(int i=0; i<25; i++)
B[i] = v;
cout << endl;
print(A,100);
}
有很多方法可以用相同的值填充数组,如果您担心性能,则需要进行测量。
C++有一个专用函数来用值填充数组,我会使用它(在#include <algorithm>
和#include <iterator>
之后):
std::fill(std::begin(A), std::end(A), 3);
你不应该低估优化编译器可以用这样的事情做什么。
如果你有兴趣看看编译器是做什么的,那么Matt Godbolt的编译器资源管理器是一个非常好的工具,如果你准备学习一点汇编程序。从这里可以看到,编译器可以优化对 12 个(和位)128 位存储的fill
调用,并展开任何循环。由于编译器了解目标环境,因此无需在源代码中编码任何特定于目标的假设即可执行此操作。
他假设long
比short
长四倍(这不能保证;他应该使用int16_t和int64_t)。
他占用更长的内存空间(64 位),并用四个短(16 位)值填充它。他通过将位移动 16 个空格来设置值。
然后,他想将短裤数组视为长整型数组,因此他可以通过仅执行 25 次循环迭代而不是 100 次循环迭代来设置 100 个 16 位值。
这就是你的老师的想法,但正如其他人所说,这种投射是不确定的行为。
真是一堆猪圈。
对于初学者来说,
v
将在编译时计算。取消引用
B
跟随long *B = (long*)A;
的行为是未定义的,因为这些类型不相关。B[i]
是对B
的取消引用。
假设long
比short
大四倍是没有任何道理的。
以简单的方式使用for
循环,并信任编译器进行优化。请漂亮,上面放糖。
这个问题有C++标签(没有 C 标签),所以这应该以C++风格完成:
// C++ 03
std::vector<int> tab(100, 3);
// C++ 11
auto tab = std::vector<int>(100, 3);
auto tab2 = std::array<int, 100>{};
tab2.fill(3);
此外,老师正在尝试比编译器更聪明,它可以做令人兴奋的事情。做这样的技巧是没有意义的,因为如果配置正确,编译器可以为您完成:
- 您的代码程序集
- 删除了勾号的代码程序集
- 阵列方法
- 矢量方法
如您所见,每个版本的-O2
结果代码(几乎)相同。在-O1
的情况下,技巧会有所改善。
所以底线,你必须做出选择:
- 编写难以阅读的代码,不使用编译器优化
- 编写可读代码并使用
-O2
使用 Godbolt 站点试验其他编译器和配置。 另请参阅最新的cppCon演讲。
正如其他答案所解释的那样,该代码违反了类型别名规则,并做出了标准无法保证的假设。
如果您真的想手动进行此优化,这将是一种具有明确定义行为的正确方法:
long v;
for(int i=0; i < sizeof v / sizeof *A; i++) {
v = (v << sizeof *A * CHAR_BIT) + 3;
}
for(int i=0; i < sizeof A / sizeof v; i++) {
std:memcpy(A + i * sizeof v, &v, sizeof v);
}
关于对象大小的不安全假设通过使用sizeof
来修复,而混叠冲突通过使用std::memcpy
来修复,无论底层类型如何,它都有明确定义的行为。
也就是说,最好保持代码简单,让编译器发挥其魔力。
为什么我需要左移运算符?
关键是用较小整数的多个副本填充较大的整数。如果你把一个两字节的值s
写成一个大的整数l
,然后将位向左移动两个字节(我的固定版本应该更清楚这些幻数的来源),那么你将有一个整数,其中包含构成值s
的两个字节副本。重复此操作,直到l
中的所有字节对都设置为这些相同的值。要进行移位,您需要换档操作员。
当将这些值复制到包含双字节整数数组的数组上时,单个副本会将多个对象的值设置为较大对象的字节值。由于每对字节具有相同的值,因此数组中较小的整数也是如此。
为什么我需要另一个
long
数组?
没有long
数组。只有一组short
.
老师向您展示的代码是一个格式不正确的程序,不需要诊断,因为它违反了指针实际上指向它们声称指向的事物的要求(也称为"严格别名")。
举个具体的例子,编译器可以分析你的程序,注意A
没有直接写入,也没有short
写入,并证明A
一旦创建就从未被更改过。
在C++标准下,所有这些弄乱B
都可以证明无法在格式良好的程序中修改A
。
for(;;)
循环甚至范围循环可能会优化为A
的静态初始化。 在优化编译器下,教师的代码将针对未定义的行为进行优化。
如果你真的需要一种方法来创建用一个值初始化的数组,你可以使用这个:
template<std::size_t...Is>
auto index_over(std::index_sequence<Is...>) {
return [](auto&&f)->decltype(auto) {
return f( std::integral_constant<std::size_t, Is>{}... );
};
}
template<std::size_t N>
auto index_upto(std::integral_constant<std::size_t, N> ={})
{
return index_over( std::make_index_sequence<N>{} );
}
template<class T, std::size_t N, T value>
std::array<T, N> make_filled_array() {
return index_upto<N>()( [](auto...Is)->std::array<T,N>{
return {{ (void(Is),value)... }};
});
}
现在:
int main() {
auto A = make_filled_array<short, 100, 3>();
std::cout << "n";
print(A.data(),100);
}
在编译时创建填充数组,不涉及循环。
使用 godbolt,您可以看到数组的值是在编译时计算的,并且在我访问第 50 个元素时提取了值 3。
然而,这是矫枉过正(和 c++14)。
我认为他试图通过同时复制多个数组元素来减少循环迭代的次数。正如其他用户在这里已经提到的,此逻辑将导致未定义的行为。
如果这一切都是为了减少迭代,那么通过循环展开,我们可以减少迭代次数。但对于这种较小的阵列来说,它不会明显更快。
int main() {
short int A[100];
for(int i=0; i<100; i+=4)
{
A[i] = 3;
A[i + 1] = 3;
A[i + 2] = 3;
A[i + 3] = 3;
}
print(A, 100);
}
您可以使用std::array
而不是内置数组以及一个make_array
函数模板,该模板将返回一个将所有元素设置为3
(或其他给定数字)的std::array
,如下所示。
template<std::size_t N> std::array<int, N> constexpr make_array(int val)
{
std::array<int, N> tempArray{};
for(int &elem:tempArray)
{
elem = val;
}
return tempArray;
}
int main()
{
//-------------------------------V-------->number of elements
constexpr auto arr = make_array<8>(5);
//-------------------------------^---->value of element to be initialized with
//lets confirm if all objects have the expected value
for(const auto &elem: arr)
{
std::cout << elem << std::endl; //prints all 5
}
}
工作演示
- 为什么随机数生成器不在void函数中随机化数字,而在main函数中随机化
- 是否可以初始化不可复制类型的成员变量(或基类)
- C++使用整数的压缩数组初始化对象
- C++初始化基类
- 多成员Constexpr结构初始化
- 复制列表初始化的隐式转换的等级是多少
- 内联映射初始化的动态atexit析构函数崩溃
- 使用从1到N的数字初始化std数组模板参数
- 使用数字初始化常量引用
- 成员初始化列表中初始化的向量与传递给构造函数的数字不同
- 设置::使用数字中的引号进行向量初始化
- 初始化具有递增数字的编译时常量大小的数组
- 如何初始化字符串以包含C 中所需数量的数字数量
- 如何使用整数值中的数字符号初始化字符数组
- 添加大数字C++-初始化程序失败
- 使用一系列数字有效地初始化 std::set
- 初始化成员函数字段
- Array[n]vs Array[10]-使用变量与数字文本初始化数组
- 大数字类,初始化
- c++中未初始化数组时出现奇怪的数字