Cim*_*ive 59 c++ arrays for-loop
前段时间,我的老教师发布了这段代码,说这是将数组初始化为相同数字的另一种方法(当然不是零).
在这种情况下三个.
他说这种方式比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);
}
Run Code Online (Sandbox Code Playgroud)
CB *_*ley 75
有很多方法可以用相同的值填充数组,如果你关心性能,那么你需要测量.
C++有一个专用的函数来填充一个值的数组,我会使用它(之后#include <algorithm>
和#include <iterator>
):
std::fill(std::begin(A), std::end(A), 3);
Run Code Online (Sandbox Code Playgroud)
你不应该低估优化编译器可以用这样的东西做什么.
如果您有兴趣了解编译器的功能,那么Matt Godbolt的Compiler Explorer是一个非常好的工具,如果您准备学习一点汇编程序的话.从这里可以看出,编译器可以优化fill
对12个(和一些)128位存储的调用,并且可以展开任何循环.因为编译器具有目标环境的知识,所以他们可以在不编码源代码中的任何特定于目标的假设的情况下执行此操作.
小智 67
他认为这long
是四倍short
(不保证;他应该使用int16_t和int64_t).
他占用了更长的内存空间(64位)并用四个短(16位)值填充它.他通过将位移16个空格来设置值.
然后他想将一组shorts视为long数组,因此他只需要25次循环迭代而不是100次就可以设置100个16位值.
这就是你的老师的想法,但正如其他人所说,这种演员是不明确的行为.
Bat*_*eba 44
什么是hogwash的绝对负荷.
对于初学者,v
将在编译时计算.
取消引用B
后续行为long *B = (long*)A;
未定义,因为类型不相关.B[i]
是一个解除引用B
.
假设a long
是a 的四倍,没有任何理由short
.
for
以简单的方式使用循环并信任编译器进行优化.非常好,上面加糖.
Mar*_*k R 20
这个问题有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);
Run Code Online (Sandbox Code Playgroud)
此外,老师正在尝试智能的编译器,它可以做令人兴奋的事情.没有必要做这样的技巧,因为如果配置正确,编译器可以为你做这些:
如您所见,-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);
}
Run Code Online (Sandbox Code Playgroud)
关于对象大小的不安全假设通过使用来修复sizeof
,并且通过使用修复了别名冲突std::memcpy
,其具有明确定义的行为,而不管底层类型如何.
也就是说,最好保持代码简单,让编译器发挥其魔力.
为什么我需要左移操作符?
关键是用较小整数的多个副本填充一个更大的整数.如果你把一个两字节的值写成一个s
大整数l
,那么将剩下的位移到两个字节(我的固定版本应该更清楚那些魔术数字的来源)然后你将有一个带有两个字节副本的整数构成价值s
.重复此过程,直到所有字节对l
都设置为相同的值.要进行班次,您需要班次操作员.
当这些值复制到包含双字节整数数组的数组上时,单个副本会将多个对象的值设置为较大对象的字节值.由于每对字节具有相同的值,因此数组的较小整数也是如此.
为什么我需要另一个阵列
long
?
没有阵列long
.只有一个数组short
.
您的老师向您展示的代码是一个格式错误的程序,不需要诊断,因为它违反了指针实际指向他们声称指向的东西的要求(也称为"严格别名").
作为一个具体的例子,编译器可以分析你的程序,注意A
没有直接写入并且没有short
写入,并且证明A
一旦创建就永远不会改变.
B
根据C++标准,所有这些都可以证明,因为无法A
在格式良好的程序中进行修改.
一个for(;;)
循环甚至一个ranged-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)... }};
});
}
Run Code Online (Sandbox Code Playgroud)
现在:
int main() {
auto A = make_filled_array<short, 100, 3>();
std::cout << "\n";
print(A.data(),100);
}
Run Code Online (Sandbox Code Playgroud)
在编译时创建填充数组,不涉及循环.
使用godbolt可以看到数组的值是在编译时计算的,当我访问第50个元素时,值3被提取出来.
然而,这是过度杀伤(和c ++ 14).