- •1. Модель компьютера для программиста
- •2.Программное обеспечение. Компиляторы и интерпретаторы
- •3. Создание программ. Жизненный цикл программного обеспечения.
- •4. Транслятор и компоновщик. Библиотека языка Си.
- •5. Базовые элементы языка Си. Алфавит и словарь языка.
- •6. Правила образования идентификаторов. Ключевые слова и имена. Символы операций и разделителей.
- •7. Литералы. Структура предложений в Си.
- •9.Константы и переменные в Си
- •10. Типы данных и их роль в языке Си
- •11. Стандартные скалярные типы данных
- •11.Массивы
- •Структуры
- •14 .Арифметические выражения и операции
- •15. Выражение
- •16. Выражения присваивания.
- •17. Условные и логические операции.
- •18. Выражения с указателями
- •19. Постфиксное выражение
- •21.Приоритет операций и скобочная структура выражений
- •22. Структура программы на Си
- •23. Базовые операторы языка Си
- •24. Условные операторы языка Си.
- •25. Оператор Switch . Организация множественного выбора.
- •26. Оператор return
- •27. Операторы цикла
- •28. Оператор continue как средство управления циклом
- •30. Указатели
- •31. Массивы. Синтаксис объявления.
- •32. Основные свойства массивов
- •Массив и константный указатель
- •34. Символьная строка и массив символов
- •37. Многомерный массив
- •38. Многомерные массивы и указатели
- •39. Директива процессора #define
- •40. Директива #include
- •Предварительная инициализация параметров функции
- •Предварительная инициализация параметров функции 2
- •43. Вызовы функций
- •44. Массивы и параметры
- •48. Объявление функции
- •50. Преобразование основных типов.
- •51. Тип функции
- •52. Стандартные функции библиотеки Си
43. Вызовы функций
Вызов функции имеет следующий формат:
Выражение ([список-выражений])
Выражение вычисляется как адрес функции. Список-выражений представляет собой список фактических аргументов, передаваемых в функцию. Он может быть пустым.
Выполнение вызова функции происходит следующим образом:
1. вычисляются выражения в списке выражений и производятся обычные арифметические преобразования. Затем, если известен прототип функции, тип результирующего аргумента сравнивается с типом функции, тип результирующего аргумента сравнивается с типом соответствующего формального параметра. Если они не совпадают, то либо производиться преобразование типов, либо выдается диагностическое сообщение.
Число выражений в списке выражений должно совпадать с числом формальных параметров, если только прототип функции или определение не опирается явно переменное число аргументов. В этом случае компилятор проверяет сколько аргументов, сколько имен типов имеется в списке формальных параметров и преобразует их, если необходимо, по описанным правилам.
Если в прототипе функции вместо списка формальных параметров задано ключевое слово void, это значит, что в функцию не передается никаких аргументов и в определении функции не должно быть формальных параметров. Если это не так, то выдается диагностическое сообщение.
2. Происходит замена формальных параметров на фактические. Первое выражение в списке всегда соответствует первому формальному параметру, второе – второму и т.д.
3. Управление передается на первый оператор функции.
4. Выполнение оператора return в теле функции возвращает управление и, возможно, значение в вызывающую функцию. Если оператор return не задан, то управление возвращается после выполнения последнего оператора тела функции. При этом возвращаемое значение не определено.
44. Массивы и параметры
Массивы являющиеся параметром функции можно задать двумя способами:
1. для параметра в виде массива с фиксированным размером указывается число элементов массива при объявлении параметра;
2. для параметра в виде массива с одним и тем же типом данных, но различным числом элементов используются квадратные скобки, следующие за именем массива.
Многомерные динамические массивы
ногомерный массив в C++ по своей сути одномерен. Операции new[] и delete[] позволяют создавать и удалять динамические массивы, поддерживая при этом иллюзию произвольной размерности. Деятельность по организации динамического массива требует дополнительного внимания, которое окупается важным преимуществом: характеристики массива (операнды операции new) могут не быть константными выражениями. Это позволяет создавать многомерные динамические массивы произвольной конфигурации. Следующий пример иллюстрирует работу с динамическими массивами.
#include <iostream.h>
int fdArr(int **, int, int);
int fdArr(int ***, int, int, int);
// Одноимённые функции. Различаются списками списками параметров.
// Это так называемые перегруженные функции. О них позже.
void main()
{
int i, j;
/* Переменные (!) для описания характеристик массивов.*/
int dim1 = 5, dim2 = 5, dim3 = 10, wDim = dim2;
/*
Организация двумерного динамического массива производится в два этапа.
Сначала создаётся одномерный массив указателей, а затем каждому элементу
этого массива присваивается адрес одномерного массива. Для характеристик
размеров массивов не требуется константных выражений.
*/
int **pArr = new int*[dim1];
for (i = 0; i < dim1; i++) pArr[i] = new int[dim2];
pArr[3][3] = 100;
cout << pArr[3][3] << endl;
fdArr(pArr,3,3);
/*
Последовательное уничтожение двумерного массива…
*/
for (i = 0; i < dim1; i++) delete[]pArr[i];
delete[]pArr;
/*
Организация двумерного "треугольного" динамического массива. Сначала
создаётся одномерный массив указателей, а затем каждому элементу этого
массива присваивается адрес одномерного массива. При этом размер
(количество элементов) каждого нового массива на единицу меньше
размера предыдущего. Заключённая в квадратные скобки переменная в
описателе массива, которая, в данном контексте, является операндом
операции new, позволяет легко сделать это.
*/
int **pXArr = new int*[dim1];
for (i = 0; i < dim1; i++, wDim--) pXArr[i] = new int[wDim];
pXArr[3][3] = 100;
cout << pArr[3][3] << endl;
fdArr(pXArr,3,3);
/*
Последовательное уничтожение двумерного массива треугольной конфигурации…
*/
for (i = 0; i < dim1; i++) delete[]pXArr[i];
delete[]pXArr;
/*
Создание и уничтожение трёхмерного массива требует дополнительной итерации.
Однако здесь также нет ничего принципиально нового.
*/
int ***ppArr;
ppArr = new int**[dim1];
for (i = 0; i < dim1; i++) ppArr[i] = new int*[dim2];
for (i = 0; i < dim1; i++)
{
for (j = 0; j < dim2; j++) ppArr[i][j] = new int[dim3];
}
ppArr[1][2][3] = 750; cout << ppArr[1][2][3] << endl; fdArr(ppArr,1,2,3);
for (i = 0; i < dim1; i++)
{
for (j = 0; j < dim2; j++) delete[]ppArr[i][j];
}
for (i = 0; i < dim1; i++) delete[]ppArr[i];
delete[] ppArr;
}
int fdArr(int **pKey, int index1, int index2)
{
cout << pKey[index1][index2] << endl;
}
int fdArr(int ***pKey, int index1, int index2, int index3)
{
cout << pKey[index1][index2][index3] << endl;
}
Функции с изменяемым списком параметров
Для решения задачи передачи неопределённого количества параметров C++ располагает также средствами объявления переменных списков параметров.
Вспомним несколько форм Бэкуса-Наура, определяющих синтаксис списка параметров в определении и прототипе функции.
СписокОбъявленийПараметров ::= [СписокОбъявленийПарам] [...]
::= СписокОбъявленийПарам, ...
СписокОбъявленийПарам ::= ОбъявлениеПараметра
::= [СписокОбъявленийПарам,] ОбъявлениеПараметра
Таким образом, список объявлений параметров может завершаться многоточием, отделённым запятой от списка объявлений параметров, этого многоточия в списке параметров может не быть, а возможно также, что кроме многоточия в списке параметров вовсе ничего нет.
Так вот это многоточие предупреждает транслятор о том, что определяемая или объявляемая функция может вызываться с произвольным списком параметров.
В этом случае количество и тип параметров становятся известны из списка выражений, определяющих значения параметров в выражении вызова функции.
Рассмотрим прототип и определение функции с переменным количеством параметров.
int PP(…);
int PP(…)
{
return 100;
}
Трансляция этого фрагмента кода не вызывает у транслятора никаких возражений. Многоточием в списке параметров он предупреждён о возможных неожиданностях.
Следующий фрагмент кода демонстрирует варианты выражений вызова функции PP().
int retVal;
retVal = PP();
retVal = PP(1,2 + retVal,3,4,5,25*2);
PP('z',25,17);
В ходе выполнения выражений вызова функций с переменным количеством параметров изменяется алгоритм формирования записи активации. Теперь он выглядит примерно так:
в стековом сегменте выделяется запись активации. Теперь размер записи активации зависит от количества и типа параметров в выражении вызова, (а не прототипа). Так что сначала нужно определить и запомнить общее количество и тип выражений, которые образуют список параметров в выражении вызова функции. Как и раньше, вычисление значений производится в процессе выполнения программы. Как и раньше, значение параметра может быть представлено любыми выражениями;
на основе анализа прототипов вызываемой функции, расположенных в области видимости вызывающей функции, определяются начальные значения параметров (если они имеются), которые и записываются в соответствующие области записи активации. Как мы увидим дальше, в функциях с переменным количеством параметров слева от многоточия всё же может находиться хотя бы один явным образом определённый параметр. В противном случае просто не будет возможности воспользоваться значениями параметров;
вычисляются значения выражений, которые образуют список параметров в выражении вызова. Поскольку вычисление значений производится в ходе выполнения программы, здесь также нет никаких ограничений на процесс определения значения выражений. Можно использовать любые значения, а также вызовы ранее объявленных функций;
элементам записи активации присваиваются вычисленные значения. При этом возможно, часть параметров, которым были присвоены значения умолчания (это всегда ближайшие к многоточию параметры), получит новые значения. В этом процессе не допускается неопределённых ситуаций. Либо элемент записи активации получает значение умолчания, либо ему присваивается значение при вызове. Нарушение порядка означивания, как и раньше, выявляется ещё на стадии трансляции программы;
в вызываемой функции всем параметрам, которые были указаны в списке параметров, присваиваются значения из записи активации. Для остальных (непостоянных и, естественно, безымянных) параметров выделяется дополнительная память. Эти параметры также получают свои значения из записи активации;
управление передаётся первому оператору функции. Означенные параметры используются как переменные с определёнными значениями. Доступ к безымянным параметрам, в силу того, что к ним невозможно обращение по имени, обеспечивается специальными алгоритмами.
Итак, параметрам вызываемой функции присвоены соответствующие значения, представленные в выражении вызова. Возникает вопрос, как воспользоваться этими значениями в теле вызываемой функции. Если у параметра существует собственное имя, то всё очевидно.
Если же параметр был определён как параметр без имени, то существует единственный способ доступа к таким параметрам - доступ с помощью указателей.
Дело в том, что все означенные параметры, с именами и безмянные, занимают одну непрерывную область памяти. Поэтому для доступа к элементам этого списка достаточно знать имя и тип хотя бы одного параметра. Для этого в функции определяется указатель, которому с помощью операции взятия адреса присваивается значение, которое соответствует адресу именованного параметра. Переход от параметра к параметру при этом обеспечивается с помощью операций адресной арифметики над значением этого указателя.
С точки зрения реализации всё очень просто. Если бы не одно обстоятельство, которое заметно ограничивает свободу применения подобных функций.
Дело в том, что всякий раз при создании функций с неопределённым количеством параметров, мы вынуждены разрабатывать алгоритм доступа к списку этих самых параметров. А для этого необходимо, по крайней мере, представлять закономерность расположения параметров в списке. Так что список необъявленных параметров не может состоять из подобранных случайным образом элементов, поскольку не существует универсальных средств распознавания элементов этого списка. На практике дело обычно ограничивается несколькими тривиальными вариантами.
При этом либо известен тип и количество передаваемых параметров, и процедура доступа к параметрам сводится к примитивному алгоритму, который воспроизводится в следующем примере:
#include <iostream.h>
long PP(int n, ...);
void main (void)
{
long RR;
RR = PP(5, 1, 2, 3, 4, 5 );
/*
Вызвали функцию с 6 параметрами. Единственный обязательный параметр
определяет количество передаваемых параметров.
*/
cout << RR << endl;
}
long PP(int n ...)
{
int *pPointer = &n;
// Настроились на область памяти с параметрами...
int Sum = 0;
for ( ; n; n--) Sum += *(++pPointer);
return Sum;
}
Либо известен тип элементов списка и признак завершения списка передаваемых параметров. Процедура доступа к параметрам также проста, как и в первом случае:
#include <iostream.h>
long PP(int par1 ...);
void main (void)
{
long RR;
RR = PP( 1, 2, 0, 4, 0 );
/*
Вызвали функцию с 5 параметрами. Единственный обязательный параметр -
первый параметр в списке параметров.
*/
cout << RRR << endl;
}
long PP(int par1 ...)
{
int *pPointer = &par1;
/*
Настроились на область памяти с параметрами. Признак конца списка -
параметр с нулевым значением.
*/
int Sum = 0;
for ( ; *pPointer != 0; pPointer++) Sum += *pPointer;
// Что-то здесь не так… Мы так и не обработали до конца весь список.
return Sum;
}
