Улучшение процесса инициализации объектов — КиберПедия 

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

Индивидуальные и групповые автопоилки: для животных. Схемы и конструкции...

Улучшение процесса инициализации объектов



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

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

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

 

// Пример 8.8 - универсальная инициализация структур и классов

struct Vector3 {

float x,y,z;

void print() {

std::cout << x << y << z << std::endl;

}

};

 

class Matrix3x3 {

Vector3 line1;

Vector3 line2;

Vector3 line3;

public:

Matrix3x3(Vector3 v1, Vector3 v2, Vector3 v3):

line1(v1), line2(v2), line3(v3) {

}

void print() {

line1.print();

line2.print();

line3.print();

}

};

 

Vector3 v1 = {1,2,3};

v1.print();

v1 = {11,22,33};

v1.print();

Matrix3x3 m = {{1,2,3},{4,5,6},{7,8,9}};

m.print();

m = {{10,20,30},{40,50,60},{70,80,90}};

m.print();

 

Результат работы программы следующий:

 

Если у класса есть конструктор с параметром типа std::initializer_list, то он будет иметь больший приоритет при использовании списка инициализации.

 

// Пример 8.9 - приоритет конструкторов при использовании списка

// инициализации

class Vector3 {

float x,y,z;

public:

Vector3(float v): x(v), y(v), z(v) {}

Vector3(std::initializer_list<int> il)

{

if(il.size() != 3)

x = y = z = 0;

else

{

x = *(il.begin());

y = *(il.begin() + 1);

z = *(il.begin() + 2);

}

}

void print() {

std::cout << x << y << z << std::endl;

}

};

Vector3 v1(1);

Vector3 v2{1};

v1.print();

v2.print();

 

Результат работы программы следующий:

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

 

// Пример 8.10 - создание объекта класса без указания имени типа

struct Person

{

std::string name;

std::string lastName;

int age;

void print()

{

std::cout << "Name: " << name << std::endl;

std::cout << "Last name: " << lastName << std::endl;

std::cout << "Age: " << age << std::endl;

}

};

 

Person getPerson()

{

return {"Ivan", "Ivanov", 25};

}

 

int main()

{

Person p = getPerson();

p.print();

}

 

Результат работы программы следующий:

Name: Ivan

Last name: Ivanov

Age: 25

 

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

 

// Пример 8.11 - значения по умолчанию для переменных членов класса



class Person

{

std::string name = "Unknown";

std::string lastName = "Unknown";

int age = 0;

public:

void print()

{

std::cout << "Name: " << name << std::endl;

std::cout << "Last name: " << lastName << std::endl;

std::cout << "Age: " << age << std::endl;

}

};

Person p;

p.print();

 

Результат работы программы следующий:

Name: Unknown

Last name: Unknown

Age: 0

 

В С++11 появилась возможность вызывать конструктор класса из другого конструктора этого же класса в списке инициализации, этот механизм называется делегированием. Такая возможность позволяет избежать дублирования кода между различными конструкторами. До С++11 возможно было вызвать только конструктор базового класса.

В следующем примере конструктор без параметров вызывает другой конструктор класса.

 

// Пример 8.12 - делегирование при инициализации объектов

class Person

{

std::string name = "Unknown";

std::string lastName = "Unknown";

int age = 0;

public:

Person(const std::string& n, const std::string& ln, int a): name(n), lastName(ln), age(a){}

Person():Person("Empty", "Empty", 0) {}

void print()

{

std::cout << "Name: " << name << std::endl;

std::cout << "Last name: " << lastName << std::endl;

std::cout << "Age: " << age << std::endl;

}

};

 

Person p;

p.print();

Person p1("Ivan", "Ivanov", 25);

p1.print();

 

Результат работы программы следующий:

Name: Empty

Last name: Empty

Age: 0

Name: Ivan

Last name: Ivanov

Age: 25

 

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

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

 

Цикл for по коллекции

Во многих языках программирования существует конструкция для сокращения количества кода, требуемого для перебора элементов коллекции − for_each. В стандарте С++11 появилась возможность писать более компактный код за счет появления цикла for по коллекции.



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

 

// Пример 8.13 - цикл for по коллекции для статического массива

int arr[] = {1,2,3,4,5};

for(int &v: arr)

std::cout << v << ' ';

std::cout << std::endl;

 

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

 

// Пример 8.14 - цикл for по коллекции для контейнера std::vector

std::vector<int> vec = {1,2,3,4,5,6,7,8,9,10};

for(auto v: vec)

std::cout << v << ' ';

std::cout << std::endl;

 

Результат работы программы следующий:

1 2 3 4 5 6 7 8 9 10

 

Аналогичный пример для ассоциативного массива:

 

// Пример 8.15 - цикл for по коллекции для контейнера std::map

std::map<std::string, int> personAge = {

{"Ivan", 20},

{"Anton", 25},

{"Michail", 27}

};

for(auto v: personAge)

std::cout << v.first << " - " << v.second << '\n';

 

Результат работы программы следующий:

Anton - 25

Ivan - 20

Michail - 27

 

Цикл for по коллекции может быть применен для обхода элементов пользовательских классов-коллекций. Для этого необходимо, чтобы новый класс имел два метода: begin() и end(). Метод begin() возвращает итератор на первый элемент. Метод end() возвращает итератор на элемент, следующий за последним.

В следующем примере класс MyCollection имеет вложенный класс iterator, экземпляры которого возвращаются методами MyCollection::begin() и MyCollection::end(). У итератора есть три перегруженных оператора: звездочка (*), инкремент (++), не равно (!=).

 

// Пример 8.16 - цикл for по коллекции для пользовательского класса

class MyCollection

{

std::vector<int> mVec;

public:

class iterator

{

int mIndex;

MyCollection& mCollection;

public:

iterator(MyCollection& collection, int index) :

mCollection(collection), mIndex(index) {}

int operator*() { return mCollection.at(mIndex); }

bool operator!=(const iterator& it) {

return mIndex != it.mIndex; }

void operator++() { ++mIndex; }

};

 

MyCollection(std::initializer_list<int> list){ mVec = list; }

MyCollection(std::initializer_list<std::initializer_list<int>> list) {

for(auto l: list)

mVec.insert(mVec.end(), l);

}

iterator begin() { return iterator(*this, 0); }

iterator end() { return iterator(*this, mVec.size()); }

int at(int index) { return mVec.at(index); }

};

 

int main()

{

MyCollection mc({1,2,3,4,5});

for(int it: mc)

std::cout << it << ' ';

std::cout << std::endl;

 

MyCollection mc2({{1,2,3},{4,5,6},{7,8,9}});

for(int it: mc2)

std::cout << it << ' ';

std::cout << std::endl;

 

return 0;

}

 

Результат работы программы следующий:

1 2 3 4 5

1 2 3 4 5 6 7 8 9

 

Лямбда-функции

В стандарте С++11 появилась возможность определения функций непосредственно в месте вызова − так называемых лямбда-функций. Лямбда-функции могут применяться в алгоритмах сортировки и поиска, при этом объем кода, необходимый для их написания, значительно меньше по сравнению с ранее доступными механизмами − функторами и полноценными функциями.

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

В следующем примере две лямбда-функции lambdaSumm() и lambdaMul() получают в качестве параметров два целых числа и возвращают в качестве результата сумму и произведение соответственно. При этом у функции lambdaSumm() явно указан тип возвращаемого значения. У функции lambdaMul() тип возвращаемого значения определяется автоматически исходя из выражения после оператора return.

 

// Пример 8.17 - лямбда функции, передача параметров.

auto lambdaSumm = [](int a, int b) -> int { return a + b; };

auto lambdaMul = [](int a, int b) { return a * b; };

std::cout << "Summ: " << lambdaSumm(5, 10) << std::endl;

std::cout << "Mul: " << lambdaMul(5, 10) << std::endl;

 

Результат работы программы следующий:

Summ: 15

Mul: 50

 

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

 

// Пример 8.18 - лямбда функции, замыкания.

int i = 0;

auto myLambdaFunc = [&i]() { ++i; };

std::cout << "i: " << i << std::endl;

myLambdaFunc();

std::cout << "i: " << i << std::endl;

 

Результат работы программы следующий:

i: 0

i: 1

 

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

 

// Пример 8.19 - передача переменных по значению

int a = 5, b = 3;

auto lambdaSum = [a,b](){return a+b;};

std::cout << lambdaSum() << std::endl;

 

Результат работы программы следующий:

 

Для передачи переменных может быть использована сокращенная форма записи. Если в квадратных скобках указать знак равно [=], то лямбда-функции будут доступны все переменные из области видимости, в которой объявлена функция, изменять их при этом будет нельзя. Если вместо знака равенства поставить знак амперсанда, все переменные будут доступны по ссылке.

 

int a = 5, b = 3;

auto lambdaSum = [=](){return a+b;};

std::cout << lambdaSum() << std::endl;

 

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

 

// Пример 8.20 - лямбда функция в качестве предиката для сортировки

std::vector<int> intList = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};

std::sort(intList.begin(), intList.end(), [](int a, int b) { return a < b; });

for(auto x: intList) std::cout << x << ' '; std::cout << std::endl;

 

Результат работы программы следующий:

1 2 3 4 5 6 7 8 9 10

 

Лямбда-функции могут быть применены ко всем элементам контейнера при помощь алгоритма std::for_each.

 

// Пример 8.21 - лямбда функция в алгоритме std::for_each

std::vector<int> intList = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};

int summ = 0;

std::for_each(intList.begin(), intList.end(), [&summ](int x) { summ += x; });

std::cout << "Summ: " << summ << std::endl;

 

Результат работы программы следующий:

Summ: 55

 

В следующем примере лямбда-функция применяется ко всем элементам контейнера std::vector и выполняет расчет суммы элементов и их количества.

 

// Пример 8.22 - суммирование элементов вектора

std::vector<int> intList = {1,2,3,4,5};

int summ = 0;

int count = 0;

std::for_each(intList.begin(), intList.end(), [&](int x) {

summ += x;

++count;

});

std::cout << "Summ: " << summ << std::endl;

std::cout << "Count: " << count << std::endl;

 

Результат работы программы следующий:

Summ: 15

Count: 5

 






Кормораздатчик мобильный электрифицированный: схема и процесс работы устройства...

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

Опора деревянной одностоечной и способы укрепление угловых опор: Опоры ВЛ - конструкции, предназначен­ные для поддерживания проводов на необходимой высоте над землей, водой...

Общие условия выбора системы дренажа: Система дренажа выбирается в зависимости от характера защищаемого...





© cyberpedia.su 2017 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав

0.032 с.