Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
лекции по С++.doc
Скачиваний:
34
Добавлен:
15.12.2018
Размер:
2.31 Mб
Скачать

5. Проблемы, связанные с указателями

Самые распространенные проблемами при использовании указателей:

  1. попытка работать с указате­лем, не содержащим адреса ОП, выделенной под переменную;

  2. потеря значения указателя из-за присваивания ему нового значения до освобождения ОП, которую он адресует;

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

При объявлении указателя на значение любого типа ОП для ад­ресуемого значения не резервируется. Выделяется только ОП для переменной-указателя, но указатель при этом не получает никакого значения. Рассмотрим пример, содержащий грубую ошибку – попытку работать с не­инициализированным указателем:

int *х; //указателю х выделена ОП, но х не содержит адреса ОП для

//конкретной переменной

*x=123; //ошибка

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

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

Исправить ситуацию можно с помощью одной из функций выделения динамической памяти, например функции malloc(). Форма обращения к функции malloc():

<имя_указателя> = (<тип_указателя>)malloc(<объем_ОП>);

где <имя_указателя> – имя переменной-указателя; <тип_указателя> – тип значения, возвращаемого функцией malloc(); <объем_ОП> – количество байтов ОП, выделяемой адресуемой переменной.

Например,

х=(int*)malloc(sizeof(int));

При этом из кучи выделяется 4 байта ОП для целого значения, а адрес его размещения заносится в указатель х. При таком выделении памяти работоспособность программы не нарушается. Аргумент функции malloc() определяет объем ОП для целого значения с помощью операции определения объема ОП для размещения значения целого типа sizeof(int). Операция (int*), записанная перед функцией, означает, что адрес, возвращаемый функцией malloc(), рассматривается как указатель на переменную целого типа. Это операция приведения типов.

Таким образом, ошибки не будет при использовании операторов:

int *х; //x – имя указателя

x=(int*)malloc(sizeof(int));

//выделена ОП целому значению, на которое указывает х, и переменой х

//присваивается адрес выделенной ОП

*x=123; // переменная, на которую указывает х, получила значение

// 123

Освобождение ОП в куче выполняет функция free(). Ее аргументом является имя указателя, содержащего адрес освобождаемой и ранее выделенной динамической памяти, например free(x);

6. Поразрядные операции

Каждая переменная, объявленная в программе занимает в ОП некоторый участок, который содержит столько байт, сколько требует для размещения в ОП тип переменной. В свою очередь каждый байт – совокупность из восьми бит. Бит – это минимальная единица хранения информации в памяти компьютера. В языке С++ изменять значение переменной можно не только при помощи операции присваивания (т.е. целиком), но и с помощью поразрядных операций (по битам). Поразрядные операции называют побитовыми операциями – операции, с помощью которых можно изменять значение переменной по битам. Побитовыми операциями являются проверка (логические), сдвиг и присвоение значения битам данных. Такие операции могут осуществляться только над переменными типа int или char. Побитовые операции и результаты их работы приведены в табл.7.

Таблица 7

Значение операнда 1 (оп1)

Значение операнда 2 (оп2)

Название операции

Поразрядное отрицание

Исключающее ИЛИ

Поразрядное И

Поразрядное ИЛИ

~(оп1)

(оп1)^(оп2)

(оп1)&(оп2)

(оп1)|(оп2)

Результат

0

1

1

1

0

1

0

0

1

0

0

0

1

1

0

0

1

1

1

0

0

1

0

1

При помощи операций легко проверить «состояние» того или иного бита переменной. Для этого на переменную «накладывают» маску – переменную, которая содержит проверяемую последовательность 0 и 1. Типы маски и переменной, на которую накладывается маска, должны совпадать. Наложение осуществляется при помощи операций ^, &, | :

~

Результат двойного побитового отрицания – первоначальное значение переменной

11001101 == 00110010

~00110010 == 11001101

^

11001101

&

11001101

|

11001101

01101100

01101100

01101100

10100001

01001100

10101101


Кроме поразрядных операций (см. табл.7) существуют две побитовые операции сдвига: << – операция сдвига всех битов переменной влево; >> – операция сдвига всех битов переменной вправо. При использовании этих операций указывается число разрядов, на которое происходит сдвиг битов вправо или влево. Побитовый сдвиг не является циклическим, т.е. если при сдвиге битов произошел выход за границы участка ОП, где размещена изменяемая переменная, то вышедшие за границу биты теряются. С помощью операций сдвига можно умножать или делить переменную на число, кратное двум. Форма записи этих операций:

<имя_переменной> >> <количество_разрядов>;

<имя_переменной> << <количество_разрядов>;

Например,

p=p>>3; //значение переменной p уменьшается в 23 = 8 раз

c=c<<4; // значение переменной p увеличивается в 24 = 16 раз

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

Лекция 13

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

Цели:

  • получить представление о способах объявления и обращения к элементам массивов;

  • освоить методику написания алгоритмов с использованием динамических массивов, перевода таких алгоритмов на язык программирования С++ и разработки соответствующего проекта в среде Visual C++ 6.0.

1. Способы объявления и обращения к элементам массивов

1.1. Способы объявления и обращения

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

Объявление одномерного массива с явным указанием количества элементов массива:

<тип_элементов_массива> <имя_массива> [<количество_элементов>];

Обращение к элементам одномерного массива в общем случае можно представить индексированием:

<имя_массива> [<выражение>];

где <имя_массива> – указатель–константа, адрес нулевого элемента массива; <выражение> определяет индекс элемента массива.

Элементы одномерного массива располагаются в ОП последовательно друг за другом: нулевой, первый и т.д. При объявления массива

int а[10];

компилятор выделяет массиву ОП для размещения его элементов в размере sizeof(<тип_элементов_массива>)*<размер_массива> байт, т.е. 4 ∙ 10 = 40 байт. Запись &a[i] равносильна записи (а + i). В программе &a[i] или (а + i) определяет адрес i-го элемента массива. На машинном уровне (в соответствии с определением операций над указателями) адрес i-го элемента массива &a[i] формируется в виде а + i*sizeof(int) .

В связи с вышеизложенным, будут справедливы соотношения:

а + 0 &а[0] – адрес нулевого элемента массива а;

а + 2 &а[2] – адрес второго элемента массива а;

а + i &a[i] – адрес i-го элемента массива а;

*(а + 0) *(&а[0]) а[0] – значение нулевого

элемента массива а;

*(а+2) а[2] – значение второго элемента массива;

*(а + i) a[i] – значение i-го элемента массива;

*а + 2 а[0] + 2 – сумма значений а[0] и 2.

Если р – указатель на тип данных, совпадающий с типом элементов массива а и адрес, который содержит указатель р, совпадает с адресом массива а, то а и р взаимозаменяемы. При этом

р &а[0] а + 0;

р + 2 &а[2] а + 2;

*(р + 2) *(&а[2]) а[2];

*(p + i) *(&a[i]) a[i].