Site hosted by Angelfire.com: Build your free website today!
 

 


 
 


 


Указатели (продолжение урока №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 байта.

Ну, все на этот раз.
Задание придумывать нет сил.
Потом.
Про шестнадцатеричное исчисление тоже в следующем уроке.

Заглавна Форум Компилатори за C++