将数组的所有元素初始化为相同的数字

时间:2017-10-06 07:29:30

标签: 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);
}

7 个答案:

答案 0 :(得分:77)

有很多方法可以用相同的值填充数组,如果你担心性能,那么你需要测量。

C ++有一个专门的函数来为数组填充一个值,我会使用它(在#include <algorithm>#include <iterator>之后):

std::fill(std::begin(A), std::end(A), 3);

你不应该低估优化编译器可以用这样的东西做什么。

如果你有兴趣了解编译器的作用,那么如果你准备学习一点汇编程序,那么Matt Godbolt的Compiler Explorer是一个非常好的工具。正如您在here中所看到的,编译器可以优化fill调用12个(和一些)128位存储,并展开任何循环。因为编译器知道目标环境,所以他们可以在不编码源代码中任何特定于目标的假设的情况下完成此任务。

答案 1 :(得分:69)

他认为shortnpm dependencies: "expo": "21.0.0" "react-native": "0.48.4" "react": "16.0.0-alpha.12" app.json "sdkVersion": "21.0.0" Android-7.1.1, Expo-1.20 (Android application) 长四倍(不保证;他应该使用int16_t和int64_t)。

他占用更长的内存空间(64位)并用四个短(16位)值填充它。他通过将位移16个空格来设置值。

然后他想将一组short作为long数组,所以他只需要25次循环迭代而不是100次就可以设置100个16位值。

这就是你的老师的想法,但正如其他人所说,这种演员是未定义的行为。

答案 2 :(得分:46)

hogwash的绝对负荷。

  1. 对于初学者,v将在编译时计算

  2. B之后取消引用long *B = (long*)A;的行为未定义,因为类型不相关。 B[i]B的解除引用。

  3. 假设longshort大四倍,我们没有任何理由。

  4. 以简单的方式使用for循环并信任编译器进行优化。非常好,加糖在上面。

答案 3 :(得分:22)

这个问题有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网站试验其他编译器和配置。 另请参阅the latest cppCon talk

答案 4 :(得分:8)

正如其他答案所解释的那样,代码违反了类型别名规则,并做出了标准无法保证的假设。

如果您真的想手动进行优化,这将是一种具有明确定义的行为的正确方法:

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,那么 shift 剩下两个字节的位(我的固定版本应该更清楚这些神奇的数字在哪里来自)那么你将有一个整数,其中两个字节副本构成值l。重复此过程,直到long中的所有字节对都设置为相同的值。要进行班次,您需要班次操作员。

当这些值复制到包含双字节整数数组的数组上时,单个副本会将多个对象的值设置为较大对象的字节值。由于每对字节具有相同的值,因此数组的整数也较小。

  

为什么我需要另一个long数组?

没有short的数组。只有service_id: class: 'path\to\class' calls: - [setRequest, ['@?request=']]

的数组

答案 5 :(得分:7)

您的老师向您展示的代码是一个格式错误的程序,不需要诊断,因为它违反了指针实际上指向他们声称指向的东西的要求(也称为&#34;严格别名&# 34。)

作为一个具体示例,编译器可以分析您的程序,注意A未直接写入并且没有写入short,并证明A从未更改过一旦创建。

根据C ++标准,所有与B混淆的事情都可以证明,因为无法在格式良好的程序中修改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)... }};
  });
}

现在:

int main() {

  auto A = make_filled_array<short, 100, 3>();

  std::cout << "\n";
  print(A.data(),100);
}

在编译时创建填充数组,不涉及循环。

使用godbolt,您可以看到数组的值是在编译时计算的,当我访问第50个元素时,值3被提取出来。

然而,这是过度杀伤(和)。

答案 6 :(得分:3)

我认为他试图通过同时复制多个数组元素来减少循环迭代次数。正如其他用户已在此处提到的那样,这种逻辑会导致未定义的行为。

如果完全是关于减少迭代,那么使用循环展开,我们可以减少迭代次数。但对于这种较小的阵列来说,它不会明显加快。

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);
}