Сайт о телевидении

Сайт о телевидении

» » Использование прототипов функции. Прототипы функций

Использование прототипов функции. Прототипы функций


В современных, правильно написанных программах на языке С каждую функцию перед использованием необходимо объявлять. Обычно это делается с помощью прототипа функции . В первоначальном варианте языка С прототипов не было; но они были введены уже в Стандарт С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() . Заголовки для стандартных функций описаны в

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

[СпецификаторКлассаПамяти] [СпецификаторТипа] ИмяФункции ([СписокФормальныхПараметров]) [,СписокИменФункций];

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

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

Если прототип функции не задан, а встретился вызов функции, то строится неявный прототип из анализа формы вызова функции. Тип возвращаемого значения создаваемого прототипа int, а список типов и числа параметров функции формируется на основании типов и числа фактических параметров, используемых при данном вызове.

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

1. Функция возвращает значение типа, отличного от int.

2. Требуется проинициализировать некоторый указатель на функцию до того, как эта функция будет определена.

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

В прототипе можно указать, что число параметров функции переменно, или что функция не имеет параметров.

Если прототип задан с классом памяти static, то и определение функции должно иметь класс памяти static. Если спецификатор класса памяти не указан, то подразумевается класс памяти extern.

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

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


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

int rus (unsigned char r); или rus (unsigned char);

#include

//прототип функции

void change (int &x, int &y);

//описание самой функции

void change (int &x, int &y)

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

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

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

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

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

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

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

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

Пример описания функции 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 тип новое_имя; Вве

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

Последнее обновление: 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.

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

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

Пример [ | ]

В качестве примера, рассмотрим следующий прототип функции:

int foo (int n );

Этот прототип объявляет функцию с именем «foo», которая принимает один аргумент «n» целого типа и возвращает целое число. Определение функции может располагаться где угодно в программе, но объявление требуется только в случае её использования.

Использование [ | ]

Уведомление компилятора [ | ]

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

#include /* * При реализации этого прототипа компилятор выдаст сообщение об ошибке * в main(). Если он будет пропущен, то и сообщения об ошибке не будет. */ int foo (int n ); /* Прототип функции */ int main (void ) /* Вызов функции */ { printf ("%d \n " , foo ()); /* ОШИБКА: у foo отсутствует аргумент! */ return 0 ; } int foo (int n ) /* Вызываемая функция */ { if (n == 0 ) return 1 ; else return n * foo (n - 1 ); }

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