C конструктор структуры: Конструктор Struct в C++? — CodeRoad
Содержание
c++ — На конструктор по умолчанию нельзя ссылаться при использовании std :: string в элементе union структуры
У меня есть очень базовая структура, в которой есть enum и union.
typedef struct
{
enum v{a,b,c}v;
union w{
int a;
bool b;
std::string c;
}w;
}Data_Set2;
int main()
{
Data_Set2 val; // Shows errror that the default constructor cannot be referenced
return 0;
}
При использовании такой структуры я получаю код ошибки C2280, что на конструктор по умолчанию нельзя ссылаться. Когда я объявляю структуру немного по-другому, как показано ниже
typedef struct
{
enum v{a,b,c}v;
union w{
int a;
bool b;
std::string c;
}; // changed here.
}Data_Set2;
Ошибка больше не существует. Я не понимаю причину этого. Может ли кто-нибудь объяснить, почему это происходит
4
D_wanna_B_coder
5 Фев 2019 в 16:11
3 ответа
Лучший ответ
https://en.cppreference.com/w/cpp/language/union (или см. стандарт):
Если объединение содержит нестатический член данных с нетривиальной специальной функцией-членом (конструктор копирования / перемещения, назначение копирования / перемещения или деструктор), эта функция удаляется по умолчанию в объединении и должна быть явно определена программист.
Если объединение содержит нестатический член данных с нетривиальным конструктором по умолчанию, конструктор объединения по умолчанию удаляется по умолчанию, если только вариантный член объединения не имеет инициализатора элемента по умолчанию.
Не более одного варианта элемента может иметь инициализатор элемента по умолчанию.
В вашем случае это означает, что вы должны явно объявить конструктор и деструктор. Измените свой код на:
typedef struct
{
enum v{a,b,c} v;
union w{
int a;
bool b;
std::string c;
w() {} // Explicit constructor definition
~w() { }; // Explicit destructor definition
} w;
} Data_Set2;
Это должно работать.
Как уже говорилось в моем комментарии, вы должны взглянуть на std::any
и std::variant
. Последний обеспечивает типобезопасные союзы и, вероятно, будет лучшим выбором в вашем случае. Обратите внимание, что ваш компилятор (очевидно, MSVC) должен поддерживать C ++ 17.
РЕДАКТИРОВАТЬ: Как прокомментировал eerorika, вы должны быть уверены, что вы вызываете его только на активного участника. Ссылка, указанная в начале, показывает пример объединения строк и векторов и того, как она вводит много ловушек для неопределенного поведения. Поэтому, если вы просто не пытаетесь понять, что происходит за кулисами или используете типы POD, я бы посоветовал вам работать с std::variant
.
3
andreee
17 Дек 2019 в 09:38
В первом примере вы определяете переменную-член, которая должна быть конструируемой по умолчанию. Во втором примере вы определяете член тип , который выдаст ту же ошибку, если вы используете его для определения переменной этого типа.
Что касается ошибки, вам нужно создать конструктор по умолчанию в объединении, чтобы иметь возможность правильно его инициализировать:
union w{
int a;
bool b;
std::string c;
// Default constructor initialize the string member
w() : c()
{}
}w;
2
Some programmer dude
5 Фев 2019 в 13:14
Проблема в том, что объединение w
не является ни конструируемым по умолчанию, ни разрушаемым. Конструктор по умолчанию и деструктор не генерируются неявно, потому что член c
не является ни тривиально, ни тривиально разрушаемым. Таким образом, наличие члена типа w
просто невозможно. Во втором примере вы удаляете участника, поэтому проблем нет.
Чтобы сделать w
конструируемым по умолчанию, вы можете определить конструктор по умолчанию:
union w{
int a;
bool b;
std::string c;
w() // Could activate one of the members if so desired
{}
Чтобы сделать w
разрушаемым, вы можете определить деструктор (но читайте до конца):
~w(){
//TODO destruct the active member
}
} w;
Советы по уничтожению активного члена:
- Невозможно выяснить, какой участник активен.
- Доступ к неактивному члену имеет неопределенное поведение
- Если c активен, не уничтожая его имеет неопределенное поведение
В заключение: вы должны убедиться, что w
никогда не будет уничтожен при активном участнике c
. Такой инвариант может быть реализован в деструкторе Data_Set2
, предполагая, что v
указывает, какой элемент является активным (который является другим инвариантом, который следует поддерживать; эти члены, вероятно, не должны быть открытыми).
3
eerorika
6 Фев 2019 в 14:23
C# NET: Class vs Struct или в чём различия между Классом и Структурой
Просто о NET |
created:
7/10/2011
|
published:
7/10/2011
|
updated:
11/10/2021
|
view count:
89406
| comments:
30
Мне в последнее время очень часто встречаются программисты, которые не только используют в обычной “программной” жизни структуры (struct), но вообще, ничего не знают об этом объекте. И зачастую, для простоты своей «программной» жизни используют всегда классы (class). В этой статье я бы хотел в очередной раз остановиться на различиях между структурами и классами.
Что такое struсture?
Структуры синтаксически очень похожи на классы, но существует принципиальное отличие, которое заключается в том, что класс – является ссылочным типом (reference type), а структуры – значимый класс (value type). А следовательно, классы всегда создаются в, так называемой, “куче” (heap), а структуры создаются в стеке (stack). Цитата из комментария: «Имхо, главное отличие структур и классов: структуры передаются по значению (то есть копируются), объекты классов — по ссылке. Именно это является существеннейшим различием в их поведении, а не то, где они хранятся. Примечание: структуру тоже можно передать по ссылке, используя модификаторы out и ref.»
Ключевой момент статьи: Чем больше Вы будете использовать структуры вместо небольших (наверное, правильнее будет сказать – маленьких) классов, тем менее затратным по ресурсам будет использование памяти. Смею предположить, что не требуется объяснения почему… “куча”, “сборщик мусора”, “неопределенные временные интервалы прохода”, сложность “ручного” управления кучей и сборщиком мусора. Все ключевые моменты уже перечислены.
Так же как и классы, структуры могут иметь поля, методы и конструкторы. Хотя про конструкторы надо поговорить подробнее (будет дальше по теме), ибо это есть очень важное понятие при сравнивании классов и структур.
Структуры всегда с ВамиНе хочется думать, что следующая информация, для Вас сюрпризом. В языке C# примитивные числовые типы int, long, float являются альясами для структур System.Int32, System.Int64 и System.Single соответственно. Эти структуры имеют поля и методы. Вы обычно вызываете методы у переменных данных типов. Например, каждая из перечисленных структур имеет метод ToString. Также у перечисленных структур есть статичные поля, например, Int32.MaxValue или Int32.MinValue. Получается, что Вы уже в повседневной «программной» жизни используете структуры, а значит знакомы с ними.
Таблица классов и структур в Microsoft. NET Framework
В таблице указаны альясы и соответствующие им типы, а также дана информация о представляющем типе (структура или класс).
Keyword | Type equivalent | Class or structure |
---|---|---|
bool | System.Boolean | Structure |
byte | System.Byte | Structure |
decimal | System.Decimal | Structure |
double | System.Double | Structure |
float | System.Single | Structure |
int | System.Int32 | Structure |
long | System.Int64 | Structure |
object | System.Object | Class |
sbyte | System.SByte | Structure |
short | System.Int16 | Structure |
string | System.String | Class |
uint | System.UInt32 | Structure |
ulong | System.UInt64 | Structure |
ushort | System.UInt16 | Structure |
Объявление структур
Для объявления структуры используется зарезервированное слово struct, следом наименование структуры и фигурные скобки:
struct Time { public int hours, minites, seconds; }
В отличие от классов, использование публичных полей в структурах в большинстве случаев не рекомендуется, потому что не существует способа контролирования значений в них. Например, кто-либо может установит значение минут или секунд более 60. Более правильный вариант в данном случае — использовать свойства, а в конструкторе осуществить проверку:
struct Time { public Time(int hh, int mm, int ss) { hours = hh % 24; minutes = mm % 60; seconds = ss % 60; } public int Hours() { return hours; } ... private int hours, minutes, seconds; }
Кстати, …По умолчанию, Вы не можете использовать некоторые общие операторы в Ваших структурах. Например, Вы не можете использовать оператор сравнения (==) и противоположные ему (!=) на своих структурах. Однако, Вы можете явно объявить и реализовать операторы для своих структур.
И в чем же разница между структурами и классами
Давайте рассмотрим пример, в котором уже заложена ошибка:
struct Time { public Time() { ... } // compile-time error ... }
Причина возникновении ошибки в том, что Вы не можете использовать конструктор по умолчанию (без параметров) для структуры, потому что компилятор всегда генерирует его сам. Что же касается класса, то компилятор создает конструктор по умолчанию, только в том случае, если Вы его не создали. Сгенерированный конструктор для структуры всегда устанавливает поля в 0, false или null – как и для классов. Поэтому Вы можете быть уверенными в том, что созданная структура всегда будет вести себя “адекватно” в соответствии со значениями по умолчанию в используемых типах. Если Вы не хотите использовать значения по умолчанию, Вы можете инициализировать поля своими значениями в конструкторе с параметрами для инициализации. Однако, если в этом конструкторе не будет инициализировано какое-нибудь значение, компилятор не будет его инициализировать за Вас и покажет ошибку.
struct Time { private int hours, minutes, seconds; ... public Time(int hh, int mm) { this.hours = hh; this.minutes = mm; } // compile-time error: seconds not initialized }
Первое правило Структуры: Всегда все переменные должны быть инициализированы!
В классах Вы можете инициализировать значение полей непосредственно в месте их объявления. В структурах такого сделать не получится, и поэтому данный код вызовет ошибку при компиляции:
struct Time { private int hours = 0; // compile-time error private int minutes; private int seconds; ... }
Второе правило Структуры: Нельзя инициализировать переменные в месте их объявления!
Данная таблица в некотором роде подытоживает всё вышесказанное и отображает основные отличия между классами и структурами.
Вопрос | Структура | Класс |
И какого же типа экземпляр объекта? | Структура значимый (value) тип | Класс ссылочный (reference) тип |
А где “живут” экземпляры этих объектов? | Экземпляры структуры называют значениями и “живут” они в стеке (stack). | Экземпляры классов называют объектами и “живут” они в куче (heap). |
Можно ли создать конструктор по умолчанию? | Нет | Да |
Если создается свой конструктор будет ли компилятор генерировать конструктор по умолчанию? | Да | Нет |
Если в своём конструкторе не будут инициализированы некоторые поля, будут ли они автоматически инициализированы компилятором? | Нет | Да |
Разрешается ли инициализировать переменные в месте их объявления? | Нет | Да |
Использование структур как переменных
После того как Вы создали структуры, Вы можете использовать ее также как классы и другие типы. Например, создав структуру Time, я могу использовать ее в классе:
struct Time { private int hours, minutes, seconds; ... } class Example { private Time currentTime; public void Method(Time parameter) { Time localVariable; ... } }
Кстати, …
Вы можете создавать nullable версию переменной типа структуры использую модификатор “?” и потом присвоить ей значение null:
Time? currentTime = null;
Инициализация структур
В статье я не раз говорил, что поля структуры могут быть инициализированы при использования конструктора, причем не важно какого “собственного” или “по умолчанию”. К особенностям структур можно отнести еще и тот факт, что вследствие того, что структуры являются значимым типом, то можно создать структуру без использования конструктора, например:
Time now;
В таком случае, переменная создается, но поля не будут инициализированы в соответствии параметрами конструктора.
Заключение
Используйте структуры, это признак хорошего тона в программировании на C# и да прибудет с Вами сила… структур. Цитата из комментария: «С заключением тоже не согласен. Многие маститые западные авторы наоборот рекомендуют как можно меньше использовать структуры, предпочитая классы. Хотя, конечно, молиться на них и их мнение не стоит, лучше думать своей головой.»
Структуры и классы — SwiftBook
Классы и структуры являются универсальными и гибкими конструкциями, которые станут строительными блоками для кода вашей программы. Для добавления функциональности в классах и структурах можно объявить свойства и методы, применив тот же синтаксис, как и при объявлении констант, переменных и функций.
В отличие от других языков программирования, Swift не требует создавать отдельные файлы для интерфейсов и реализаций пользовательских классов и структур. В Swift, вы объявляете структуру или класс в одном файле, и внешний интерфейс автоматически становится доступным для использования в другом коде.
Заметка
Экземпляр класса традиционно называют объектом. Тем не менее, классы и структуры в Swift гораздо ближе по функциональности, чем в других языках, и многое в этой главе описывает функциональность, которую можно применить к экземплярам и класса, и структуры. В связи с этим, употребляется более общий термин — экземпляр.
Классы и структуры в Swift имеют много общего. И в классах и в структурах можно:
- Объявлять свойства для хранения значений
- Объявлять методы, чтобы обеспечить функциональность
- Объявлять индексы, чтобы обеспечить доступ к их значениям, через синтаксис индексов
- Объявлять инициализаторы, чтобы установить их первоначальное состояние
- Они оба могут быть расширены, чтобы расширить их функционал за пределами стандартной реализации
- Они оба могут соответствовать протоколам, для обеспечения стандартной функциональности определенного типа
Для получения дополнительной информации смотрите главы Свойства, Методы, Индексы, Инициализация, Расширения и Протоколы.
Классы имеют дополнительные возможности, которых нет у структур:
- Наследование позволяет одному классу наследовать характеристики другого
- Приведение типов позволяет проверить и интерпретировать тип экземпляра класса в процессе выполнения
- Деинициализаторы позволяют экземпляру класса освободить любые ресурсы, которые он использовал
- Подсчет ссылок допускает более чем одну ссылку на экземпляр класса. Для получения дополнительной информации смотрите Наследование, Приведение типов, Деинициализаторы и Автоматический подсчет ссылок.
Дополнительные возможности поддержки классов связаны с увеличением сложности. Лучше использовать структуры и перечисления, потому что их легче понимать. Также не забывайте про классы. На практике — большинство пользовательских типов данных, с которыми вы работаете — это структуры и перечисления.
Синтаксис объявления
Классы и структуры имеют схожий синтаксис объявления. Для объявления классов, используйте ключевое слово class, а для структур — ключевое слово struct. В обоих случаях необходимо поместить все определение полностью внутрь пары фигурных скобок:
class SomeClass {
// здесь пишется определение класса
}
struct SomeStructure {
// здесь пишется определение структуры
}
Заметка
Что бы вы не создавали, новый класс или структуру, вы фактически создаете новый тип в Swift. Назначайте имена типов, используя UpperCamelCase(SomeClass или SomeStructure), чтобы соответствовать стандартам написания имен типов в Swift (например, String, Int и Bool). С другой стороны, всегда назначайте свойствам и методам имена в lowerCamelCase (например, frameRate и incrementCount), чтобы отличить их от имен типов.
Пример определения структуры и класса:
struct Resolution {
var width = 0
var height = 0
}
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
Пример выше объявляет новую структуру Resolution для описания разрешения монитора в пикселях. Эта структура имеет два свойства width, height. Хранимые свойства — или константы, или переменные, которые сгруппированы и сохранены в рамках класса или структуры. Этим свойствам выведен тип Int, так как мы им присвоили целочисленное значение 0.
В примере мы так же объявили и новый класс VideoMode, для описания видеорежима для отображения на видеодисплее. У класса есть четыре свойства в виде переменных. Первое — resolution, инициализировано с помощью экземпляра структуры Resolution, что выводит тип свойства как Resolution. Для остальных трех свойств новый экземпляр класса будет инициализирован с interlaced = false, frameRate = 0.0 и опциональным значением типа String с названием name. Это свойство name автоматически будет иметь значение nil или «нет значения для name», потому что это опциональный тип.
Экземпляры класса и структуры
Объявление структуры Resolution и класса VideoMode только описывают как Resolution и VideoMode будут выглядеть. Сами по себе они не описывают специфическое разрешение или видеорежим. Для того чтобы это сделать нам нужно создать экземпляр структуры или класса.
Синтаксис для образования экземпляра класса или структуры очень схож:
let someResolution = Resolution()
let someVideoMode = VideoMode()
И классы и структуры используют синтаксис инициализатора для образования новых экземпляров. Самая простая форма синтаксиса инициализатора — использование имени типа и пустые круглые скобки сразу после него Resolution(), VideoMode(). Это создает новый экземпляр класса или структуры с любыми инициализированными свойствами с их значениями по умолчанию. Инициализация классов и структур описана более подробно в главе Инициализация.
Доступ к свойствам
Вы можете получить доступ к свойствам экземпляра, используя точечный синтаксис. В точечном синтаксисе имя свойства пишется сразу после имени экземпляра, а между ними вписывается точка (.) без пробелов:
print("The width of someResolution is \(someResolution.width)")
// Выведет "The width of someResolution is 0"
В этом примере someResolution.width ссылается на свойство width экземпляра someResolution, у которого начальное значение равно 0.
Вы можете углубиться в подсвойства, например, свойство width свойства resolution класса VideoMode:
print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// Выведет "The width of someVideoMode is 0"
Вы так же можете использовать точечный синтаксис для присваивания нового значения свойству:
someVideoMode.resolution.width = 1280
print("The width of someVideoMode is now \(someVideoMode.resolution.width)")
// Выведет "The width of someVideoMode is now 1280"
Поэлементные инициализаторы структурных типов
Все структуры имеют автоматически генерированный «поэлементный инициализатор», который вы можете использовать для инициализации свойств новых экземпляров структуры. Начальные значения для свойств нового экземпляра могут быть переданы поэлементному инициализатору по имени:
let vga = Resolution(width: 640, height: 480)
В отличие от структур, классы не получили поэлементного инициализатора исходных значений. Инициализаторы более подробно описаны в Инициализация.
Тип значения — это тип, значение которого копируется, когда оно присваивается константе или переменной, или когда передается функции.
Вообще вы уже достаточно активно использовали типы на протяжении предыдущих глав. Но факт в том, что все базовые типы Swift — типы значений и реализованы они как структуры.
Все структуры и перечисления — типы значений в Swift. Это значит, что любой экземпляр структуры и перечисления, который вы создаете, и любые типы значений, которые они имеют в качестве свойств, всегда копируются, когда он передается по вашему коду.
Заметка
Коллекции, определенные стандартной библиотекой, такие как массивы, словари и строки, используют оптимизацию для снижения затрат на копирование. Вместо того, чтобы немедленно сделать копию, эти коллекции совместно используют память, в которой элементы хранятся между исходным экземпляром и любыми копиями. Если одна из копий коллекции модифицирована, элементы копируются непосредственно перед изменением.
Рассмотрим пример, который использует структуру Resolution из предыдущего примера:
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
Этот пример объявляет константу hd и присваивает экземпляр Resolution, инициализированный двумя значениями width и height.
В свою очередь, объявляем переменную cinema и присваиваем ей текущее значение hd. Так как Resolution — структура, делается копия существующего экземпляра, и эта новая копия присваивается cinema. Даже не смотря на то, что hd и cinema имеют одни и те же height, width, они являются абсолютно разными экземплярами.
Следующим шагом изменим значение свойства width у cinema, мы сделаем его чуть больше 2 тысяч, что является стандартным для цифровой кинопроекции (2048 пикселей ширины на 1080 пикселей высоты):
cinema.width = 2048
Если мы проверим свойство width у cinema, то мы увидим, что оно на самом деле изменилось на 2048:
print("cinema is now \(cinema.width) pixels wide")
// Выведет "cinema is now 2048 pixels wide"
Однако свойство width исходного hd экземпляра осталось 1920:
print("hd is still \(hd.width) pixels wide")
// Выведет "hd is still 1920 pixels wide"
Когда мы присвоили cinema текущее значение hd, то значения, которые хранились в hd были скопированы в новый экземпляр cinema. И в качестве результата мы имеем два совершенно отдельных экземпляра, которые содержат одинаковые числовые значения. Так как они являются раздельными экземплярами, то установив значение свойства width у cinema на 2048 никак не повлияет на значение width у hd, это показано ниже:
То же поведение применимо к перечислениям:
enum CompassPoint {
case north, south, east, west
mutating func turnNorth() {
self = .north
}
}
var currentDirection = CompassPoint.west
let rememberedDirection = currentDirection
currentDirection.turnNorth()
print("Текущее направление - \(currentDirection)")
// Выведет "Текущее направление - north"
Когда мы присваиваем rememberedDirection значение currentDirection, мы фактически копируем это значение. Изменяя значение currentDirection, мы не меняем копию исходного значения, хранящейся в rememberedDirection.
В отличие от типа значений, ссылочный тип не копируется, когда его присваивают переменной или константе, или когда его передают функции. Вместо копирования используется ссылка на существующий экземпляр.
Вот пример с использованием класса VideoMode, который был объявлен выше:
let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0
В этом примере объявляем новую константу tenEighty и устанавливаем ссылку на новый экземпляр класса VideoMode. Значения видеорежима были присвоены копией со значениями 1920 на 1080. Мы ставим tenEighty.interlaced = true и даем имя “1080i”. Затем устанавливаем частоту кадров в секунду 25 .
Следующее, что мы сделаем, это tenEighty присвоим новой константе alsoTenEighty и изменим частоту кадров:
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
Так как это классы ссылочного типа, то экземпляры tenEighty и alsoTenEighty ссылаются на один и тот же экземпляр VideoMode. Фактически получается, что у нас два разных имени для одного единственного экземпляра, как показано на рисунке ниже:
Если мы проверим свойство frameRate у tenEighty, то мы увидим, что новая частота кадров 30.0, которая берется у экземпляра VideoMode:
print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// Выведет "The frameRate property of tenEighty is now 30.0"
Этот пример показывает как сложно бывает отследить за ссылочными типами. Если tenEighty и alsoTenEighty находились бы в разных уголках вашей программы, то было бы сложно найти все места, где мы меняем режим воспроизведения видео. Где бы вы не использовали tenEighty, вам так же бы приходилось думать и о alsoTenEighty, и наоборот. В отличие от ссылочного типа, с типами значения дела обстоят значительно проще, так как весь код, который взаимодействует с одним и тем же значением, находится рядом, в вашем исходном файле.
Обратите внимание, что tenEighty и alsoTenEighty объявлены как константы, а не переменные. Однако вы все равно можете менять tenEighty.frameRate и alsoTenEighty.frameRate, потому что значения tenEighty и alsoTenEighty сами по себе не меняются, так как они не «содержат» значение экземпляра VideoMode, а напротив, они лишь ссылаются на него. Это свойство frameRate лежащего в основе VideoMode, которое меняется, а не значения константы ссылающейся на VideoMode.
Операторы тождественности
Так как классы являются ссылочными типами, то есть возможность сделать так, чтобы несколько констант и переменных ссылались на один единственный экземпляр класса. (Такое поведение не применимо к структурам и перечислениям, так как они копируют значение, когда присваиваются константам или переменным или передаются функциям.)
Иногда бывает полезно выяснить ссылаются ли две константы или переменные на один и тот же экземпляр класса. Для проверки этого в Swift есть два оператора тождественности:
- Идентичен (===)
- Не идентичен ( !== )
Можно использовать эти операторы для проверки того, ссылаются ли две константы или переменные на один и тот же экземпляр:
if tenEighty === alsoTenEighty {
print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
}
// Выведет "tenEighty and alsoTenEighty refer to the same VideoMode instance."
Обратите внимание, что «идентичность» (в виде трех знаков равенства, или ===) не имеет в виду «равенство» (в виде двух знаков равенства, или ==). Идентичность или тождественность значит, что две константы или переменные ссылаются на один и тот же экземпляр класса. Равенство значит, что экземпляры равны или эквивалентны в значении в самом обычном понимании «равны».
Когда вы объявляете свой пользовательский класс или структуру, вы сами решаете, что означает «равенство» двух экземпляров. Процесс определения своей собственной реализации операторов «равенства» или «неравенства» описан в разделе Операторы эквивалентности.
Указатели
Если у вас есть опыт работы в C, C++ или Objective-C, то вы, может быть, знаете, что эти языки используют указатели для ссылки на адрес памяти. В Swift константы и переменные, которые ссылаются на экземпляр какого-либо ссылочного типа, аналогичны указателям C, но это не прямые указатели на адрес памяти, и они не требуют от вас написания звездочки(*) для индикации того, что вы создаете ссылку. Вместо этого такие ссылки объявляются как другие константы или переменные в Swift. Стандартная библиотека предоставляет типы указателей и буферов, которые вы можете использовать, если вам нужно напрямую взаимодействовать с указателями — см. «Управление памятью вручную».
Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.
C структура в структуре — Вэб-шпаргалка для интернет предпринимателей!
Как вам должно быть уже известно, классы относятся к ссылочным типам данных. Это означает, что объекты конкретного класса доступны по ссылке, в отличие от значений простых типов, доступных непосредственно. Но иногда прямой доступ к объектам как к значениям простых типов оказывается полезно иметь, например, ради повышения эффективности программы. Ведь каждый доступ к объектам (даже самым мелким) по ссылке связан с дополнительными издержками на расход вычислительных ресурсов и оперативной памяти.
Для разрешения подобных затруднений в C# предусмотрена структура, которая подобна классу, но относится к типу значения, а не к ссылочному типу данных. Т.е. структуры отличаются от классов тем, как они сохраняются в памяти и как к ним осуществляется доступ (классы — это ссылочные типы, размещаемые в куче, структуры — типы значений, размещаемые в стеке), а также некоторыми свойствами (например, структуры не поддерживают наследование). Из соображений производительности вы будете использовать структуры для небольших типов данных. Однако в отношении синтаксиса структуры очень похожи на классы.
Главное отличие состоит в том, что при их объявлении используется ключевое слово struct вместо class. Ниже приведена общая форма объявления структуры:
где имя обозначает конкретное имя структуры.
Как и у классов, у каждой структуры имеются свои члены: методы, поля, индексаторы, свойства, операторные методы и события. В структурах допускается также определять конструкторы, но не деструкторы. В то же время для структуры нельзя определить конструктор, используемый по умолчанию (т.е. конструктор без параметров). Дело в том, что конструктор, вызываемый по умолчанию, определяется для всех структур автоматически и не подлежит изменению. Такой конструктор инициализирует поля структуры значениями, задаваемыми по умолчанию. А поскольку структуры не поддерживают наследование, то их члены нельзя указывать как abstract, virtual или protected.
Объект структуры может быть создан с помощью оператора new таким же образом, как и объект класса, но в этом нет особой необходимости. Ведь когда используется оператор new, то вызывается конструктор, используемый по умолчанию. А когда этот оператор не используется, объект по-прежнему создается, хотя и не инициализируется. В этом случае инициализацию любых членов структуры придется выполнить вручную.
Давайте рассмотрим пример использования структур:
Обратите внимание, когда одна структура присваивается другой, создается копия ее объекта. В этом заключается одно из главных отличий структуры от класса. Когда ссылка на один класс присваивается ссылке на другой класс, в итоге ссылка в левой части оператора присваивания указывает на тот же самый объект, что и ссылка в правой его части. А когда переменная одной структуры присваивается переменной другой структуры, создается копия объекта структуры из правой части оператора присваивания.
Поэтому, если бы в предыдущем примере использовался класс UserInfo вместо структуры, получился бы следующий результат:
Назначение структур
В связи с изложенным выше возникает резонный вопрос: зачем в C# включена структура, если она обладает более скромными возможностями, чем класс? Ответ на этот вопрос заключается в повышении эффективности и производительности программ. Структуры относятся к типам значений, и поэтому ими можно оперировать непосредственно, а не по ссылке. Следовательно, для работы со структурой вообще не требуется переменная ссылочного типа, а это означает в ряде случаев существенную экономию оперативной памяти.
Более того, работа со структурой не приводит к ухудшению производительности, столь характерному для обращения к объекту класса. Ведь доступ к структуре осуществляется непосредственно, а к объектам — по ссылке, поскольку классы относятся к данным ссылочного типа. Косвенный характер доступа к объектам подразумевает дополнительные издержки вычислительных ресурсов на каждый такой доступ, тогда как обращение к структурам не влечет за собой подобные издержки. И вообще, если нужно просто сохранить группу связанных вместе данных, не требующих наследования и обращения по ссылке, то с точки зрения производительности для них лучше выбрать структуру.
Любопытно, что в С++ также имеются структуры и используется ключевое слово struct. Но эти структуры отличаются от тех, что имеются в C#. Так, в С++ структура относится к типу класса, а значит, структура и класс в этом языке практически равноценны и отличаются друг от друга лишь доступом по умолчанию к их членам, которые оказываются закрытыми для класса и открытыми для структуры. А в C# структура относится к типу значения, тогда как класс — к ссылочному типу.
Введение
Мир вокруг можно моделировать различными способами. Самым естественным из них является представление о нём, как о наборе объектов. У каждого объекта есть свои свойства. Например, для человека это возраст, пол, рост, вес и т.д. Для велосипеда – тип, размер колёс, вес, материал, изготовитель и пр. Для товара в магазине – идентификационный номер, название, группа, вес, цена, скидка и т.д.
У классов объектов набор этих свойств одинаковый: все собаки могут быть описаны, с той или иной точностью, одинаковым набором свойств, но значения этих свойств будут разные.
Все самолёты обладают набором общих свойств в пределах одного класса. Если же нам надо более точное описание, то можно выделить подклассы: самолёт амфибии, боевые истребители, пассажирские лайнеры – и в пределах уже этих классов описывать объекты. Например, нам необходимо хранить информацию о сотрудниках компании. Каждый сотрудник, в общем, обладает большим количеством разных свойств. Мы выберем только те, которые нас интересуют для решения прикладной задачи: пол, имя, фамилия, возраст, идентификационный номер. Для работы с таким объектом нам необходима конструкция, которая бы могла агрегировать различные типы данных под одним именем. Для этих целей в си используются структуры.
Объявление структуры
Синтаксис объявления структуры
Полями структуры могут быть любые объявленные типы, кроме самой структуры этого же типа, но можно хранить указатель на структуру этого типа:
В том случае, если несколько полей имеют один тип, то их можно перечислить через запятую:
После того, как мы объявили структуру, можно создавать переменную такого типа с использованием служебного слова struct. Доступ до полей структуры осуществляется с помощью операции точка:
Структура, объявленная в глобальном контексте, видна всем. Структура также может быть объявлена внутри функции:
Можно упростить пример: синтаксис языка позволяет создавать экземпляры структуры сразу же после определения:
Структура также может быть анонимной. Тогда мы не сможем использовать имя структуры в дальнейшем.
В этом примере мы создали переменную A. Она является структурой с двумя полями.
Начальная инициализация структур
Структуру можно инициализировать во время создания как массив. Поля в этом случае будут присваиваться по порядку.
Замечание: таким образом можно только иницализировать структуру. Присваивать значение всей структуре таким образом нельзя.
Современный стандарт си позволяет инициализировать поля структуры по имени. Для этого используется следующий синтакис:
Определение нового типа
Когда мы определяем новую структуру с помощью служебного слова struct, в пространстве имён структур (оно не имеет ничего общего с пространствами имён С++) создаётся новый идентификатор. Для доступа к нему необходимо использовать служебное слово struct. Можно определить новый тип с помощью служебного слова typedef. Тогда будет создан псевдоним для нашей структуры, видимый в глобальном контексте.
Теперь при работе с типом Point нет необходимости каждый раз писать слово struct. Два объявления можно объединить в одно
Замечание. Если мы создаём новый тип-структуру, полем которого является указатель на этот же тип, то его необходимо объявлять явно с использованием служебного слова struct
Указатели на структуру
Указатель на структуру создаётся как обычно. Отличие заключается в том, что можно обращаться к полям структуры через указатель с помощью операции «стрелка» (минус + больше). Пример – пользователь вводит число – размер массива пользователей. Поле этого вводит для каждого из них логин и пароль. Третье поле — идентификатор – задаётся автоматически. После этого все пользователи выводятся на экран.
Обратите внимание на удаление массива структур: при удалении экземпляра структуры он не удаляет своих полей самостоятельно, поэтому необходимо сначала удалять поля, после этого удалять сам массив.
При вызове функции jsonUser мы передаём указатель на экземпляр структуры, поэтому внутри функции доступ до полей осуществляется с помощью оператора стрелка.
Устройство структуры в памяти
Поля структуры расположены в памяти друг за другом. Тип поля определяет сдвиг относительно предыдущего поля. Имя поля — это сдвиг относительно адреса экземпляра. На самом деле размер структуры не всегда равен сумме размеров её полей: это связано с тем, что компилятор оптимизирует расположение структуры в памяти и может поля небольшого размера подгонять до чётных адресов.
Первая структура должна иметь размер 6 байт, вторая 8 байт, третья 7 байт, однако на 32-разрядной машине компилятор VC сделает их все три равными 8 байт. Стандарт гарантирует, что поля расположены друг за другом, но не гарантирует, что непрерывно.
Есть возможность изменить упаковку структур в памяти. Можно явно указать компилятору каким образом производить упаковку полей структуры, объединений или полей класса. Каким образом это делать, зависит от компилятора. Один из самых распространённых способов прагма pack()
У неё есть несколько разновидностей, рассмотрим только одну. pragma pack(n) указывает значение в байтах, используемое для упаковки. Если параметр компилятора не заданы для модуля значения по умолчанию n 8. Допустимыми значениями являются 1, 2, 4, 8 и 16. Выравнивание поля происходит по адресу, кратному n или сумме нескольких полей объекта, в зависимости от того, какая из этих величин меньше.
Использование #pragma pack не приветствуется: логика работы программы не должна зависить от внутреннего представления структуры (если, конечно, вы не занимаетесь системным программированием или ломаете чужие программы и сети).
Приведение типов
Стандартом поведение при приведении одной структуры к другой не определено. Это значит, что даже если структуры имеют одинаковые поля, то нельзя явно кастовать одну структуру до другой.
Этот пример работает, но это хак, которого необходимо избегать. Правильно писать так
Привести массив к структуре (или любому другому типу) по стандарту также невозможно (хотя в различных компиляторах есть для этого инструменты).
Но в си возможно всё.
Но запомните, что в данном случае поведение не определено.
Вложенные структуры
Структура сама может являться полем структуры. Пример: структура Model – модель автомобиля, имеет название, номер, год выпуска и поле Make, которое в свою очередь хранит номер марки и её название.
Вложенные структуры инициализируются как многомерные массивы. В предыдущем примере можно произвести начальную инициализацию следующим образом:
P.S. подобным образом инициализировать строки не стоит, здесь так сделано только для того, чтобы упростить код.
Указатели на поля структуры и на вложенные структуры
Указатели на поля структуры определяются также, как и обычные указатели. Указатели на вложенные структуры возможны только тогда, когда структура определена. Немного переделаем предыдущий пример: «деанонимизируем» вложенную безымянную структуру и возьмём указатели на поля структуры Model:
Как уже говорилось ранее, в си, даже если у двух структур совпадают поля, но структуры имеют разные имена, то их нельзя приводить к одному типу. Поэтому приходится избавляться от анонимных вложенных структур, если на них нужно взять указатель. Можно попытаться взять указатель типа char* на поле структуры, но нет гарантии, что поля будут расположены непрерывно.
Примеры
1. Стек, реализованный с помощью структуры «Узел», которая хранит значение (в нашем примере типа int) и указатель на следующий узел. Это неэффективная реализация, которая требует удаления и выделения памяти под узел при каждом вызове операции push и pop.
2. Реализуем структуру — массив, и некоторые операции для работы с массивами. Тип массива зададим макроподстановкой.
3. Структура Линия, состоит из двух структур точек. Для краткости реализуем только пару операций
Обратите внимание на операции создания и копирования линии. Обязательно нужно копировать содержимое, иначе при изменении или удалении объектов, которые мы получили в качестве аргументов, наша линия также изменится. Если структура содержит другие структуры в качестве полей, то необходимо проводить копирование содержимого всех полей. Глубокое копирование позволяет избежать неявных зависимостей.
4. Структура комплексное число и функции для работы с ней.
Структуры. Часть 2. Выделение памяти для структуры. Вложенные структуры. Массивы native -структур
Данная тема базируется на темах:
Содержание
1. Каким образом выделяется память для структурной переменной? Пример применения операции sizeof
Объявление структурной переменной осуществляется в 2 этапа:
- объявление шаблона структуры как нового типа данных. На этом этапе память не выделяется. Формируется только информация о содержимом структуры;
- объявление самой переменной. На этом этапе выделяется память для любого поля (переменной), которое описывается в шаблоне структуры.
Пример. Пусть задан шаблон native -структуры, которая описывает книгу в библиотеке.
После такого описания память не выделяется.
Если описать переменную типа Book , тогда для такой переменной память будет выделена.
Размер памяти, которая выделяется для переменной B будет составлять: 70 + 50 + 4 + 4 = 128 байт. Для поля title выделится 70 байт (тип char занимает 1 байт). Для поля author выделится 50 байт. Для поля year (в Win32 ) выделится 4 байта (тип int ). Для поля price выделится 4 байта (тип float ).
В зависимости от разрядности операционной системы и конфигурации оборудования эта цифра может быть другой.
Пример. Определение размера памяти, которая выделяется для структурной переменной.
2. Какие особенности использования вложенных структур в программах?
Шаблон любой структуры может включать в себя другие структуры. Если в структуре описывается другая структурная-переменная, тогда для этой переменной память выделяется согласно правилам выделения памяти для структурной переменной (см. п.1).
3. Пример объявления и использования вложенной native -структуры
Пусть заданы два шаблона структур, которые объявляются в отдельном файле «MyStruct.h» :
– шаблон Point , описывающий точку на координатной плоскости:
– шаблон Triangle , описывающий треугольник на плоскости:
В шаблоне Triangle описывается три вложенных структуры (точки) типа Point .
Демонстрация работы со структурой Triangle .
Для использования метода strcpy() и подключения файла структуры нужно в начале модуля программы вписать:
В native -структуре
- можно объявлять другую native -структуру;
- можно объявлять value -структуру;
- нельзя объявлять ref -структуру, поскольку она есть типом-ссылкой.
4. Пример объявления и использование вложенной ref -структуры
Пусть задан шаблон ref -структуры, которая описывает точку на координатной плоскости
Однако, эта структура может быть вложенной в другой ref -структуре. Пример
В ref -структуре:
- можно объявлять другую ref -структуру;
- можно объявлять value -структуру;
- нельзя объявлять native -структуру. Система выдает сообщение: «Mixed types are not supported» («Смешанные типы не поддерживаются»).
5. Пример объявления и использования вложенной value -структуры
Пусть заданы два шаблона value -структур, которые описывают точку ( Point_value ) и треугольник ( Triangle_value )
В шаблоне value -структуры:
- можно объявлять другую вложенную value -структуру;
- нельзя объявлять вложенную ref -структуру
- нельзя объявлять вложенную native -структуру.
6. Как объявить массив структур ( native )? Пример объявления и использования
Для native -структур объявления массива происходит классическим для C/C++ способом.
Пусть задан шаблон структуры Point_native , которая описывает точку на плоскости.
Пример 1. Объявление и использование массива структур как значений.
Пример 2. Объявление и использование массива указателей на native -структуры.
7. Пример объявления и использования массива native -структур, который есть отдельным полем структуры (вложенным в структуру)
Структура может содержать вложенный массив структур.
Пусть задан шаблон native -структуры Point_native , что описывает точку на плоскости
Массив из n точек ( n = 1..10) можно представить в виде такого шаблона:
8. Пример объявления и использования двумерного массива native -структур
Пусть задана native -структура, которая описывает точку на плоскости.
Тогда, работа с двумерным массивом таких структур размером 5*10 будет приблизительно следующей
Рекомендуем к прочтению
Структуры | C++
Структуры — это тип данных, очень похожий на классы и на массивы. Структуры используют чтобы хранить элементы, но в отличии от массива, у элементов могут быть разные типы данных. Структуры удобно использовать, когда нужно объединить несколько переменных под одним именем.
Листинг объявления структуры:
struct имя_структуры
{
тип_переменной1 имя_переменной1;
тип_переменной2 имя_переменной2;
тип_переменной3 имя_переменной3;
….
}; (точка с запятой обязательна!)
| struct имя_структуры { тип_переменной1 имя_переменной1; тип_переменной2 имя_переменной2; тип_переменной3 имя_переменной3; …. }; (точка с запятой обязательна!) |
Для примера создадим карточку для учёта роста детей:
struct card // card — имя структуры
{ // переменные
string name;
int age;
float hight;
};
| struct card // card — имя структуры { // переменные string name; int age; float hight; }; |
Как было сказано ранее, структура — это тип данных, поэтому чтоб создать экземпляр структуры card нужно действовать как и с простыми типами данных:
или же при объявлении структуры перечислить их перед точкой с запятой:
struct card
{
…
} Student, teacher, sport;
| struct card { … } Student, teacher, sport; |
Для доступа к полю структуры используется следующая конструкция
имя_экземпляра_структуры.имя_поля
| имя_экземпляра_структуры.имя_поля |
Создадим элемент структуры нашего примера и инициализируем её поля:
card Tim;
Tim.name = «Anna»;
Tim.age = 15;
Tim.hight = 156.7;
| card Tim; Tim.name = «Anna»; Tim.age = 15; Tim.hight = 156.7; |
Х-101/102, крылатые ракеты
Компания участник: Радуга, Государственное Машиностроительное конструкторское бюро им. А.Я. Березняка, АО
Разработка крылатых ракет для замены Х-55 в составе вооружения стратегических бомбардировщиков-ракетоносцев началась еще в середине 80-х годов. Велись работы как над гиперзвуковыми, сверхскоростными ракетами, так и над ракетами значительно большей дальности, чем Х-55. Это привело к созданию силами МКБ «Радуга» в середине 90-х ракеты Х-101. Первые бросковые испытания состоялись в 1995 году (по другим данным — в 1998). Ракета была принята на вооружение стратегической авиацией России в 2012 году. В данный момент, судя по обрывочной информации, выпускается небольшими сериями — возможно, продолжает проходить испытания и доработки.
Ракета выпускается в обычном (Х-101) и ядерном (Х-102) оснащении. Возможно, Х-102 обладает большей дальностью за счет меньшей массы и габаритов боевой части. Также весьма вероятны отличия в системах наведения — у ядерной ракеты ниже требования к точности, но выше к надежности и помехозащищенности.
Крылатая ракета нового поколения имеет ряд качественных отличий от Х-55. В первую очередь, это дальность полета, достигающая 5000−5500 км при полете по смешанному профилю. У Х-55 она в схожей ситуации 2000−2500 км (у модификации Х-55 СМ с ядерной БЧ и накладными топливными баками — до 3500 км). Расплатой стала увеличившаяся масса ракеты. Это оправданно, так как пуск заведомо за пределами зоны действия ПВО значительно повышает выживаемость носителя, а больший запас по дальности ракеты позволяет выбирать более безопасный маршрут полета и раньше переходить на полет на предельно малых высотах с огибанием рельефа местности — более безопасный, но требующий большего расхода топлива. Еще одним фактором, способствующим достижению цели, является широкое внедрение средств понижения радиолокационной заметности.
Вторым качественным отличием от аналогов является сочетание высокой дальности с высочайшей точностью, в том числе и по движущимся целям. Наряду с взаимодополняющими инерциальной и спутниковой навигационными системами на Х-101 установлена оптическая головка самонаведения. На конечном участке коррекция осуществляется телевизионной системой «Спрут», что обеспечивает возможность поражения, в том числе и движущихся целей.
Носителями ракет семейства Х-101/102 являются прошедшие модернизацию стратегические бомбардировщики-ракетоносцыТу-95МС (до 8 на внешних узлах подвески) и Ту-160 (до 12 на внутренних барабанных пусковых установках). Есть непроверенная информация о том, что в перспективе будет обеспечена их установка и на фронтовые бомбардировщики Су-34, и модернизированные дальние бомбардировщики Ту-22М3.
Технические характеристики
Длина, м: | 7,45 |
Диаметр, мм | 742 |
Размах крыла, м | 3 |
Стартовый вес, кг | 2200—2400 |
Масса БЧ, кг | 400 |
Масса топлива, кг: | 1250 |
Скорость, м/c, крейсерская | 190—200 |
Скорость, м/c, максимальная | 250—270 |
Дальность пуска, км | до 4500—5500 по разным данным |
Профиль полёта | изменяемый |
Высота профиля полёта, м | от 30—70 до 10 000 |
ЭПР, кв. м. | 0,01 |
Тип ГСН | оптоэлектронная система коррекции + ТВ |
Силовая установка | Двигатель ДТРД ТРДД-50А с тягой 450 кгс |
Точность (КВО), м | 5-6 |
Ядерный вариант ракеты | Х-102 (250 кт) |
Видео
Как создавать «конструкторы» для конструкций в C
Нет ответа, который всегда будет правильным, потому что разные программы будут иметь разные потребности. Если у вас достаточно информации, чтобы так или иначе сделать осознанный звонок, вам следует поступить именно так. (Например, если ваша программа должна иметь максимально возможную пропускную способность, форсирование пары malloc ()
/ free ()
каждый раз, когда какая-то функция должна использовать вашу структуру, вероятно, не сработает.)
Если вы собираетесь создать библиотеку общего назначения, не потребуется много дополнительных усилий, чтобы построить ее таким образом, чтобы она поддерживала оба:
void example_init (struct example * p, int x, int y, int z)
— Инициализирует структуру, на которую указывает p
. Это может быть вызвано любым, у кого есть пример структуры
, будь то автоматический, выделенный в куче или извлеченный из пула.
void example_destroy (struct example * p)
— Делает все необходимое для деинициализации структуры, на которую указывает p
.(В вашем случае это освободит память, выделенную для другого члена
.) Обратите внимание, что он освобождает , а не , p
, потому что он не знает, находится ли структура в куче или вызывающий абонент не собирается использовать его повторно, снова вызывая example_init ()
.
Пример структуры * example_new (int x, int y, int z)
— Выделяет пространство для структуры в куче, вызывает example_init ()
и возвращает указатель.
void example_del (struct example * p)
— вызывает example_destroy (p)
и затем освобождает p
.
При таком подходе существует опасность, заключающаяся в том, что кто-то может попытаться выполнить пары из вызовов example_init ()
/ example_del ()
или example_new ()
/ example_destroy ()
. Такие инструменты, как valgrind
и некоторые реализации free ()
, могут устранить эти проблемы автоматически. Другой подход заключался бы в том, чтобы отложить немного где-нибудь в структуре, которая может быть установлена, когда структура была в куче, и использовать утверждения, чтобы пожаловаться, если кто-то пойдет не так.( example_del ()
необходимо очистить бит перед вызовом example_destroy ()
.)
Конструкторы и списки инициализаторов членов
Конструктор — это специальная нестатическая функция-член класса, которая используется для инициализации объектов этого типа класса.
В определении конструктора класса список инициализаторов членов указывает инициализаторы для прямых и виртуальных баз и нестатических элементов данных.
(Не путать с std :: initializer_list.)
Конструктор не должен быть сопрограммой. | (начиная с C ++ 20) |
[править] Синтаксис
Конструкторы объявляются с использованием деклараторов функций-членов следующей формы:
имя класса ( список параметров (необязательно) ) except-spec (необязательно) attr (необязательно) | (1) | ||||||||
Где имя-класса должно называть текущий класс (или текущую реализацию шаблона класса), или, когда оно объявлено в области пространства имен или в объявлении друга, оно должно быть квалифицированным именем класса.
Единственными спецификаторами, разрешенными в decl-спецификаторе-seq объявления конструктора, являются friend
, inline
, constexpr
(начиная с C ++ 11), consteval
(начиная с C ++ 20) и явный
(в частности, не допускается возвращаемый тип). Обратите внимание, что квалификаторы cv- и ref также не допускаются: семантика const и volatile для строящегося объекта не срабатывает, пока не завершится наиболее производный конструктор.
Тело определения функции любого конструктора перед открывающей скобкой составного оператора может включать список инициализаторов членов , синтаксис которого является символом двоеточия :
, за которым следует список разделенных запятыми один или несколько инициализаторов-членов, каждый из которых имеет следующий синтаксис:
класс-или-идентификатор ( список-выражений (необязательно) ) | (1) | ||||||||
список-инициализационных скобок класса или идентификатора | (2) | (начиная с C ++ 11) | |||||||
пакет параметров ... | (3) | (начиная с C ++ 11) | |||||||
класс или идентификатор | — | любой идентификатор, который называет нестатический член данных или любое имя типа, которое называет либо сам класс (для делегирования конструкторов), либо прямую или виртуальную базу. |
список выражений | — | , возможно, пустой, разделенный запятыми список аргументов для передачи конструктору базы или члена |
список инициализации в фигурных скобках | — | заключенный в фигурные скобки список инициализаторов, разделенных запятыми, и вложенные списки инициализации в фигурных скобках |
пакет параметров | — | имя пакета параметров вариативного шаблона |
struct S { int n; S (интервал); // объявление конструктора S (): n (7) {} // определение конструктора // ": n (7)" - список инициализаторов // ": n (7) {}" - тело функции }; S :: S (int x): n {x} {} // определение конструктора: ": n {x}" - это список инициализаторов int main () { SS; // вызывает S :: S () S s2 (10); // вызывает S :: S (int) }
[править] Объяснение
Конструкторы не имеют имен и не могут быть вызваны напрямую.Они вызываются при инициализации и выбираются в соответствии с правилами инициализации. Конструкторы без явного спецификатора
являются конструкторами преобразования. Конструкторы со спецификатором constexpr
делают свой тип LiteralType. Конструкторы, которые можно вызывать без аргументов, являются конструкторами по умолчанию. Конструкторы, которые принимают другой объект того же типа, что и аргумент, являются конструкторами копирования и конструкторами перемещения.
Перед выполнением составного оператора, образующего тело функции конструктора, инициализация всех прямых баз, виртуальных баз и нестатических элементов данных завершается.Список инициализаторов элементов — это место, где можно указать нестандартную инициализацию этих объектов. Для базовых и нестатических элементов данных, которые не могут быть инициализированы по умолчанию, таких как ссылочные и константные типы, должны быть указаны инициализаторы членов. Инициализация не выполняется для анонимных объединений или вариантов членов, у которых нет инициализатора члена.
Инициализаторы, в которых класс или идентификатор именуют виртуальный базовый класс, игнорируются во время создания любого класса, который не является наиболее производным классом создаваемого объекта.
Имена, которые появляются в списке-выражений или списке-инициализации фигурных скобок, оцениваются в области действия конструктора:
класс X { int a, b, i, j; общественность: const int & r; X (int я) : r (a) // инициализирует X :: r для ссылки на X :: a , b {i} // инициализирует X :: b значением параметра i , i (i) // инициализирует X :: i значением параметра i , j (this-> i) // инициализирует X :: j значением X :: i {} };
Исключения, возникающие из инициализаторов членов, могут обрабатываться блоком function-try-block
Функции-члены (включая виртуальные функции-члены) могут быть вызваны из инициализаторов элементов, но поведение не определено, если не все прямые базы инициализированы в этой точке.
Для виртуальных вызовов (если прямые базы инициализированы в этой точке) применяются те же правила, что и правила для виртуальных вызовов из конструкторов и деструкторов: виртуальные функции-члены ведут себя так, как если бы динамический тип * это статический тип создаваемый класс (динамическая отправка не распространяется вниз по иерархии наследования) и виртуальные вызовы (но не статические вызовы) чистым виртуальным функциям-членам являются неопределенным поведением.
Если нестатический член данных имеет инициализатор члена по умолчанию и также появляется в списке инициализаторов члена, то используется инициализатор члена, а инициализатор члена по умолчанию игнорируется: struct S { int n = 42; // инициализатор члена по умолчанию S (): n (7) {} // установит n равным 7, а не 42 }; | (начиная с C ++ 11) |
Ссылочные элементы не могут быть привязаны к временным объектам в списке инициализаторов элементов:
struct A { A (): v (42) {} // Ошибка const int & v; };
Примечание: то же самое относится к инициализатору члена по умолчанию.
Делегирующий конструкторЕсли имя самого класса появляется как класс или идентификатор в списке инициализаторов членов, то список должен состоять только из этого инициализатора одного члена; такой конструктор известен как делегирующий конструктор , а конструктор, выбранный единственным членом списка инициализаторов, является целевым конструктором В этом случае целевой конструктор выбирается путем разрешения перегрузки и выполняется первым, затем управление возвращается конструктору делегирования, и его тело выполняется. Делегирующие конструкторы не могут быть рекурсивными. class Foo { общественность: Foo (char x, int y) {} Foo (int y): Foo ('a', y) {} // Foo (int) делегирует Foo (char, int) }; Наследование конструкторовСм. Заявление об использовании. | (начиная с C ++ 11) |
[править] Порядок инициализации
Порядок инициализаторов элементов в списке не имеет значения: фактический порядок инициализации следующий:
1) Если конструктор предназначен для наиболее производного класса, виртуальные базы инициализируются в том порядке, в котором они появляются при обходе объявлений базового класса слева направо в глубину (слева направо относится к внешнему виду). в списках базовых спецификаторов)
2) Затем прямые базы инициализируются в порядке слева направо, как они появляются в списке базовых спецификаторов этого класса
3) Затем нестатические элементы данных инициализируются в порядке объявления в определении класса.
4) Наконец, выполняется тело конструктора
(Примечание: если порядок инициализации контролируется появлением в списках инициализаторов членов различных конструкторов, то деструктор не сможет гарантировать, что порядок уничтожения является обратным порядку построения)
[править] Пример
#include#include <строка> #include <мьютекс> struct Base { int n; }; struct Class: общедоступная база { беззнаковый символ x; беззнаковый char y; std :: mutex m; std :: lock_guard lg; std :: fstream f; std :: string s; Класс (int x) : Base {123}, // инициализировать базовый класс x (x), // x (член) инициализируется x (параметр) y {0}, // y инициализируется значением 0 f {"test.cc ", std :: ios :: app}, // это происходит после инициализации m и lg s (__ func__), // __ func__ доступен, потому что init-list является частью конструктора lg (m), // lg использует m, который уже инициализирован m {} // m инициализируется перед lg, хотя здесь он появляется последним {} // пустой составной оператор Класс (двойной а) : у (а + 1), x (y), // x будет инициализирован перед y, его значение здесь не определено lg (м) {} // инициализатор базового класса не отображается в списке, это // инициализируется по умолчанию (не то же самое, как если бы использовалась Base (), то есть value-init) Класс() try // блок функции-try начинается перед телом функции, которое включает список инициализации : Класс (0.0) // конструктор делегата { // ... } ловить (...) { // исключение произошло при инициализации } }; int main () { Класс c; Класс c1 (1); Класс c2 (0,1); }
[править] Отчеты о дефектах
Следующие ниже отчеты о дефектах, изменяющих поведение, были применены задним числом к ранее опубликованным стандартам C ++.
DR | Применяется к | Поведение, как опубликовано | Правильное поведение |
---|---|---|---|
CWG 1696 | C ++ 98 | могут быть инициализированы временными объектами (время жизни которых закончится в конце ctor) | такой init неправильно сформирован |
[править] Ссылки
- Стандарт C ++ 11 (ISO / IEC 14882: 2011):
- 12.1 Конструкторы [class.ctor]
- 12.6.2 Инициализация баз и членов [class.base.init]
- Стандарт C ++ 98 (ISO / IEC 14882: 1998):
- 12.1 Конструкторы [class.ctor]
- 12.6.2 Инициализация баз и членов [class.base.init]
[править] См. Также
Сопоставление типов структур и объединений из C — учебник
Это второй пост в серии.Самым первым учебным курсом этой серии является Сопоставление примитивных типов данных из C. Есть также указатели функций сопоставления из C и Mapping Strings из руководств C.
В этом руководстве вы узнаете:
Сопоставление структур и объединений типов C
Лучший способ понять сопоставление между Kotlin и C — это попробовать крошечный пример. Мы объявим структуру и объединение на языке C, чтобы увидеть, как они отображаются в Kotlin.
Kotlin / Native поставляется с инструментом cinterop
, инструмент генерирует привязки между языком C и Kotlin.Он использует файл .def
, чтобы указать библиотеку C для импорта. Более подробная информация обсуждается в учебнике «Взаимодействие с библиотеками C.
В предыдущем руководстве вы создали файл lib.h
. На этот раз включите эти объявления непосредственно в файл interop.def
после разделительной строки ---
:
—
typedef struct {
int a;
двойной б;
} MyStruct;
void struct_by_value (MyStruct s) {}
void struct_by_pointer (MyStruct * s) {}
typedef union {
int a;
MyStruct b;
float c;
} MyUnion;
void union_by_value (MyUnion u) {}
void union_by_pointer (MyUnion * u) {}
Взаимодействие .def
достаточно для компиляции и запуска приложения или открытия его в среде IDE. Пришло время создать файлы проекта, открыть проект в IntelliJ IDEA и запустить его.
Проверка сгенерированных API-интерфейсов Kotlin для библиотеки C
Хотя можно использовать командную строку напрямую или путем объединения ее с файлом сценария (например, .sh
или .bat
файлом), этот подход не работает. плохо масштабируется для больших проектов с сотнями файлов и библиотек. Тогда лучше использовать компилятор Kotlin / Native с системой сборки, поскольку он помогает загружать и кэшировать двоичные файлы и библиотеки компилятора Kotlin / Native с транзитивными зависимостями и запускать компилятор и тесты.Kotlin / Native может использовать систему сборки Gradle через плагин kotlin-multiplatform.
Мы рассмотрели основы настройки IDE-совместимого проекта с Gradle в учебнике A Basic Kotlin / Native Application. Пожалуйста, проверьте его, если вы ищете подробные первые шаги и инструкции о том, как запустить новый проект Kotlin / Native и открыть его в IntelliJ IDEA. В этом руководстве мы рассмотрим расширенные возможности взаимодействия с C для Kotlin / Native и мультиплатформенных сборок с Gradle.
Сначала создайте папку проекта. Все пути в этом руководстве будут относиться к этой папке. Иногда недостающие каталоги необходимо создать до того, как можно будет добавить какие-либо новые файлы.
Используйте следующий файл сборки build.gradle (.kts)
Gradle:
plugins {
котлин («мультиплатформенная») версия «1.5.31»
}
репозитории {
mavenCentral ()
}
kotlin {
linuxX64 («native») {// в Linux
// macosX64 («родной») {// на x86_64 macOS
// macosArm64 («native») {// в Apple Silicon macOS
// mingwX64 («native») {// в Windows
val main компиляциями.получающий
val взаимодействие от main.cinterops.creating
двоичные файлы {
исполняемый файл ()
}
}
}
tasks.wrapper {
gradleVersion = «6.7.1»
distributionType = Wrapper.DistributionType.BIN
}
плагины {
id ‘org.jetbrains.kotlin.multiplatform’ версия ‘1.5.31’
}
репозитории {
mavenCentral ()
}
kotlin {
linuxX64 (‘native’) {// в Linux
// macosX64 («родной») {// на x86_64 macOS
// macosArm64 («native») {// в Apple Silicon macOS
// mingwX64 (‘native’) {// в Windows
сборники.main.cinterops {
взаимодействие
}
двоичные файлы {
исполняемый файл ()
}
}
}
wrapper {
gradleVersion = ‘6.7.1’
distributionType = ‘BIN’
}
Файл проекта настраивает взаимодействие C как дополнительный этап сборки. Давайте переместим файл interop.def
в каталог src / nativeInterop / cinterop
. Gradle рекомендует использовать соглашения вместо конфигураций, например, исходные файлы должны находиться в папке src / nativeMain / kotlin
.По умолчанию все символы из C импортируются в пакет interop
, вы можете импортировать весь пакет в наши файлы .kt
. Ознакомьтесь с документацией плагина kotlin-multiplatform, чтобы узнать обо всех различных способах его настройки.
Создайте файл-заглушку src / nativeMain / kotlin / hello.kt
со следующим содержимым, чтобы увидеть, как объявления C видны из Kotlin:
import interop. *
fun main () {
println («Привет, Котлин / Родной!»)
struct_by_value (/ * исправь меня * /)
struct_by_pointer (/ * исправь меня * /)
union_by_value (/ * исправь меня * /)
union_by_pointer (/ * исправь меня * /)
}
Теперь вы готовы открыть проект в IntelliJ IDEA и посмотреть, как исправить пример проекта.При этом посмотрите, как примитивные типы C отображаются в Kotlin / Native.
Примитивные типы в Kotlin
С помощью IntelliJ IDEA Перейти к | Объявление или ошибки компилятора, вы увидите следующий сгенерированный API для функций C, struct
и union
:
fun struct_by_value (s: CValue
fun struct_by_pointer (s: CValuesRef
весело union_by_value (u: CValue
fun union_by_pointer (u: CValuesRef
конструктор класса MyStruct (rawPtr: NativePtr / * = NativePtr * /): CStructVar {
var a: Int
var b: Double
сопутствующий объект: CStructVar.Тип
}
конструктор класса MyUnion (rawPtr: NativePtr / * = NativePtr * /): CStructVar {
var a: Int
val b: MyStruct
var c: Float
сопутствующий объект: CStructVar.Type
}
Вы видите, что cinterop
сгенерировал типы оболочки для наших типов struct
и union
. Для объявлений типов MyStruct
и MyUnion
в C создаются классы Kotlin MyStruct
и MyUnion
соответственно. Оболочки наследуются от базового класса CStructVar
и объявляют все поля как свойства Kotlin.Он использует CValue
для представления параметра структуры по значению и CValuesRef
, чтобы представить передачу указателя на структуру или объединение.
Технически нет разницы между типами struct
и union
на стороне Kotlin. Обратите внимание, что свойства a
, b
и c
класса MyUnion
в Kotlin используют одно и то же место в памяти для чтения / записи своего значения, точно так же, как union
на языке C.
Более подробная информация и расширенные варианты использования представлены в документации
C Interop
Используйте типы структур и объединений из Kotlin
Сгенерированные классы-оболочки для типов C struct
и union
из Kotlin легко использовать. Благодаря сгенерированным свойствам кажется естественным использовать их в коде Kotlin. Пока что единственный вопрос — как создать новый экземпляр для этих классов. Как видно из объявлений MyStruct
и MyUnion
, их конструкторам требуется NativePtr
.Конечно, вы не хотите иметь дело с указателями вручную. Вместо этого вы можете использовать Kotlin API для создания экземпляров этих объектов для нас.
Давайте посмотрим на сгенерированные функции, которые принимают наши MyStruct
и MyUnion
в качестве параметров. Вы видите, что параметры по значению представлены как kotlinx.cinterop.CValue
. А для параметров типизированного указателя вы видите kotlinx.cinterop.CValuesRef
. Kotlin предоставляет нам API для простой работы с обоими типами, давайте попробуем и посмотрим.
Создайте CValue
CValue
используется для передачи параметров по значению в вызов функции C. Используйте функцию cValue
, чтобы создать экземпляр объекта CValue
. Функция требует лямбда-функции с приемником для инициализации базового типа C. Функция объявлена следующим образом:
fun
Теперь пора посмотреть, как использовать cValue
и передавать параметры по значению:
fun callValue () {
val cStruct = cValue
а = 42
б = 3.14
}
struct_by_value (cStruct)
val cUnion = cValue
b.a = 5
b.b = 2,7182
}
union_by_value (cUnion)
}
Создать структуру и объединение как CValuesRef
CValuesRef
используется в Kotlin для передачи параметра типизированного указателя функции C. Во-первых, вам понадобится экземпляр классов MyStruct
и MyUnion
. Создавайте их прямо в родной памяти. Используйте
fun
Функция расширения
на kotlinx.cinterop.NativePlacement
для этого.
NativePlacement
представляет внутреннюю память с функциями, аналогичными malloc
и free
. Существует несколько реализаций NativePlacement
. Глобальный вызывается с помощью kotlinx.cinterop.nativeHeap
и не забудьте вызвать функцию nativeHeap.free (..)
, чтобы освободить память после использования.
Другой вариант — использовать
fun
функция. Он создает краткосрочную область выделения памяти, и все выделения будут очищены автоматически в конце блока
.
Ваш код для вызова функций с указателями будет выглядеть так:
fun callRef () {
memScoped {
val cStruct = alloc
cStruct.a = 42
cStruct.b = 3,14
struct_by_pointer (cStruct.ptr)
val cUnion = alloc
cUnion.b.a = 5
cUnion.b.b = 2,7182
union_by_pointer (cUnion.ptr)
}
}
Обратите внимание, что этот код использует свойство расширения ptr
, которое происходит от типа лямбда-приемника memScoped
, чтобы превратить экземпляры MyStruct
и MyUnion
в собственные указатели.
Классы MyStruct
и MyUnion
имеют указатель на внутреннюю память внизу. Память будет освобождена, когда функция memScoped
завершится, что равно концу ее блока
.Убедитесь, что указатель не используется вне вызова memScoped
. Вы можете использовать Arena ()
или nativeHeap
для указателей, которые должны быть доступны дольше или кэшируются внутри библиотеки C.
Преобразование между CValue и CValuesRef
Конечно, есть случаи использования, когда вам нужно передать структуру в качестве значения одному вызову, а затем передать ту же структуру в качестве ссылки на другой вызов. Это возможно и в Kotlin / Native. Здесь потребуется NativePlacement
.
Давайте теперь посмотрим CValue
сначала превращается в указатель:
fun callMix_ref () {
val cStruct = cValue
а = 42
б = 3,14
}
memScoped {
struct_by_pointer (cStruct.ptr)
}
}
Этот код использует свойство расширения ptr
, которое происходит от типа лямбда-приемника memScoped
, чтобы превратить экземпляры MyStruct
и MyUnion
в собственные указатели. Эти указатели действительны только внутри блока memScoped
.
Для обратного преобразования, чтобы превратить указатель в переменную по значению, мы вызываем функцию расширения readValue ()
:
fun callMix_value () {
memScoped {
val cStruct = alloc
cStruct.a = 42
cStruct.b = 3,14
struct_by_value (cStruct.readValue ())
}
}
Выполните код
Теперь, когда вы узнали, как использовать объявления C в своем коде, вы готовы опробовать это на реальном примере. Давайте исправим код и посмотрим, как он работает, вызвав задачу runDebugExecutableNative
Gradle в среде IDE или используя следующую консольную команду:
./ gradlew runDebugExecutableNative
Последний код в файле hello.kt
может выглядеть следующим образом:
import interop. *
импорт kotlinx.cinterop.alloc
import kotlinx.cinterop.cValue
import kotlinx.cinterop.memScoped
импорт kotlinx.cinterop.ptr
import kotlinx.cinterop.readValue
fun main () {
println («Привет, Котлин / Родной!»)
val cUnion = cValue
b.a = 5
b.b = 2,7182
}
memScoped {
union_by_value (cUnion)
union_by_pointer (cUnion.ptr)
}
memScoped {
val cStruct = alloc
а = 42
б = 3.14
}
struct_by_value (cStruct.readValue ())
struct_by_pointer (cStruct.ptr)
}
}
Следующие шаги
Продолжите изучение типов языка C и их представления в Kotlin / Native в соответствующих руководствах:
Документация C Interop охватывает более сложные сценарии взаимодействия.
Последнее изменение: 08 сентября 2021 г.
Что такое странный синтаксис элемента-двоеточия («:») в конструкторе? | Сай Камеш
Это список инициализации участников .
Это инициализация конструктора. Это правильный способ инициализации членов в конструкторе класса, поскольку он предотвращает вызов конструктора по умолчанию.
программ:
тип 1: обычно в программе мы делаем
struct TestStruct {
int id;
TestStruct ()
{
id = 42;
}
};
тип 2: мы пишем то же самое, используя список инициализаторов,
struct TestStruct {
int id;
двойной номер;
TestStruct (): id (42) {}
};
Еще один пример:
Point (int i = 0, int j = 0): x (i), y (j) {}
Указанное выше использование списка инициализаторов в качестве конструктора необязательно. также может быть записано как:
Point (int i = 0, int j = 0) {
x = i;
y = j;
}
простая программа для списка инициализаторов.
CPP поддерживает список инициализаторов. Java не поддерживает список инициализаторов, потому что
В C ++ списки инициализаторов необходимы из-за нескольких языковых функций, которые либо отсутствуют в Java, либо работают по-другому в Java:
- const: в C ++ можно определить поля, помеченные как const, которые не могут быть присвоены и должны быть инициализированы в списке инициализаторов. В Java есть поля final, но вы можете назначить их полям final в теле конструктора.В C ++ присвоение константному полю в конструкторе недопустимо.
- Ссылки: В C ++ ссылки (в отличие от указателей) должны быть инициализированы для привязки к какому-либо объекту. Создание ссылки без инициализатора запрещено. В C ++ это указывается с помощью списка инициализаторов, поскольку, если бы вы ссылались на ссылку в теле конструктора без его предварительной инициализации, вы бы использовали неинициализированную ссылку. В Java объектные ссылки ведут себя как указатели C ++ и могут быть присвоены им после создания.В противном случае они просто по умолчанию равны нулю.
- Прямые подобъекты. В C ++ объект может содержать объект непосредственно в виде полей, тогда как в Java объекты могут содержать только ссылок на эти объекты. То есть в C ++, если вы объявляете объект, который имеет строку в качестве члена, пространство для хранения этой строки встроено непосредственно в пространство для самого объекта, тогда как в Java вы просто получаете место для ссылки на какую-то другую строку объект хранится в другом месте. Следовательно, C ++ должен предоставить вам возможность присвоить этим подобъектам начальные значения, иначе они просто останутся неинициализированными.По умолчанию он использует конструктор по умолчанию для этих типов, но если вы хотите использовать другой конструктор или конструктор по умолчанию недоступен, список инициализаторов дает вам способ обойти это. В Java вам не нужно беспокоиться об этом, потому что ссылки по умолчанию будут иметь значение null, и вы можете назначить их для ссылки на объекты, на которые вы действительно хотите, чтобы они ссылались. Если вы хотите использовать конструктор, отличный от конструктора по умолчанию, вам не нужен специальный синтаксис для него; просто установите ссылку на новый объект, инициализированный с помощью соответствующего конструктора.
- В тех немногих случаях, когда Java может потребоваться списки инициализаторов (например, для вызова конструкторов суперкласса или присвоения значений по умолчанию его полям), это обрабатывается с помощью двух других языковых функций: суперключевое слово для вызова конструкторов суперкласса и тот факт, что Java объекты могут присваивать своим полям значения по умолчанию в той точке, в которой они объявлены.