Как изменить массив через функцию c

Массивы в параметрах функции в языке программирования C++, константные массивы, замена массива указателями, передача многомерных массивов

Массивы в параметрах функции

Последнее обновление: 01.10.2017

Если функция принимает в качестве параметра массив, то фактически в эту функцию передается указатель на первый элемент массива.
То есть как и в случае с указателями нам доступен адрес, по которому мы можем менять значения. Поэтому следующие объявления функции
будут по сути равноценны:

void print(int numbers[]);
void print(int *numbers);

Передадим в функцию массив:

#include <iostream>

void print(int[]);

int main()
{
    int nums[] = {1, 2, 3, 4, 5};
	print(nums);
    return 0;
}

void print(int numbers[])
{
	std::cout << "First number: " <<  numbers[0] << std::endl;
}

В данном случае функция print выводит на консоль первый элемент массива.

Теперь определим параметр как указатель:

#include <iostream>

void print(int*);

int main()
{
    int nums[] = {1, 2, 3, 4, 5};
	print(nums);
    return 0;
}

void print(int *numbers)
{
	std::cout << "First number: " <<  *numbers << std::endl;
}

Здесь также в функцию передается массив, однако параметр представляет указатель на первый элемент массива.

Ограничения

Поскольку параметр, определенный как массив, рассматривается именно как указатель на первый элемент, то мы не сможем корректно получить длину массива, например, следующим образом:

void print(int numbers[])
{
	int size = sizeof(numbers) / sizeof(numbers[0]);
	std::cout << size << std::endl;
}

И также мы не сможем использовать цикл for для перебора этого массива:

void print(int numbers[])
{
	for (int n : numbers)
		std::cout << n << std::endl;
}

Передача маркера конца массива

Чтобы должным образом определять конец массив, перебирать элементы массива, необходимо использовать специальный маркер, который бы сигнализировал об
окончании массива. Для этого могут использоваться разные подходы.

Первый подход заключается в том, чтобы один из элементов массива сам сигнализировал о его окончании. В частности, массив символов может представлять строку — набор символов,
который завершается нулевым символом ». Фактически нулевой символ служит признком окончания символьного массива:

#include <iostream>

void print(char[]);

int main()
{
	char chars[] = "Hello";

	print(chars);
	return 0;
}

void print(char chars[])
{
	for (int i = 0; chars[i] != ''; i++)
	{
		std::cout << chars[i] << "t";
	}
}

Второй подход заключается в передаче в функцию размера массива:

#include <iostream>

void print(int[], int);

int main()
{
    int nums[] = {1, 2, 3, 4, 5};
	int n = sizeof(nums)/sizeof(nums[0]);
	print(nums, n);
    return 0;
}

void print(int numbers[], int n)
{
	for(int i=0; i < n; i++)
    {
        std::cout << numbers[i] << "t";
	}
}

Третий подход заключается в передаче указателя на конец массива. Можно вручную вычислять вычислять указатель на конец массива.
А можно использовать встроенные библиотечные функции std::begin() и std::end():

int nums[] = { 1, 2, 3, 4, 5 };
int *begin = std::begin(nums);		// указатель на начало массива
int *end = std::end(nums);		// указатель на конец массива

Причем end возвращает указатель не на последний элемент, а адрес за последним элементом в массиве.

Применим данные функции:

#include <iostream>

void print(int*, int*);

int main()
{
	int nums[] = { 1, 2, 3, 4, 5 };
	int *begin = std::begin(nums);
	int *end = std::end(nums);

	print(begin, end);
	return 0;
}

void print(int *begin, int *end)
{
	for (int *ptr  = begin; ptr != end; ptr++)
	{
		std::cout << *ptr << "t";
	}
}

Константные массивы

Поскольку при передаче массива передается фактически указатель на первый элемент, то используя этот указатель, мы можем изменить
элемены массива. Если нет необходимости в изменении массива, то лучше параметр-массив определять как константный:

#include <iostream>

void print(const int*, const int*);
void twice(int*, int*);

int main()
{
	int nums1[] = { 1, 2, 3, 4, 5 };
	int *begin = std::begin(nums1);
	int *end = std::end(nums1);
	print(begin, end);
	std::cout << std::endl;

	int nums2[] = { 1, 2, 3, 4, 5 }; 
	begin = std::begin(nums2);
	end = std::end(nums2);
	twice(begin, end);
	for (int *ptr = begin; ptr != end; ptr++)
	{
		std::cout << *ptr << "t";
	}
	std::cout << std::endl;
	return 0;
}

void print(const int *begin, const int *end)
{
	for (const int *ptr  = begin; ptr != end; ptr++)
	{
		std::cout << *ptr << "t";
	}
}
void twice(int *begin, int *end)
{
	for (int *ptr = begin; ptr != end; ptr++)
	{
		*ptr = *ptr * 2;
	}
}

В данном случае функция print просто выводит значения из массива, поэтому параметры этой функции помечаются как константные.

Функция twice изменяет элементы массива — увеличивает их в два раза, поэтому в этой функции параметры являются неконстантными.
Причем поле выполнения функции twice массив nums3 будет изменен.

Консольный вывод программы:

Передача многомерного массива

Многомерный массив также передается как указатель наего первый элемент. В то же время поскольку элементами многомерного массива являются
другие массивы, то указатель на первый элемент многомерного массива фактически будет представлять указатель на массив.

При определении параметра как указателя на массив размер второй размерности (а также всех последующих размерностей) должен быть определен,
так как данный размер является частью типа элемента. Пример объявления:

void print(int (*numbers)[3])

Здесь предполагается, что передаваемый массив будет двухмерным, и все его подмассивы будут иметь по 3 элемента.
Стоит обратить внимание на скобки вокруг имени параметра, которые и позволяют определить параметр как указатель на массив. И от
этой ситуации стоит отличать следующую:

void print(int *numbers[3])

В данном случае параметр определен как массив указателей, а не как указатель на массив.

Рассмотрим применение указателя на массив в качестве параметра:

#include <iostream>

void print(int(*)[3], int);
int main()
{
	int table[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };
	// количество строк или подмассивов
	int rowsCount = sizeof(table) / sizeof(table[0]);

	print(table, rowsCount);
	return 0;
}

void print(int (*numbers)[3], int rowsCount)
{
	// количество столбцов или элементов в каждом подмассиве
	int columnsCount = sizeof(*numbers)/ sizeof(*numbers[0]);
	for(int i =0; i < rowsCount; i++)
	{
		for (int j = 0; j < columnsCount; j++)
		{
			std::cout << numbers[i][j] << "t";
		}
		std::cout << std::endl;
	}
}

В функции main определяется двухмерных массив — он состоит из трех подмассивов. Каждый подмассив имеет по три элемента.

В функцию print вместе с массивом передается и число строк — по сути число подмассивов. В самой функции print получаем количество элементов в
каждом подмассиве и с помощью двух циклов перебираем все элементы. С помощью выражения number[0] можно обратиться к первому подмассиву
в двухмерном массиве, а с помощью выражения numbers[0][0] — к первому элементу первого подмассива. И таким образом, манипулируя индексами
можно перебрать весь двухмерный массив.

В итоге мы получим следующий консольный вывод:

Также мы могли бы определить параметр функци print непосредственно как двухмерный массив, но в этом случае опять же надо было бы указать явным образом вторую размерность:

void print(int numbers[][3], int rowsCount)
{
	// количество столбцов или элементов в каждом подмассиве
	int columnsCount = sizeof(numbers[0])/ sizeof(numbers[0][0]);
	for(int i =0; i < rowsCount; i++)
	{
		for (int j = 0; j < columnsCount; j++)
		{
			std::cout << numbers[i][j] << "t";
		}
		std::cout << std::endl;
	}
}

Consider your first program.

#include <stdio.h>

void change_byte(int *byte);

int main()
{
    int byte = 0;
    change_byte(&byte);
    printf("Byte : %d rn", byte);
}

void change_byte(int *byte)
{
    *byte= 5;
}

In this program the object byte is passed to the function change_byte by reference through a pointer to it

change_byte(&byte);

In C passing by reference means passing an object indirectly through a pointer tp it.

So dereferencing the pointer byte declared as a function parameter

void change_byte(int *byte);

you get a direct access to the pointed object byte of the type int defined in main.

Now let’s consider your second program

#include <stdio.h>
#define SIZE_ARRAY 10

void change_array(int *array, int size);

int main()
{
    int array[SIZE_ARRAY] = {0};
    change_array(array, SIZE_ARRAY);
    
    printf("Array : ");
    for(int i = 0 ; i < SIZE_ARRAY ; i++)
    {
        printf("%d ", array[i]);
    }
}

void change_array(int *array, int size)
{
    for(int i = 0 ; i < size ; i++)
    {
        array[i] = 5;
    }
}

In main you declared an integer array

int array[SIZE_ARRAY] = {0};

Array designators used in expressions with rare exceptions are converted to pointers to their first elements.

Thus this call

change_array(array, SIZE_ARRAY);

is equivalent to

change_array( &array[0], SIZE_ARRAY);

So dereferencing the pointer within the function you can change the first element of the array defined in main.

But array elements are stored in a continuous extent of memory. So using the pointer arithmetic and having a pointer to the first element of an array you can access all elements of the array.

In fact all elements of the array array are passed to the function change_array by reference through a pointer to the first element of the array.

For example the for loop within the function you could rewrite like

    for(int i = 0 ; i < size ; i++)
    {
        *( array + i ) = 5;
    }

Now let’s consider your third program.

#include <stdio.h>
#define SIZE_ARRAY 10

int global_array[10] = {5, 5, 5, 5, 5, 5, 5, 5, 5, 5};

void change_array(int *array, int size);

int main()
{
    int array[SIZE_ARRAY] = {0};
    change_array(array, SIZE_ARRAY);
    
    printf("Array : ");
    for(int i = 0 ; i < SIZE_ARRAY ; i++)
    {
        printf("%d ", array[i]);
    }
}

void change_array(int *array, int size)
{
    array = global_array;
}

As it was pointed out already the array array passed to the function change_array is converted to a pointer to its first element.

You may imagine the function call and its definition the following way (I will rename the first function parameter that to avoid name ambiguity).

change_array(array, SIZE_ARRAY);

//...

void change_array( /* int *parm_array, int size */)
{
    int * parm_array = array;
    int size = SIZE_ARRAY;

    parm_array = global_array;
}

That is function parameters are its local variables. The parameter parm_array is alive until the function stops its execution.

Thus this statement

    parm_array = global_array;

assign the pointer to the first element of the global array global_array to the local variable parm_array of the function. This assignment does not touch in any way the array array defined in main. It only changes the local variable parm_array declared in the function change_array.

To achieve the expected by you result you could define in main a pointer that is initialized by the array array. And in the function change_array you could reassigned the pointer with the array global_array passing it to the function by reference the same way as you passed the object byte in your first program.

Here is a demonstrative program.

#include <stdio.h>
#define SIZE_ARRAY 10

int global_array[10] = {5, 5, 5, 5, 5, 5, 5, 5, 5, 5};

void change_array( int **array_ptr );

int main()
{
    int array[SIZE_ARRAY] = {0};
    int *array_ptr = array;
    
    change_array( &array_ptr );
    
    printf("Array : ");
    for(int i = 0 ; i < SIZE_ARRAY ; i++)
    {
        printf("%d ", array_ptr[i]);
    }
}

void change_array( int **array_ptr )
{
    *array_ptr = global_array;
}

The program output is’

Array : 5 5 5 5 5 5 5 5 5 5 

Consider your first program.

#include <stdio.h>

void change_byte(int *byte);

int main()
{
    int byte = 0;
    change_byte(&byte);
    printf("Byte : %d rn", byte);
}

void change_byte(int *byte)
{
    *byte= 5;
}

In this program the object byte is passed to the function change_byte by reference through a pointer to it

change_byte(&byte);

In C passing by reference means passing an object indirectly through a pointer tp it.

So dereferencing the pointer byte declared as a function parameter

void change_byte(int *byte);

you get a direct access to the pointed object byte of the type int defined in main.

Now let’s consider your second program

#include <stdio.h>
#define SIZE_ARRAY 10

void change_array(int *array, int size);

int main()
{
    int array[SIZE_ARRAY] = {0};
    change_array(array, SIZE_ARRAY);
    
    printf("Array : ");
    for(int i = 0 ; i < SIZE_ARRAY ; i++)
    {
        printf("%d ", array[i]);
    }
}

void change_array(int *array, int size)
{
    for(int i = 0 ; i < size ; i++)
    {
        array[i] = 5;
    }
}

In main you declared an integer array

int array[SIZE_ARRAY] = {0};

Array designators used in expressions with rare exceptions are converted to pointers to their first elements.

Thus this call

change_array(array, SIZE_ARRAY);

is equivalent to

change_array( &array[0], SIZE_ARRAY);

So dereferencing the pointer within the function you can change the first element of the array defined in main.

But array elements are stored in a continuous extent of memory. So using the pointer arithmetic and having a pointer to the first element of an array you can access all elements of the array.

In fact all elements of the array array are passed to the function change_array by reference through a pointer to the first element of the array.

For example the for loop within the function you could rewrite like

    for(int i = 0 ; i < size ; i++)
    {
        *( array + i ) = 5;
    }

Now let’s consider your third program.

#include <stdio.h>
#define SIZE_ARRAY 10

int global_array[10] = {5, 5, 5, 5, 5, 5, 5, 5, 5, 5};

void change_array(int *array, int size);

int main()
{
    int array[SIZE_ARRAY] = {0};
    change_array(array, SIZE_ARRAY);
    
    printf("Array : ");
    for(int i = 0 ; i < SIZE_ARRAY ; i++)
    {
        printf("%d ", array[i]);
    }
}

void change_array(int *array, int size)
{
    array = global_array;
}

As it was pointed out already the array array passed to the function change_array is converted to a pointer to its first element.

You may imagine the function call and its definition the following way (I will rename the first function parameter that to avoid name ambiguity).

change_array(array, SIZE_ARRAY);

//...

void change_array( /* int *parm_array, int size */)
{
    int * parm_array = array;
    int size = SIZE_ARRAY;

    parm_array = global_array;
}

That is function parameters are its local variables. The parameter parm_array is alive until the function stops its execution.

Thus this statement

    parm_array = global_array;

assign the pointer to the first element of the global array global_array to the local variable parm_array of the function. This assignment does not touch in any way the array array defined in main. It only changes the local variable parm_array declared in the function change_array.

To achieve the expected by you result you could define in main a pointer that is initialized by the array array. And in the function change_array you could reassigned the pointer with the array global_array passing it to the function by reference the same way as you passed the object byte in your first program.

Here is a demonstrative program.

#include <stdio.h>
#define SIZE_ARRAY 10

int global_array[10] = {5, 5, 5, 5, 5, 5, 5, 5, 5, 5};

void change_array( int **array_ptr );

int main()
{
    int array[SIZE_ARRAY] = {0};
    int *array_ptr = array;
    
    change_array( &array_ptr );
    
    printf("Array : ");
    for(int i = 0 ; i < SIZE_ARRAY ; i++)
    {
        printf("%d ", array_ptr[i]);
    }
}

void change_array( int **array_ptr )
{
    *array_ptr = global_array;
}

The program output is’

Array : 5 5 5 5 5 5 5 5 5 5 

Дело в том что в C++ в функцию можно передать только указатель (или ссылку) на массив, соответственно вы всегда будете работать с исходным массивом, а не с его копией (и вернете также указатель на исходный массив):

int* Func(int *Array)
{
    for(i = 0; i < 3; i++) {
       Array[i]++;
    }
    return Array;
}

Так же этот код не учитывает размер массива (цикл проходит строго три раза), что может привести к следующему:

  • если длина входного массива больше 3: обработаны будут только первые три элемента массива;
  • если длина входного массива меньше 3: программа упадет с грохотом и ошибкой доступа к памяти.

Проблему с размером можно решить двумя путями: передавать в функцию дополнительно размер массива или передавать массив по ссылке (если передавать массив по ссылке, ошибки с тем, что вы передали или вернули массив неправильного размера будут выявлены на стадии компиляции):

int* Func(int* Array, int size) {...}  // передача размера, тогда цикл будет for (int i = 0; i < size; i++)
int (& Func(int (&Array)[3]))[3] {...} // передача по ссылке, тогда цикл будет for (int i = 0; i < 3; i++)

В обоих случаях вернется указатель (или ссылка) на исходный массив. Но смысла возвращать что либо из функции в таком случае нет, вы просто можете продолжать использовать старую переменную.


Чтобы вернуть новый массив, придется просто внутри функции выделить память под новый массив и вернуть указатель на нее (главное потом не забыть очистить память):

int* Func(int* Array, int size)
{
    int *new_array = new int[size];
    for(int i = 0; i < size; i++) {
       new_array[i] = Array[i] + 1;
    }
    return new_array;
}

Но у нас же C++, черт возьми, так что стоит вместо стандартных сишных массивов воспользоваться теми контейнерами, которые предоставляет

Родина

STL:

  • std::vector<type> такой «массив» с изменяемым размером;
  • std::array<type, size> массив фиксированного размера.

Теперь мы можем использовать итераторы, Range-based for loop (начиная с C++11) и прочие прелести:

std::vector<int> Func(std::vector<int> Array) // пример с vector и циклом со счетчиком
{
    for (int i = 0; i < Array.size(); i++) {
        Array[i]++;
    }

std::array<int, 3> Func(std::array<int, 3> Array) // с array и крутым циклом с итераторами
{
    for (std::array<int, 3>::iterator it = Array.begin(); it < Array.end(); it++) {
        *i++;
    }
    return Array;
}

Одной из наиболее часто используемых структур данных является массив. О том как работать с массивами в C# вы узнаете в этом уроке.

  • Объявление массивов и инициализация массивов
    • Объявление массивов
    • Инициализация массивов
    • Неявная типизация
  • Доступ к элементам массива. Обход элементов массива.
  • Передача массива в метод
  • Многомерные массивы
    • Прямоугольные массивы
    • Зубчатые массивы
  • Класс System.Array

Исходный код примеров из этой статьи можете скачать из нашего github-репозитория.

Объявление массивов и инициализация массивов

Объявление массивов

Массив – это структура данных для хранения элементом определенного типа, имеющая фиксированный размер. Доступ к элементам массива производится по числовому индексу.

Для объявления массива, после указания типа его элементов, ставятся квадратные скобки:

int[] a1; // массив типа int

Перед использованием, массив обязательно нужно проинициализировать, это можно сделать сразу, при его объявлении:

int[] na2 = new int[5]; // массив из пяти элементов типа int

Либо после объявления:

int[] na3;
na3 = new int[5]; // массив из пяти элементов типа int

Для доступа к элементам массива используются числовые индексы. Значения элементов массива будут равны значению по умолчанию для типа, массив которого был создан.

Например, для указанного выше a3 – это будут нули, так как для типа int значение по умолчанию: 0;

Console.WriteLine(na3[0]); // значение: 0
Console.WriteLine(na3[1]); // значение: 0

Если попытаться вывести элементы массива na1:

Console.WriteLine(na1[0]); // ошибка компиляции

то приложение не будет собрано, т.к. массив предварительно нужно проинициализировать.

Инициализация массивов

Рассмотрим различные варианты инициализации массива. Как уже было сказано, можно просто указать количество элементов в массиве, при этом его элементам будут присвоены значения по умолчанию:

bool[] ba1 = new bool[3];
Console.WriteLine("ba1[0]: " + ba1[0].ToString());

После объявления массива значения элементам присваиваются через индекс:

string[] sa1 = new string[3];
sa1[0] = "abc";
sa1[1] = "def";
sa1[2] = "ghi";
Console.WriteLine($"sa1: {sa1[0]}, {sa1[1]}, {sa1[2]}");

Есть возможность задать конкретные значения в момент объявления с использованием ключевого слова new и указанием типа:

double[] da1 = new double[3] {0.1, 0.2, 0.3};
Console.WriteLine($"da1: {da1[0]}, {da1[1]}, {da1[2]}");

Либо без ключевого слова new:

double[] da2 = {0.4, 0.5, 0.6};
Console.WriteLine($"da2: {da2[0]}, {da2[1]}, {da2[2]}");

Неявная типизация

При объявлении массива можно воспользоваться ключевым словом var. При этом тип элементов массива следует задать явно в правой части объявления:

var va2 = new string[3];
va2[0] = "John";
va2[1] = "Mary";
va2[2] = "Mike";
Console.WriteLine($"va2: {va2[0]}, {va2[1]}, {va2[2]}");

Либо предоставить возможность “поработать” системе вывода типов:

var va1 = new[] {1, 2, 3};
Console.WriteLine($"va1: {va1[0]}, {va1[1]}, {va1[2]}");

Доступ к элементам массива. Обход элементов массива.

Как уже было сказано выше, за доступ к элементам массива отвечают числовые индексы: 

int[] na4 = {1, 2, 3, 4, 5};
Console.WriteLine($"na4[0]: {na4[0]}");

При этом, если вы укажете индекс больше, чем максимально возможный, то будет выброшено исключение:

Console.WriteLine($"na4[10]: {na4[10]}");

Приведенная выше строка приведет к выбросу следующего исключения:

Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array.

Обход элементов массива можно производить с помощью циклов for, foreach и while, последний самый неудобный для работы с массивами, его мы рассматривать не будем. Если вы работаете с циклом for, то для указания верхней границы инкрементируемой переменной можно воспользоваться свойством Length у массива:

for(int i = 0; i < na4.Length; i++)
{
    Console.Write(na4[i].ToString() + " ");
} // 1 2 3 4 5

Более удобным для обхода элементов будет foreach:

foreach(var v in na4)
{
    Console.Write(v.ToString() + " ");
} // 1 2 3 4 5

Преимущество цикла for состоит в том, что в нем вы можете модифицировать элементы массива:

for(int i = 0; i < na4.Length; i++)
{
    na4[i] = (na4[i] + 3) * 10;
    Console.Write(na4[i].ToString() + " ");
} // 40 50 60 70 80

Передача массива в метод

Массивы являются ссылочным типом данных, это означает, что их значения хранятся в куче, а имя переменной массива является ссылкой на соответствующую область памяти. При передаче массива в качестве аргумента в метод, происходит присваивание значения переменной массива переменной определяющей аргумент, а так как имя массива – это ссылка, то фактически происходит передача ссылки на значение в куче. Поэтому, если вы передали массив в функцию и внутри этой функции произошла модификация этого массива, то исходный массив тоже изменится.

Создадим метода WorkWithArray, который изменяет содержимое массива:

public static void WorkWithArray(int[] arr)
{        
    arr[0] = 123;
}

Вызовем его в методе Main:

int[] na5 = {1, 2, 3, 4, 5};           
foreach(var v in na5) // 1 2 3 4 5
    Console.Write(v + " ");
         
Console.WriteLine();

WorkWithArray(na5);

foreach(var v in na5) // 123 2 3 4 5
    Console.Write(v + " ");

Ниже приведена иллюстрация того, как массив и ссылки на него располагаются в памяти.

Переменные na5 в методе Main и arr в методе WorkWithArray ссылаются на одну и ту же область памяти в куче, поэтому изменение массива через переменную arr отражается на переменной na5.

Многомерные массивы

Массивы имеющее более одного измерения называются многомерными. До этого мы работали с одномерными массивами. В C# предлагается к использованию два вида многомерных массивов: прямоугольные и зубчатые, которые иногда называются массивы массивов.

Прямоугольные массивы

Прямоугольные массивы могут содержать несколько измерений (два и более), при этом количество элементов в каждом подизмерении (в каждой строке) одинаково.

Рассмотрим на примерах работу с такими массивами:

double[,] dm1 = new double[3, 3];
for (int i = 0; i < 3; i++)
    for (int j = 0; j < 3; j++)
        dm1[i, j] = i + j;

for (int i = 0; i < 3; i++)
{
    for (int j = 0; j < 3; j++)
        Console.Write($"{dm1[i, j]} ");
    Console.WriteLine();
}
Console.WriteLine();

double[,] dm2 = { { 1, 2, 3 }, { 4, 5, 6 } };
for (int i = 0; i < 2; i++)
{
    for (int j = 0; j < 3; j++)
        Console.Write($"{dm2[i, j]} ");
    Console.WriteLine();
}
Console.WriteLine();

Зубчатые массивы

В зубчатых массивах элементами верхнего уровня являются другие массивы, это позволяет создавать многомерные структуры, у которых строки имеют разную длину:

int[][] nm1 = new int[3][];
for(int i = 0; i < nm1.Length; i++)           
    nm1[i] = new int[i+1];
 
for(int i = 0; i < nm1.Length; i++)
{
    for(int j = 0; j < nm1[i].Length; j++)
        Console.Write($"{nm1[i][j]} ");
 
    Console.WriteLine();
}

Класс System.Array

Класс System.Array является базовым для всех массивов, это позволяет использовать свойства и методы данного класса при работе с массивом. Ниже в таблицах приведены некоторые полезные свойства и методы из System.Array, более полную информацию вы можете найти в официальной документации Microsoft (https://docs.microsoft.com/ru-ru/dotnet/api/system.array).

Свойства класса System.Array

Имя свойства

Назначение

Length

Число элементов в массиве. Учитываются все измерения.

Rank

Ранг массива – число измерений.

int[] na6 = {1, 2, 3, 4, 5, 6, 7};
int[,] nm2 = { {1, 2, 3}, {4, 5, 6}};
int[][] nm3 = new int[3][];
for(int i = 0; i < nm3.Length; i++)           
    nm3[i] = new int[i+1];

Console.WriteLine($"na6: Length={na6.Length}, Rank={na6.Rank}"); // na6: Length=7, Rank=1
Console.WriteLine($"nm2: Length={nm2.Length}, Rank={nm2.Rank}"); // nm2: Length=6, Rank=2
Console.WriteLine($"nm3: Length={nm3.Length}, Rank={nm3.Rank}"); // nm3: Length=3, Rank=1

Методы класса System.Array

Символ * после названия метода означает, что он имеет более одной сигнатуры, за дополнительной информацией обращайтесь к официальной документации.

Имя метода

Назначение

BinarySearch(Array, Object)*

Выполняет поиск элемента в массиве.

Clear(Array, Int32, Int32)

Присваивает значение по умолчанию определенному количеству элементов массива начиная с заданного индекса.

Clone()

Создает копию массива (неполную).

Copy(Array, Array, Int32)*

Копирует данные из одного массива в другой в заданном количестве.

CopyTo(Array, Int32)*

Копирует элементы из текущего массива в заданный, начиная с указанного индекса.

Exists<T>(T[], Predicate<T>)

Определяет наличие элемента удовлетворяющему предикату.

GetValue(Int32)*

Возвращает значение по указанному индексу.

IndexOf(Array, Object)*

Возвращает индекс первого вхождения элемента в массиве.

Reverse(Array)*

Задает обратный порядок для элементов в массиве.

Sort(Array)*

Сортирует элементы массива.

Для вывода содержимого массива в консоль создадим метод PrintArray:

public static void PrintArray<T>(string txt, T[] arr)
{
    Console.Write($"{txt}: ");
    foreach(var v in arr)
    {
        Console.Write($"{v} ");
    }
}

Ниже приведены примеры использования представленных выше методов и свойств класса System.Array:

int[] na6 = {1, 2, 3, 4, 5, 6, 7};
int[,] nm2 = { {1, 2, 3}, {4, 5, 6}};
int[][] nm3 = new int[3][];
for(int i = 0; i < nm3.Length; i++)           
    nm3[i] = new int[i+1];
Console.WriteLine($"na6: Length={na6.Length}, Rank={na6.Rank}"); // na6: Length=7, Rank=1
Console.WriteLine($"nm2: Length={nm2.Length}, Rank={nm2.Rank}"); // nm2: Length=6, Rank=2
Console.WriteLine($"nm3: Length={nm3.Length}, Rank={nm3.Rank}"); // nm3: Length=3, Rank=1
Console.WriteLine("BinarySearch result: " + Array.BinarySearch(na6, 5).ToString()); // BinarySearch result: 4
          
var na7 = (int[])na6.Clone();
Array.Clear(na7, 2, 2);
PrintArray<int>("na6", na6); // na6: 1 2 3 4 5 6 7
PrintArray<int>("na7", na7); // na7: 1 2 0 0 5 6 7
Array.Copy(na7, na6, 4);
PrintArray<int>("na6 after copy", na6); // na6 after copy: 1 2 0 0 5 6 7
(new int[]{1, 2, 3, 4}).CopyTo(na6, 0);
PrintArray<int>("na6", na6); // na6: 1 2 3 4 5 6 7
          
var ans = Array.Exists<int>(na6, v => (v % 2) == 0);
Console.WriteLine($"Is even number exists in na6? Answer: {ans}");
Array.Fill<int>(na7, 7);
PrintArray<int>("na7", na7); // na7: 7 7 7 7 7 7 7
          
Console.WriteLine($"Value at 3 index in na6: {na6.GetValue(3)}");
Console.WriteLine($"Index of value=5 in na6: {Array.IndexOf(na6, 5)}");
Array.Reverse(na6);
PrintArray<int>("na6", na6); // na6: 7 6 5 4 3 2 1
Array.Sort(na6);
PrintArray<int>("na6", na6); // na6: 1 2 3 4 5 6 7

Исходный код примеров из этой статьи можете скачать из нашего github-репозитория.

Массивы и функции

Массивы, также как остальные переменные, можно передавать в функции в качестве аргументов. Рассмотрим такую программу:

#include <stdio.h>
#include <time.h>
 
#define N 10
 
void arr_make(int arr[], int min, int max);
 
int main () {
    int arrI[N], i;
 
    arr_make(arrI, 30, 90);
 
    for (i=0; i<N; i++)
        printf("%d ", arrI[i]);
    printf("n");
}
 
void arr_make(int arr[], int min, int max) {
    int i;
 
    srand(time(NULL));
 
    for (i=0; i<N; i++)
        arr[i] = rand() % (max - min + 1) + min;
}

В теле функции main() объявляется массив, состоящий из 10 элементов. Далее вызывается функция arr_make(), которой передаются в качестве аргументов имя массива и два целых числа.

Если посмотреть на функцию arr_make(), то можно заметить, что ее первый параметр выглядит немного странно. Функция принимает массив неизвестно какого размера. Если предположить, что массивы передаются по значению, т.е. передаются их копии, то как при компиляции будет вычислен необходимый объем памяти для функции arr_make(), если неизвестно какого размера будет один из ее параметров?

На прошлом уроке мы выяснили, что имя массива — это константный указатель на первый элемент массива; т.е. имя массива содержит адрес. Выходит, что мы передаем в функцию копию адреса, а не копию значения. Как мы уже знаем, передача адреса приводит к возможности изменения локальных переменных в вызывающей функции из вызываемой. Ведь на одну и ту же ячейку памяти могут ссылаться множество переменных, и изменение значения в этой ячейке с помощью одной переменной неминуемо отражается на значениях других переменных.

Описание вида arr[] в параметрах функций говорит о том, что в качестве значения мы получаем указатель на массив, а не обычную (скалярную) переменную типа int, char, float и т.п.

Продолжим рассуждения. Если в функцию передается только адрес массива, то в теле функции никакого массива не существует, и когда там выполняется выражение типа arr[i], то на самом деле arr — это не имя массива, а переменная-указатель, к которой прибавляется смещение. Поэтому цикл в функции arr_make() можно переписать на такой:

for(i=0; i<N; i++)
    *arr++ = rand() % (max - min + 1) + min;

В теле цикла результат выражения справа от знака присваивания записывается по адресу, на который указывает arr. За это отвечает выражение *arr. Затем указатель arr начинает указывать на следующую ячейку памяти, т.к. к нему прибавляется единица (arr++). Еще раз: сначала выполняется выражение записи значения по адресу, который содержится в arr; после чего изменяется адрес, содержащийся в указателе (сдвигается на одну ячейку памяти определенного размера).

Поскольку мы можем изменять arr, это доказывает, что arr — обычный указатель, а не имя массива. Тогда зачем в заголовке функции такой гламур, как arr[]? Действительно, чаще используют просто переменную-указатель:

void arr_make(int *arr, int min, int max);

Хотя в таком случае становится не очевидно, что принимает функция — указатель на обычную переменную или все-таки на массив. В любом случае она будет работать.

Часто при передаче в функцию массивов туда же передают и количество его элементов в виде отдельного параметра. В примере выше N является глобальной константой, поэтому ее значение доступно как из функции main(), так и arr_make(). Иначе, более грамотно было бы написать функцию arr_make() так:

void arr_make(int *arr, int n, int min, int max) {
    int i;
 
    srand(time(NULL));
 
    for (i=0; i<n; i++)
        arr[i] = rand() % (max - min + 1) + min;
}

В данном случае параметр n — это количество обрабатываемых элементов массива.

Следует еще раз обратить внимание на то, что при передачи имени массива в функцию, последняя может его изменять. Однако такой эффект не всегда является желательным. Конечно, можно просто не менять значения элементов массива внутри функции, как в данном примере, где вычисляется сумма элементов массива; при этом сами элементы никак не изменяются:

int arr_sum(int *arr) {
    int i, s=0;
 
    for(i=0; i<N; i++) {
        s = s + arr[i];
    }
 
    return s;
}

Но если вы хотите написать более надежную программу, в которой большинство функций не должны менять значения элементов массивов, то лучше в заголовках этих функций объявлять параметр-указатель как константу, например:

int arr_sum(const int *arr);

В этом случае, любая попытка изменить значение по адресу, содержащемуся в таком константном указателе, будет приводить к ошибке и программист будет знать, что функция пытается изменить массив.

Усовершенствуем программу, которая была приведена в начале этого урока:

#include <stdio.h>
#include <time.h>
 
#define N 10
 
void arr_make(int *arr, int min, int max);
void arr_inc_dec(int arr[], char sign);
void arr_print(int *arr);
 
int main () {
    int arrI[N], i, minimum, maximum;
    char ch;
 
    printf("Enter minimum & maximum: ");
    scanf("%d %d", &minimum, &maximum);
    arr_make(arrI, minimum, maximum);
    arr_print(arrI);
 
    scanf("%*c"); // избавляемся от n
 
    printf("Enter sign (+,-): ");
    scanf("%c", &ch);
    arr_inc_dec(arrI, ch);
    arr_print(arrI);  
}
 
void arr_make(int *arr, int min, int max) {  
    int i;
    srand(time(NULL));
 
    for(i=0; i<N; i++)
        *arr++ = rand() % (max - min + 1) + min;
}
 
void arr_inc_dec(int *arr, char sign) {  
    int i;
    for (i=0; i<N; i++) {
        if (sign == '+') arr[i]++;
        if (sign == '-') arr[i]--;
    }
}
 
void arr_print(int *arr) {
    int i;
    printf("The array is: ");
    for (i=0; i<N; i++)
        printf("%d ", *arr++);
    printf("n");
}

Теперь у пользователя запрашивается минимум и максимум, затем создается массив из элементов, значения которых лежат в указанном диапазоне. Массив выводится на экран с помощью функции arr_print(). Далее у пользователя запрашивается знак + или -. Вызывается функция arr_inc_dec(), которая в зависимости от введенного знака увеличивает или уменьшает на единицу значения элементов массива.

В функциях arr_make() и arr_print() используется нотация указателей. Причем в теле функций значения указателей меняются: они указывают сначала на первый элемент массива, затем на второй и т.д. В функции arr_inc_dec() используется вид обращения к элементам массива. При этом значение указателя не меняется: к arr прибавляется смещение, которое увеличивается на каждой итерации цикла. Ведь на самом деле запись arr[i] означает *(arr+i).

При использовании нотации обращения к элементам массива программы получаются более ясные, а при использовании записи с помощью указателей они компилируются чуть быстрее. Это связано с тем, что когда компилятор встречает выражение типа arr[i], то он тратит время на преобразование его к виду *(arr+i). Однако лучше потратить лишнюю секунду при компиляции, но получить более читаемый код.

  1. Переделайте программу, которая приведена выше таким образом, чтобы она работала с вещественными числами. Вместо функции arr_inc_dec() напишите другую, которая изменяет значения элементов массива на любое значение, которое указывает пользователь.
  2. Напишите программу, в которой из одной функции в другую передается указатель не на начало массива, а на его середину.
  3. Напишите программу, в которой из функции main() в другую функцию передаются два массива: «заполненный» и «пустой». В теле этой функции элементам «пустого» массива должны присваиваться значения, так или иначе преобразованные из значений элементов «заполненного» массива, который не должен изменяться.

Курс с решением части задач:
pdf-версия, android-приложение


Содержание

  • 1. Особенности передачи ссылок на массивы экземпляров класса с использованием модификаторов ref и out
  • 2. Передача одномерного массива типа int как ref-параметра. Пример
  • 3. Передача одномерного массива экземпляров класса как out-параметра. Пример
  • 4. Передача двумерного массива экземпляров класса как ref-параметра. Пример
  • Связанные темы

Поиск на других ресурсах:

1. Особенности передачи ссылок на массивы экземпляров класса с использованием модификаторов ref и out

Существует 2 способа передачи в функцию ссылок на массивы экземпляров класса:

  • передача по значению. В этом случае, массив полностью копируется в функцию. Изменение значений массива внутри функции не изменит эти значения в вызывающем коде;
  • передача по ссылке. В этом случае используются модификаторы ref или out. При такой передаче можно изменить количество элементов в массиве, перераспределить память для каждого элемента, заполнить новыми значениями эти элементы массива.

Итак, при передаче массива с модификаторами ref или out:

  • можно изменять значения самой ссылки. Это значит, что можно перераспределить память для массива и его элементов и заполнить новыми значениями элементы массива;
  • изменение значений элементов массива внутри функции будет отображаться в вызывающем коде.

 

2. Передача одномерного массива типа int как ref-параметра. Пример

В нижеследующем примере разработан класс Integer, содержащий один внутренний параметр value.

В тестируемом классе Program объявляются следующие элементы:

  • статическая функция GetArray(), формирующая массив случайных чисел типа Integer. Функция получает входным параметром массив A типа Integer[] как ref-параметр и перераспределяет память для самой ссылки A. Функция по новому выделяет память для любого экземпляра и заполняет поля экземпляра значениями. Изменение значений фиксируется в вызывающей функции main();
  • статическая функция SumIntegers() – предназначена для вычисления суммы целочисленных значений value массива типа Integer. Функция получает входным параметром массив типа Integer[] как ref-ссылку.

После завершения работы программы не нужно освобождать память (как в C++). Память будет освобождена автоматически при так называемой «сборке мусора».

using System;

namespace ConsoleApp2
{
  // Класс Integer - ссылочный тип
  class Integer
  {
    public int value; // целочисленное значение
  }

  class Program
  {
    // Статическая функция GetArray() генерирует (формирует) массив случайных чисел,
    // масив передается как ref-параметр, можно изменять саму ссылку A
    static void GetArray(ref Integer[] A)
    {
      // 1. Инициализировать генератор случайных чисел
      Random rnd_num = new Random();

      // 2. Изменить количество элементов в массиве A - перераспределить память
      A = new Integer[6]; // изменяется сама ссылка - выделить память для 6 экземпляров

      // 3. Выделить память для каждого экземпляра отдельно
      for (int i = 0; i < A.Length; i++)
      {
        A[i] = new Integer();
      }

      // 4. Заполнить массив случайными целыми числами в пределах [0; 10]
      for (int i = 0; i < A.Length; i++)
      {
        // Все переменные в массиве будут отображаться за пределами данной функции
        A[i].value = rnd_num.Next(0, 10);
      }

      // 5. Возврат из функции
      return;
    }

    // Статическая функция, которая находит сумму элементов массива,
    // массив передается по ссылке (с модификатором ref)
    static int SumIntegers(ref Integer[] A)
    {
      int sum = 0;
      for (int i=0; i<A.Length; i++)
      {
        sum += A[i].value;
      }
      return sum;
    }

    static void Main(string[] args)
    {
      // Передача ref-массива экземпляров класса в метод

      // 1. Объявить массив типа Integer
      const int MAX_ITEMS = 8; // максимально-допустимое количество элементов в массиве
      Integer[] Array;

      // 2. Выделить память для MAX_ITEMS элементов, сформированных случайно
      Array = new Integer[MAX_ITEMS];

      // 3. Выделить память для каждого элемента типа Integer
      for (int i = 0; i < Array.Length; i++)
        Array[i] = new Integer();

      // 4. Заполнить массив Array фиксированными значениями от 1 до Array.Length
      for (int i=0; i<Array.Length; i++)
      {
        Array[i].value = i + 1;
      }

      // 5. Вывести массив на экран в строку
      Console.WriteLine("Items in Array:");
      for (int i=0; i<Array.Length; i++)
      {
        Console.Write($"{Array[i].value}t");
      }

      // 6. Заполнить массив Array случайными числами
      // с помощью вызова функции GetArray()
      GetArray(ref Array); // функция статическая, изменяет массив изнутри

      // 7. Вывести измененный массив на экран
      Console.WriteLine("nArray after random forming:");
      for (int i=0; i<Array.Length; i++)
      {
        Console.Write($"{Array[i].value}t");
      }

      // 8. Вычислить сумму элементов измененного массива и вывести ее на экран
      int summ = Program.SumIntegers(ref Array);
      Console.WriteLine($"nsumm = {summ}");
    }
  }
}

Результат выполнения программы:

Items in Array:
1 2 3 4 5 6 7 8
Array after random forming:
4 3 8 9 7 7
summ = 38


 

3. Передача одномерного массива экземпляров класса как out-параметра. Пример

Данный пример очень близок к примеру в п. 2 (передача массива как ref-параметра). Отличие только в функции GetArray(), в которой модификатор ref заменен на модификатор out.

using System;

namespace ConsoleApp2
{
  // Клас Integer - ссылочный тип
  class Integer
  {
    public int value;
  }

  class Program
  {
    // Статическая функция GetArray() генерирует (формирует) массив случайных чисел,
    // массив передается как out-параметр, можно изменять саму ссылку A
    static void GetArrayOut(out Integer[] A)
    {
      // 1. Инициализировать генератор случайных чисел
      Random rnd_num = new Random();

      // 2. Изменить количество элементов в массиве A
      A = new Integer[6]; // изменяется сама ссылка - выделить память для 6 экземпляров

      // 3. Выделить память для каждого экземпляра отдельно
      for (int i = 0; i < A.Length; i++)
      {
        A[i] = new Integer();
      }

      // 4. Заполнить массив случайными целыми числами в пределах [0; 10]
      for (int i = 0; i < A.Length; i++)
      {
        // Все изменения в массиве будут отображаться за пределами данной функции
        A[i].value = rnd_num.Next(0, 10);
      }

      // 5. Возврат из функции
      return;
    }

    // Статическая функция, которая находит сумму элементов массива,
    // массив передается по ссылке (с модификатором ref)
    static int SumIntegers(ref Integer[] A)
    {
      int sum = 0;
      for (int i=0; i<A.Length; i++)
      {
        sum += A[i].value;
      }
      return sum;
    }

    static void Main(string[] args)
    {
      // Передача out-массива экземпляров класса в метод

      // 1. Объявить массив типа Integer
      const int MAX_ITEMS = 8; // максимально-допустимое количество элементов в массиве
      Integer[] Array;

      // 2. Выделить память для MAX_ITEMS элементов, сформированных случайно
      Array = new Integer[MAX_ITEMS];

      // 3. Выделить память для каждого элемента типа Integer
      for (int i = 0; i < Array.Length; i++)
        Array[i] = new Integer();

      // 4. Заполнить массив Array фиксированными значениями от 1 до MAX_ITEMS
      for (int i=0; i<Array.Length; i++)
      {
        Array[i].value = i + 1;
      }

      // 5. Вывести массив на экран в строку
      Console.WriteLine("Items in Array:");
      for (int i=0; i<Array.Length; i++)
      {
        Console.Write($"{Array[i].value}t");
      }

      // 6. Заполнить массив Array случайными числами
      // с помощью вызова функции GetArray()
      GetArrayOut(out Array); // функция статическая, изменяет массив изнутри

      // 7. Вывести измененный массив на экран
      Console.WriteLine("nArray after random forming:");
      for (int i=0; i<Array.Length; i++)
      {
        Console.Write($"{Array[i].value}t");
      }

      // 8. Вычислить сумму элементов измененного массива и вывести ее на экран
      int summ = Program.SumIntegers(ref Array);
      Console.WriteLine($"nsumm = {summ}");
    }
  }
}

Результат выполнения программы:

Items in Array:
1 2 3 4 5 6 7 8
Array after random forming:
1 4 5 9 5 2
summ = 26

 

4. Передача двумерного массива экземпляров класса как ref-параметра. Пример

В примере демонстрируется передача в функцию двумерного массива экземпляров класса как ref-параметра. Если нужно реализовать передачу в функцию двумерного массива как out-параметра, то модификатор ref заменяется на модификатор out.

В программе реализованы следующие классы и методы:

  • класс Integer – реализует целое число;
  • статический метод GetArray2D(), который получает параметрами ссылку на двумерный массив A экземпляров класса Integer и ссылку на переменные Rows, Columns (количество строк и количество столбцов). В этом методе выделяется память для массива, формируются размеры и значение его элементов. Все изменения внутри функции будут отображаться в вызывающем коде (функции main());
  • статический метод SumIntegers2D(), который получает параметрами ref-ссылку на массив и его размерность. Метод вычисляет сумму элементов массива;
  • статический метод PrintArray2D(), который получает массив и его размерность по значению (без модификаторов ref или out). Данный метод выводит массив на экран.

Текст приложения, созданного по шаблону Console Application следующий.

using System;

namespace ConsoleApp2
{
  // Класс Integer - ссылочный тип
  class Integer
  {
    public int value;
  }

  class Program
  {
    // Статическая функция GetArray2D() генерирует (формирует) массив случайных чисел.
    // Массив передается как ref-параметр, можно изменять саму ссылку A.
    // Параметры Rows, Columns возвращают количество столбцов и строк массива.
    static void GetArray2D(ref Integer[,] A, ref int Rows, ref int Columns)
    {
      // 1. Инициализировать генератор случайных чисел
      Random rnd_num = new Random();

      // 2. Сформировать размер массива
      Rows = 3; // количество строк
      Columns = 4; // количество столбцов

      // 3. Выделить память для двумерного массива в целом
      A = new Integer[Rows, Columns];

      // 4. Выделить память для каждого экземпляра отдельно
      for (int i = 0; i < Rows; i++)
        for (int j = 0; j < Columns; j++)
        {
          A[i, j] = new Integer();
        }

      // 5. Заполнить массив случайными целыми числами в пределах [0; 10]
      for (int i = 0; i < Rows; i++)
        for (int j = 0; j < Columns; j++)
        {
          // Все изменения в массиве будут отображаться за пределами данной функции
          A[i, j].value = rnd_num.Next(0, 10);
        }

      // 6. Возврат из функции
      return;
    }

    // Статическая функция, которая вычисляет сумму элементов двумерного массива.
    // Массив передается по ссылке (с модификатором ref).
    // Параметры Rows, Columns передаются по значению.
    static int SumIntegers2D(ref Integer[,] A, int Rows, int Columns)
    {
      int sum = 0;
      for (int i = 0; i < Rows; i++)
        for (int j = 0; j < Columns; j++)
        {
          sum += A[i, j].value;
        }
      return sum;
    }

    // Статическая функция PringArray2D(), которая выводит двумерный массив на экран.
    // Функция получает массив A, размеры массива m, n. Параметры передаются по значению
    static void PrintArray2D(Integer[,] A, int m, int n)
    {
      for (int i = 0; i < m; i++)
      {
        for (int j = 0; j < n; j++)
        {
          Console.Write("{0}t", A[i, j].value);
        }
        Console.WriteLine();
      }
    }

    static void Main(string[] args)
    {
      // Передача двумерного ref-массива экземпляров класса в метод

      // 1. Объявить ссылку на двумерный массив типа Integer
      Integer[,] Array2D = null;
      // 2. Установить количество строк и стобцов в массиве
      int m, n;
      m = 2;
      n = 3;

      // 3. Сформировать массив с помощью функции GetArray2D()
      GetArray2D(ref Array2D, ref m, ref n);

      // 4. Вывести сформированный массив на экран
      Console.WriteLine("Array2D:");
      PrintArray2D(Array2D, m, n);

      // 5. Вычислить сумму элементов массива и вывести ее на экран
      int summ = SumIntegers2D(ref Array2D, m, n);
      Console.WriteLine($"summ = {summ}");
    }
  }
}

Результат работы программы

Array2D:
5 1 7 6
7 7 8 9
7 2 3 7
summ = 69

 


Связанные темы

  • 2. Модификаторы параметров ref и outПримерыОтличия между модификаторами ref и out

 


Понравилась статья? Поделить с друзьями:

Читайте также:

  • Как изменить массив автокад
  • Как изменить массив char c
  • Как изменить маску подсети на телефоне
  • Как изменить маску подсети на роутере
  • Как изменить маску подсети на андроид

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии