Главная страница

конспект. Конспект. Курс лекций по дисциплине процедурное программирование


Скачать 1.95 Mb.
НазваниеКурс лекций по дисциплине процедурное программирование
Анкорконспект
Дата27.12.2021
Размер1.95 Mb.
Формат файлаpdf
Имя файлаКонспект.pdf
ТипКурс лекций
#319290
страница5 из 6
1   2   3   4   5   6
Тема 14. Перегрузка функций в C++
Перегрузка функций в C++ используется, когда нужно сделать одно и то же действие с разными типами данных. Для примера, создадим простую функцию max
, которая будет определять максимальное из двух целых чисел.
/* Функция max для целых чисел */
int max(int num1, int num2)
{
if (num1 > num2)
return num1;
return num2;
}
В эту функцию мы можем передавать только целочисленные параметры. Для того, чтобы сделать аналог этой функции для чисел с плавающей запятой, выполним перегрузку этой функции:
/* Функция max для чисел с плавающей запятой */
double max(double num1, double num2)
{
if (num1 > num2)
return num1;
return num2;
}
Теперь, когда мы будет вызывать функцию max с целыми параметрами, то вызовется первая функция. А если с дробными — то вторая. Например:
// Здесь будет использоваться первый вариант функции max
int imax = max(1, 10);
// А здесь - второй
double dmax = max(1.0, 20.0);
Задание: попробуйте написать функцию сортировки массива пузырьком для целочисленных массивов. А затем перегрузить эту же функцию для массивов типа double
Исходный код примера из урока.
Следующий урок — перегрузка методов класса в C++.
Тема 15. Перегрузка методов класса в C++
Методы класса можно перегружать также, как и обычные функции. Особенно это удобно, когда нужно сделать несколько конструкторов, которые будут принимать разные параметры.

Например, попробуем создать основу класса decimal
, который реализует длинную арифметику для чисел произвольной точности. В таких случаях, обычно хранят число внутри строки, а логика математических операций реализуется через написание соответствующих операторов класса.
Сделаем так, чтобы в конструктор этого класса можно было передавать и строку и число типа double
// Передача в конструктор строки decimal num1("10000000.999999");
// Передача числа decimal num2(10000.0);
Для того, чтобы класс поддерживал такую универсальность, мы сделаем два разных конструктора для строки и числа:
/**
* Представим, что этот класс реализует длинную арифметику для чисел любой
* точности
*/
class decimal
{
public:
/*
* Конструктор, принимающий в качестве аргумента строку, содержащую число
*/ decimal(string number)
{ clog << "First constructor called\n";
}
/**
* Конструктор принимат число типа double
*/ decimal(double number)
{ clog << "Second constructor called\n";
}
private: string number;
};
При передаче строки будет вызван первый конструктор, а при передаче числа — второй.
Полный текст программы:
#include
#include
using namespace std;
/**
* Представим, что этот класс реализует длинную арифметику для чисел любой точности
*/
class decimal
{
public:

/*
* Конструктор, принимающий в качестве аргумента строку,
* содержащее число
*/ decimal(string number)
{ clog << "First constructor called\n";
this->number = number;
}
/**
* Конструктор принимает число типа double
*/ decimal(double number)
{
/**
* преобразуем double в строку с максимально возможной точностью
* и записываем полученное значение в this->number
*/ clog << "Second constructor called\n";
}
private: string number;
};
int main()
{
// Будет вызван первый конструктор decimal num1("10000000.999999");
// Будет вызван второй конструктор decimal num2(10000.0); cin.get();
return 0;
}
Конечно, наш класс пока ничего не делает, потому что реализация длинной арифметики выходит за рамки данной статьи. Но на этом примере можно понять, когда может быть полезно использовать перегрузку методов класса.
Задание: попробуйте написать класс student
, в конструктор которого можно будет передавать либо его имя и фамилию, либо имя и год рождения. При передаче года рождения, должен считаться примерный возраст студента. Пример использования класса: student stud1("Иван", "Иванов"); student stud2("Иван", 1990);
Тема 16. Определение и перегрузка операторов
класса в C++
В C++ можно определять пользовательские операторы для собственных типов данных.
Оператор определяется, как обычная функция-член класса, только после определения возвращаемого типа ставится ключевое слово operator
Пример определения оператора сложения:

int operator+ (int value) { return number + value; }
Оператор может быть унарным или бинарным. Унарный оператор не принимает никаких аргументов. Например, оператор отрицания — «!». Бинарный оператор принимает дополнительный параметр. Например, в случае со сложением, принимается второе слагаемое.
Чтобы прояснить картину, попробуем написать класс simple_fraction
, который будет описывать простую дробь с целыми числителем и знаменателем. И определим операторы сложения, вычитания, умножения и деления для этого класса.
/*
* Класс, описывающий простую дробь
*/
class simple_fraction
{
public: simple_fraction(int numerator, int denominator)
{
if (denominator == 0) // Ошибка деления на ноль
throw std::runtime_error("zero division error");
this->numerator = numerator;
this->denominator = denominator;
}
// Определение основных математических операций для простой дроби
double operator+ (int val) { return number() + val; } // Сложение
double operator- (int val) { return number() - val; } // Вычитание
double operator* (int val) { return number() * val; } // Умножение
double operator/ (int val) // Деление
{
if (val == 0) {
throw std::runtime_error("zero division error");
}
return number() / val;
}
// Получение значения дроби в виде обычного double-числа
double number() { return numerator / (double) denominator; }
private:
int numerator; // Числитель
int denominator; // Знаменатель
};
Для операции деления, мы также сделали проверку деления на ноль.
Пример использования класса simple_fraction
:
// Простая дробь 2/3 simple_fraction fr(2, 3);
double sum = fr + 10; // сумма
double diff = fr - 10; // разность
double factor = fr * 10; // произведение
double div = fr / 10; // частное
Операторы можно перегружать так же, как и обычные функции-члены класса. Например, можно перегрузить оператор сложения для двух простых дробей, который будет
возвращать новую простую дробь. Тогда, нам придется привести дроби к общему знаменателю и вернуть другую простую дробь.
Задание: усовершенствуйте класс simple_fraction
. Перегрузите операторы сложения, вычитания, умножения и деления так, чтобы можно было производить операции над двумя простыми дробями и получать новую простую дробь. Реализуйте приведение двух дробей к общему знаменателю.
Пример использования будущего класса: simple_fraction fr1(2, 3); simple_fraction fr2(3, 4);
// 2/3 + 3/4 — это 17/12 simple_fraction sum = fr1 + fr2;
Тема 15. Раздельная компиляция программ на
C++
Когда мы пишем программу на C/C++ в одном файле, проблем обычно не возникает. Они ждут того момента, когда исходный текст необходимо разбить на несколько файлов. В этой статье я постараюсь рассказать, как это сделать правильно.
Термины
Пара слов о терминах. Ниже даны определения терминов так, как они используются в данной статье. В некоторых случаях эти определения имеют более узкий смысл, чем общепринятые. Это сделано намеренно, дабы не утонуть в деталях и лишних уточнениях.
Исходный код — программа, написанная на языке программирования, в текстовом формате. А также текстовый файл, содержащий исходный код.
Компилятор — программа, выполняющая компиляцию (неожиданно! не правда ли?). На данный момент среди начинающих наиболее популярными компиляторами C/C++ являются GNU g++ (и его порты под различные ОС) и MS Visual Studio C++ различных версий. Подробнее см. в Википедии статьи: Компиляторы, Компиляторы C++.
Компиляция — преобразование исходного кода в объектный модуль.
Объектный модуль — двоичный файл, который содержит в себе особым образом подготовленный исполняемый код, который может быть объединён с другими объектными файлами при помощи редактора связей (компоновщика) для получения готового исполняемого модуля, либо библиотеки. (подробности)
Компоновщик (редактор связей, линкер, сборщик) — это программа, которая производит компоновку («линковку», «сборку»): принимает на вход один или несколько объектных модулей и собирает по ним исполнимый модуль. (подробности)
Исполняемый модуль (исполняемый файл) — файл, который может быть запущен на исполнение процессором под управлением операционной системы. (подробности)
Препроцессор — программа для обработки текста. Может существовать как отдельная программа, так и быть интегрированной в компилятор. В любом случае, входные и
выходные данные для препроцессора имеют текстовый формат. Препроцессор преобразует текст в соответствии с директивами препроцессора. Если текст не содержит директив препроцессора, то текст остаётся без изменений. Подробнее см. в
Википедии: Препроцессор и Препроцессор Си.
IDE (англ. Integrated Development Environment) — интегрированная среда разработки.
Программа (или комплекс программ), предназначенных для упрощения написания исходного кода, отладки, управления проектом, установки параметров компилятора, линкера, отладчика. Важно не путать IDE и компилятор. Как правило, компилятор самодостаточен. В состав IDE компилятор может не входить. С другой стороны с некоторыми IDE могут быть использованы различные компиляторы. (подробности)
Объявление — описание некой сущности: сигнатура функции, определение типа, описание внешней переменной, шаблон и т.п. Объявление уведомляет компилятор о её существовании и свойствах.
Определение — реализация некой сущности: переменная, функция, метод класса и т.п.
При обработке определения компилятор генерирует информацию для объектного модуля: исполняемый код, резервирование памяти под переменную и т.д.
От исходного кода к исполняемому модулю
Создание исполняемого файла издавна производилось в три этапа: (1) обработка исходного кода препроцессором, (2) компиляция в объектный код и (3) компоновка объектных модулей, включая модули из объектных библиотек, в исполняемый файл. Это классическая схема для компилируемых языков. (Сейчас уже используются и другие схемы.)
Часто компиляцией программы называют весь процесс преобразования исходного кода в исполняемы модуль. Что неправильно. Обратите внимание, что в IDE этот процесс называется построение (build) проекта.
IDE обычно скрывают три отдельных этапа создания исполняемого модуля. Они проявляются только в тех случаях, когда на этапе препроцессинга или компоновки обнаруживаются ошибки.
Итак, допустим, у нас есть программа на C++ «Hello, World!»:
#include
int main() { std::cout << "Hello, World!\n";
}
Сначала исходный код обрабатывается препроцессором. Препроцессор находит директиву
#include
, ищет файл iostream и заменяет директиву текстом из этого файла, попутно обрабатывая все директивы препроцессора во включаемом тексте.
Файл, указанный в директиве
#include
, в данном случае является заголовочным
файлом (или «хеадером», «хидером», «заголовком»). Это обычный текстовый файл, содержащий объявления (объявления типов, прототипы функций, шаблоны, директивы препроцессора и т.п.). После текстуального включения заголовочного файла в текст программы (или модуля) становится возможным использование в тексте программы всего того, что описано в этом заголовочном файле.

Затем результат работы препроцессора передаётся компилятору. Компилятор производит весь положенный комплекс работ: от синтаксического разбора и поиска ошибок до создания объектного файла (понятно, что если имеются синтаксические ошибки, то объектный файл не создаётся). В объектном файле обычно имеется таблица внешних
ссылок — некая таблица, в которой, в частности, перечислены имена подпрограмм, которые используются в объектном модуле, но код которых отсутствует в данном объектном модуле. Эти подпрограммы внешние по отношению к модулю.
Исходный код, который может быть откомпилирован, называется единицей компиляции.
Наша программа содержит одну единицу компиляции.
Что бы получить нормальный исполняемый модуль, необходимо «разрешить» внешние ссылки. Т.е. добавить в исполняемый модуль код отсутствующих подпрограмм и настроить соответствующим образом все ссылки на этот код. Этим занимается компоновщик. Он анализирует таблицу внешних ссылок объектного модуля, ищет в объектных библиотеках недостающие модули, копирует их в исполняемый модуль и настраивает ссылки. После этого исполняемый модуль готов.
Библиотека (объектная библиотека) — это набор откомпилированных подпрограмм, собранных в единый файл определённой структуры. Подключение библиотеки происходит на этапе компоновки исполняемого файла из объектных файлов (т.е. из тех файлов, которые получаются в результате компиляции исходного текста программы).
Необходимые объектные библиотеки входят в комплект поставки компилятора. В комплект поставки библиотек (любых) входит набор заголовочных файлов, которые содержат объявления, необходимые компилятору.
Если исходный код программы разделён на несколько файлов, то процесс компиляции и сборки происходит аналогично. Сначала все единицы компиляции по отдельности компилируются, а затем компоновщик собирает полученные объектные модули (с подключением библиотек) в исполняемый файл. Собственно, этот процесс и называется раздельной компиляцией.
Разделение текста программы на модули
Разделение исходного текста программы на несколько файлов становится необходимым по многим причинам:
1. С большим текстом просто неудобно работать.
2. Разделение программы на отдельные модули, которые решают конкретные подзадачи.
3. Разделение программы на отдельные модули, с целью повторного использования этих модулей в других программах.
4. Разделение интерфейса и реализации.
Я намеренно использовал слово «модуль», поскольку модулем может быть как класс, так и набор функций — вопрос используемой технологии программирования.
Как только мы решаем разделить исходный текст программы на несколько файлов, возникают две проблемы:
1. Необходимо от простой компиляции программы перейти к раздельной. Для этого надо внести соответствующие изменения либо в последовательность действий при построении приложения вручную, либо внести изменения в командные или make-файлы, автоматизирующие процесс построения, либо внести изменения в проект IDE.

2. Необходимо решить каким образом разбить текст программы на отдельные файлы.
Первая проблема — чисто техническая. Она решается чтением руководств по компилятору и/или линкеру, утилите make или IDE. В самом худшем случае просто придётся проштудировать все эти руководства. Поэтому на решении этой проблемы мы останавливаться не будем.
Вторая проблема — требует гораздо более творческого подхода. Хотя и здесь существуют определённые рекомендации, несоблюдение которых приводит либо к невозможности собрать проект, либо к трудностям в дальнейшем развитии проекта.
Во-первых, нужно определить какие части программы выделить в отдельные модули. Что бы это получилось просто и естественно, программа должна быть правильно спроектирована. Как правильно спроектировать программу? — на эту тему написано много больших и правильных книг. Обязательно поищите и почитайте книги по методологии программирования — это очень полезно. А в качестве краткой рекомендации можно сказать: вся программа должна состоять из слабо связанных фрагментов. Тогда каждый такой фрагмент может быть естественным образом преобразован в отдельный модуль (единицу компиляции). Обратите внимание, что под «фрагментом» подразумевается не просто произвольный кусок кода, а функция, или группа логически связанных функций, или класс, или несколько тесно взаимодействующих классов.
Во-вторых, нужно определить интерфейсы для модулей. Здесь есть вполне чёткие правила.
Интерфейс и реализация
Когда часть программы выделяется в модуль (единицу компиляции), остальной части программы (а если быть точным, то компилятору, который будет обрабатывать остальную часть программы) надо каким-то образом объяснить что имеется в этом модуле. Для этого служат заголовочные файлы.
Таким образом, модуль состоит из двух файлов: заголовочного (интерфейс) и файла реализации.
Заголовочный файл, как правило, имеет расширение .h или .hpp, а файл реализации
.cpp для программ на C++ и .c, для программ на языке C. (Хотя в STL включаемые файлы вообще без расширений, но, по сути, они являются заголовочными файлами.)
Заголовочный файл должен содержать все объявления, которые должны быть видны снаружи. Объявления, которые не должны быть видны снаружи, делаются в файле реализации.
Что может быть в заголовочном файле
1   2   3   4   5   6


написать администратору сайта