C ++通过指针访问2D数组中的连续值 [英] C++ Accessing Successive Values in a 2D Array by Pointer

查看:54
本文介绍了C ++通过指针访问2D数组中的连续值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我了解如何通过指针访问2D数组中的元素,但是在访问数组行中的第二个元素"并使用它进行比较时遇到了一些麻烦.例如,如果我有数组:

int numbers[3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};

我需要访问元素2、5和8才能实现其他功能.但是当我声明一个指针并将其指向数组中的下一个元素时,就像这样:

int (*p)[3] = &(numbers[1]);

int (*p)[3] = numbers + 1;

它本身指向下一个 元素,即数组中的下一行,因为它是一个2D数组.据我了解,但我想知道是否可以做些什么,使指针指向当前行中的下一个元素",而不是指向下一行?

解决方案

首先,让我们了解C ++中数组的潜在问题.

您的数组int numbers[3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};可以在内存中这样显示,假设sizeof(int)为4(在您的系统上可能为true,也可能不为true,但是C ++并未规定int和4的内存大小.比8更容易绘制:

正如我经常想解释的那样,C ++中的多维数组只是一维数组的特殊情况,只是其元素类型本身就是数组类型.

在您的情况下,numbers是大小为3且元素类型为int[3]的数组.整个事情在内存中是连续的,这意味着所有元素都彼此相邻放置,而它们之间没有任何孔. 行"和列"的概念实际上只是一种错觉,由[]表示法通过在幕后执行指针算术访问单个数组元素提供.

获取指向行"的指针很容易,因为行"的元素彼此相邻.例如,指向第二个行"的指针只是指向"4"元素的指针,因为构成int[3]行"的"4","5"和"6"元素紧挨着彼此:

现在,您想要的是指向列"的指针.如图所示,在如此低的语言水平下,这是完全不可能的.例如,第二个列"由"2","5"和"8"元素组成.但是,显然,没有地方可以拥有指向这三个元素在内存中彼此相邻放置的位置的指针.

因此,只能在这样低的语言级别上非常间接地实现列"的概念,通过操作而不是通过数据类型,首先要遍历行".示例:

// print 2nd column:
int const column_index = 1;
for (int row_index = 0; row_index < 3; ++row_index)
{
    std::cout << numbers[row_index][column_index];
}

尽管如此,这是C ++,它是一种能够抽象出这种低级机制的语言.解决您的问题的一种方法是创建一个自定义数据类型,该数据类型在内部执行间接操作并保持行和列"的错觉.您甚至不需要二维数组.一维就足够了.

这是一个非常简单的示例(没有错误处理,硬编码的值并且没有任何const操作):

#include <iostream>

class Column
{
public:
    int& element(int row_index)
    {
        return array[row_index * width + column_index];
    }

private:
    friend class Matrix;

    Column(int width, int(&array)[9], int column_index) :
        width(width),
        array(array),
        column_index(column_index)
    {
    }

    int width;  
    int(&array)[9];
    int column_index;
};

class Matrix
{
public:
    Matrix() : width(3), array {1, 2, 3, 4, 5, 6, 7, 8, 9 }
    {}

    Column column(int column_index)
    {
        return Column(width, array, column_index);
    }

private:
    int width;
    int array[9];

};

int main()
{
    Matrix m;

    for (int column_index = 0; column_index < 3; ++column_index)
    {
        for (int row_index = 0; row_index < 3; ++row_index)
        {
            std::cout << m.column(column_index).element(row_index) << " ";
        }
        std::cout << "\n";
    }
}

在此示例中,

Column被称为代理类. friend声明和私有构造函数确保只有Matrix可以创建该类的新对象.

结合C ++运算符重载,您甚至可以实现原始的[][]表示法.实际上,您只是为columnelement成员函数赋予了不同的名称:

#include <iostream>

class Column
{
public:
    int& operator[](int row_index)
    {
        return array[row_index * width + column_index];
    }

private:
    friend class Matrix;

    Column(int width, int(&array)[9], int column_index) :
        width(width),
        array(array),
        column_index(column_index)
    {
    }

    int width;  
    int(&array)[9];
    int column_index;
};

class Matrix
{
public:
    Matrix() : width(3), array {1, 2, 3, 4, 5, 6, 7, 8, 9 }
    {}

    Column operator[](int column_index)
    {
        return Column(width, array, column_index);
    }

private:
    int width;
    int array[9];

};

int main()
{
    Matrix m;

    for (int column_index = 0; column_index < 3; ++column_index)
    {
        for (int row_index = 0; row_index < 3; ++row_index)
        {
            std::cout << m[column_index][row_index] << " ";
        }
        std::cout << "\n";
    }
}

您现在可以实现您的原始目标,因为您可以完美地创建一个对象来代表整个列:

Matrix m;

Column second_column = m[1];

for (int row_index = 0; row_index < 3; ++row_index)
{
    std::cout << second_column[row_index] << " ";
}

但是我们还没有到那儿. Column当前有点危险,因为它不能从函数安全地返回.对矩阵数组的内部引用很容易变成一个悬空的数组:

Column get_column() // dangerous, see below!
{
    Matrix m;

    Column second_column = m[1];

    // m is destroyed, but reference to its array
    // member is kept in the returned Column, which
    // results in undefined behaviour:
    return second_column;
}

这类似于通常不希望将迭代器对象保留更长时间的方式.

不过,您可以禁用Column的复制并为其提供私有移动构造函数,

private:
    Column(Column const&) = delete;
    Column& operator=(Column const&) = delete;
    Column(Column&& src) = default;

这强烈地阻止了上面的不良行为,因为实际上必须使get_column返回Column&&std::move返回值才能进行编译.

就我们上面的输出循环而言,它看起来像这样:

Matrix m;

auto&& second_column = m[1];

for (int row_index = 0; row_index < 3; ++row_index)
{
    std::cout << second_column[row_index] << " ";
}

这一切是否值得麻烦,我不知道.也许像// treat Column like an iterator, making sure that it never outlives the Matrix it refers to这样的大号评论在实践中也同样适用.

I understand how to access elements in a 2D array by pointer, but I'm having a bit of trouble accessing the second "element" in an array row and using it to make comparisons. For example, if I had the array:

int numbers[3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};

I would need to access the elements 2, 5, and 8 for another function. But when I declare a pointer and point it to the next element in an array, like so:

int (*p)[3] = &(numbers[1]);

or

int (*p)[3] = numbers + 1;

It points to the next element, per se, being the next row in the array, since it's a 2D array. That I understand, but I was wondering if there was anything that could be done so that the pointer points to the next "element" in the current row, instead of pointing to the very next row?

解决方案

First, let's understand the underlying problem with arrays in C++.

Your array int numbers[3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; can be pictured like this in memory, assuming that sizeof(int) is 4 (which may or may not be true on your system, but C++ does not prescribe the memory size of an int and 4 is easier to draw than 8):

As I often like to explain, a multi-dimensional array in C++ is just a special case of a one-dimensional array, just that its element type itself is an array type.

In your case, numbers is an array of size 3 with element type int[3]. The whole thing is contiguous in memory, which means that all elements are placed next to each other without any holes in between. The concept of "rows" and "columns" is really just an illusion provided by how the [] notation accesses the individual array elements by performing pointer arithmetics under the hood.

Getting a pointer to a "row" is easy, because the elements of the "row" all rest next to each other. For example, a pointer to the second "row" is just one that points to the "4" element, because the "4", "5" and "6" elements which constitute the int[3] "row" are directly next to each other:

Now what you want is a pointer to a "column". As the picture shows, that's completely impossible on such a low language level. For example, the second "column" is made up by the "2", "5" and "8" elements. But there is, obviously, no place you can have a pointer point to where those three elements are placed next to each other in memory.

The concept of a "column" can thus be achieved only very indirectly on such a low language level, via operations and not via data types, by traversing the "rows" first. Example:

// print 2nd column:
int const column_index = 1;
for (int row_index = 0; row_index < 3; ++row_index)
{
    std::cout << numbers[row_index][column_index];
}

Nevertheless, this is C++, a language well capable of abstracting away such low-level mechanics. One solution to your problem would be creating a custom data type which performs the indirection internally and keeps the "rows and columns" illusion. You don't even need a two-dimensional array for this; a one-dimensional one is sufficient.

Here is a rather simple example (without error handling, hard-coded values and no const operations):

#include <iostream>

class Column
{
public:
    int& element(int row_index)
    {
        return array[row_index * width + column_index];
    }

private:
    friend class Matrix;

    Column(int width, int(&array)[9], int column_index) :
        width(width),
        array(array),
        column_index(column_index)
    {
    }

    int width;  
    int(&array)[9];
    int column_index;
};

class Matrix
{
public:
    Matrix() : width(3), array {1, 2, 3, 4, 5, 6, 7, 8, 9 }
    {}

    Column column(int column_index)
    {
        return Column(width, array, column_index);
    }

private:
    int width;
    int array[9];

};

int main()
{
    Matrix m;

    for (int column_index = 0; column_index < 3; ++column_index)
    {
        for (int row_index = 0; row_index < 3; ++row_index)
        {
            std::cout << m.column(column_index).element(row_index) << " ";
        }
        std::cout << "\n";
    }
}

Column in this example is called a proxy class. The friend declaration and private constructor ensure that only Matrix can create new objects of the class.

Combined with C++ operator overloading, you can even achieve the original [][] notation. You are practically just giving different names to the column and element member functions:

#include <iostream>

class Column
{
public:
    int& operator[](int row_index)
    {
        return array[row_index * width + column_index];
    }

private:
    friend class Matrix;

    Column(int width, int(&array)[9], int column_index) :
        width(width),
        array(array),
        column_index(column_index)
    {
    }

    int width;  
    int(&array)[9];
    int column_index;
};

class Matrix
{
public:
    Matrix() : width(3), array {1, 2, 3, 4, 5, 6, 7, 8, 9 }
    {}

    Column operator[](int column_index)
    {
        return Column(width, array, column_index);
    }

private:
    int width;
    int array[9];

};

int main()
{
    Matrix m;

    for (int column_index = 0; column_index < 3; ++column_index)
    {
        for (int row_index = 0; row_index < 3; ++row_index)
        {
            std::cout << m[column_index][row_index] << " ";
        }
        std::cout << "\n";
    }
}

Your original goal is now achieved, because you can perfectly create an object to represent an entire column:

Matrix m;

Column second_column = m[1];

for (int row_index = 0; row_index < 3; ++row_index)
{
    std::cout << second_column[row_index] << " ";
}

But we are not there yet. Column is currently a bit of a dangerous class, because it cannot be safely returned from a function. The internal reference to the matrix' array can easily become a dangling one:

Column get_column() // dangerous, see below!
{
    Matrix m;

    Column second_column = m[1];

    // m is destroyed, but reference to its array
    // member is kept in the returned Column, which
    // results in undefined behaviour:
    return second_column;
}

This is similar to how iterator objects are usually not meant to be kept around for a longer time.

You can disable copying for Column and give it a private move constructor, though:

private:
    Column(Column const&) = delete;
    Column& operator=(Column const&) = delete;
    Column(Column&& src) = default;

This strongly discourages the bad behaviour from above, because one would actually have to make get_column return Column&& and std::move the return value to make it compile.

As far as our output loop from above is concerned, it would look like this:

Matrix m;

auto&& second_column = m[1];

for (int row_index = 0; row_index < 3; ++row_index)
{
    std::cout << second_column[row_index] << " ";
}

Whether all of this is worth the trouble, I don't know. Perhaps a big fat comment like // treat Column like an iterator, making sure that it never outlives the Matrix it refers to would work equally well in practice.

这篇关于C ++通过指针访问2D数组中的连续值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆