Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Программирование на C / C++ / Язык программирования Си++. Лекции.DOC
Скачиваний:
173
Добавлен:
02.05.2014
Размер:
775.17 Кб
Скачать
    1. Непосредственная работа с экранной памятью

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

Рассмотрим принцип организации экранной памяти для вывода информации на экран в текстовом режиме. Каждому знакоместу экрана соответствует слово памяти (2 байта). Младший байт содержит код символа, старший атрибут символа, устанавливающий его цвет. В свою очередь, атрибут символа в старших четырех битах содержит цвет фона (самый старший бит может устанавливать режим мигания), а в младших четырех битах - цвет символа. Вычислить атрибут символа, зная цвет фона - fonи цвет символаsym, можно по следующей формуле:

attr = fon * 16 + sym;

или

attr = fon << 4 | sym;

Таким образом, каждой строке экрана соответствует 160 байт экранной памяти. Экранная память начинается с адреса 0xB8000000L. Буква L в конце адресной константы необходима для того, чтобы компилятор создал константу длиной 4 байта.

Ниже приведен текст функции, выводящей строку символов, на которую указывает указатель str, начиная с колонкиcolиз строкиrowэкрана. При выводе строки используется атрибутattr.

void PutsXY (int col, int row, char *str, int attr)

{

char far *p = (char far *) 0xB8000000L;

p+= 160*(row-1)+2*(col-1); /* Начало строки */

while (*str)

{

*(p++) = *(str++); /* Вывод символа */

*(p++) = attr; /* Вывод атрибута */

}

}

Единственное замечание, которое следует сделать, заключается в том, что для адресации экранной области памяти используется дальний (far) указатель на символы. Такой указатель необходимо использовать для того, чтобы независимо от того, в какой 16-разрядной модели памяти создается программа (даже вTiny), была возможной адресация этой памяти.

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

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

void PutAttr (int col, int row, int len, int attr)

{

char far *p = (char far *) 0xB8000000L;

p+= 160*(row-1)+2*(col-1)+1;

while (len--)

{

*p = attr; /* Вывод атрибута */

p += 2; /* Переход к следующему атрибуту */

}

}

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

  1. Дополнительные сведения о функциях

    1. Области видимости и глобальные данные

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

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

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

После выполнения следующего примера:

#include <stdio.h>

int i=1;

void PrintI(void)

{

printf( "i = %d\n", i );

}

void main(void)

{

int i=10;

printf( "i = %d\n", i );

PrintI();

{

int i=100;

printf( "i = %d\n", i );

PrintI();

}

printf( "i = %d\n", i );

PrintI();

}

будет напечатано

i = 10

i = 1

i = 100

i = 1

i = 10

i = 1

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

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

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

#include <stdio.h>

int i;

int Sum(int A[], int n)

{

int s = 0; /* Сумма одномерного массива */

for(i=0; i<n; i++) s += A[i];

return s;

}

void main(void)

{

int B[3][2] = { { 1, 1 },

{ 2, 2 },

{ 3, 3 } };

int s = 0; /* Сумма двумерного массива */

for(i=0; i<3; i++) s += Sum(B[i], 2);

printf("s = %d\n", s);

}

Никаких ошибок компиляции в программе нет. Если рассматривать все ее функции по отдельности, то ошибка тоже не видна. Но программа дает неверный результат: s = 2. Это происходит потому, что цикл головной программы выполняется всего один раз, так как после возврата из функции Sum(), значение глобальной переменной i будет равно 3. Если описать переменные циклов i внутри каждой функции, то программа станет выдавать правильную сумму элементов двумерного массива: s = 12.

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

extern double Speed;

говорит о том, что в каком-то из модулей программы выделена память под переменную Speed типа double и функции данного модуля могут ее использовать.

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

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

static int Count;

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

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

static double cotan(double x);

функция cotan будет доступна только в том модуле (файле), где она определена, то есть, где находится ее тело.

Соседние файлы в папке C++