Массив как поле класса c
Перейти к содержимому

Массив как поле класса c

Выделение динамической памяти в конструкторе класса.
Деструктор класса, освобождение динамически выделенной памяти в деструкторе.

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

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

Оператор new выделяет запрошенное количество памяти и возвращает указатель на выделенную область памяти.
Оператор delete высвобождает память из под динамического объекта.
Оператор delete [] высвобождает память из под динамического массива.

Каких особых телодвижений требуют динамические поля класса?

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

Для реализации первого и второго пункта из представленного выше списка, необходимо в классе предусмотреть конструктор, в котором выполняется выделение требуемого объема динамической памяти и описать два обязательных поля: указатель на динамическую память и целочисленное поле для хранения кол-ва элементов динамического массива (поля).
Рассмотрим пример описания такого класса, класса SmartArray:

Динамическое поле необязательно должно быть типа char *, Вы вольны использовать любой другой тип.
Конструктор данного класса должен быть с параметром, это требуется по той причине, что при создании экземпляра, динамическому полю требуется знать сколько именно элементов ему хранить.
Конечно, есть возможность описать конструктор по умолчанию для данного класса, следующим образом:

В таком случае, динамическому полю присваивается значение nullptr (пустой указатель), а поле — размерность массива, обнуляется. Так как был описан конструктор по умолчанию, то для работы с экземплярами данного класса требуется предусмотреть метод для проверки динамического поля на пустоту, для этого и был реализован метод is_empty.

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

И как же перехватывать момент удаления экземпляра?

А для этого служит специальный метод — деструктор класса, он такой же особенный как и конструктор, его не нужно вызывать, он вызывается автоматом в момент удаления объекта и вызывается для конкретного экземпляра только один раз.

В данном методе описывается правильное высвобождение динамической памяти, если же все поля класс автоматические, то тело деструктора остаётся пустым.
Пример описания деструктора для нашего класса SmartArray:

Обратите внимание на синтаксис описания деструктора, у него не указывается тип возвращаемого параметра, имя деструктора состоит из символа тильда

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

Любой класс может содержать только один деструктор!
В самом деструкторе, как Вы можете наблюдать, содержится вызов метода delete[], который и выполняет высвобождение занятой памяти из под динамического поля.

Заметка
В описанном деструкторе класса можно было перед вызов оператора delete[] выполнить проверку: «а не является ли массив пустым?»:

Но этого не было сделано по причине того, что оператор delete[], вызванный для указателя равного nullptr, будет выполнен правильно, без формирования какой либо ошибки.

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

Обращение к элементам динамического поля можно реализовать через метод?

Можно, но для этого лучше воспользоваться специальным оператором [].
Как ранее было сказано, класс позволяет описать тип в котором содержится набор полей, методов и операторов, так что, нет ничего сверхъестественного в описание оператора [] для нашего класса.
Необходимо знать следующее:
— в классе можно описать (переопределить) только поддерживаемые в С++ операторы, своих напридумывать нельзя
— переопределение операторов (в отличии от реализации пользовательских методов) производится четко, в соответствии с правилами стандарта С++

Синтаксис переопределения оператора [] следующий:
тип_элемента_массива& opeator[] (int index)

Оператор является функцией-членом класса, он возвращает ссылку на объект (на элемент массива), тип которого соответствует типу элемента динамического массива (поля).
Оператор [] принимает только один аргумент, данный аргумент может быть любого типа (int, size_t и т.д.) и обязан определять индекс целевого элемента динамического поля (массива).
При описании данного оператора, в его теле можно реализовать проверку выхода заданного (в качестве аргумента) индекса за границы массива, как это сделано в следующем примере:

Как Вы можете заметить, в класс добавилось дополнительное поле err_value, это поле хранит значение, которое будет возвращаться оператором [] в случае выхода индекса за пределы массива.
Никто не запрещает убрать проверку выхода за границы массива из оператора [], для данного примера это будет даже лучше, так как избавляет нас от описания костылика в виде дополнительного поля err_value, но при этом, проверку придется выполнять во внешних функциях, в которых осуществляется обработка экземпляров данного класса.
Не забудьте в данном классе реализовать метод size(), который будет возвращать кол-во элементов динамического поля.

Может ли быть массив свойством класса?

Если может быть, то как на примере массива строк это все можно организовать?

user avatar

Малость добавлю от себя. Если вы пишете код в Visual Studio, то очень удобно пользоваться так называемыми снипетами. Для создания свойств существует несколько снипетов:

  • наберите prop и дважды нажмите на клавишу Tab у вас появится заготовка свойства public int MyProperty < get; set; >между int и MyProperty можно переключаться с помощью все той же Tab . Так вот, когда вы воспользуетесь этим снипетом, то курсор у вас будет стоять в позиции int сразу начинайте вводить List<string> после нажмите Tab и вы сможете дать нужное вам название свойству, чтобы закончить нажмите клавишу ввода.
  • propg даст public int MyProperty

user avatar

как и для всего остального

user avatar

из комментария @Vladislav Khapin

Всё ещё ищете ответ? Посмотрите другие вопросы с метками c# массивы или задайте свой вопрос.

Site design / logo © 2022 Stack Exchange Inc; user contributions licensed under cc by-sa. rev 2022.6.10.42345

Нажимая «Принять все файлы cookie», вы соглашаетесь, что Stack Exchange может хранить файлы cookie на вашем устройстве и раскрывать информацию в соответствии с нашей Политикой в отношении файлов cookie.

Массив как поле класса c

Здравствуйте, Аноним, Вы писали:

AG>>Кроме статического члена класса, массив-член класс не может иметь инициализатор через фигурные скобки.
А>В какой книге это написанно?
Вот:

Поля-массивы в классе
В С++ нет запрета на объявление в классе поля-массива. Естественно, размер класса с полем-массивом увеличивается на размер массива. Однако использование массива в классе недостаточно хорошо отражено в стандарте. Это вызывает многочисленные вопросы, особенно связанные с инициализацией. Мы начнем разбираться с этими вопросами на самом простом примере, в котором объявляются и инициализируются несколько полей-массивов (листинг 4.1).
Листинг 4.1. Поля-массивы в классе

В классе Arrays объявлено три поля-массива: m0, m1 и m2. Как видим, задать количество элементов массива можно либо явной константой, либо константным выражением со статическими и (или) перечислимыми константами, причем константы должны быть определены раньше массива. Задавать количество элементов поля-массива обязательно.
Нельзя задать количество элементов как значение другого поля, заполняемого конструктором, даже если это поле константное. Например, пусть в классе объявлены следующие поля:

В этом случае система Visual C++.NET 2003 выдает ошибку компиляции C2327, которая сигнализирует о том, что k в данном случае не является ни статической константой, ни константой перечислимого типа.
Конструктор без аргументов Array() обнуляет массивы, выполняя цикл в теле. Такой способ инициализации поля-массива — в теле конструктора — является наиболее простым и позволяет присваивать элементам массива любые значения. Однако наиболее часто выполняется обнуление, поэтому для массивов разрешается применять инициализацию нулем. Тогда наш конструктор выглядит значительно проще:

Для массива объектов некоторого типа такая запись означает вызов конструктора по умолчанию (без аргументов), и не забудьте, что этот вызов выполняется для каждого элемента массива.
К сожалению, применение списков инициализации для полей-массивов этим и ограничивается — в скобках нельзя ничего указывать. Например, при написании следующей конструкции система Visual C++.NET 2003 выдаст сообщение об ошибке С2536, сигнализируя о том, что явная инициализация массивов запрещена:

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

После этого попытаемся в списке инициализации конструктора присвоить этому массиву символьную константу:

Тогда в системе Visual C++.NET 2003 мы получим сообщение об ошибке:
error C2536: ‘Arrays::Arrays::s’ :
cannot specify explicit initializer for arrays

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

Затем можно инициализировать указатель символьной константой в списке инициализации конструктора, как было показано ранее.
Как это ни странно, но большие проблемы возникают при попытках объявить в классе константный массив встроенного типа! Как мы уже выяснили, константы нельзя инициализировать в теле конструктора, значения им присваиваются только в списке инициализации конструктора. Однако для константного массива встроенного типа не работает даже инициализация нулем.

ПРИМЕЧАНИЕ
Этот вопрос практически не отражен в стандарте, поэтому компиляторы ведут себя по-разному. В системе Visual C++.NET 2003 выдается ошибка компиляции C2439, а Borland C++ Builder 6 выдает только предупреждение W8038 о том, что массив не инициализируется.

Не проходит и отмена константности. Например, зададим массив m0 как константный, а в теле конструктора определим инициализацию в цикле:

Однако и Visual C++.NET 2003, и Borland C++ Builder 6 отказываются компилировать такой цикл.
Но для константного массива из объектов не встроенного типа задавать инициализацию нулем разрешается. Для этого в классе должен быть определен конструктор без аргументов, который вызывается для инициализации каждого элемента константного поля-массива. Например, вполне можно инициализировать константный массив денег:
const TMoney ss[10];
Для этого достаточно задать в списке инициализации конструктора инициализацию нулем ss(). Как реально инициализируется такой массив, конечно, зависит от реализации конструктора по умолчанию.

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

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

Во-вторых, и это гораздо важнее, неконстантные поля-массивы существенно увеличивают размер класса — на 118 байт. Пока объектов немного, это не доставляет беспокойства. Но представьте, что нам надо объявить массив размером в 1000 элементов типа TString. Ситуация отнюдь не надуманная, так как подобный массив, например, в качестве буфера может использовать простой текстовый редактор. Но такой массив имеет уже 118 000 лишних байтов, которые еще и заполняются во время создания — ведь конструктор вызывается для каждого элемента массива. Ситуация совершенно неприемлемая как с точки зрения расхода памяти, так и с точки зрения эффективного выполнения программы.

Так как массивы у нас символьные, то можно было бы обойтись указателями на символ — тем более, что их можно инициализировать символьной константой. Ситуация с памятью несколько улучшается: два указателя занимают в классе всего 8 байт. Однако указатели тоже будут инициализироваться для каждого создаваемого объекта.

Массивы нужно все-таки иметь в единственном экземпляре; они, очевидно, должны быть объявлены константными и проинициализированы один раз при объявлении. Мы можем это сделать, если объявим их глобальными. Однако такое решение совершенно неприемлемо с точки зрения инкапсуляции — глобальные переменные излишне доступны и их использование способствует появлению трудноуловимых ошибок. Массивы по сути своей являются (и должны быть) частью класса TString.

В языке С++ есть средство, позволяющее сделать именно то, что нам требуется: объявить массивы в единственном экземпляре, сделать их константными, проинициализировать при объявлении и, тем не менее, локализовать их в классе. Такие константные поля-массивы надо объявить в классе статическими (см. п.п. 9.4.2 в [1]):

Инициализация (определение) статических полей выполняется вне класса:

Этот оператор присваивания является определением статического члена класса (а в классе — только объявление). Такой оператор присваивания — единственная форма инициализации константных статических массивов (и любых других константных статических полей встроенного нецелочисленного типа). Обратите внимание, что слово static в операторе инициализации отсутствует — его там писать нельзя. Статические массивы, как и статические константы (и вообще любые статические поля), являются частью класса, создаются в единственном экземпляре при запуске программы (до создания любых объектов класса), поэтому их нельзя инициализировать в конструкторе.

ПРИМЕЧАНИЕ
Помимо статических полей, в классе можно объявлять и статические методы. Такие методы являются методами класса и могут вызываться до создания первого объекта этого класса.

Cтатические константы места в классе не занимают. Точно так же не увеличивают размер класса и любые статические поля — они хранятся вне класса. Но принцип инкапсуляции выполняется — доступ к таким полям имеют только методы и «друзья» класса. Наши методы перевода регистра упрощаются (листинг 4.20).
Листинг 4.20. Реализация методов перевода регистра

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *