К указателям можно применить операцию присваивания. Указатели одного и того же типа могут использоваться в операции присваивания, как любые другие переменные.
# include <stdio.h>
main ()
{
int x = 0 ;
int *p, *g ;
p = &x ;
g = p ;
printf(“%p”, p); /* печать содержимого p */
printf(“%p”, g ); /* печать содержимого g */
printf(“%d %d”, x., *g);/*печать величины х и величины по адресу g */
}
В этом примере приведена еще одна спецификация формата функции printf() %p – печать адреса памяти в шестнадцатеричной системе счисления.
В языке Си указателю допустимо присвоить любой адрес памяти. Однако, если объявлен указатель на целое int *pi; а по адресу, который присвоен данному указателю, находится переменная типа float, то при компиляции будет выдано сообщение об ошибке в строке
p = &x;
Эту ошибку можно исправить, преобразовав указатель на int к типу указателя на float явным преобразованием типа:
p = (int*)&x;
но при этом теряется информация о том, на какой тип указывал исходный указатель.
Пример неправильной программы:
main ()
{
float x =10.1, y;
int *p;
p = &x /* потом заменим на p=(int*)&x */
y = *p;
printf (“x = %f y = &f \ n”, x, y);
}
В результате работы этой программы не будет получен тот ответ, который ожидался. Переменной y не будет присвоено значение переменной x, т.к. будут обрабатываться не 4 байта, как положено для переменной типа float, а только 2 байта, т.к. базовый тип указателя - int.
Как и над другими типами переменных, над указателями можно производить арифметические операции сложения и вычитания, а также операции (++) и (--). Указатели можно сравнивать. Применимы шесть операций:
<, >, < =, > =, =, = =, ! =
main()
{
int *p;
int x;
p = &x;
printf (“%p%p”, p, ++p);
printf (“%p”, ++p);
}
После выполнения этой программы, мы увидим, что при операции ++p значение указателя изменится не на 1, а на 2. И это правильно, т.к. значение
указателя должно указывать не на следующий адрес памяти, а на адрес следующего целого. А целое, как мы помним, занимает 2 байта. Если бы базовой тип указателя был не int, а double, то были бы напечатаны адреса, отличающиеся на 8. Именно столько байт занимает переменная типа double.
К указателям можно прибавлять некоторое целое или вычитать. Пусть указатель p имеет значение 2000 и указывает на целое, тогда в результате выполнения оператора p=p+3 его значение будет 2006. Если указатель p1=2000 был бы указателем на float, то после применения оператора p1= p1+10 значение p1 было бы 2040.
Общая формула для вычисления указателя по формуле p= p+ n будет иметь вид:
<p>=<p>+n*<количество байт памяти базового типа указателя>.
Сравнение p < g указывает, что адрес, находящийся в p, меньше адреса, находящегося в g.
4.3. Массивы и указатели на языке Си
Массивы описывают регулярную структуру данных одного типа.
Одномерные массивы:
int temp [365];
char arr [10];
char *point[10];
Двумерные массивы:
int array[4] [10];
char arr [3] [7];
Число в [ ] указывает количество элементов массива, поэтому:
temp [365] – массив из 365 элементов.
Доступ к каждому элементу осуществляется по его индексу (номеру), т.е. temp[0], temp[1],…,temp[364]- последний элемент. Элементы массива нумеруются начиная с 0.
Можно также использовать многомерные массивы, например: int arr [k] [l] …[n];
Однако следует помнить, что для хранения элементов таких массивов требуется значительный объем памяти.
Рассмотрим, как происходит размещение элементов массива в памяти ЭВМ. Как правило, элементы массива занимают последовательные ячейки памяти. При этом элементы размещаются таким образом, что самый последний индекс возрастает быстрее. Это в случае двумерного массива означает, что он будет записываться построчно: строка за строкой. Поскольку указатели указывают адрес ячейки, то между массивами и указателями существует тесная связь. Вспомним, что имя массива – это указатель на его первый элемент. По существу массив можно рассматривать как индексированный указатель. Доступ к элементам массива осуществляется по номеру индекса. При этом приращение индекса на единицу вызывает перемещение указателя на число байт, соответствующее объекту данного типа: для целых чисел на 2 байта, для действительных - на 4 байта и т.д.
Объявления int mas[] и int *mas идентичны по действию: оба объявляют mas указателем.
Индекс массива действует аналогично стрелки часов, показывающей по очереди на каждый следующий элемент массива.
Пример 4.5:
int mas[10];
int *ptr;
ptr = mas; // присваивает адрес указателю
// следующие операции дадут один и тот же результат:
mas[2] = 20;
*(ptr + 2) = 20;
// следующая операция прибавит 2 к первому элементу:
*ptr + 2;
Указатели и многомерные массивы. Рассмотрим двумерный массив и действия с указателями.
int mas[4][2];
int *ptr;
ptr = mas;
ptr сейчас указывает на первый столбец первой строки, т.е.
ptr = = mas = = &mas [0] [0];
Увеличим указатель:
ptr+1 = = &mas [0] [1];
ptr+2 = = &mas [1] [0];
ptr+3 = = &mas [1] [1] и т.д.
Двумерный массив можно представить как массив массивов. В нашем случае мы имеем четырех элементный массив, состоящий из двух элементов. Примечательно, что этот четырех элементный массив можно представить в виде одномерного mas[0],…,mas[3]. При этом имя массива по-прежнему является указателем на его первый элемент, т.е. mas[0]= =&mas[0] [0] . На что же будут указывать mas[i]? В этом случае mas [i] указывает на i-тую строку, т.е. на первый элемент i - й строки. Таким образом
mas [0] == &mas [0] [0];
mas [1] == &mas [1] [0];
mas[2] == &mas [2] [0];
mas[3] == &mas [3] [0];
Массивы и указатели – различия.
Имя массива – является указателем – константой. Описания:
char string_1[20]= «Язык Си» и
char *string_2 = «Язык Си»;
размещают в памяти соответствующую строку символов. Различие состоит в том, что указатель string_1 является константой, а указатель string_2 – переменной. Это различие проявляется в случае использования операции единичного приращения ++. Эту операцию можно применять только к переменным. Поэтому string_2++ - допустимая конструкция, а string_1++ - запрещенная. Однако и в том и в другом случае можно использовать операции сложения с указателем, т.е.
string_1 + i ;
string_2 + i ; - допустимые конструкции.
При задании массива символов можно указывать размер явно, например:
char mas_1 [10] = “Яблоко”;
Или определить массив по умолчанию
Char mas_2 [ ] = “Груша”.
Отличие заключается в том, что во втором случае будет выделено ровно столько памяти, сколько необходимо.
Массивы и указатели символьных строк
Часто бывает необходимо иметь массив символьных строк. При этом возможно задать два типа описаний:
1) char string_1[10][20]; //10 строк по 20 символов
2) char* string_2[10]; // массив из 10 указателей на строки символов. Какие здесь различия? Различие заключается в том, что в первом случае задается “прямоугольный” массив, в котором каждая строка имеет фиксированную длину, а во втором определяется “рваный” массив, где длина каждой строки занимает столько байт , сколько необходимо.