|
Дружественные функции (Friend Functions)
Итак, мы уже познакомились с одним из основных правил ООП: поля-данные (переменные) класса должны быть защищены от внешнего воздействия, то есть доступ к ним можно получить только с помощью функций-членов данного класса. Тем не менее, как показывает практика, иногда необходимо получить прямой доступ к полям-данных класса, не используя его интерфейс. Т.е. чуть-чуть отступить от правил и обратиться к полям-данных класса через функцию или функцию-член другого класса. Зачем это может понадобиться? Одна из причин может быть следующей, например - при доступе к внутренним переменным класса через его функции уменьшается эффективность работы за счет затрат на вызов функции. В большинстве случаев это не критично, но не всегда. В некоторых случаях это может играть существенную роль. Конечно, можно добавить новый метод к классу для получения прямого доступа к внутренним переменным. Однако, в большинстве случаев, интерфейс класса спланирован для выполнения определенного круга операций, и наша функция может оказаться как бы ни у дел. Или, еще один пример, нам необходимо получить прямой доступ к внутренним данным двух разных классов. Вот здесь-то и возникает проблемка... Для решения подобного круга задач в С++ и существует возможность описания функции, функции-члена другого класса как дружественной (friend). Класс может предоставлять особые привилегии определенным внешним функциям или функциям-членам другого класса. Вот эти функции и получили название дружественных. Рассмотрим как работает этот механизм. Для описания функции (функции-члена) дружественной тому или иному классу, необходимо в описании этого класса объявить дружественную функцию, используя ключевое слово friend. Причем, обратите внимание, не играет никакой роли в каком из разделов класса Вы разместите объявление дружественной функции. Согласитесь, небольшие нарушения ограничения доступа могут быть весьма полезны, если их использовать с достаточной осторожностью. Словосочетание "с достаточной осторожностью" обозначает, что функция или класс, пользующиеся привилегиями доступа должны быть очень тесно связаны с тем классом, который наделил их этими привилегиями. Главной целью использования дружественных функций и классов является повышение эффективности программы. Дружественные функции и классы могут осуществлять прямой доступ к закрытым полям объекта без использования функций-членов этого класса. Рассмотрим пример использования дружественной функции (объяснение приводится в коментариях): #include <iostream.h>
class A //интерфейс (протокол) класса A { //закрытая область данных private: //хотя, по умолчанию, действует модификатор //доступа private //для читабельности программы мы его укажем явно int pole; //вот эта переменная и будет нашей подопытной
//открытая область данных public: A(){ //конструктор по умолчанию pole=55;//инициализация закрытой переменной //выведем на экран сообщение, что //конструктор отработал cout<<"Constructor A\n"; }
//объявление дружественной функции которая принимает экземпляр класса A friend void ourfriend(const A &f); };
//раз объявили дружественную функцию, то давайте уже и реализуем ее void ourfriend(const A &f) { //итак, для демонстрации работы дружественной функции, мы обратимся //к закрытой переменной pole экземпляра класса f и выведем ее //значение на экран. cout<<"\nWow! We have free access to private members,\n"; cout<<"pole ="<<f.pole<<endl; }
void main() { A example; //создали экземпляр класса А - example ourfriend(example); //собственно тестирование дружественной //функции } Результат выполнения программы: Constructor A
Wow! We have free access to private members, pole =55 Итак, мы смогли получить доступ к закрытой переменной класса из функции (которая, при этом, не является членом класса), используя механизм дружественных функций. Заметим, что при работе с дружественной функцией следует помнить о том, что она не имеет указателя this класса в котором она объявлена как дружественная. Почему?.. Ответ мы найдем в самом смысле использования дружественных функций: дружественная функция не является членом этого класса. Если все функции-члены одного класса являются дружественными функциями другого класса, то это можно записать как: friend class имя_класса; Может ли функция быть дружественной нескольким классам? Да, но если функция дружественна нескольким классам, то надо добавить это описание во все классы, к внутренним данным которых будет производиться обращение. Подозреваем, что у Вас возник вопрос: "А как же быть с защищенностью данных, если можно получить доступ к данным напрямую?" Не волнуйтесь, с точки зрения использования механизма инкапсуляции (напомним, под этим понятием мы подразумеваем сокрытие данных) защищенность данных сохраняется, поскольку полностью исключается всякая возможность доступа к данным так, чтобы объект не был осведомлен об этом. То есть в дружественных функциях мы свободно работаем только с экземплярами даного класса, но не с самим классом. Снова, рассмотрим пример, но на этот раз, в роли дружественных функций у нас будут выступать функции-члены. #include <iostream.h>
class A //интерфейс (протокол) класса A { //открытая область данных public: A(){ //конструктор по умолчанию cout<<"Constructor A\n"; //конструктор отработал } //функция-член FriendToB() будет использоваться как //дружественная функция для класса B void FriendToB(); void SomeFunction(); };
class B //интерфейс (протокол) класса B { //область закрытых данных private: int i,j; //наши "подопытные" закрытые переменные; //если сможем обратиться к этим переменным //из дружественной функии FriendToB(), то //"опыт удался" //область открытых данных public: B(){ i=10; //произведем инициализацию j=17; cout<<"Constructor B\n"; //конструктор отработал } //укажем, что класс B имеет дружественную функцию, которая //является функцией-членом класса A friend void A::FriendToB(); };
class C //интерфейс (протокол) класса C { //область закрытых данных private: int k,n; //закрытые //область открытых данных public: C(){ k=4; //начальная инициализация n=7; cout<<"\n\nConstructor C\n"; //конструктор отработал } //укажем что ВСЕ функции-члены класса A, являются //дружественными для даного класса friend class A; };
//действительно ли может функция-член одного //класса обращаться к закрытым данным другого //класса. Давайте проверим! void A::FriendToB() { B example; //объявим экземпляр класса B cout<<"So, variables' value are:\n"; //и произведем доступ к закрытым данным cout<<"example.i="<<example.i; cout<<"\nexample.j="<<example.j<<endl; }
//действительно ли после объявления в классе С //friend class A; -все функции-члены класса A //станут дружественными функциями для класса С? //Ведь мы нигде не указывали, что функция-член //SomeFunction() является дружественной классу С. //Надо проверить...
void A::SomeFunction() { C temp; //объявим экземпляр класса С cout<<"And now we are testing friend class...\n"; //и произведем доступ к закрытым данным cout<<"temp.k="<<temp.k; cout<<"\ntemp.n="<<temp.n<<endl; }
void main() { A test; //создали экземпляр класса А - example //собственно тестирование дружественной функции классу B test.FriendToB(); //проверим, все ли функции-члены класса A являются дружественными //классу С (на примере, функции SomeFunction) test.SomeFunction(); } Вот что будет выведено на экран, в результате выполнения программы: Constructor A Constructor B So, variables' value are: example.i=10 example.j=17
Constructor C And now we are testing friend class... temp.k=4 temp.n=7 Итак, если функция или функция-член объявлены как дружественные, то такие функции или функции-члены такого класса могут осуществлять непосредственный доступ ко всем (private, protected, public) данным (полям-данным и функциям-членам) класса, для которого они дружественны. |
Данный листочек посвящен основам чтения данных из файлов и вывода данных в файл.
На самом деле, файловый ввод-вывод ничем не отличается от консольного. За единственным исключением – если данные читаются из файла, то в любой момент можно вернуться к началу файла и считать все заново.
Для того, чтобы в C++ работать с файлами, необходимо подключить заголовочный файл fstream:
#include <fstream>
После этого можно объявлять объекты, привязанные к файлам: для чтения данных из файла используются объекты типа ifstream (аббревиатура от i nput f ile stream, для записи данных в файл используются объекты типа ofstream (o utput f ile stream). Например
ifstream in; // Поток in будем использовать для чтения
ofstream out; // Поток out будем использовать для записи
Чтобы привязать тот или иной поток к файлу (открыть файл для чтения или для записи) используется метод open, которому необходимо передать параметр – текстовую строку, содержащую имя открываемого файла.
in.open("input.txt");
out.open("output.txt");
После открытия файлов и привязки их к файловым потокам, работать с файлами можно так же, как со стандартными потоками ввода-вывода cin и cout. Например, чтобы вывести значение переменной x в поток out используются следующая операция
out<<x;
А чтобы считать значение переменной из потока in
in>>x;
Для закрытия ранее открытого файла используется метод close() без аргументов:
in.close();
out.close();
Закрытый файловый поток можно переоткрыть заново при помощи метода open, привязав его к тому же или другому файлу.
РОК 13. ПЕРЕГРУЗКА ФУНКЦИЙ При определении функций в своих программах вы должны указать тип возвращаемого функцией значения, а также количество параметров и тип каждого из них. В прошлом (если вы программировали на языке С), когда у вас была функция с именем add_values, которая работала с двумя целыми значениями, а вы хотели бы использовать подобную функцию для сложения трех целых значений, вам следовало создать функцию с другим именем. Например, вы могли бы использовать add_two_values и add_three_values. Аналогично если вы хотели использовать подобную функцию для сложения значений типа float, то вам была бы необходима еще одна функция с еще одним именем. Чтобы избежать дублирования функции, C++ позволяет вам определять несколько функций с одним и тем же именем. В процессе компиляции C++ принимает во внимание количество аргументов, используемых каждой функцией, и затем вызывает именно требуемую функцию. Предоставление компилятору выбора среди нескольких функций называется перегрузкой. В этом уроке вы научитесь использовать перегруженные функции. К концу данного урока вы освоите следующие основные концепции: • Перегрузка функций позволяет вам использовать одно и то же имя для нескольких функций с разными типами параметров. • Для перегрузки функций просто определите две функции с одним и тем же именем и типом возвращаемого значения, которые отличаются количеством параметров или их типом. Перегрузка функций является особенностью языка C++, которой нет в языке С. Как вы увидите, перегрузка функций достаточно удобна и может улучшить удобочитаемость ваших программ. ПЕРВОЕ ЗНАКОМСТВО С ПЕРЕГРУЗКОЙ ФУНКЦИЙ Перегрузка функций позволяет вашим программам определять несколько функций с одним и тем же именем и типом возвращаемого значения. Например, следующая программа перегружает функцию с именем add_values. Первое определение функции складывает два значения типа int. Второе определение функции складывает три значения. В процессе компиляции C++ корректно определяет функцию, которую необходимо использовать: #include <iostream.h> int add_values(int a,int b) { int add_values (int a, int b, int c) ( void main(void) { Как видите, программа определяет две функции с именами add_values Первая функция складывает два значения типа int, в то время как вторая складывает три значения. Вы не обязаны что-либо предпринимать специально для того, чтобы предупредить компилятор о перегрузке, просто используйте ее. Компилятор разгадает, какую функцию следует использовать, основываясь на предлагаемых программой параметрах. Подобным образом следующая программа MSG_OVR.CPP перегружает функцию show_message. Первая функция с именем show_message выводит стандартное сообщение, параметры ей не передаются. Вторая выводит передаваемое ей сообщение, а третья выводит два сообщения: #include <iostream.h> void show_message(void) { void show_message(char *message) { void show_message(char *first, char *second) { void main(void) { КОГДА НЕОБХОДИМА ПЕРЕГРУЗКА Одним из наиболее общих случаев использования перегрузки является применение функции для получения определенного результата, исходя из различных параметров. Например, предположим, что в вашей программе есть функция с именем day_of_week, которая возвращает текущий день недели (0 для воскресенья, 1 для понедельника,..., 6 для субботы). Ваша программа могла бы перегрузить эту функцию таким образом, чтобы она верно возвращала день недели, если ей передан юлианский день в качестве параметра, или если ей переданы день, месяц и год: int day_of_week(int julian_day) { int day_of_week(int month, int day, int year) { По мере изучения объектно-ориентированного программирования в C++, представленного в следующих уроках, вы будете использовать перегрузку функций для расширения возможностей своих программ. Перегрузка функций улучшает удобочитаемость программ Перегрузка функций C++ позволяет вашим программам определять несколько функций с одним и тем же именем. Перегруженные функции должны возвращать значения одинакового типа*, но могут отличаться количеством и типом параметров. До появления перегрузки функций в C++ программисты языка С должны были создавать несколько функций с почти одинаковыми именами. К сожалению программисты, желающие использовать такие функции, должны были помнить, какая комбинация параметров соответствует какой функции. С другой стороны, перегрузка функций упрощает задачу программистов, требуя, чтобы они помнили только одно имя функции. * Перегруженные функции не обязаны возвращать значения одинакового типа по той причине, что компилятор однозначно идентифицирует функцию по ее имени и набору ее аргументов. Для компилятора функции с одинаковыми именами, но различными типами аргументов — разные функции, поэтому тип возвращаемого значения — прерогатива каждой функции. — Прим.перев. ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ Перегрузка функций позволяет вам указать несколько определений для одной и той же функции. В процессе компиляции C++ определит, какую функцию следует использовать, основываясь на количестве и типе передаваемых параметров. Из данного урока вы узнали, что перегружать функции достаточно просто. Из урока 14 вы узнаете, как ссылки C++ упрощают процесс изменения параметров внутри функций. Однако, прежде чем перейти к уроку 14, убедитесь, что вы изучили следующие основные концепции:
Предыдущий урок | Следующий урок | Начало формы
Конец формы
Тут стрейч пленка оптом. |
Дата добавления: 2015-10-21; просмотров: 33 | Нарушение авторских прав
<== предыдущая лекция | | | следующая лекция ==> |
Письменные источники по истории древних германцев содержат данные, без изучения которых невозможно понять специфику исторического развития как народов, населявших в древности Северную и | | | Концепция общественной безопасности России |