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

Programmirovanie_-_1_kurs / Методические указания к лабораторным работам 3-4

.pdf
Скачиваний:
58
Добавлен:
09.06.2015
Размер:
700.69 Кб
Скачать

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

Пример:

int

X

= 10;

int*

pX

= &X;

int** ppX

= &pX;

cout << X << " " << pX << " " << ppX << "\n"; cout << **ppX << " "<< *ppX << " "<< ppX <<"\n";

Возможный результат:

10 002EFC38 002EFC2C

10 002EFC38 002EFC2C

Рисунок 6. Указатели на указатели

3.5. Ссылки

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

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

тип_данных& имя_ссылки = объект;

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

30

int A = 10;

int &B = A; // B – ссылка на переменную A

cout << B << " ";

// 10

A /= 2;

// 5

cout << B << " ";

B *=5;

// 25

cout << A << " ";

int C = 20;

 

B = C;

// 20

cout << A << " ";

4. РАБОТА С ПАМЯТЬЮ

4.1. Области памяти

В32-битных системах процессор использует для адресации памяти 4 байтные значения. Это означает, что каждому процессу (каждой программе) выделяется диапазон виртуальных адресов от 0x00000000 до 0xFFFFFFFF – всего 4 Гб адресного пространства.

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

Вадресном пространстве под программу отводится несколько областей, среди них:

область кода (code segment) – содержит исполнимые машинные коды функций программы

область данных (data segment) – содержит глобальные переменные, а также строковые константы

стек (stack) – содержит локальные переменные, параметры вызова функций, а также ряд других данных.

31

«куча» (heap) – используется для хранения динамических данных.

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

Для каждой переменной отводится определенное место в памяти. При этом память под переменные может выделяться статически и динамически.

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

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

динамическими переменными.

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

Динамическое выделение памяти, часто используется для массивов. Как вы знаете, при объявлении массива, необходимо указать его размер. Однако размер массива может быть не известен заранее, до выполнения программы. В таком случае, можно выделить память под массив динамически, после того, как размер массива станет известен.

4.2.1. Операторы new и delete

Для управления динамической памятью в языке C++ используются операторы new и delete.

32

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

При работе с единичными объектами для операторов new и delete применяется следующий синтаксис:

тип_объекта* имя = new тип_объекта;

...

delete имя;

При работе с массивами, синтаксис немного отличается:

тип_объекта* имя = new тип_объекта[размер];

...

delete [] имя;

Предположим, у нас имеется некоторая структура с названием MyStruct:

struct MyStruct

{

int a; int b; int c; int d;

};

Выделим память для хранения объекта этой структуры:

MyStruct* A;

A = new MyStruct;

Следует отметить, что обращение к полям структуры может осуществляться непосредственно через указатель, без операции разыменования. Для этого используется специальная операция – «стрелочка»: –>

A->a = 10; (*A).b = 20;

После работы с динамической структурой, необходимо освободить выделенную память:

delete A;

33

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

int* A = new int[50]; for (int i=0; i<50; i++) A[i] = i;

...

delete [] A;

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

#include <iostream> #include <conio.h> #include <stdlib.h> using namespace std;

// Программа вычисляет сумму элементов массива void main()

{

int N; // Количество элементов массива cout << "Введите размер массива: "; cin >> N;

//Выделяем память под массив int* ptr = new int[N];

//Вводим значения элементов массива for (int i=0; i<N; i++)

{

cout << "Элемент №" << i+1 << ": "; int k;

cin >> k; *(ptr+i) = k;

}

//Находим сумму элементов

int sum = 0;

for (int i=0; i<N; i++) sum += *(ptr+i);

cout << "Сумма = " << sum;

// Освобождаем память, занятую массивом delete [] ptr;

}

34

5. СТРОКИ

5.1. Представление строк в памяти

В языке C++ для представления строк не существует стандартного типа данных. Все текстовые строки представляются в памяти как массивы элементов типа данных char.

Любая строка заканчивается служебным символом с нулевым кодом – терминатором строки ('/0'). При объявлении строкового массива необходимо принимать во внимание наличие данного терминатора и резервировать под него память:

//Массив из 10 элементов char

//Может хранить строку длиной

//до 9 символов включительно char buffer[10];

Строковый массив может при объявлении инициализироваться начальным значением. При этом компилятор автоматически вычисляет размер будущей строки и добавляет в конец нулевой терминатор (рис. 7):

char str[] = "Строка";

Рисунок 7. Представление строки в памяти

Строка, указанная при инициализации, может иметь меньшую длину, чем объявляемый символьный массив

(рис. 8).

char str[10] = "Строка";

Рисунок 8. Представление строки в памяти

35

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

char empty_str[] = ""; // Пустая строка (1 байт)

5.2. Использование служебных символов в строках

Для обозначения в строке некоторых служебных символов используются специальные escape-последовательности (таблица 2).

 

 

 

Таблица 2. Служебные символы

 

Escape-

 

 

 

Описание

 

 

последовательность

 

 

 

 

 

 

\\

 

Вывод обратной черты

 

 

 

\'

 

Вывод апострофа

 

 

 

 

 

\”

 

Вывод кавычки

 

 

 

 

 

\a

 

Подача звукового сигнала

 

 

 

 

\b

 

Возврат курсора на один символ назад

 

\n

 

Перевод строки

 

 

 

 

 

\r

 

Возврат курсора на начало текущей строки

 

\t

 

Горизонтальная табуляция

 

 

\0

 

Терминатор строки

 

 

 

 

Таблица 3. Пример использования escape-последовательностей

 

 

Инструкция

 

Результат вывода

 

 

Cout << "12345\n67890";

 

12345

 

 

 

 

 

 

 

 

67890

 

 

 

 

Cout << "Кавычка - \"";

 

Кавычка - "

 

 

 

Cout << "1\t2\t3";

 

1

2

3

 

 

Cout << "abcdefg\r123";

 

123defg

 

 

 

5.3. Работа со строками

5.3.1. Копирование строк

Запись строки в массив символов char не может выполняться при помощи оператора присваивания, ведь оператор присваивания не позволяет копировать массивы.

char str[7];

// !!! Так делать НЕЛЬЗЯ

str = "Строка";

36

Для записи строки в массив используется специальная функция strcpy, определенная в заголовочном файле string.h (cstring).

Функция strcpy (сокр. от string copy) имеет следующий синтаксис вызова:

char* strcpy(char* str1, const char* str2)

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

char str[7]; strcpy(str, "Строка");

char s1[] = "Hello, world!"; char s2[100];

cout << strcpy(s2, s1);

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

char s1[10] = "Copy test"; char s2[10];

char* ptr = s1;

ptr += 5; // Устанавливаем ptr на 6-й символ strcpy(s2, ptr); // Копируем подстроку

cout << s2; // "test"

Рисунок 9. Копирование части строки

37

Еще одна функция strncpy позволяет указать максимальное количество символов, которое будет скопировано:

char* strncpy(char* str1, const char* str2, size_t num)

Следует отметить, что функция strncpy не проставляет нулевой терминатор в конец строки:

char s1[10] = "Copy test"; char s2[10] = "123456789"; strncpy(s2, s1, 4);

cout << s2; // "Copy56789"

5.3.2. Определение длины строки

Фактическая длина строки, хранящейся в массиве char, может не совпадать с размером самого массива. Как вы знаете, строка ограничивается нулевым терминатором, соответственно, ее длина равна количеству символов, предшествующих терминатору.

Для определения длины строки может использоваться функция strlen (сокр. от string length), объявленная в заголовочном файле string.h:

size_t strlen(const char* str)

Примеры:

char s1[] = "Hello";

cout << strlen(s1) << "\n"; // 5

char s2[100]; strcpy(s2, s1);

cout << strlen(s2) << "\n"; // 5

5.3.3. Конкатенация строк

Конкатенация строк – операция добавления подстроки в конец исходной строки. Другими словами, конкатенация – это склеивание строк. Конкатенация осуществляется при помощи функции strcat (сокр. от string concat):

38

char *strcat(char *str1, const char *str2);

Функция добавляет в конец исходной строки str1 строку str2.

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

char str[100] = "Hello"; strcat(str, ", world!");

cout << str; // "Hello, world!" char str1[100] = "Тестируем"; char str2[] = "конкатенацию"; strcat(str1, " "); strcat(str1, str2);

cout << str1; // "Тестируем конкатенацию"

Существует также возможность добавить к исходной строке только часть другой строки. Делается это при помощи функции strncat:

char* strncat(char* str1, const char* str2, size_t num);

Пример:

char str1[100] = "Hello "; char str2[] = "world!"; strncat(str1, str2, 5);

cout << str1; // "Hello world"

5.3.4. Сравнение строк

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

char str1[] = "String #1"; char str2[] = "String #2";

if (str1 == str2) ... // !!! Так делать нельзя

Сравнение строк может быть выполнено при помощи специальной функции strcmp (сокр. от string compare), входящей в состав библиотеки string.h:

int strcmp(const char *str1, const char *str2);

39