Терентьев М. «Грамматика турецкая, персидская, киргизская и узбекская».-Книга І, СПб., 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.