Терентьев М. «Грамматика турецкая, персидская, киргизская и узбекская».-Книга І, СПб., 1875.

Цель: Ознакомление с возможностями работы с указателями.

Адресная арифметика, типы указателей и операции над указателями.

Урок 12.

Тема: «Указатели и строки»

Ход урока:

1.Адресная арифметика, типы указателей

В языках Си и Си++ для расширения возможностей адресной арифметики каждый указатель связан с некоторым типом.

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

Основные типы определятся ключевыми словами char, int, float, long, double, short, unsigned, signed, void.

Производных типов может быть бесконечно много, однако правила их конструирования из более простых типов точно определены. К производным типам отнесены: массивы, функции, указатели, ссылки, константы, классы, структуры, объединения, определенные пользователем типы.

Примеры указателей, относящихся к основным типам char, int, float,уже рассматривали. Вот еще некоторые примеры определения указателей, относящихся к основным типам:

 

long double ld = 0.0; // ld – переменная

long double *ldptr = &ld; // ldptr – указатель

void *vptr; // vptr – указатель типа void*

unsigned char *cucptr; // cucptr – указатель без начального значения

unsigned long int *ptr = NULL;

 

2. Операции над указателями

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

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

 

Например:

 

Указатель char *cp; при обращении к памяти работает с участком в один байт.

Указатель long double *ldp; будет “доставать” данные из 10 смежных байт памяти, и т.д.

 

Пример программы, где указателям разных типов присваиваются значения адреса одного участка памяти:

 

// prim 1.cpp – выбор данных из памяти с помощью разных указателей

#include<iostream.h>

 

void main()

{

unsigned long L = 0х12345678L;

char *cp = (char *)&L; // *cp равно 0х78

int *ip = (int *)&L; // *ip равно 0х5678

long *lp = (long *)&L; // *lp равно 0х12345678

cout << hex; // шестнадцатеричное представление выводимых значений

cout << “\n Адрес L, т.е. &L” << &L;

cout << “\n cp =” << (void *)cp << “\t*cp = 0x” << (int) *cp;

cout << “\n ip = ” << (void*)ip << “\t *ip = 0x” << *ip;

cout << “\n lp = ” << (void*)lp << “\t *lp = 0x” << *lp;

}

 

Результат выполнения программы:

Адрес L, т.е. &L = 0х1E190FFC

cp = 0х1E190FFC *cp = 0х78

ip = 0х1E190FFC *ip = 0х5678

lp = 0х1E190FFC *lp = 0х12345678

 

Пояснения к программе:

1) Во всех случаях значения указателей (адреса объектов) совпадают, равны адресу переменной L.

2) При инициализации указателей присваиваемое значение явно преобразуется соответственно к типам char *, int *, long *, т.к. L(&L) имеет тип unsigned long *.

3) При выводе значений указателей (void *)cp, (void *)ip, (void *)lp, они преобразуются к типу (void *), т.к. нас не интересуют длины участков памяти, связанных со значениями указателей.

4) При выходе значения *cp использовано явное преобразование типа (int), т.к. при его отсутствии будет выведен не код (0х78), а соответствующий ему символ ASCII – кода – ‘x’.

5) hex – это манипулятор форматирования потокового вывода – обеспечивает вывод числовых кодов в шестнадцатеричном виде. (в printf() это спецификатор %x )

 

Итак, основной вывод:

Значения указателей (адреса) разных типов в примере совпадают, а количество байтов, “извлекаемых” из памяти, зависит от типа указателя.

 

Примечание: Указатели типа (void *) называются родовыми. Основная идея родового программирования состоит в том, что программа или отдельная функция создаются таким образом, чтобы они могли работать с максимальным количеством типов данных. Указатель (void *) как бы создан “на все случаи жизни”, но, как всякая абстракция, может применяться только с конкретизацией, в данном случае явным приведением типа.

 

Допусктимые операции над указателями:

- доступ по адресу или разыменование (*);

- преобразование типов или приведение типов;

- присваивания (=);

- получение (взятие) адреса (&)$

- сложение, вычитание адресов (аддитивные операции);

- инкремент (автоувеличение) (++);

- декремент (автоуменьшение) (- -);

- операции отношения (сравнения);

 

Первые 3 операции уже рассмотрели в примерах.

О получении адреса указателя можно сказать очень кратко: указатель есть объект, и как объект имеет адрес соответствующего ему участка памяти. Значение этого адреса доступно с помощью операции &, применяемой к указателю:

unsigned int *uip1=NULL, *uip2;

uip2= (unsigned int *) &uip1;

Здесь описаны два указателя, первый из которых uip1 получает нулевое значение при инициализации, а второму uip2 в качестве значения присваивается адрес указателя uip1. Обратите внимание на явное преобразование типа в операторе присваивания. При его отсутствии, т.е. для оператора uip2= &uip1; будет выдаваться сообщение об ошибке.

 

Аддитивные операции - Сложение и вычитание применимы к указателям на объекты одного типа и к указателю и целой константе. Разность однотипных указателей, адресующих два смежных объекта любого типа, по абсолютной величине всегда равна 1.

 

Вычитая два указателя одного типа, можно определить “расстояние” между участками памяти. Оно определяется в единицах, кратных длине объекта того типа, к которому отнесен указатель.

 

Пример:

//prim2.cpp

#include <stdio.h>

void main()

{

char ac = ‘f’, bc = ‘2’;

char *pac = &ac, *pbc = &bc;

long int al = 3, bl = 4;

 

cout << “\n Значения и разности указателей.”;

cout << “\npac = ” << pac <<\pbc = ” <, pbc;

cout << “\npac-pbc = ” << pac-pbc;

cout << “\npal = ” << pal << “\tpbl = ” << pbl;

cout << “\npbl-pal = ” << pbl-pal;

cout <<”\nРазности числовых значений указателей”;

cout <<”\n(int)pac – (int )pbc = ” << (int)pac – (int )pbc;

cout << “\n(int)pal – (int )pbl = ” << (int)pal – (int )pbl;

}

 

Результат:

 

Значения и разности указателей:

pac = 0x0fff pbc = 0x0fee

pac – pbc = 1

pal = 0x0ff2 pbl = 0x0fee

pbl – pal = -1

Разности числовых значений указателей:

(int) pac – (int) pbc = 2

(int) pbl – (int) pal = -4

В первой части разности адресов по абсолютной величине равны 1, т.е. pac и pbc, а также pbl и pal находятся в смежных адресах. (обычное резервирование места в памяти).

Во второй части программы мы имеем не расстояния объектов друг от друга, а количество байт между переменными.

Складывать указатели нельзя, а только вычитать.

Из указателя можно вычитать (прибавлять) целочисленное значение k. При этом значение указателя изменяется на величину k*sizeof (type)

 

Пример:

//prim3.cpp

#include <iostream.h>

void main()

{

float zero = 0.0, pi = 3.14, culer = 2.72;

float *ptr = &culer;

cout << “\n ptr = ” << ptr << “*ptr = ” << *ptr;

cout << “\n (ptr+1)= ” << ptr +1 << ”*(ptr+1)= “ << *(ptr+1);

cout << “\n (ptr+2)= ” << ptr+2 << “*(ptr+2)= ” << *(ptrr+2);

}

Результат:

ptr = 0x22510ff4 *ptrt = 2.72

(ptr+1) = 0x22510ff8 *(ptr+1) = 3.14

(ptr+2) = 0x22510ffc *(ptr+2) = 0.0

 

2. Радлов В.В. «Фонетика северных тюрксих языков» (Солтүстік түркі тілдерінің фонетикасы). Неміс тілінде жазылған. – Лейпциг, 1882.