Урок №19. Прототип функции и Предварительное объявление



Книга Урок №19. Прототип функции и Предварительное объявление

В рамках данного занятия мы изучим концепцию прототипа функции и предварительного объявления в языке программирования C++.

Наличие проблемы

Взгляните на данный, казалось бы, безобидный отрывок кода с названием add.cpp:

#include

int main ( )

{

std :: cout << "The sum of 3 and 4 is: " << add ( 3 , 4 ) << std :: endl ;

return 0 ;

}

int add ( int x , int y )

{

return x + y ;

}

Возможно, вы ожидаете увидеть приблизительно такой результат:

When you add 3 and 4 together, you get a total of 7

Однако на самом деле данная программа не сможет быть скомпилирована. Это происходит потому, что компилятор читает код по порядку. Когда он видит вызов функции add() в строке №5 функции main(), он не знает, что такое add(), так как ее еще не определили! В итоге возникнет следующая ошибка:

Ошибка: идентификатор не обнаружен

Для решения данной проблемы необходимо учитывать, что компилятор не распознает функцию add(). Существует два способа решения этой проблемы.

Первое решение: Разместить определение функции add() до её вызова (то есть до функции main()):

#include

int add ( int x , int y )

{

return x + y ;

}

int main ( )

{

std :: cout << "The sum of 3 and 4 is: " << add ( 3 , 4 ) << std :: endl ;

return 0 ;

}

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

Кроме того, такой вариант не всегда доступен. Например, предположим, у нас есть программа с двумя функциями: А и В. Если функция А вызывает функцию B, а функция B вызывает функцию А, то нет возможности упорядочить эти функции так, чтобы они обе знали о существовании друг друга одновременно. Если объявить сначала функцию А, то компилятор выдаст ошибку, не найдя функцию B. Если объявить сначала функцию B, то компилятор выдаст ошибку, не найдя функцию A.

Прототипы функций и Предварительное объявление

Второй вариант решения: Применить предварительное объявление.

Предварительное объявление - это информация, которая дает компилятору знать о существовании идентификатора до того, как он будет фактически определен.

При работе с функциями мы можем предварительно сообщить компилятору о наличии функции до её фактического определения. Для этого необходимо использовать прототип функции. Прототип функции включает в себя тип возвращаемого значения, имя функции и параметры (тип и имя параметра). В кратком прототипе отсутствуют имена параметров функции, а основная часть (между фигурными скобками) опускается. Поскольку прототип функции является оператором, он также завершается точкой с запятой.

Вот пример функции add():

int add ( int x , int y ) ; // прототип функции состоит из типа возврата функции, её имени, параметров и точки с запятой

Вот пример вышеуказанной программы, но уже с использованием прототипа функции в качестве предварительного объявления аdd():

#include

int add ( int x , int y ) ; // предварительное объявление функции add() (используется её прототип)

int main ( )

{

std :: cout << "The sum of 3 and 4 is: " << add ( 3 , 4 ) << std :: endl ; // это работает, так как мы предварительно (выше функции main()) объявили функцию add()

return 0 ;

}

int add ( int x , int y ) // хотя определение функции add() находится ниже её вызова

{

return x + y ;

}

Сейчас, когда компилятор обнаруживает вызов функции add() в функции main(), он понимает, что это за функция и где её найти.

Важно отметить, что в прототипах функций не обязательно указывать имена параметров. Например, приведенный выше прототип можно записать иначе:

int add ( int , int ) ;

Однако, рекомендуется указывать имена параметров, чтобы избежать недопониманий.

Совет: Для быстрого создания прототипов функций можно воспользоваться копированием и вставкой из уже существующего определения функции. Главное не забыть добавить точку с запятой в конце.

Предварительно объявили, но не определили

Вопрос: "Что произойдет, если мы объявим функцию заранее, но не определим её?". Ответ на этот вопрос неоднозначен. Если объявление функции есть, но она никогда не вызывается, то программа может успешно выполниться без ошибок. Однако, если функция вызывается, но её определение отсутствует, то возникнет ошибка на этапе линковки: программа не сможет обработать вызов этой функции.

Давайте изучим следующий код:

#include

int add ( int x , int y ) ; // предварительное объявление функции add() (используется её прототип)

int main ( )

{

std :: cout << "The sum of 3 and 4 is: " << add ( 3 , 4 ) << std :: endl ;

return 0 ;

}

В данной программе мы заранее объявили функцию add(), вызвали её в main(), но не предоставили её реализацию. При компиляции данной программы возникнет ошибка со стороны линкера.

Объявление vs. Определение

Часто встречающиеся термины в языке программирования C++ - "объявление" и "определение". Но что они означают?

Идентификатор фактически реализует выделение памяти. Вот несколько примеров определений:

int add ( int x , int y ) // определяем функцию add()

{

int z = x + y ; // определяем переменную z

return z ;

}

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

В C++ существует правило одного определения, которое включает в себя три компонента:

В одном файле может быть только одно определение функции, объекта, типа или шаблона.

В рамках программы каждый объект или обычная функция должны иметь лишь одно определение.

В программе могут существовать несколько определений для типов, шаблонов функций и встроенных функций, если они одинаковы.

Если первая часть правила будет нарушена, это приведет к ошибке компиляции. Нарушение второй или третьей части правила приведет к ошибке линковки.

Объявление представляет собой инструкцию, которая информирует компилятор о наличии определенного идентификатора и его типе. Вот несколько примеров объявлений:

Объявляем функцию add(), которая принимает два целочисленных параметра и возвращает целочисленное значение.

int x ; // объявляем целочисленную переменную х

Для правильной работы компилятора необходимо наличие объявления, однако этого недостаточно для корректной работы линкера. Определение же обеспечивает корректную работу как компилятора, так и линкера.

Тест

Первое задание: Каковы отличия между прототипом функции и предварительным объявлением?

Второе задание: Напишите прототип функции, описанной ниже:

int doMath ( int first , int second , int third , int fourth )

{

return first + second * third / fourth ;

}

Третье задание: Определите, какие из представленных программ не будут успешно скомпилированы, какие не пройдут процесс линковки, а какие не пройдут и тот, и другой этап?

Первая программа:

#include

int add ( int x , int y ) ;

int main ( )

{

std :: cout << "3 + 4 + 5 = " << add ( 3 , 4 , 5 ) << std :: endl ;

return 0 ;

}

int add ( int x , int y )

{

return x + y ;

}

Вторая программа:

#include

int add ( int x , int y ) ;

int main ( )

{

std :: cout << "3 + 4 + 5 = " << add ( 3 , 4 , 5 ) << std :: endl ;

return 0 ;

}

int add ( int x , int y , int z )

{

return x + y + z ;

}

Третья программа:

#include

int add ( int x , int y ) ;

int main ( )

{

std :: cout << "3 + 4 + 5 = " << add ( 3 , 4 ) << std :: endl ;

return 0 ;

}

int add ( int x , int y , int z )

{

return x + y + z ;

}

Четвертая программа:

#include

int add ( int x , int y , int z ) ;

int main ( )

{

std :: cout << "3 + 4 + 5 = " << add ( 3 , 4 , 5 ) << std :: endl ;

return 0 ;

}

int add ( int x , int y , int z )

{

return x + y + z ;

}

149   0  

Comments

    Ничего не найдено.