Константный указатель на константу 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Константный указатель на константу



Константный указатель на константу (попробуйте-ка трижды быстро произнести это вслух!) изменить вообще нельзя. Это неизменяемый адрес неизменяемой величины.

int i = 17;

int j = 29;

const int* const p; // Нельзя. Должен быть задан начальный адрес

const int* const p1 = &i; // Можно

*p1 = 29; // Нельзя

p1 = &j; // Нельзя

Читаем справа налево

const char* pTr; // pTr является указателем (*) на констатный char
char* const pTr; // pTr является констатным (const) указателем (*) на char

 

Время жизни динамических переменных – от точки создания до конца программы или до явного освобождения памяти. В C++ используется два способа работы с динамической памятью. Первый использует семейство функций malloc и достался в наследство от языка C, второй использует операции new и delete.

Существуют следующие способы инициализации указателя:

1. Присваивание указателю адреса существующего объекта:

• с помощью операции получения адреса:

 

int а = 5; // целая переменная

int* p = &a; // в указатель записывается адрес а

int* р (&а); // то же самое другим способом

 

• с помощью значения другого инициализированного указателя:

 

int* r = р;

 

• с помощью имени массива или функции, которые трактуются как адрес:

int b[10]; // массив

int* t = b; // присваивание адреса начала массива

 

void f(int а){ /*... */ } // определение функции

void (*pf) (int); // указатель на функцию

pf = f; // присваивание адреса функции

 

2. Присваивание указателю адреса области памяти в явном виде:

 

char* cp = (char *) 0хВ8000000;

 

Здесь 0хВ8000000 — шестнадцатеричная константа, (char *) – операция приведения типа: константа преобразуется к типу «указатель на char».

3. Присваивание пустого значения:

 

int* suxx = NULL;

int* rulez = 0;

 

В первой строке используется константа NULL, определенная в некоторых заголовочных файлах C как указатель, равный нулю. Рекомендуется использовать просто 0, так как это значение типа int будет правильно преобразовано стандартными способами в соответствии с контекстом.

4. Выделение участка динамической памяти и присваивание ее адреса указателю:

• с помощью операции new:

int* n = new int; // 1

int* m = new int (10); // 2

int* q = new int [10]; // 3

 

• с помощью функции mallос:

int* u = (int *)malloc(sizeof(int)); // 4

 

В операторе 1 операция new выполняет выделение достаточного для размещения величины типа int участка динамической памяти и записывает адрес начала этого участка в переменную n. Память под саму переменную n (размера, достаточного для размещения указателя) выделяется на этапе компиляции. В операторе 2, кроме описанных выше действий, производится инициализация выделенной динамической памяти значением 10.

В операторе 3 операция new выполняет выделение памяти под 10 величин типа int (массива из 10 элементов) и записывает адрес начала этого участка в переменную q, которая может трактоваться как имя массива. Через имя можно обращаться к любому элементу массива.

В операторе 4 делается то же самое, что и в операторе 1, но с помощью функции выделения памяти malloc, унаследованной из библиотеки С. В функцию передается один параметр – количество выделяемой памяти в байтах.

Операцию new использовать предпочтительнее, чем функцию malloc, особенно при работе с объектами.

Освобождение памяти, выделенной с помощью операции new, должно выполняться с помощью delete, а для выделенной с помощь malloc осовобождаться с помощью free.

 

Для описанных выше переменных освобождение памяти будет выглядеть так:

 

delete n; // 1

delete m; // 2

delete []q; // 3

free(u); // 4

 

При выделении памяти с помощью new[], необходимо применять delete[]. Размерность массива при этом не указывается.

 

Операции с указателями

С указателями можно выполнять следующие операции: разыменовывание (или косвенное обращение к объекту) (*), присваивание, сложение с константой, вычитание, инкремент (++), декремент (--), сравнение, приведение типов.

При работе с указателями часто используется операция получения адреса &.

Операция разыменования предназначена для доступа к величине, адрес которой хранится в указателе. Эту операцию можно использовать как для получения, так и для изменения значения величины (если она не объявлена как константа):

 

char а; // переменная типа char

/* выделение памяти под указатель (char *р) и

под динамическую переменную типа char (new char)*/

char *р = new char;

 

*р = 'Ю'; // присваивание значения динамической переменной

 

// копирование содержимого ячейки памяти на которую указывает p в переменную a

а = *р;

 

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

Арифметические операции с указателями (сложение с константой, вычитание, инкремент и декремент) автоматически учитывают размер типа величин, адресуемых указателями. Эти операции применимы только к указателям одного типа и имеют смысл в основном при работе со структурами данных, последовательно размещенными в памяти, например, с массивами.

Инкремент перемещает указатель к следующему элементу массива, декремент – к предыдущему. Фактически значение указателя изменяется на величину sizeof(тип). Если указатель на определенный тип увеличивается или уменьшается на константу, его значение изменяется на величину этой константы, умноженную на размер объекта данного типа, например:

 

short *р = new short [5];

p++; // значение р увеличивается на 2

long *q = new long [5];

q++; // значение q увеличивается на 4

 

Разность двух указателей – это разность их значений, деленная на размер типа в байтах (в применении к массивам разность указателей, например, на третий и шестой элементы равна 3). Суммирование двух указателей не допускается.

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

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

 

int а = 5; // переменная a

int *p = &a; // в указатель записывается адрес а

 

Ссылки

Ссылка представляет собой синоним имени, указанного при инициализации ссылки. Ссылку можно рассматривать как указатель, который всегда разыменовывается. Формат объявления ссылки:

 

тип & имя;

 

где тип — это тип величины, на которую указывает ссылка, & — оператор ссылки, означающий, что следующее за ним имя является именем переменной ссылочного типа, например:

 

int kol;

int& раl = kol: // ссылка раl — альтернативное имя для kol

 

Переменная-ссылка должна явно инициализироваться при ее описании, кроме случаев, когда она является параметром функции, описана как extern или ссылается на поле данных класса

После инициализации ссылке не может быть присвоена другая переменная.

Тип ссылки должен совпадать с типом величины, на которую она ссылается.

Не разрешается определять указатели на ссылки, создавать массивы ссылок и ссылки на ссылки.

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

Ссылка, в отличие от указателя, не занимает дополнительного пространства в памяти и является просто другим именем величины. Операция над ссылкой приводит к изменению величины, на которую она ссылается.

Указатели и массивы

Доступ к произвольному элементу массива обеспечивается по имени массива и индексу – целочисленному смещение от начала:

 

имя_массива [ индекс ]

 

Имя массива является указателем-константой. К нему приемлемы все правила адресной арифметики, связанные с указателями. Для любого массива соблюдается равенство:

 

имя_массива == &имя_массива == &имя_массива[ ]

 

Доступ к элементам массива возможен по его имени как указателю. Если описан массив

int z[3];

то *zаналогично z [ 0 ], а *( z + 1 ) == z [ 1 ] и т.д. Т.е.

 

z [ 0 ] == *( z ) == *( z + 0 )

z [ 1 ] == *( z + 1 )

z [ 2 ] == *( z + 2 )

z [ i ] == *( z + i )

 

Так как имя массива есть не просто указатель, а указатель константа, то значение имени невозможно изменить. Получить доступ ко второму элементу массива int x [ 4 ] c помощью выражения *(++ z ) будет ошибкой, а выражение *( x + 1 ) допустимо.

Рассмотрим такой пример обращения к массиву: вывести на экран элементы массива, используя имя массива как указатель.

 

#include <iostream>

using namespace std;

void main()

{

int x[ ]= {1, 2, 3, 4, 5, 0};

int i = 0;

int size = sizeof(x)/sizeof(x[0]);

while (i < size)

{

cout << endl << *(x + i); //аналогично x[i]

i++;

}

}

 

Замечание.

Обращение к элементу массива относят к постфиксному выражению вида PE[IE]. PE – указатель на нужный тип, IE – целочисленный тип, PE[IE] – индексированный элемент этого массива. Аналогично при использовании адресной арифметики *(PE + IE). Поскольку сложение коммутативно, то запись *(PE + IE) эквивалентна *(IE + PE), а, следовательно, и IE[PE] именует тот же элемент массива, что и PE[IE]. Т.е. приведенного выше примера будет справедливо:

 

*(x + i) == *(i + x) == i[x] == x[i]

 

Динамические массивы

Если до начала работы программы неизвестно, сколько в массиве элементов, в программе следует использовать динамические массивы. Память под них выделяется с помощью операции new или функции malloc в динамической области памяти во время выполнения программы. Адрес начала массива хранится в переменной, называемой указателем. Например:

int n = 10:

int *а = new int[n];

Если динамический массив в какой-то момент работы программы перестает быть нужным и мы собираемся впоследствии использовать эту память повторно, необходимо освободить ее с помощью операции delete[].

Таким образом, время жизни динамического массива, как и любой динамической переменной, с момента выделения памяти до момента ее освобождения. Область действия зависит от места описания указателя, через который производится работа с массивом. Локальная переменная при выходе из блока, в котором она описана, «теряется». Если эта переменная является указателем и в ней хранится адрес выделенной динамической памяти, при выходе из блока эта память перестает быть доступной, однако не помечается как свободная, поэтому не может быть использована в дальнейшем. Это называется утечкой памяти и является распространенной ошибкой.

 

Примеры программ

Пример 1. Найти сумму элементов в динамическом массиве. Размерность массива ввести с клавиатуры. Использовать различные варианты при обращении к массиву.

#include <iostream>

using namespace std;

void main()

{

setlocale(LC_ALL, "Russian");

//введем размерность массива

int N;

cout << "Введите размерность массива: ";

cin >> N;

//создаем массив динамически

int *a = new int[N];

//введем элементы массива с клавиатуры

for (int i = 0; i < N; i++)

{

cout << "Введите " << i <<"-й элемент массива: ";

cin >> a[i];

}

//объявим переменную для хранения суммы элементов

int s = 0;

//просуммируем элементы массива

for (int i = 0; i < N; i++)

s += *(a + i);

//выведем на экран элементы массива и их сумму

cout << "a:";

for (int i = 0; i < N; i++)

cout << " " << i[a];

cout << endl << "Сумма элементов: " << s << endl;

//очищаем выделенную памать

delete[] a;

}

 

 

Пример 2. //Удалить из матрицы строку с номером K

#include <iostream>

using namespace std;

void main()

{

setlocale(LC_ALL, "russian");

int n, m;//размерность матрицы

int i, j;

cout << "\nEnter n";

cin >> n;//строки

cout << "\nEnter m";

cin >> m;//столбцы

//выделение памяти

int **matr = new int*[n];// массив указателей на строки

for (i = 0; i<n; i++)

matr[i] = new int[m];//память под элементы матрицы

//заполнение матрицы

for (i = 0; i<n; i++)

for (j = 0; j<m; j++)

matr[i][j] = rand() % 10;//заполнение матрицы

//печать сформированной матрицы

for (i = 0; i<n; i++)

{

for (j = 0; j<m; j++)

cout << matr[i][j] << " ";

cout << "\n";

}

//удаление строки с номером к

int k;

cout << "\nEnter k";

cin >> k;

 

for (i = k - 1; i < n - 1; i++)

for (j = 0; j < m; j++)

matr[i][j] = matr[i + 1][j];

delete matr[--n];

//печать новой матрицы

for (i = 0; i<n; i++)

{

for (j = 0; j<m; j++)

cout << matr[i][j] << " ";

cout << "\n";

}

 

//удаление старой матрицы

for (i = 0; i<n; i++)

delete matr[i];

delete[]matr;

system("pause");

}

 



Поделиться:


Последнее изменение этой страницы: 2016-12-11; просмотров: 416; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 18.216.227.76 (0.057 с.)