Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции_СП_2004_1_00.doc
Скачиваний:
69
Добавлен:
04.11.2018
Размер:
882.69 Кб
Скачать

5. Инициализация указателей

Ввиду того, что с инициализацией указателей мы уже столкнулись при их обсуждении в предыдущих параграфах, здесь будет рассмотрен лишь один частный вопрос. Пусть необходимо разместить простую переменную или массив на фиксированных адресах оперативной памяти. Для этого указатель на соответствующий элемент или структуру данных должен быть инициализирован числовым значением, определяющим абсолютный физический адрес. Поскольку такая потребность чаще всего возникает при работе с видео памятью компьютера IBM PC, рассмотрим способ обращения к ячейкам видеопамяти в алфавитно-цифровом режиме. Учитывая, что интересующая нас область памяти имеет сегментный адрес 0xB800 и каждой позиции экрана отвечают два байта этой памяти, достаточно определить массив элементов типа int, расположив его по требуемому адресу. В том случае, когда видеосистема установлена в режим 25 строк по 80 символов, соответствующее описание должно иметь следующий вид:

(*vmem_16)[25][80] = 0xB8000000;

После этого занесению какой-либо информации во всякий элемент массива (*vmem_16) будет соответствовать определенный эффект на экране видеотерминала.

Лекция 5

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

1. Функции в языке си. Формальные и фактические параметры. Механизм передачи параметров. Возвращаемые значения

Всякая программа на языке Си представляет собой совокупность функций, выполняющих основную работу по реализации некоторого алгоритма. Каждая из них, в свою очередь, есть независимый набор описаний и операторов, заключенных между заголовком функции и ее концом. Все объекты, определенные в теле функции, ограниченном открывающей и закрывающей фигурными скобками, являются локальными для этой функции в смысле области видимости и времени существования. Детали этих понятий будут разобраны в Лекции 7. В составе общей программы любая функция идентифицируется своим собственным уникальным именем, которым может быть любое правильное имя в смысле грамматики языка Си (см. Лекцию 1, $ 3). Та функция, с которой начинается выполнение программы, называется главной функцией и должна иметь предопределенное имя main. Все остальные функции, входящие в программу, запускаются в работу путем их прямого или опосредованного (через другие функции) вызова из главной функции. Функции играют чрезвычайно важную роль при подготовке Си-программы. Действительно, используя функции, исходную задачу можно представить в виде последовательности более простых задач, каждая из которых реализует некоторую часть общего алгоритма. Такой подход к разработке программного продукта иногда называют методом декомпозиции. Оперируя функциями, состоящими из сравнительно небольшого числа операторов, значительно легче удовлетворить требованиям структурного программирования и снизить трудозатраты на отладку программного обеспечения. С другой стороны, в виде функций могут быть реализованы отдельные часто используемые алгоритмы. Включение таких функций в состав стандартных библиотек дает принципиальную возможность не перепрограммировать всякий раз наиболее ходовые методы, алгоритмы и операции. Для организации связи между независимыми функциями в языке Си используется либо аппарат формальных/фактических параметров, либо набор глобальных или внешних переменных. Формальными параметрами мы будем называть аргументы функции, стоящие в ее заголовке и имена которых используются для построения тела функции при ее определении. Они могут иметь любой простой или структурированный тип, поддерживаемый имеющимся компилятором языка или определенный в программе при помощи инструкции typedef (см. Лекцию 8, $ 8). Фактическими же параметрами являются произвольные выражения, значения которых передаются формальным параметрам при обращении к функции. Таким образом реализуется возможность передачи необходимой информации от вызывающей функции к вызываемой непосредственно в момент ее вызова. В свою очередь, имя вызываемой функции может служить носителем выходного значения, получаемого в результате работы этой функции, делая его доступным вызвавшей функции. Случай, когда результатом работы функции является совокупность значений, будет рассмотрен в $ 2 настоящей лекции. Обсуждение же вопроса об использовании глобальных объектов мы пока отложим до Лекции 7. Упоминание о том, что имя функции может быть использовано для передачи выработанного этой функцией значения, подсказывает необходимость связать с этим именем конкретный тип данных из числа поддерживаемых компилятором языка Си. Эта связь устанавливается при определении самой функции или при составлении ее предварительного описания (см. $ 3 настоящей лекции). Определяя функцию в соответствующей программной компоненте, нужно, прежде всего, указать ее имя и тип возвращаемого значения, задать список формальных параметров и определить тип каждого из них. Такую совокупность описаний принято называть заголовком функции. Вслед за ним должно размещаться тело функции, представляющее собой правильный блок, т. е. набор описаний и операторов, заключенных в фигурные скобки. В языке Си определение функции имеет следующий формат:

<sc-specifier> <type-specifier> declarator (<parameter-list>)

<parameter-declarations>

function-body

Здесь sc-specifier есть описатель класса памяти (static или extern), подробно рассматриваемый в Лекции 7. type-specifier и declarator вместе определяют тип возвращаемого функцией значения и ее имя, причем отсутствие первого из них равносильно типу int. В приведенной схеме, declarator чаще всего является просто идентификатором функции, перед которым может стоять символ звездочка (*), если эта функция возвращает указатель на элемент данных соответствующего типа. Однако в ряде случаев он может иметь и более сложный формат. Никакая функция не должна возвращать массивов или других функций, но допустима передача указателей на эти об'екты. В тех случаях, когда функция не вырабатывает никакого значения или возвращает указатель неопределенного типа (см. Лекцию 4, $ 1), ее описание должно начинаться с ключевого слова void, стоящего на месте имени типа возвращаемого значения. Например,

void work(n, beta)

int n;

float beta;

{ .................

.................

.................

}

Parameter-list представляет собой необязательный список формальных параметров (аргументов) определяемой функции, разделенных запятыми:

(<identifier<, identifier> ... >)

где identifier суть имя формального параметра. Описания формальных параметров parameter-declarations в заголовке функции имеют тот же самый формат, что и рассмотренные ранее описания простых и структурированных переменных, и служат для определения типа этих параметров. Те параметры, имена которых не объявлены явным образом в одной из инструкций описания, по умолчанию получают тип int, назначаемый компилятором. Память под размещение формальных параметров выделяется динамически из ресурса программного стека в момент обращения к соответствующей функции. Это означает, что параметры всегда должны иметь класс памяти auto или register (см. Лекцию 7, $ 2), причем первый из них назначается компилятором по умолчанию. Тело функции, являясь правильным блоком или составным оператором (при отсутствии в нем описаний переменных), выполняет основную работу внутри этой функции. Те переменные, которые описаны в заголовке блока (т. е. в теле функции) будут локальными для этой функции, ибо область их видимости ограничена соответствующим блоком. Они создаются в момент обращения к данной функции и исчезают по окончании ее работы (класс памяти auto), а их имена не должны совпадать с именами формальных параметров. Поскольку память под размещение локальных переменных выделяется исполняющей системой динамически из программного стека, последний должен иметь достаточную для этого длину. Инициализация локальных объектов допустима лишь в случае простых переменных и невозможна для массивов и других структурированных данных. Подробный разговор об этом пойдет в Лекции 7. Выполнение инструкций в теле функции начинается с самого первого оператора и продолжается до тех пор, пока не встретится оператор возврата return, либо пока не будет достигнут конец внешнего блока. Возвращаемое функцией значение равно значению выражения в операторе return (см. Лекцию 3, $ 4), а при его отсутствии считается неопределенным. В случае необходимости тип результата преобразуется к типу

функции стандартным образом. Инструкция вызова функции в общем случае имеет следующий формат:

expression (<expression-list>)

и по существу представляет собой выражение, которое может играть роль операнда в составе более сложного выражения. Здесь expression есть некоторое адресное выражение, например, имя функции, определяющее ее входную точку. Список фактических параметров expression-list содержит произвольные выражения, разделенные запятыми, значения которых вычисляются в момент обращения к функции и копируются в область ее формальных параметров. Таким образом, в языке Си реализован механизм передачи параметров по значению. Поскольку всякая функция работает лишь с копиями значений своих аргументов, а не с их адресами, то никакие изменения значений формальных параметров в теле функции не могут отразиться на значениях фактических параметров. Это в свою очередь означает, что аргументы функции являются носителями лишь входной информации и не могут быть использованы для передачи результатов ее работы вызвавшей функции. Для преодоления этого ограничения необходимо использовать указатели в качестве аргументов функций, передавая тем самым числовые значения соответствующих адресов. Таким же образом решается проблема передачи массивов, функций и некоторых других структурированных данных. Для иллюстрации изложенного материала приведем пример программы, вычисляющей квадратный корень из числа, введенного с клавиатуры консольного терминала. Здесь основная работа по нахождению корня выполняется функцией sqrt(), реализующей итерационный метод Ньютона с фиксированным числом итераций.

#include <stdio.h>

main()

{ float dat;

float sqrt(float); /* Описание функции (см. $ 3) */

printf("\nЗадайте положительное вещественное число ... ");

scanf("%f", &dat);

printf("\n\nКорень из числа %.3f равен %.3f", dat, sqrt(dat));

}

float sqrt(arg)

float arg;

{ int count;

float root = arg/2.0;

for (count = 1; count <= 5; count++)

root = 0.5*(root + arg/root);

return (root);

}

Если теперь в ответ на запрос программы ввести, например, число 25, то по окончании ее работы будет напечатан следующий результат:

Корень из числа 25.000 равен 5.000

Заметим, что значение переменной dat в главной функции ни при каких обстоятельствах не может быть изменено при выполнении итерационного алгоритма в теле функции sqrt(), поскольку вся работа здесь ведется с копией значения, переданного через параметр arg. Использованное в этом примере предварительное описание функции sqrt() в теле функции main() будет рассмотрено в $ 3 настоящей лекции.