Массив объектов с параметрами
массив объектов с несколькими параметрами, используя указатель
Как мне переключить оба параметра класса на объект, который я создаю с указателем? Какой синтаксис? (Они должны получать все одинаковые параметры)
В нормальной ситуации я использую это:
Но … в этом случае какой синтаксис? :
Как я могу передать два параметра?
Решение
Нет синтаксиса для инициализации списка динамически распределенного массива.
1) не вариант, если класс не является конструируемым по умолчанию. 2) немного сложнее, но вы не должны делать это сами, так как std::vector уже реализовал это для вас. В любом случае вы должны использовать vector, так как он заботится об управлении памятью.
Пример с вектором, который аналогичен вашей «нормальной ситуации». Обратите внимание, что ваша «нормальная ситуация» не соответствует описанию в вашем вопросе:
Пример с вектором, который соответствует вашему описанию передачи одного и того же аргумента всем объектам в массиве. Хотя технически это копирует конструирование элементов массива:
Другие решения
Я предполагаю, что ваш вопрос: «Как я могу инициализировать все объекты в массиве с точно таким же вызовом конструктора?»
Если это так, то самый простой способ сделать это — использовать std::vector конструктор (2) :
Ваш пример становится:
Если вы действительно хотите использовать массив в стиле C, вам придется вручную вызывать конструкторы, используя размещение new и вручную вызывать деструкторы, если вы хотите избежать ненужных конструктор по умолчанию звонки. Пример:
Обратите внимание, что приведенный выше код может быть (и должен быть) преобразован в безопасную абстракцию.
Передача массивов в качестве аргументов (руководство по программированию на C#)
Массивы можно передавать в качестве аргументов в параметры метода. Поскольку массивы представляют собой ссылочные типы, метод может изменять значения элементов.
Передача одномерных массивов в качестве аргументов
Инициализированный одномерный массив можно передать в метод. Например, следующий оператор передает массив в метод печати.
В следующем примере кода показана разделяемая реализация метода печати.
Новый массив можно инициализировать и передать за один шаг, как показано в следующем примере.
Пример
В следующем примере массив строк инициализируется и передается в качестве аргумента в метод DisplayArray для строк. Этот метод отображает элементы массива. Затем метод ChangeArray размещает элементы массива в обратном порядке, а метод ChangeArrayElements изменяет первые три элемента массива. После возврата каждого метода метод DisplayArray показывает, что передача массива по значению не препятствует изменению элементов массива.
Передача многомерных массивов в качестве аргументов
Инициализированный многомерный массив можно передать в метод так же, как и одномерный массив.
В следующем коде показано разделяемое объявление метода печати, который принимает в качестве аргумента двухмерный массив.
Новый массив можно инициализировать и передать за один шаг, как показано в следующем примере.
Пример
Массивы
Ярким примером ссылочного типа данных являются массивы (как объекты!).
Массив представляет собой совокупность переменных одного типа с общим для обращения к ним именем.
В C# массивы могут быть как одномерными, так и многомерными.
Массивы служат самым разным целям, поскольку они предоставляют удобные средства для объединения связанных вместе переменных.
Массивами в C# можно пользоваться практически так же, как и в других языках программирования.
Тем не менее, у них имеется одна особенность: они реализованы в виде объектов. Смотрите также заметку «Массивы. Класс System.Array».
Объединение данных возможно и в коллекции, об этом — в статье Класс ArrayList. Пример необобщенной коллекции
Объявление массивов
Для того чтобы воспользоваться массивом в программе, требуется двухэтапная процедура. Во-первых, необходимо объявить переменную, которая может обращаться к массиву. И во-вторых, нужно создать экземпляр массива (объект), используя оператор new.
Важно! Если массив только объявляется, но явно не инициализируется, каждый его элемент будет установлен в значение, принятое по умолчанию для соответствующего типа данных (например, элементы массива типа bool будут устанавливаться в false, а элементы массива типа int — в 0). В примере, если мы удалим строки с инициализацией, будет напечатано пять нулей.
Примечание. Такие же действия с полями экземпляра структуры выполняет конструктор по умолчанию (без параметров).
Доступ к элементам массива
Инициализация массива
Помимо заполнения массива элемент за элементом (как показано в предыдущем примере), можно также заполнять его с использованием специального синтаксиса инициализации массивов.
Для этого необходимо перечислить включаемые в массив элементы в фигурных скобках < >. Такой синтаксис удобен при создании массива известного размера, когда нужно быстро задать его начальные значения:
1) инициализация массива с использованием ключевого слова new:
int[] m1 = new int[] <10,20,30,40,50>;
2) инициализации строкового массива без использования слова new:
string[] m2 = < «Фамилия», «Имя», «Отчество» >;
3) используем ключевое слово new и желаемый размер массива символов:
char[] m3 = new char[4] < ‘Я’,’з’,’ы’,’к’ >;
Обратите внимание, что в случае применения синтаксиса с фигурными скобками размер массива указывать не требуется (как видно на примере создания переменной m1), поскольку этот размер автоматически вычисляется на основе количества элементов внутри фигурных скобок.
Кроме того, применять ключевое слово new не обязательно (как при создании массива m2).
Неявно типизированные массивы
Ключевое слово var позволяет определить переменную так, чтобы лежащий в ее основе тип выводился компилятором. Аналогичным образом можно также определять неявно типизированные локальные массивы. С использованием такого подхода можно определить новую переменную массива без указания типа элементов, содержащихся в массиве.
Результат:
Разумеется, как и при создании массива с использованием явного синтаксиса C#, элементы, указываемые в списке инициализации массива, должны обязательно иметь один и тот же базовый тип (т.е. должны все быть int, string или char). Обратите внимание на метод GetType(), позволяющий программным путем определять тип элементов массива.
Определение массива объектов
В большинстве случаев при определении массива тип элемента, содержащегося в массиве, указывается явно.
В результате получается, что в случае определения массива объектов находящиеся внутри него элементы могут представлять собой что угодно.
Если обратимся к определению массива, данному выше: «Массив представляет собой совокупность переменных одного типа с общим для обращения к ним именем», то это выглядит несколько противоречиво. Но тем не менее, все это возможно потому, что каждый элемент является объектом. Приведем пример:
Обратите внимание на четвертый тип цикла foreach (object me in arrByObject). Легко запомнить: Для каждого (for each) объекта с именем me, входящего в (in) массив arrByObject (учите английский!). На печать выводится как сам объект (элемент массива объектов), так и тип этого объекта (метод GetType(), присущий всем объектам класса Object, от которого наследуются все типы).
Свойство Length
Реализация в C# массивов в виде объектов дает целый ряд преимуществ. Одно из них заключается в том, что с каждым массивом связано свойство Length, содержащее число элементов, из которых может состоять массив. Следовательно, у каждого массива имеется специальное свойство, позволяющее определить его длину.
Когда запрашивается длина многомерного массива, то возвращается общее число элементов, из которых может состоять массив. Благодаря наличию у массивов свойства Length операции с массивами во многих алгоритмах становятся более простыми, а значит, и более надежными.
Вставим в предыдущем примере перед Console.ReadKey() оператор
Console.WriteLine(arrByObject.Length);
Будет напечатано значение, равное 4 (число объектов в массиве). Чаще всего оно используется для задания числа элементов массива в цикле for<>.
Многомерные массивы
Многомерным называется такой массив, который отличается двумя или более измерениями, причем доступ к каждому элементу такого массива осуществляется с помощью определенной комбинации двух или более индексов. Многомерный массив индексируется двумя и более целыми числами.
Двумерные массивы. Простейшей формой многомерного массива является двумерный массив. Местоположение любого элемента в двумерном массиве обозначается двумя индексами. Такой массив можно представить в виде таблицы, на строки которой указывает первый индекс, а на столбцы — второй. Пример объявления и инициализации двумерного массива показан ниже:
Обратите особое внимание на способ объявления двумерного массива. Схематическое представление массива myArr[,] показано ниже:
Заметим, что в программе используется еще один объект – ran, принадлежащий к классу Random, метод которого (функция Next() ) возвращает целое число в заданном диапазоне (1,15).
В C# допускаются массивы трех и более измерений. Ниже приведена общая форма объявления многомерного массива:
тип[,…,] имя_массива = new тип[размер1, размер2, … размеры];
Инициализация многомерных массивов
Для инициализации многомерного массива достаточно заключить в фигурные скобки список инициализаторов каждого его размера:
тип[,] имя_массива = <
< val, val, val, …, val>,
>;
где val обозначает инициализирующее значение, а каждый внутренний блок — отдельный ряд.
Первое значение в каждом ряду сохраняется на первой позиции в массиве, второе значение — на второй позиции и т.д.
Обратите внимание на то, что блоки инициализаторов разделяются запятыми, а после завершающей эти блоки закрывающей фигурной скобки ставится точка с запятой.
Ниже в качестве примера приведена общая форма инициализации двумерного массива (4 строки, 2 столбца):
Перейдем к рассмотрению примеров решения задач, где применяются массивы и циклы.
Задача «Три цикла»
Требуется найти сумму и произведение N элементов массива, используя три варианта циклов (for, while, do-while).
Решение. В классе Program объявим статический массив действительных чисел a[1000] и 7 методов (кроме Main()), ввод исходных данных и вычисление сумм и произведений с использованием трех типов циклов.
Тогда наша программа может быть написана так:
Результат:
Задание. Сравните алгоритмы вычисления суммы и произведения и циклы между собой, найдите общее и различия.
Оператор foreach
Последний пример иллюстрирует применение оператора foreach к массивам. Если есть необходимость выполнить некоторые действия со всеми элементами массивов, то этот оператор цикла будет самым кратким.
Задачи, предполагающие использование массивов и циклов, подробно рассмотрены в разделе «Примеры решения задач на тему «Массивы строки»»
Перейдем к рассмотрению объектов, относящихся к классу String (строка).
Массивы в C++
Продолжаем серию «C++, копаем вглубь». Цель этой серии — рассказать максимально подробно о разных особенностях языка, возможно довольно специальных. Это четвертая статья из серии, первые три, посвященные перегрузке в C++, находятся здесь, здесь и здесь.
Эта статья посвящена массивам. Массивы можно отнести к наиболее древним слоям C++, они пришли из первых версий C. Тем не менее, массивы вошли в объектно-ориентированную систему типов C++, хотя и с определенными оговорками. Программисту важно знать об этих особенностях, чтобы избежать потенциальных ошибок. В статье также рассмотрено другое наследие C – тривиальные типы и неинициализированные переменные. Часть нововведений C++11, С++14, С++17 затрагивают работу с массивами, все эти новые возможности также подробно описаны. Итак, попробуем рассказать о массивах все.
Оглавление
1. Общие положения
Массив является простейшим агрегатным типом. Он моделирует набор однотипных элементов, расположенных подряд в непрерывном отрезке памяти. Массивы в той или иной форме поддерживаются практически всеми языками программирования и неудивительно, что они появились в первых версиях C и затем стали частью C++.
1.1. Объявление массивов
Если T некоторый тип, N константа или выражение, вычисляемое во время компиляции, то инструкция
Такие массивы еще называют встроенными массивами (regular arrays), чтобы подчеркнуть отличие от других вариантов массивов, термин «массив» используется в программировании и в том числе в C++ достаточно широко.
Вот примеры правильных объявлений массивов:
А вот примеры некорректных объявлений массивов:
Выход за границы массива не контролируется, ошибка может привести к неопределенному поведению.
В одной инструкции можно объявить несколько массивов, но размер должен быть указан для каждого.
Для типов массивов можно вводить псевдонимы. Можно использовать традиционный вариант с ключевым словом typedef :
или более современный (C++11) с ключевым словом using :
После этого массивы объявляются как простые переменные:
Это будет то же самое, что
1.2. Операторы и стандартные функции для работы с массивами
Для работы с массивами можно использовать оператор sizeof и несколько стандартных функций и макросов.
Оператор sizeof возвращает полный размер массива в байтах, то есть размер элемента умноженный на размер массива.
А также в стандартных алгоритмах:
1.3. Размещение в памяти
Если массив объявлен статически, то есть в глобальной области видимости, в области видимости пространства имен или в качестве статического члена класса, то он размещается в статической памяти. Массивам, объявленным локально, память выделяется на стеке. (Естественно, надо учитывать ограниченный размер стека при выборе размера локальных массивов.) Нестатические члены класса размещаются в границах экземпляра класса. Динамические массивы (см. раздел 6) размещаются в динамической памяти.
1.4. Ограничения на типы элементов массивов
Нельзя объявить массив ссылок.
Вместо этого можно использовать массив константных указателей.
(Синтаксис инициализации массивов будет обсуждаться в разделе 3.2.)
Нельзя объявить массив функций.
Вместо этого можно использовать массив указателей на функцию.
Квалификатор const не применим к типу массива, а только к типам его элементов.
2. Сведение и копирование массивов
В данном разделе рассматриваются особенности массивов, которые выделяют их из общей системы типов C++.
2.1. Сведение
Конечно, тесную связь массивов и указателей отрицать нельзя. Вот стандартный (в стиле C) способ обработать все элементы массива:
Но все же сведение можно отнести к сишным архаизмам и с ним надо быть внимательным и аккуратным, иначе можно столкнуться с не самыми приятными неожиданностями.
Вот как сведение влияет на объявления функций. Функции
не являются перегруженными функциями — это одно и то же. Размер надо передавать дополнительным параметром или использовать специальное соглашение для определения размера (например, завершающий ноль для строк).
При внешнем связывании массива также происходит сведение.
Для размера также надо использовать дополнительную переменную или использовать специальное соглашение для определения размера.
При объявлении переменной с помощью ключевого слова auto также происходит сведение.
При конкретизации шаблона функции
тип параметра шаблонной функции также будет выведен как указатель, если аргумент является массивом.
Сведение вызывает дополнительные проблемы при использовании наследования. (В C ведь нет наследования.) Рассмотрим пример.
Следующий код компилируется без ошибок и предупреждений.
2.2. Копирование
Наряду со сведением (и тесно связанная с ним) есть еще одна особенность типа массива, которая делает его в некотором смысле «неполноценным». Массивы не поддерживают привычный синтаксис инициализации и присваивания, основанный на семантике копирования:
Также функция не может возвращать массив.
Но если массив является членом класса/структуры/объединения, то проблемы с копированием (а также сведение) отсутствуют.
Для этой структуры компилятор сгенерирует копирующий конструктор по умолчанию и соответствующий оператор присваивания, которые без проблем скопируют массив.
3. Инициализация массивов
Для описания правил инициализации массивов необходимо кратко рассказать о тривиальных типах.
3.1. Тривиальные типы и неинициализированные переменные
Конструкторы и деструкторы можно назвать ключевыми элементами объектной модели С++. При создании объекта обязательно вызывается конструктор, а при удалении — деструктор. Но проблемы совместимости с С вынудили сделать некоторое исключение, и это исключение называется тривиальные типы. Они введены для моделирования сишных типов и сишного жизненного цикла переменных, без обязательного вызова конструктора и деструктора. Сишный код, если он компилируется и выполняется в С++, должен работать так же как в С. К тривиальным типам относятся числовые типы, указатели, перечисления, а также классы, структуры, объединения и массивы, состоящие из тривиальных типов. Классы и структуры должны удовлетворять некоторым дополнительным условиям: отсутствие пользовательского конструктора, деструктора, копирования, присваивания, виртуальных функций.
Переменная тривиального типа будет неинициализированной, если не использовать какой-нибудь вариант явной инициализации. Для тривиального класса компилятор может сгенерировать конструктор по умолчанию и деструктор. Конструктор по умолчанию обнуляет объект, деструктор ничего не делает. Но этот конструктор будет сгенерирован и использован только, если использовать какой-нибудь вариант явной инициализации, иначе переменная останется неинициализированной.
Неинициализированная переменная устроена следующим образом: если она объявлена в области видимости пространства имен (глобально), будет иметь все биты нулевыми, если локально, или создана динамически, то получит случайный набор битов. Понятно, что использование такой переменной может привести к непредсказуемому поведению программы. Массивы достаточно часто имеют тривиальный тип и поэтому эта проблема для них весьма актуальна.
Неинициализированные константы тривиального типа выявляет компилятор, иногда он выявляет и другие неинициализированные переменные, но с этой задачей лучше справляются статические анализаторы кода.
3.2. Синтаксис инициализации массивов
3.2.1. Общие положения
Если не использовать явную инициализацию, то для массивов нетривиального типа гарантируется вызов конструктора по умолчанию для каждого элемента. Естественно, что в этом случае такой конструктор должен быть, иначе возникает ошибка. Но для массивов тривиального типа или, если конструктор по умолчанию отсутствует или не устраивает, необходимо использовать явную инициализацию.
Со времен C массивы можно было инициализировать с помощью синтаксиса агрегатной инициализации:
В С++11 появилась универсальная инициализация (uniform initialization) и теперь можно инициализировать так:
Для универсальной инициализации также можно использовать =, и различать эти два типа инициализации не всегда просто, а, скорее всего, не очень нужно.
Размер массива можно не указывать, тогда он определится по числу инициализаторов.
Если размер массива указан, то число инициализаторов не должно быть больше размера массива. Если размер массива больше числа инициализаторов, то для оставшихся элементов гарантируется вызов конструктора по умолчанию (который, естественно, должен быть), в том числе и для тривиальных типов. Таким образам, указав пустой список инициализации, мы гарантируем вызов конструктора по умолчанию для всех элементов массива тривиального типа.
Массивы констант тривиального типа требуют обязательного списка инициализации.
Число инициализаторов может быть меньше размера массива, в этом случае оставшиеся элементы инициализируются конструктором по умолчанию.
Символьные массивы можно инициализировать строковым литералом.
Размер такого массива будет на единицу больше числа символов строки, нужно хранить завершающий нулевой символ.
3.2.2. Инициализация членов класса
В С++11 появилась возможность инициализировать массивы, являющиеся нестатическими членами класса. Это можно сделать двумя способами: непосредственно при объявлении или в списке инициализации членов при определении конструктора.
Правда в этом случае надо всегда явно задавать размер массива, неявное определение размера через список инициализации не разрешается.
Статические массивы, как и ранее, можно инициализировать только при определении, размер массива может быть определен через список инициализации.
3.2.3. Требования к инициализаторам
Выражения, стоящие в списке инициализации, вычисляются непосредственно перед инициализацией, они не обязаны быть известными на стадии компиляции (конечно, за исключением массивов, объявленных как constexpr ). Требования к элементам списка инициализации такие же как и к аргументу функции, имеющей параметр того же типа, что и элемент массива — должно существовать неявное преобразование от типа элемента списка инициализации к типу элемента массива. Пусть у нас есть объявление массива:
Наличие нужного преобразования эквивалентно корректности инструкции
Элемент списка инициализации может быть сам списком инициализации. В этом случае корректность этой инструкции также гарантирует корректную инициализацию элемента массива.
Этот пример также демонстрирует как с помощью списка инициализации мы можем создать массив для типа у которого нет конструктора по умолчанию. Но в этом случае число инициализаторов должно совпадать с размером массива.
4. Указатели и ссылки на массивы
4.1. Указатели на массивы
Пусть у нас объявлен массив
Указатель на этот массив объявляется и инициализируется следующим образом:
Указатель на массив — это не указатель на первый элемент (хотя побитово они, конечно, совпадают), здесь нет никакого сведения. Это полноценный тип, который «знает» размер массива. Поэтому при инициализации размеры должны совпадать.
При инкременте указатель на массив увеличивается на размер всего массива, а не на размер элемента.
Для доступа к элементу массива через указатель надо использовать оператор * и индексатор.
При использовании псевдонимов можно получить более привычный синтаксис объявления указателя на массив.
Понимание указателей на массивы необходимо для правильной работы с многомерными массивами, которые подробно будут рассмотрены далее.
4.2. Ссылки на массивы
Пусть у нас объявлен массив
Ссылка на этот массив объявляется и инициализируется следующим образом:
Также ссылку на массив можно инициализировать разыменованным указателем на массив.
Как и указатель, ссылка «знает» размер массива. Поэтому при инициализации размеры должны совпадать.
Доступ к элементу массива через ссылку осуществляется так же, как и через идентификатор массива.
Ссылки на массивы как раз и являются теми средствами, с помощью которых можно обойти сведение.
При использовании псевдонимов можно получить более привычный синтаксис объявления ссылки на массив.
При конкретизации шаблона функции
тип параметра шаблонной функции также будет выведен как ссылка на массив, если аргумент является массивом.
Особенно удобно использовать шаблоны с выводом типа и размера массива.
5. Многомерные массивы
Если T некоторый тип, N и M выражения, допустимые для определения размера массива, то инструкция
Сведение преобразует массив к указателю на элемент. Для двумерного массива этот элемент сам является массивом, а значит двумерный массив сводится к указателю на массив.
Таким образом, при передаче двумерного массива в функцию следующие варианты объявления соответствующего параметра эквивалентны:
Это означает, что внешний размер двумерного массива теряется и его надо передавать отдельным параметром.
При использовании псевдонимов можно получить более лаконичный синтаксис объявления двумерных массивов.
Это то же самое, что
Двумерные массивы инициализируются следующим образом:
Можно получить указатель на двумерный массив:
Также можно получить ссылку. Вот пример использования ссылки на двумерный массив.
Двумерный массив хорошо согласуется с математическими матрицами. В объявлении
6. Динамические массивы
В C++ отсутствует тип «динамический массив». Имеются только операторы для создания и удаления динамического массива, доступ к нему осуществляется через указатели на начало массива (своего рода полное сведение). Размер такого массива надо хранить отдельно. Динамические массивы желательно инкапсулировать в C++ классы.
6.1. Создание и удаление динамического массива
Если T некоторый тип, n переменная, значение которой может определяются в процессе выполнения программы, то инструкция
Если тип T тривиальный, то элементы будут иметь случайное значение, в противном случае для инициализации элементов будет использован конструктор по умолчанию.
В C++11 появилась возможность использовать список инициализации.
Если число инициализаторов больше размера массива, то лишние не используются (компилятор может выдать ошибку, если значение n известно на стадии компиляции). Если размер массива больше числа инициализаторов, то для оставшихся элементов гарантируется вызов конструктора по умолчанию, в том числе и для тривиальных типов. Таким образам, указав пустой список инициализации, мы гарантируем вызов конструктора по умолчанию для всех элементов массива тривиального типа.
При этом, если при создании массива использовался конструктор, то для всех элементов массива вызывается деструктор в порядке, обратном вызову конструктора (деструктор не должен выбрасывать исключений), затем выделенная память освобождается.
6.2. Динамические массивы и интеллектуальные указатели
В C++14 появилась возможность создать динамический массив и инициализировать им экземпляр std::unique_ptr<> с помощью std::make_unique<> :
При этом гарантируется инициализация элементов массива по умолчанию, в том числе и для тривиальных типов.
Интеллектуальный указатель std::shared_ptr<> стал поддерживать такую специализацию только в C++17, а использование std::make_shared<> для этой специализации появилось только в C++20.
6.3. Многомерные динамические массивы
При использовании псевдонимов можно получить более лаконичный синтаксис.
Используя перегрузку оператора [] легко создать класс, который хранит данные в одномерном массиве, но при этом предоставляет интерфейс многомерного массива. Вот пример предельно упрощенного класса матрицы.
Вот пример использования:
7. Использование массивов в шаблонах
Тип массива можно использовать в качестве шаблонных аргументов и для специализации шаблонов классов.
В стандартной библиотеке частичная специализация интеллектуального указателя std::unique_ptr<> и std::shared_ptr<> для массивов используется для управления жизненным циклом динамического массива, подробнее см. раздел 6.2.
В качестве реального примера использования этих свойст типов приведем немного упрощенное определение перегруженного варианта шаблона функции std::make_unique<> для массивов (см. раздел 6.2):
8. Стандартные альтернативы массивам
Стандартная библиотека предоставляет несколько классов (точнее шаблонов классов), которые рекомендуется использовать вместо массивов.
Этот шаблон поддерживает индексатор и традиционный интерфейс стандартного контейнера.
Список литературы
[Josuttis]
Джосаттис, Николаи М. Стандартная библиотека C++: справочное руководство, 2-е изд.: Пер. с англ. — М.: ООО «И.Д. Вильямс», 2014.