- •Экзамен 374 Предварительные рассуждения Вступительное слово
- •Исторические факты
- •Начнем!
- •Проба пера
- •Открытие сохраненного проекта
- •Вывод данных
- •Типы данных
- •Хороший стиль программирования
- •Переменные и константы
- •Практический пример
- •Ввод данных
- •Например:
- •Пример:
- •Арифметические операции с числами
- •Литералы
- •Некоторые примеры
- •Домашнее задание
- •Напишите программу, которая вводит число из трех цифр, разделяет число на отдельные цифры и печатает их отдельно друг от друга с тремя пробелами между ними. Преобразование типов
- •Перечисляемые типы
- •Типичная ошибка
- •Хороший стиль программирования
- •Типичная ошибка
- •Выражения
- •Оператор if
- •Структура программы
- •Логические операции
- •Структура множественного выбора switch
- •Практический пример
- •Цикл for
- •Практический пример
- •Цикл do-while
- •Домашнее задание
- •Вызов функции
- •Прототипы функций
- •Разбор программы
- •Область видимости
- •Аргументы по умолчанию
- •Встраивание
- •Перегрузка функций
- •Учебный пример перегруженных функций. Иллюстрация перегрузки
- •Результат работы программы
- •Практические примеры
- •Домашнее задание
- •Примеры домашней работы урока 1 Пример №1
- •Как работает программа
- •Пример №2
- •Как работает программа
- •Примеры домашних работ на создание функций Пример №1
- •Как работает программа
- •Пример №2
- •Как работает программа
- •Массивы
- •Объявление массивов
- •Примеры использования массивов
- •Программа 1
- •Программа 2
- •Обратите внимание!
- •Типичная ошибка программирования
- •Типичная ошибка программирования
- •Программа 3
- •Типичная ошибка программирования
- •Замечание по технике программирования
- •Программа 4
- •Программа 5
- •Программа нахождения минимального и максимального элементов массива
- •Сортировка массивов
- •Домашнее задание
- •Что такое указатели?
- •За кулисами...
- •Как работать с указателями?..
- •Зачем нужны указатели?
- •Указатели и Массивы.
- •Примеры задач
- •Пример 1
- •Пример 2
- •Пример 3
- •Указатели - аргументы функций.
- •Ссылочные параметры
- •Примеры решения задач
- •Домашнее задание
- •Операторы свободной памяти new и delete
- •Функции работы со строками из библиотеки обработки строк
- •Пример 1.
- •Пример2
- •Пример 3
- •Пример задачи на новый материал
- •Домашнее задание
- •Двухмерные массивы, как частный случай многомерных массивов
- •Программа.
- •Результаты работы программы.
- •Многомерные динамические массивы
- •Пример на многомерные динамические массивы
- •Домашнее задание
- •Рекурсия
- •Рекурсии или итерации
- •Указатели на функции
- •Пример №1
- •Результат выполнения программы:
- •Пример №2
- •Результат выполнения программы
- •Пример №3
- •Результаты выполнения программы
- •Определения структур
- •Пример #1 на использование структур
- •Пример #2 на использование структур
- •Оператор указателя на структуру
- •Домашнее задание
- •Тест по c Группа ___________________ф. И. О. ______________________
- •Объектно-ориентированное программирование.
- •Наследование (Inheritance).
- •Инкапсуляция (Encapsulation).
- •Определение класса
- •Конструкторы и деструкторы Инициализация объектов класса: конструкторы
- •Основное назначение конструкторов - инициализация объектов.
- •Использование конструкторов с аргументами по умолчанию
- •Если параметры не передаются конструктору, в определении объекта не нужно включать пустые круглые скобки.
- •Использование деструкторов
- •Когда вызываются конструкторы и деструкторы.
- •Домашнее задание
- •Конструктор копирования
- •Синтаксис конструктора копирования
- •Памятка
- •Пример использования конструктора копирования.
- •Перегруженные конструкторы
- •Экскурс в историю
- •Послесловие к примеру
- •Маленькое замечание
- •Домашнее задание
- •Создание класса ''строка''
- •Перегрузка операций.
- •Общие принципы перегрузки операторов.
- •Преобразования, определяемые классом
- •Пример строкового класса с перегруженными операторами
- •Домашнее задание
- •Дружественные функции (Friend Functions)
- •Пример строкового класса с перегруженными операторами и дружественными функциями
- •Перегрузка операторов new и delete
- •Перегрузка оператора индексирования
- •Класс вектор. Часть1.
- •Класс вектор. Часть 2.
- •Класс вектор. Часть 3.
- •Домашнее задание
- •Наследование (Inheritance). Часть 1.
- •Наследование (Inheritance). Часть 2.
- •Множественное наследование (multiple inheritance)
- •Пример множественного наследования
- •Домашнее задание
- •Статические члены данных
- •Раннее и позднее связывание
- •Виртуальные функции
- •Пример.
- •Абстрактные классы
- •Виртуальный базовый класс
- •Практический пример
- •Домашнее задание
- •Потоки ввода-вывода.
- •Iostream.H: stream - поток, "I" - сокр. Input - ввод, "o" - сокр. Output - вывод.
- •Предопределенные потоки.
- •Операции помещения в поток и извлечения из потока.
- •Файловый ввод-вывод с применением потоков.
- •Конструкторы файловых потоков.
- •Функции для открытия и закрытия файлов.
- •Функции для обмена с потоками.
- •Часто применяемые функции потока.
- •Ввод/вывод массива в/из файл(-а).
- •Практический пример: перекодировка файла.
- •Домашнее задание
- •Немного о файлах...
- •И снова файлы...
- •Пример "Телефонная книга"
- •Файл abonent.H
- •Форматирование данных при обменах с потоками.
- •Состояние потока.
- •Использование аргументов командной строки.
- •Ввод/вывод в с.
- •Домашнее задание
- •Определение шаблонов функций
- •Переопределение шаблонов функций
- •Шаблоны классов
- •Шаблонный класс вектор
- •Шаблонный класс вектор
- •Шаблонный класс вектор
- •Введение
- •Обработка исключительных ситуаций
- •Практический пример
- •Программа
- •Домашнее задание
- •Экзамен
Пример строкового класса с перегруженными операторами и дружественными функциями
Рассмотрим следующую задачу: необходимо добавить в строковый класс, функцию сцепления строк, используя перегрузку бинарного оператора +, функцию пересечения строк, используя перегрузку бинарного оператора *, а также реализовать функции ввода-вывода строк и сортировки строк.
Чтобы решить данную задачу, воспользуемся знаниями, полученными на этом занятии. Подробные пояснения к примеру приводятся в коментариях.
#include <string.h>
#include <iostream.h>
class string // Объявление строкового класса
{
private:
char* S; // Строка
int len; // Длина строки
public:
string(); // Конструктор по умолчанию
string(const char* s); // Перегруженный конструктор
string(const string& s); // Конструктор копирования
~string() { delete [] S; }// Деструктор
// Дружественные функции
// Перегрузка бинарного оператора
// Функция реализует сцепление строк
friend string operator+(const string&, const string&);
// Перегрузка бинарного оператора
// Функция реализует пересечение строк
friend string operator*(const string&, const string&);
// Перегрузка унарного оператора
// Функция реализует поиск уникальных элементов строки
friend string operator!(const string&);
// Перегрузка бинарного оператора
// Функция реализует ввод объектов класса с клавиатуры
friend istream& operator>>(istream&, string&);
// Перегрузка бинарного оператора
// Функция реализует вывод объектов класса на экран
friend ostream& operator<<(ostream&, string&);
string& operator=(const string&);
// Перегрузка типа
// Функция реализует преобразование объекта класса к типу char*
operator char*() { return S; }
void Sort(string s[], int n); // Функция сортировки
int GetLen() { return len; } // Функция возвращает длину строки
};
string::string()
{
S = new char[81]; // Выделение памяти под 81 элемент
// строки для объекта по умолчанию
for(int i = 0; i < 81; i++)
S[i] = 0;
len = 80;
}
string::string(const char* s)
{
len = strlen(s);
S = new char[len + 1];
strcpy(S, s); // Инициализация строкой, переданной
// пользователем
}
string::string(const string& s)
{
len = s.len;
S = new char[len + 1]; // Безопасное копирование
strcpy(S, s.S);
}
void string::Sort(string s[], int n)
{ // Сортировка строк
bool flag = true;
string temp;
for(int j = 1; ; j++)
{
for(int i = 0; i < n - j; i++)
if(strcmp(s[i], s[i + 1]) > 0) // Происходит обращение к
{ // строкам напрямую, благодаря
// неявному вызову функции класса string
// operator char*()
temp = s[i]; // Вызов функции operator=(s[i])
s[i] = s[i + 1]; // Вызов функции operator=(s[i + 1])
s[i + 1] = temp; // Вызов функции operator=(temp)
flag = false;
}
if(flag)
break;
flag = true;
}
}
string operator+(const string &str1, const string &str2)
{ // Функция сцепления строк
string s; // Создание временного объекта
delete [] s.S; // Удаление старой строки
s.len = str1.len + str2.len;// Вычисление новой длины строки
s.S = new char[s.len + 1]; // Выделение памяти под новую строку
strcpy(s.S, str1.S); // Инициализация первой части строки
strcat(s.S, str2.S); // Инициализация второй части строки
return s; // Возврат нового объекта
}
string operator*(const string &str1, const string &str2)
{ // Функция сцепления строк
string s; // Создание временного объекта
delete [] s.S; // Удаление старой строки
s.len = (str1.len > str2.len)? str1.len : str2.len;
// Вычисление новой длины строки
s.S = new char[s.len + 1]; // Выделение памяти под новую строку
int s1 = str1.len; // Длина первой строки
int s2 = str2.len; // Длина второй строки
int k = 0; // Индекс результирующей строки
for(int i = 0; i < s1; i++)
for(int j = 0; j < s2; j++)
if(str1.S[i] == str2.S[j])
{
s.S[k] = str1.S[i];
// Помещаем найденный символ,
k++; // увеличиваем индекс,
break; // выходим из цикла
}
s.S[k] = 0; // Вставляем завершающий символ \0
s.len = strlen(s.S);
if(s.len > 1)
return !s; // Возврат нового объекта
// Вызов функции operator !(s)
return s;
}
string operator!(const string& str)
{ // Функция нахождения уникальных
// элементов строк
string s;
delete [] s.S;
int l = s.len = str.len;
s.S = new char[s.len + 1];
s.S[0] = str.S[0]; // Копирование первого символа
for(int n = 1; n < l; n++)
s.S[n] = 0; // Обнуление остальных элементов
int k = 1; // Индекс для новой строки
bool flag = true;
for(int i = 1; i < l; i++) // Поиск уникальных элементов
{
for(int j = 0; j < l; j++)
if(s.S[j] == 0)
break;
else if(s.S[j] == str.S[i])
{
flag = false;
break;
}
if(flag)
{
s.S[k] = str.S[i]; // Добавление уникального символа
// в строку
k++; // Увеличение индекса
}
flag = true;
}
s.S[k] = 0;
return s; // Возврат полученной строки
}
istream& operator>>(istream& is, string& str)
{
char temp[256];
is.getline(temp, 256); // Запрос строки
delete [] str.S;
str.len = strlen(temp); // Выделение памяти под новую строку
str.S = new char[str.len + 1];
strcpy(str.S, temp); // Копирование строки
return is; // Возврат объекта ввода
}
ostream& operator<<(ostream& os, string& str)
{
os << str.S; // Вывод строки
return os; // Возврат объекта вывода
}
string& string::operator=(const string &str)
{ // Функция, реализующая безопасное присваивание
delete [] S; // Удаление старой строки
len = str.len; // Вычисление новой длины строки
S = new char[len + 1]; // Выделение памяти под новую строку
strcpy(S, str.S); // Инициализация строки
return *this; // Возврат ссылки на "самого себя"
// Благодаря этому возможно многократное
// присваивание объектов друг другу
// например, string a, b, c; a = b = c;
}
void main(void)
{
string a, b, c;
cout << "Input the first part of string:\t\t";
cin >> a;
cout << "Input the second part of string:\t";
cin >> b;
// Использывались неявные вызовы функций
// operator >>(cin, a) и operator >>(cin, b)
c = a + b + "\nThat's all !\n";
// Произошел вызов конструктора преобразования
// для строки "\nThat's all !\n"
cout << c; // Вывод результирующей строки
// Неявный вызов функции operator <<(cout, c)
c = a * b + "\nThat's all !\n";
cout << c;
}
Перегрузка инкремента (++) и декремента (--)
В отличие от всех других унарных операций операции ++ и -- имеют, кроме префиксной формы еще и постфиксную. Это привело к особенностям при их перегрузке.
Чтобы перегрузить операцию инкремента для получения возможности использования и префиксной, и постфиксной форм, каждая из этих двух перегруженных функций-членов, должна иметь разную сигнатуру, чтобы компилятор имел возможность определить, какая версия ++ имеется в виду в каждом конкретном случае. Префиксный вариант перегружается так же, как любая другая унарная операция.
Предположим, мы хотим прибавить 1 к объекту с0 класса С. Когда компилятор встречает выражение с префиксным инкрементом ++c0, он генерирует вызов функции-элемента c0.operator++(), прототип которой должен иметь вид:
C& operator++ ();
Если префиксная форма инкремента реализуется как функция, которая не является функцией-членом класса, то, когда компилятор встречает выражение ++c0, он генерирует вызов функции operator++(c0). Прототип такой функции должен быть объявлен в классе С как дружественный:
friend C& operator++(C&);
Вы уже, наверное, сгораете от любопытства, а как же перегружается постфиксная форма операции инкремента, ведь компилятор должен быть способен различать сигнатуры перегруженных функций-операций инкремента в постфиксной и префиксной формах. Все очень просто. По соглашению, принятому в С++, когда компилятор встречает выражение постфиксной формы инкремента c0++, он генерирует вызов функции с0.operator++(0), прототипом которой является
C& operator++(int).
Нуль (0) в генерируемом вызове функции явялется чисто формальным значением, введенным для того, чтобы сделать список аргументов функции operator++, используемой для постфиксной формы инкремента, отличным от списка агументов функции operator++, используемых для префиксной формы инкремента. Фиктивный параметр int никогда не используется, а лишь служит признаком того, что функция вызывается для выполнения операции ++ или -- в постфиксном варианте.
Если постфиксная форма инкремента реализуется как функция, которая не является функцией-членом класса, то, когда компилятор встречает выражение c0++, он генерирует вызов функции operator++(c0, 0). Прототип такой функции должен быть объявлен в классе С как дружественный:
friend C& operator++(C&, int);
Опять формальный аргумент 0 используется компилятором только для того, чтобы список аргументов функции operator++, которая используется для постфиксной формы инкремента, отличался от списка аргументов функции operator++, используемой для префиксной формы инкремента.
Все рассмотренное в этом разделе по отношению к перегрузке операций инкремента в префиксной и постфиксной формах, применимо и к перегрузке операций декремента.
Следующая программа просто иллюстрирует возможности применения разных операций-функций для постфиксной и префиксной операций ++ и --.
#include <iostream.h>
class pair
{
int N;
double x;
friend pair& operator ++(pair&);
friend pair& operator ++(pair&, int);
public:
pair (int n, double xn)
{
N = n;
x = xn;
}
void display()
{
cout << "Координаты: N = " << N
<< "\tx = " << x << endl;
}
pair& operator --()
{
cout << "prefix --" << endl;
N /= 10; x /= 10;
return *this;
}
pair& operator --(int k)
{
cout << "postfix --" << endl;
N/=2; x /= 2.0;
return *this;
}
};
pair& operator ++(pair& P)
{
cout << "prefix ++" << endl;
P.N *= 10;
P.x *= 10;
return P;
}
pair& operator ++(pair& P, int k)
{
cout << "postfix ++" << endl;
P.N = P.N * 2+ k;
P.x = P.x * 2 + k;
return P;
}
void main ()
{
pair Z(10, 20.0);
Z.display();
++Z;
Z.display();
--Z;
Z.display();
Z++;
Z.display();
Z--;
Z.display();
}
Для демонстрации полной независимости смысла перегруженной операции от ее традиционного (стандартного) значения в операциях-функциях для префиксных операций ++ соответствуют увеличению в 10 раз, а -- - уменьшению в 10 раз. Для постфиксных операций ++ определено как увеличение в 2 раза, а -- как уменьшение в 2 раза. Попытки использовать в постфиксных операциях-функциях значение дополнительного параметра int k подтверждает его равенство 0.
Результаты выполнения программы:
Координаты: N =10 x = 20
prefix ++
Координаты: N = 100 x = 200
prefix --
Координаты: N = 10 x = 20
postfix ++
Координаты: N = 20 x = 40
postfix --
Координаты: N = 10 x = 20