二维向量vector の 邪恶的多维数组 (一)

在写二维向量之前,先看一片文章

Multidimentional arrays are evil


I see a lot of noobs get sucked into the vortex(漩涡) that is multidimentional (hereon MD) arrays. MD arrays are a "feature" of C/C++ which allow you to use multiple indeces(索引,并不是index的复数形式indexes) when looking up things in an array. A typical use is to have a 2D array to represent a grid or a game map with 1 index for X coords and another for Y coords. Another common usage is for a 'Matrix' class or somesuch(诸如此类).

我看到很多新手陷入多维数组(MD=multi-dimentional)不能自拔。多维数组是C/C++的一个特性,他允许我们使用多个索引查找数组元素。典型的,可以用一个二维数组表示网格或者游戏地图(有兴趣的可以尝试搜索“基于网格的路径搜索”)。一个用来表示X轴,另一个用来表示Y轴。另一个常用的就是把数组用作矩阵。

The thing these coders don't know (or don't realize) is that MD arrays sucksomething sucks 英英释义spokennot politeused when you dislike something very much or think something is very bad: 例句If you ask me, the whole thing sucks.).They're syntax poison. They're confusing, hard to work with, and (depending on the implementation) may actually consume more memory and yield worse performance.

程序员不知道(或者没有意识到的是)多维矩阵并不好。它会影响整个语法。因为它们难以理解,很难使用,并且(视实现而定)可能回消耗更多的内存,导致更低的性能。
After seeing more and more posts about MD arrays pop up on these forums, I've decided to sketch out this article to shine some light on MD arrays, and provide a reasonable alternative.
在看到论坛上的出现越来越多关于MD的帖子之后,我决定写这篇文章,为初学者指出一条前行的道路,并提供一个合理的选择。

MD Array Flavor 1: Straight
The most obvious way to make an MD array is just a straight allocation:

MD数组第一道菜:直接(还有一个直的意思是连续空间分配----线性)

创建一个MD数组最明显的方法是 ---- 直接分配:

int evil[5][7];
evil[3][2] = 5;
Seems harmless enough. And it is, for the most part. This is the friendliest flavor of MD arrays.

看起来挺好的。大多数情况来说,的确挺好的。这也是MD数组最友好的一面。
However even with this form, problems creep up. What if you need to pass this array to another function? The way you'd do it is like so:

即使使用这种格式,问题也会显现。如果你把这个数组传递给另一个函数又该怎么做?你可能会这样做:

int func(int p[][7])
{
  p[2][3] = 1;
}

//...
func(evil);
This will pass 'evil' by pointer to the 'func' function. This isn't all that horrible... but one of its limitations is that it will only accept an array of [x][7]. What if you want to call the function with an MD array of a different size... like int evil[4][5];? Bottom line is you can't. You'd have to take the MD array out of the function completely and turn it into a 1D array and pass the width as a seperate parameter:

“EVIL”通过指针传递给了函数func。但是这不是“EVIL”的全貌。。。但是有一个限制就是,func函数之恩能够接受[x][7]类型的数组(二维数组且是7列)。如果你传递给这个函数的MD数组不是 X×7的。。。比如:int evil[4][5]; 编译就无法通过。你不得不把这个二维数组转成一个 一位数组并把它的列数 分别传给函数:

int func2(int p[], int width)
{
  // p[2][3] = 1;         // no good anymore, 1D array,已经是一维的了,该方法不再适用
  p[ (2*width) + 3] = 1;  // the 1D apprach to do the same thing

/*  It's worth noting here that the above (y*width)+x calculation is basically what 
    MD arrays compile to anyway.  The compiler just hides it from you with MD arrays.
     Or at least it's true with Straight Flavor MD arrays.  Other flavors are more sinister.

值得一提的是上面的(y*width)+x 是基于MD数组是如何编译的。编译器让MD数组透明化。
或者,对于MD数组直接存储是对的。其他的就不敢保证了。

 */}//...int evil[4][5];func2(evil[0],5);  // ugly


You might notice I marked that last line as ugly. It's because it is. It is confusing, has unclear syntax, and it is very easy to make a mistake and pass the wrong value as the width.
The other problem with this flavor is that it is completely incompatible with other MD flavors. As you'll soon see.

也许你会注意到我在最后一行标注了一个ugly。因为它本来就ugly。很混乱,语法很不清晰,很容易犯错,会传入错误的width值。另一个问题,正如你将要看到的那样,它和后面将要介绍的MD特征是完全不相符的。

完整代码:

#include <iostream>

using namespace std;

void func(int p[], int width){
    p[(2*width)+3] = 1;
}
int main()
{
    int evil[4][5]={0};
    func(evil[0], 5);

    for(int i=0;i<4;++i){
        for (int j=0; j<5; ++j){
            cout << evil[i][j]<<"\t";
        }
        cout<<endl;
    }
    return 0;
}
输出结果为(当然你要确保你的数组是按照行排序存储的):

0       0       0       0       0
0       0       0       0       0
0       0       0       1       0
0       0       0       0       0

MD Array Flavor 2: Nested New (dynamic)
Once people graduate from straight MD arrays, they'll start wondering how to make the size of their arrays dynamic. This is where the dreaded "nested new" technique comes in:

MD数组第二道菜:嵌套的new(动态)

一旦人们MD阵列的straight有所了解,他们就会开始琢磨着如何让自己的数组的大小动态可变这就是可怕的“嵌套 new”技术的开始

// let's say we want to dynamically make int[Y][X]:
// 我们想动态的创建int[Y][X]
int** superevil = new int*[Y];
for(int i = 0; i < Y; ++i)
  superevil<i> = new int[X];

// now we can do this:现在我们可以这样做了:
superevil[2][3] = 1;

// but cleanup is just as ugly as allocation:
// 但是清理工作一样的ugly。。。。
for(int i = 0; i < Y; ++i)
  delete[] superevil<i>;
delete[] superevil;

Note that this actually makes a pointer to a pointer, and not really a straight 2D array. And while "superevil" and "evil" (from the previous section) are both accessed with [double][brakets], they both function( 有或起作用; 行使职责;) very differently under the hood(在后台,在底层).
需要注意的是:这里实际上使用的是指针的指针,并不是一个真正的直的2D 数组。并且"superevil"和"evil"的访问都是通过[ ][ ] ——两个方括号来访问的 。它们的作用都是大相径庭。
Nested New MD arrays are inefficient. In addition to (除...之外)allocating all the space for the normal 2D array, you're also allocating space for POINTERS. Not only that, but in order to access any element in the array, you have to dereference( 解引用two pointers!

多维数组的嵌套New是低效的。除了要为普通的2D数组分配所有的空间,还要为指针分配空间。不仅如此但为了访问数组中的任意元素你必须解引用两个指针
And really... look at the messy allocation/destruction code that's required.
而且真的...分配/销毁 代码是多么的混乱。
And let's go back to calling functions... let's take our two functions from the previous section:
让我们再回到调用函数...让我们带着前面章节的两个函数:

int func(int p[][7]);
int func2(int p[], int width);

How would you call those functions with 'superevil'?

如何这些函数处理'superevil'?

Guess what. You can't. That's right... you're hosed. Nested New arrays are not really 2D arrays in the same sense that straight arrays are. Straight MD arrays only require 1 dereference, but nested new requires 2 dereferences. In order to pass this to a function, you'd need to make another one:
你猜怎么着。你办不到。没错...你束手无策。嵌套New生成的数组,并不是一个straight数组。从这点来说,它也不是一个真正的2D数组直的MD阵列只需要一次解引用嵌套New需要两次解引用为了能把'superevil'传递给一个函数,你需要创建另一个新的函数:

int func3(int** p,int width);

But now you might notice that func3 doesn't work with straight arrays. They're just totally incompatible.

但是,现在也许你会注意到 当直数组传递给func3时,并不能工作。他们完全不兼容。
The one saving grace to the nested new approach is that it doesn't require the entire MD array to be contiguous, which means you have a better chance of getting the memory you need if memory is badly fragmented. But this mild benefit is seldom applicable, and is overshadowed by its faults.

嵌套New的方法存在的原因是:它不需要整个MD数组是连续的。这就意味着,如果内存碎片已经很多很多了,使用嵌套new可以更好的获得内存,用以存储数组。但这种仅有的温存并不会让它经常出现在人们的视野里,而且它的出现总是伴随着疾风骤雨。

完整代码(以下代码,一部分来自数据结构与算法分析 C++语言描述 Larry Nyhoff, 经本人修改得):

#include <iostream>
using namespace std;

void func(int ** p, int width){
    p[3][3] = -1;
}
int main()
{
    const int X= 4,
            Y = 5;
    int *arr = new int [5];
    for(int i=0; i< 5; ++i){
        arr[i] = 0;
    }
    arr[3] = 1;
    for(int i=0; i< 5; ++i){
        cout << arr[i] <<"\t" ;
    }
    cout <<"\n****************\n";
    delete[] arr;
    // 4 × 5
    int ** supervEvial = new int *[X];
    for(int i=0; i<X; ++i){
        supervEvial[i] = new int [Y];
    }
    for(int i=0; i< X; ++i){
        for(int j=0 ; j< Y; ++j){
            supervEvial[i][j] = 0;
        }
    }
    supervEvial[2][3] = 1;
    func(supervEvial,Y);
    for(int i=0; i< X; ++i){
        for(int j=0 ; j< Y; ++j){
            cout<<supervEvial[i][j] <<"\t";
        }
        cout<<endl;
    }

    for(int i = 0; i<X; ++i){
        delete[] supervEvial[i];
    }
    delete[] supervEvial;
}

输出:

0       0       0       1       0
****************
0       0       0       0       0
0       0       0       0       0
0       0       0       1       0
0       0       0       -1      0


MD Array Flavor 3: Vectors of Vectors

MD数组第三道菜: 向量的向量
The more STL savvy coder will opt for a vector-of-a-vector approach to MD arrays, rather than Nested New:

对STL有更深入了解的程序员会选择使用 向量的向量来 实现MD数组,而不是前面介绍的嵌套New:

// to make a [5][6] array:
vector< vector<int> > stillevil( 5, vector<int>(6) );

stillevil[2][3] = 1

Not a lot to say about this one. Like Nested New it's incompatible with other MD array flavors. If you need to pass this array to a function, none of the above functions will work.
没有什么可以多说的。想嵌套New一样,他与其他的MD数组特性一样,互不兼容。如果你想把数组传递给函数,上面的函数没有一个可以正常运作。
Also, like Nested New, it's inefficent in that is requires additional memory and has double indirection.
并且,像嵌套New一样,当需要更多地内存时,它也是低效的。而且还有双重间接引用。
On the plus side, you don't need cleanup code, and the allocation code is not as ugly (but still pretty ugly!)

从好的一面来说,你并不需要清理,分配的代码也不像嵌套New那样ugly(但是仍然很ugly!)

完整代码:

#include <iostream>
#include <vector>
#include <iomanip>
using namespace std;

void print(const vector< vector<double> > &table){
     for(unsigned int i=0; i< table.size();++i){
            cout<< "ROW size : " << table[i].size()<<"|";
        for(unsigned int j=0; j< table[i].size(); ++j)
            cout<<setw(5)<< table[i][j];
            cout<<endl;
     }
}
int main()
{
    // 二维向量  3行5列
    const int ROW =3, COLUMN = 5;
    vector< vector<double>  > table(ROW,vector<double>(COLUMN,0.0));
    // 分开看:
    // vector<double> (COLUMN, 0.0)
    // 这是一个匿名的向量,不是匿名的应该是 vector<double> col(COLUMN, 0,0);
    // 这样就定义了一个只有5个元素的向量

    // 紧接,再次定义一个向量,这个向量的每一个元素都是一个向量。
    // 这样就构成了一个二维向量
    // vector < 元素类型为向量 > 新的向量名( 该向量具有ROW个元素,  每个元素的值为vector<double>(COLUMN,0.0));

    // 输出向量table的行数
    cout << "table 有"<< table.size() <<"行\n";
    // 输出每一行向量的列数
    for( unsigned int i=0; i< table.size();++i)
        cout <<i<<"行,有 "<<table[i].size()<<"个元素"<<endl;
    // 输出所有元素
    print(table);

    cout<<"Add new ROW:\n";
    // 新增一行5个元素,初始值为0(第四行)
    table.push_back(vector<double>(COLUMN));
    // 新增一行5个元素.该行初始值全1.1(第五行)
    table.push_back(vector<double>(COLUMN,1.1));
    // 第六航只有一个元素, 没有任何值(第六行)
    table.push_back(vector<double>());
    // 添加一行,只有一个元素,初始值默认为0(第七行)
    table.push_back(vector<double>(1));
    // 添加一行,只有一个元素,初始值默认为3.3(第八行)
    table.push_back(vector<double>(1,3.3));
    print(table);
    return 0;
}
程序输出:
table 有3行
0行,有 5个元素
1行,有 5个元素
2行,有 5个元素
ROW size : 5|    0    0    0    0    0
ROW size : 5|    0    0    0    0    0
ROW size : 5|    0    0    0    0    0
Add new ROW:
ROW size : 5|    0    0    0    0    0
ROW size : 5|    0    0    0    0    0
ROW size : 5|    0    0    0    0    0
ROW size : 5|    0    0    0    0    0
ROW size : 5|  1.1  1.1  1.1  1.1  1.1
ROW size : 0|
ROW size : 1|    0
ROW size : 1|  3.3























你可能感兴趣的:(数据结构,C++,多维数组,向量)