Массивы
Массивы являются одной из областей, в которой внешнее сходство в синтаксисе между C++ и C# скрывает то, что реально происходящее 'за сценой' существенно различается в этих двух языках. В C++ массив является по сути множеством переменных, упакованных вместе в памяти и доступных через указатель. В C#, с другой стороны, массив является экземпляром базового класса System.Array
, и поэтому выступает полноценным объектом, хранящимся в куче под управлением сборщика мусора. Для доступа к методам этого класса C# использует синтаксис типа C++ способом, который создает иллюзию доступа к массиву. Недостаток этого подхода состоит в том, что накладные расходы для массивов больше, чем в C++, но преимуществом является то, что массивы C# более гибкие и при этом проще кодируются. В качестве примера: все массивы C# имеют свойство Length
, которое задает число элементов массива, не требуя тем самым хранить его отдельно. К тому же массивы C# значительно безопаснее в использовании — так, проверка границ индекса выполняется автоматически.
В C# возможно сделать простой массив без накладных расходов класса System.Array
, но для этого понадобится использовать указатели и ненадежные блоки кода.
Одномерные массивы
Для одномерных массивов (терминология C#: массивы ранга 1) синтаксис доступа в обоих языках идентичен — с квадратными скобками, используемыми для индикации элементов массива. Массивы начинаются с нулевого индекса в обоих языках.
Например, чтобы умножить каждый элемент массива float
на 2:
// массив, объявлен как массив float
// этот код работает в C++ и C# без каких-либо изменений
for (int i = 0; i < 10; i++) Array[i] = 2.0f;
Однако, как упоминалось ранее, массивы C# поддерживают свойство Length
, которое используется для определения числа элементов в массиве.
// массив, объявлен как массив float
// этот код компилируется только в C#
for (int i = 0; i < Array.Length; i++) Array[i] *= 2.0f;
В C# можно также использовать инструкцию foreach
для доступа к элементам массива, что рассматривалось ранее.
Синтаксис объявления массивов в C# слегка отличается, так как массивы C# всегда объявляются как ссылочные объекты.
double [] Array; // простое объявление ссылки без реального
// создания экземпляра массива
Array = new double[10]; // реально создается экземпляр объекта
// System.Array и задается размер 10.
Или, объединяя эти инструкции, запишем:
double [] array = new double[10];
Отметим, что размер массива задается только вместе с созданием его экземпляра. Объявление ссылки использует просто квадратные скобки для указания, что размерность (ранг) массива будет единица. В C# ранг считается частью типа массива, в отличие от числа элементов.
Ближайший эквивалент в C++ приведенного выше определения будет выглядеть так:
double *pArray = new double[10];
Эта инструкция C++ действительно дает достаточно близкую аналогию, так как обе версии C++ и C# размещаются в куче. Отметим, что версия C++ является просто областью памяти, которая содержит 10 double
, в то время как версия C# создает экземпляр полноценного объекта. Более простая стековая версия C++:
doublе pArray[10];
не имеет аналога в C#, который использует реальный массив C#, хотя инструкция C# stackalloc
может создать эквивалент этой инструкции с помощью указателей. Об этом мы будем говорить позже в разделе, в котором рассматривается ненадежный код.
Массивы в C# можно явно инициализировать при создании экземпляра:
double [] Array = new double[10] {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 9.0, 10.0};
Существует также более короткая форма:
double [] Array = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 9.0, 10.0};
Если массив не инициализирован явно, то будет автоматически вызываться конструктор по умолчанию для каждого из его элементов. (Элементы массива формально рассматриваются как поля-члены класса.) Это поведение отличается от C++, где не допускается никакой автоматической инициализации массивов, размещенных с помощью оператора new
в куче (хотя C++ допускает это для массивов на основе стека).
Многомерные массивы
C# существенно отклонился от C++ в вопросе многомерных массивов, так как C# поддерживает как прямоугольные, так и неровные массивы.
Прямоугольный массив является правильной сеткой чисел. В C# это указывается синтаксисом, где запятая разделяет число элементов в каждой размерности. Например, двухмерный прямоугольный массив, можно определить следующим образом:
int [,] MyArray2d;
MyArray2d = new int[2, 3] {{1, 0}, {3, 6}, {9, 12}};
Синтаксис здесь является интуитивно понятным расширением синтаксиса одномерных массивов. Список инициализации в таком коде может отсутствовать. Например:
int [,,] MyArray3d = new int [2, 3, 2];
Это приведет к вызову конструктора по умолчанию для каждого элемента и к инициализации каждого int
нулем. В этом частном примере проиллюстрировано создание трехмерного массива. Общее число элементов в массиве равно 2×3×2 = 12. Характеристика прямоугольных массивов состоит в том, что все строки имеют одинаковое число элементов.
Элементы прямоугольного массива доступны с помощью следующего синтаксиса.
int X = MyArray3d[1, 2, 0] + MyArray2d[0, 1];
Прямоугольный массив C# не имеет прямых аналогов в C++. Однако неровные массивы в C# соответствуют достаточно точно многомерным массивам C++. Например, если объявить в C++ массив следующим образом:
int MyCppArray[3][5];
то реально объявляется не массив 3×5, а массив массивов — массив размера 3, каждый элемент которого является массивом размера 5. Это будет, возможно, понятнее, если сделать то же самое динамически. Запишем:
int pMyCppArray = new int[3];
for (int i=0; i<3; i++) pMyCppArray[i] = new int[5];