Массивы и указатели символьных строк




Указатели

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

Указатель объявляется следующим образом:

тип *<имя указателя>;

Примеры:

int *p;

struct {int x,y;} *p;

complex *p;

 

Операции над указателями. С указателями связаны две специальные операции & и *. Обе эти операции являются унарными, т. е. они имеют один операнд, перед которыми они ставятся.

Операция & соответствует операции «взять адрес».

Операция * соответствует словам «значение, расположенное по указанному адресу».

В указателе очень важным является базовый тип, т.к. он определяет, сколько байтов занимает объект, на который указывает указатель. Если int – 2 байта, char – один байт и т.д. Простейшие действия с указателями продемонстрируем на следующей программе.

Пример:

# include <stdio.h>

main ()

{

float x = 10.1, y;

float * pf;

pf = &x;

y = *pf;

printf(“x = %f y= %f”, x, y); // Результат: x=10.1; y=10.1; pf=632

*pf ++;

printf (“x = %f y = %f”, x, y); // Результат: x=10.1; y=10.1; pf=636

y =1+ *pf * y;

printf (“x = %f y = %f”, x, y); // Результат: x=10.1; y=1; pf=636

return 0;

}

Как и над другими типами переменных, над указателями можно производить арифметические операции сложения и вычитания, а также операции (++) и (--). Указатели можно сравнивать. Применимы шесть операций:

<, >, < =, > =, =, = =,! =

Пример:

main()

{

int *p;

int x;

p = &x;

printf (“%p%p”, p, ++p);

printf (“%p”, ++p);

}

 

После выполнения этой программы, мы увидим, что при операции ++p значение указателя изменится не на 1, а на 2. И это правильно, т.к. значение указателя должно указывать не на следующий адрес памяти, а на адрес следующего целого. А целое, как мы помним, занимает 2 байта. Если бы базовой тип указателя был не int, а double, то были бы напечатаны адреса, отличающиеся на 8. Именно столько байт занимает переменная типа double.

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

 

1.2.1 Динамические объекты

Указатели используются при создании и обработке динамических объектов.

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

Для этого служат специальные функции:

malloc () и calloc().

Спецификации данных функций:

char *malloc(size);

unsigned size (объем памяти, который нужно выделить в байтах);

char *calloc(nelem, elsize);

unsigned nelem (число элементов, для которых необходимо выделить память)

unsigned elsize (объем памяти, который необходимо выделить для каждого элемента)

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

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

Пример:

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

Выделяется память для одного целого значения. Адрес этой области памяти присваивается переменной p после его преобразования из типа char* к типу int*.

Объявление одномерного массива:

int *r=(int*)malloc(sizeof(n*sizeof(int));

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

int **r = (int**) malloc(n*sizeof(int*));

for(i=0; i<n; i++) r[i] = (int*) malloc(n*sizeof(int));

Время жизни динамического объекта

Память, занимаемая динамическим объектом, может быть освобождена. Явное освобождение выполняется при помощи функции free().

Указание на произвольную ячейку памяти

Например, определен указатель

int *p;

Тогда указатель на ячейку памяти 0777000 можно получить с помощью следующего оператора:

P=(int*)0777000;

 

1.2.2 Связь между указателями и массивами

Между массивами и указателями существует тесная связь.

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

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

Таким образом, действие записи

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

можно объяснить так

*(имя_массива + индекс)

Пример:

int mas[10];

int *ptr;

ptr = mas; // присваивает адрес указателю

// следующие операции дадут один и тот же результат:

mas[2] = 20;

*(ptr + 2) = 20;

// следующая операция прибавит 2 к первому элементу:

*ptr + 2;

Соответственно объявления int mas[] и int *mas идентичны по действию: оба объявляют mas указателем.

При этом следует учитывать, что

- mas[ ] – указатель-константа;

- *mas – указатель-переменная.

Существуют некоторые ограничения на операции с указателями-константами. В частности, в следующем примере показана недопустимая конструкция:

Пример:

int mas[ ] = {1, 2, 3, 4};

int *ptr=(int*)malloc(sizeof(4*sizeof(int));

mas=ptr; // недопустимый оператор, так как mas - указатель-константа и его значение изменить нельзя.

ptr=mas; // хотя синтаксически верен, но опасен, так как участок памяти, выделенный функцией malloc(), становится недоступным. Его нельзя теперь не только использовать, но и освободить, так как функции free() нужен адрес начала освобождаемой памяти, а его значение потеряно.

Кроме того, например, операции единичного приращения ++ можно применять только к переменным. Поэтому ptr++ - допустимая конструкция, а mas++ - запрещенная. Однако и в том и в другом случае можно использовать операции сложения с указателем, т.е. ptr + i; и mas + i; - допустимые конструкции.

Также, например, при использовании операции sizeof() к имени массива, результатом будет размер в байтах участка памяти, выделенного не для указателя (имя массива), а для массива в целом. А применение операции & (получения адреса) к имени массива даст в результате адрес первого элемента массива (0-го), а не указателя (имя массива), т.е. для любого массива соблюдается равенство:

имя_массива == &имя_массива==&имя_массивах[0].

Указатели и многомерные массивы. Рассмотрим двумерный массив и действия с указателями.

int mas[4][2];

int *ptr;

ptr = mas;

ptr сейчас указывает на первый столбец первой строки, т.е.

ptr = = mas = = &mas [0] [0];

Увеличим указатель:

ptr+1 = = &mas [0] [1];

ptr+2 = = &mas [1] [0];

ptr+3 = = &mas [1] [1] и т.д.

Двумерный массив можно представить как массив массивов. В нашем случае мы имеем четырех элементный массив, состоящий из двух элементов. Примечательно, что этот четырех элементный массив можно представить в виде одномерного mas[0],…,mas[3]. При этом имя массива по-прежнему является указателем на его первый элемент, т.е. mas[0]= =&mas[0] [0]. На что же будут указывать mas[i]? В этом случае mas [i] указывает на i-тую строку, т.е. на первый элемент i - й строки. Таким образом

mas [0] == &mas [0] [0];

mas [1] == &mas [1] [0];

mas[2] == &mas [2] [0];

mas[3] == &mas [3] [0];

 

Массивы и указатели символьных строк

 

Часто бывает необходимо работы с символьными строками. В языке Си нет специального типа «строка». Вместо этого каждая символьная строка в памяти ЭВМ представляется в виде одномерного массива типа char, последним элементом которого является символ ‘\0’.

При этом возможны следующие типы определений символьных массивов:

1) «Прямолинейное», т.е. задается массив типа char с указанием конкретного значения предельного размера, соответствующего максимальному числу символов в строке. Например, определим символьный массив для хранения 10 строк, каждая из которых может содержать не более 20 символов:

char string_1[10][20] = {“Иванов”, “Петров”, “Сидоров”};

2) При определении массива один из его предельных размеров (самый левый индекс) можно не указывать. В этом случае количество элементов массива определяется, например, при инициализации:

char string_2[ ][20] = {“Иванов”, “Петров”, “Сидоров”};

 

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

3) При помощи массива указателей типа char*:

char* string_3[ ] = {“Иванов”, “Петров”, “Сидоров”};

Для указателей массива string_3, в котором при таком определении 3 элемента и каждый является указателем-переменной типа char*, выделяется всего 3*sizeof(char*) байт. Кроме того, компилятор размещает в памяти три строковые константы «Иванов» (7 байт), «Петров» (7 байт), «Сидоров (8 байт)», а их адреса становятся значениями элементов массива string_1.

 



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-02-16 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: