Когда стоит сохранять длину массива в локальную переменную в C#
Читая Хабр, я наткнулся на статью «Стоит ли сохранять длину массива в локальную переменную в C#?» (которая была в разделе «лучшее»). Мне кажется глупый вопрос, не совсем корректные измерения (почему нет измерений для вложенных циклов?) и странный вывод.
Длину массива в С# стоит сохранять в отдельную переменную в случае когда у нас несколько вложенных циклов, ниже пример.
Вот простой тестовый код без сохранения длины массива в переменную:
Random rnd1 = new Random(DateTime.UtcNow.Millisecond); int[,] arr1 = new int[Int16.MaxValue, Byte.MaxValue]; for (int i = 0; i < arr1.GetLength(0); i++) < for (int j = 0; j < arr1.GetLength(1); j++) < arr1[i, j] = rnd1.Next(Int32.MinValue, Int32.MaxValue); >>
Вот тот же код c сохранением длины массива в переменную:
Random rnd1 = new Random(DateTime.UtcNow.Millisecond); int[,] arr1 = new int[Int16.MaxValue, Byte.MaxValue]; int len1 = arr1.GetLength(0), len2 = arr1.GetLength(1); for (int i = 0; i < len1; i++) < for (int j = 0; j < len2; j++) < arr1[i, j] = rnd1.Next(Int32.MinValue, Int32.MaxValue); >>
Код с сохранением длины массива в переменную (второй вариант) выполняется примерно на 15% быстрее.
Подобный ответ можно найти в более-менее толстых книжках по C# или .Net, но при этом умный человек постит это на Хабре и никто в комментариях не указал ему что длину массива в С# сохраняют в переменную обычно для вложенных циклов и там это действительно имеет смысл.
Я просто хотел оставить там комментарий, но без регистрации не смог, а после регистрации оказалось — что я и после регистрации не могу оставить там комментарий (так как прошло более 10 дней с момента публикации). Может кто-то заметит эту заметку и скопирует ее туда в виде комментария или вроде того.
Getlength 0 c что это
Массив — это совокупность данных одного типа, имеющих одно общее имя. Доступ к элементам массива осуществляется путём указания имени массива и номера элемента.
Особенностью массивов в C# относительно языков C/C++ является то, что память под них всегда выделяется динамически . Это объясняется тем, что любой массив является наследником класса Array.
Вначале рассмотрим работу с одномерным массивом, затем рассмотрим приёмы работы с двухмерными и многомерными массивами.
Одномерные массивы
Одномерный массив — это массив, имеющий одно измерение.
Прежде чем начать работать с массивом, необходимо создать ссылку на объект:
Затем выделяется память под заданное количество элементов массива:
имя_массива = new тип_элементов [ количество_элементов ];
После этого с массивом можно выполнять какие-либо действия.
Например, создадим массив из 5 вещественных чисел:
Две рассмотренные выше операции можно объединить:
double [] x = new double[5];
Как и простую переменную, массив можно инициализировать , т.е. задать элементам массива начальные значения (пусть это будут числа 4,5,3,2,1 ):
double []x = new double[5] ;
Если чисел при инициализации задать больше или меньше необходимого, то система на этапе компиляции выдаст ошибку. Вывод: данных при инициализации надо задавать ровно столько, сколько выделяем памяти.
Полученную запись можно упростить:
Здесь память под массив будет выделяться по фактическому количеству данных, записанных в блоке (фигурных скобках).
В обоих случаях выделяется память под массива из 5 элементов вещественного типа.
К стати. А как система отреагирует вот на такую запись:
Оказывается, совершенно спокойно. Точно так же её вполне устроит и такое:
double []x = new double[0];
Массив из нуль чисел — это тоже массив. Создать его можно, а вот обращаться к данным этого массива не получится. Отрицательное же количество элементов оценивается как ошибочное. Ни создать такой массив, и тем более работать с ним не выйдет. Компилятор сразу выдаст ошибку.
Дальнейшая работа с массивом ведётся так же, как обычно это было в C/C++. После выхода из блока, в котором был создан массив, выделенная под массив память освобождается «сборщиком мусора». Но происходит это не сразу, а тогда, когда система посчитает нужным это сделать. В большинстве случает это не имеет для программиста ни какого значения.
Пример . Дан массив из n действительных чисел. Вычесть из каждого элемента массива среднее значение массива. Распечатать полученный массив.
Возможный вариант решения:
public static void Main(string[] args)
double []x = new double[5];
int i, n = x.Length;
Console.WriteLine(«Задайте вещественных чисел:», n);
s /= n; // это среднее значение
Console.WriteLine(«Массив после обработки:»);
foreach(double r in x)
Console.Write(«Press any key to continue . . . «);
Двумерные массивы (матрицы)
Двумерный массив имеет два измерения. Работа с матрицей, как и работа с одномерным массивом, начинается с создания ссылки на матрицу с последующим выделением памяти. Также возможна инициализация элементов матрицы. Пусть требуется создать матрицу действительных чисел A[2×3] . Рассмотрим варианты работы.
1)Создание ссылки на матрицу и выделение памяти:
double [,]a = new double[2, 3];
2)Создаём ссылку на матрицу, выделяем память и одновременно инициализируем элементы матрицы:
Как видим, каждую строку берём в свой блок, в остальном всё делается так же, как и для одномерного массива.
То же самое можно записать по аналогии с одномерным массивом ещё проще:
Пример . Пронормировать матрицу действительных чисел A[2×3] , т.е. каждый элемент матрицы поделить на максимальное по модулю число.
Возможный вариант решения:
public static void Main(string[] args)
double [,]a = new double[n, m];
Console.WriteLine(«Задайте матрицу A[*]:», n , m);
a[i, j] = double.Parse(Console.ReadLine());
double max = Math.Abs(a[0, 0]);
if(Math.Abs(a[i, j]) > max)
max = Math.Abs(a[i, j]);
Console.WriteLine(«Mатрица A[*] после нормирования:», n , m);
Console.Write(«Press any key to continue . . . «);
Многомерные массивы
При работе с многомерными массивами всё делается по аналогии. Единственно, надо учитывать количество измерений таких массивов.
Пример описания трёхмерного массива:
double [,,]V = new double[2, 3, 3];
Здесь выделяется память под массив, который можно представить состоящим из двух таблиц размером 3 x3 .
Пример инициализации трёхмерного массива:
double [,,]V = new double[2, 3, 3]
А ещё проще инициализировать так:
Дальнейшая работа с многомерными массивами ведётся как и с матрицами, только индексов больше и, как правило, возрастает вложенность циклов.
Стандартные методы и свойства для работы с массивами
Все массивы, как было сказано ранее, являются наследниками класса Array . Благодаря этому при работе с массивами имеется большое количество готовых методов и свойств. Рассмотрим некоторые из них. Во всех примерах x — одномерный массив, a — матрица.
1) Length — возвращает количество элементов в массиве. Обычно используется в циклах, например:
2) Rank — возвращает размерность массива. Пример:
1) Sort() — сортировка массива. Метод многократно перегружен. Примеры использования:
сортируем весь массив по возрастанию:
начиная с i-го элемента, упорядывачиваем k элементов массива (у нас: сортируем 3 элемента, начиная с 2-го):
2) Reverse() — изменяем порядок следования элементов массива на обратный:
А таким способом первые три элемента будут записаны в обратном порядке:
Array.Reverse(x, 0, 3);
3) BinarySearch() — поиск в упорядоченном массиве индекса элемента, равного заданному значению. Если поиск не увенчался успехом, то ответом будет отрицательное число. Пример:
int j = Array.BinarySearch(x, t);
4) IndexOf() — поиск первого вхождения в массиве заданного значения, например, поиск положения числа 4 в массиве x . Если в массиве нет такого значения, то ответом будет отрицательное число.
int j = Array.IndexOf(x,4);
5) LastIndexOf() — поиск последнего вхождения в массиве заданного значения, например, поиск положения числа 4 в массиве x . Если в массиве нет такого значения, то ответом будет отрицательное число.
int j = Array.LastIndexOf(x,4);
6) GetLength() — получение количества элементов по данной размерности. Размерности нумеруются с 0 . Таким образом, для двумерного массива ввод массива действительных чисел с клавиатуры можно записать:
a[i, j] = double.Parse(Console.ReadLine());
Getlength 0 c что это
На этом шаге мы рассмотрим организацию и доступ к элементам такого массива .
В двумерном массиве для однозначной идентификации элемента в массиве необходимо указать два индекса. Двумерный массив удобно представлять в виде таблицы. Первый индекс определяет строку, в которой находится элемент, а второй индекс определяет столбец. Создается двумерный массив практически так же, как и одномерный: объявляется переменная массива, создается собственно массив, и ссылка на этот массив записывается в переменную массива. При объявлении переменной двумерного массива после идентификатора типа указываются квадратные скобки с запятой внутри [,] . Например, если мы хотим объявить переменную для двумерного целочисленного массива, то соответствующая инструкция могла бы выглядеть как
int[,] nums; .
При создании двумерного массива используется инструкция new , а после нее указывается идентификатор типа для элементов массива и в квадратных скобках разделенные через запятую два целых числа, определяющих размер массива по каждому из индексов (количество строк и столбцов в двумерном массиве). Например, создать двумерный целочисленный массив из 3 строк и 5 столбцов можно инструкцией
new int [3, 5].
Как и для одномерного массива, значением инструкции, создающей двумерный массив, является ссылка на этот массив. Поэтому если переменная nums предварительно объявлена командой
int [,] nums; ,
то законной была бы команда
nums = new int [3, 5]; .
Команды объявления переменной массива и создания массива можно объединять:
int[,] nums = new int [3, 5]; .
Ниже приведен шаблон объявления переменной для двумерного массива и создания двумерного массива:
тип[,] переменная; переменая = new тип[размер, размер];
Можно использовать и такой шаблон:
тип[,] переменная = new тип[размер, размер];
Для обращения к элементу массива указывают имя массива и в квадратных скобках через запятую — индексы этого элемента. Индексация по каждому индексу начинается с нуля. То есть инструкция nums[0, 0] означает обращение к элементу массива nums , который расположен в первой строке и первом столбце (в строке с индексом 0 и столбце с индексом 0). Выражение nums[1, 2] является обращением к элементу, находящемуся в строке с индексом 1 (вторая по порядку строка) и столбце с индексом 2 (третий по порядку столбец).
Свойство Length для двумерного массива возвращает общее количество элементов в массиве. То есть для массива nums из 3 строк и 5 столбцов значением выражения nums.Length является число 15 (произведение 3 на 5). Чтобы узнать размер массива по какому-то индексу (то есть количество строк или количество столбцов в массиве), используют метод GetLength() . Метод вызывается из переменной массива, а аргументом методу передается целое число, определяющее индекс, для которого возвращается размер массива.
Массивы в C# по факту реализуются как объекты. Как и у прочих объектов, у массива есть методы (и свойства). Более детально объекты обсуждаются немного позже. Здесь для нас важно научиться пользоваться преимуществами, которые имеются у массивов благодаря такому «объектному» способу их реализации.
Размерность массива определяется количеством индексов, которые нужно указать для идентификации элемента массива. Определить размерность массива можно с помощью свойства Rank . Размерность двумерного массива равна 2.
Если мы вызываем метод GetLength() с аргументом 0, то значением возвращается размер массива по первому индексу. Если вызвать метод GetLength() с аргументом 1, то получим размер массива по второму индексу.
Например, если массив nums состоит из 3 строк и 5 столбцов, то значением выражения nums.GetLength(0) является число 3 (размер но первому индексу — количество строк), а значением выражения nums.GetLength(1) является число 5 (размер по второму индексу — количество столбцов).
НИже приведен пример, в котором иллюстрируется способ создания двумерного массива: создается целочисленный массив и построчно заполняется натуральными числами.
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace pr56_1 < class Program < static void Main() < // Количество строк и столбцов в массиве: int rows = 3, cols = 5; // Создание двумерного массива: int[,] nums = new int[rows, cols]; // Значение первого элемента в массиве: int value = 1; // Заполнение и отображение массива. // Перебор строк в массиве: for(int i = 0; i < nums.GetLength(0); i++) < // Перебор столбцов в строке: for (int j = 0; j < nums.GetLength(1); j++) < // Присваивание значения элементу массива: nums[i, j] = value; // Это будет значение следующего элемента: value++; // Отображение элемента в строке: Console.Write(nums[i, j] + "\t"); > // Переход к новой строке: Console.WriteLine(); > // Задержка: Console.ReadLine(); > > >
Архив проекта можно взять здесь.
Результат выполнения программы представлен ниже.
Рис.1. Результат работы приложения
В программе количество строк и столбцов в двумерном массиве задается через целочисленные переменные rows и cols (значения 3 и 5 соответственно). Двумерный массив создается командой
int[,] nums = new int[rows, cols]; .
Для заполнения массива и отображения значений его элементов используются вложенные конструкции цикла. Во внешнем цикле индексная переменная i перебирает строки двумерного массива. Начальное значение переменной равно 0, а верхняя граница определяется значением выражения nums.GetLength(0) (количество строк в массиве nums — переменная i строго меньше этого значения). Во внутреннем цикле переменная j перебирает столбцы двумерного массива. Начальное значение переменной равно 0, и за каждый цикл переменная увеличивается на единицу. Конструкция цикла выполняется, пока значение переменной строго меньше значения выражения nums.GetLength(1) (количество столбцов в массиве nums ). При заданных значениях индексов i и j командой
nums[i, j] = value;
соответствующему элементу массива присваивается значение. Начальное значение переменной value равно 1, поэтому первый элемент в массиве (элемент с двумя нулевыми индексами — элемент в первой строке в первом столбце) получает единичное значение. После выполнения команды
value++;
значение переменной value увеличивается на единицу. На следующей итерации цикла это новое значение будет присвоено очередному элементу.
После того как значение элементу присвоено, командой
Console.Write(nums[i, j] + "\t");
оно отображается в консольном окне. Элементы из одной строки массива отображаются в одной и той же строке консольного окна. Для размещения элементов в строке использована табуляция.
Тело внешнего цикла состоит из внутренней конструкции цикла и команды
Console.WriteLine(); .
Внутренний цикл нужен для заполнения строк массива и отображения значений элементов в консольном окне. Когда внутренний цикл завершает работу, благодаря команде
Console.WriteLine();
выполняется переход к новой строке. На этом завершается очередная итерация внешней конструкции цикла. На следующей итерации внешнего цикла снова выполняется внутренняя конструкция цикла, и затем команда
Console.WriteLine(); ,
и так далее, пока не будут перебраны все строки двумерного массива.
На следующем шаге мы закончим изучение этого вопроса .
Getlength 0 c что это
Массив представляет набор однотипных данных. Объявление массива похоже на объявление переменной за тем исключением, что после указания типа ставятся квадратные скобки:
тип_переменной[] название_массива;
Например, определим массив целых чисел:
int[] numbers;
После определения переменной массива мы можем присвоить ей определенное значение:
int[] nums = new int[4];
Здесь вначале мы объявили массив nums, который будет хранить данные типа int . Далее используя операцию new , мы выделили память для 4 элементов массива: new int[4] . Число 4 еще называется длиной массива . При таком определении все элементы получают значение по умолчанию, которое предусмотренно для их типа. Для типа int значение по умолчанию — 0.
Также мы сразу можем указать значения для этих элементов:
int[] nums2 = new int[4] < 1, 2, 3, 5 >; int[] nums3 = new int[] < 1, 2, 3, 5 >; int[] nums4 = new[] < 1, 2, 3, 5 >; int[] nums5 = < 1, 2, 3, 5 >;
Все перечисленные выше способы будут равноценны.
Подобным образом можно определять массивы и других типов, например, массив значений типа string :
string[] people = < "Tom", "Sam", "Bob" >;
Индексы и получение элементов массива
Для обращения к элементам массива используются индексы . Индекс представляет номер элемента в массиве, при этом нумерация начинается с нуля, поэтому индекс первого элемента будет равен 0, индекс четвертого элемента — 3.
Используя индексы, мы можем получить элементы массива:
int[] numbers = < 1, 2, 3, 5 >; // получение элемента массива Console.WriteLine(numbers[3]); // 5 // получение элемента массива в переменную var n = numbers[1]; // 2 Console.WriteLine(n); // 2
Также мы можем изменить элемент массива по индексу:
int[] numbers = < 1, 2, 3, 5 >; // изменим второй элемент массива numbers[1] = 505; Console.WriteLine(numbers[1]); // 505
И так как у нас массив определен только для 4 элементов, то мы не можем обратиться, например, к шестому элементу. Если мы так попытаемся сделать, то мы получим ошибку во время выполнения:
int[] numbers = < 1, 2, 3, 5 >; Console.WriteLine(numbers[6]); // ! Ошибка - в массиве только 4 элемента
Свойство Length и длина массива
каждый массив имеет свойство Length , которое хранит длину массива. Например, получим длину выше созданного массива numbers:
int[] numbers = < 1, 2, 3, 5 >; Console.WriteLine(numbers.Length); // 4
Для получения длины массива после названия массива через точку указывается свойство Length : numbers.Length .
Получение элементов с конца массива
Благодаря наличию свойства Length , мы можем вычислить индекс последнего элемента массива — это длина массива — 1. Например, если длина массива — 4 (то есть массив имеет 4 элемента), то индекс последнего элемента будет равен 3. И, используя свойство Length , мы можем легко получить элементы с конца массива:
int[] numbers = < 1, 2, 3, 5>; Console.WriteLine(numbers[numbers.Length - 1]); // 5 - первый с конца или последний элемент Console.WriteLine(numbers[numbers.Length - 2]); // 3 - второй с конца или предпоследний элемент Console.WriteLine(numbers[numbers.Length - 3]); // 2 - третий элемент с конца
Однако при подобном подходе выражения типа numbers.Length — 1 , смысл которых состоит в том, чтобы получить какой-то определенный элемент с конца массива, утяжеляют код. И, начиная, с версии C# 8.0 в язык был добавлен специальный оператор ^ , с помощью которого можно задать индекс относительно конца коллекции.
Перепишем предыдущий пример, применяя оператор ^ :
int[] numbers = < 1, 2, 3, 5>; Console.WriteLine(numbers[^1]); // 5 - первый с конца или последний элемент Console.WriteLine(numbers[^2]); // 3 - второй с конца или предпоследний элемент Console.WriteLine(numbers[^3]); // 2 - третий элемент с конца
Перебор массивов
Для перебора массивов мы можем использовать различные типы циклов. Например, цикл foreach :
int[] numbers = < 1, 2, 3, 4, 5 >; foreach (int i in numbers)
Здесь в качестве контейнера выступает массив данных типа int . Поэтому мы объявляем переменную с типом int
Подобные действия мы можем сделать и с помощью цикл for:
int[] numbers = < 1, 2, 3, 4, 5 >; for (int i = 0; i
В то же время цикл for более гибкий по сравнению с foreach . Если foreach последовательно извлекает элементы контейнера и только для чтения, то в цикле for мы можем перескакивать на несколько элементов вперед в зависимости от приращения счетчика, а также можем изменять элементы:
int[] numbers = < 1, 2, 3, 4, 5 >; for (int i = 0; i
Также можно использовать и другие виды циклов, например, while :
int[] numbers = < 1, 2, 3, 4, 5 >; int i = 0; while(i
Многомерные массивы
Массивы характеризуются таким понятием как ранг или количество измерений. Выше мы рассматривали массивы, которые имеют одно измерение (то есть их ранг равен 1) — такие массивы можно представлять в виде ряда (строки или столбца) элемента. Но массивы также бывают многомерными. У таких массивов количество измерений (то есть ранг) больше 1.
Массивы которые имеют два измерения (ранг равен 2) называют двухмерными. Например, создадим одномерный и двухмерный массивы, которые имеют одинаковые элементы:
int[] nums1 = new int[] < 0, 1, 2, 3, 4, 5 >; int[,] nums2 = < < 0, 1, 2 >, < 3, 4, 5 >>;
Визуально оба массива можно представить следующим образом: