12

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

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

话虽如此,什么时候仍然值得使用经典的数组/字符*(如果有的话)?

4

8 回答 8

17

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

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

于 2009-02-27T12:23:27.933 回答
16

绝不。

如果原始数组似乎比向量更好的解决方案(出于此处所述的其他原因),那么我在 C++11 编译器(或boost::array )中使用std::tr1::array或 std::array 。它只是简单地检查我无论如何都会做的检查,并且大小值的使用使 DRY 自动实现(例如,我在循环中使用大小,以便将来对数组声明的更改将自动起作用)。

无论如何,数组实现“是”一个带有检查并提供大小常量的原始数组,因此也很容易在嵌入式代码中获取数组代码,因为该代码对于任何编译器来说都不是真的“太聪明” 。至于编译器支持模板,我会在我的代码中复制 boost 标头,以允许我使用这个而不是原始数组。因为使用原始数组显然太容易出错了。原始数组是邪恶的。它们容易出错。

它与 STL 算法(如果可用)配合得很好。

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

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

于 2009-02-27T14:54:51.657 回答
5

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

  1. 我应该如何管理平面数组数据的内存?
  2. 我应该如何访问平面数组的元素?

我个人更喜欢使用 std::vector 来管理内存,除非我需要保持与不使用 STL 的代码的兼容性(即与直接 C 代码交互时)。使用通过 new 或 malloc 分配的原始数组编写异常安全代码要困难得多(部分原因是很容易忘记您需要担心它)。有关原因,请参阅任何有关RAII的文章。

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

如果在特定平台上的分析表明 operator[] 是一个瓶颈,那么我切换到直接访问原始数组。有趣的是,根据编译器和操作系统,有时使用 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;
}
于 2009-02-27T14:58:52.380 回答
4

当兼容性或性能非常重要时,Array/char* 很有用。向量和字符串是更高级别的对象,在代码可维护性、可读性和整体易用性方面表现更好。几乎总是如此。

于 2009-02-27T12:31:19.053 回答
3

只要您知道编译时本身的大小,我建议您使用数组。尽管可以使用向量,但我们必须记住,向量的开销与在堆上完成的内存分配有关。如果大小未知,那么当然使用向量。

于 2009-02-27T12:27:05.990 回答
1

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

于 2009-02-27T12:32:48.067 回答
1

我看到两个原因:

  1. 兼容性(没有 STL 的旧代码)。
  2. 速度。(我比较了使用vector/binary_search & array/handwritten binary search的速度。对于最后一个获得了丑陋的代码(重新分配内存),但它比第一个快1.2-1.5倍,我使用MS VC++ 8 )
于 2009-02-27T12:38:33.543 回答
1

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

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

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

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

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

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

于 2009-02-27T16:58:31.890 回答