# Глава 10.1. Объявления и определения
Проект на C++ может быть сколь угодно сложным и содержащим произвольную иерархию директорий. Он почти всегда включает специфичные для системы сборки скрипты, конфиги и другие вспомогательные файлы. Но нас интересуют только файлы с кодом на C++. Они содержат объявления и определения. Мы разберем, что это такое, а потом обсудим, как организуются проекты без модулей и с модулями.
## Видимость объявлений для компилятора
Функции, классы, переменные и другие сущности в программе становятся видны компилятору после точки своего объявления. Если использовать их до места, где они объявлены, то компилятор выдаст ошибку.
Убедимся в этом на примере, в котором есть [взаимная рекурсия.](https://ru.wikipedia.org/wiki/%D0%92%D0%B7%D0%B0%D0%B8%D0%BC%D0%BD%D0%B0%D1%8F_%D1%80%D0%B5%D0%BA%D1%83%D1%80%D1%81%D0%B8%D1%8F) Пример реализует [гипотезу Коллатца:](https://ru.wikipedia.org/wiki/%D0%93%D0%B8%D0%BF%D0%BE%D1%82%D0%B5%D0%B7%D0%B0_%D0%9A%D0%BE%D0%BB%D0%BB%D0%B0%D1%82%D1%86%D0%B0) какое бы натуральное число `n` мы ни взяли, рано или поздно мы получим единицу, если будем совершать действия:
- Если `n` чётное, делить его на 2.
- Если `n` нечётное, умножать на 3 и прибавлять 1.
```cpp {.example_for_playground}
import std;
int collatz_multiply(int x)
{
return (x % 2 > 0) ? 3 * x + 1 : collatz_divide(x);
}
int collatz_divide(int x)
{
return (x % 2 == 0) ? x / 2 : collatz_multiply(x);
}
int main()
{
int n = 17;
std::println("Checking Collatz conjecture for {}", n);
while (n > 1)
{
n = collatz_multiply(n);
std::print("{} ", n);
}
}
```
```
main.cpp:5:36: error: use of undeclared identifier 'collatz_divide'
5 | return x % 2 > 0 ? 3 * x + 1 : collatz_divide(x);
| ^
```
Компилятор не нашёл функцию, вызванную до своего объявления. Поправить это переносом функции выше места её вызова не получится. Ведь `collatz_multiply()` вызывает `collatz_divide()` и наоборот! Кроме того, код может быть достаточно сложным, чтобы подходящего места для размещения всех функций _до_ их вызовов просто бы не нашлось. Как же решить эту проблему?
## Что такое объявление и определение {#block-declaration-definition}
Все функции, классы и другие сущности, с которыми вы работали в прошлых главах, это не просто объявления, а заодно и _определения._ Единственное исключение — объявление функции `read_file()` в файле `main.cpp` [предыдущей практики](/courses/cpp/practice/cpp_brainfuck_interpreter/) «Интерпретатор Brainfuck».
[Определение](https://en.cppreference.com/w/cpp/language/definition.html) (definition) — это _объявление_ вместе с информацией, которой достаточно для использования сущности в коде. Определение функции включает и её тело, то есть реализацию. Любое определение также является и объявлением. {#block-definitions}
Как же выглядят объявления, которые не считаются определениями?
[Объявление](https://en.cppreference.com/w/cpp/language/declarations.html) (declaration) делает сущность видимой для компилятора. Объявление функции состоит из возвращаемого типа, имени функции и параметров. После объявления ставится символ `;`. {#block-declarations}
```cpp
int collatz_multiply(int x);
```
Чтобы исправить пример кода с гипотезой Коллатца, разместим объявления функций до их использования:
```cpp {.example_for_playground}
import std;
int collatz_multiply(int x); // объявление
int collatz_divide(int x); // объявление
int main()
{
int n = 17;
std::println("Checking Collatz conjecture for {}", n);
while (n > 1)
{
n = collatz_multiply(n);
std::print("{} ", n);
}
}
int collatz_multiply(int x) // определение
{
return (x % 2 > 0) ? 3 * x + 1 : collatz_divide(x);
}
int collatz_divide(int x) // определение
{
return (x % 2 == 0) ? x / 2 : collatz_multiply(x);
}
```
```
Checking Collatz conjecture for 17
52 26 13 40 20 10 5 16 8 4 2 1
```
## Правило одного определения
Объявлений одной и той же сущности в программе может быть сколь угодно много. Но определение должно быть единственным. Так гласит важный пункт из [правила одного определения](https://en.cppreference.com/w/cpp/language/definition) (ODR, one definition rule). Нарушение ODR приведёт к ошибке компиляции. {#block-odr}
Что выведет этот код? {.task_text}
В случае ошибки напишите `err`. {.task_text}
```cpp {.example_for_playground}
import std;
int main()
{
std::println("{}", to_miles(0.0));
}
double to_miles(double km)
{
return km * 0.62;
}
```
```consoleoutput {.task_source #cpp_chapter_0101_task_0010}
```
Функция `to_miles()` объявлена после её вызова. {.task_hint}
```cpp {.task_answer}
err
```
## Объявление и определение класса
Объявление класса не включает реализацию его методов:
```cpp {.example_for_playground .example_for_playground_001}
class Message
{
public:
Message(std::string raw_text);
std::string get_message();
std::time_t get_timestamp();
private:
std::string msg;
std::time_t ts;
};
```
Объявление класса в связке с реализацией методов считается определением класса. При реализации метода _вне_ тела класса перед методом указывается имя класса, отделённое от метода оператором разрешения области видимости `::`.
```cpp {.example_for_playground .example_for_playground_002}
Message::Message(std::string raw_text)
{
msg = parse_message(raw_text);
ts = parse_time(raw_text);
}
std::string Message::get_message()
{
return msg;
}
std::time_t Message::get_timestamp()
{
return ts;
}
```
Приемная комиссия колледжа принимает заявления на поступление вместе с результатами теста. Члены комиссии должны отслеживать `n`-ый наивысший балл по тестам. Это нужно делать в потоковом режиме, по мере того, как поступают новые заявления. Отслеживание `n`-ного наивысшего балла помогает определить пороговое значение, необходимое для зачисления `n` учеников. {.task_text}
Перед вами объявление класса `NthLargest`. Реализуйте определения методов. Они должны идти после объявления класса. {.task_text}
Конструктор принимает число `n` и значение по умолчанию, которое нужно возвращать, пока не накопится `n` результатов теста. {.task_text}
Метод `add()` добавляет новый результат теста и возвращает обновлённый `n`-ый наивысший балл. Пока `n` результатов не накопилось, метод возвращает `default_val`. {.task_text}
В реализации используется [очередь с приоритетами.](/courses/cpp/chapters/cpp_chapter_0075/#block-priority-queue) {.task_text}
```cpp {.task_source #cpp_chapter_0101_task_0020}
class NthLargest
{
public:
NthLargest(std::size_t n, std::size_t default_val);
std::size_t add(std::size_t val);
private:
std::size_t m_n;
std::size_t m_default;
// https://en.cppreference.com/w/cpp/container/priority_queue
std::priority_queue<std::size_t, // тип элемента
std::vector<std::size_t>, // контейнер для адаптера
std::greater<std::size_t> // компаратор
> m_pq;
};
```
Очередь с приоритетами реализует структуру данных [куча](https://ru.wikipedia.org/wiki/%D0%9A%D1%83%D1%87%D0%B0_(%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85)) (heap). По умолчанию элемент с наибольшим значением ключа находится на вершине кучи. Чтобы на вершине оказался наименьший элемент, шаблонный класс `std::priority_queue` был инстанцирован компаратором `std::greater` вместо `std::less`. Вам осталось контроллировать количество элементов кучи. Оно не должно превосходить `n`. {.task_hint}
```cpp {.task_answer}
class NthLargest
{
public:
NthLargest(std::size_t n, std::size_t default_val);
std::size_t add(std::size_t val);
private:
std::size_t m_n;
std::size_t m_default;
// https://en.cppreference.com/w/cpp/container/priority_queue
std::priority_queue<std::size_t, // тип элемента
std::vector<std::size_t>, // контейнер для адаптера
std::greater<std::size_t> // компаратор
> m_pq;
};
NthLargest::NthLargest(std::size_t n, std::size_t default_val)
{
m_n = n;
m_default = default_val;
}
std::size_t NthLargest::add(std::size_t val)
{
if (m_pq.size() < m_n)
{
m_pq.push(val);
return (m_pq.size() < m_n) ? m_default : m_pq.top();
}
if (std::size_t cur_nth_largest = m_pq.top(); cur_nth_largest < val)
{
m_pq.push(val);
if (m_pq.size() > m_n)
m_pq.pop();
}
return m_pq.top();
}
```
----------
## Резюме {#block-summary}
- Объявление (declaration) делает сущность видимой для компилятора, а определение (definition) реализует её.
- Любое определение является объявлением.
- Объявлений одной и той же сущности в проекте может быть несколько. Но определение должно быть единственным. Это важный пункт ODR (one definition rule).
Наша группа в telegram. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!