Дороговкази в програмуванні, devprog блог для програмістів

На написання даної замітки мене підштовхнуло те, що в підручниках з програмування не приділяється достатньої уваги такій важливій темі, як покажчики. Тобто, увага, то звичайно, приділяється, але ось принцип навчання роботи з покажчиками або не дуже зрозумілий або, що ще гірше, взагалі не ясний. Таким чином, на різних форумах і конференціях з програмування кожен третій програміст просить допомогти йому розібратися з цією темою. Ця невелика замітка постарається заповнити цю прогалину. Всі приклади будуть на різних мовах програмування - так щоб всім було зрозуміло, незалежно від того, яку мову ви вивчаєте. Я так само припускаю, що ви знайомі хоча б з одним з трьох мов програмування - це Pascal \ Delphi, C ++ і Асемблер.


Як ви знаєте, кожен тип змінної має свій розмір. Так, наприклад, під тип integer - процесор виділяє 2 байта пам'яті, а під тип byte - 1 байт. Під longint - DWORD - тобто «подвійне слово», що означає 4 байта. Так, WORD - це 2 байта, або «слово». Так, один байт - це дві цифри від 00 до FF (в шістнадцятковій системі числення). Для кожного типу даних - свій розмір в пам'яті (який саме, можна дізнатися в довіднику за мовою).

Тобто, як ви вже зрозуміли, якщо дивитися по нашій таблиці, і якби ми описали змінну - покажчик на тип Integer, то вона б зайняла рівно 2 байта в пам'яті і наша таблиця, тобто пам'ять, прийняла б такий вигляд:

Дороговкази в програмуванні, devprog блог для програмістів

Для чого використовуються покажчики?

Як ви знаєте, виклик процедур і функцій в мовах програмування виконується наступним чином:

# іncludе <іostrеam.h>
Іnt maіn (int argc, char * argv [])

char * lpString; // оголосивши змінну-покажчик на тип char

char String [] = "Наша нова рядок»; // оголосивши масив (рядок)

cout <

cout <

return 0; // Кінець програми

До речі, для того щоб взяти значення безпосередньо з осередку - використовується операція разименовиванія покажчика, шляхом приставки у вигляді символу «*» перед початком змінної.
Тобто, якби ми замість рядка:

cout <

cout <<*lpString <

Те отримали б на виході всього один символ - «Н».

Наведу приклади на інших мовах:

begin
MessageBox (0, lpString, String1,0); // виводимо вікно з текстом але з різних джерел
end.

program test1;
var str: string;
lpStr: pointer;
begin
lpStr: = @ str;
str: = 'Pascal pointer!';
writeln (string (lpStr ^));
readln;
dispose (lpStr);
lpStr: = nil;
end.

До речі, зверніть увагу, як я виводжу рядок:

program test1;
uses crt;
type
lpStr = ^ string;
lpInt = ^ integer;
lpReal = ^ real;

Pointer_String: lpStr;
Pointer_Integer: lpInt;
Pointer_Real: lpReal;

vString: String;
vInteger: Integer;
vReal: Real;

clrscr;
vString: = 'Where is my pointer?';
vInteger: = 100;
vReal: = 456.3000;

Pointer_String: = @vString;
Pointer_Integer: = @vInteger;
Pointer_Real: = @vReal;

writeln (Pointer_String ^);
writeln (Pointer_Integer ^);
writeln (Pointer_Real ^);
...
... тут ми чистимо наші покажчики ...
...

Тут, ми явно вказуємо типи вказівників. Тобто, наприклад, lpStr - це покажчик на тип String і тд. І при зверненні до разименованному вказівником, ми вже вільні не вказувати, на що саме посилається наш покажчик.

Ну, і, врешті-решт, подивимося приклад на мові Асемблера з використанням поширеного компілятора - MASM32. Я не буду приводити весь код, а покажу тільки найважливіше:

String db «Assembly string!», 00; Оголосили масив з нульовим байтом на кінці

push offset String; кладемо в стек зміщення змінної String
pop [lpString]; витягуємо її з стека і кладемо в lpString

push 0; викликаємо вікно
push [lpString]; c покажчиками на рядки
push [lpString]
push HWND_DESKTOP

Count dd 00h;
lpString dd 00h;
string db «Hello Wordl», 00

Як бачите, цей асемблер справляється з такими конструкціями дуже легко.
Ну, начебто з покажчиками розібралися. Тепер закріпимо все однією зручною таблицею і продовжимо працювати з покажчиками.

Дороговкази в програмуванні, devprog блог для програмістів

Покажчики - прекрасний інструмент роботи з рядками

Розробимо програму, яка б заміняла в рядку всі символи «*» на прогалини і друкувала результат в консолі. Варіант на Сі:

# іncludе <іostrеam.h>
# іncludе

voіd main ()
char * lpCharString;
char szText [] = "1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 0";
int a;

lpCharString = szText [0];
int size;

for (size = lstrlen (lpCharString); size! = 0; size-, lpCharString ++)

if (* lpCharString == '*') * lpCharString = 0x20; cout <<*lpCharString;
>
>

var
szStr: array [0..20] of Char;
lpStr: ^ Char;
iSize: Integer;
Counter: Integer;
begin
szStr: = '1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 0';
lpStr: = @szStr;
iSize: = Length (szStr);

for Counter: = 1 to iSize-2 do
begin
if lpStr ^ = '*' then begin
lpStr ^: = '';
end;
write (lpStr ^);
inc (lpStr);
end;
Readln;
end.

Як бачите теж все досить зручно і наочно. Але одну річ я все - таки поясню. Бачите, де в циклі я вказую -2 від загальної довжини? Так ось, формат Delphi рядків такий, що перед самою рядком варто поле типу WORD і в цьому полі міститься довжина рядка. Виглядає це ось так:

Дороговкази в програмуванні, devprog блог для програмістів

А так як нам вони не потрібні, ми просто їх забираємо і все. Хоча якщо не забирати - все буде працювати звичайно. Але я вам просто показав, який формат Delphi рядків. В іншому, в цій програмі все повністю ідентично тій, яка запрограмована на Сі. З паскалем справи йдуть так само.

Схожі статті