Студопедия
Случайная страница | ТОМ-1 | ТОМ-2 | ТОМ-3
АрхитектураБиологияГеографияДругоеИностранные языки
ИнформатикаИсторияКультураЛитератураМатематика
МедицинаМеханикаОбразованиеОхрана трудаПедагогика
ПолитикаПравоПрограммированиеПсихологияРелигия
СоциологияСпортСтроительствоФизикаФилософия
ФинансыХимияЭкологияЭкономикаЭлектроника

Дружественные функции (Friend Functions)



Дружественные функции (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)

{
return(a + b);
)

int add_values (int a, int b, int c)

(
return(a + b + c);
)

void main(void)

{
cout << "200 + 801 = " << add_values(200, 801) << endl;
cout << "100 + 201 + 700 = " << add_values(100, 201, 700) << endl;
}

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

Подобным образом следующая программа MSG_OVR.CPP перегружает функцию show_message. Первая функция с именем show_message выводит стандартное сообщение, параметры ей не передаются. Вторая выводит передаваемое ей сообщение, а третья выводит два сообщения:

#include <iostream.h>

void show_message(void)

{
cout << "Стандартное сообщение: " << "Учимся программировать на C++" << endl;
}

void show_message(char *message)

{
cout << message << endl;
}

void show_message(char *first, char *second)

{
cout << first << endl;
cout << second << endl;
}

void main(void)

{
show_message();
show_message("Учимся программировать на языке C++!");
show_message("B C++ нет предрассудков!","Перегрузка - это круто!");
}

КОГДА НЕОБХОДИМА ПЕРЕГРУЗКА

Одним из наиболее общих случаев использования перегрузки является применение функции для получения определенного результата, исходя из различных параметров. Например, предположим, что в вашей программе есть функция с именем 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, убедитесь, что вы изучили следующие основные концепции:

    1. Перегрузка функций предоставляет несколько "взглядов" на одну и ту же функцию внутри вашей программы.
    2. Для перегрузки функций просто определите несколько функций с одним и тем же именем и типом возвращаемого значения, которые отличаются только количеством и типом параметров.
    3. В процессе компиляции C++ определит, какую функцию следует вызвать, основываясь на количестве и типе передаваемых параметров.
    4. Перегрузка функций упрощает программирование, позволяя программистам работать только с одним именем функции.

Предыдущий урок | Следующий урок

Начало формы

 

Конец формы

 

Тут стрейч пленка оптом.

сумки- холодильники- бесплатную доставку в интернет- магазине Комус.

 


Дата добавления: 2015-10-21; просмотров: 33 | Нарушение авторских прав




<== предыдущая лекция | следующая лекция ==>
Письменные источники по истории древних германцев содержат данные, без изучения которых невозможно понять специфику исторического развития как народов, населявших в древности Северную и | Концепция общественной безопасности России

mybiblioteka.su - 2015-2024 год. (0.028 сек.)