Что такое параметризированный конструктор с
Перейти к содержимому

Что такое параметризированный конструктор с

  • автор:

Конструкторы (C++)

Чтобы настроить, как класс инициализирует его члены или вызывать функции при создании объекта класса, определите конструктор. Конструкторы имеют имена, совпадающие с именами классов, и не имеют возвращаемых значений. Можно определить столько перегруженных конструкторов, сколько необходимо для настройки инициализации различными способами. Как правило, конструкторы имеют открытый доступ, поэтому код за пределами определения класса или иерархии наследования может создавать объекты класса. Но вы также можете объявить конструктор как protected или private .

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

class Box < public: // Default constructor Box() <>// Initialize a Box with equal dimensions (i.e. a cube) explicit Box(int i) : m_width(i), m_length(i), m_height(i) // member init list <> // Initialize a Box with custom dimensions Box(int width, int length, int height) : m_width(width), m_length(length), m_height(height) <> int Volume() < return m_width * m_length * m_height; >private: // Will have value of 0 when default constructor is called. // If we didn't zero-init here, default constructor would // leave them uninitialized with garbage values. int m_width< 0 >; int m_length< 0 >; int m_height< 0 >; >; 

При объявлении экземпляра класса компилятор выбирает конструктор для вызова на основе правил разрешения перегрузки:

int main() < Box b; // Calls Box() // Using uniform initialization (preferred): Box b2 ; // Calls Box(int) Box b3 ; // Calls Box(int, int, int) // Using function-style notation: Box b4(2, 4, 6); // Calls Box(int, int, int) > 
  • Конструкторы могут быть объявлены как inline , friendexplicit или constexpr .
  • Конструктор может инициализировать объект, объявленный как const или volatileconst volatile . Объект становится const после завершения конструктора.
  • Чтобы определить конструктор в файле реализации, присвойте ему полное имя, как и любая другая функция-член: Box::Box()

Списки инициализатора элементов

Конструктор может иметь список инициализаторов элементов, который инициализирует элементы класса перед запуском текста конструктора. (Список инициализаторов элементов не совпадает со списком инициализаторов типа std::initializer_list .)

Предпочесть списки инициализаторов элементов вместо назначения значений в тексте конструктора. Список инициализаторов элементов напрямую инициализирует элементы. В следующем примере показан список инициализатора элементов, состоящий из всех identifier(argument) выражений после двоеточия:

 Box(int width, int length, int height) : m_width(width), m_length(length), m_height(height) <> 

Идентификатор должен ссылаться на член класса; он инициализирован со значением аргумента. Аргумент может быть одним из параметров конструктора, вызовом функции или одним std::initializer_list из них.

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

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

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

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

class Box < public: Box() < /*perform any required default initialization steps*/>// All params have default values Box (int w = 1, int l = 1, int h = 1): m_width(w), m_height(h), m_length(l)<> . > 

Конструкторы по умолчанию являются одной из специальных функций-членов. Если конструкторы не объявляются в классе, компилятор предоставляет неявный inline конструктор по умолчанию.

#include using namespace std; class Box < public: int Volume() private: int m_width < 0 >; int m_height < 0 >; int m_length < 0 >; >; int main() < Box box1; // Invoke compiler-generated constructor cout 

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

Можно запретить компилятору создавать неявный конструктор по умолчанию, определив его как deleted:

 // Default constructor Box() = delete; 

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

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

class myclass<>; int main() < myclass mc(); // warning C4930: prototyped function not called (was a variable definition intended?) >

Это оператор является примером проблемы "Большинство Vexing Parse". Можно интерпретировать myclass md(); как объявление функции или как вызов конструктора по умолчанию. Так как средства синтаксического анализа C++ предпочитают объявления по сравнению с другими вещами, выражение рассматривается как объявление функции. Дополнительные сведения см. в статье Википедии Наиболее неоднозначный анализ.

Если объявлены какие-либо конструкторы, отличные от по умолчанию, компилятор не предоставляет конструктор по умолчанию:

class Box < public: Box(int width, int length, int height) : m_width(width), m_length(length), m_height(height)<>private: int m_width; int m_length; int m_height; >; int main()< Box box1(1, 2, 3); Box box2< 2, 3, 4 >; Box box3; // C2512: no appropriate default constructor available > 

Если у класса нет конструктора по умолчанию, массив объектов этого класса не может быть создан только с помощью синтаксиса квадратной скобки. Например, учитывая предыдущий блок кода, массив Boxes нельзя объявить следующим образом:

Box boxes[3]; // C2512: no appropriate default constructor available 

Но для инициализации массива объектов Box можно использовать набор списков инициализаторов:

Box boxes[3]< < 1, 2, 3 >, < 4, 5, 6 >, < 7, 8, 9 >>; 

Дополнительные сведения см. в статье Инициализаторы.

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

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

У конструктора копий может быть одна из следующих сигнатур:

 Box(Box& other); // Avoid if possible--allows modification of other. Box(const Box& other); Box(volatile Box& other); Box(volatile const Box& other); // Additional parameters OK if they have default values Box(Box& other, int i = 42, string label = "Box"); 

При определении конструктора копий необходимо также определить оператор присваивания копированием (=). Дополнительные сведения см. в статьях Присваивание и Конструкторы копий и операторы присваивания копированием.

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

 Box (const Box& other) = delete; 

При попытке копирования объекта возвращается ошибка C2280: предпринята попытка ссылки на удаленную функцию.

Конструкторы перемещения

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

Box(Box&& other); 

Компилятор выбирает конструктор перемещения, когда объект инициализирован другим объектом того же типа, если другой объект будет уничтожен и больше не нуждается в его ресурсах. В следующем примере показан один случай, когда конструктор перемещения выбирается с помощью разрешения перегрузки. В конструкторе, который вызывает get_Box() , возвращаемое значение будет xvalue (значение с истекающим сроком действия). Поэтому он не назначается какой-либо переменной и поэтому выходит из область. Чтобы создать основу для этого примера, пусть у объекта Box будет большой вектор строк, которые представляют его содержимое. Вместо копирования вектора и его строк конструктор перемещения "крадет" его из значения с истекающим сроком действия box, чтобы вектор теперь принадлежал новому объекту. Необходим только вызов std::move , так как классы vector и string реализуют собственные конструкторы перемещения.

#include #include #include #include using namespace std; class Box < public: Box() < std::cout Box(int width, int height, int length) : m_width(width), m_height(height), m_length(length) < std::cout Box(Box& other) : m_width(other.m_width), m_height(other.m_height), m_length(other.m_length) < std::cout Box(Box&& other) : m_width(other.m_width), m_height(other.m_height), m_length(other.m_length) < m_contents = std::move(other.m_contents); std::cout int Volume() < return m_width * m_height * m_length; >void Add_Item(string item) < m_contents.push_back(item); >void Print_Contents() < for (const auto& item : m_contents) < cout > private: int m_width< 0 >; int m_height< 0 >; int m_length< 0 >; vector m_contents; >; Box get_Box() < Box b(5, 10, 18); // "int,int,int" b.Add_Item("Toupee"); b.Add_Item("Megaphone"); b.Add_Item("Suit"); return b; >int main() < Box b; // "default" Box b1(b); // "copy" Box b2(get_Box()); // "move" cout > ch; // keep window open return 0; > 

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

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

Дополнительные сведения о написании нетривиального конструктора перемещения см. в статье Конструкторы перемещения и операторы присваивания перемещением.

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

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

class Box2 < public: Box2() = delete; Box2(const Box2& other) = default; Box2& operator=(const Box2& other) = default; Box2(Box2&& other) = default; Box2& operator=(Box2&& other) = default; //. >; 

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

Конструктор может быть объявлен как constexpr в следующих случаях:

  • он либо объявлен как стандартный, либо он удовлетворяет всем условиям для функций constexpr в целом;
  • у класса нет виртуальных базовых классов;
  • каждый из параметров является типом литерала;
  • Текст не является блоком пробной функции;
  • инициализированы все нестатические члены данных и подобъекты базового класса;
  • если класс является a) объединением с вариантами членов или б) имеет анонимные объединения, то инициализируется только одно из объединений;
  • каждый нестатический член данных типа класса, а все подобъекты базового класса имеют конструктор constexpr

Конструкторы списков инициализаторов

 Box(initializer_list list, int w = 0, int h = 0, int l = 0) : m_contents(list), m_width(w), m_height(h), m_length(l) <> 

Затем создайте объекты Box следующим образом:

 Box b< "apples", "oranges", "pears" >; // or . Box b2(initializer_list < "bread", "cheese", "wine" >, 2, 4, 6); 

Явные конструкторы

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

Box(int size): m_width(size), m_length(size), m_height(size)<> 

Можно инициализировать Box следующим образом:

Box b = 42; 

Или передать целое значение функции, принимающей объект Box:

class ShippingOrder < public: ShippingOrder(Box b, double postage) : m_box(b), m_postage(postage)<>private: Box m_box; double m_postage; > //elsewhere. ShippingOrder so(42, 10.8); 

В некоторых случаях подобные преобразования могут быть полезны, однако чаще всего они могут привести к незаметным, но серьезным ошибкам в вашем коде. В качестве общего правила следует использовать explicit ключевое слово конструктора (и определяемых пользователем операторов), чтобы предотвратить такое неявное преобразование типов:

explicit Box(int size): m_width(size), m_length(size), m_height(size)<> 

Когда конструктор является явным, эта строка вызывает ошибку компилятора: ShippingOrder so(42, 10.8); . Дополнительные сведения см. в разделе Заданные пользователем преобразования типов (C++).

Порядок строительства

Конструктор выполняет свою работу в следующем порядке.

  1. Вызывает конструкторы базовых классов и членов в порядке объявления.
  2. Если класс является производным от виртуальных базовых классов, конструктор инициализирует указатели виртуальных базовых классов объекта.
  3. Если класс имеет или наследует виртуальные функции, конструктор инициализирует указатели виртуальных функций объекта. Указатели виртуальных функций указывают на таблицу виртуальных функций класса, чтобы обеспечить правильную привязку вызовов виртуальных функций к коду.
  4. Выполняет весь код в теле функции.

В следующем примере показан порядок, в котором конструкторы базовых классов и членов вызываются в конструкторе для производного класса. Сначала вызывается базовый конструктор. Затем члены базового класса инициализированы в порядке, в котором они отображаются в объявлении класса. Наконец, вызывается производный конструктор.

#include using namespace std; class Contained1 < public: Contained1() < cout >; class Contained2 < public: Contained2() < cout >; class Contained3 < public: Contained3() < cout >; class BaseContainer < public: BaseContainer() < cout private: Contained1 c1; Contained2 c2; >; class DerivedContainer : public BaseContainer < public: DerivedContainer() : BaseContainer() < cout private: Contained3 c3; >; int main()

Появятся следующие выходные данные.

Contained1 ctor Contained2 ctor BaseContainer ctor Contained3 ctor DerivedContainer ctor 

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

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

class Box < public: Box(int width, int length, int height)< m_width = width; m_length = length; m_height = height; >private: int m_width; int m_length; int m_height; >; class StorageBox : public Box < public: StorageBox(int width, int length, int height, const string label&) : Box(width, length, height)< m_label = label; >private: string m_label; >; int main()

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

  1. Отменяется код в теле функции конструктора.
  2. Объекты базовых классов и объекты-члены удаляются в порядке, обратном объявлению.
  3. Если конструктор не делегируется, все полностью созданные объекты базового класса и члены уничтожаются. Однако, поскольку сам объект не полностью построен, деструктор не выполняется.

Производные конструкторы и расширенная инициализация агрегатов

Если конструктор базового класса не является открытым, но доступен для производного класса, то вы не можете использовать пустые фигурные скобки для инициализации объекта производного типа в /std:c++17 режиме, а затем в Visual Studio 2017 и более поздних версий.

В следующем примере показана соответствующая реакция на событие в C++14:

struct Derived; struct Base < friend struct Derived; private: Base() <>>; struct Derived : Base <>; Derived d1; // OK. No aggregate init involved. Derived d2 <>; // OK in C++14: Calls Derived::Derived() // which can call Base ctor. 

В C++17 Derived теперь считается агрегатным типом. Это означает, что инициализация Base через закрытый конструктор по умолчанию происходит непосредственно как часть расширенного правила агрегатной инициализации. Ранее частный Base конструктор был вызван с помощью Derived конструктора, и он успешно выполнен из-за friend объявления.

В следующем примере показано поведение C++17 в Visual Studio 2017 и более поздних версиях в режиме /std:c++17 :

struct Derived; struct Base < friend struct Derived; private: Base() <>>; struct Derived : Base < Derived() <>// add user-defined constructor // to call with <> initialization >; Derived d1; // OK. No aggregate init involved. Derived d2 <>; // error C2248: 'Base::Base': can't access // private member declared in class 'Base' 

Конструкторы классов с множественным наследованием

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

#include using namespace std; class BaseClass1 < public: BaseClass1() < cout >; class BaseClass2 < public: BaseClass2() < cout >; class BaseClass3 < public: BaseClass3() < cout >; class DerivedClass : public BaseClass1, public BaseClass2, public BaseClass3 < public: DerivedClass() < cout >; int main()

Должны выводиться следующие выходные данные:

BaseClass1 ctor BaseClass2 ctor BaseClass3 ctor DerivedClass ctor 

Делегирующие конструкторы

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

class Box < public: // Default constructor Box() <>// Initialize a Box with equal dimensions (i.e. a cube) Box(int i) : Box(i, i, i) // delegating constructor <> // Initialize a Box with custom dimensions Box(int width, int length, int height) : m_width(width), m_length(length), m_height(height) <> //. rest of class as before >; 

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

Наследование конструкторов (C++11)

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

#include using namespace std; class Base < public: Base() < cout Base(const Base& other) < cout explicit Base(int i) : num(i) < cout explicit Base(char c) : letter(c) < cout private: int num; char letter; >; class Derived : Base < public: // Inherit all constructors from Base using Base::Base; private: // Can't initialize newMember from Base constructors. int newMember< 0 >; >; int main() < cout /* Output: Derived d1(5) calls: Base(int) Derived d1('c') calls: Base(char) Derived d3 = d2 calls: Base(Base&) Derived d4 calls: Base()*/ 

Visual Studio 2017 и более поздних версий: оператор using в режиме и более поздних версиях преобразуется в /std:c++17 область все конструкторы базового класса, кроме тех, которые имеют идентичную сигнатуру конструкторам в производном классе. Как правило, рекомендуется использовать наследующие конструкторы, когда производный класс не объявляет новых элементов данных или конструкторов.

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

template < typename T >class Derived : T < using T::T; // declare the constructors from T // . >; 

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

Конструкторы и составные классы

Классы, содержащие члены типа класса, называются составными классами. При создании члена типа класса составного класса конструктор вызывается перед собственным конструктором класса. Если у содержащегося класса нет конструктора по умолчанию, необходимо использовать список инициализации в конструкторе составного класса. В предыдущем примере StorageBox при присвоении типу переменной-члена m_label нового класса Label необходимо вызвать конструктор базового класса и инициализировать переменную m_label в конструкторе StorageBox :

class Label < public: Label(const string& name, const string& address) < m_name = name; m_address = address; >string m_name; string m_address; >; class StorageBox : public Box < public: StorageBox(int width, int length, int height, Label label) : Box(width, length, height), m_label(label)<>private: Label m_label; >; int main()< // passing a named Label Label label1< "some_name", "some_address" >; StorageBox sb1(1, 2, 3, label1); // passing a temporary label StorageBox sb2(3, 4, 5, Label< "another name", "another address" >); // passing a temporary label as an initializer list StorageBox sb3(1, 2, 3, ); > 

В этом разделе

  • Конструкторы копий и операторы присваивания копированием
  • Конструкторы перемещения и операторы присваивания перемещением
  • Делегирующие конструкторы

Конструкторы (руководство по программированию на C#)

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

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

  1. Для полей экземпляра задано значение 0. Обычно это делается средой выполнения.
  2. Запускаются инициализаторы полей. Инициализаторы полей в большинстве производных типов выполняются.
  3. Запускаются инициализаторы полей базового типа. Инициализаторы полей начинаются с прямой базы с каждым базовым типом System.Object.
  4. Запускаются конструкторы базовых экземпляров. Все конструкторы экземпляров, начиная с Object.Object каждого базового класса, до прямого базового класса.
  5. Выполняется конструктор экземпляра. Конструктор экземпляра для выполнения типа.
  6. Запускаются инициализаторы объектов. Если выражение включает в себя инициализаторы объектов, они выполняются после запуска конструктора экземпляра. Инициализаторы объектов выполняются в текстовом порядке.

Предыдущие действия выполняются при инициализации нового экземпляра. Если для нового экземпляра задано значение, default все поля экземпляра struct имеют значение 0.

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

Синтаксис конструктора

Конструктор — это метод, имя которого совпадает с именем его типа. Его сигнатура метода включает только дополнительный модификатор доступа, имя метода и его список параметров; он не включает возвращаемый тип. В приведенном ниже примере демонстрируется конструктор для класса с именем Person .

public class Person < private string last; private string first; public Person(string lastName, string firstName) < last = lastName; first = firstName; >// Remaining implementation of Person class. > 

Если конструктор поддерживает реализацию в виде оператора, можно использовать определение тела выражения. В следующем примере определяется класс Location , конструктор которого имеет один строковый параметр name. Определение тела выражения присваивает аргумент полю locationName .

public class Location < private string locationName; public Location(string name) =>Name = name; public string Name < get =>locationName; set => locationName = value; > > 

Статические конструкторы

В приведенных выше примерах показаны конструкторы экземпляров, которые создают новый объект. В классе или структуре также может быть статический конструктор, который инициализирует статические члены типа. Статические конструкторы не имеют параметров. Если вы не предоставили статический конструктор для инициализации статических полей, компилятор C# инициализирует статические поля значениями по умолчанию, как показано в статье Значения по умолчанию типов C#.

В следующем примере статический конструктор используется для инициализации статического поля.

public class Child : Person < private static int maximumAge; public Child(string lastName, string firstName) : base(lastName, firstName) < >static Child() => maximumAge = 18; // Remaining implementation of Child class. > 

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

public class Child : Person < private static int maximumAge; public Child(string lastName, string firstName) : base(lastName, firstName) < >static Child() => maximumAge = 18; // Remaining implementation of Child class. > 

Дополнительные сведения и примеры см. в разделе Статические конструкторы.

В этом разделе

  • Использование конструкторов
  • Конструкторы экземпляров
  • Частные конструкторы
  • Статические конструкторы
  • Практическое руководство. Создание конструктора копий

См. также

  • Руководство по программированию на C#
  • Система типов C#
  • Методы завершения
  • static
  • Why Do Initializers Run In The Opposite Order As Constructors? Part One (Почему инициализаторы выполняются в порядке, обратном действию конструкторов? Часть 1)

Совместная работа с нами на GitHub

Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.

Обратная связь

Coming soon: Throughout 2024 we will be phasing out GitHub Issues as the feedback mechanism for content and replacing it with a new feedback system. For more information see: https://aka.ms/ContentUserFeedback.

Отправить и просмотреть отзыв по

Дополнительные ресурсы

Значок отказа согласно Закону Калифорнии о защите конфиденциальности потребителей (CCPA)

  • Светлая
  • Темная
  • Высокая контрастность
  • Предыдущие версии
  • Блог
  • Участие в доработке
  • Конфиденциальность
  • Условия использования
  • Товарные знаки
  • © Microsoft 2024

Что такое параметризированный конструктор с

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

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

Person tom = new Person(); // Создание объекта класса Person tom.Print(); // Имя: Tom Возраст: 37 class Person < public string name; public int age; public Person() < Console.WriteLine("Создание объекта Person"); name = "Tom"; age = 37; >public void Print() < Console.WriteLine($"Имя: Возраст: "); > >

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

public Person()

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

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

Person tom = new Person(); // Создание объекта Person

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

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

Person tom = new Person(); // вызов 1-ого конструктора без параметров Person bob = new Person("Bob"); //вызов 2-ого конструктора с одним параметром Person sam = new Person("Sam", 25); // вызов 3-его конструктора с двумя параметрами tom.Print(); // Имя: Неизвестно Возраст: 18 bob.Print(); // Имя: Bob Возраст: 18 sam.Print(); // Имя: Sam Возраст: 25 class Person < public string name; public int age; public Person() < name = "Неизвестно"; age = 18; >// 1 конструктор public Person(string n) < name = n; age = 18; >// 2 конструктор public Person(string n, int a) < name = n; age = a; >// 3 конструктор public void Print() < Console.WriteLine($"Имя: Возраст: "); > >

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

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

Имя: Неизвестно Возраст: 18 Имя: Bob Возраст: 18 Имя: Sam Возраст: 25

Стоит отметить, что начиная с версии C# 9 мы можем сократить вызов конструктора, убрав из него название типа:

Person tom = new (); // аналогично new Person(); Person bob = new ("Bob"); // аналогично new Person("Bob"); Person sam = new ("Sam", 25); // аналогично new Person("Sam", 25);

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

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

Person sam = new("Sam", 25); sam.Print(); // Имя: Sam Возраст: 25 class Person < public string name; public int age; public Person() < name = "Неизвестно"; age = 18; >public Person(string name) < this.name = name; age = 18; >public Person(string name, int age) < this.name = name; this.age = age; >public void Print() => Console.WriteLine($"Имя: Возраст: "); >

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

this.name = name;

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

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

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

class Person < public string name; public int age; public Person() : this("Неизвестно") // первый конструктор < >public Person(string name) : this(name, 18) // второй конструктор < >public Person(string name, int age) // третий конструктор < this.name = name; this.age = age; >public void Print() => Console.WriteLine($"Имя: Возраст: "); >

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

public Person(string name) : this(name, 18)

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

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

Person tom = new(); Person bob = new("Bob"); Person sam = new("Sam", 25); tom.Print(); // Имя: Неизвестно Возраст: 18 bob.Print(); // Имя: Bob Возраст: 18 sam.Print(); // Имя: Sam Возраст: 25 class Person < public string name; public int age; public Person(string name = "Неизвестно", int age = 18) < this.name = name; this.age = age; >public void Print() => Console.WriteLine($"Имя: Возраст: "); >

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

Первичные конструкторы

Начиная с версии C# 12 в язык C# была добавлена поддержка первичных конструкторов (Primary constructors). Первичные конструкторы позволяют добавлять параметры к определению класса и использовать эти параметры внутри класса:

var tom = new Person("Tom", 38); Console.WriteLine(tom); public class Person(string name, int age) < public Person(string name) : this(name, 18) < >public void Print() => Console.WriteLine($"name: , age: "); >

Здесь для класса Person определен первичный конструктор с двумя параметрами - name и age. Эти параметры применяются для используются в методе Print.

За кадром для каждого параметра первичного конструктора в классе создается приватное поле, которое хранит значение параметра. Благодаря этому они могут использоваться в теле класса.

Кроме первичных конструкторов класс может определять дополнительные конструкторы, как примере выше. Но эти дополнительные конструкторы должны вызывать первичный конструктор:

public Person(string name) : this(name, 18)

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

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

Person tom = new Person < name = "Tom", age = 31 >; // или так // Person tom = new() < name = "Tom", age = 31 >; tom.Print(); // Имя: Tom Возраст: 31

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

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

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

Person tom = new Person < name = "Tom", company = < title = "Microsoft">>; tom.Print(); // Имя: Tom Компания: Microsoft class Person < public string name; public Company company; public Person() < name = "Undefined"; company = new Company(); >public void Print() => Console.WriteLine($"Имя: Компания: "); > class Company

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

company =

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

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

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

class Person < string name; int age; public Person(string name, int age) < this.name = name; this.age = age; >public void Deconstruct(out string personName, out int personAge) < personName = name; personAge = age; >>

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

Person person = new Person("Tom", 33); (string name, int age) = person; Console.WriteLine(name); // Tom Console.WriteLine(age); // 33

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

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

Person person = new Person("Tom", 33); string name; int age; person.Deconstruct(out name, out age);

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

Person person = new Person("Tom", 33); (_, int age) = person; Console.WriteLine(age); // 33

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

Параметризированные конструкторы

Конструкторам можно передавать аргументы. Обычно эти аргументы используются для того, чтобы помочь инициализировать создаваемый объект. Для того, чтобы создать параметризированный конструктор, достаточно попросту добавить параметры, как это делается для любой другой фун­кции. При определении тела конструктора, т.е. кода, который реализует конструктор, эти пара­метры используются для инициализации объекта. Например, можно усовершенствовать класс queue путем передачи ему в качестве аргумента числа, слу­жащего идентификатором объекта. Объявление класса queue будет иметь следующий вид:

// создание класса очередь
class queue int q[100];
int sloс, rloc;
int who; // содержит идентификатор очереди
public:
queue(int id); // параметризированный конструктор
~queue (); // деструктор
void qput(int i);
int qget();
>

Переменная who используется для хранения значения идентификатора, служащего для иденти­фикации объекта типа queue. Его значение определяется аргументом id при создании объекта. Конструктор queue() будет выглядеть следующим образом:

Для передачи аргумента конструктору необходимо задать его значение при объявлении объек­та. С++ поддерживает два способа решения этой задачи. Первый из них выглядит следующим образом:

queue а = queue(101);

Здесь конструктор класса queue вызывается непосредственно с передачей ему значения 101. Значе­нием переменной а служит сам созданный объект.

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

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

Общая форма для передачи аргументов конструктору имеет следующий вид:

Здесь список_аргументов представляет собой список аргументов, разделенных запятыми. Эти ар­гументы и передаются конструктору.

Следующая версия программы queue демонстрирует передачу аргументов конструктору:

#include
// создание класса очередь
class queue int q[100];
int sloс, rloc;
int who; // содержит идентификатор очереди
public:
queue (int id); // параметризированный конструктор
~queue(); // деструктор
void qput(int i);
int qget();
> ;
/ / конструктор
queue::queue(int id)
sloс = rloc = 0;
who = id;
cout >
/ / деструктор
queue::~queue()
cout >
void queue::qput(int i)
if (sloс == 99) cout return;
>
sloc++;
q[sloc] = i;
>
int queue::qget()
if (rloc == sloс) cout return 0;
>
rloc++;
return q[rloc];
>
int main()
queue a(1), b(2); // создание двух объектов типа queue
a.qput(10);
b.qput(19);
a.qput(20);
b.qput(1);
cout cout cout cout return 0;
>

Эта программа выводит следующий текст:

Queue 1 initialized.
Queue 2 initialized.
10 20 19 1
Queue 2 destroyed.
Queue 1 destroyed.

Посмотрев на функцию main(), мы увидим, что объект а имеет в качестве идентификатора 1, а объект b — 2.

Хотя в данном примере при создании объекта ему передается только один аргумент, можно передавать также и несколько аргументов. Например, в следующей программе объектам типа widget передаются два значения:

#include
class widget int i;
int j;
public:
widget (int a, int b);
void put_widget();
>;
widget::widget (int a, int b)
i = a;
j = b;
>
void widget::put__widget ()
cout >
int main()
widget x(10, 20),
y(0, 0);
x.put_widget();
у.put_widget();
return 0;
>

Эта программа выведет на экран значения
10 20
0 0

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

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