Указатели и одномерные массивы данных
Цель:получение практических навыков решения задач обработки массивов с использованием указателей.
I. Теоретические сведения.
Указатели
Переменная в программе – объект, имеющий имя и значение. По имени можно обратиться к переменной и получить ее значение. Имя переменной соответствует адресу участка памяти, выделенного для переменной, и явно не связано с адресом. Значение переменной соответствует содержимому участка памяти. Чтобы получить адрес в явном виде, используется унарная операция взятие адреса (&), которая применима только к объектам, имеющим имя и размещенным в памяти.
Адреса имеют целочисленные беззнаковые значения, поэтому их можно обрабатывать как целочисленные величины, для этого используются переменные типа указатель, обеспечивающие непосредственную взаимосвязь данных и возможность изменения этих связей. Указатель – переменная, содержащая информацию о расположении в памяти другой переменной, т.е. адрес объекта конкретного типа (адрес другой переменной). Значением указателя может быть также нулевой адрес, для обозначения которого в ряде заголовочных файлов (stdio.h) определена специальная константа null.
Структура объявления указателя:
<тип_указуемых_данных> *<имя_указателя>;
Символ«*»– знак унарной операции косвенной адресации (разыменования), являющейся операцией раскрытия ссылки (обращения по адресу), результатом которой является объект, адресуемый указателем.
Например,
char *z; // указатель на объект символьного типа
int *k; // указатель на объекты целого типа
float *f; // указатель на объект вещественного типа
Переменные z, k, f являются указателями. *z обозначает объект типа char, на который указывает z. Обозначения *z, *k, *f имеют права переменных соответствующих типов.Оператор *z=’ ‘ засылает символ пробел в тот участок памяти, адрес которого определяет указатель z.
Указатель может ссылаться на объекты того типа, который присутствует в объявлении указателя. Исключением являются указатели, в объявлении которых использован тип void, т.е. отсутствие значения. Такие указатели могут ссылаться на объекты любого типа, однако к ним нельзя применять операцию разыменования.
Например,
1. объявление переменных:
int a,x; // целые переменные
int *p; // переменная – указатель на другую целую переменную (объявление переменной, при косвенном обращении к которой получается значение переменной целого типа)
2. операции присваивания объявленным переменным:
a=2000;
p=&a; // указатель содержит адрес переменной a (присваивание переменной p адреса переменной a – назначение указателя p на переменную a)
3. использование косвенного обращения:
x=x+*p; // при косвенном обращении по указателю p берется значение переменной a, что эквивалентно: x=x+a
Массив как статическая структура данных
Статическая (фундаментальная) структура данных – совокупность фиксированного количества данных постоянной размерности с неизменным характером связей между ними.
Переменные статических структур могут изменять только свое значение, а их структура и множество допустимых значений остаются неизменными; размер памяти, занимаемой такими переменными, остается постоянным.
К статическим структурам, в частности, относится массив данных, который представляет собой упорядоченную последовательность данных одного и того же типа, имеющих общее имя; доступ к элементам массива осуществляется при помощи индексов, являющихся порядковыми номерами элементов в последовательности. На этапе компиляции под элементы массива выделяется фиксированный объем памяти, который не меняется в процессе выполнения программы. В памяти элементы массива размещаются последовательно в соответствии с ростом индекса.
Одномерный массив данных
Если для получения доступа к элементам массива требуется один индекс, то массив является одномерным (линейным). Математическим представлением одномерного массива является вектор.
Структура объявления одномерного массива:
<тип_элемента_массива> <имя_массива>[<количество_элементов];
Например,
char v[10]; // объявление одномерного массива из 10-и символов
Например, копирование 10-и элементов одного массива в другой можно организовать следующим образом:
int v1[10]={1,2,3,4,5,6,7,8,9,10},v2[10]; // объявление переменных
// копирование элементов одного массива в другой
for (int i=0;i<10;i++) // для i, начиная с 0 до 9,
v2[i]=v1[i]; // копировать i-ый элемент массива v1 в i-ый элемент массива v2, после чего i увеличивается на 1 (инкремент переменной целого типа)
При обращении к элементу массива происходит обращение по его адресу, поэтому предыдущий фрагмент программы можно представить следующим образом, используя указатели:
int v1[10]={1,2,3,4,5,6,7,8,9,10},v2[10],*p1,*p2; // объявление переменных
p1=&v1[0]; // в указатель p1 заносится адрес первого элемента массива v1
p2=&v2[0]; // в p2 – адрес первого элемента массива v2
// копирование элементов одного массива в другой
for (int i=0;i<10;i++)
{
*p2=*p1;
p2++; p1++;
}
Адресная арифметика
Операцияадресной арифметики интерпретируется следующим образом:
• указатель потенциально ссылается на неограниченную в обе стороны область памяти, заполненную переменными того типа, на который ссылается указатель;
• переменные в области памяти нумеруются от текущей переменной, на которую указывает указатель и которая получает относительный номер 0; переменные в направлении возрастания адресов памяти нумеруются положительными значениями (1,2...), в направлении убывания – отрицательными (-1,-2…);
• результатом операции указатель+i является адрес i-й переменной в этой области памяти относительно текущего положения указателя, т.е. значение указателя на i-ю переменную.
Таким образом, указатель ссылается на неограниченный массив с относительной нумерацией элементов массива от переменной, на которую указывает указатель.
p+i; // установить указатель на i-ю переменную после той, на которую указывает p
p-i; // установить указатель на i-ю переменную перед той, на которую указывает p
*(p+i); // получить значение i-й переменной после той, на которую указывает p, что эквивалентно: p[i]
p++; // получить значение переменной, на которую указывает p, затем указатель установить на следующую переменную
p--; // получить значение переменной, на которую указывает p, затем указатель установить на предшествующую переменную
p+=i; // переместить указатель на i переменных вперед относительно той, на которую указывает p
p-=i; // переместить указатель на i переменных назад относительно той, на которую указывает p
*p++; // получить значение переменной, на которую указывает p, затем указатель установить на следующую переменную
*(--p); // переместить указатель к переменной, предшествующей той, на которую указывает p, затем получить ее значение
(*p)++; // получить значение переменной, на которую указывает p, затем увеличить ее значения на 1
++(*p); // получить значение переменной, на которую указывает p, увеличенное на 1