Прототип функции c. Использование прототипов функции. Что будем делать с полученным материалом

Faq 08.04.2019
Faq

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

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

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

Использование прототипа называют еще объявлением функции или просто ее 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 типа int eger содержит в себе количество аргументов командной строки.

Аргумент 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.

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

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

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

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

Если прототип функции не задан, а встретился вызов функции, то строится неявный прототип из анализа формы вызова функции. Тип возвращаемого значения создаваемого прототипа 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.

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

Для чего нужны функции в C?

Функции в Си применяются для выполнения определённых действий в рамках общей программы. Программист сам решает какие именно действия вывести в функции. Особенно удобно применять функции для многократно повторяющихся действий.

Простой пример функции в Cи

Пример функции в Cи:

#include #include int main(void) { puts("Functions in C"); return EXIT_SUCCESS; }

Это очень простая программа на Си. Она просто выводит строку «Functions in C». В программе имеется единственная функция под названием main. Рассмотрим эту функцию подробно. В заголовке функции, т.е. в строке

int – это тип возвращаемого функцией значения;

main - это имя функции;

(void) - это перечень аргументов функции. Слово void указывает, что у данной функции нет аргументов;

return – это оператор, который завершает выполнение функции и возвращает результат работы функции в точку вызова этой функции;

EXIT_SUCCESS - это значение, равное нулю. Оно определено в файле stdlib.h;

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

{
puts("Functions in C");
return EXIT_SUCCESS;
}

называют телом функции.

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

Как из одной функции в Cи вызвать другую функцию?

Рассмотрим пример вызова функций в Си:

/* Author: @author Subbotin B.P..h> #include int main(void) { puts("Functions in C"); int d = 1; int e = 2; int f = sum(d, e); printf("1 + 2 = %d", f); return EXIT_SUCCESS; }

Запускаем на выполнение и получаем:

В этом примере создана функция sum, которая складывает два целых числа и возвращает результат. Разберём подробно устройство этой функции.

Заголовок функции sum:

int sum(int a, int b)

здесь int - это тип возвращаемого функцией значения;

sum - это имя функции;

(int a, int b) - в круглых скобках после имени функции дан перечень её аргументов: первый аргумент int a, второй аргумент int b. Имена аргументов являются формальными, т.е. при вызове функции мы не обязаны отправлять в эту функцию в качестве аргументов значения перемнных с именами a и b. В функции main мы вызываем функцию sum так: sum(d, e);. Но важно, чтоб переданные в функцию аргументы совпадали по типу с объявленными в функции.

В теле функции sum, т.е. внутри фигурных скобок после заголовка функции, мы создаем локальную переменную int c, присваиваем ей значение суммы a плюс b и возвращаем её в качестве результата работы функции опрератором return.

Теперь посмотрим как функция sum вызывается из функции main.

Вот функция main:

Int main(void) { puts("Functions in C"); int d = 1; int e = 2; int f = sum(d, e); printf("1 + 2 = %d", f); return EXIT_SUCCESS; }

Сначала мы создаём две переменных типа int

Int d = 1; int e = 2;

их мы передадим в функцию sum в качестве значений аргументов.

int f = sum(d, e);

её значением будет результат работы функции sum, т.е. мы вызываем функцию sum, которая возвратит значение типа int, его-то мы и присваиваем переменной f. В качестве аргументов передаём d и f. Но в заголовке функции sum

int sum(int a, int b)

аргументы называются a и b, почему тогда мы передаем d и f? Потому что в заголовке функций пишут формальные аргументы, т.е. НЕ важны названия аргументов, а важны их типы. У функции sum оба аргумента имеют тип int, значит при вызове этой функции надо передать два аргумента типа int с любыми названиями.

Ещё одна тонкость. Функция должна быть объявлена до места её первого вызова. В нашем примере так и было: сначала объявлена функция sum, а уж после мы вызываем её из функции main. Если функция объявляется после места её вызова, то следует использовать прототип функции.

Прототип функции в Си

Рассмотрим пример функциив Си:

/* Author: @author Subbotin B.P..h> #include int sum(int a, int b); int main(void) { puts("Functions in C"); int d = 1; int e = 2; int f = sum(d, e); printf("1 + 2 = %d", f); return EXIT_SUCCESS; } int sum(int a, int b) { int c = 0; c = a + b; return c; }

В этом примере функция sum определена ниже места её вызова в функции main. В таком случае надо использовать прототип функции sum. Прототип у нас объявлен выше функции main:

int sum(int a, int b);

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

int f = sum(d, e);

а ниже функции main определяем функцию sum, которая предварительно была объявлена в прототипе:

Int sum(int a, int b) { int c = 0; c = a + b; return c; }

Чем объявление функции в Си отличается от определения функции в Си?

Когда мы пишем прототип функции, например так:

int sum(int a, int b);

то мы объявляем функцию.

А когда мы реализуем функцию, т.е. записываем не только заголовок, но и тело функции, например:

Int sum(int a, int b) { int c = 0; c = a + b; return c; }

то мы определяем функцию.

Оператор return

Оператор return завершает работу функции в C и возвращает результат её работы в точку вызова. Пример:

Int sum(int a, int b) { int c = 0; c = a + b; return c; }

Эту функцию можно упростить:

Int sum(int a, int b) { return a + b; }

здесь оператор return вернёт значение суммы a + b.

Операторов return в одной функции может быть несколько. Пример:

Int sum(int a, int b) { if(a > 2) { return 0;// Первый случай; } if(b < 0) { return 0;// Второй случай; } return a + b; }

Если в примере значение аргумента a окажется больше двух, то функция вернет ноль (первый случай) и всё, что ниже комментария «// Первый случай;» выполнятся не будет. Если a будет меньше двух, но b будет меньше нуля, то функция завершит свою работу и всё, что ниже комментария «// Второй случай;» выполнятся не будет.

И только если оба предыдущих условия не выполняются, то выполнение программы дойдёт до последнего оператора return и будет возвращена сумма a + b.

Передача аргументов функции по значению

Аргументы можно передавать в функцию C по значению. Пример:

/* Author: @author Subbotin B.P..h> #include int sum(int a) { return a += 5; } int main(void) { puts("Functions in C"); int d = 10; printf("sum = %d\n", sum(d)); printf("d = %d", d); return EXIT_SUCCESS; }

В примере, в функции main, создаём переменную int d = 10. Передаём по значению эту переменную в функцию sum(d). Внутри функции sum значение переменной увеличивается на 5. Но в функции main значение d не изменится, ведь она была передана по значению. Это означает, что было передано значение переменной, а не сама переменная. Об этом говорит и результат работы программы:

т.е. после возврата из функции sum значеие d не изменилось, тогда как внутри функции sum оно менялось.

Передача указателей функции Си

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

/* Author: @author Subbotin B.P..h> #include int sum(int *a) { return *a += 5; } int main(void) { puts("Functions in C"); int d = 10; printf("sum = %d\n", sum(&d)); printf("d = %d", d); return EXIT_SUCCESS; }

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

printf("sum = %d\n", sum(&d));

в функцию sum передается не значение переменной d, равное 10-ти, а адрес этой переменной, вот так:

Теперь посмотрим на функцию sum:

Int sum(int *a) { return *a += 5; }

Аргументом её является указатель на int. Мы знаем, что указатель - это переменная, значением которой является адрес какого-то объекта. Адрес переменной d отправляем в функцию sum:

Внутри sum указатель int *a разыменовывается. Это позволяет от указателя перейти к самой переменной, на которую и указывает наш указатель. А в нашем случае это переменная d, т.е. выражение

равносильно выражению

Результат: функция sum изменяет значение переменной d:

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

C/C++ в Eclipse

Все примеры для этой статьи я сделал в Eclipse. Как работать с C/C++ в Eclipse можно посмотреть . Если вы работаете в другой среде, то примеры и там будут работать.

Стандарт 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().

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



Рекомендуем почитать

Наверх