[БЕЗ_ЗВУКА] Когда у нас есть большой проект,
в котором много файлов, то мы, естественно,
не можем помнить досконально, в каком файле какие функции есть.
И очень часто хочется, открыв файл, понять
интерфейс этого файла, то есть понять, какие функции и классы в этом файле есть.
Но при этом не хочется разбираться в их реализации,
потому что мы можем просто искать какую-то конкретную функцию, и нам не хочется
прочитывать большое количество кода, чтобы просто эту функцию найти.
Поэтому нам хочется иметь возможность зайти в файл и увидеть его интерфейс,
увидеть, какие вообще в нем есть функции или классы.
Вот давайте посмотрим на наш файл tests.h,
в котором у нас написаны юнит-тесты для функций.
Он достаточно большой и громоздкий, и открыв этот файл,
достаточно трудно понять, например: а какие функции в этом файле вообще есть?
Нам чтобы это понять, нужно весь файл пролистать.
То есть вот мы открыли, видим: ага, у нас есть тест, называется TestAddSynonyms.
Значит, он, скорее всего, тестирует функцию AddSynonyms,
и мы это можем понять, прочитав его реализацию.
Дальше мы должны пролистать эту функцию увидеть: ага, у нас есть еще TestCount.
Отлично.
Что у нас есть еще?
Тут нам надо пролистать всю реализацию, глазами как бы понять,
где у нас началась новая функция.
Вот, ага, у нас есть TestAreSynonyms, есть еще функция TestAll.
То есть интерфейс этого файла, те функции, которые
мы можем использовать, они вот как бы так равномерно размазаны по всему файлу.
Конечно, у нас есть IDE, она может нам помочь это сделать,
вот если в Eclipse нажать Ctrl + O, то появляется так называемый quick outline,
и здесь можно посмотреть, какие вообще есть функции,
какие вообще есть в данном случае еще include-ы.
И IDE действительно помогает в том,
чтобы увидеть интерфейс,
но давайте сейчас рассмотрим случай, как нам это сделать без IDE.
И давайте для начала поймем, чего нам вообще хочется.
А хочется нам вот чего-то такого.
Чтобы мы открыли файл и просто увидели список
функций и классов, которые в нем есть.
Вот у нас есть пример: вот мы открыли файл,
у нас есть функция, она называется GreatestCommonDivisor.
Мы понимаем: ага, в этом файле есть что-то,
что умеет считать наибольший общий делитель.
А еще есть функция ReadFile.
И мы прямо по ней видим: ага, вот если нам нужно будет прочитать
содержимое какого-то файла, то вот она, эта функция.
И точно так же у нас приведен пример интерфейса класса.
Мы видим, что вот есть класс, он представляет собой прямоугольник.
Мы сразу видим, что у него есть два метода:
один считает площадь прямоугольника, другой считает периметр.
И мы если представим, что класс прямоугольника,
он как-то сложно реализован,
нам не нужно разбираться в деталях его конструктора, в деталях его методов,
мы просто видим этот их список, и мы знаем, что мы можем делать с этим классом.
Нас сейчас не интересует, как это работает, нас интересует,
что мы с ним можем делать.
И вот это то, чего нам хотелось бы добиться.
Чтобы нам этого добиться, нам нужно
узнать два новых определения, два новых понятия.
Это понятия «объявление» и «определение».
Вот у меня приведен на экране приведен пример объявления и определения функции.
Мы видим, что объявление — это просто сигнатура функции, то есть возвращаемый
тип, имя функции и список параметров, за которым идет точка с запятой.
Объявление функции говорит компилятору, что где-то в программе,
неизвестно где, есть вот функция вот с такой сигнатурой.
Определение функции — это ее, собственно, реализация.
То есть определение как выглядит?
Это опять же сигнатура функции: возвращаемый тип, имя,
список значений, а потом в фигурных скобках — реализация.
Это тело этой функции.
Что здесь важно?
Важно то, что, как я сказал,
объявление функции говорит компилятору, что эта функция где-то определена,
но самое важное здесь — что функция объявлена может быть несколько раз,
объявлений у нее может быть много, они могут быть в разных местах,
в разных файлах, важно, чтобы они все были одинаковые.
Но вот определена эта функция может быть только в одном месте.
И давайте мы посмотрим на это повнимательнее.
Давайте переключимся в Eclipse и откроем достаточно простой пример.
Пока закроем наш большой многострадальный проект.
Вот у нас есть другой проект, в котором есть всего один файл,
и он выглядит вот так вот просто.
У нас есть функция main пустая, и есть две функции: функция bar и функция foo.
Они ничего не делают, поэтому я взял для них такие вот имена,
которые часто используют в книжках и статьях о программировании.
И если мы вот эту программу простую скомпилируем,
то она у нас не скомпилируется с ошибкой о том,
что компилятор говорит: «Я не знаю, что такое bar».
Вот вы можете видеть: 'bar' was not declared in this scope,
declared in this scope.
А теперь давайте мы возьмем эту функцию bar и объявим ее.
Вот, мы сделали объявление функции bar.
Сказали компилятору: «Где-то в нашей программе есть функция bar».
Запускаем компиляцию, и программа компилируется.
То есть до этого, когда компилятор встречал вот это вот использование
функции bar, он не знал, что это такое, потому что он не дошел еще вот досюда,
теперь же мы сделали объявление, объявили эту функцию,
и у нас все компилируется.
Таким образом вы видите, что объявление позволяет нам
известить компилятор о том, какие у нас есть функции.
При этом мы можем наплодить этих объявлений очень много,
и программа продолжит компилироваться.
А вот это вот — это определение функции bar.
Если их будет несколько,
то мы получим уже знакомый нам redefinition, потому что это две...
два определения одной и той же функции.
Такого быть не может.
Хорошо.
Это то, что касается определений и объявлений функций.
Но то же самое есть и для классов.
Вот, значит, на экране у меня сейчас представлено объявление класса Rectangle,
который представляет собой прямоугольник.
Мы видим имя класса, мы видим все его публичные и приватные
поля и методы, но методы не реализованы.
Это точно так же,
как для функции мы просто сказали: вот такие методы у этого класса есть.
Вот так вот выглядит определение методов класса.
То есть у нас класс, в нем что?
Есть поля и методы.
Вот поля, мы их просто объявили, а методы — их надо еще определить.
И вот так вот выглядит определение методов.
Мы пишем имя класса — Rectangle,
ставим два двоеточия и потом указываем имя метода, который мы хотим определить.
Ну и вот вы можете видеть на примере методов Area и Perimeter,
что тип возвращаемого значения, он идет первым, то есть сначала мы указываем тип
возвращаемого значения, потом имя класса, два двоеточия, имя метода и т.д.
Вот таким образом выглядит определение класса.
Итак, мы познакомились с такими понятиями,
как определение и объявление и давайте вспомним, зачем мы всем этим занялись.
Мы хотели сделать так, чтобы мы могли видеть интерфейс файла,
мы, открывая файл, видели все те функции и классы, которые в этом файле есть.
И это мы можем сделать, если мы в начале нашего файла
объявим все функции и классы, которые в нем есть.
Давайте вернемся в Eclipse и сделаем это для нашего большого проекта,
с которым мы работаем.
Откроем tests.h.
Мы как раз с него начали рассматривать,
с него начали разговор об объявлениях и определениях.
И прямо вот здесь пишем.
Ой, капслок.
void TestAddSynonyms() ;
void Test AreSynonyms,
void TestCount, и void TestAll.
Мы объявили все функции, которые есть в этом классе.
Мы можем даже вот так вот сделать.
Сказать, что вот здесь у нас идет интерфейс файла,
а вот здесь его реализация.
Получилось похоже на язык Паскаль.
Там модули таким образом объявляются.
Скомпилируем нашу программу.
Видим, она компилируется, всё хорошо, всё работает.
Давайте то же самое сделаем и для остальных заголовочных
файлов в нашем проекте.
Объявим функцию AddSynonyms,
функцию AreSynonyms
добавим и
GetSynonymCount тоже объявим.
Таким образом, если мы заглянем в Synonyms.h, мы увидим сначала интерфейс.
Мы увидим, что есть определение нового типа,
и объявлены три функции, которыми мы можем пользоваться.
А дальше они уже реализованы.
Компилируем.
Компилируется, отлично.
У нас остался еще один файл — это TestRunner.
Причем TestRunner — он особый.
В нем у нас находятся шаблоны.
А мы пока говорили про функции и классы,
а это всё-таки шаблоны функций, это немного другая сущность языка.
Но объявлять их тоже можно.
Мы точно также можем
скопировать их объявление.
Вот операторы
вывода мы объявили,
теперь добавим объявление
нашего шаблона AssertEqual,
функцию Assert объявим,
и остался
нам после Assert'а class TestRunner.
Мы можем его отсюда сюда вставить.
И реализации методов нам здесь не нужны,
потому что тут мы его хотим только объявить.
Поэтому реализацию мы уносим,
здесь пишем TestRunner.
А там где мы его объявляли, мы реализацию убираем и вставляем точку с запятой.
И то же самое делаем с деструктором.
Здесь делаем точку с запятой, это объявление,
а здесь пишем TestRunner,
TestRunner и вставляем реализацию.
Так.
Лишняя закрывающая скобка.
Скомпилируем то, что у нас получилось.
Отлично, компилируется.
Даже работает, тесты выполняются, всё хорошо.
У нас получилось в классе TestRunner, что мы видим его интерфейс.
Мы видим, что у нас есть операторы вывода для множества и для Map,
есть шаблон AssertEqual, функция Assert и класс TestRunner,
и его интерфейс нам говорит, как им пользоваться.
А дальше идет реализация.
Давайте подведем итоги.
Итак, в этом видео мы узнали, что такое объявление и определение.
Объявление сообщает компилятору, что заданная функция,
класс или шаблон где-то определены.
При этом объявлений может быть несколько, а определение только одно.
Теперь мы знаем, что группировка объявлений в начале файла помогает
нам узнать, какие функции и классы в этом файле есть.
Для этого нам необязательно вникать в их реализацию.