Необходимо поменять местами две строки матрицы. Нашел в интернете несколько примеров в которых используется поэлементный обмен. Мне такой код не очень нравится, поэтому я написал свой вариант обмена строк матрицы с использованием ссылочных переменных и указателей, но вот беда - я сам не до конца понимаю как он работает ( он работает, проверял на произвольных матрицах ).
Объясните, пожалуйста.
(В данном конкретном случае меняю местами строки в зависимости от четности индекса)
template <typename T>
void swapRows(T **matrix, int rowsQuantity) {
for ( int i = 0; i < rowsQuantity - 1; i++) {
if ( i % 2 == 0 ) {
T &temp = *matrix[i];
matrix[i] = matrix[i+1];
matrix[i+1] = &temp;
}
}
}
Практическим путем выяснил что " *matrix[i] " возвращает значение первого элемента i-ой строки. Честно говоря, не понятно почему так. Знаю, что имя массива это указатель на его первый элемент, видимо, это как-то связано, но точную логическую цепочку не получается провести.
Ссылочная переменная " temp " получает адрес этого первого элемента i-ой строки.
Как работает " matrix[i+1] = &temp; " не очень понятно.
Указатель matrix
содержит адрес указателья(является указателем на указатель). matrix[i]
это указатель (matrix + i)
, *matrix[i]
это первый элемент в массиве(строке) matrix + i
.
T &temp = *matrix[i];
означает, что первому элементу i - той
строки придаем имя temp
matrix[i] = matrix[i+1];
теперь matrix[i]
указывает на начало того же массива, что и matrix[i + 1]
matrix[i+1] = &temp;
теперь i + 1
- тый указатель получает значение: адрес первого элемента i
- той строки, равно, указывает на строку i
Практически вы могли бы написать функцию проще:
template <typename T>
void swapRows(T **matrix, int rowsQuantity) {
for ( int i = 0; i < rowsQuantity - 1; i += 2)
std::iter_swap(matrix[i], matrix[i + 1]);
}
Необходимо поменять местами две строки матрицы.
Во-первых, надо определиться, каким образом объявляется матрица.
Самый простой подход - это объявить матрицу в виде двумерного массива. Например,
const size_t N = 3;
int a[N][N] =
{
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
};
В этом случае приведенная вами функция не годится, то есть не будет работать, так как она принимает указатель на указатель в качестве аргумента, а не двумерный массив.
Для вашей функции матрица определяется скорей всего динамически (хотя в частном случае это может быть и локальный массив) как указатель на динамически выделенный одномерный массив, элементы которого в свою очередь являются указателями на динамически выделенные одномерные массивы.
Например,
const size_t N = 3;
int **a = new int *[N];
int value = 1;
for ( size_t i = 0; i < N; i++ )
{
a[i] = new int[N] { value++, value++, value++ };
}
Теперь указатель a
можно передать в вашу функцию в качестве первого аргумента.
Внутри этой функции обмениваются соседние элементы динамически созданного массива a
, которые являются указателями на первые элементы других выделенных динамически массивов, как
int **a = new int *[N];
следующим образом
for ( int i = 0; i < rowsQuantity - 1; i++) {
if ( i % 2 == 0 ) {
T &temp = *matrix[i];
matrix[i] = matrix[i+1];
matrix[i+1] = &temp;
}
}
Здесь выражение matrix[i]
дает значение элемента, то есть значение указателя, в i-ом
элементе массива matrix
. Этот указатель содержит адрес первого элемента i-го
динамически выделенного массива.
Объявление
T &temp = *matrix[i];
объявляет ссылку на этот первый элемент i-го
. Поэтому если взять адрес этого первого элемента, используя ссылку
matrix[i+1] = &temp;
то значение выражения &item
будет равно значению, хранящемся в выражении matrix[i]
.
Эквивалентный код может выглядеть следующим образом
if ( i % 2 == 0 ) {
T *temp = matrix[i];
matrix[i] = matrix[i+1];
matrix[i+1] = temp;
}
Имейте в виду, что есть стандартная функция std::swap
, объявленная в заголовке <utility>
. которая выполняет данную операцию. С помощью этой функции вы могли бы просто написать
if ( i % 2 == 0 ) {
std::swap( matrix[i], matrix[i+1] );
}
Ниже представлена демонстрационная программа, которая использует два подхода к определению матрицы: первый - в виде массива и второй - в виде набора динамических массивов.
#include <iostream>
#include <utility>
template <typename T, size_t N>
void swapRows( T ( &a )[N][N] )
{
for ( size_t i = 0; i + 1 < N; i += 2 )
{
std::swap( a[i], a[i + 1] );
}
}
template <typename T>
void swapRows( T **a, size_t n )
{
for ( size_t i = 0; i + 1 < n; i += 2 )
{
std::swap( a[i], a[i + 1] );
}
}
int main()
{
{
const size_t N = 3;
int **a = new int *[N];
int value = 1;
for ( size_t i = 0; i < N; i++ )
{
a[i] = new int[N] { value++, value++, value++ };
}
for ( size_t i = 0; i < N; i++ )
{
for ( size_t j = 0; j < N; j++ )
{
std::cout << a[i][j] << ' ';
}
std::cout << '\n';
}
std::cout << '\n';
swapRows( a, N );
for ( size_t i = 0; i < N; i++ )
{
for ( size_t j = 0; j < N; j++ )
{
std::cout << a[i][j] << ' ';
}
std::cout << '\n';
}
std::cout << '\n';
}
{
const size_t N = 3;
int a[N][N] =
{
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
};
for ( const auto &row : a )
{
for ( const auto &item : row )
{
std::cout << item << ' ';
}
std::cout << '\n';
}
std::cout << '\n';
swapRows( a );
for ( const auto &row : a )
{
for ( const auto &item : row )
{
std::cout << item << ' ';
}
std::cout << '\n';
}
std::cout << '\n';
}
return 0;
}
Вывод программы на консоль:
1 2 3
4 5 6
7 8 9
4 5 6
1 2 3
7 8 9
1 2 3
4 5 6
7 8 9
4 5 6
1 2 3
7 8 9
В представленном коде происходит следующее:
1)
template <typename T>
void swapRows(T **matrix, int rowsQuantity) {
Эта шаблонная функция первым аргументом принимает указатель на указатель на какой-то тип. Судя по всему, предполагается, что этот тип является элементарным. Например, int
, float
, double
.
Исходя из того, что matrix
- это указатель на указатель, ваша матрица представлена как массив, в котором хранятся указатели на массивы (строки), в которых хранятся сами элементы:
Где matrix
- это указатель, в котором хранится адрес массива ABC
;
2)
for (int i = 0; i < rowsQuantity - 1; i++) {
Происходит обход всей матрицы по строкам. Здесь сразу видны две проблемы:
rowsQuantity
;int
не вполне подходит для адресации элементов массива. Переменная типа size_t
подойдет гораздо лучше.3)
if ( i % 2 == 0 ) {
Действия выполняются при посещении
каждой строки, чей индекс кратен двум (0, 2, 4, и пр.). И действия, соответственно, выполняются попарно над строками 0-1, 2-3, 4-5.Это не совсем рационально, потому что такой подход выполняет в два раза больше инкрементов переменной i
, в два раза больше делений с остатком и в два раза больше сравнений, чем реально необходимо.
Гораздо рациональнее было бы выполнять i += 2
, вообще отбросив деление с остатком и проверку. Но такой подход потребует выполнение проверки целочисленного переполнения переменной i
.
4)
T &temp = *matrix[i];
matrix[i] = matrix[i+1];
matrix[i+1] = &temp;
На первый взгляд, это довольно идиоматическая запись, необходимая для классического обмена местами двух значений с использованием промежуточной переменной:
temp = x;
x = y;
y = temp;
Но не совсем. Я не уверен до конца, что происходит в недрах компилятора, и что по этому поводу говорит Стандарт
.
matrix[i]
- получает значение указателя на строку матрицы (адрес массива со значениями строки). *
- разыменовывает данный адрес, получая значение (значения) всей строки. По всей видимости, значение строки помещается в ссылочную переменную temp
.
Я не специалист по C++
, но мне данный код кажется очень странным. По логике, ссылка является сущностью времени компиляции, иначе говоря - это просто всевдоним для чего-то. То есть, эти три странные строчки можно свести к двум:
x = y;
y = x;
В результате чего и в x
, и в y
будет находится изначальное значение y
. Но, по-видимому, компилятор в принципе понимает, что вы от него хотите. А может быть, это неопределенное поведение. Точно сказать не могу, потому что текущий стандарт C++
занимает почти 2000 страниц.
5)
matrix[i+1] = &temp;
Здесь происходит использование адреса данных, связанных со ссылкой temp
.
PS. Я бы рекомендовал как можно реже и меньше смешивать C++
с его C
подмножеством. Не потому, что C
плохо, а потому что такое смешивание имеет ряд существенных минусов.
Во-первых, низкоуровневая явность C
несовместима с высокоуровневой неявностью C++
. Это гарантированно будет приводить к трудным для понимания ошибкам даже во вполне тривиальном коде. Так же это будет вынуждать вас писать еще больше кода, чем при использовании одного только C
- вот такой парадокс.
Во-вторых, смешивание ссылок с указателями, шаблонов с макросами и исключений с кодами возврата приводит к неподдающимся анализу ситуациям, потому что названные элементы противоречивы в деталях, хотя поверхностно кажутся очень похожими.
matrix
имеет тип указатель на указатель на T
, что можно также трактовать как массив указателей на T
. Аргумент rowQuantity
, по всей видимости, содержит количество элементов этого массива. Немного дополню ващ код:
T &temp = *(matrix[i]);
matrix[i] = matrix[i + 1];
matrix[i + 1] = &temp;
matrix[i]
возвращает итый элемент массива указателей на T
, далее *(итый указатель)
разыменовывает итый указатель и возвращает ссылку на первый элемент итой строки, полученная ссылка получает имя temp
. matrix[i] = matrix[i + 1]
итому элементу присваивается итый плюс один элемент, теперь в них содержатся одинаковые указатели. matrix[i + 1] = &temp
, здесь &temp
возвращает указатель на то на что ссылается temp
, а именно на первый элемент итой строки, полученный адрес помещается в итый плюс один элемент массива.
Вот эквивалентный и более понятный код:
T * temp = matrix[i];
matrix[i] = matrix[i + 1];
matrix[i + 1] = temp;
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Какие существуют виды рекламных бордов и как выбрать подходящий?
Допустим есть черно-белое изображение размера 10x10 пикселей, то его нужно перевести в матрицу 10x10 в которой черному цвету будет соответствовать...
Есть такое задание: в массиве найти минимальное числоЕсли минимальных чисел несколько, то присвоить им среднее арифметическое исходного...