- •Предисловие
- •Глава 1. Основные понятия
- •1.1. Элементы языка программирования
- •1.2. Процесс создания программы
- •1.3. Первая программа
- •1.4. Состав программы
- •Глава 2. Средства разработки на C++
- •2.1. Системы Turbo C++ 3.0/Borland C++ 3.1
- •2.2. Система C++ Builder
- •Глава 3. Работа с числовыми данными
- •3.1. Целые типы
- •3.2. Числа с плавающей точкой
- •3.3. Ввод и вывод чисел
- •3.4. Логический тип и логические операции
- •3.5. Математические функции
- •Глава 4. Операторы. Ключевые слова
- •4.1. Операторы
- •4.2. Приоритеты операторов
- •4.3. Ключевые слова
- •4.4. Структура программы
- •4.5. Константы
- •Задачи - . Простейшие вычисления
- •Глава 5. Управление и циклы
- •5.1. Условный оператор
- •5.2. Операторы цикла
- •5.3. Переключатель
- •5.4. Операторы break и continue
- •Задачи -. Выбор и циклы
- •Глава 6. Массивы
- •6.1. Одномерные массивы
- •6.2. Двумерные массивы
- •Задачи -. Одно- и двумерные массивы
- •Глава 7. Функции
- •7.1. Определение функции
- •7.2. Формальные параметры и фактические аргументы
- •7.3. Автоматические и статические переменные
- •7.4. Прототипы функций
- •7.5. Массивы как аргументы функций
- •7.6. Внешние переменные
- •7.7. Рекурсия
- •7.8. Перегруженные имена функций
- •7.9. Аргументы функций по умолчанию
- •Задачи -. Функции
- •Глава 8. Символы и строки
- •8.1. Символы
- •8.2. Строки символов
- •Задачи -. Символы и строки
- •Глава 9. Препроцессор
- •9.1. Директивы препроцессора
- •9.2. Макросы
- •Задачи -. Макросы
- •Глава 10. Указатели и ссылки
- •10.1. Указатели и адреса
- •10.2. Указатели и массивы
- •10.3. Адресная арифметика
- •10.4. Символьные указатели
- •10.5. Массивы указателей
- •10.6. Указатели на функции
- •10.7. Ссылки
- •10.8. Операторы new и delete
- •Задачи -. Указатели и ссылки
- •Глава 11. О файлах и командной строке
- •11.1. Знакомство с файлами
- •11.2. Командная строка
- •11.3. Перенаправление стандартного ввода и вывода на файл
- •11.4. Аргументы командной строки
- •Задачи -. Файлы и командная строка
- •Глава 12. Работа с экраном дисплея
- •12.1. Текстовый режим
- •12.2. Графический режим
- •Задачи -. Работа с экраном
- •Глава 13. Внутреннее представление чисел
- •13.1. Двоичная система счисления
- •13.2. Беззнаковые целые
- •13.3. Двоичный дополнительный код
- •13.4. Двоичный код с избытком
- •13.5. Побитовые операторы
- •13.6. Дробные числа в двоичной системе
- •13.7. Внутреннее представление плавающих типов
- •13.8. Преобразование типов
- •Задачи -. Побитовые операторы
- •Глава 14. Структуры, перечисления, объединения
- •14.1. Объявление структур
- •14.2. Структуры и функции
- •14.3. Указатели на структуры
- •14.4. Массивы структур
- •14.5. Перечисления
- •14.6. Объединения
- •14.7. Битовые поля
- •14.8. О бинарных файлах
- •Задачи -. Структуры
- •Глава 15. Классы
- •15.1. Структуры в C++. Инкапсуляция
- •15.2. Встроенные функции
- •15.3. Классы. Скрытие данных
- •15.4. Конструкторы
- •15.5. Статические члены класса
- •15.6. Друзья класса
- •15.7. Копирование объектов класса
- •15.8. Управление доступом
- •15.9. Ссылка на себя
- •15.10. Деструкторы
- •Задачи -. Работа с классами
- •Глава 16. Программы из нескольких файлов
- •16.1. Работа с проектами
- •16.2. Область действия имен
- •16.3. Заголовочные файлы
- •16.4. Пространства имен
- •Задачи -. Работа со стеком
- •Глава 17. Перегрузка операторов
- •17.1. Правила перегрузки операторов
- •Задачи -. Перегрузка операторов
- •Глава 18. Конструктор копирования и оператор присваивания
- •18.1. Проблемы при копировании
- •Задачи -. Конструктор копирования
- •Глава 19. Ввод и вывод
- •19.1. Вывод
- •19.2. Ввод
- •19.3. Ввод и вывод определяемых пользователем типов
- •19.4. Работа с файлами
- •Глава 20. Взаимоотношения классов
- •20.1. Объекты как члены класса
- •20.2. Конструкторы встроенных типов
- •20.3. Наследование
- •20.4. Виртуальные функции
- •20.5. Абстрактные классы
- •20.6. Совместимость типов
- •20.7. Множественное наследование
- •Задачи -. Наследование классов
- •Глава 21. Шаблоны, исключения
- •21.1. Шаблоны
- •21.2. Шаблоны функций
- •21.3. Классы и шаблоны
- •21.4. Обработка исключений
- •21.5. Стандартная библиотека шаблонов
- •Литература
- •Предметный указатель
243
Глава 17. Перегрузка операторов
В языке C++ имеется возможность определять функции, имена которых совпадают с именами встроенных операторов: +, -, *, / и т.д. Такие функции определяются с ключевым словом operator. Они называются функциями-операторами. При вызове таких функций достаточно указывать только знак оператора, например, (+). Как правило, в функциях-операторах выполняются действия, которые отражаются знаком используемого оператора, что делает программы более наглядными и понятными.
Синтаксис объявления и использования функций-операторов приведен в следующей программе.
Программа 53. Обыкновенные дроби
Рассмотрим класс Fraction для моделирования обыкновенных дробей:
// Файл Fraction.h |
|
#ifndef FractionH |
|
#define FractionH |
|
#include <iostream.h> |
|
class Fraction{ |
|
int num; |
// Числитель |
int denom; |
// Знаменатель |
public: |
|
Fraction(int n = 1, int m = 1){num = n; denom = m;} // Конструктор
// Методы доступа к закрытым членам |
|
|
int numerator() |
{return num;} |
|
int denominator() |
{return denom;} |
|
Fraction operator+(Fraction a); |
// Метод сложения дробей |
|
Fraction operator-() |
// Изменение знака дроби |
|
{return Fraction(-num, denom);} |
|
// Функция-друг вычитания дробей
friend Fraction operator-(Fraction a, Fraction b);
void print() // Метод для вывода дроби {cout << num << ’/’ << denom;}
};
// Независимая функция для умножения дробей Fraction operator*(Fraction a, Fraction b);
244
#endif
Оператор унарный минус не имеет аргументов потому, что ему как методу класса доступна дробь, для которой он вызывается. Вызов конструктора создает безымянный объект, значение которого возвращается из функции. Унарный минус реализован непосредственно в объявлении класса, то есть является встроенной функцией.
Метод класса сложения дробей имеет один аргумент, который является вторым слагаемым. Первым слагаемым является тот объект, для которого будет вызван этот метод.
// Файл Fraction.cpp |
|
#include ”Fraction.h” |
|
Fraction Fraction::operator+(Fraction b) |
// Сложение дробей |
{ |
|
int cd = denom * b.denom; |
// Общий знаменатель |
int ns = num * b.denom + b.num * denom; |
// Числитель суммы |
Fraction sum(ns, cd); |
// sum – сумма дробей |
return sum; |
|
} |
|
В функции сложения создается локальная переменная sum, числитель и знаменатель которой находятся по правилам сложения обыкновенных дробей. Ее значение возвращается как результат работы функции.
Функция-оператор вычитания дробей не является членом класса, поэтому она должна иметь два аргумента: уменьшаемое и вычитаемое. Эта функция является другом, поэтому ей разрешен доступ к закрытым членам класса.
Fraction operator-(Fraction a, Fraction b) |
// Вычитание дробей |
{ |
|
int cd = a.denom * b.denom; |
// Общий знаменатель |
int ns = a.num * b.denom - b.num * a.denom; // Числитель разности
Fraction subtr(ns, cd); |
// subtr – разность дробей |
return subtr; |
|
} |
|
Функция-оператор умножения дробей, не являясь членом класса, должна иметь два аргумента – сомножителя. Для доступа к закрытым членам класса она использует открытые методы класса.
Fraction operator*(Fraction a, Fraction b)
{
int np = a.numerator() * b.numerator();// Числитель произведения int dp = a.denominator() * b.denominator();// и знаменатель return Fraction(np, dp);
245
}
В данной функции не создается именованная локальная переменная для произведения. Вместо этого вызывается конструктор, которому передаются числитель и знаменатель произведения, конструктор создает безымянный объект, значение которого и возвращается из функции, после чего этот безымянный объект уничтожается.
Испытаем класс Fraction.
// Файл FractOpr.cpp #include <conio.h> #include ”Fraction.h” int main()
{
Fraction f12(1, 2), f13(1, 3), fs, fd, fp, fm; cout << ”f12 = ”; f12.print(); cout << endl; cout << ”f13 = ”; f13.print(); cout << endl;
fs = f12.operator+(f13); |
// Явный вызов функции-оператора |
cout << ”fs = ”; fs.print(); cout << endl; |
|
fd = f12 – f13; |
// Неявный вызов функции-оператора |
cout << ”fd = ”; fd.print(); cout << endl; |
|
fp = operator*(f12, f13) |
// Вычисление произведения |
cout << ”fp = ”; fp.print(); cout << endl; fm = -f12;
cout << ”fm = ”; fm.print(); cout << endl; getch();
}
Программа выводит:
f12 = 1/2
f13 = 1/3 f1s = 5/6 fd = 1/6 fp = 1/6 fm = -1/2
В функции main для сложения и умножения дробей использованы явные вызовы соответствующих функций-операторов. Их неявный вызов выглядит более понятно:
fs = f12 + f13; fp = f12 * f13;
17.1. Правила перегрузки операторов
Перегружены могут быть любые операторы языка C++ (они перечислены в табл.20), за исключением операторов:
246
. |
.* |
:: |
?: |
Нельзя менять синтаксис операторов, например, нельзя определить оператор перемножения сразу трех дробей, так как встроенный оператор умножения – бинарный:
Fraction operator*(Fraction a, Fraction b, Fraction c); // Ошибка, // 3
сомножителя
Для перегруженных операторов сохраняются приоритеты и порядок выполнения, указанные в табл.20.
Функция-оператор должна или быть членом класса, или иметь аргумент типа класса. Нельзя определить функцию-оператор, имеющую аргументы только встроенных типов, например, нельзя определить функцию-оператор сложения int и double:
int operator+(int, double); // Ошибка, аргументы только
|
// встроенных типов |
Нельзя изобрести новый знак оператора, например, |
|
Fraction operator@(); |
// Ошибка, несуществующий оператор @ |
Программа 54. Комплексные числа
В данной программе приводятся дополнительные примеры использования перегрузки операторов.
Назовем модуль для класса комплексных чисел UnComplex. В файле UnComplex.h разместим объявление класса комплексных чисел:
// Файл UnComplex.h #ifndef UnComplexH #define UnComplexH #include <math.h>
class Complex |
|
{ |
// Данные класса |
double re, im; // Действительная и мнимая части комплексного числа
public: |
// Методы класса |
Complex() |
// Конструктор по умолчанию |
{ re = 0; im = 0; } |
|
Complex(double x) // Конструктор с одним аргументом, формирование { re = x; im = 0; } // комплексного числа по одному вещественному
Complex(double x, double y) // Конструктор с двумя аргументами { re = x; im = y; }
double Arg(); // Аргумент комплексного числа
|
247 |
double Abs(); |
// Модуль комплексного числа |
Complex& operator+=(Complex z) |
// Сложение с присваиванием |
{ |
|
re += z.re; im += z.im; return *this; |
|
} |
|
Complex& operator-=(Complex z) |
// Вычитание с присваиванием |
{ |
|
re -= z.re; im -= z.im; return *this; |
|
} |
|
Complex operator*(Complex); // Перемножение комплексных чисел Complex operator*(double);// Умножение комплексного на вещественное
Complex operator/(Complex); |
// Деление комплексных чисел |
|
Complex operator-(); |
// Унарный минус |
|
void Roots(int m, Complex* rts); |
// Извлечение корней степени m |
|
void Print(); |
// Вывод комплексного числа |
|
}; |
|
|
// Объявление функций, не являющихся членами класса Complex |
||
Complex operator+(Complex z, Complex t); |
// Сложение |
|
Complex operator-(Complex z, Complex t); |
// Вычитание |
|
Complex operator*(double a, Complex z); |
// Умножение |
Complex Pow(Complex z, int m); //Возведение комплексного в степень m // Получение комплексного числа по его модулю и аргументу
Complex Polar(double mod, double arg); #endif
В классе Complex предусмотрены три конструктора, чтобы можно было создавать комплексные числа любым из следующих способов:
Complex z1(1, 2); |
// z1.re = 1, z1.im = 2 |
Complex z2(1); |
// z2.re = 1, z2.im = 0 |
Complex z3; |
// z3.re = 0, z3.im = 0 |
Наличие конструктора позволяет инициализировать комплексные числа при их определении:
Complex z4 = 13; |
// z4.re = 13, z4.im = 0 |
Здесь сначала создается безымянное комплексное число Complex(13) с использованием конструктора, которое затем используется для инициализации переменной z4.
Реализацию функций для работы с комплексными числами разместим в файле UnComplex.cpp.
// Файл UnComplex.cpp #include "UnComplex.h"
248
#include <iostream.h> |
|
// Функции-члены класса Complex |
|
double Complex::Arg() |
// Аргумент комплексного числа |
{ return atan2(im, re); } |
|
Функция математической библиотеки (заголовочный файл math.h) double atan2(double y, double x);
возвращает значение arctg(y/x). Она работает корректно, даже когда угол близок к π/2 или -π/2 (x близко к 0).
double Complex :: Abs() |
// Модуль комплексного числа |
{ return sqrt(re * re + im * im); } |
|
Complex Complex :: operator*(Complex z)// Умножение двух комплексных
{
Complex t(re * z.re - im * z.im, re * z.im + im * z.re); return t;
}
Так как функция умножения является членом класса, ей доступно комплексное число, для которого она вызывается. Это число, представленное re и im, рассматривается как первый сомножитель. Второй сомножитель z передается в функцию как аргумент.
В следующей функции число с плавающей точкой a преобразуется в комплексное число конструктором, а затем используется функция умножения двух комплексных:
Complex Complex :: operator*(double a) |
// Умножение на double |
{ |
|
return *this * Complex(a); |
|
} |
|
В функции деления реализованы обычные формулы деления комплексных чисел:
Complex Complex :: operator/(Complex z) |
// Деление |
{ |
|
double r = (re * z.re +im * z.im) / (z.re * z.re + z.im * z.im); double i = (-re * z.im + im * z.re) / (z.re * z.re + z.im * z.im); return Complex(r, i);
}
Функция получения противоположного по знаку комплексного числа реализована как функция-член без аргументов:
Complex Complex :: operator-() |
// Унарный минус, |
{ |
// получение комплексного числа |
249
return Complex(-re, -im); |
// с противоположным знаком |
}
Напомним, что комплексное число можно представить в алгебраической, показательной или тригонометрической форме:
z = x + iy = ρeiϕ = ρ(cosϕ + i sinϕ ) .
Здесь |
|
x – действительная |
часть, |
y – мнимая часть, ρ – модуль |
||||||||||||
комплексного числа, |
φ – аргумент. Корень степени |
m находится по |
||||||||||||||
формуле: |
|
|
|
|
|
|
|
|
|
|
|
|
||||
m |
|
|
|
|
i |
ϕ +2kπ |
|
|
æ |
|
ϕ + 2kπ |
|
ϕ + 2kπ |
ö |
|
|
z = m ρe |
|
|
|
+ i sin |
|
|
||||||||||
|
|
m = m ρ çcos |
m |
m |
÷, k = 0,m -1 |
|||||||||||
|
|
|
|
|
|
|
è |
|
|
ø |
|
|
Функция Roots находит все корни m-й степени из комплексного числа и заносит их в массив rts
void Complex::Roots(int m, Complex rts[]) |
// Извлечение корней |
|
{ |
|
// степени m |
double angle = Arg(); |
|
// Аргумент |
double mod = Abs(); |
|
// Модуль |
mod = pow(mod, 1.0 / m); |
|
// Корень m-й степени из модуля |
for(int k = 0; k < m; k++) |
|
// Получение m корней |
rts[k] = Polar(mod, (angle + 2 * k * M_PI) / m); |
||
} |
|
|
void Complex::Print() |
// Функция вывода комплексного числа |
{ cout << "(" << re << ", " << im << ")"; }
Функции, не являющиеся членами класса, используют функциичлены для доступа к закрытому представлению комплексного числа.
Complex operator+(Complex z, Complex t) |
// Сложение |
{ return z += t; } |
|
Для реализации сложения двух комплексных использован перегруженный оператор +=, аналогично, для реализации вычитания используется оператор -=.
Complex operator-(Complex z, Complex t) |
// Вычитание |
|
{ return z -= t; } |
|
|
//Умножение вещественного на комплексное |
|
|
Complex operator*(double a, Complex z) |
|
|
{ |
|
|
return z * a; |
// Умножение комплексного на double |
|
} |
|
|
250
В данной функции используется определенный ранее оператор умножения комплексного числа на действителное.
Следующая функция обеспечивает получение комплексного числа по его модулю и аргументу:
Complex Polar(double mod, double arg)
{ return Complex(mod * cos(arg), mod * sin(arg)); }
Целую степень комплексного числа можно найти по формуле:
zm = ( x + iy)m = ρ meiϕm . Функция Pow реализует эту формулу.
Complex Pow(Complex z, int m) |
// Возведение комплексного числа |
{ |
// в степень m |
double angle = z.Arg(); |
// Аргумент |
double mod = z.Abs(); |
// Модуль |
angle *= m; |
// Увеличение аргумента в m раз |
double mp = 1.0; |
// m-я степень модуля |
for(int k = 0; k < m; k++) |
// находится с помощью |
mp *= mod; |
// произведения |
return Polar(mp, angle); |
|
} |
|
В качестве примера использования класса комплексных чисел решим приведенное кубическое уравнение:
z3 + pz + q = 0 .
Его корни можно выразить формулой Кардано:
|
q |
|
|
q2 |
p3 |
|
q |
|
q2 |
p3 |
|
|||
z = 3 − |
|
+ |
|
|
+ |
|
|
+ 3 − |
2 |
− |
4 + |
|
. |
|
2 |
4 |
27 |
27 |
Решение уравнения дают такие комбинации кубических корней, произведение которых равно –p/3.
Функцию main поместим в файле UnMainComplex.cpp.
#include ”UnComplex.h” #include <iostream.h> #include <conio.h> #include "Rus.h"
int main() |
|
{ |
|
const double eps = 0.1E-4; |
// Малое число (точность) |
double x, y;
cout << Rus("Решаем приведенное кубическое уравнение \n”
251
” z^3 + p*z + q = 0\n");
cout << Rus("Введите действительную и мнимую части p: "); cin >> x >> y;
Complex p(x, y); |
// Создание коэффициента p |
cout << Rus("Введите действительную и мнимую части q: "); |
|
cin >> x >> y; |
|
Complex q(x, y); |
// Создание коэффициента q |
Complex inner[2]; |
// Массив для квадратных корней |
// Массивы для первого и второго кубического корня |
|
Complex root3_1[3], root3_2[3]; |
|
Complex D; |
// Дискриминант кубического уравнения |
D = q * q / 4 + p * p * p / 27; |
|
D.Roots(2, inner); |
// Квадратный корень из дискриминанта |
//Первое выражение под куб. корнем Complex D1 = (-0.5) * q + inner[0];
//Второе выражение под куб. корнем Complex D2 = (-0.5) * q - inner[0];
D1.Roots(3, root3_1); |
// Извлечение первого кубического корня |
D2.Roots(3, root3_2); |
// Извлечение второго кубического корня |
Complex p3 = -p / 3; |
// Критерий для отбора корней |
Complex eq; |
// Левая часть уравнения |
Complex z; |
// Переменная для корня |
Complex prod; |
// Произведение кубических корней |
// Перебор возможных комбинаций кубических корней |
|
for(int i = 0; i < 3; i++) |
// Перебор значений первого кубич. корня |
for(int j = 0; j < 3; j++){// Перебор значений второго кубич. корня prod = root3_1[i] * root3_2[j]; //Произведение кубических корней double err = (prod - p3).Abs(); //Отклонение произведения от -p/3
if( err < eps){ |
//Если отклонение мало, |
z = root3_1[i] + root3_2[j]; |
//это корень |
cout << Rus("\nКорень z = |
"); z.Print(); |
eq = Pow(z, 3) + p * z + q; |
//Подстановка корня в уравнение |
cout << Rus("\nПроверка: z^3 + p*z + q = "); eq.Print();
}
}
getch(); return 0;
}
В качестве примера решим уравнение z3 − 2z +1 = 0 ,
имеющее корни:
z1 = 1, z2 = −(1+ 5)2 = -1.61803, z3 = (5 −1)2 = 0.61803.