C конструктор по умолчанию структура

Обновлено: 07.05.2024

I have been trying to solve this problem but I am not getting the syntax.

16 Answers 16

In C++ the only difference between a class and a struct is that members and base classes are private by default in classes, whereas they are public by default in structs.

So structs can have constructors, and the syntax is the same as for classes.

@sth Your right on the difference between struct and class, however I think he's having a compile issue. The issue might be because of a union that is using the struct. You can't have non-trivial constructors in the type you have in a union.

@Chap: If he has concrete problems where the general solution doesn't work, it would probably be the best idea to post some code that shows the problem and the compiler errors that are generated. But as general as the question is asked I don't think one can really infer too much about the concrete problem the OP is trying to solve.

@GMan: Right idea, wrong wording. A struct inherits its base classes publicly by default; there is no change to classes deriving from the struct .

@BenVoigt: Whoa. How'd you find this old comment. :) Yeesh wish I could edit it. even I'm confused at what I wrote. I think I omitted the word "bases" from the end but even that sucks.

@user152949: No one said it would. We could all comment on all code excerpts saying 'This won't work if [some totally different scenario]', but what's the point?

@varungupta It's the body of the constructor function. There's no code we want to run in the constructor in this case, so it's empty.

All the above answers technically answer the asker's question, but just thought I'd point out a case where you might encounter problems.

If you declare your struct like this:

You will have problems trying to declare a constructor. This is of course because you haven't actually declared a struct named "foo", you've created an anonymous struct and assigned it the alias "foo". This also means you will not be able to use "foo" with a scoping operator in a cpp file:

To fix this, you must either do this:

Where the latter creates a struct called "foo" and gives it the alias "foo" so you don't have to use the struct keyword when referencing it.

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

Вот простой пример, как вы ответите на следующий вопрос: сколько значимых типов из .NET Framework содержит конструкторы по умолчанию? Интуитивным ответом кажется "все", и будете не правы, поскольку на самом деле, ни один из значимых типов .NET Framework не содержит конструктора по умолчанию.


Но давайте обо всем по порядку и начнем со сравнения таких понятий, как конструктор по умолчанию (default constructor) и значения по умолчанию (default values).

В данном случае для обоих строк кода компилятор сгенерирует одну и ту же инструкцию iniobj, которая приведет к идентичному результату в обоих случаях – обнулению всех полей структуры Size.

Аналогичное смешивание понятий существует не только при вызове оператора new, но и при инициализации полей структуры в конструкторе. Так, для конструкторов структур применяются те же правила обязательной инициализации всех полей структуры, аналогичные правилам для локальных переменных (definite assignment rules). Это означает, что до завершения тела конструктора все поля структуры должны быть явно или неявно проинициализированы:

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

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

Оба типа Size и CustomValueType являются значимыми типами, при этом тип CustomValueType содержит конструктор по умолчанию (мы рассмотрим позднее, как этого добиться). В строке 2 происходит инициализация переменной size значениями по умолчанию, с помощью инструкции initobj, а в строке 3 происходит вызов конструктора типа CustomValueType с помощью инструкции call CustomValueType..ctor.

Создание структуры с конструктором по умолчанию

Для генерации структуры воспользуемся модулем System.Reflection.Emit, который поддерживает весь необходимый функционал. Процесс создания нового типа начинается создания объекта AssemblyBuilder, внутри которого создается экземпляр DynamicModuleBuilder, в котором уже создается тип. Такой порядок объясняется тем, что сборка, на самом деле, содержит лишь метаданные сборки (зависимости и т.п.) и модули, которые, в свою очередь уже содержат пользовательские типы.

Метод GenerateValueTypeWithDefaultConstructor принимает имя типа и строку, выводимую на консоль (строка нужна для написания модульного теста, проверяющего работу этого метода). После этого, мы генерируем простой значимый тип с конструктором, вызывающим Console.WriteLine с указанной строкой.
После этого мы можем создать данный тип с помощью Activator.CreateInstance или же сохранить сгенерированную сборку с помощью AssemblyBuilder.Save и использовать ее обычным образом. После этого, можно будет пользоваться сгенерированным типом обычным образом и спокойно вызвать конструктор по умолчанию значимого типа:

Правила вызова конструктора по умолчанию

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

Для создания массива используется инструкция newarr, при этом происходит инициализация всех элементов массива значениями по умолчанию, что в случае значимых типов означает «обнуление» всех полей всех экземпляров массива. Даже если используемый тип содержит настоящий конструктор по умолчанию (как в нашем случае), в интересах производительности они все равно вызваны не будут.

Для вызова конструктора всех элементов нужно сделать это явно:

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


Хочу обратить внимание на рассогласованность поведения в следующем случае: известно, что создания экземпляра обобщенного (generic) параметра происходит с помощью Activator.CreateInstance, именно поэтому при возникновении исключения в конструкторе пользователь обобщенного метода получит его не в чистом виде, а в виде TargetInvocationException. Однако при создании экземпляра типа CustomValueType с помощью Activator.CreateInstance наш конструктор по умолчанию будет вызван, а при вызове метода CreateWithNew и создания экземпляра значимого типа с помощью new T() – нет.

Ранее для определения классов мы использовали ключевое слово class . Однако C++ предоставляет еще один способ для определения пользовательских типов, который заключается в использовании структур. Данный способ был унаследован языком С++ еще от языка Си.

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

Для определения структуры применяется ключевое слово struct , а сам формат определения выглядит следующим образом:

Имя_структуры представляет произвольный идентификатор, к которому применяются те же правила, что и при наименовании переменных.

После имени структуры в фигурных скобках помещаются Компоненты_структуры , которые представляют набор описаний объектов и функций, которые составляют структуру.

Например, определим простейшую структуру:

Здесь определена структура person , которая имеет два элемента: age (представляет тип int) и name (представляет тип string).

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

По сути структура похожа на класс, то есть с помощью структур также можно определять сущности для использования в программе. В то же время все члены структуры, для которых не используется спецификатор доступа (public, private), по умолчанию являются открытыми (public). Тогда как в классе все его члены, для которых не указан спецификатор доступа, являются закрытыми (private).

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

Инициализация структур аналогична инициализации массивов: в фигурных скобках передаются значения для элементов структуры по порядку. Так как в структуре person первым определено свойство, которое представляет тип int - число, то в фигурных скобках вначале идет число. И так далее для всех элементов структуры по порядку.

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

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

И в плане конечного результата программы мы не увидели бы никакой разницы.

Когда использовать структуры? Как правило, структуры используются для описания таких данных, которые имеют только набор публичных атрибутов - открытых переменных. Например, как та же структура person, которая была определена в начале статьи. Иногда подобные сущности еще называют аггрегатными классами (aggregate classes).

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

На уровне кода конструктор представляет метод, который называется по имени класса, который может иметь параметры, но для него не надо определять возвращаемый тип. Например, определим в классе Person простейший конструктор:

Конструкторы могут иметь модификаторы, которые указываются перед именем конструктора. Так, в данном случае, чтобы конструктор был доступен вне класса Person, он определен с модификатором public .

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

В данном случае выражение Person() как раз представляет вызов определенного в классе конструктора (это больше не автоматический конструктор по умолчанию, которого у класса теперь нет). Соответственно при его выполнении на консоли будет выводиться строка "Создание объекта Person"

Подобным образом мы можем определять и другие конструкторы в классе. Например, изменим класс Person следующим образом:

Теперь в классе определено три конструктора, каждый из которых принимает различное количество параметров и устанавливает значения полей класса. И мы можем вызвать один из этих конструкторов для создания объекта класса.

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

Ключевое слово this

Ключевое слово this представляет ссылку на текущий экземпляр/объект класса. В каких ситуациях оно нам может пригодиться?

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

первая часть - this.name означает, что name - это поле текущего класса, а не название параметра name. Если бы у нас параметры и поля назывались по-разному, то использовать слово this было бы необязательно. Также через ключевое слово this можно обращаться к любому полю или методу.

Цепочка вызова конструкторов

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

В данном случае первый конструктор вызывает второй, а второй конструктор вызывает третий. По количеству и типу параметров компилятор узнает, какой именно конструктор вызывается. Например, во втором конструкторе:

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

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

И если при вызове конструктора мы не передаем значение для какого-то параметра, то применяется значение по умолчанию.

Инициализаторы объектов

Для инициализации объектов классов можно применять инициализаторы . Инициализаторы представляют передачу в фигурных скобках значений доступным полям и свойствам объекта:

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

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

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

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

Обратите внимание, как устанавливается поле company :

Деконструкторы

Деконструкторы (не путать с деструкторами) позволяют выполнить декомпозицию объекта на отдельные части.

Например, пусть у нас есть следующий класс Person:

В этом случае мы могли бы выполнить декомпозицию объекта Person так:

Значения переменным из деконструктора передаюся по позиции. То есть первое возвращаемое значение в виде параметра personName передается первой переменной - name, второе возващаемое значение - переменной age.

По сути деконструкторы это не более,чем синтаксический сахар. Это все равно, что если бы мы написали:

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

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

Определение структуры

Для определения структуры применяется ключевое слово struct :

После слова struct идет название структуры и далее в фигурных скобках размещаются элементы структуры - поля, методы и т.д.

Например, определим структуру, которая будет называться Person и которая будет представлять человека:

Как и классы, структуры могут хранить состояние в виде полей (переменных) и определять поведение в виде методов. Например, добавим в структуру Person пару полей и метод:

В данном случае определены две переменные - name и age для хранения соответственно имени и возраста человека и метод Print для вывода информации о человеке на консоль.

И как и в случае с классами, для обращения к функциональности структуры - полям, методам и другим компонентам структуры применяется точечная нотация - после объекта структуры ставится точка, а затем указывается компонент структуры:

Создание объекта структуры

Инициализация с помощью конструктора

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

Например, создадим объект структуры Person с помощью конструктора по умолчанию:

В данном случае создается объект tom. Для его создания вызывается конструктор по умолчанию, который устанавливает значения по умолчанию для его полей. Для числовых данных это значение 0, поэтому поле age будет иметь значение 0. Для строк это значение null , которое указывает на отсутствие значения. Но далее, если поля доступны (а в данном случае поскольку они имеют модификатор public они доступны), мы можем измениь их значения. Так, здесь полю name присваивается строка "Tom". Соответственно при выполнении метода Print() мы получим следующий консольный вывод:

Непосредственная иницилизация полей

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

Инициализация полей по умолчанию

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

Конструкторы структуры

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

Например, добавим в структуру Person конструктор:

В данном случае в структуре Person определен конструктор с двумя параметрами, для которых предоставлены значения по умолчания. Однако обратите внимание на создание первого объекта структуры:

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

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

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

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

Инициализатор структуры

Также, как и для класса, можно использовать инициализатор для создания структуры:

При использовании инициализатора сначала вызывается конструктор без параметров: если мы явным образом не определили конструктор без параметров, то вызывается конструктор по умолчанию. А затем его полям присваиваются соответствующие значения.

Копирование структуры с помощью with

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

В данном случае объект bob получает все значения объекта tom, а затем после оператора with в фигурных скобках указывается поля со значениями, которые мы хотим изменить.

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