我是一名初学者的C++程序员,所以我学习使用数组而不是向量(这似乎是一般做法,然后再转向向量)。
我注意到SO上很多答案建议使用向量而不是数组,使用字符串而不是字符数组。似乎这是在C++中编码的“正确”方式。
尽管如此,什么情况下仍值得使用经典的数组/char*(如果有的话)?
永远不要使用裸数组。
如果出于其他原因,裸数组似乎比向量更好,则可以在C++11编译器中使用 std::tr1::array 或 std::array(或者在boost::array中使用)。它只是执行了我会执行的检查以确保数组的正确性,并且它自动实现了DRY(例如,在循环中使用大小,因此将来对数组声明进行的更改将自动工作)。
数组实现“就是”具有检查和提供大小常量的裸数组,因此很容易将数组代码嵌入到嵌入式代码中,因为该代码并不是真正“太聪明”适用于任何编译器。只要编译器支持模板,我就会将boost头文件复制到我的代码中,以便我可以使用这个而不是裸数组,因为使用裸数组很容易出错。裸数组是邪恶的。它们容易出错。
并且它与STL算法非常配合得好(如果可用)。
现在,有两种情况需要使用裸数组(必须使用):当您处于仅C代码的情况下(不与C代码通信,而是编写仅C部分的代码,例如C库)时。但那是另一种语言。另一个原因是编译器根本不支持模板......
这个问题实际上可以分成两个部分:
我个人喜欢使用std::vector来管理内存,除非需要与不使用STL的代码保持兼容性(例如与纯C代码进行接口)。使用new或malloc分配原始数组的内存(部分原因是很容易忘记需要担心它)使得编写异常安全的代码更加困难。有关原因,请参见任何一篇RAII文章。
在实践中,std::vector被实现为平坦数组。因此,总是可以提取出原始数组并使用C风格的访问模式。我通常从向量下标操作符语法开始。对于某些编译器,在生成调试版本时,向量提供自动边界检查。这很慢(通常会导致紧密循环的10倍减速),但有助于发现某些类型的错误。
如果在特定平台上进行分析表明operator[]是瓶颈,那么我会切换到直接访问原始数组。有趣的是,根据编译器和操作系统的不同,使用STL向量有时比使用原始数组更快。下面是一个简单测试应用程序的结果。它是在32位Release模式下使用/O2优化编译的Visual Studio 2008中运行在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;
}
当兼容性或性能至关重要时,数组/char*非常有用。向量和字符串是更高级别的对象,对于代码可维护性、可读性和总体易用性更好。几乎总是如此。
我建议在编译时就知道大小的情况下使用数组。虽然可以使用向量,但必须记住向量带有与在堆上进行的内存分配相关的开销。如果大小未知,则当然要使用向量。
vector
是错误的。第一个例子,大小为5000万。 我没有那么多堆栈。 第二个例子,我想要一个快速或nothrow swap
操作 - 仅向量具有此功能,而数组不具备(不仅因为std :: swap
不适用于数组,您也无法编写自己的快速数组swap,或者针对没有自己的nothrow交换的类型的数组进行nothrow交换)。 因此,有时我会使用vector
,尽管知道大小。 - Steve Jessop我认为有两个原因:
我正在开发一个需要访问结构化数据的共享库。这些数据在编译时已知,因此使用文件范围常量数组POD(普通旧数据)结构来保存数据。
这会导致编译器和链接器将大部分数据放在只读区域中,有两个好处:
唯一的例外是编译器仍然会生成初始化代码来加载浮点常量,因此任何包含浮点数的结构最终都会在可写区域中。我怀疑这与浮点异常或浮点舍入模式有关,但我不确定如何验证这两个假设。
如果我使用向量和字符串对象,则编译器将生成更多的初始化代码,每当我的共享库被加载时就会执行。常量数据将分配在堆上,因此不能在进程之间共享。
如果我从磁盘中读取数据,则必须处理数据格式的检查,而不是让C++编译器为我完成。我还必须在一个自始至终都将全局数据“烤入”其中的代码库中管理数据的生命周期。