Чтение онлайн

ЖАНРЫ

Программирование. Принципы и практика использования C++ Исправленное издание
Шрифт:

<ctype.h>
. Классификация символов; см. раздел 11.6.

<stdbool.h>
. Булевы макросы.

Полное описание стандартной библиотеки языка С можно найти в соответствующем учебнике, например в книге K&R. Все эти библиотеки (и заголовочные файлы) также доступны и в языке С++.

27.2. Функции

В языке C есть несколько особенностей при работе с функциями.

• Может существовать только одна функция с заданным именем.

• Проверка типов аргументов функции является необязательной.

• Ссылок нет (а значит, нет и механизма передачи аргументов по ссылке).

Нет функций-членов.

• Нет подставляемых функций (за исключением версии C99).

• Существует альтернативный синтаксис объявления функций.

Помимо этого, все остальное мало отличается от языка С++. Изучим указанные отличия по отдельности.

27.2.1. Отсутствие перегрузки имен функций

Рассмотрим следующий пример:

void print(int); /* печать целого числа */

void print(const char*); /* печать строки */ /* ошибка! */

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

void print_int(int); /* печать целого числа int */

void print_string(const char*); /* печать строки */

Иногда это свойство называют преимуществом: теперь вы не сможете случайно использовать неправильную функцию для вывода целого числа! Очевидно, что нас такой аргумент убедить не сможет, а отсутствие перегруженных функций усложняет реализацию идей обобщенного программирования, поскольку они основаны на семантически похожих функциях, имеющих одинаковые имена.

27.2.2. Проверка типов аргументов функций

Рассмотрим следующий пример:

int main

{

f(2);

}

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

К сожалению, это определение в другом исходном файле может выглядеть следующим образом:

/* other_file.c: */

int f(char* p)

{

int r = 0;

while (*p++) r++;

return r;

}

Редактор связей не сообщит об этой ошибке. Вместо этого вы получите ошибку на этапе выполнения программы или случайный результат.

Как решить эту проблему? На практике программисты придерживаются согласованного использования заголовочных файлов. Если все функции, которые вы вызываете или определяете, объявлены в заголовке, поставленном в соответствующее место программы с помощью директивы
#include
, будет включен механизм проверки типов. Однако в больших программах на это трудно рассчитывать. Вследствие этого в большинстве компиляторов языка С существуют опции, предусматривающие выдачу предупреждений о вызовах необъявленных
функций: воспользуйтесь ими. Кроме того, с первых дней существования языка C появились программы, с помощью которых можно выявлять все возможные проблемы, связанные непротиворечивостью типов. Обычно они называются lint. Используйте их для любой нетривиальной программы на языке С. Вы обнаружите, что программы lint подталкивают вас использовать язык С как подмножество языка С++. Одно из наблюдений, приведших к разработке языка С++, состояло в том, что компилятор мог легко проверять многое (но не все), что могли проверять программы
lint
.

Вы можете попросить включить проверку аргументов функций в языке С. Для этого достаточно объявить функцию с заданными типами аргументов (точно так же, как в языке С++). Такое объявление называется прототипом функции (function prototype). Тем не менее следует избегать объявлений, не задающих аргументы; они не являются прототипами функций и не включают механизм проверки типов.

int g(double); /* прототип — как в языке С ++ */

int h; /* не прототип — типы аргументов не указаны */

void my_fct

{

g; /* ошибка: пропущен аргумент */

g("asdf"); /* ошибка: неправильный тип аргумента */

g(2); /* OK: 2 преобразуется в 2.0 */

g(2,3); /* ошибка: один аргумент лишний */

h; /* Компилятор допускает! Результат непредсказуем */

h("asdf"); /* Компилятор допускает! Результат непредсказуем */

h(2); /* Компилятор допускает! Результат непредсказуем */

h(2,3); /* Компилятор допускает! Результат непредсказуем */

}

В объявлении функции
h
не указан тип аргумента. Это не означает, что функция
h
не получает ни одного аргумента; это значит: принимает любой набор аргументов и надеется, что это набор при вызове окажется правильным. И снова отметим, что хороший компилятор предупредит об этой проблеме, а программа
lint
перехватит ее.

Существует специальный набор правил, регламентирующих преобразование аргументов, если в области видимости нет прототипа функции. Например, переменные типов

char
и
short
преобразуются в переменные типа
int
, а переменные типа
float
— в переменные типа
double
. Если вы хотите знать, скажем, что произойдет с переменной типа
long
, загляните в хороший учебник по языку С. Наша рекомендация проста: не вызывайте функций, не имеющих прототипов.

Обратите внимание на то, что, хотя компилятор допускает передачу аргументов неправильного типа, например параметр типа

char*
вместо параметра типа int, использование таких аргументов приводит к ошибкам. Как сказал Деннис Ритчи: “С — это язык программирования со строгим контролем типов и слабой проверкой”.

27.2.3. Определения функций

Можете определять функции точно так же, как в языке С++. Эти определения являются прототипами функций.

Поделиться с друзьями: