Skip to content

ООП Лекция 03. Структура программы.

Vladislav Mansurov edited this page Apr 20, 2022 · 3 revisions

Что такое программа на Си

Программа на Си - набор файлов, включающий директивы препроцессора, объявления переменных. Один из файлов должен содержать функцию с именем main (но не обязательно, программа может начинаться с любой другой функции). Иными словами, программа на языке Си является набор подпрограмм (функций), внешних по отношению друг к другу (по стандарту).

Любая функция может вызвать любую другую, в том числе вызвать саму себя.

Исключением является функция main() - в С++ функция main() саму себя вызвать не может. При выполнении консольного приложения, написанного на языке Си, операционная система компьютера передаёт управление функции с именем main(). Функцию main() нельзя вызывать из других функций программы, она является управляющей.

Компиляция программы

Объявления необходимы компилятору. Объявлять можно один раз, определять - сколько угодно. Компилятор подставляет константы.

В С и С++ каждый файл компилируется раздельно, либо с помощью makefile.

Получение исполняемого файла *.exe:

image

  1. Препроцессирование - удаление комментариев, замена #define, подключение заголовочных файлов *.h (путем копирования содержимого). Для избежания повторного копирования используется #pragma once или #ifndef G_H.

  2. Компиляция - перевод написанного на языке C++ кода в код ассемблера.

  3. Ассемблирование - перевод из кода ассемблера в машинный код. Получаем объектный файл с расширением *.obj.

  4. Компоновка/линковка - объединение объектных файлов в один исполняемый *.exe.

Пример многофайловой программы:

#pragma once исключает множественное включение файлов.

Заголовочный файл

Заголовочные файлы - описание типов, констант, библиотеки, функций, логически вынесенных в другой исходный файл.

Что можно, а что нельзя определять в заголовочном файле:

  • Определять переменные нельзя. Мы конечно можем это сделать, и на этапе компиляции проблем не будет, но сборщик покажет множественное определение - ошибочка.
  • То же самое касается функций. В заголовочном файле описываются только прототипы функций.
  • К языковой константе компилятор относится как к макросу, поэтому в заголовочном можно определять константу.
  • Аналогично с типами - типы тоже можно определять в заголовочном файле.

То есть все константы и типы выносим в заголовочный, а так же прототипы функций.

С такими константами компилятор вычисляет ее, и подставляет в код вместо вызова.

const int a = 100; \\ Константа времени компиляции
------------------
int i;
...
const int a = i; \\ Константа времени выполнения и является полноценным данным

Определяем константы в том случае, если она вычислима на этапе компиляции - Константа времени компиляции, а те константы которые не могут быть вычислены на этапе компиляции, а вычисляются во время выполнения, то это Константа времени выполнения нельзя определять в заголовочном файле

В языке С структурное соответствие типов, то есть typedef это определение еще одного имени, один и тот же тип может иметь много имен, проверяется не имя типа, а его структура.

В языки С++ именное соответствие типов, две одинаковые структуры, имеющие разные являются разными типами.

Подключать g.h на уровне f.h файлов нельзя, приводит к лавинное перекомпиляции, на время разработки мы их комментируем. В случае релиз версии тогда, все g.h включения идут в f.h файл, а в f.cpp .h комментируем.

image

f.h и f.cpp рассматривается как модуль и многие среды создают такие пары.

Версии программы и контроль включения

  1. Debug version. Заголовочные файлы подключаем напрямую в .срp, в нужные .h ставим пометки, что подключили. При смене заголовочного перекомпилируются только те файлы, в которые он включался, а значит быстрее идет сборка.

  2. Realese version. Подключаем заголовочные файлы в другие заголовочные файлы. При этом, при изменении заголовочного файла, пересобираются не только заинтересованные .cpp, но и все те, в которые включен тот заголовок, куда подключали именно этот заголовок. Долга сборка, но понятные взаимосвязи.

image

Супер дополнение! Пунктирные линии - один из вариантов, либо debug, либо release. fg.h - заголовочный файл, в котором будет только определение констант.

Контролировать включение с помощью #ifndef - уникальное имя файла. Проверка происходит на этапе подключения.

#ifndef F_H
#define F_H
...
#endif

#pragma once - дает гарантию того, что файл будет включен один раз (зависит от версии компилятора). Во многих средах отсутствует.

Функции с переменным числом параметров

Есть некоторые расширения языки С до С++, которые нам необходимы. Еще что качается языка С сильно активно использовать в C++.

В Си возможны функции с переменным числом параметров. Параметры передаются в стек, начиная с конца. В голове стека - первый параметр.

Если количество или типы некоторых аргументов неизвестны, надо заменить их многоточием. При этом:

  • хотя бы один аргумент должен быть известен (иначе откуда функция узнает количество и типы аргументов?)
  • известные аргументы должны быть в начале списка
  • ИЛИ один из обязательных передаваемых параметров информирует о количестве необязательных параметров, ИЛИ закреплено дно стека (например известно, что последним переданным параметр будет 0)
//  Примеры функций суммирования
int sum(int num, ...);       // явным параметром передается
                            //   количество суммируемых целых чисел
int sum(int a, int b, ...); // суммирование прекращается,
                           // если очередное слагаемое равно 0

В С++ будем реализовывать ограничение дно стека, если работаем указателем, то указатель на NULL иначе default значение

Ссылки

Структуру по значению передавать плохо, так как происходит копирование в стек, а это занимает время. Лучше по ссылке или указателю. Но у указателей есть проблема - контроль (сложно проверить корректность, только на NULL).

Ссылка (alias) - ещё одно имя того же самого данного. Ссылка - не тип данных!

// Пример, демонстрирующий принцип работы ссылки
int i;
int& ai = i;
ai = 2;       // аналогично, что i = 2;

Ссылки используются для передачи параметра в функцию. На низком уровне ссылки хоть ничем не отличается от указателя, но идет контроль!

О возврате. Нельзя возвращать ссылку на локальную переменную. Возврат по ссылке нужен для формирования левого выражения = , чтобы присвоить переменной нужное значение.

\\ передача по ссылке
void swap(double &d1, doueble &d2) \\ под капотом это тот же указатель, но с контролем
{
   doueble temp = d1;
   d1 = d2;
   d2 = temp;
}
...
swap(arr[i], arr[j]);
...

\\ возврат по ссылке

\\ такого нельзя
double &maxElem(double *arr, int count)
{
   double amax = arr;
   for(double *p = arr + 1; p < count; p++)
      if(amax < *p)
        amax = *p
   return amax \\ возврат ссылки на локальную переменную
}
\\ о звездочках или указателей забыть
double &maxElem(double *arr, int count)
{
   double *pmax = arr;
   for(double *p = arr + 1; p < count; p++)
      if(*pmax < *p)
        *p = max
   return *pmax \\ возврат ссылку на этот элемент, формировать левые выражения, нахождения по отношению оператора =
}
...
maxElem(arr, n) = 0; \\ обнулил первый максимальный элемент
...

Перегрузка функций

В С++ можно создавать разные функции с одинаковыми именами, у которых параметры разного типа или их разное количество - использовать перегрузку функций.

Теряется такой механизм неявное приведение типа - а это ХОРОШО

В С++ о неявном будем говорит, так как создатель добавил возможность задавать свои механизмы неявного типа - но это ПЛОХО, пишем код для врагов.

На перегрузку функции влияет:

  • Разное количество принимаемых параметров.
  • Разный тип переменных.
  • const значение функции/метода. Если объект константный, то будет вызываться const метод, иначе не const.

Примечание о том, что значит модификатор const у метода. Константный метод - это метод, который гарантирует, что не будет изменять объект или вызывать неконстантные методы класса (поскольку они могут изменить объект). Соответственно, для константных объектов нельзя вызывать неконстантные методы.

Тип возвращаемой переменной не имеет значение.

//  Пример функции возведения в квадрат
int sqr(int x);  // для целых
double sqr(double x); // для дробных
int sqr(const int x) const; // для const  целых

Параметры по умолчанию

В Си++ один или несколько аргументов функции могут задаваться по умолчанию. Для каждого параметра значение по умолчанию можно указать не более одного раза, но каждое последующее объявление функции, а также определение функции может назначать параметрам значения по умолчанию. Параметры по умолчанию должны быть последними в списке параметров.

// Пример: функция сортировки по возрастанию массива вещественных чисел
void sort(double *arr, int count, int key = 0);

// key показывает направление сортировки (0 - по возрастанию, 1 - по убыванию). 
// По умолчанию сортировка будет по возрастанию 
// (key = 0, если в функцию передаются только два параметра - указатель на массив
// и количество элементов в массиве). 
Clone this wiki locally