Язык программирования C. Структура простейшей программы на языке C, страница 8

Существует тесная связь между массивами и указателями. Под масивами мы понимаем индексированные наборы данных определенного типа. Как мы уже говорили ранее, многомерные массивы в памяти компьютера представляются в виде одномерного вектора. Компилятор в соответствующую таблицу помещает адрес первого элемента, число элементов по каждой размерности (если массив многомерный) и размер одного элемента массива. Это позволяет ему получить доступ к любому элементу массива базируясь лишь на его индексе (индексах). Теперь, если у программиста есть адрес первого элемента массива а все остальное он получает в процессе выполнения программы, то он и сам может получить доступ к любому элементу. Если говорить другими словами, язык С показал программистам изнанку работы исполняющей системы с массивами и позволил выполнять ему эту работу. Итак, как работает адресная арифметика.

Пусть у нас есть массив целых чисел и переменная типа указатель на переменную целого типа.

int aiArray[10];

int piPointer = NULL;

Можно получить адрес первого элемента массива и присвоить этот адрес указателю.

piPointer = &aiArray[0]; (Я надеюсь, вы помните что индексация в языке С начинается с 0, а не с 1).

Теперь piPointer указывает на первый элемент массива и получить его значение можно как через индес 0, так и через указатель - *piPointer.

И так, если piPointer указывает на первый элемент массивы, то на что будет показывать (piPointer + 1)? На второй! Как так, скажете вы, ведь мы прибавили всего 1, а целая переменная занимает 2 байта (по крайней мере на 16 разрадной платформе). Следовательно, если увеличить значение адреса только на 1, то мы сдвинемся в середину целого числа. Но прелесть адресной арифметики в том, что она оперирует типизированными указателями. То есть, если мы увеличиваем на 1 значение указателя на переменную целого типа, то сдвиг произойдет на длину, занимаемую целой переменной. Если бы это был указатель на переменную вещественного типа, то сдвиг произошел бы на соответствующее число байт. Аналогичное произошло бы и с указателем на более сложный тип данных, например, структуру. Теперь мы видим, что между указателями и индексацией существует тесная связь. Более того, компилятор оперирует только с указателями, преобразуя все операции с индексами в операции с указателями. Поэтому и имя массива является указателем на первый элемент массива. Существует, правда, одно принципиальное отличие между переменной типа указатель и именем массива в качестве указателя. Имя массива – константа, и к нему нельзя применить операции адресной арифметики.

Помимо работы с массивами и адресной арифметики указатели предоставляют нам возможность получить из функции в качестве результата не только то значение, которое возвращается с помощью оператора return. Передача параметров в функцию всегда осуществляется путем копирования аргументов в точке вызова во внутренние переменные функции (ее параметры). Такой механизм не является уникальной принадлежностью языка  С – так поступает подавляющее большинство языков. Но если в качестве аргумента функции описать указатель, то это означет, что в функцию можно передать адрес переменной, которая размещена вне области видимости функции. Тогда можно изменить состояние этой переменной из функции. С одной стороны, это вроде бы как бы нарушение принципа инкапсуляции – ведь догмат определения функции требует, чтобы она работала только с переменными внутри себя. Но с другой стороны, чтобы получить доступ к переменной вне области функции, мы должны задукоментировать это в списке аргументов функции. Те же подпрограммы в старых реализациях FORTRAN’а делали подобные операции без всякого уведомления.

Рассмотрим следующие примеры. Например, реализуем функцию swap, которая меняет местами две переменные (она будет очень полезна нам при реализации любого алгоритма сортировки).

Вариант 1.