Урок №126. дружественные функции и классы

Дружественные функции и несколько классов

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


#include <iostream>

class Humidity; class Temperature { private: int m_temp; public: Temperature(int temp=0) { m_temp = temp; } friend void outWeather(const Temperature &temperature, const Humidity &humidity); }; class Humidity { private: int m_humidity; public: Humidity(int humidity=0) { m_humidity = humidity; } friend void outWeather(const Temperature &temperature, const Humidity &humidity); }; void outWeather(const Temperature &temperature, const Humidity &humidity) { std::cout << «The temperature is » << temperature.m_temp << » and the humidity is » << humidity.m_humidity << ‘\n’; } int main() { Temperature temp(15); Humidity hum(11); outWeather(temp, hum); 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

#include <iostream>  

classHumidity;

classTemperature

{

private

intm_temp;

public

Temperature(inttemp=){m_temp=temp;}

friendvoidoutWeather(constTemperature&temperature,constHumidity&humidity);

};

classHumidity

{

private

intm_humidity;

public

Humidity(inthumidity=){m_humidity=humidity;}

friendvoidoutWeather(constTemperature&temperature,constHumidity&humidity);

};

voidoutWeather(constTemperature&temperature,constHumidity&humidity)

{

std::cout<<«The temperature is «<<temperature.m_temp<<

» and the humidity is «<<humidity.m_humidity<<‘\n’;

}

intmain()

{

Temperature temp(15);

Humidity hum(11);

outWeather(temp,hum);

return;

}

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

Во-вторых, обратите внимание на следующую строку в примере, приведенном выше:

class Humidity;

1 classHumidity;

Это прототип класса, который сообщает компилятору, что мы определим класс Humidity чуть позже. Без этой строчки компилятор выдал бы ошибку, что не знает, что такое Humidity при анализе прототипа дружественной функции outWeather() внутри класса Temperature. Прототипы классов выполняют ту же роль, что и прототипы функций: они сообщают компилятору об объектах, которые позднее будут определены, но которые сейчас нужно использовать. Однако, в отличие от функций, классы не имеют типа возврата или параметров, поэтому их прототипы предельно лаконичны: (например, ).

Добавление нового функционала в дочерний класс

В примере, приведенном выше, поскольку мы имеем доступ к исходному коду класса Parent, то мы можем добавить новый функционал непосредственно в класс Parent, если захотим.

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

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

В любом случае лучшим решением является написание собственного родительского класса и добавление нужного нам функционала в наши дочерние классы.

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

Добавление нового функционала в дочерний класс выполняется как обычно:

class Child: public Parent { public: Child(int value) : Parent(value) { } int getValue() { return m_value; } };

1 2 3 4 5 6 7 8 9 10

classChildpublicParent

{

public

Child(intvalue)

Parent(value)

{

}

intgetValue(){returnm_value;}

};

Теперь другие объекты извне смогут вызывать getValue() через объект класса Child для доступа к :

int main() { Child child(7); std::cout << «child has value » << child.getValue() << ‘\n’; return 0; }

1 2 3 4 5 6 7

intmain()


{

Childchild(7);

std::cout<<«child has value «<<child.getValue()<<‘\n’;

return;

}

Результат:

Хотя это может быть очевидно, но объекты класса Parent не имеют доступа к методу getValue() в Child. Следующее не сработает:

int main() { Parent parent(7); std::cout << «parent has value » << parent.getValue() << ‘\n’; return 0; }

1 2 3 4 5 6 7

intmain()

{

Parentparent(7);

std::cout<<«parent has value «<<parent.getValue()<<‘\n’;

return;

}

Это связано с тем, что в классе Parent нету метода getValue(). Метод getValue() принадлежит классу Child. А поскольку Child является дочерним классу Parent, то Child имеет доступ к членам Parent, а Parent не имеет доступа ни к чему в классе Child.

ФОРМЫ

Форма входаФорма регистрацииФорма оформления заказаКонтактная формаФорма входа в соц сетиРегистрацияФорма с иконкамиРассылка по почтеСложенная формаАдаптивная формаФорма всплывающаяФорма линейнаяОчистить поле вводаКопирование текста в буфер обменаАнимированный поискКнопка поискаПолноэкранный поискПоле ввода в менюФорма входа в менюПользовательский флажок/радиоПользовательский выборТумблер перключательУстановить флажокОпределить Caps LockКнопка запуска на EnterПроверка пароляПереключение видимости пароляМногоступенчатая формаФункция автозаполнения

ЕЩЁ

Полноэкранное видеоМодальное окноШкала времениИндикатор прокрутки Индикатор выполненияПанель навыковПолзунок диапазонаПодсказки при наведенииВсплывающие окнаСкладная секцияКалендарьВключить HTMLСписок делЗагрузчикиЗвездный рейтингПользовательский рейтингНаложениеКонтактные чипыКарточкиФлип-картаКарточка профиляКарточка товараОкно тревогиВыноска сообщенияПримечаниеМеткиКругиHR Горизонтальная линияКупонГруппа списковОтзывчивый текстВырезанный текстСветящийся текстФиксированный подвалЛипкий элементРавная высота столбцовОчистка поплавкаОтзывчивые поплавкиСнэк-бар/тостПолноэкранное режимЧертеж при прокруткеПлавная прокруткаГрадиент фонаЛипкий заголовокИзменить заголовок при прокруткеОтзывчивые столбцы ценПараллаксСоотношение сторонПереключатель нравится/не нравитсяПереключатель скрыть/показатьПереключаель текстаПереключатель классаДобавить классУдалить классАктивный классДревовидное представлениеУдалить свойствоАвтономный режим обнаруженияСделать скрытый элементПеренаправление веб страницыУвеличить при наведенииФлип-боксЭлемент вертикально по центруПереход при наведении курсораСтрелкиФигурыСсылка для скачиванияПолная высота элементаОкно браузераПользовательская полоса прокруткиРазличные устройстваЦвет заполнителяЦвет выделения текстаЦвет макераВертикальная линияАнимированные иконкиТаймер обратного отсчетаПишущая машинкаСтраница заставкиСообщение чатаВсплывающее окно чатаРазделенный экранРекомендацииСчетчик разделаСлайд-шоу цитатЗакрываемые злементы спискаТипичные точки прерыванияПеретаскиваемый HTML элементМедиа запросы JSПодсветка синтаксисаJS анимацииПолучить элементы Iframe

ФОРМЫ

Форма входаФорма регистрацииФорма оформления заказаКонтактная формаФорма входа в соц сетиРегистрацияФорма с иконкамиРассылка по почтеСложенная формаАдаптивная формаФорма всплывающаяФорма линейнаяОчистить поле вводаКопирование текста в буфер обменаАнимированный поискКнопка поискаПолноэкранный поискПоле ввода в менюФорма входа в менюПользовательский флажок/радиоПользовательский выборТумблер перключательУстановить флажокОпределить Caps LockКнопка запуска на EnterПроверка пароляПереключение видимости пароляМногоступенчатая формаФункция автозаполнения

Модификаторы доступа private и public

Модификаторы доступа служат для определения полномочий доступа к членам класса извне. Если перед полем или методом стоит ключевое слово private, то обращаться к данному члену можно только внутри класса. Член с модификатором public доступен за пределами класса: то есть другие классы могут напрямую получить или модифицировать значение поля (категорически не рекомендуется поле делать public, для безопасного получения и установки значения нужно использовать геттеры и сеттеры), либо вызвать публичный метод.

С помощью модификаторов доступа реализуется ключевой принцип ООП – инкапсуляция данных (их сокрытие).

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

  • private в C#;
  • public в Java.

Создание проекта библиотеки классов

Последнее обновление: 08.10.2019

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

Вначале создадим новый проект консольного приложения .NET Core, который назовем BankApplication:

И после этого создается стандартный пустой проект с классом Program и методом Main. Это будет главный проект приложения.

Но для хранения классов и интерфейсов нередко создаются отдельные проекты, в рамках которых все классы компилируются в файл библиотеки dll, которая затем подключается к главному проекту. Поэтому добавим в решение новый проект. Для этого нажмем правой кнопкой мыши на решение и выберем в контекстном меню Add -> New Project…:

В качестве типа нового проекта выберем шаблон Class Library (.NET Core) и назовем новый проект BankLibrary:

После этого в решение добавляется новый проект, который по умолчанию имеет один файл Class1.cs. Он нам не нужен, поэтому удалим этот файл.

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

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

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

public interface IAccount
{
    // Положить деньги на счет
    void Put(decimal sum);
    // Взять со счета
    decimal Withdraw(decimal sum);
}

Данный интерфейс определяем два метода для того, чтобы положить на счет или вывести средства со счета.

Для реакции на изменения состояния счеты мы будем использовать событийную модель, то есть обрабатываться различные изменения счета через события. Для этого добавим в проект BankLibrary новый файл AccountStateHandler.cs, в котором определим делегат и вспомогательный класс:

namespace BankLibrary
{
    public delegate void AccountStateHandler(object sender, AccountEventArgs e);

    public class AccountEventArgs
    {
        // Сообщение
        public string Message { get; private set;}
        // Сумма, на которую изменился счет
        public decimal Sum { get; private set;}

        public AccountEventArgs(string _mes, decimal _sum)
        {
            Message = _mes;
            Sum = _sum;
        }
    }
}

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

Теперь определим основной класс приложения Account.

НазадВперед

Классы

Одной из наиболее полезных особенностей языка C++ является возможность определять собственные типы данных, которые будут лучше соответствовать в решении конкретных проблем. Вы уже видели, как перечисления и структуры могут использоваться для создания собственных пользовательских типов данных. Например, структура для хранения даты:

struct DateStruct { int day; int month; int year; };

1 2 3 4 5 6

structDateStruct

{

intday;

intmonth;

intyear;

};

Перечисления и структуры — это традиционный (не объектно-ориентированный) мир программирования, в котором мы можем только хранить данные. В C++11 мы можем создать и инициализировать структуру следующим образом:

DateStruct today { 12, 11, 2018}; // используем uniform-инициализацию

1 DateStructtoday{12,11,2018};// используем uniform-инициализацию

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

#include <iostream> struct DateStruct { int day; int month; int year; }; void print(DateStruct &date) { std::cout << date.day<< «/» << date.month << «/» << date.year; } int main() { DateStruct today { 12, 11, 2018}; // используем uniform-инициализацию today.day = 18; // используем оператор выбора члена для выбора члена структуры print(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

#include <iostream>

structDateStruct

{

intday;

intmonth;

intyear;

};

voidprint(DateStruct&date)

{


std::cout<<date.day<<«/»<<date.month<<«/»<<date.year;

}

intmain()

{

DateStructtoday{12,11,2018};// используем uniform-инициализацию

today.day=18;// используем оператор выбора члена для выбора члена структуры

print(today);

return;

}

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

В объектно-ориентированном программировании типы данных могут содержать не только данные, но и функции, которые будут работать с этими данными. Для определения такого типа данных в языке C++ используется ключевое слово class. Использование ключевого слова class определяет новый пользовательский тип данных — класс.

В языке C++ классы очень похожи на структуры, за исключением того, что они обеспечивают гораздо большую мощность и гибкость. Фактически, следующая структура и класс идентичны по функционалу:

struct DateStruct { int day; int month; int year; }; class DateClass { public: int m_day; int m_month; int m_year; };

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

structDateStruct

{

intday;

intmonth;

intyear;

};

classDateClass

{

public

intm_day;

intm_month;

intm_year;

};

Единственным существенным отличием здесь является — ключевое слово в классе (о нем мы поговорим детально на соответствующем уроке).

Так же, как объявление структуры, так же и объявление класса не приводит к выделению какой-либо памяти. Для использования класса нужно объявить переменную этого типа класса:

DateClass today { 12, 11, 2018 }; // инициализируем переменную класса DateClass

1 DateClasstoday{12,11,2018};// инициализируем переменную класса DateClass

В языке C++ переменная класса называется экземпляром (или «объектом») класса. Точно так же, как определение переменной фундаментального типа данных (например, ) приводит к выделению памяти для этой переменной, так же и создание объекта класса (например, ) приводит к выделению памяти для этого объекта.

Порядок выполнения в списке инициализации

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

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

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

Классы и заголовочные файлы

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

Вот наш класс Date, но уже разбитый на файлы .cpp и .h:

Date.h:

#ifndef DATE_H #define DATE_H class Date { private: int m_day; int m_month; int m_year; public: Date(int day, int month, int year); void SetDate(int day, int month, int year); int getDay() { return m_day; } int getMonth() { return m_month; } int getYear() { return m_year; } }; #endif

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

#ifndef DATE_H #define DATE_H

classDate

{

private

intm_day;

intm_month;

intm_year;

public

Date(intday,intmonth,intyear);

voidSetDate(intday,intmonth,intyear);

intgetDay(){returnm_day;}

intgetMonth(){returnm_month;}

intgetYear(){returnm_year;}

};

#endif

Date.cpp:

#include «Date.h» // Конструктор класса Date Date::Date(int day, int month, int year) { SetDate(day, month, year); } // Метод класса Date void Date::SetDate(int day, int month, int year) { m_day = day; m_month = month; m_year = year; }

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

#include «Date.h»

// Конструктор класса Date

Date::Date(intday,intmonth,intyear)


{

SetDate(day,month,year);

}

// Метод класса Date

voidDate::SetDate(intday,intmonth,intyear)

{

m_day=day;

m_month=month;

m_year=year;

}

Теперь любой другой файл .h или .cpp, который захочет использовать класс Date, сможет просто

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

Вопрос №1: «Разве определение класса в заголовочном файле не нарушает правило одного определения?».

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

Вопрос №2: «Разве определения методов класса в заголовочном файле не нарушает правило одного определения?».

Методы, определенные внутри тела класса, считаются неявно встроенными. Встроенные функции освобождаются от правила одного определения. А это означает, что проблем с определением простых методов (таких как функции доступа) внутри самого класса возникать не должно.

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

ИЗОБРАЖЕНИЯ

Слайд шоуГалерея слайд шоуМодальное изображениеЛайтбоксОтзывчивая сетка изображенийСетка изображенийГалерея вкладокЭффект наведения на изображениеНаложение слайда на изображениеНаложение на изображениеНаложение заголовка на изображениеНаложение иконки на изображениеЭффект к изображениюЧерно-белое изображениеТекст на изображенииИзображение с текстовым блокомИзображение c прозрачным текстомИзображение на всю страницуФорма на изображенииИзображение герояРазмытое фоновое изображениеФоновое изображениеВыравненные изображенияОкругленные изображенияИзображение аватарОтзывчивое изображениеИзображение по центруМинитюрное изображениеЗнакомство с командойЛипкое изображениеЗеркальное изображениеДрожание изображенияГалерея портфолиоПортфолио фильтрЗум изображенияЛупа изображенияПолзунок сравнения

Как присоединиться к курсу с помощью кода

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

  1. Перейдите на страницу classroom.google.com и нажмите Войти.

    Войдите в аккаунт Google, например imya@shkola.edu или imya@gmail.com. Подробнее…

  2. Войдите в аккаунт, который используете для работы с Классом. Если вам нужно сменить аккаунт, в правом верхнем углу нажмите на фото профиля выберите или добавьте аккаунт.
  3. В верхней части страницы нажмите на значок и выберите «Присоединиться».
  4. Введите код курса и нажмите Присоединиться.

    Примечание. Код курса состоит из 6–7 букв и цифр. Он не должен содержать пробелы и специальные символы.

Как присоединиться к курсу по электронному приглашению

Преподаватель мог отправить вам электронное приглашение на курс. Вы можете присоединиться к курсу из письма или в Классе.

Как принять приглашение в Классе

  1. Перейдите на страницу classroom.google.com и нажмите Войти.

    Войдите в аккаунт Google, например imya@shkola.edu или imya@gmail.com. Подробнее…

  2. Войдите в аккаунт, который используете для работы с Классом. Если вам нужно сменить аккаунт, в правом верхнем углу нажмите на фото профиля выберите или добавьте аккаунт.
  3. На карточке курса нажмите Присоединиться.

Как принять приглашение в письме

  1. Откройте клиент электронной почты, используемый для работы с Классом.
  2. В письме с приглашением нажмите Присоединиться.
  3. Если вы видите ошибку Курс не найден, нажмите на стрелку вниз и войдите в аккаунт, используемый с Классом.Подробнее…
  4. Нажмите Присоединиться.

пример

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

За пределами закрывающего класса доступ к вложенным классам осуществляется с помощью оператора области видимости. Однако изнутри закрывающего класса вложенные классы могут использоваться без квалификаторов:

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

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

C ++ 11

До C ++ 11 вложенные классы имели доступ только к именам типов, членам и счетчикам из охватывающего класса; все остальные члены, определенные в охватывающем классе, были недоступны.

C ++ 11

Начиная с C ++ 11, вложенные классы и их члены обрабатываются так, как если бы они были s входящего класса и могли обращаться ко всем своим членам в соответствии с обычными правилами доступа; если члены вложенного класса требуют возможности оценить один или несколько нестатических членов охватывающего класса, они должны, таким образом, передать экземпляр:

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

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

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

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

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

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

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

Previous Next


С этим читают