Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

OOP_C++ / 05

.htm
Скачиваний:
20
Добавлен:
02.02.2015
Размер:
22.24 Кб
Скачать

05 - Указатели и ссылки. Строки Содержание     Предыдущее занятие     Следующее занятие

Занятие 05 Указатели и ссылки. Строки 1 Указатели Указатель – это объект, содержащий адрес другого объекта и позволяющий косвенно манипулировать этим объектом. Каждый указатель ассоциируется с некоторым типом данных, который определяет тип указателя.

Для определения указателя необходимо перед его идентификатором (после имени типа) разместить знак *:

int *pi; // Указатель на объект целого типа Унарная операция взятия адреса обозначается знаком &. С помощью данной операции можно получить адрес объекта, к которому она применяется. Операция & может быть применена только к объектам, расположенным в памяти (lvalue). Такими объектами являются переменные и элементы массивов. Операцию взятия адреса нельзя применять ни к выражениям, ни к константам.

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

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

int i = 0; int *pi = &i; // Указатель на i Существует специальный тип указателя void*, который называется обобщенным указателем. . Указателю такого типа можно присвоить адрес объекта любого типа без явного приведения. Следует избегать использования указателя void*, так как это сопряжено с ошибками при последующем явном приведении типов и разыменовании.

Указателю можно присваивать целое значение 0 (трактуется как "указатель ни на что не указывает") и сравнивать с нулем.

Можно определить указатель, который хранит адрес константы (указатель на константу). Для этого нужно ко всему определению добавить префикс const.

const double *pd; Константным является объект, а не указатель, который на него указывает. Указателю на константу можно присвоить адрес обычной переменной. Но такую переменную нельзя изменить через этот указатель. Указатель на const нельзя присвоить указателю типа void*.

Существует константный указатель, которые может указывать на любые объекты. Значения этих объектов можно менять, но сам указатель нельзя изменить после определения. Для описания константного указателя нужно поставить const не перед всем определением, а перед именем указателя справа от *. Такой указатель нужно инициализировать при определении:

int i = 1; int * const pi = &i; Указателю не может быть присвоена величина, не являющаяся адресом. Нельзя присвоить указателю одного типа значение, являющееся адресом объекта другого типа.

Указатели могут быть использованы в арифметических выражениях. Для указателей определены операции сравнения, сложения указателя с целым числом, вычитание двух указателей, а также операция индексирования (операция []). Прибавление к указателю 1 увеличивает содержащееся в нем значение на размер области памяти, отводимой объекту соответствующего типа. Такие действия над элементами массива получили название адресной арифметики.

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

int a[10]; int* pa = a; К указателю pa также применима операция обращения по индексу:

рa[0] = 0; // То же самое, что a[0] = 0; cout << pa[0]; // То же самое, что cout << a[0]; С помощью выражения pa + i (или a + i) можно получить адрес i-го элемента массива.

Элемент массива a разрешается изображать и виде указателя со смещением и в виде имени массива с индексом. Имеет место тождество:

a[i] == *(a+i); Начальный элемент массива можно просто записать как *а, т.е.

а[0] == *a; Чтобы получить адрес следующего элемента, можно использовать операцию инкремента:

pa++; // но не a++, так как a - константный указатель Аналогично могут использоваться операции вычитания целого значения и декремента. Если указатели указывают на элементы одного массива, то они могут сравниваться ( по <, >, >= ,<= == и !=). Для двух указателей на элементы одного массива допускается операция вычитания. Ее результат - целый (количество элементов начиная с элемента с меньшим индексом, исключая элемент с большим индексом).

2 Массивы символов Массивы символов могут инициализироваться так называемым строковым литералом. Строковый литерал - это строковая константа . Строка символов хранится в памяти как массив, и доступ к ней осуществляется при помощи указателя типа char*.

char *st = "C++"; Стандартная библиотека С предоставляет набор функций для манипулирования строками:

// возвращает длину строки: int strlen(const char*); // сравнивает две строки: int strcmp(const char*, const char*); // копирует одну строку в другую char*: strcpy(char*, const char*); Для использования этих функций необходимо включить заголовочный файл: #include<cstring> или #include<string.h> Указатель на char, с помощью которого мы обращаемся к строке, указывает на соответствующий строке массив символов.

При работе со стандартными строками в стиле С (массивами символов) нельзя сравнивать строки с помощью == и присваивать их друг другу с помощью =. Для чтения строковых данных целесообразно вначале выделить память для массива символов, достаточную для размещения строки:

char s[20]; cin >> s; Использование строк в стиле С стало менее целесообразным в связи с появлением класса string стандартной библиотеки С++.

3 Пример работы с массивами символов Необходимо проинициализировать массив строк списком начальных значений, рассортировать по алфавиту и вывести результат сортировки на экран.

Сортировка строк по алфавиту аналогична сортировке массива по возрастанию элементов. Алгоритм сортировки массива по возрастанию заключается в следующем:

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

если после просмотра всех пар элементов оказалось, что никаких два элемента не пришлось менять местами, сортировка завершается, в противном случае повторяется пункт 1.

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

bool mustSort; то общая схема сортировки массива а из n элементов по возрастанию будет выглядеть так:

do { mustSort = false; for (int i = 0; i < n - 1; i++)// Пар на 1 меньше, чем элементов if (a[i] > a[i+1]) { // Обмен местами элементов mustSort = true; } } while (mustSort); Применительно к сортировке строк по алфавиту данную задачу можно решить двумя способами.

Первый способ Строки можно представить как массивы символов, массив строк - как двумерный массив:

const int n = 5; char s[n][7] = {"John", "Bob", "Tom", "Andrew", "Bill"}; В описании массива второй индекс - максимальная длина строки (в данном случае - 6 символов в строке "Andrew" плюс символ с кодом 0). Обращение к s с одним индексом (s[i]) дает i-й элемент массива строк.

Для выяснения необходимости обмена местами элементов нужно использовать функцию strcmp():

if (strcmp(s[i], s[i+1]) > 0) Данная функция возвращает отрицательное значение, если s[i] находится по алфавиту до s[i+1], ноль, если строки равны, и положительное число в противном случае.

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

char temp[7]; strcpy(temp, s[i]); strcpy(s[i], s[i+1]); strcpy(s[i+1], temp); Теперь можно записать всю программу:

#include <iostream> #include <cstring> using namespace std; int main(int argc, char* argv[]) { const int n = 5; char s[n][7] = {"John", "Bob", "Tom", "Andrew", "Bill"}; bool mustSort; do { mustSort = false; for (int i = 0; i < n - 1; i++) if (strcmp(s[i], s[i+1]) > 0) { char temp[7]; strcpy(temp, s[i]); strcpy(s[i], s[i+1]); strcpy(s[i+1], temp); mustSort = true; } } while (mustSort); for (int i = 0; i < n; i++) cout << s[i] << endl; return 0; } Второй способ Для представления массивов строк можно определить и проинициализировать массив указателей на символ:

const int n = 5; char* s[n] = {"John", "Bob", "Tom", "Andrew", "Bill"}; Теперь при обмене местами элементов массива вместо копирования строк можно изменить значения указателей:

char* temp = s[i]; s[i] = s[i+1]; s[i+1] = temp; mustSort = true; После такого обмена указатель s[i] указывает на строку, на которую ранее указывал s[i+1], и наоборот. Теперь можно записать всю программу, которая в остальном аналогична предыдущей:

#include <iostream> #include <cstring> using namespace std; int main(int argc, char* argv[]) { const int n = 5; char* s[n] = {"John", "Bob", "Tom", "Andrew", "Bill"}; bool mustSort; do { mustSort = false; for (int i = 0; i < n - 1; i++) if (strcmp(s[i], s[i+1]) > 0) { char* temp = s[i]; s[i] = s[i+1]; s[i+1] = temp; mustSort = true; } } while (mustSort); for (int i = 0; i < n; i++) cout << s[i] << endl; cin.get(); return 0; } 4 Динамическое распределение памяти Динамическим распределением памяти называется явное выделение и освобождение памяти во время выполнения программы. Свободная память выделяется для хранения объекта с помощью операции new. После слова new указывается тип объекта, который должен быть размещен динамически. Операция new возвращает указатель на динамически созданный объект:

int *p = new int; Для инициализации динамически создаваемого объекта начальным значением это значение указывается в скобках после специализации типа:

int *p = new int(100); // *p == 100 Если после специализации типа указать в квадратных скобках значение размерности, это приведет к динамическому размещению массива. Размерность может быть выражением произвольной сложности (не только константой). Операция new возвращает указатель на первый элемент массива:

int i = 100; double *a = new double[i*2]; После того как динамически размещенный объект оказался ненужным, память следует освободить с помощью операции delete:

int *p = new int; // выделение памяти delete p; // освобождение памяти Если динамически был размещен массив, для освобождения памяти необходимо между delete и указателем вставить пару пустых квадратных скобок:

int *a = new int[100]; delete [] a; Необходимо следить, чтобы вся память, выделенная с помощью операции new, была освобождена с помощью операции delete. К указателю на константный объект нельзя применять операцию delete.

5 Примеры работы с массивами в динамической памяти Пример 1 Необходимо проинициализировать одномерный массив вещественных чисел списком начальных значений, определить количество отрицательных элементов, динамически разместить новый массив размерности, необходимой для хранения всех отрицательных элементов исходного массива. Записать отрицательные элементы в новый массив и вывести результат на экран.

Инициализируем массив:

const int n = 7; double a[7] = {10, 11, -3, -2, 0, -1, 4};

Подсчитываем количество отрицательных элементов:

int count = 0; for (int i = 0; i < n; i++) if (a[i] < 0) count++;

Динамически размещаем новый массив размерности count:

double *b = new double [count];

Записываем в новый массив отрицательные элементы. При этом необходимо использовать отдельную переменную для индексирования массива b:

int j = 0; // Индекс массива b for (int i = 0; i < n; i++) if (a[i] < 0) { b[j] = a[i]; j++; }

Выводим новый массив и освобождаем динамическую память:

for (j = 0; j < count; j++) // Переменная j была определена ранее cout << b[j] << ' '; delete [] b; Пример 2 Необходимо ввести с клавиатуры размерность двумерного массива (количество строк и количество столбцов), разместить двумерный массив целых чисел в динамической памяти, ввести с клавиатуры значения элементов массива, найти сумму первых элементов строк и вывести ее на экран.

Вначале пользователь вводит размерность массива - m (количество строк) и n (количество столбцов):

int m, n; cout << "Enter m and n:" << endl; cin >> m >> n; Для решения задачи размещения двумерного массива в динамической памяти можно использовать следующий подход: разместить вначале массив указателей на целое размерности m, затем каждую строку разместить в динамической памяти в цикле, причем каждый указатель размещенного ранее массива указателей будет указывать на соответствующую строку:

int **a = new int* [m]; for (int i = 0; i < m; i++) a[i] = new int [n]; Далее в цикле вводятся все элементы массива:

cout << "Enter elements of array:" << endl; for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) cin >> a[i][j]; Вычисляется сумма элементов первого (нулевого) столбца и выводится на экран:

int b = 0; for (int i = 0; i < m; i++) b += a[i][0]; cout << b; Занятую ранее динамическую память необходимо освободить:

for (int i = 0; i < m; i++) delete [] a[i]; delete [] a; 6 Ссылки Ссылка представляет собой альтернативное имя объекта. Тип ссылки определяется спецификацией типа, за которой следует операция &. Так же, как и константа, ссылка при определении должна быть инициализирована некоторым существующим объектом:

int i = 10; int &j = i; j теперь будет другим именем для i. Ссылка иногда также называется псевдонимом. Любые действия с объектом могут теперь производиться как с использованием основного имени, так и с использованием ссылки.

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

Нельзя определять массив ссылок:

int &a[10]; // ошибка Нет константных ссылок, однако существуют ссылки на константный объект. Через ссылку на константный объект нельзя менять сам объект:

int i = 100; const int &ciref = i; ciref = 200; // ошибка Ссылку на константный объект можно инициализировать константой и объектом не ее типа.

7 Задания на самостоятельную работу Задание 1 Проинициализировать массив строк списком начальных значений, рассортировать по возрастанию длин строк и вывести результат сортировки на экран.

Задание 2 Реализовать программу, в которой вводится размерность двумерного массива (количество строк и количество столбцов), вводятся элементы массива и формируется новый одномерный массив сумм элементов строк.

 

Содержание     Предыдущее занятие     Следующее занятие

 

© 2001 - 2006 Иванов Л.В.

Соседние файлы в папке OOP_C++