你什么时候使用数组而不是矢量/字符串?

时间:2009-02-27 12:17:24

标签: c++ arrays string vector

我是初学者C ++程序员,所以我学会了使用数组而不是向量(这似乎是做事情的一般方法,然后转向向量)。

我注意到很多关于SO的答案建议使用向量而非数组,以及字符串在char数组上。看来这是用C ++编写代码的“正确”方式。

所有人都说,什么时候仍然值得使用经典数组/ char *(如果有的话)?

8 个答案:

答案 0 :(得分:17)

编写应该在其他项目中使用的代码时,特别是如果您针对可能不存在STL的特殊平台(嵌入式,游戏控制台等)。

具有特殊要求的旧项目或项目可能不希望在STL库上引入依赖项。依赖于数组,char *或任何与任何东西兼容的接口,因为它是语言的一部分。但是,并不保证STL存在于所有构建环境中。

答案 1 :(得分:15)

从不。

如果原始数组看起来比矢量更好(因为其他原因在这里说),那么我在C ++ 11编译器中使用 std :: tr1 :: array 或std :: array(或boost::array)。 它只是进行我将要做的检查以确保并且大小值使用使DRY自动实现(例如,我在循环中使用大小,以便将来更改数组声明将自动工作)。

这是数组实现“是”一个带有检查和提供的大小常量的原始数组,因此很容易在嵌入式代码中获取数组代码,因为the code is not really "too smart"适用于任何编译器。至于编译器支持模板,我会在我的代码中复制boost头,以允许我使用这个而不是原始数组。因为使用原始数组显然很容易出错。 Raw arrays are evil.他们容易出错。

它与STL算法(如果可用)一起工作得非常好。

现在,有两种情况你需要使用原始数组(义务):当你使用纯C代码时(不与C代码通信,而是用C语言编写部分)代码,如C库)。但那时它是另一种语言

另一个原因是编译器根本不支持模板...

答案 2 :(得分:6)

这个问题实际上可以分为两部分:

  1. 如何管理平面阵列数据的内存?
  2. 我应该如何访问平面阵列的元素?
  3. 我个人更喜欢使用std :: vector来管理内存,除非我需要保持与不使用STL的代码的兼容性(即与直接C代码接口时)。使用new或malloc分配的原始数组制作异常安全代码要困难得多(部分原因是因为它很容易忘记你需要担心它)。有关原因,请参阅RAII上的任何文章。

    实际上, std :: vector实现为平面数组。因此,总是可以拉出原始数组并使用C风格的访问模式。我通常从向量下标运算符语法开始。对于某些编译器,在生成调试版本时,向量提供自动边界检查。这很慢(对于紧密循环通常是10倍减速),但有助于找到某些类型的错误。

    如果在特定平台上进行分析表明运算符[]是瓶颈,那么我切换到直接访问原始数组。有趣的是,根据编译器和操作系统, 使用STL向量有时比原始数组 更快。

    以下是一个简单测试应用程序的一些结果。它是使用Visual Studio 2008在32位版本模式下使用/ O2优化编译的,并在Vista x64上运行。使用64位测试应用程序可以获得类似的结果。

    Binary search...
               fill vector (for reference) :  0.27 s
                       array with ptr math :  0.38 s <-- C-style pointers lose
                      array with int index :  0.23 s <-- [] on raw array wins
                array with ptrdiff_t index :  0.24 s
                     vector with int index :  0.30 s  <-- small penalty for vector abstraction
               vector with ptrdiff_t index :  0.30 s
    
    Counting memory (de)allocation...
                    memset (for reference) :  2.85 s
          fill malloc-ed raw array with [] :  2.66 s
         fill malloc-ed raw array with ptr :  2.81 s
             fill new-ed raw array with [] :  2.64 s
            fill new-ed raw array with ptr :  2.65 s
                      fill vector as array :  3.06 s  \ something's slower 
                               fill vector :  3.05 s  / with vector!
    
    NOT counting memory (de)allocation...
                    memset (for reference) :  2.57 s
          fill malloc-ed raw array with [] :  2.86 s
         fill malloc-ed raw array with ptr :  2.60 s
             fill new-ed raw array with [] :  2.63 s
            fill new-ed raw array with ptr :  2.78 s
                      fill vector as array :  2.49 s \ after discounting the  
                               fill vector :  2.54 s / (de)allocation vector is faster!
    

    代码:

    #define WINDOWS_LEAN_AND_MEAN
    #include <windows.h>
    #include <string>
    #include <vector>
    #include <stdio.h>
    
    using namespace std;
    
    __int64 freq; // initialized in main
    int const N = 1024*1024*1024/sizeof(int)/2; // 1/2 GB of data
    int const nIter = 10;
    
    class Timer {
    public:
      Timer(char *name) : name(name) {
        QueryPerformanceCounter((LARGE_INTEGER*)&start);
      }
      ~Timer() {
        __int64 stop;
        QueryPerformanceCounter((LARGE_INTEGER*)&stop);
        printf("  %36s : % 4.2f s\n", name.c_str(), (stop - start)/double(freq));
      }
    private:
      string const name;
      __int64 start;
    };
    
    
    template <typename Container, typename Index>
    int binarySearch_indexed(Container sortedArray, Index first, Index last, int key) {
      while (first <= last) {
        Index mid = (first + last) / 2; // NOT safe if (first+last) is too big!
        if (key > sortedArray[mid])      first = mid + 1;
        else if (key < sortedArray[mid])  last = mid - 1; 
        else return mid;  
      }
      return 0; // Use "(Index)-1" in real code
    }
    
    int Dummy = -1;
    int const *binarySearch_ptr(int const *first, int const *last, int key) {
      while (first <= last) {
        int const *mid = (int const *)(((unsigned __int64)first + (unsigned __int64)last) / 2);  
        if (key > *mid)      first = mid + 1;
        else if (key < *mid)  last = mid - 1; 
        else return mid;  
      }
      return &Dummy; // no NULL checks: don't do this for real
    }
    
    void timeFillWithAlloc() {
      printf("Counting memory (de)allocation...\n");
      { 
        Timer tt("memset (for reference)");
        int *data = (int*)malloc(N*sizeof(int));
        for (int it=0; it<nIter; it++) memset(data, 0, N*sizeof(int));
        free(data);
      }
      { 
        Timer tt("fill malloc-ed raw array with []");
        int *data = (int*)malloc(N*sizeof(int));
        for (int it=0; it<nIter; it++) for (size_t i=0; i<N; i++) data[i] = (int)i;
        free(data);
      }
      { 
        Timer tt("fill malloc-ed raw array with ptr");
        int *data = (int*)malloc(N*sizeof(int));
        for (int it=0; it<nIter; it++) {
        int *d = data;
        for (size_t i=0; i<N; i++) *d++ = (int)i;
        }
        free(data);
      }
      { 
        Timer tt("fill new-ed raw array with []");
        int *data = new int[N];
        for (int it=0; it<nIter; it++) for (size_t i=0; i<N; i++) data[i] = (int)i;
        delete [] data;
      }
      { 
        Timer tt("fill new-ed raw array with ptr");
        int *data = new int[N];
        for (int it=0; it<nIter; it++) {
        int *d = data;
        for (size_t i=0; i<N; i++) *d++ = (int)i;
        }
        delete [] data;
      }
      { 
        Timer tt("fill vector as array");
        vector<int> data(N); 
        for (int it=0; it<nIter; it++) {
          int *d = &data[0]; 
        for (size_t i=0; i<N; i++) *d++ = (int)i;
        }
      }
      { 
        Timer tt("fill vector");
        vector<int> data(N); 
        for (int it=0; it<nIter; it++) for (size_t i=0; i<N; i++) data[i] = (int)i;
      }
      printf("\n");
    }
    
    void timeFillNoAlloc() {
      printf("NOT counting memory (de)allocation...\n");
    
      { 
        int *data = (int*)malloc(N*sizeof(int));
        {
          Timer tt("memset (for reference)");
          for (int it=0; it<nIter; it++) memset(data, 0, N*sizeof(int));
        }
        free(data);
      }
      { 
        int *data = (int*)malloc(N*sizeof(int));
        {
          Timer tt("fill malloc-ed raw array with []");
          for (int it=0; it<nIter; it++) for (size_t i=0; i<N; i++) data[i] = (int)i;
        }
        free(data);
      }
      { 
        int *data = (int*)malloc(N*sizeof(int));
        {
          Timer tt("fill malloc-ed raw array with ptr");
          for (int it=0; it<nIter; it++) {
            int *d = data;
            for (size_t i=0; i<N; i++) *d++ = (int)i;
          }
        }
        free(data);
      }
      { 
        int *data = new int[N];
        {
          Timer tt("fill new-ed raw array with []");
          for (int it=0; it<nIter; it++) for (size_t i=0; i<N; i++) data[i] = (int)i;
        }
        delete [] data;
      }
      { 
        int *data = new int[N];
        {
          Timer tt("fill new-ed raw array with ptr");
          for (int it=0; it<nIter; it++) {
            int *d = data;
            for (size_t i=0; i<N; i++) *d++ = (int)i;
          }
        }
        delete [] data;
      }
      { 
        vector<int> data(N); 
        {
          Timer tt("fill vector as array");
          for (int it=0; it<nIter; it++) {
            int *d = &data[0]; 
            for (size_t i=0; i<N; i++) *d++ = (int)i;
          }
        }
      }
      { 
        vector<int> data(N); 
        {
          Timer tt("fill vector");
          for (int it=0; it<nIter; it++) for (size_t i=0; i<N; i++) data[i] = (int)i;
        }
      }
      printf("\n");
    }
    
    void timeBinarySearch() {
      printf("Binary search...\n");
      vector<int> data(N); 
      {
        Timer tt("fill vector (for reference)");
        for (size_t i=0; i<N; i++) data[i] = (int)i;
      }
    
      {
        Timer tt("array with ptr math");
        int sum = 0;
        for (int i=-1000000; i<1000000; i++) {
          sum += *binarySearch_ptr(&data[0], &data[0]+data.size(), i);
        }
      }
      {
        Timer tt("array with int index");
        int sum = 0;
        for (int i=-1000000; i<1000000; i++) {
          sum += data[binarySearch_indexed<int const *, int>(
            &data[0], 0, (int)data.size(), -1)];
        }
      }
      {
        Timer tt("array with ptrdiff_t index");
        int sum = 0;
        for (int i=-1000000; i<1000000; i++) {
          sum += data[binarySearch_indexed<int const *, ptrdiff_t>(
            &data[0], 0, (ptrdiff_t)data.size(), -1)];
        }
      }
      {
        Timer tt("vector with int index");
        int sum = 0;
        for (int i=-1000000; i<1000000; i++) {
          sum += data[binarySearch_indexed<vector<int> const &, int>(
            data, 0, (int)data.size(), -1)];
        }
      }
      {
        Timer tt("vector with ptrdiff_t index");
        int sum = 0;
        for (int i=-1000000; i<1000000; i++) {
          sum += data[binarySearch_indexed<vector<int> const &, ptrdiff_t>(
            data, 0, (ptrdiff_t)data.size(), -1)];
        }
      }
    
      printf("\n");
    }
    
    int main(int argc, char **argv)
    {
      QueryPerformanceFrequency((LARGE_INTEGER*)&freq);
    
      timeBinarySearch();
      timeFillWithAlloc();
      timeFillNoAlloc();
    
      return 0;
    }
    

答案 3 :(得分:4)

当兼容性性能具有非常高的优先级时,

Array / char *非常有用。向量和字符串是更高级别的对象,当代码可维护性,可读性和总体容易度很重要时,它们会更好。几乎总是,那就是。

答案 4 :(得分:3)

我建议在编译时自己知道大小时使用数组。虽然可以使用向量,但我们必须记住向量带有与堆上完成的内存分配相关的开销。如果大小未知,那么当然使用矢量。

答案 5 :(得分:1)

我能想到的唯一原因就是速度。您可以对数组/指针类型进行比对应对象更好的优化。但是如果我绝对知道我的数据结构需要保存的数据量,我甚至会使用STL。在优化步骤中从STL更改为基本类型比使用难以阅读的代码启动项目更好。

答案 6 :(得分:1)

我认为有两个原因:

  1. 兼容性(没有STL的旧代码)。
  2. 速度。 (我比较了使用vector / binary_search和数组/手写二进制搜索的速度。最后一个获得了丑陋的代码(带有内存的重新分配),但它比第一个快了大约1.2-1.5倍,我使用了MS VC ++ 8)

答案 7 :(得分:1)

我在一个需要访问结构化数据的共享库上工作。这些数据在编译时是已知的,因此它使用文件范围的POD(普通旧数据)结构常量数组来保存数据。

这会导致编译器和链接器将大部分数据放入只读部分,这有两个好处:

  • 它可以从磁盘映射到内存目录,而无需运行任何特殊的初始化代码。
  • 它可以在进程之间共享。

唯一的例外是编译器仍会生成初始化代码以加载浮点常量,因此任何包含浮点数的结构都会在可写部分中结束。我怀疑这与浮动异常或浮点舍入模式有关,但我不确定如何验证这两种假设。

如果我为此使用了向量和字符串对象,那么编译器将生成更多的初始化代码,只要加载我的共享库就会执行该代码。常量数据将在堆上分配,因此它不能在进程之间共享。

如果我从磁盘上的文件读取数据,我将不得不处理检查数据的格式,而不是让C ++编译器为我做。我还必须在代码库中管理数据的生命周期,该代码库从一开始就将这个全局数据“融入”它。