Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
lesson_12_13.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
365.57 Кб
Скачать
  1. Указатели и массивы.

Указатели, массивы и арифметика указателей

// addpntrs.ерр -- сложение указателей

#include <iostream>

int main()

{

using namespace std;

double wages[3] = {10000.0, 20000.0, 30000.0};

short stacks[3] = {3, 2, 1};

// Два способа получить адрес массива

double * pw = wages; // имя массива равно адресу

short * ps = &stacks[0]; // либо использование операции взятия адреса

//с элементом массива

cout « "pw = " << pw << ", *pw = " << *pw << endl;

pw = pw + 1;

cout << "add 1 to the pw pointer: \n"; // добавление 1 к указателю pw

cout « "pw = " « pw « ", *pw = " « *pw « "\n\n" ;

cout « "ps = " « ps « ", *ps = " « *ps « endl;

ps = ps + 1;

cout << "add 1 to the ps pointer: \n"; // добавление 1 к указателю ps

cout « "ps = " << ps << ", *ps = " << *ps << "\n\n";

// Доступ к двум элементам с помощью нотации массивов

cout « "access two elements with array notation\n";

cout << "stacks[0] = " << stacks[0]

« ", stacks[l] = " << stacks[l] << endl;

// Доступ к двум элементам с помощью нотации указателей

cout << "access two elements with pointer notation\n";

cout << "*stacks = " << *stacks

« ", * (stacks + 1) = " « * (stacks + 1) « endl;

cout « sizeof (wages) « " = size of wages array\n"; // размер массива wages

cout « sizeof (pw) « " = size of pw pointer\n"; // размер указателя pw

return 0;

}

В большинстве контекстов C++ интерпретирует имя массива как адрес его первого элемента. Таким образом, следующий оператор создает pw как указатель на тип double, затем инициализирует его wages, который также является адресом первого элемента массива wages:

double * pw = wages;

Для wages, как и любого другого массива, справедливо следующее утверждение:

wages = &wages[0] = адрес первого элемента массива

Далее программа инспектирует значения pw и *pw. Первое из них представляет адрес, а второе — значение, расположенное по этому адресу. Поскольку pw указывает на первый элемент, значение, отображаемое *pw, и будет значением первого элемента — 10000. Затем программа прибавляет единицу к pw. Как и ожидалось, это добавляет 8 (fd24 + 8 = f 62с в шестнадцатеричном виде) к числовому значению адреса, потому что double в этой системе занимает 8 байт. Это присваивает pw адрес второго элемента. Таким образом, теперь *pw равно 20000, т.е. значение второго элемента.

Теперь рассмотрим выражение stacks [1]. Компилятор C++ трактует это выражение точно так же, как если бы вы написали * (stacks + 1). Второе выражение означает вычисление адреса второго элемента массива, и затем извлечение значения, сохраненного в нем. Конечный результат — значение stacks [1].

Вывод программы демонстрирует, что * (stacks + 1) и stacks [1] — это одно и то же. Аналогично * (stacks + 1) эквивалентно stacks [2]. В общем случае, всякий раз, когда вы используете нотацию массивов, C++ выполняет следующее преобразование:

имя_массива[і] превращается в * (имя_массива + і)

И если вы используете указатель вместо имени массива, C++ осуществляет то же самое преобразование:

имя_указателя[і] превращается в * (имя_указателя + і)

Таким образом, во многих отношениях имена указателей и имена массивов можно использовать одинаковым образом. Нотация квадратных скобок применима и там, и там. К обоим можно применять операцию разыменования (*). В большинстве выражений каждое имя представляет адрес. Единственное отличие состоит в том, что значение указателя изменить можно, а имя массива является константой:

имя_указателя = имя_указателя + 1; // правильно

имя массива = имя массива + 1; // не допускается

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

Адрес массива

Получение адреса массива является другим случаем, при котором имя массива не интерпретируется как его адрес. Но подождите, разве имя массива не интерпретируется как адрес массива? Не совсем — имя массива интерпретируется как адрес первого элемента массива, в то время как применение операции взятия адреса приводит к выдаче адреса целого массива:

short tell [10]; // создание массива из 20 байт

cout « tell « endl; // отображение &tell[0] (адреса первого элемента массива)

cout « &tell « endl; // отображение адреса целого массива

Подведение итогов относительно указателей

Позже вы сможете несколько углубить свои знания об указателях, а пока подведем итоги относительно того, что известно об указателях и массивах к настоящему моменту.

Объявление указателей

Чтобы объявить указатель на определенный тип, нужно использовать следующую форму:

имяТипа * имяУказателя;

Вот некоторые примеры:

double * рn; // рn может указывать на значение double

char * рс; // рс может указывать на значение char

Здесь рn и рс — указатели, a double * и char*— нотация C++ для представления указателя на double и указателя на char.

Присваивание значений указателям

Указателям должны быть присвоены адреса памяти. Можно применить операцию & к имени переменной, чтобы получить адрес именованной области памяти, либо операцию new, которая возвращает адрес неименованной памяти.

Вот некоторые примеры:

double * рn; // рп может указывать на значение double

double * pa; // так же и ра

char * рс; // рс может указывать на значение char

double bubble = 3.2;

pn = &bubble; // присваивание адреса bubble переменной рn

pc = new char; // присваивание адреса выделенной памяти char переменной pc

pa = new double [30] ; // присваивание адреса массива из 30 double переменной pa

Разыменование указателей

Разыменование указателя — это получение значения, на которое он указывает. Для этого к указателю применяется операция разыменования (*). То есть, если рп — указатель на bubble, как в предыдущем примере, то *рп — значение, на которое он указывает, в данном случае — 3.2.

Вот некоторые примеры:

cout « *рn; // вывод значения bubble

*рс = 'S'; // помещение 'S' в область памяти, на которую указывает рс

Нотация массивов — второй способ разыменования указателя; например, рп [ 0 ] — это то же самое, что и *рn. Никогда не следует разыменовывать указатель, который не был инициализирован правильным адресом.

Различие между указателем и указываемым значением

Помните, если pt — указатель на int, то *pt — не указатель на int, а полный эквивалент переменной типа int. Указателем является просто pt.

Вот некоторые примеры:

int * pt = new int; // присваивание адреса переменной pt

*pt = 5; // сохранение 5 по этому адресу

Имена массивов

В большинстве контекстов C++ трактует имя массива как эквивалент адреса его первого элемента.

Вот пример:

int tacos[10]; // теперь tacos — то же самое, что и &tacos[0J

Одно исключение из этого правила связано с применением операции sizeof к имени массива. В этом случае sizeof возвращает размер всего массива в байтах.

Арифметика указателей

C++ позволяет добавлять целые числа к указателю. Результат добавления к указателю единицы равен исходному адресу плюс значение, эквивалентное количеству байт в указываемом объекте. Можно также вычесть один указатель из другого, чтобы получить разницу между двумя указателями. Последняя операция, которая возвращает целочисленное значение, имеет смысл только в случае, когда два указателя указывают на элементы одного и того же массива (указание одной из позиций за границей массива также допускается); при этом результат означает расстояние между элементами массива.

Ниже приведен ряд примеров:

int tacos[10] = {5,2,8,4,1,2,2,4,6,8};

int * pt = tacos; // предположим, что pf и tacos указывают на адрес 3000

pt = pt + 1; // теперь pt равно 3004, если int имеет размер 4 байта

int *ре = &tacos[9]; // ре равно 3036, если int имеет размер 4 байта

ре = ре - 1; // теперь ре равно 30 32 - адресу элемента tacos [8]

int diff = ре - pt; // diff равно 7, т.е. расстоянию между tacos [8] и tacos [1]

Динамическое и статическое связывание для массивов

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

int tacos[10]; // статическое связывание, размер фиксирован во время компиляции

Для создания массива с динамическим связыванием (динамического массива) используется операция new [ ]. Память для этого массива выделяется в соответствии с размером, указанным во время выполнения программы. Когда работа с таким массивом завершена, выделенная ему память освобождается с помощью операции

delete []:

int size;

cin >> size;

int * pz = new int [size] ; // динамическое связывание, размер устанавливается во время выполнения

delete [] pz; // освобождение памяти по окончании работы с массивом

Нотация массивов и нотация указателей

Использование нотации массивов с квадратными скобками эквивалентно разыменованию указателя:

tacos [0] означает *tacos и означает значение, находящееся по адресу tacos

tacos [3] означает * (tacos+ 3) и означает значение, находящееся по адресу tacos+ 3

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

Вот некоторые примеры:

int * pt = new int [10]; // pt указывает на блок из 10 значений int

*pt =5; // присваивает элементу 0 значение 5

pt[0] = 6; // присваивает элементу 0 значение 6

pt[9] = 44; // устанавливает десятый элемент (элемент номер 9) в 44

int coats[10];

* (coats + 4) = 12; // устанавливает coats [4] в 12

______________________________________________________________

В языке C существует сильная взаимосвязь между указателями и массивами, настолько сильная, что указатели и массивы действительно следует рассматривать одновременно. Любую операцию, которую можно выполнить с помощью индексов массива, можно сделать и с помощью указателей. Вариант с указателями обычно оказывается более быстрым, но и несколько более трудным для непосредственного понимания, по крайней мере для начинающего, поэтому советуем внимательнее отнестись к данной теме. Приступим.

Описание

int a[10];

определяет массив размера 10, т.е. набор из 10 последовательных объектов, называемых a[0], a[1], ..., a[9]. Запись a[i] соответствует элементу массива через i позиций от начала. Если pa - указатель целого, описанный как

int *pa;

то присваивание

pa = &a[0]

приводит к тому, что pa указывает на нулевой элемент массива a. Это означает, что pa содержит адрес элемента a[0]. Теперь, присваивание

x = *pa

будет копировать содержимое a[0] в x.

Согласно, арифметике указателей, если ра указывает на некоторый определенный элемент массива a, то по определению pa+1 указывает на следующий элемент, и вообще pa-i указывает на элемент, стоящий на i позиций до элемента, указываемого pa, а pa+i на элемент, стоящий на i позиций после. Таким образом, если pa указывает на a[0], то

*(pa+1)

ссылается на содержимое a[1], pa+i - адрес a[i], а *(pa+i) - содержимое a[i].

Эти замечания справедливы независимо от типа переменных в массиве a. Суть определения "добавления 1 к указателю", а также его распространения на всю арифметику указателей, состоит в том, что приращение масштабируется размером памяти, занимаемой объектом, на который указывает указатель. Таким образом, i в pa+i перед прибавлением умножается на размер объектов, на которые указывает pa.

Очевидно существует очень тесное соответствие между индексацией и арифметикой указателей. В действительности компилятор преобразует ссылку на массив в указатель на начало массива. В результате этого имя массива является указательным выражением. Отсюда вытекает несколько весьма полезных следствий. Так как имя массива является синонимом местоположения его нулевого элемента, то присваивание

pa = &a[0]

можно записать как pa = a.

Примечание: Кстати! При вычитании одного адреса из другого, будет получена разница между ними в элементах заданного ими типа, а не разница в байтах.

Еще более удивительным, по крайней мере на первый взгляд, кажется тот факт, что ссылку на a[i] можно записать в виде *(a+i). При анализировании выражения a[i] в языке C оно немедленно преобразуется к виду *(a+i); эти две формы совершенно эквивалентны. Если применить операцию & к обеим частям такого соотношения эквивалентности, то мы получим, что &a[i] и a+i тоже идентичны: a+i - адрес i-го элемента от начала a. С другой стороны, если pa является указателем, то в выражениях его можно использовать с индексом: pa[i] идентично *(pa+i). Короче, любое выражение, включающее массивы и индексы, может быть записано через указатели и смещения и наоборот, причем даже в одном и том же утверждении.

Имеется одно различие между именем массива и указателем, которое необходимо иметь в виду. Указатель является переменной, так что операции pa=a и pa++ имеют смысл. Но имя массива является константой, а не переменной: конструкции типа a=pa или a++ и любая другая попытка изменения будут незаконными.

Когда имя массива передается функции, то на самом деле ей передается местоположение начала этого массива. Внутри вызванной функции такой аргумент является точно такой же переменной, как и любая другая, так что имя массива в качестве аргумента действительно является указателем, т.е. переменной, содержащей адрес.

/* показывает на экран массив m */

void ShowElements(int *m, int size)

{

int n;

for (n = 0; n < size; m++,n++)

cout<<*m<<"\t";

}

Операция увеличения m совершенно законна, поскольку эта переменная является указателем, m++ никак не влияет на массив в обратившейся к ShowElements функции, а только увеличивает локальную для функции ShowElements копию адреса.

Описания формальных параметров в определении функции в виде

int m[];

и

int *m;

совершенно эквивалентны; какой вид описания следует предпочесть, определяется в значительной степени тем, какие выражения будут использованы при написании функции. Если функции передается имя массива, то в зависимости от того, что удобнее, можно полагать, что функция оперирует либо с массивом, либо с указателем, и действовать далее соответвующим образом. Можно даже использовать оба вида операций, если это кажется уместным и ясным.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]