Прототипы функций. Прототип функции в программе на языке Си

Стандарт ANSI С расширяет концепцию предварительного описания функции. Данное расширенное описание называется прототипом функции.

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

тип имя_функции (список параметров);

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

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

Если возможно, С автоматически преобразует тип аргумента в тип, получаемый параметром. Тем не менее, некоторые преобразования типов недопустимы. Если функция имеет прототип, то все нелегальные преобразования будут найдены и появится сообщение об ошибке. В качестве примера, следующая программа вызывает сообщение об ошибке, поскольку пытается вызвать func() с указателем, а не с требуемым float. (Нельзя преобразовать указатель к типу float.)

/* Данная программа использует прототипы функций для достижения строгой проверки типов при вызове func(). Программа не компилируется из-за несоответствия между типом аргументов, определенных в прототипе функции, и типом аргументов, используемых при вызове функции. */

#include

int main(void)
{
int x, *y;
x = 10;
у = &x;
func(x, у) ; /* несоответствие типов */
return 0;
}

Float func (int x, float y)
{
printf("%f", у/(float)x);
return у/(float) x;
}

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

/* Программа не компилируется из-за несоответствия между числом параметров, определенных в прототипе функции, и числом аргументов, используемых при вызове функции. */

#include
float func (int x, float у); /* прототип */
int main(void)
{
func (2, 2.0, 4); /* неверное число аргументов */
return 0;
}

Float func {int x, float y)
{
printf ("%f", у/(float)x);
return у/(float) x;
}

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

Char func (char *, int);

Char func (char *str, int count);

Тем не менее, если включить имена параметров, компилятор использует имена для выдачи сообщений о несоответствии типов.

Некоторые функции типа printf() могут принимать переменное число аргументов. Переменное число аргументов определяется в прототипе с помощью многоточия. Например, прототип функции printf() выглядит так:

Int printf(const char *fmt, ...);

Для создания функции с переменным числом аргументов надо обратиться к описанию стандартной библиотечной функции va_arg().

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

Функции могут располагаться в исходном файле в любом порядке. А сама исходная программа, как отмечалось вначале, может размещаться в нескольких файлах.

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

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

Прототип функции имеет вид:

тип_результата имя_функции (список ) ;

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

Пример описания функции fun , которая имеет три параметра типа int , один параметр типа double и возвращает результат типа double :

double fun(int, int, int, double);

Пример описания для вышеприведенной функции Min:

int Min (int x, int y);

int Min (int, int);

Конец работы -

Эта тема принадлежит разделу:

Структура программы на языке СИ. Этапы выполнения программы

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

Если Вам нужно дополнительный материал на эту тему, или Вы не нашли то, что искали, рекомендуем воспользоваться поиском по нашей базе работ:

Что будем делать с полученным материалом:

Если этот материал оказался полезным ля Вас, Вы можете сохранить его на свою страничку в социальных сетях:

Все темы данного раздела:

Алфавит языка Си
Алфавит языка Си включает: - прописные и строчные буквы латинского алфавита, а также знак подчеркивания (код ASCII 95); - арабские цифры от 0 до 9; - специальные символы:

Идентификаторы и ключевые слова
Идентификатор (в дальнейшем, для краткости - ID) – это имя программного объекта (константы, переменной, метки, типа, функции, модуля, поля в структуре). В иден

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

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

Этапы обработки программы
Язык Си относится к языкам высокого уровня, т.е. предназначенным для записи программы в форме, удобной для человека и не "привязанной" к конкретному типу машин. Ис

Роль препроцессора
Перед компиляцией программа на языке Си обрабатывается специ­альной программой – препроцессором, который работает под управле­нием директив. Препроцессорные директи

Основные типы данных
Данные в языке Си разделяются на две категории: простые (скалярные), будем их называть базовыми, и сложные (составные) типы данных. Тип данных определяет: внутреннее представлени

Константы в программах
Константы - объекты, не подлежащие использованию в левой части оператора присваивания, т.к. константа - является неадресуемой величиной и, хотя она хранится в памяти ЭВМ, обычно нет никакого способ

Целочисленные константы
Общий формат: ±n (+ обычно не ставится). Десятичные константы - последовательность цифр 0...9, первая из которых не должна быть 0. Например, 22 и 273 - обычные целые констант

Константы вещественного типа
Данные константы размещаются в памяти по формату double, а во внешнем представлении могут иметь две формы: 1) с фиксированной десятичной точкой, формат записи: ±n.m, где n

Символьные константы
Символьная константа - это символ, заключенный в одинарные кавычки: "A", "х" (занимает 1 байт). В языке Си используются и. специальные (управляющие) символы,

Строковые константы
Строковая константа представляет собой последователь­ность символов кода ASCII, заключенная в кавычки (”) . Во внутреннем представлении к строковым константам добавляется нулевой символ "", еще на

Операция присваивания
Формат операции присваивания: Операнд_1 = Операнд_2 Операндом_1 может быть только переменная. Этот (левый) операнд операции присваивания получил

Бинарных операций
При выполнении операций могут встречаться операнды различных типов. Но для выполнения операции оба операнда должны быть преобразованы к общему типу в соответствии с небольшим набором правил.

Преобразование типов при присваивании
При присваивании значение правой части преобразуется к типу левой. И здесь необходимо быть внимательным, так как при некорректном использовании операций присваивания могут возникнуть неконтролируем

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

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

Стандартные математические функции
Математические функции языка Си декларированы в файлах math.h и stdlib.h. В приведенных здесь функциях аргументы и возвращаемый результат имеют

Потоковый ввод-вывод
Поток – это абстрактное понятие, которое относится к любому переносу данных от источника к приемнику. Потоки С++ обеспечивают надежную работу как со стандартными (stdin, stdout), так

Консольные функции вывода данных на экран
Наряду с потоковым вводом-выводом, в консольных приложениях применяются и функции ввода-вывода языка Си. Их декларации приведены в заголовочных файлах stdio.h и c

Консольные функции ввода информации
Функция scanf предназначена для форматированного ввода исходной информации с клавиатуры: scanf (управляющая строка, список адресов объектов ввода

Советы по программированию
1. Выбирайте тип для переменных с учетом диапазона их возможных значений и требуемой точности представления данных. 2. Старайтесь давать переменным ID (имена), отражающие их назначе

Составление циклических алгоритмов
Под циклом понимается организованное повторение некоторой последовательности операторов. Любой цикл состоит из кода цикла, т.е. тех операторов, которые выполняются несколько раз, начальных

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

Советы по программированию
1. Выражение, стоящее в круглых скобках операторов if, while и do – while вычисляется по правилам стандартных приоритетов операций. 2. Если в какой-либо ветви вычислен

Массивы
Массив представляет собой упорядоченную конечную совокупность элементов одного типа. Число элементов массива называют его размером. Каждый элемент массива определяется и

Одномерные массивы
В программе одномерный массив объявляется следующим образом: типID_массива [размер] = {список начальных значений}; тип – тип эл

Одномерные массивы. Нахождение суммы, произведения, количества
Задача 2. Найти сумму элементов массива. #include #include #include void main() {

Одномерные массивы. Удаление и вставка в массивах
Задача 11. Удалить из массива второй по счету элемент. Поскольку полное количество элементов в массиве задано в его объявлении, физически "удалить" элемент

Одномерные массивы. Обмен местами
Задача 14. Поменять местами первый и последний элемент массива. При обмене, чтобы не потерять одно из значений, потребуется дополнительная переменная:  

Одномерные массивы. Сортировка массива
Задача 16. Отсортировать массив по возрастанию (т.е. расположить его элементы в порядке возрастания). Для этой задачи придумано множество различных алгоритмов. Один

Одномерные массивы. Поиск совпадений
Задача 17. Найти в массиве элемент, повторяющийся наибольшее количество раз. (Если таких элементов несколько, вывести любой из них). for(max=i=0; i

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

Многомерные массивы. Диагонали квадратной матрицы
Квадратной называется матрица, у которой число строк равно числу столбцов. Ее главной диагональю называется диагональный ряд элементов, идущий из верхнего левого угла

Многомерные массивы. Работа со строками и столбцами
Строка или столбец матрицы аналогичны одномерному массиву. Поэтому к ним применимы все алгоритмы, рассмотренные для одномерных массивов. В применении же ко всей матрице это обычно требует

Компонента StringGrid
В оконном режиме ввод и вывод массивов обычно организуется с использованием компоненты StringGrid, предназначенной для отображения информации в виде двумерной таблицы, каждая ячейка которой

Общие понятия
Данные и программы во время работы ПЭВМ размещаются в оперативной памяти (ОЗУ), которая представляет собой последовательность пронумерованных ячеек. По указанному номеру процессор находит нужную яч

Операция sizeof
Данная операция позволяет определить размер объекта по ID или типу, результатом является размер памяти в байтах (тип результата int). Формат записи: sizeof(параметр

Кодирование программы
Программа в машинных кодах (исполняемый код) – это последовательность команд (инструкций), которые помещаются в памяти и выполняются процессором в указа

Регистры
Заметим, что кроме собственно ОЗУ, в компьютере имеются ячейки памяти, размещенные непосредственно в процессоре. Такие ячейки памяти называются регистрами. В процессоре обыч

Регистры. Ввод-вывод строк - массивов char
Для ввода с консоли строк - массивов char обычно используются две стандартные функции: scanf() (см. тему "Функции ввода-вывода"; специфик

Регистры. Поэлементная работа со строками
В языке С не допускается ни присваивание, ни сравнение массивов. Операции над строками могут быть выполнены либо непосредственно действиями над отдельными символами (как над элементами масси

Регистры. Перевод строк - массивов char в числа и наоборот
Функции преобразования строки S в число: - целое: int atoi(char *S); - длинное целое: long atol(char *S); - действительное: doub

Русификация консольных приложений
При работе в консольном приложении ввод-вывод выполняется в кодировке ASCII (см. тему "Кодирование символов", кодовые таблицы). В тексте же программы символы отображаются в принятой в Win

Б) Действия над типом String
Основными операциями с типом String являются: 1) Присваивание: S1=S2; 2) Сравнение: S1==S2, S1<=S2, S1!=S2 и т.д. Здесь знак <

А) Преобразование из массива char в String и наоборот
Как упоминалось выше, для преобразования массива char к типу String достаточно просто присвоить его переменной типа String: char c="Привет!"; String s=

Б) Преобразование из String в простую переменную типа char
При таком присваивании нужно указать номер символа в строке, который будет присвоен: String s="*"; char c=s; Обратное же присваивание н

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

Тип_результата имя_функции (список параметров)
{ код функции return выражение; } Параметры - это переменные, доступные внутри функции, значения которы

Область видимости
Область видимости (действия) объекта (переменной и др.) – это та часть кода (текста) программы, в которой его можно использовать. В сложных программах ограничение этой области помогает изб

Разбиение программы на модули
Разбиение программы на модули (отдельные файлы с текстом программы) позволяет использовать готовые модули в разных программах, а также является важнейшим способом разделения труда при работе в колл

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

Декларация структурного типа данных
Структурный тип данных задается в виде шаблона, общий формат описания которого следующий: struct ID структурного типа { описание полей

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

Обращение к полям структуры
Обращение к полю структуры производится при помощи составных имен, которые образуются с использованием операции принадлежности (.) в виде: ID_струк

Вложенные структуры
Структуры могут быть вложенными, т.е. поле структуры может cамо быть структурой, описание которой должно предшествовать описанию внешней структуры. Например, в структуре person, содержащей

Массивы структур
Структурный тип "struct ID_структуры", как правило, используют для декларации массивов, элементами которых являются структурные переменные. Это позволяет создавать программы, оперирующие

Размещение структурных переменных в памяти
Элементы структур в общем случае размещаются в памяти последо­ва­тельно с учетом выравнивания начальных адресов полей. Выравнивание (align) означает, что ком

Битовые поля
Наряду с "обычными" типами, допустимыми и для "отдельных" переменных, поля структуры могут иметь особый целочисленный тип, допустимый только для них - битовые поля

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

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

Типы файлов
В файле может храниться любая информация. Если там хранится текст в виде последовательности символов (включая символы перевода строк, хранимые по определенным правилам - см. ниже) и не содержится и

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

Закрытие файла
После окончания работы с файлом доступ к нему необходимо закрыть. Это выполняет функция fclose(указатель файла). Например, файл из предыдущего примера закрывается так: fclose (f);

Запись - чтение информации
Все действия по чтению-записи данных в файл можно разделить на три группы: - операции посимвольного ввода-вывода; - операции построчного и форматированного ввода-вывода;

А) Посимвольный ввод-вывод
В функциях посимвольного ввода-вывода происходит прием одного символа (байта) из файла или передача одного символа в файл: int fgetc(FILE *f) - считывает и возв

Б) Построчный и форматированный ввод-вывод
Эти функции служат для чтения/записи текста и обычно применяются для текстовых файлов. В функциях построчного ввода-вывода происходит пере­нос из файла, или в файл

В) Блоковый ввод-вывод
Функции блокового ввода-вывода читают из файла или записывают в файл заданное число байт без их преобразования. Другими словами, они способны записать в файл любую переменную или массив в том же дв

Д) Сброс буфера файла
Заметим, что если после записи данных файл не был закрыт, часть «записанных» данных может не сохраниться. Это связано с тем, что данные вначале записываются в буфер файла, и

Текстовые файлы
Для работы с текстовыми файлами удобнее всего пользоваться функциями fprintf(), fscanf(), fgets() и fputs(). Создание текстовых результирующих файлов обычно необходимо для оформления отчет

Перенаправление стандартного ввода-вывода
В консольном режимесуществует понятие стандартных файловых потоков: stdin - ввод (по умолчанию - клавиатура), stdout - выв

Бинарные файлы
При чтении-записи бинарных (двоичных) файлов удобнее всего пользоваться функциями, выполняющи­ми блоковый ввод-вывод: fread() и fwrite().

Дополнительные полезные функции
Рассмотрим некоторые функции, которые могут пригодиться для работы с файлами (они работают с любыми файлами, но чаще применяются к бинарным): int fileno(FILE *f)

Определение указателей
Как говорилось выше, машинная память состоит из байт. Все байты в памяти пронумерованы. Адресом байта называется его номер. (Нумерация при этом идет либо в пределах всей

Связь указателей и массивов
Идентификатор массива указывает адрес памяти, начиная с которого он расположен, т.е. адрес его первого элемента. Работа с массивами тесно связана с применением указателей. Пусть объявлен м

Указатели на указатели
В языке Си можно описать и переменную типа «указатель на указатель». Это ячейка оперативной памяти, в которой будет храниться адрес указателя на какую либо переменную. Признак такого типа да

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

Указатели на структуры
Указатели могут указывать и на структурный тип данных: struct Point{ int x,y; } r, *p; p=&r; Для обращения к полю ст

Указатели на функции
В языке Си допускаются указатели не только на данные, но и на функции. Они позволяют, например, создать функцию, строящую таблицу значений любой другой функции (с заданным видом списка параметров);

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

Создание одномерного динамического массива
В языке С размерность массива при объявлении должна задаваться константным выражением. При необходимости работы с массивами перемен­ной размерности нужно объявить вместо массива указат

Создание двуxмерного динамического массива
Операция new способна выделить память лишь под одномерный массив. А как быть, если массив двумерный? Наиболее удобный способ - это представить двумерный массив как массив из массивов

Операция typedef
Любому типу данных, как стандартному, так и определенному пользователем, можно задать новое имя с помощью операции typedef: typedef тип новое_имя; Вве

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


В современных, правильно написанных программах на языке С каждую функцию перед использованием необходимо объявлять. Обычно это делается с помощью прототипа функции . В первоначальном варианте языка С прототипов не было; но они были введены уже в Стандарт С89. Хотя прототипы формально не требуются, но их использование очень желательно. (Впрочем, в C++ прототипы обязательны !) Во всех примерах этой книги имеются полные прототипы функций. Прототипы дают компилятору возможность тщательнее выполнять проверку типов, подобно тому, как это делается в таких языках как Pascal. Если используются прототипы, то компилятор может обнаружить любые сомнительные преобразования типов аргументов, необходимые при вызове функции, если тип ее параметров отличается от типов аргументов. При этом будут выданы предупреждения обо всех таких сомнительных преобразованиях. Компилятор также обнаружит различия в количестве аргументов, использованных при вызове функции, и в количестве параметров функции.

В общем виде прототип функции должен выглядеть таким образом:

тип имя_функции (тип имя_парам1, тип имя_парам2, ..., имя_парамN );

Использование имен параметров не обязательно. Однако они дают возможность компилятору при наличии ошибки указать имена, для которых обнаружено несоответствие типов, так что не поленитесь указать этих имен - это позволит сэкономить время впоследствии.

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

/* В этой программе используется прототип функции чтобы обеспечить тщательную проверку типов. */ void sqr_it(int *i); /* прототип */ int main(void) { int x; x = 10; sqr_it(x); /* несоответствие типов */ return 0; } void sqr_it(int *i) { *i = *i * *i; }

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

#include /* Это определение будет также служить и прототипом внутри этой программы. */ void f(int a, int b) { printf("%d ", a % b); } int main(void) { f(10,3); return 0; }

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

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

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

Int f(); /* Прототип C++ для функции, не имеющей параметров */

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

Если функция в языке С не имеет параметров, то в ее прототипе внутри списка параметров стоит только ключевое слово void . Вот, например, прототип функции f() в том виде, в каком он должен быть в программе на языке С:

Float f(void);

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

Прототипы функций позволяют «отлавливать» ошибки еще до запуска программы. Кроме того, они запрещают вызов функций при несовпадении типов (т.е. с неподходящими аргументами) и тем самым помогают проверять правильность программы.

И напоследок хотелось бы сказать следующее: так как в ранних версиях С синтаксис прототипов в полном объеме не поддерживался, то в С прототипы формально не обязательны. Такой подход необходим для совместимости с С-кодом, созданным еще до появления прототипов. Но если старый С-код переносится в C++, то перед компиляцией этого кода в него необходимо добавить полные прототипы функций. Помните, что хотя прототипы в С не обязательны, но они обязательны в C++. Это значит, что каждая функция в программе на языке C++ должна иметь полный прототип. Поэтому при написании программ на С в них указываются полные прототипы функций - именно так поступает большинство программистов, работающих на этом языке.

Старомодные объявления функций

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

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

#include double div(); /* старомодное объявление функции */ int main(void) { printf("%f", div(10.2, 20.0)); return 0; } double div(double num, double denom) { return num / denom; }

Старомодное объявление типа функции сообщает компилятору, что функция div() возвращает результат типа double . Это объявление позволяет компилятору правильно генерировать код для вызовов этой функции. Однако оно ничего не говорит о параметрах div() .

Общий вид старомодного оператора объявления функции такой:

спецификатор_типа имя_функции ();

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

Как уже говорилось, старомодное объявление функции устарело и не должно использоваться в новом коде. Кроме того, оно несовместимо с C++.

Прототипы старомодных библиотечных функций

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

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

Прототип функции имеет следующий вид

тип<имя функции>(список параметров)

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

Поэтому прототип функции может иметь один из следующих видов:

int fun1(int x, int y, char* c); или int fun1(int, int, char*);

"Вызов функции" это выражение, которое передает управление и действительные аргументы, если они есть, функции. В вызове функции "выражение" вычисляется для получения адреса функции, а "список-выражений" это список разделенных запятыми выражений. Значения этих выражений являются действительными аргументами, передаваемыми в функцию. Если у функции нет аргументов, то список выражений может быть пустым.

Вопрос 33

Массивы - это группа элементов одинакового типа (double, float, int и т.п.). Из объявления массива компилятор должен получить информацию о типе элементов массива и их количестве. Объявление массива имеет два формата:

спецификатор-типа описатель [константное - выражение];

спецификатор-типа описатель ;

Описатель - это идентификатор массива.

Спецификатор-типа задает тип элементов объявляемого массива. Элементами массива не могут быть функции и элементы типа void.

Константное-выражение в квадратных скобках задает количество элементов массива. Константное-выражение при объявлении массива может быть опущено в следующих случаях:

При объявлении массив инициализируется,

Массив объявлен как формальный параметр функции,

Вопрос 34

Массивы символов - это группа элементов одинакового типа (char)

Синтаксис объявления имеет вид:

где ID – идентификатор массива, N – длина массива, при этом в памяти для хранения строки выделяется N байт.

Например, для переменной char ST в памяти выделяется 10 байт, что дает возможность сформировать строку из 9 символов. Для таких строк действуют все правила представления и обработки массивов.

Идентификатор массива – константа типа указатель, значение которой равно адресу первого элемента массива.

Инициализация возможна двумя способами:

· посимвольная инициализация char st={"y","e","s","\0"};

при этом оставшиеся 6 позиций не будут заполнены;

· инициализация на основе строковой константы char st ="Yes";

при этом в выделенную для строки память будут помещены 3 символа и добавлен четвертый – символ "\0".

Инициализация и объявление возможны без указания длины массива char st={"y","e","s","\0"};

в этом случае будет создан массив из четырех элементов.

Вопрос 35

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

Каждое константное-выражение в квадратных скобках определяет число элементов по данному измерению массива, так что объявление двухмерного массива содержит два константных-выражения, трехмерного - три и т.д. Отметим, что в языке СИ первый элемент массива имеет индекс равный 0.

double b; /* вектор из 10 элементов имеющих тип double */

int w = { { 2, 3, 4 },

Вопрос 36

Указатель - это переменная, содержащая адрес переменной.

двумерный массив - это индексированная переменная, которая определяется двумя индексами. Первый ее индекс- показывает порядковый номер строки, а второй индекс показывает номер столбца. Ниже в таблице Вы видите обозначение каждого элемента двумерного массива (матрицы).

a a a a
a a a a
a a a a

Количество байт памяти, которое необходимо для хранения массива, вычисляется по формуле:
Количество байт = <размер типа данных> * <количество строк> * <количество столбцов>
Например, если массив содержит натуральные числа от 1 до 255, каждое из которых, как известно, занимает по 1 байту памяти, то массив, состоящий из трех строк и четырех столбцов, занимает 12 байт (1 байт * 3 строки * 4 столбца) памяти. В таблице слева вы видите, что под массив размером: 3 строки на 4 столбца отведено 12 клеточек. Каждая клеточка условно изображает один байт. Обратите внимание на обозначение элементов массива. Здесь: a - это имя массива, и далее первый индекс в квадратных скобках - номер строки, второй индекс в квадратных скобках - номер столбца.
Напомним, что в Си многомерный массив обозначается следующим образом: тип <имя массива;> [размер1][размер2]...[размерN] .
Поэтому одномерный массив будет иметь следующее описание: тип <имя массива;> [размер] . Например: int a . Данный массив содержит 100 целых чисел. В Си индексы нумеруются от нуля. Таким образом, здесь элементы имеют коды: a, a, ..., a .
В памяти компьютера массив располагается непрерывно по строкам: так упомянутый выше двумерный массив представляет собой вытянутый в линию непрерывную строку: a, a, a, a, a, a, a, a, a, a, a, a.

Вопрос 36

Указатели в языке программирования Си. Операции с указателями, адресная арифметика. Взаимосвязь массивов и указателей.

Унарный оператор & выдает адрес объекта, так что инструкция

присваивает переменной p адрес ячейки c (говорят, что p указывает на c ). Оператор & применяется только к объектам, расположенным в памяти: к переменным и элементам массивов. Его операндом не может быть ни выражение, ни константа, ни регистровая переменная.

Унарный оператор * есть оператор косвенного доступа . Примененный к указателю он выдает объект, на который данный указатель указывает. Предположим, что x и y имеют тип int , а ip – укаэатель на int . Следующие несколько строк придуманы специально для того, чтобы показать, каким образом объявляются указатели и как используются операторы & и * .

int х = 1, у = 2, z;

int *ip; /* ip - указатель на int */

ip = &x; /* теперь ip указывает на x */

y = *ip; /* y теперь равен 1 */

*ip = 0; /* x теперь равен 0 */

ip = &z; /* ip теперь указывает на z */

Объявления x , y и z нам уже знакомы. Объявление указателя ip

Если p есть указатель на некоторый элемент массива, то p++ увеличивает p так, чтобы он указывал на следующий элемент, а p+=i увеличивает его, чтобы он указывал на i -й элемент после того, на который указывал ранее. Эти и подобные конструкции - самые простые примеры арифметики над указателями, называемой также адресной арифметикой.

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

Объявление

Определяет массив a размера 10, т. е. блок из 10 последовательных объектов с именами a, a, ..., a.

Запись a[i] отсылает нас к i -му элементу массива. Если pa есть указатель на int , т. е. объявлен как

то в результате присваивания

pa будет указывать на нулевой элемент a , иначе говоря, pa будет содержать адрес элемента a .

Теперь присваивание

будет копировать содержимое a в x .

Если pa указывает на некоторый элемент массива, то pa+1 по определению указывает на следующий элемент, pa+i - на i -й элемент после pa , a pa-i - на i -й элемент перед pa . Таким образом, если pa указывает на a , то

есть содержимое a , a+i - адрес a[i] , a *(pa+i) - содержимое a[i] .

Вопрос 37

Варианты описания массивов в языке программирования Си. Аргументы функции main.

int a; /* представлено в виде матрицы

float b; /* вектор из 10 элементов имеющих тип double */

int w = { { 2, 3, 4 },

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

В языке C заданы два встроенных аргумента функции main: argc и argv.

Выглядит это так:

int main(int argc, char *argv)

Аргумент argc типа integer содержит в себе количество аргументов командной строки.

Аргумент argv типа char - указатель на массив строк. Каждый элемент массива указывает на аргументы командной строки. Один параметр отделяется от другого пробелами.

argv - полное имя запущенной программы

argv - первая строка записаная после имени программы

argv - вторая строка записаная после имени программы

argv - последняя строка записаная после имени программы

argv - NULL

Вопрос 38

Основные спецификации форматного ввода/вывода (для функций printf и scanf) в библиотеке stdio языка программирования Си.

Символ Вводимые данные; тип аргумента
d десятичное целое: int *
i целое: int *. Целое может быть восьмеричным (с 0 слева) или шестнадцатеричным (с 0x или 0X слева)
o восьмеричное целое (с нулем слева или без него); int *
u беззнаковое десятичное целое; unsigned int *
x шестнадцатеричное целое (с 0x или 0X слева или без них); int *
c символы; char *. Следующие символы ввода (по умолчанию один) размещаются в указанном месте. Обычный пропуск символов- разделителей подавляется; чтобы прочесть очередной символ, отличный от символа-разделителя, используйте %1s
s Строка символов(без обрамляющих кавычек); char *, указывающая на массив символов, достаточный для строки и завершающего символа "\0", который будет добавлен
e, f, g число с плавающей точкой, возможно, со знаком; обязательно присутствие либо десятичной точки, либо экспоненциальной части, а возможно, и обеих вместе; float *
% сам знак %, никакое присваивание не выполняется
Символ Тип аргумента; вид печати
d, i int; десятичное целое
o unsigned int; беззнаковое восьмеричное (octal ) целое (без нуля слева)
x, X unsigned int; беззнаковое шестнадцатеричное целое (без 0x или 0X слева), для 10...15 используются abcdef или ABCDEF
u unsigned int; беззнаковое десятичное целое
c int; одиночный символ
s char *; печатает символы, расположенные до знака \0, или в количестве, заданном точностью
f double; [-]m.dddddd, где количество цифр d задается точностью (по умолчанию равно 6 )
e, E double; [-]m.dddddde±xx или [-]m.ddddddE±xx, где количество цифр d задается точностью (по умолчанию равно 6 )
g, G double; использует %e или %E, если порядок меньше, чем -4, или больше или равен точности; в противном случае использует %f. Завершающие нули и завершающая десятичная точка не печатаются
p void *; указатель (представление зависит от реализации)
% Аргумент не преобразуется; печатается знак %

Вопрос 39

Основные функции, используемые при работе с текстовыми файлами (открытие, закрытие, чтение, запись и пр.) в библиотеке stdio языка программирования Си.

Обращение к fopen в программе может выглядеть следующим образом:

fp = fopen(name, mode);

Первый аргумент - строка, содержащая имя файла. Второй аргумент несет информацию о режиме. Это тоже строка: в ней указывается, каким образом пользователь намерен применять файл. Возможны следующие режимы: чтение (read - "r"), запись (write - "w") и добавление (append - "a"), т. е. запись информации в конец уже существующего файла. В некоторых системах различаются текстовые и бинарные файлы; в случае последних в строку режима необходимо добавить букву "b" (binary - бинарный).

Следующее, что нам необходимо знать, - это как читать из файла или писать в файл, коль скоро он открыт. Существует несколько способов сделать это, из которых самый простой состоит в том, чтобы воспользоваться функциями getc и putc . Функция getc возвращает следующий символ из файла; ей необходимо сообщить указатель файла, чтобы она знала откуда брать символ.

int getc(FILE *fp);

Функция getc возвращает следующий символ из потока, на который указывает *fp ; в случае исчерпания файла или ошибки она возвращает EOF.

Функция putc пишет символ c в файл fp

int putc(int с, FILE *fp);

и возвращает записанный символ или EOF в случае ошибки

При запуске Си-программы операционная система всегда открывает три файла и обеспечивает три файловые ссылки на них. Этими файлами являются: стандартный ввод, стандартный вывод и стандартный файл ошибок; соответствующие им указатели называются stdin , stdout и stderr ; они описаны в . Обычно stdin соотнесен с клавиатурой, а stdout и stderr - с экраном. С помощью getc , putc , stdin и stdout функции getchar и putchar теперь можно определить следующим образом:

#define getchar() getc(stdin)

#define putchar(c) putc((c), stdout)

Форматный ввод-вывод файлов можно построить на функциях fscanf и fprintf . Они идентичны scanf и printf с той лишь разницей, что первым их аргументом является указатель на файл, для которого осуществляется ввод-вывод, формат же указывается вторым аргументом.

int fscanf(FILE *fp, char *format, ...)

int fprintf(FILE *fp, char *format, ...)

Вопрос 40

Основные функции, используемые при работе со строками в библиотеке string,stdio,stlib языка программирования Си.

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

char *fgets(char *line, int maxline, FILE *fp)

Функция fgets читает следующую строку ввода (включая и символ новой строки) из файла fp в массив символов line , причем она может прочитать не более MAXLINE-1 символов. Переписанная строка дополняется символом "\0". Обычно fgets возвращает line , а по исчерпании файла или в случае ошибки - NULL. (Наша getline возвращала длину строки, которой мы потом пользовались, и нуль в случае конца файла.)

Функция вывода fputs пишет строку (которая может и не заканчиваться символом новой строки) в файл.

int fputs(char *line, FILE *fp)

Эта функция возвращает EOF, если возникла ошибка, и неотрицательное значение в противном случае.

Библиотечные функции gets и puts подобны функциям fgets и fputs . Отличаются они тем, что оперируют только стандартными файлами stdin и stdout , и кроме того, gets выбрасывает последний символ "\n", a puts его добавляет.

char *strcpy(s,ct) копирует строку ct в строку s , включая "\0"; возвращает s
char *strncpy(s,ct,n) копирует не более n символов строки ct в s ; возвращает s . Дополняет результат символами "\0", если символов в ct меньше n
char *strcat(s,ct) приписывает ct к s ; возвращает s
char *strncat(s,ct,n) приписывает не более n символов ct к s , завершая s символом "\0"; возвращает s
char strcmp(cs,st) сравнивает cs и ct ; возвращает <0, если cs0, если cs>ct (I.B.: вообще-то, функция возвращает int )
char strncmp(cs,ct) сравнивает не более n символов cs и ct ; возвращает <0, если cs0, если cs>ct (I.B.: тоже int должна возвращать )
char *strchr(cs,c) c в cs
char *strrchr(cs,c) возвращает указатель на последнее вхождение c в cs или, если такового не оказалось, NULL
size_t strspn(cs,ct) cs , состоящего из символов, входящих в строку ct
size_t strcspn(cs,ct) возвращает длину начального сегмента cs , состоящего из символов, не входящих в строку ct
char *strpbrk(cs,ct) возвращает указатель в cs на первый символ, который совпал с одним из символов, входящих в ct , или, если такового не оказалось, NULL
char *strstr(cs, ct) возвращает указатель на первое вхождение ct в cs или, если такового не оказалось, NULL
size_t strlen(cs) возвращает длину cs
char * strerror(n) возвращает указатель на зависящую от реализации строку, соответствующую номеру ошибки n
char * strtok(s, ct) strtok ищет в s лексему, ограниченную символами из ct ; более подробное описание этой функции см. ниже

Функции mem... предназначены для манипулирования с объектами как с массивами символов; их назначение - получить интерфейсы к эффективным программам. В приведенной ниже таблице s и t принадлежат типу void * ; cs и ct - типу const void * ; n - типу size_t ; а c имеет значение типа int , приведенное к типу char .

double strtod(const char *s, char **endp)

strtod s в double *endp (если endp не NULL), при переполнении она выдает HUGE_VAL с соответствующим знаком, в случае, если результат оказывается меньше, чем возможно представить данным типом, возвращается 0; в обоих случаях в errno устанавливается ERANGE .

long strtol(const char *s, char **endp, int base)

strtol преобразует первые символы строки s в long , игнорируя начальные символы-разделители; запоминает указатель на непреобразованный конец в *endp (если endp не NULL). Если base находится в диапазоне от 2 до 36, то преобразование делается в предположении, что на входе - запись числа по основанию base . Если base равно нулю, то основанием числа считается 8, 10 или 16; число, начинающееся с цифры 0, считается восьмеричным, а с 0x или 0X - шестнадцатеричным. Цифры от 10 до base-1 записываются начальными буквами латинского алфавита в любом регистре. При основании, равном 16, в начале числа разрешается помещать 0x или 0X. В случае переполнения функция возвращает LONG_MAX или LONG_MIN (в зависимости от знака), a в errno устанавливается ERANGE.

unsigned long strtoul(const char *s, char **endp, int base)

strtoul работает так же, как и strtol , с той лишь разницей, что возвращает результат типа unsigned long , а в случае переполнения - ULONG_MAX.

Спс Грише за последние 4 вопроса


Похожая информация.


Последнее обновление: 31.10.2015

Паттерн Прототип (Prototype) позволяет создавать объекты на основе уже ранее созданных объектов-прототипов. То есть по сути данный паттерн предлагает технику клонирования объектов.

Когда использовать Прототип?

    Когда конкретный тип создаваемого объекта должен определяться динамически во время выполнения

    Когда нежелательно создание отдельной иерархии классов фабрик для создания объектов-продуктов из параллельной иерархии классов (как это делается, например, при использовании паттерна Абстрактная фабрика)

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

На языке UML отношения между классами при применении данного паттерна можно описать следующим образом:

Формальная структура паттерна на C# могла бы выглядеть следующим образом:

Class Client { void Operation() { Prototype prototype = new ConcretePrototype1(1); Prototype clone = prototype.Clone(); prototype = new ConcretePrototype2(2); clone = prototype.Clone(); } } abstract class Prototype { public int Id { get; private set; } public Prototype(int id) { this.Id = id; } public abstract Prototype Clone(); } class ConcretePrototype1: Prototype { public ConcretePrototype1(int id) : base(id) { } public override Prototype Clone() { return new ConcretePrototype1(Id); } } class ConcretePrototype2: Prototype { public ConcretePrototype2(int id) : base(id) { } public override Prototype Clone() { return new ConcretePrototype2(Id); } }

Участники

    Prototype : определяет интерфейс для клонирования самого себя, который, как правило, представляет метод Clone()

    ConcretePrototype1 и ConcretePrototype2 : конкретные реализации прототипа. Реализуют метод Clone()

    Client : создает объекты прототипов с помощью метода Clone()

Рассмотрим клонирование на примере фигур - прямоугольников и кругов:

Class Program { static void Main(string args) { IFigure figure = new Rectangle(30,40); IFigure clonedFigure = figure.Clone(); figure.GetInfo(); clonedFigure.GetInfo(); figure = new Circle(30); clonedFigure=figure.Clone(); figure.GetInfo(); clonedFigure.GetInfo(); Console.Read(); } } interface IFigure { IFigure Clone(); void GetInfo(); } class Rectangle: IFigure { int width; int height; public Rectangle(int w, int h) { width = w; height = h; } public IFigure Clone() { return new Rectangle(this.width, this.height); } public void GetInfo() { Console.WriteLine("Прямоугольник длиной {0} и шириной {1}", height, width); } } class Circle: IFigure { int radius; public Circle(int r) { radius = r; } public IFigure Clone() { return new Circle(this.radius); } public void GetInfo() { Console.WriteLine("Круг радиусом {0}", radius); } }

Здесь в качестве прототипа используется интерфейс IFigure, который реализуется классами Circle и Rectangle.

Но в данном случае надо заметить, что фреймворк.NET предлагает функционал для копирования в виде метода MemberwiseClone() . Например, мы могли бы изменить реализацию метода Clone() в классах прямоугольника и круга следующим образом:

Public IFigure Clone() { return this.MemberwiseClone() as IFigure; }

Причем данный метод был бы общим для обоих классов. И работа программы никак бы не изменилась.

В то же время надо учитывать, что метод MemberwiseClone() осуществляет неполное копирование - то есть копирование значимых типов. Если же класс фигуры содержал бы объекты ссылочных типов, то оба объекта после клонирования содержали бы ссылку на один и тот же ссылочный объект. Например, пусть фигура круг имеет свойство ссылочного типа:

Class Point { public int X { get; set; } public int Y { get; set; } } class Circle: IFigure { int radius; public Point Point { get; set; } public Circle(int r, int x, int y) { radius = r; this.Point = new Point { X = x, Y = y }; } public IFigure Clone() { return this.MemberwiseClone() as IFigure; } public void GetInfo() { Console.WriteLine("Круг радиусом {0} и центром в точке ({1}, {2})", radius, Point.X, Point.Y); } }

В этом случае при изменении значений в свойстве Point начальной фигуры автоматически бы изменилось соответствующее значение и у клонированной фигуры:

Circle figure = new Circle(30, 50, 60); Circle clonedFigure=figure.Clone() as Circle; figure.Point.X = 100; // изменяем координаты начальной фигуры figure.GetInfo(); // figure.Point.X = 100 clonedFigure.GetInfo(); // clonedFigure.Point.X = 100

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

Using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; //........................ class Program { static void Main(string args) { Circle figure = new Circle(30, 50, 60); // применяем глубокое копирование Circle clonedFigure=figure.DeepCopy() as Circle; figure.Point.X = 100; figure.GetInfo(); clonedFigure.GetInfo(); Console.Read(); } } //......................... class Point { public int X { get; set; } public int Y { get; set; } } class Circle: IFigure { int radius; public Point Point { get; set; } public Circle(int r, int x, int y) { radius = r; this.Point = new Point { X = x, Y = y }; } public IFigure Clone() { return this.MemberwiseClone() as IFigure; } public object DeepCopy() { object figure = null; using (MemoryStream tempStream = new MemoryStream()) { BinaryFormatter binFormatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone)); binFormatter.Serialize(tempStream, this); tempStream.Seek(0, SeekOrigin.Begin); figure = binFormatter.Deserialize(tempStream); } return figure; } public void GetInfo() { Console.WriteLine("Круг радиусом {0} и центром в точке ({1}, {2})", radius, Point.X, Point.Y); } }

Чтобы вручную не создавать у клонированного объекта вложенный объект Point, здесь используются механизмы бинарной сериализации. И в этом случае все классы, объекты которых подлежат копированию, должны быть помечены атрибутом Serializable.