| |
Указатели
(продолжение урока №5)
Если у тебя
возникли проблемы с
задачей про видеопамять из
первой части или ты вообще
не знаешь с какой стороны к
ней приступить, приведу
пример. Он также будет
полезен для понимания
сущности указателей.
Еще раз повторюсь, что
данная программа будет
компилироваться только в
Борландовском Си и она под
ДОС.
| 1 |
#include
<dos.h> |
| 2 |
#include
<conio.h> |
| 3 |
void
ShowString(int x,int y,char *lpszStr,char
nColor); |
| 4 |
void
main(void){ |
| 5 |
ShowString(20,10,"Check
out this !",0x1e); |
| 6 |
getch();
} |
| 7 |
void
ShowString(int x,int y,char *lpszStr,char
nColor){ |
| 8 |
char far
*lpScreen=( char far
*)MK_FP(0xB800,0000);
|
| 9 |
int
nOffset=(x+y*80)*2; |
| 10 |
while(*lpszStr){ |
| 11 |
lpScreen[nOffset++]=
*lpszStr; |
| 12 |
lpScreen[nOffset++]=
nColor; |
| 13 |
lpszStr++; }
}
|
Чтобы понять эту
небольшую программку,
необходимо знать
много вещей:
указатели
функции
шестнадцатеричное
исчисление
устройство
видеопамяти в
текстовом режиме
В начале я
кратко пройдусь по строкам
этой программы, затем
подробнее расскажу о новых
вещах, встретившихся
здесь.
(1) подключаем dos.h, чтобы
воспользоваться функцией
MK_FP
(2) подключаем conio.h, чтобы
воспользоваться функцией
getch()
(3) объявляем функцию,
которую напишем позднее.
Необходимо, чтобы
воспользоваться ей в
строке (5). Если не сделать
объявление, то компилятор
ругнется, что он не знает
такой функции потому, что
определена функция будет
позднее - начиная со строки
(7)
(4) задаем основную функцию
программы. Она не
принимает аргументов и
ничего не возвращает.
(5) Вызываем написанную
нами ShowString. На экране
появляется надпись
ярко-желтым цветом на
синем фоне.
(6) getch() из conio.h
останавливает исполнение
программы ожидая нажатия
пользователем клавиши.
Возвращает код нажатой
клавиши. Мы его никуда не
используем - здесь это не
надо. Хотя в других
программах с помощью нее
можно организовать
ожидание выбора пункта
меню пользователем.
(7) Создаем функцию, которая
принимает 2 числа типа int,
строку, и еще маленькое
число и которая ничего не
возвращает
(8) Создаем переменную -
дальний указатель на char и
инициализируем её (заносим
в нее значение. Она теперь
указывает на текстовую
видеопамять.
(9) Создаем целую
переменную и
инициализируем её.
(10) Начинаем цикл. Пока lpszStr
указывает на число
(символ), отличное от нуля
(11) Заносим по вычисленному
нами смещению текущий
символ из строки и
увеличиваем смещение на 1
(12) Заносим в следующий
байт цвет символа и опять
увеличиваем смещение на 1
(13) Передвигаем указатель
на следующий символ в
строке и возвращаемся на
проверку условия (10)
Таково
краткое описание работы
программы.
Теперь подробнее.
Желательно, чтобы не
осталось никаких
недопониманий что
делается в каждой строчке.
Сначала об
устройстве видеопамяти в
текстовом режиме.
В стандартном режиме на
экране 25 строк по 80
символов в каждой строке.
Каждому символу на экране
можно задать цвет
(атрибут), который состоит
из цвета фона и цвета
символа.
Итак, минимальной единицей
информации, которую можно
считать/записать в память
компьютера является байт.
В си это char.байты идут
подряд и адрес - это номер
байта.
На каждый символ в
видеопамяти отводится 2
байта. Первый байт - символ
(вернее ASCII код символа),
второй - его цвет. Значит,
когда мы установили
переменную lpScreen на начало
видеопамяти (B800:0000), то она
стала указывать на байт,
где лежит код первого
символа на экране - того,
что в верхнем левом углу. К
нему доступ получаем
lpScreen[0]. В следующем байте
лежит его атрибут (цвет).
Его можно достать так :
lpScreen[1]. И так далее - lpScreen[2] -
код второго символа в
первой строке, lpScreen[3] - его
атрибут. Тут надо проявить
математическое мышление и
заметить, что если нам надо
получить доступ к символу
с номерм i, то надо взять
lpScreen[i*2]. Атрибут его будет
лежать в следующем байте
lpScreen[i*2+1].
Когда мы доходим до
последнего символа в
строке, 80го по адресу
lpScreen[158] и его цвета по
адресу lpScreen[159] ( отсчет мы
ведем, не от 1, а от нуля -
поэтому 80й символ имеет
номер 79, 79*2=158), то в ячейке
lpScreen[160] будет лежать 1й
символ из следующей
строки, а в lpScreen[161] - его
атрибут. Отсюда берется
формула, по которой я
вычисляю сколько надо
отступить от начала
видеопамяти, чтобы
добраться до строки с
номером j. Одна строка
занимает 160=80*2 байт - 80
символов по 2 байта на
каждый. Чтобы попасть на 2ю
строку надо сразу
перескочить на 160 байт. На
третью - 320 и тд. Чтобы на
j-тую : j*160 (если j
отсчитывать от нуля).
Теперь, чтобы добраться на
символа с координатами (i,j)
надо пропустить j строк :
j*160 и отступить от начала
строки еще на i*2 байт. Всего
160*j+2*i.
Теперь те. У кого с
математикой более-менее
увидят, что двойку можно
вынести за скобки: 2*(j*80+i).
Именно так я вычисляю
отступ в строке программы
(9).
Подробно по строчкам:
(11) lpScreen[nOffset++]= *lpszStr; -
заношу код символа в
lpScreen[nOffset] тут же
увеличиваю nOffset на единицу.
Т.е. эта строка
эквивалентна двум:
lpScreen[nOffset]= *lpszStr;
nOffset++;
запись *lpszStr - это
извлесения занченя по
адресу, лежащему в lpszStr.
Эквивалентно lpszStr[0]. Как
видно, строки в си тоже
передаются через
указатель. Указатель на
ЧАРы.
(12) lpScreen[nOffset++]= nColor; -
заносим в следующий байт
видеопамяти атрибут
символа и тут же
увеличиваем nOffset на
единицу.
(13) lpszStr++; Увеличиваем
указатель на единицу.
Указатель на строку.
Подробнее о
строках
Строка - это
последовательность байт в
памяти, лежащих подряд и
заканчивающихся байтом, в
котором лежит число ноль.
Т.е. это нечто, что
существует независимо от
языка Си. Чтобы работать со
строками используются
указатели на char и массивы
car'ов. В массиве можно
хранить строку, но
размером не более, чем
размер массива минут
единица. Потому как надо
еще оставить один байт на
конце под символ конца
строки. Пример :
char str[10];
strcpy(str,"yes");
после этого в
нулевом эл-те массива
будет лежать символ
"y"(число 0x79) , в первом
- "e" (0x65), 2м - "s"
(0x73) и в 3м - 0(ноль).
Остальные элементы
массива не используются.
С этой переменной можно
сделать что угодно -
получить любой символ из
строки по номеру, как
элемент массива или
передать всю строку
какой-либо функции в виде
параметра: printf(str);
Однако, когда мы передаем
строку просто указывая имя
массива м передаем не
массив, а указатель на
начало массива. Указатель
на char'ы. Строки всегда
передаются через
указатель, т.е. через адрес
где в памяти лежит первый
символ строки.
Именно поэтому 4й параметр
ф-ии объявлен, как
указатель на char. Однако,
следует понимать, что
указатель на char - это еще не
сама строка. Сама строка
лежит в памяти. А указатель
является лишь способом
доступа к ней. Поэтому так
делать нельзя:
char *str;
strcpy(str,"yes");
копировать
строку можно в память, где
есть место под строку. В
предыдущем примере мы
зарезервировали 10 байт и
на это место законно
копировали. Здесь же мы
объявили
переменную-указатель. В
ней можно хранить адрес. Но
в момент копирования она
никуда не указывает. И
поэтому мы копируем строку
«в никуда». Обычно это
приводит в закрытию
программы по ошибке. Чтобы
воспользоваться
указателем, его надо
сначала
проинициализировать:
char str1[10];
char *str;
str= str1;
strcpy(str,"yes");
как я уже
говорил, написав имя
массива без квадратных
скобок мы получили
указатель на первый
элемент массива.
Еще об
указателях
С указателями можно
работать ,как с массивами,
получая элемент с заданным
отступом от начала через
квадратные скобки:
str1[2] тоже самое, что str[2] в
предыдущем примере.
Но с указателем можно
делать еще кое-что. Можно
взять значение по адресу,
который лежит в указателе:
*str. Так, как в указателе
лежит адрес самого первого
элемента массива
(нулевого), то это
эквивалентно str[0].
И самая употребительная
фишка - это то, что
указатель можно
«двигать», изменяя адрес,
который лежит в нем. Можно
прибавить конкретное
число, можно сделать
инкремент с помощью
оператора ++.
После str++ в str[0] и *str , будет
лежать уже не "y", а
"e".
Именно это я использовал,
чтобы перебрать по очереди
все символы в строке : в (13)
я смещал указатель на
следующий символ и в (10)
проверял, не дошел ли я до
последнего символа -
символа конца строки.
Хотя в данном случае
действия str++ и str=str+1
эквивалентны, но меду ними
есть разница. str=str+1 смещает
указатель на 1 байт. str++
смещает указатель на
размер элемента, на
который указывает эта
переменная. В данном
случае это char, у которого
размер равен 1 байту.
Однако, в случае с
указателем на int это уже не
будет верно. В Борланд си int
имеет размер 2 байта.
Ну, все на
этот раз.
Задание придумывать нет
сил.
Потом.
Про шестнадцатеричное
исчисление тоже в
следующем уроке.
        

|
|