Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Программирование на языке C
Лабораторная работа 3
Вызовы функций по указателю и функции с переменным числом параметров на языке C
Цель: Научиться работать с указателями, использовать их при вызове функций и разрабатывать и использовать функции с переменным числом параметров на языке C.
При вызове функции с переменным числом параметров в вызове этой функции задается любое требуемое число аргументов. В объявлении и определении такой функции переменное число аргументов задается многоточием в конце списка формальных параметров или списка типов аргументов.
Все аргументы, заданные в вызове функции, размещаются в стеке. Количество формальных параметров, объявленных для функции, определяется числом аргументов, которые берутся из стека и присваиваются формальным параметрам. Программист отвечает за правильность выбора дополнительных аргументов из стека и определение числа аргументов, находящихся в стеке. При этом объявление функции будет иметь вид:
[модификатор] [тип-функции] имя-функции([список-обязательных-формальных-параметров,]…);
где троеточие … означает наличие необязательных параметров функции.
Примерами функций с переменным числом параметров являются функции из библиотеки функций языка СИ, осуществляющие операции ввода-вывода информации (printf,scanf и т.п.).
Программист может разрабатывать свои функции с переменным числом параметров. Для обеспечения удобного способа доступа к аргументам функции с переменным числом параметров имеются три макроопределения (макросы) va_start, va_arg, va_end, находящиеся в заголовочном файле stdarg.h. Эти макросы указывают на то, что функция, разработанная пользователем, имеет некоторое число обязательных аргументов, за которыми следует переменное число необязательных аргументов. Обязательные аргументы доступны через свои имена как при вызове обычной функции. Для извлечения необязательных аргументов используются макросы va_start, va_arg, va_end в следующем порядке.
Макрос va_start предназначен для установки аргумента arg_ptr на начало списка необязательных параметров и имеет вид функции с двумя параметрами:
void va_start(arg_ptr,prav_param);
Параметр prav_param должен быть последним обязательным параметром вызываемой функции, а указатель arg_prt должен быть объявлен с предопределением в списке переменных типа va_list в виде:
va_list arg_ptr;
Макрос va_start должен быть использован до первого использования макроса va_arg.
Макрокоманда va_arg обеспечивает доступ к текущему параметру вызываемой функции и тоже имеет вид функции с двумя параметрами
type_arg va_arg(arg_ptr,type);
Эта макрокоманда извлекает значение типа type по адресу, заданному указателем arg_ptr, увеличивает значение указателя arg_ptr на длину использованного параметра (длина type) и таким образом параметр arg_ptr будет указывать на следующий параметр вызываемой функции. Макрокоманда va_arg используется столько раз, сколько необходимо для извлечения всех параметров вызываемой функции.
Макрос va_end используется по окончании обработки всех параметров функции и устанавливает указатель списка необязательных параметров на ноль (NULL).
Рассмотрим применение этих макросов для обработки параметров функции, вычисляющей среднее значение произвольной последовательности целых чисел. Поскольку функция имеет переменное число параметров, будем считать концом списка значение равное -1. Поскольку в списке должен быть хотя бы один элемент, у функции будет один обязательный параметр.
Пример:
#include
int main()
{ int n;
int sred_znach(int,...);
n=sred_znach(2,3,4,-1);
/* вызов с четырьмя параметрами */
printf("n=%d",n);
n=sred_znach(5,6,7,8,9,-1);
/* вызов с шестью параметрами */
printf("n=%d",n);
return (0);
}
int sred_znach(int x,...);
{
int i=0, j=0, sum=0;
va_list uk_arg;
va_start(uk_arg,x); /* установка указателя uk_arg на */
/* первый необязательный параметр */
if (x!=-1) sum=x; /* проверка на пустоту списка */
else return (0);
j++;
while ( (i=va_arg(uk_arg,int))!=-1)
/* выборка очередного */
{ /* параметра и проверка */
sum+=i; /* на конец списка */
j++;
}
va_end(uk_arg); /* закрытие списка параметров */
return (sum/j);
}
Указатель - это адрес памяти, распределяемой для размещения идентификатора (в качестве идентификатора может выступать имя переменной, массива, структуры, строкового литерала). В том случае, если переменная объявлена как указатель, то она содержит адрес памяти, по которому может находится скалярная величина любого типа. При объявлении переменной типа указатель, необходимо определить тип объекта данных, адрес которых будет содержать переменная, и имя указателя с предшествующей звездочкой (или группой звездочек). Формат объявления указателя:
спецификатор-типа [ модификатор ] * описатель;
Спецификатор-типа задает тип объекта и может быть любого основного типа, типа структуры, объединения. Задавая вместо спецификатора-типа ключевое слово void, можно своеобразным образом отсрочить спецификацию типа, на который ссылается указатель. Переменная, объявляемая как указатель на тип void, может быть использована для ссылки на объект любого типа. Однако для того, чтобы можно было выполнить арифметические и логические операции над указателями или над объектами, на которые они указывают, необходимо при выполнении каждой операции явно определить тип объектов. Такие определения типов может быть выполнено с помощью операции приведения типов.
В качестве модификаторов при объявлении указателя могут выступать ключевые слова const, near, far, huge. Ключевое слово const указывает, что указатель не может быть изменен в программе. Размер переменной объявленной как указатель, зависит от архитектуры компьютера и от используемой модели памяти, для которой будет компилироваться программа. Указатели на различные типы данных не обязательно должны иметь одинаковую длину.
Для модификации размера указателя можно использовать ключевые слова near, far, huge.
Примеры:
unsigned int * a; /* переменная а представляет собой указатель
на тип unsigned int (целые числа без знака) */
double * x; /* переменная х указывает на тип данных с
плавающей точкой удвоенной точности */
char * fuffer ; /* объявляется указатель с именем fuffer
который указывает на переменную типа char */
double nomer;
void *addres;
addres = & nomer;
(double *)addres ++;
Здесь переменная addres объявлена как указатель на объект любого типа. Поэтому ей можно присвоить адрес любого объекта (& - операция вычисления адреса). Однако, как было отмечено выше, ни одна арифмитическая операция не может быть выполнена над указателем, пока не будет явно определен тип данных, на которые он указывает. Это можно сделать, используя операцию приведения типа (double *) для преобразования addres к указателю на тип double, а затем увеличение адреса.
const * dr;
Здесь переменная dr объявлена как указатель на константное выражение, т.е. значение указателя может изменяться в процессе выполнения программы, а величина, на которую он указывает, нет.
unsigned char * const w = &obj.
Здесь переменная w объявлена как константный указатель на данные типа char unsigned. Это означает, что на протяжение всей программы w будет указывать на одну и ту же область памяти. Содержание же этой области может быть изменено.
Общий вид объявления функции имеет вид:
[модификатор] [тип-функции] имя-функции([список-формальных-параметров]);
где модификатор модификатор типа объявляемой функции; тип-функции тип данных, возвращаемый функцией; список-формальных-параметров список формальных параметров, передаваемых функции при ее вызове.
При этом вызов функции имеет следующий вид:
адресное-выражение([список-выражений])
Поскольку синтаксически имя функции является адресом начала тела функции, в качестве обращения к функции может быть использовано адресное-выражение (в том числе и имя функции или разадресация указателя на функцию), имеющее значение адреса функции.
Список-выражений представляет собой список фактических параметров, передаваемых в функцию. Этот список может быть и пустым, но наличие круглых скобок обязательно.
Фактический параметр может быть величиной любого основного типа, структурой, объединением, перечислением или указателем на объект любого типа. Массив и функция не могут быть использованы в качестве фактических параметров, но можно использовать указатели на эти объекты.
Адресное выражение, стоящее перед скобками определяет адрес вызываемой функции. Это значит что функция может быть вызвана через указатель на функцию.
Пример:
int (*fun)(int x, int *y);
Здесь объявлена переменная fun как указатель на функцию с двумя параметрами: типа int и указателем на int. Сама функция должна возвращать значение типа int. Круглые скобки, содержащие имя указателя fun и признак указателя *, обязательны, иначе запись
int *fun (intx,int *y);
будет интерпретироваться как объявление функции fun возвращающей указатель на int.
Вызов функции возможен только после инициализации значения указателя fun и имеет вид:
(*fun)(i,&j);
В этом выражении для получения адреса функции, на которую ссылается указатель fun используется операция разадресации *.
Указатель на функцию может быть передан в качестве параметра функции. При этом разадресация происходит во время вызова функции, на которую ссылается указатель на функцию. Присвоить значение указателю на функцию можно в операторе присваивания, употребив имя функции без списка параметров.
Пример:
double (*fun1)(int x, int y);
double fun2(int k, int l);
fun1=fun2; /* инициализация указателя на функцию */
(*fun1)(2,7); /* обращение к функции */
В рассмотренном примере указатель на функцию fun1 описан как указатель на функцию с двумя параметрами, возвращающую значение типа double, и также описана функция fun2. В противном случае, т.е. когда указателю на функцию присваивается функция описанная иначе чем указатель, произойдет ошибка.
Рассмотрим пример использования указателя на функцию в качестве параметра функции вычисляющей производную от функции cos(x).
Пример:
double proiz(double x, double dx, double (*f)(double x) );
double fun(double z);
int main()
{
double x; /* точка вычисления производной */
double dx; /* приращение */
double z; /* значение производной */
scanf("%f,%f",&x,&dx); /* ввод значений x и dx */
z=proiz(x,dx,fun); /* вызов функции */
printf("%f",z); /* печать значения производной */
return 0;
}
double proiz(double x,double dx, double (*f)(double z) )
{ /* функция вычисляющая производную */
double xk,xk1,pr;
xk=fun(x);
xk1=fun(x+dx);
pr=(xk1/xk-1e0)*xk/dx;
return pr;
}
double fun( double z)
{ /* функция от которой вычисляется производная */
return (cos(z));
}
Для вычисления производной от какой-либо другой функции можно изменить тело функции fun или использовать при вызове функции proiz имя другой функции. В частности, для вычисления производной от функции cos(x) можно вызвать функцию proiz в форме
z=proiz(x,dx,cos);
а для вычисления производной от функции sin(x) в форме
z=proiz(x,dx,sin);
Составить программу обработки текста, считываемого из файла. Для чего разработать функцию для обработки текста с переменным числом параметров, в качестве параметров она должна принимать значения текстовых предложений (разделитель - .), строк (разделитель - \n) или слов (разделитель пробел или . , ! ? \n) (по варианту задания) для обработки и возвращать указатель на обработанный текст. В качестве первого параметра имя функции (указатель), используемой для перевода символов из одного формата в другой, которую определить ниже по тексту программы. Данная функция должна вызываться через переданный указатель и принимать обрабатываемый(-ые) символ(ы), возвращая результирующий. Обработанный текст вывести в результирующий файл. В отчете привести исходный и обработанный текст.
Вариант задания рассчитывается по номеру студента в журнале преподавателя.
Вариант |
Функция с переменным числом параметров получает |
Функция обработки символа |
Вариант |
Функция с переменным числом параметров получает |
Функция обработки символа |
1 |
Строки |
Изменение регистра на противоположный (рус) |
15 |
Слова |
Изменение регистра на противоположный (англ) |
2 |
Слова |
Исправление неверной раскладки (с рус на англ) |
16 |
Строки |
Исправление неверной раскладки (с англ на рус) |
3 |
Предложения |
Все буквы прописные (рус) |
17 |
Строки |
Все буквы прописные (англ) |
4 |
Слова |
Все буквы строчные (англ) |
18 |
Предложения |
Все буквы строчные (рус) |
5 |
Строки |
Все строки с загл. Буквы |
19 |
Строки |
Все строки с мал. Буквы |
6 |
Предложения |
Замена всех гласных (рус) на * |
20 |
Предложения |
Замена всех согласных (рус) на # |
7 |
Строки |
Замена всех загл. (рус) на ~ |
21 |
Строки |
Замена всех загл. (англ) на $ |
8 |
Слова |
Замена всех гласных (англ) на $ |
22 |
Слова |
Замена всех согласных (англ) на $ |
9 |
Предложения |
Замена более двух подряд повторов символов на ^ |
23 |
Предложения |
Замена двух и более загл. Символов (рус) на * |
10 |
Слова |
Слова с загл. буквы |
24 |
Слова |
Слова с мал. буквы |
11 |
Строки |
Замена двух и более загл. Символов (англ) на $ |
25 |
Строки |
Строки нач. с мал. буквы, все остальные большие (рус) |
12 |
Предложения |
Замена всех цифр на буквы: 0 а, 1 б… |
26 |
Предложения |
Замена всех загл. (рус) на ~ |
13 |
Строки |
Исправление ошибочного нажатия Shift при введении цифр |
27 |
Строки |
Строки нач. с мал. буквы, все остальные большие (англ) |
14 |
Слова |
Слова нач. с мал. буквы, все остальные большие (укр) |
28 |
Слова |
Слова нач. с мал. буквы, все остальные большие (англ) |
Содержание отчета и литература. Содержание отчета и список рекомендуемой литературы совпадает с указанными в указаниям к лабораторной работе 1.