Урок №141. конструктор копирования

JS Tutorial

JS HOMEJS IntroductionJS Where ToJS OutputJS StatementsJS SyntaxJS CommentsJS VariablesJS OperatorsJS ArithmeticJS AssignmentJS Data TypesJS FunctionsJS ObjectsJS EventsJS StringsJS String MethodsJS NumbersJS Number MethodsJS ArraysJS Array MethodsJS Array SortJS Array IterationJS DatesJS Date FormatsJS Date Get MethodsJS Date Set MethodsJS MathJS RandomJS BooleansJS ComparisonsJS ConditionsJS SwitchJS Loop ForJS Loop WhileJS BreakJS Type ConversionJS BitwiseJS RegExpJS ErrorsJS ScopeJS HoistingJS Strict ModeJS this KeywordJS LetJS ConstJS Arrow FunctionJS DebuggingJS Style GuideJS Best PracticesJS MistakesJS PerformanceJS Reserved WordsJS VersionsJS Version ES5JS Version ES6JS JSON

Решение в C++11

Неплохо было бы, чтобы конструктор вызывал конструктор Boo() для выполнения :


class Boo { public: Boo() { // Часть кода X } Boo(int value) { Boo(); // используем конструктор выше для выполнения части кода X // Часть кода Y } };

1 2 3 4 5 6 7 8 9 10 11 12 13 14

classBoo

{

public

Boo()

{

// Часть кода X

}

Boo(intvalue)

{

Boo();// используем конструктор выше для выполнения части кода X

// Часть кода Y

}

};

Или:

class Boo { public: Boo() { // часть кода X } Boo(int value): Boo() // используем конструктор выше для выполнения части кода X { // часть кода Y } };

1 2 3 4 5 6 7 8 9 10 11 12 13

classBoo

{

public

Boo()

{

// часть кода X

}

Boo(intvalue)Boo()// используем конструктор выше для выполнения части кода X

{

// часть кода Y

}

};

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

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

Did You Know?

As you can see above, JavaScript has object versions of the primitive data types , , and . But there is no reason to create complex objects. Primitive values are much faster.

ALSO:

Use object literals instead of .

Use string literals instead of .

Use number literals instead of .

Use boolean literals instead of .

Use array literals instead of .

Use pattern literals instead of .

Use function expressions instead of .

Example

var x1 = {};            // new object var x2 = «»;            // new primitive string var x3 = 0;             // new primitive number var x4 = false;         // new primitive boolean var x5 = [];            // new array object var x6 = /()/           // new regexp object var x7 = function(){};  // new function object

Использование отдельного метода

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

class Boo { private: void DoX() { // Часть кода X } public: Boo() { DoX(); } Boo(int nValue) { DoX(); // Часть кода Y } };

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

classBoo

{

private

voidDoX()

{

// Часть кода X

}

public

Boo()

{

DoX();

}

Boo(intnValue)

{

DoX();

// Часть кода Y

}

};

Здесь мы свели дублирование кода к минимуму.

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

class Boo { public: Boo() { Init(); } Boo(int value) { Init(); // Делаем что-либо с value } void Init() { // Код инициализации Boo } };

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19

classBoo

{

public

Boo()

{

Init();

}

Boo(intvalue)

{

Init();

// Делаем что-либо с value

}

voidInit()

{

// Код инициализации Boo

}

};

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

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

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

#include <cassert> class Fraction { private: int m_numerator; int m_denominator; public: Fraction() // конструктор по умолчанию { m_numerator = 0; m_denominator = 1; } // Конструктор с двумя параметрами, один из которых имеет значение по умолчанию Fraction(int numerator, int denominator=1) { assert(denominator != 0); m_numerator = numerator; m_denominator = denominator; } int getNumerator() { return m_numerator; } int getDenominator() { return m_denominator; } double getValue() { return static_cast<double>(m_numerator) / m_denominator; } };

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

#include <cassert>

classFraction

{

private

intm_numerator;

intm_denominator;

public

Fraction()// конструктор по умолчанию

{

m_numerator=;

m_denominator=1;

}

// Конструктор с двумя параметрами, один из которых имеет значение по умолчанию

Fraction(intnumerator,intdenominator=1)

{

assert(denominator!=);

m_numerator=numerator;

m_denominator=denominator;

}

intgetNumerator(){returnm_numerator;}

intgetDenominator(){returnm_denominator;}

doublegetValue(){returnstatic_cast<double>(m_numerator)m_denominator;}

};

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

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

Как использовать конструктор с параметрами? Всё просто! Прямая инициализация:

int a(7); // прямая инициализация Fraction drob(4, 5); // инициализируем напрямую, вызывается конструктор Fraction(int, int)

1 2


inta(7);// прямая инициализация

Fraction drob(4,5);// инициализируем напрямую, вызывается конструктор Fraction(int, int)

Здесь мы инициализировали нашу дробь числами и , результат — !

В C++11 мы также можем использовать uniform-инициализацию:

int a { 7 }; // uniform-инициализация Fraction drob {4, 5}; // uniform-инициализация, вызывается конструктор Fraction(int, int)

1 2

inta{7};// uniform-инициализация

Fractiondrob{4,5};// uniform-инициализация, вызывается конструктор Fraction(int, int)

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

Fraction seven(7); // вызывается конструктор Fraction(int, int), второй параметр использует значение по умолчанию

1 Fraction seven(7);// вызывается конструктор Fraction(int, int), второй параметр использует значение по умолчанию

Значения по умолчанию для конструкторов работают точно так же, как и для любой другой функции, поэтому в вышеприведенном примере, когда мы вызываем , вызывается , второй параметр которого равен (значение по умолчанию).

Правило: Используйте прямую инициализацию или uniform-инициализацию с объектами ваших классов.

Еще один пример

Рассмотрим еще пару классов, с которыми мы работали ранее:

#include <iostream> #include <string>

class Human { public: std::string m_name; int m_age; Human(std::string name = «», int age = 0) : m_name(name), m_age(age ) { } std::string getName() const { return m_name; } int getAge() const { return m_age; } }; // BasketballPlayer открыто наследует класс Human class BasketballPlayer: public Human { public: double m_gameAverage; int m_points; BasketballPlayer(double gameAverage = 0.0, int points = 0) : m_gameAverage(gameAverage), m_points(points) { } };

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

#include <iostream> #include <string>  

classHuman

{

public

std::stringm_name;

intm_age;

Human(std::stringname=»»,intage=)

m_name(name),m_age(age)

{

}

std::stringgetName()const{returnm_name;}

intgetAge()const{returnm_age;}

};

// BasketballPlayer открыто наследует класс Human

classBasketballPlayerpublicHuman

{

public

doublem_gameAverage;

intm_points;

BasketballPlayer(doublegameAverage=0.0,intpoints=)

m_gameAverage(gameAverage),m_points(points)

{

}

};

Как мы уже знаем, класс BasketballPlayer только инициализирует свои собственные члены и не указывает использование конкретного конструктора класса Human. Это означает, что каждый созданный объект класса BasketballPlayer будет использовать конструктор по умолчанию класса Human, который будет инициализировать переменную-член пустым значением, а — значением . Поскольку мы хотим назвать нашего BasketballPlayer и указать его возраст при его создании, то мы должны изменить этот конструктор, добавив необходимые параметры.

Вот наши обновленные классы с членами private и с вызовом конкретного конструктора класса Human:

#include <string>

class Human { private: std::string m_name; int m_age; public: Human(std::string name = «», int age = 0) : m_name(name), m_age(age ) { } std::string getName() const { return m_name; } int getAge() const { return m_age; } }; // BasketballPlayer открыто наследует класс Human class BasketballPlayer: public Human { private: double m_gameAverage; int m_points; public: BasketballPlayer(std::string name = «», int age = 0, double gameAverage = 0.0, int points = 0) : Human(name, age), // вызывается Human(std::string, int) для инициализации членов name и age m_gameAverage(gameAverage), m_points(points) { } double getGameAverage() const { return m_gameAverage; } int getPoints() const { return m_points; } };

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36

#include <string>  

classHuman

{

private

std::stringm_name;

intm_age;

public

Human(std::stringname=»»,intage=)

m_name(name),m_age(age)

{

}

std::stringgetName()const{returnm_name;}

intgetAge()const{returnm_age;}

};

// BasketballPlayer открыто наследует класс Human

classBasketballPlayerpublicHuman

{

private

doublem_gameAverage;

intm_points;

public

BasketballPlayer(std::stringname=»»,intage=,

doublegameAverage=0.0,intpoints=)

Human(name,age),// вызывается Human(std::string, int) для инициализации членов name и age

m_gameAverage(gameAverage),m_points(points)

{

}

doublegetGameAverage()const{returnm_gameAverage;}

intgetPoints()const{returnm_points;}

};

Теперь мы можем создавать объекты класса BasketballPlayer следующим образом:

int main() { BasketballPlayer anton(«Anton Ivanovuch», 45, 300, 310); std::cout << anton.getName() << ‘\n’; std::cout << anton.getAge() << ‘\n’; std::cout << anton.getPoints() << ‘\n’; return 0; }

1 2 3 4 5 6 7 8 9 10

intmain()

{

BasketballPlayer anton(«Anton Ivanovuch»,45,300,310);

std::cout<<anton.getName()<<‘\n’;

std::cout<<anton.getAge()<<‘\n’;

std::cout<<anton.getPoints()<<‘\n’;

return;

}

Результат выполнения программы:

Как вы можете видеть, всё корректно инициализировано.

JavaScript

JS Array concat() constructor copyWithin() entries() every() fill() filter() find() findIndex() forEach() from() includes() indexOf() isArray() join() keys() length lastIndexOf() map() pop() prototype push() reduce() reduceRight() reverse() shift() slice() some() sort() splice() toString() unshift() valueOf()

JS Boolean constructor prototype toString() valueOf()

JS Classes constructor() extends static super

JS Date constructor getDate() getDay() getFullYear() getHours() getMilliseconds() getMinutes() getMonth() getSeconds() getTime() getTimezoneOffset() getUTCDate() getUTCDay() getUTCFullYear() getUTCHours() getUTCMilliseconds() getUTCMinutes() getUTCMonth() getUTCSeconds() now() parse() prototype setDate() setFullYear() setHours() setMilliseconds() setMinutes() setMonth() setSeconds() setTime() setUTCDate() setUTCFullYear() setUTCHours() setUTCMilliseconds() setUTCMinutes() setUTCMonth() setUTCSeconds() toDateString() toISOString() toJSON() toLocaleDateString() toLocaleTimeString() toLocaleString() toString() toTimeString() toUTCString() UTC() valueOf()

JS Error name message

JS Global decodeURI() decodeURIComponent() encodeURI() encodeURIComponent() escape() eval() Infinity isFinite() isNaN() NaN Number() parseFloat() parseInt() String() undefined unescape()

JS JSON parse() stringify()

JS Math abs() acos() acosh() asin() asinh() atan() atan2() atanh() cbrt() ceil() cos() cosh() E exp() floor() LN2 LN10 log() LOG2E LOG10E max() min() PI pow() random() round() sin() sqrt() SQRT1_2 SQRT2 tan() tanh() trunc()

JS Number constructor isFinite() isInteger() isNaN() isSafeInteger() MAX_VALUE MIN_VALUE NEGATIVE_INFINITY NaN POSITIVE_INFINITY prototype toExponential() toFixed() toLocaleString() toPrecision() toString() valueOf()

JS OperatorsJS RegExp constructor compile() exec() g global i ignoreCase lastIndex m multiline n+ n* n? n{X} n{X,Y} n{X,} n$ ^n ?=n ?!n source test() toString() (x|y) . \w \W \d \D \s \S \b \B \0 \n \f \r \t \v \xxx \xdd \uxxxx

JS Statements break class continue debugger do…while for for…in for…of function if…else return switch throw try…catch var while

JS String charAt() charCodeAt() concat() constructor endsWith() fromCharCode() includes() indexOf() lastIndexOf() length localeCompare() match() prototype repeat() replace() search() slice() split() startsWith() substr() substring() toLocaleLowerCase() toLocaleUpperCase() toLowerCase() toString() toUpperCase() trim() valueOf()

Примечания

  1. Подпрограммы Эйфеля являются либо процедурами либо функциями. У процедур нет никакого возвращаемого типа. Функции всегда имеют возвращаемый тип.
  2. Поскольку должен быть также удовлетворён инвариант наследуемого(-х) класса(-ов), нет обязательного требования вызова родительских конструкторов.
  3. Полная спецификация содержится в стандартах ISO/ECMA по языку программироная Эйфель в онлайн доступе.
  4. Стандарт Эйфеля требует, чтобы поля были инициализированы при первом доступе к ним, т.ч. нет необходимости осуществлять их инициализацию значениями по умолчанию во время создания объекта.

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

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

#include <iostream> #include <string>

class SomeString { private: std::string m_string; public: SomeString(int a) // выделяем строку размером a { m_string.resize(a); }

SomeString(const char *string) // выделяем строку для хранения значения типа string { m_string = string; }

friend std::ostream& operator<<(std::ostream& out, const SomeString &s);

};

std::ostream& operator<<(std::ostream& out, const SomeString &s) { out << s.m_string; return out; }

int main() { SomeString mystring = ‘a’; // выполняется копирующая инициализация std::cout << mystring; return 0; }

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34

#include <iostream> #include <string>  

classSomeString


{

private

std::stringm_string;

public

SomeString(inta)// выделяем строку размером a

{

m_string.resize(a);

}

SomeString(constchar*string)// выделяем строку для хранения значения типа string

{

m_string=string;

}

friendstd::ostream&operator<<(std::ostream&out,constSomeString&s);

};

std::ostream&operator<<(std::ostream&out,constSomeString&s)

{

out<<s.m_string;

returnout;

}  

intmain()

{

SomeString mystring=’a’;// выполняется копирующая инициализация

std::cout<<mystring;

return;

}

В примере, приведенном выше, мы пытаемся инициализировать строку одним символом типа char. Поскольку char являются частью семейства целочисленных типов, то компилятор будет использовать конструктор преобразования для неявного преобразования символа типа char в SomeString. Результатом является не то, что ожидается.

Один из способов решения этой проблемы — сделать конструктор явным, используя ключевое слово explicit (которое пишется перед именем конструктора). Явные конструкторы (с ключевым словом explicit) не используются для неявных конвертаций:

#include <iostream> #include <string>

class SomeString { private: std::string m_string; public: // Ключевое слово explicit делает этот конструктор закрытым для выполнения любых неявных преобразований explicit SomeString(int a) // выделяем строку размером a { m_string.resize(a); }

SomeString(const char *string) // выделяем строку для хранения значения типа string { m_string = string; }

friend std::ostream& operator<<(std::ostream& out, const SomeString &s);

};

std::ostream& operator<<(std::ostream& out, const SomeString &s) { out << s.m_string; return out; }

int main() { SomeString mystring = ‘a’; // ошибка компиляции, поскольку SomeString(int) теперь является explicit и, соответственно, недоступен, а другого подходящего конструктора для преобразования компилятор не видит std::cout << mystring; return 0; }

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

#include <iostream> #include <string>  

classSomeString

{

private

std::stringm_string;

public

// Ключевое слово explicit делает этот конструктор закрытым для выполнения любых неявных преобразований

explicitSomeString(inta)// выделяем строку размером a

{

m_string.resize(a);

}

SomeString(constchar*string)// выделяем строку для хранения значения типа string

{

m_string=string;

}

friendstd::ostream&operator<<(std::ostream&out,constSomeString&s);

};

std::ostream&operator<<(std::ostream&out,constSomeString&s)

{

out<<s.m_string;

returnout;

}  

intmain()

{

SomeString mystring=’a’;// ошибка компиляции, поскольку SomeString(int) теперь является explicit и, соответственно, недоступен, а другого подходящего конструктора для преобразования компилятор не видит

std::cout<<mystring;

return;

}

Вышеприведенная программа не скомпилируется, так как мы сделали явным, а другого конструктора преобразования, который выполнил бы неявную конвертацию в SomeString, компилятор просто не нашел.

Однако использование явного конструктора только предотвращает выполнение неявных преобразований. Явные конвертации (через операторы явного преобразования) по-прежнему разрешены:

std::cout << static_cast<SomeString>(7); // разрешено: явное преобразование 7 в SomeString через оператор static_cast

1 std::cout<<static_cast<SomeString>(7);// разрешено: явное преобразование 7 в SomeString через оператор static_cast

При прямой- или uniform-инициализации неявная конвертация также будет выполняться:

SomeString str(‘a’); // разрешено

1 SomeString str(‘a’);// разрешено

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

Adding a Method to a Constructor

Your constructor function can also define methods:

Example

function Person(first, last, age, eyecolor) {   this.firstName = first;   this.lastName = last;   this.age = age;   this.eyeColor = eyecolor;  this.name = function() {return this.firstName + » » + this.lastName;}; }

You cannot add a new method to an object constructor the same way you add a new method to an existing object.

Adding methods to an object constructor must be done inside the constructor function:

Example

function Person(firstName, lastName, age, eyeColor) {   this.firstName = firstName;    this.lastName = lastName;   this.age = age;   this.eyeColor = eyeColor;   this.changeName = function (name) {     this.lastName = name;   }; }

The changeName() function assigns the value of name to the person’s lastName property.

Now You Can Try:

myMother.changeName(«Doe»);

JavaScript knows which person you are talking about by «substituting» this with myMother.

JavaScript

JS Array concat() constructor copyWithin() entries() every() fill() filter() find() findIndex() forEach() from() includes() indexOf() isArray() join() keys() length lastIndexOf() map() pop() prototype push() reduce() reduceRight() reverse() shift() slice() some() sort() splice() toString() unshift() valueOf()

JS Boolean constructor prototype toString() valueOf()

JS Classes constructor() extends static super

JS Date constructor getDate() getDay() getFullYear() getHours() getMilliseconds() getMinutes() getMonth() getSeconds() getTime() getTimezoneOffset() getUTCDate() getUTCDay() getUTCFullYear() getUTCHours() getUTCMilliseconds() getUTCMinutes() getUTCMonth() getUTCSeconds() now() parse() prototype setDate() setFullYear() setHours() setMilliseconds() setMinutes() setMonth() setSeconds() setTime() setUTCDate() setUTCFullYear() setUTCHours() setUTCMilliseconds() setUTCMinutes() setUTCMonth() setUTCSeconds() toDateString() toISOString() toJSON() toLocaleDateString() toLocaleTimeString() toLocaleString() toString() toTimeString() toUTCString() UTC() valueOf()

JS Error name message

JS Global decodeURI() decodeURIComponent() encodeURI() encodeURIComponent() escape() eval() Infinity isFinite() isNaN() NaN Number() parseFloat() parseInt() String() undefined unescape()

JS JSON parse() stringify()

JS Math abs() acos() acosh() asin() asinh() atan() atan2() atanh() cbrt() ceil() cos() cosh() E exp() floor() LN2 LN10 log() LOG2E LOG10E max() min() PI pow() random() round() sin() sqrt() SQRT1_2 SQRT2 tan() tanh() trunc()

JS Number constructor isFinite() isInteger() isNaN() isSafeInteger() MAX_VALUE MIN_VALUE NEGATIVE_INFINITY NaN POSITIVE_INFINITY prototype toExponential() toFixed() toLocaleString() toPrecision() toString() valueOf()

JS OperatorsJS RegExp constructor compile() exec() g global i ignoreCase lastIndex m multiline n+ n* n? n{X} n{X,Y} n{X,} n$ ^n ?=n ?!n source test() toString() (x|y) . \w \W \d \D \s \S \b \B \0 \n \f \r \t \v \xxx \xdd \uxxxx

JS Statements break class continue debugger do…while for for…in for…of function if…else return switch throw try…catch var while

JS String charAt() charCodeAt() concat() constructor endsWith() fromCharCode() includes() indexOf() lastIndexOf() length localeCompare() match() prototype repeat() replace() search() slice() split() startsWith() substr() substring() toLocaleLowerCase() toLocaleUpperCase() toLowerCase() toString() toUpperCase() trim() valueOf()

JavaScript

JS Array concat() constructor copyWithin() entries() every() fill() filter() find() findIndex() forEach() from() includes() indexOf() isArray() join() keys() length lastIndexOf() map() pop() prototype push() reduce() reduceRight() reverse() shift() slice() some() sort() splice() toString() unshift() valueOf()

JS Boolean constructor prototype toString() valueOf()

JS Classes constructor() extends static super

JS Date constructor getDate() getDay() getFullYear() getHours() getMilliseconds() getMinutes() getMonth() getSeconds() getTime() getTimezoneOffset() getUTCDate() getUTCDay() getUTCFullYear() getUTCHours() getUTCMilliseconds() getUTCMinutes() getUTCMonth() getUTCSeconds() now() parse() prototype setDate() setFullYear() setHours() setMilliseconds() setMinutes() setMonth() setSeconds() setTime() setUTCDate() setUTCFullYear() setUTCHours() setUTCMilliseconds() setUTCMinutes() setUTCMonth() setUTCSeconds() toDateString() toISOString() toJSON() toLocaleDateString() toLocaleTimeString() toLocaleString() toString() toTimeString() toUTCString() UTC() valueOf()

JS Error name message

JS Global decodeURI() decodeURIComponent() encodeURI() encodeURIComponent() escape() eval() Infinity isFinite() isNaN() NaN Number() parseFloat() parseInt() String() undefined unescape()

JS JSON parse() stringify()

JS Math abs() acos() acosh() asin() asinh() atan() atan2() atanh() cbrt() ceil() cos() cosh() E exp() floor() LN2 LN10 log() LOG2E LOG10E max() min() PI pow() random() round() sin() sqrt() SQRT1_2 SQRT2 tan() tanh() trunc()

JS Number constructor isFinite() isInteger() isNaN() isSafeInteger() MAX_VALUE MIN_VALUE NEGATIVE_INFINITY NaN POSITIVE_INFINITY prototype toExponential() toFixed() toLocaleString() toPrecision() toString() valueOf()

JS OperatorsJS RegExp constructor compile() exec() g global i ignoreCase lastIndex m multiline n+ n* n? n{X} n{X,Y} n{X,} n$ ^n ?=n ?!n source test() toString() (x|y) . \w \W \d \D \s \S \b \B \0 \n \f \r \t \v \xxx \xdd \uxxxx

JS Statements break class continue debugger do…while for for…in for…of function if…else return switch throw try…catch var while

JS String charAt() charCodeAt() concat() constructor endsWith() fromCharCode() includes() indexOf() lastIndexOf() length localeCompare() match() prototype repeat() replace() search() slice() split() startsWith() substr() substring() toLocaleLowerCase() toLocaleUpperCase() toLowerCase() toString() toUpperCase() trim() valueOf()

Конструктор копирования может быть проигнорирован

Рассмотрим следующий код:

#include <cassert> #include <iostream> class Drob { private: int m_numerator; int m_denominator; public: // Конструктор по умолчанию Drob(int numerator=0, int denominator=1) : m_numerator(numerator), m_denominator(denominator) { assert(denominator != 0); } // Конструктор копирования Drob(const Drob &drob) : m_numerator(drob.m_numerator), m_denominator(drob.m_denominator) { // Нет необходимости выполнять проверку denominator здесь, так как эта проверка уже осуществляется в конструкторе класса Drob std::cout << «Copy constructor worked here!\n»; // просто чтобы показать, что это работает } friend std::ostream& operator<<(std::ostream& out, const Drob &d1); }; std::ostream& operator<<(std::ostream& out, const Drob &d1) { out << d1.m_numerator << «/» << d1.m_denominator; return out; } int main() { Drob sixSeven(Drob(6, 7)); std::cout << sixSeven; return 0; }

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40

#include <cassert> #include <iostream>

classDrob

{

private

intm_numerator;

intm_denominator;

public

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

Drob(intnumerator=,intdenominator=1)

m_numerator(numerator),m_denominator(denominator)

{

assert(denominator!=);

}

// Конструктор копирования

Drob(constDrob&drob)

m_numerator(drob.m_numerator),m_denominator(drob.m_denominator)

{

// Нет необходимости выполнять проверку denominator здесь, так как эта проверка уже осуществляется в конструкторе класса Drob

std::cout<<«Copy constructor worked here!\n»;// просто чтобы показать, что это работает

}

friendstd::ostream&operator<<(std::ostream&out,constDrob&d1);

};

std::ostream&operator<<(std::ostream&out,constDrob&d1)

{

out<<d1.m_numerator<<«/»<<d1.m_denominator;

returnout;

}

intmain()


{

Drob sixSeven(Drob(6,7));

std::cout<<sixSeven;

return;

}

Сначала инициализируется анонимный объект Drob, который приводит к вызову конструктора . Затем этот анонимный объект используется для инициализации объекта класса Drob. Поскольку анонимный объект является объектом класса Drob, как и , то здесь должен вызываться конструктор копирования, верно?

Запустите эту программу самостоятельно. Ожидаемый результат:

Реальный результат:

Почему наш конструктор копирования не сработал?

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

По этой причине в таких случаях компилятору разрешается отказаться от вызова конструктора копирования и просто выполнить прямую инициализацию. Этот процесс называется элизией.

Поэтому, даже если вы напишите:

Drob sixSeven(Drob(6, 7));

1 Drob sixSeven(Drob(6,7));

Компилятор может изменить это на:

Drob sixSeven(6, 7);

1 Drob sixSeven(6,7);

В последнем случае с прямой инициализацией потребуется вызов только одного конструктора ()

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

Наконец, если вы делаете свой конструктор копирования закрытым, то любая инициализация, использующая этот закрытый конструктор копирования, приведет к ошибкам компиляции, даже если конструктор копирования будет проигнорирован!

Неявно генерируемый конструктор по умолчанию

Если ваш класс не имеет других конструкторов, то язык C++ автоматически сгенерирует для вашего класса открытый конструктор по умолчанию. Его иногда называют неявным конструктором (или «неявно сгенерированным конструктором»). Рассмотрим следующий класс:

class Date { private: int m_day = 12; int m_month = 1; int m_year = 2018; };

1 2 3 4 5 6 7

classDate

{

private

intm_day=12;

intm_month=1;

intm_year=2018;

};

У этого класса нет конструктора, поэтому компилятор сгенерирует следующий, идентичен по выполнению, конструктор:

class Date { private: int m_day = 12; int m_month = 1; int m_year = 2018; public: Date() // неявно генерируемый конструктор { } };

1 2 3 4 5 6 7 8 9 10 11 12

classDate

{

private

intm_day=12;

intm_month=1;

intm_year=2018;

public

Date()// неявно генерируемый конструктор

{

}

};

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

Хотя вы не можете увидеть неявно сгенерированный конструктор, но его существование можно доказать:

class Date { private: int m_day = 12; int m_month = 1; int m_year = 2018; // Не было предоставлено конструктора, поэтому C++ автоматически создаст открытый конструктор по умолчанию }; int main() { Date date; // вызов неявного конструктора return 0; }

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

classDate

{

private

intm_day=12;

intm_month=1;

intm_year=2018;

// Не было предоставлено конструктора, поэтому C++ автоматически создаст открытый конструктор по умолчанию

};

intmain()

{

Date date;// вызов неявного конструктора

return;

}

Вышеприведенный код скомпилируется, поскольку в объекте сработает неявный конструктор (который является открытым). Если ваш класс имеет другие конструкторы, то неявно генерируемый конструктор создаваться не будет. Например:

class Date { private: int m_day = 12; int m_month = 1; int m_year = 2018; public: Date(int day, int month, int year) // обычный конструктор (не по умолчанию) { m_day = day; m_month = month; m_year = year; } // Неявный конструктор не создастся, так как мы уже определили свой конструктор }; int main() { Date date; // ошибка: Невозможно создать объект, так как конструктор по умолчанию не существует, и компилятор не сгенерировал неявный конструктор автоматически Date today(14, 10, 2020); // инициализируем объект today return 0; }

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

classDate

{

private

intm_day=12;

intm_month=1;

intm_year=2018;

public

Date(intday,intmonth,intyear)// обычный конструктор (не по умолчанию)

{

m_day=day;

m_month=month;

m_year=year;

}

// Неявный конструктор не создастся, так как мы уже определили свой конструктор

};

intmain()

{

Date date;// ошибка: Невозможно создать объект, так как конструктор по умолчанию не существует, и компилятор не сгенерировал неявный конструктор автоматически

Date today(14,10,2020);// инициализируем объект today

return;

}

Рекомендуется всегда создавать по крайней мере один конструктор в классе. Это позволит вам контролировать процесс создания объектов вашего класса, и предотвратит возникновение потенциальных проблем после добавления других конструкторов.

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


С этим читают