русс | укр

Языки программирования

ПаскальСиАссемблерJavaMatlabPhpHtmlJavaScriptCSSC#DelphiТурбо Пролог

Компьютерные сетиСистемное программное обеспечениеИнформационные технологииПрограммирование

Все о программировании


Linux Unix Алгоритмические языки Аналоговые и гибридные вычислительные устройства Архитектура микроконтроллеров Введение в разработку распределенных информационных систем Введение в численные методы Дискретная математика Информационное обслуживание пользователей Информация и моделирование в управлении производством Компьютерная графика Математическое и компьютерное моделирование Моделирование Нейрокомпьютеры Проектирование программ диагностики компьютерных систем и сетей Проектирование системных программ Системы счисления Теория статистики Теория оптимизации Уроки AutoCAD 3D Уроки базы данных Access Уроки Orcad Цифровые автоматы Шпаргалки по компьютеру Шпаргалки по программированию Экспертные системы Элементы теории информации

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


Дата добавления: 2014-04-05; просмотров: 972; Нарушение авторских прав


Функции с переменным числом параметров

Одним из представителей подобных функций является, например, функция printf (), используемая в языках C и C++ для вывода данных:

 

void main()

{

int a = 10, b = 20;

char *str = "Это текст";

printf("a = %d, с = %s, b = %d", a, str, b);

}

 

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

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

1. При разработке тела функции необходимо использовать макросы для работы со списками аргументов функций. Для их использования в программу необходимо включить заголовочный файл <stdarg.h>:

 

#include <stdarg.h>

 

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

 

double Summa (int N, …)

 

Здесь в списке параметров присутствует один обязательный параметр N, за которым могут следовать необязательные параметры. Необязательные параметры должны всегда находиться в конце списка параметров.

3. В теле функции необходимо определить переменную для работы со списком параметров (тип данных этой переменной va_list определен в <stdarg.h>), значение которой инициализируют с помощью макроса va_start (он также принадлежит <stdarg.h>). Перед завершением работы функции необходимо (обязательно!) с помощью макроса va_end очистить стек программы от необязательных параметров функции:



 

{

va_list L; // Объявляем переменную для списка параметров

va_start (L, N); // Инициализируем переменную списка параметров

……….

va_end (L); // Очищаем стек

return ………;

}

 

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

4. Получение очередного параметра из списка необязательных параметров осуществляется с помощью макроса va_arg (), например, так:

 

double R = 0;

for (int I = 0; I < N; ++ I)

R += va_arg (L, double);

 

Параметрами макроса va_arg являются переменная списка параметров и тип данных очередного параметра из списка.

 

Приведенные выше “заготовки” приводят нас к функции, выполняющей суммирование N вещественных значений:

 

#include <iostream>

#include <stdio.h>

#include <stdarg.h>

 

using namespace std;

 

double Summa (int N, …)

{

va_list L; // Объявляем переменную для списка параметров

va_start (L, N); // Инициализируем переменную списка параметров

double R = 0;

for (int I = 0; I < N; ++ I)

R += va_arg (L, double); // Добавляем к R очередной аргумент

va_end (L); // Очищаем стек

return R;

}

 

int main ()

{

cout << Summa (3, 10.0, 20.0, 30.0) << endl; // 60

cout << Summa (5, 10.0, 20.0, 30.0, 40.0, 50.0) << endl; // 150

return 0;

}

Рекурсивное использование функций

Функции внутри своего тела могут вызывать сами себя. Такой вызов называется рекурсией. На основе рекурсии можно строить очень интересные алгоритмы обработки данных.

Рассмотрим функцию возведения вещественного значения D в целую положительную степень P. Очевидная реализация этой функции основана на использовании цикла:

 

double Pow (double D, unsigned P)

{

double R = 1;

for (unsigned I = 0; I < P; ++ I)

R *= D;

return R;

}

 

А вот та же самая функция, реализованная на основе рекурсии:

 

double Pow (double D, unsigned P)

{

if (P)

return Pow ( D, P – 1 ) * D;

else

return 1;

}

 

Основанием для рекурсии в этом примере послужило следующее рекуррентное соотношение:

 
приP = 0 приP > 0  

Более сложный пример рекурсии основан на следующем рекуррентном соотношении (обычная реализация этого примера приведена в разделе 5.1 конспекта):

 
приn = 0 приn = 1 приn ≥ 2

Через рекурсию это рекуррентное соотношение реализуется так:

 

double T (unsigned n, double x)

{

switch (n)

{

case 0:

return 1;

case 1:

return x;

default:

return 2 * x * T (n - 1, x) - T (n - 2, x);

}

}

 

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

Однако в ряде случаев использование рекурсии позволяет достичь существенного положительного эффекта.

Рассмотрим следующий пример: необходимо написать функцию для вычисления биномиальных коэффициентов:

n >= 0, 0 <= m <= n.

Значение биномиального коэффициента определяет число различных вариантов выбора m объектов из n объектов (как говорят, число сочетаний из n по m).

Основные свойства биномиальных коэффициентов:

 

Максимальное значение биномиального коэффициента достигается при m = n / 2.

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

 

unsigned C(int n, int m)

{

if (m > n / 2)

m = n - m;

unsigned a = 1, b = 1;

for (int i = n; i >= n - m + 1; -- i)

a *= i;

for (int i = 1; i <= m; ++ i)

b *= i;

return a / b;

}

 

Нетрудно заметить, что этот алгоритм быстро приводит к выходу значений числителя или знаменателя за пределы диапазона значений типа данных unsigned. И действительно, эксперименты с этим вариантом функции показывают, что точное вычисление биномиальных коэффициентов возможно только при n < 17.

Найдем следующее соотношение:

.

 

То есть:

.

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

 

 
приm = 0 приm > 0

 

Это рекуррентное соотношение реализуется с помощью следующей рекурсивной функции:

 

unsigned int C(unsigned int n, unsigned int m)

{

if (m > n / 2)

m = n - m;

if (m == 0)

return 1;

else

return C(n, m - 1) * (n - m + 1) / m ;

}

 

Этот вариант функции позволяет точно вычислять значения биномиальных коэффициентов при n < 31. Диапазон решения расширен почти в два раза.

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

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

 

cout << C << endl; // C – это функция из предыдущего параграфа

 

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

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

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

Рассмотрим пример:

 

double add (double a, double b)

{

return a + b;

}

 

double mul (double a, double b)

{

return a * b;

}

 

typedef double (*f_Ptr) (double, double);

 

double oper (f_Ptr F, double a, double b)

{

return F (a, b);

}

 

int main ()

{

cout << oper (add, 20, 30) << endl; // 50

cout << oper (mul, 20, 30) << endl; // 600

return 0;

}

 

В этом примере имеется некоторая универсальная функция oper, возвращающая значение вычисленной функции F для заданных значений a и b. Функция F, которую необходимо вычислить, передается в функцию oper через параметр этой функции. Ключевые элементы этого алгоритма выделены красным цветом. Для того, чтобы параметр F функции oper мог принимать в качестве аргумента другую функцию, с помощью инструкции typedef определяется тип данных указателя f_Ptr на функции, возвращающие значение типа double и имеющие два параметра типа double.

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

Несколько слов об инструкции typedef. С помощью этой инструкции можно определять новые типы данных и переопределять уже имеющиеся. Например: довольно утомительно в тексте программы многократно использовать название типа данных unsigned short. С помощью инструкции typedef можно упростить набор текста программы:

 

typedef unsigned short UnS;

 

Теперь везде в тексте программы можно использовать вместо типа unsigned short тип UnS.

Однако увлекаться такими “переименованиями” не следует. В основном инструкция typedef используется при определении новых пользовательских типов данных.

Встраиваемые функции (inline - функции)

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

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

 

inline int ReadInt(char *S) // Ввод целых значений по текстовому запросу S

{

cout << S;

int I;

cin >> I;

return I;

}

 

{

……..

int Vozrast = ReadInt (“Сколько Вам лет? ”);

……..

int Kurs = ReadInt (“На каком курсе Вы учитесь? ”);

……..

int Ocenka = ReadInt (“Сколько у Вас отличных оценок в последней сессии? ”);

……..

}

 

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

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

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

· рекурсии;

· циклов, переключателей, инструкций goto;

· статических (static) переменных.

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

Прототипы функций

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

 

double F (int P1, double P2 ); // Это прототип функции F

 

double F (int P1, double P2 ) // А это сама функция F

{

return P1 * P2;

}

 

В прототипе функции допускается не указывать имена параметров (типы параметров должны быть указаны обязательно). Например, прототип той же функции можно записать так:

 

double F (int, double); // И это прототип функции F

 

Назначение прототипов – это опережающее описание функции, определяющее правила вызова функции.

Обычно прототипы функций используются в заголовочных файлах.

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

 

void A ()

{

……

B ();

……

}

 

void B ()

{

……

A ();

……

}

 

Это пример, так называемого, “перекрестного” вызова функций (функция А вызывает функцию В, а функция В вызывает функцию А). В этом случае конфликт может быть разрешен с помощью использования прототипа функции B:

 

void B ();

 

void A ()

{

……

B ();

……

}

 

void B ()

{

……

A ();

……

}

 

Теперь функция А “знает”, что представляет собой функция В и как ее вызвать.

9.2. Структура программы. Глобальные и локальные данные (области видимости и время жизни)

Структура программы

Любая программа на языке C++ представляет собой одну или множество функций. Среди этих функций должна обязательно присутствовать главная функция main (), являющаяся точкой входа в программу (выполнение программы начинается с выполнения этой функции).

Основное требование по использованию функций состоит в том, что определение любой функции должно предшествовать ее вызову (компилятору необходимо “знать”, как вызвать ту или иную функцию: какое значение она возвращает, что собой представляют ее параметры). Возникновение коллизий, связанных с этим правилом, разрешается использованием прототипов функций.

В языке C++ отсутствует понятие вложенных функций, то есть внутри определять другие функции нельзя.

Глобальные и локальные данные

В языках программирования очень большое значение имеют понятия область видимости и время жизни объектов программы.

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

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

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

Составной оператор;



<== предыдущая лекция | следующая лекция ==>
При обращении внутри функции к значению аргумента через параметр-указатель необходимо осуществить разыменование этого указателя. | Определение функции.


Карта сайта Карта сайта укр


Уроки php mysql Программирование

Онлайн система счисления Калькулятор онлайн обычный Инженерный калькулятор онлайн Замена русских букв на английские для вебмастеров Замена русских букв на английские

Аппаратное и программное обеспечение Графика и компьютерная сфера Интегрированная геоинформационная система Интернет Компьютер Комплектующие компьютера Лекции Методы и средства измерений неэлектрических величин Обслуживание компьютерных и периферийных устройств Операционные системы Параллельное программирование Проектирование электронных средств Периферийные устройства Полезные ресурсы для программистов Программы для программистов Статьи для программистов Cтруктура и организация данных


 


Не нашли то, что искали? Google вам в помощь!

 
 

© life-prog.ru При использовании материалов прямая ссылка на сайт обязательна.

Генерация страницы за: 0.101 сек.