- •Методические указания к лабораторным работам
- •Лабораторная работа №1 простые программы с циклами и операторами консольного ввода/вывода
- •Задание
- •Описание примера
- •Методика выполнения
- •Содержание отчета
- •Контрольные вопросы
- •Лабораторная работа №2 работа с текстовыми файлами, структурами данных и меню
- •Задание
- •Структурное программирование и функциональная декомпозиция системы
- •Функции
- •Организация меню в консольном приложении
- •Структуры данных
- •Операции с файлами
- •Методика выполнения
- •Содержание отчета
- •Контрольные вопросы
- •Лабораторная работа №3 разработка и спецификация функций и модулей программы
- •Задание
- •Модульная структура программ
- •Параметры командной строки
- •Методика выполнения
- •Содержание отчета
- •Контрольные вопросы
- •Лабораторная работа №4 разработка и спецификация структур данных, использование указателей и динамических массивов структур
- •Задание
- •Указатели
- •Методика выполнения
- •Содержание отчета
- •Контрольные вопросы
- •Лабораторная работа №5 использование объектно-ориентированного программирования в разработке приложений
- •Задание
- •Конструкторы и деструкторы
- •Конструктор по умолчанию
- •Конструктор копирования
- •Массивы объектов
- •Friend-конструкции
- •Методика выполнения
- •Содержание отчета
- •Контрольные вопросы
- •Лабораторная работа №6 использование наследования, полиморфизма и абстрактных классов
- •Задание
- •Наследование данных и методов
- •Полиморфизм и виртуальные функции
- •Абстрактный класс
- •Методика выполнения
- •Содержание отчета
- •Контрольные вопросы
- •Лабораторная работа №7
- •Сложные структуры из объектов классов
- •Цель работы - изучение организации различных структур данных и разработка методов манипулирования данными.
- •Задание
- •Методика выполнения
- •Содержание отчета
- •Контрольные вопросы
- •Лабораторная работа №8 разработка windows-интерфейса приложения
- •Задание
- •Методика выполнения
- •Содержание отчета
- •Контрольные вопросы
- •Лабораторная работа №9 разработка и использование com-сервера
- •Задание
- •Шаблоны классов
- •Использование библиотеки atl для создания серверов сом
- •Методика выполнения
- •Содержание отчета
- •Контрольные вопросы
- •Литература
Friend-конструкции
Private-данные и методы какого-либо класса прямо доступны только внутри методов класса. Это требует обеспечить каждый класс достаточным количеством методов для манипуляции данными во всех реально встречаемых ситуациях. C++ имеет способ обойти это жесткое ограничение и позволить избранным внешним классам, отдельным методам класса или обычным внешним функциям прямой доступ ко всем данным класса, в том числе и к private. Самое простое — это объявить обычную функцию (не являющуюся членом какого-либо класса) friend-функцией класса. Теперь она имеет доступ ко всем данным и методам класса и может модифицировать private-данные класса. Например:
class A {
private:
int i; // private-данное
friend void set (A*, int); // friend-функция
public:
void set (int j) { i=j; } // public-метод
void p() { printf("\n i=%d”,i);}
};
void set (A* p, int i) // Тело friend-функции
{
p->i=i;
}
void main()
{
A obj; // Объект obj класса А
set (&obj, 10); //Доступ с помощью friend-функции
obj.p(); // Вывод поля
obj.set (11); // Доступ с помощью public-метода
obj.p();
}
В примере объявлена внешняя функция set. Она не принадлежит никакому классу. Но она, будучи объявленной friend-функцией класса А, имеет прямой доступ к private-переменной класса А, так же, как и метод set собственного класса. Указатель (или ссылку) на объект класса А следует передать в качестве параметра, так как friend-функция является посторонней функцией и не знает объекта obj класса А (в отличие от метода void A::set(int); класса, которому неявно передается указатель this). При вызове внешней функции мы передаем ей &obj — адрес объекта. Этот адрес присваивается локальному указателю р, после чего выражение p->i позволяет обратиться к private-переменной объекта, на который ссылается указатель р. Поле объекта obj прямо доступно внутри friend-функции set.
Friend-функция может принадлежать какому-либо другому классу. Тогда ее можно назвать friend-методом. Чтобы сократить время доступа метода класса В к методу класса А, целесообразно объявить в классе А конкретный метод класса В как friend-метод класса А. Кроме того, в классе А можно объявить весь класс В как friend-клacc класса А. Теперь все методы класса В являются friend-методами класса А, то есть внутренние данные класса А теперь прямо доступны в методах класса В. Объявление friend-конструкций может быть размещено в любой секции (private, protected, public) описания класса. Это не влияет на смысл объявления. Следующая схема иллюстрирует рассмотренные возможности:
class A{
friend int strange(char* ch); // Friend-функция
friend class B; // Friend-класс
friend char* C::methc(); // Friend-метод класса С
};
Свойство friend не транзитивно, то есть если класс В объявлен friend-классом в классе А, а класс С — в классе В, то это не означает, что класс С является friend-классом для класса А. Свойство friend не наследуется.
Функции ввода-вывода библиотеки классов C++
Процедуры ввода-вывода библиотеки, на которую ссылается iostream.h, осуществляют преобразование значений переменные стандартных типов (int, double и т.п.) в последовательность символов, и наоборот. В библиотеке описан класс ostream, в котором операция << переопределена так, чтобы вывод в поток данных всех стандартных типов языка C++ был максимально простым и удобным. Операция >> переопределена в классе istream для ввода данных стандартных типов. Для ввода и вывода внутренних данных классов, определенных пользователем, операции >> и << должны быть переопределены в этих классах.
В библиотеке определены объекты класса ostream для организации операций ввода/вывода:
cout - связан со стандартным потоком вывода и является аналогом потока stdout в библиотеке stdio.h;
сerr -поток вывода сообщений об ошибках ввода-вывода;
clog - поток вывода сообщений пользователю;
endl – заканчивает строку вывода и очищает буфер (переход на новую строку).
Объект cin класса istream обозначает стандартный поток ввода.
Пример иллюстрирует использование операций >> и << для ввода и вывода стандартных типов.
#include <iostream.h>
void main()
{
char с = 'С', str[80];
int i = -32767;
float v = 2.99792e8, t=3, s=0.;
//= = = = = = = Вывод в поток cout = = = ====//
cout <<\n i=" << i;
cout << "\n c=" << c;
cout << "\n s+vt= " << (s+v*t) << " m";
cout << "\nEnter an integer : "; cin >> i;
cout << "\nA real : "; cin >> s;
cout << "\nA string of chars: "; cin >> str;
cout << "\nYou entered: "<<i<<' '<<s<<' '<<str<<endl;
}
Результат работы этой программы:
i=-32768
с=С
s+vt= 8.99376e+08 m
Для форматирования текста в потоке вывода используются функции-манипуляторы:
setw(n) – устанавливает ширину поля, равной n-символам
cout << "d1 = " << setw(20) << d1 << endl;
setprecision(n) – устанавливает количество выводимых цифр для вещественных чисел
cout << " v=" << setprecision(3)<<v;
setioflags(flag1 | flag2 | …) – устанавливает флаги форматирования выходного потока
cout << setiosflags(ios_base::scientific);
Значения флагов, а также описание действия манипуляторов resetioflags, setfill, setbase смотрите в MSDN Library.
Переопределение операций
С помощью ключевого слова operator программист для объектов класса может переопределить смысл любой из более чем 40 встроенных операций языка (*, /, +, -, &&, и т.д.). Существуют два способа переопределения бинарных операций: public-методом с одним аргументом и friend-функцией с двумя аргументами. Унарную операцию можно переопределить либо public-методом класса без параметров, либо friend-функцией с одним параметром.
Рассмотрим выражение х+у, где х, у являются объектами какого-либо класса. Предположим, что в этом классе одним из двух возможных способов переопределена бинарная операция +. Тогда выражение х+у может быть интерпретировано компилятором также двумя способами:
х.operator + (у); //либо как
operator + (х,у);
Первая интерпретация означает, что объекту х посылается сообщение operator+(у). То есть вызывается функция (член класса) с именем operator+(), при этом в качестве единственного параметра передается объект у. Вторая означает вызов внешней функции с именем operator+(), которой передаются два параметра х и у.
Функция, переопределяющая операцию, не может изменить количество аргументов операции. Например, унарная операция не может стать бинарной, и наоборот. Невозможно также изменить существующий приоритет при нормальном использовании операции.
Переопределяя операции, мы как бы наделяем язык свойствами, которыми он раньше не обладал. Например, переопределив операции +,-,*,/ в классе комплексных чисел, мы как бы наделяем язык C++ способностью оперировать комплексными числами по обычным правилам.
Оператор cout<<i; интерпретируется как cout.operator<<(i);. Так как тип возвращаемого значения операции - ostream&, то возможна цепочка последовательного вывода. Оператор cout<<i<<j; интерпретируется как (cout.operator<<(i)).operator<<(j);.
Для ввода и вывода переменных нестандартных типов, например, объектов класса, операции << и >> следует переопределить в этом классе с помощью friend-функций.
class Vector {
double x1, x2; // Координаты вектора
public:
// Три конструктора
Vector (double c1, double c2) // С параметрами
{x1=c1; x2=c2;}
Vector() // По умолчанию
{x1=x2= 0.; }
Vector (const Vector& v) // Копирования
{x1=v.x1; x2=v.x2;}
//= = = = = = Переопределение операций =====//
friend ostream& operator<<(ostream&, Vector& v);
friend istream& operator>>(istream&, Vector& v);
};
Pеализуем тела friend-функций вне блока описания класса.
ostream& operator<<(ostream& os, Vector& v)
{return os<<"\n Vector: x1="<<v.x1<<" x2="<<v.x2;}
istream& operator>>(istream& is, Vector& v)
{
double d1, d2;
cout << "\n x1= "; is >> d1;
cout << " x2= "; is >> d2;
v=Vector (d1, d2); // Создание объекта
return is; // Входной поток
}
Первый формальный параметр (&os) функции operator<< — ссылка на объект класса ostream. С ним связан стандартный поток cout. Второй параметр является ссылкой на объект класса Vector. Функция возвращает адрес выходного потока. Аналогично для переопределенной операции ввода. Теперь можно вводить и выводить объекты класса Vector:
Vector х, у;
//Переопределенные операции ввода-вывода cout << "\n\n Enter vector:"; cin >> x; cout << "\n Enter one more:"; cin >> y; cout << "\n\n You have entered: " << x <<y;