Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
для фаэ си++.doc
Скачиваний:
6
Добавлен:
18.04.2019
Размер:
403.97 Кб
Скачать

Инициализация указателей

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

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

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

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

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

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

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

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

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

  • значения другого инициализированного указателя:

int* r = p;

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

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

int* t = b;// Присваивание имени массива

...

void f(int a ){ /* … */ }// Определение функции

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

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

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

char* vp = (char *)0xB8000000;//шестнадцатиричная константа

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

int* suxx = NULL;

int* rulez = 0;

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

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

int* n = new int;// 1

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

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

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

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

Освобождение памяти, выделенной с помощью операции new, должно выполняться с помощью delete, а памяти, выделенной функцией malloc — посредством функции free. При этом переменная-указатель сохраняется и может инициализироваться повторно. Приведенные выше динамические переменные уничтожаются следующим образом:

delete n; delete m; delete [] q; free (u);

ВНИМАНИЕ

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

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

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

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

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

char * p = new char;/*выделение памяти под указатель и под динамическую переменную типа char */

*p = 'Ю'; a = *p;//присваивание значения обеим переменным

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

#include <stdio.h>

int main()

{unsigned long int A=0Xсс77ffaa;

unsigned int* pint =(unsigned int *) &A;

unsigned char* pchar =(unsigned char *) &A;

printf(" | %x | %x |", *pint, *pchar);}

на IBM PC выведет на экран строку:

| ffaa | aa |

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

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

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

Присваивание указателей данных указателям функций (и наоборот) недопустимо.

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

Инкремент перемещает указатель к следующему элементу массива, декремент — к предыдущему.

Фактически значение указателя изменяется на величину sizeof(тип).

Разность двух указателей — это разность их значений, деленная на размер типа в байтах. Суммирование двух указателей не допускается.

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

*p++ = 10;

То же самое можно записать подробнее:

*p = 10; p++;

Выражение (*p)++, напротив, инкрементирует значение, на которое ссылается указатель.

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

Массивы

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

Конечная именованная последовательность однотипных величин называется массивом. Описание массива в программе отличается от описания простой переменной наличием после имени квадратных скобок, в которых задается количество элементов массива (размерность):

float a [10]; //Пример описания массива из 10 вещественных чисел

Элементы массива нумеруются с нуля. Инициализирующие значения для массивов записываются в фигурных скобках.:

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

Размерность массива может быть задана только целой положительной константой или константным выражением.

Для доступа к элементу массива после его имени указывается номер элемента (индекс) в квадратных скобках. В следующем примере подсчитывается сумма элементов массива.

Пример #1.

#include <iostream.h>

int main(){

const int n = 10;

int marks[n] = {3, 4, 5, 4, 4};

for (int i = 0, sum = 0; i<n; i++) sum += marks[i];

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

Размерность массивов предпочтительнее задавать с помощью типизированных констант.

Динамические массивы создают с помощью операции new, при этом необходимо указать тип и размерность, например:

float *p = new float [100];

В этой строке создается переменная-указатель на float, в динамической памяти отводится непрерывная область, достаточная для размещения 100 элементов вещественного типа, и адрес ее начала записывается в указатель p. Динамические массивы нельзя при создании инициализировать, и они не обнуляются.

Преимущество динамических массивов состоит в том, что их размерность может не быть константой.

Память, зарезервированная под динамический массив с помощью new [], должна освобождаться оператором delete [], а память, выделенная функцией malloc — посредством функции free, например:

delete [] p; free (q);

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

Многомерные массивы задаются указанием каждого измерения в квадратных скобках, например, оператор

int matr [6][8];

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

Для доступа к элементу многомерного массива указываются все его индексы, например, matr[i][j].

Инициализация многомерного массива:

int mass2 [][]={ {1, 1}, {0, 2}, {1, 0} };

int mass2 [3][2]={1, 1, 0, 2, 1, 0};

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

matr = new int [a] [b];

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

Пример #2.

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

#include <stdio.h>

int main(){

const int nstr = 4, nstb = 5;//Размерности массива

int b[nstr][nstb];

int i, j;

for (i = 0; i<nstr; i++)//Ввод массива

for (j = 0; j<nstb; j++) scanf("%d", &b[i][j]);

int istr = -1, MaxKol = 0;

for (i=0; i<nstr; i++){//Просмотр массива по строкам

int Kol = 0ж

for (j = 0; j<nstb; j++)if (b[i][j]==0)Kol++;

if (Kol>MaxKol){istr = i; MaxKol = Kol;}

}

printf(" Исходный массив:\n"); for (i = 0; i<nstr; i++){

for (j = 0; j<nstb; j++)printf("%d ", b[i][j]);

printf("\n");}

if (-1 == istr)printf("Нулевых элементов нет");

else printf("Номер строки: %d", istr);

return 0;}

Строки

Строка представляет собой массив символов, заканчивающийся нуль-символом. Нуль-символ — это символ с кодом, равным 0, что записывается в виде управляющей последовательности '\0'. По положению нуль-символа определяется фактическая длина строки. Строку можно инициализировать строковым литералом:

char str[10] = "Vasia";

В этом примере под строку выделяется 10 байт, 5 из которых занято под символы строки, а шестой — под нуль-символ. Если строка при определении инициализируется, ее размерность можно опускать (компилятор сам выделит соответствующее количество байт):

char str[] = "Vasia";//Выделено и заполнено 6 байт

Оператор

char *str = "Vasia"

создает не строковую переменную, а указатель на строковую константу.

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

Библиотека предоставляет возможности копирования, сравнения, объединения строк, поиска подстроки, определения длины строки и т.д. (возможности библиотеки описаны в разделе «Функции работы со строками и символами»), а также содержит специальные функции ввода строк и отдельных символов с клавиатуры и из файла.

Пример #3.

Пример (программа запрашивает пароль не более трех раз).

#include <stdio.h>

#include <string.h>

int main(){

char s[5], passw[] = "kuku";/*passw – эталонный пароль. Можно описать как *passw = "kuku";*/

int i, k = 0;

for (i = 0; !k && i<3; i++){

printf("\nвведите пароль:\n");

gets(s);//функция ввода строки

if (strstr(s,passw))k = 1;// функция сравнения строк

}

if (k) printf("\nпароль принят");

else printf("\nпароль не принят");}

При работе со строками часто используются указатели.

Пример #4.

Рассмотрим процесс копирования строки srcв строку dest.

#include <iostream.h>

int main(){

char *src = new char [10];

char *dest = new char [10], *d = dest;

cin << src;

while ( *d++ = *src++);

cout << dest;}

Типы данных, определяемые пользователем

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

Язык С++ позволяет программисту определять свои типы данных и правила работы с ними.