Главная

Популярная публикация

Научная публикация

Случайная публикация

Обратная связь

ТОР 5 статей:

Методические подходы к анализу финансового состояния предприятия

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

Ценовые и неценовые факторы

Характеристика шлифовальных кругов и ее маркировка

Служебные части речи. Предлог. Союз. Частицы

КАТЕГОРИИ:






Использование указателей

Лабораторная работа №1

Тема работы «Работа с динамическими массивами»

  1. Цель работы

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

Использование указателей

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

int *pointer;

Инициализировать указатель можно:

1) используя адрес уже объявленной переменной с помощью оператора взятия адреса &.;

2) выделяя под него память функцией malloc().

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

Пример:

#include "stdafx.h"

#include "conio.h"

void main()

{

int someVariable = 4,k; //объявляем и инициализируем переменную someVariable

int *pointer = &someVariable; //объявляем указатель

pointer = &someVariable; //инициализируем его адресом переменной someVariable

k=*pointer+1; // изменяем значение, находящееся по адресу, на который ссылается

// указатель pointer

printf("Текущее значение переменной someVariable = %d\n", someVariable);

printf("Текущее значение переменной *pointer = %d", k);

getch();

}

Выведет строки:

Текущее значение переменной someVariable = 4

Текущее значение переменной*pointer = 5

Второй случай рассмотрен в п.5 данных методических указаний.

  1. Указатели и массивы

Понятия указателей и массивов тесно связаны. Рассмотрим следующий фрагмент программы:

char str[80], *p1;

p1 = str;

Напомним, что имя массива без индекса возвращает адрес первого элемента массива. Поэтому здесь p1 указывает на первый элемент массива str. Обратиться к пятому элементу массива str можно с помощью любого из двух выражений:

str[4]

или

* (p1+4)

Массив начинается с нуля. Поэтому для пятого элемента массива str нужно использовать индекс 4. Можно также увеличить p1 на 4, тогда он будет указывать на пятый элемент.

Рассмотрим следующий фрагмент программы:

int *p, i[10];

p = i;

p[5] = 100; /* в присвоении используется индекс */

*(p+5) = 100; /* в присвоении используется адресная арифметика */

Оба оператора присваивания заносят число 100 в 6-й элемент массива i. Первый из них индексирует указатель p, во втором применяются правила адресной арифметики. В обоих случаях получается один и тот же результат.

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

В следующем фрагменте программы приведены две версии функции putstr(), выводящей строку на экран. В первой версии используется индексация массива, а во второй – адресная арифметика:

/* Индексация указателя s как массива. */

void putstr(char *s)

{

register int t;

for(t=0; s[t]; ++t) putchar(s[t]);

}

/* Использование адресной арифметики. */

void putstr(char *s)

{

while(*s) putchar(*s++);

}

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

Можно также индексировать указатели на многомерные массивы. Например, если а — это указатель на двухмерный массив целых размерностью 10×10, то следующие два выражения эквивалентны:

a

&a[0][0]

Более того, к элементу (0,4)D:\notebook\ХНБ\polnyy_spravochnik_po_c\polnyy_spravochnik_po_c_gerbert_shildt\04\0407.htm - 11 можно обратиться двумя способами:

1) либо указав индексы массива: а[0][4],

2) либо с помощью указателя: *((int*)а+4).

Аналогично для элемента (1,2): а[1][2] или *((int*)а+12).

В общем виде для двухмерного массива справедлива следующая формула:

a[j][k] эквивалентно *((базовый_тип *)а+(j* длина_строки)+k)

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

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

int num[10][10];

/*... */

void pr_row(int j)

{

int *p, t;

p = (int *) &num[j][0]; /* вычисление адреса 1-го

элемента строки номер j */

for(t=0; t<10; ++t) printf("%d ", *(p+t));

}

Эту функцию можно обобщить, включив в список аргументов номер строки, длину строки и указатель на 1-й элемент:

void pr_row(int j, int row_dimension, int *p)

{

int t;

p = p + (j * row_dimension);

for(t=0; t<row_dimension; ++t)

printf("%d ", *(p+t));

}

 

/*... */

void f(void)

{

int num[10][10];

pr_row(0, 10, (int *) num); /* печать 1-й строки */

}

Такой прием "понижения размерности" годится не только для двухмерных массивов, но и для любых многомерных. Например, вместо того, чтобы работать с трехмерным массивом, можно использовать указатель на двухмерный массив, причем вместо него в свою очередь можно использовать указатель на одномерный массив. В общем случае вместо того, чтобы обращаться к n -мерному массиву, можно работать с указателем на (n-1)-мерный массив. Причем этот процесс понижения размерности кончается на одномерном массиве.

 

  1. Массивы указателей

Как и обычные переменные, указатели могут быть собраны в массив. В следующем операторе объявлен массив из 10 указателей на объекты типа int:

int *x[10];

Для присвоения, например, адреса переменной var третьему элементу массива указателей, необходимо написать:

x[2] = &var;

В результате этой операции, следующее выражение принимает то же значение, что и var:

*x[2]

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

void display_array(int *q[])

{

int t;

for(t=0; t<10; t++)

printf("%d ", *q[t]);

}

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

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

void syntax_error(int num)

{

static char *err[] = {

"Нельзя открыть файл\n",

"Ошибка при чтении\n",

"Ошибка при записи\n",

"Некачественный носитель\n"

};

printf("%s", err[num]);

}

Массив err содержит указатели на строки с сообщениями об ошибках. Здесь строковые константы в выражении инициализации создают указатели на строки. Аргументом функции printf() служит один из указателей массива err, который в соответствии с индексом num указывает на нужную строку с сообщением об ошибке. Например, если в функцию syntax_error() передается num со значением 2, то выводится сообщение Ошибка при записи.

  1. Динамическое распределение памяти, динамические структуры данных

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

Память, выделяемая в С функциями динамического распределения данных, находится в т.н. куче – динамически распределяемой области памяти (heap) [1]. Динамически распределяемая область памяти – это свободная область памяти, не используемая программой, операционной системой или другими программами и расположенная между кодом программы, сегментом данных и стеком. Размер динамически распределяемой области памяти заранее неизвестен, но как правило в ней достаточно памяти для размещения данных программы. Большинство компиляторов поддерживают библиотечные функции, позволяющие получить текущий размер динамически распределяемой области памяти, однако эти функции не определены в стандарте С. Хотя размер динамически распределяемой области памяти очень большой, все же она конечна и может быть исчерпана.

Основу системы динамического распределения в С составляют функции malloc() и free(). Эти функции работают совместно.

Функция malloc() выделяет память, а free() — освобождает ее.

Это значит, что при каждом запросе функция malloc() выделяет требуемый участок свободной памяти, a free() освобождает его, то есть возвращает системе. В программу, использующую эти функции, должен быть включен заголовочный файл <stdlib.h>.

Прототип функции malloc() следующий:

void *malloc(size_t количество_байтов);

Здесь количество_байтов — размер памяти, необходимой для размещения данных. (Тип size_t определен в <stdlib.h> как некоторый целый без знака.) Функция malloc() возвращает указатель типа void *, поэтому его можно присвоить указателю любого типа. При успешном выполнении malloc() возвращает указатель на первый байт непрерывного участка памяти, выделенного в динамически распределяемой области памяти. Если в динамически распределяемой области памяти недостаточно свободной памяти для выполнения запроса, то память не выделяется и malloc() возвращает нуль.

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

char *p;

p = malloc(1000); /* выделение 1000 байтов */

 

В C++ нужно преобразовывать типы указателей явно. Поэтому чтобы данная программа была правильной и в С, и в C++, необходимо выполнить явное приведение типа значения, возвращаемого функцией malloc(). Для этого строчку, в которой указателю р присваивается это значение, нужно переписать следующим образом:

char *p;

p = (char*) malloc(1000); /* выделение 1000 байтов */

 

После присвоения указатель p ссылается на первый из 1000 байтов выделенного участка памяти.

В следующем примере выделяется память для 50 целых. Для повышения мобильности (переносимости программы с одной машины на другую) используется оператор sizeof.

int *p;

p = malloc(50*sizeof(int));

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

p = malloc(100);

if(!p) {

printf("Нехватка памяти.\n");

exit(1);

}

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

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

void free(void * p)

Здесь р — указатель на участок памяти, выделенный перед этим функцией malloc(). Функцию free() ни в коем случае нельзя вызывать с неправильным аргументом, это мгновенно разрушит всю систему распределения памяти.

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

Рассматрим другое важное применение динамического размещения: размещение массивов.

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

Довольно часто возникает необходимость выделить память динамически, используя malloc(), но работать с этой памятью удобнее так, будто это массив, который можно индексировать. В этом случае нужно создать динамический массив. Сделать это несложно, потому что каждый указатель можно индексировать как массив. В следующем примере одномерный динамический массив содержит строку:

/* Динамическое распределение строки, строка вводится

пользователем, а затем распечатывается справа налево. */

 

#include "stdafx.h"

 

#include <stdlib.h>

#include <string.h>

#include <conio.h>

 

int main(void)

{

char *s;

register int t;

s= (char*)malloc(80); // указателю присваивает адрес первой ячейки памяти, выделенной для хранения 80 байт информации

if(!s) {

printf("Требуемая память не выделена.\n");

exit(1);

}

gets(s);

for(t=strlen(s)-1; t>=0; t--) putchar(s[t]);

free(s);

getch();

return 0;

}

 

Перед первым использованием s программа проверяет, успешно ли прошло выделение памяти. Эта проверка необходима для предотвращения случайного использования нулевого указателя. Обратите внимание на то, что указатель s используется в функции gets(), а также при выводе на экран (но на этот раз уже как обыкновенный массив).

Можно также динамически выделить память для многомерного массива. Для этого нужно объявить указатель, определяющий все, кроме самого левого измерения массива. В следующем примере двухмерный динамический массив содержит таблицу чисел от 1 до 10 в степенях 1, 2, 3 и 4.

#include "stdafx.h"

#include <stdlib.h>

#include <conio.h>

 

int pwr(int a, int b);

 

int main(void)

{

/* Объявление указателя на массив из 10 строк

в которых хранятсяцелые числа (int). */

int (*p)[10];

 

register int i, j;

 

/* выделение памяти для массива 4 x 10 */

p = (int(*)[10]) malloc(40*sizeof(int));

 

if(!p) {

printf("Требуемая память не выделена.\n");

exit(1);

}

 

for(j=1; j<11; j++)

for(i=1; i<5; i++) p[i-1][j-1] = pwr(j, i);

 

for(j=1; j<11; j++) {

for(i=1; i<5; i++) printf("%10d ", p[i-1][j-1]);

printf("\n");

}

getch();

return 0;

}

 

/* Возведение чисел в степень. */

int pwr(int a, int b)

{

register int t=1;

 

for(; b; b--) t = t*a;

return t;

}

Программа выводит на экран следующее:

1 1 1 1

2 4 8 16

3 9 27 81

4 16 64 256

5 25 125 625

6 36 216 1296

7 49 343 2401

8 64 512 4096

9 81 729 6561

10 100 1000 10000

 

Указатель р в главной программе (main()) объявлен как

int (*p)[10]

Следует отметить, что скобки вокруг *р обязательны. Такое объявление означает, что р указывает на массив из 10 целых. Если увеличить указатель р на 1, то он будет указывать на следующие 10 целых чисел. Таким образом, р – это указатель на двухмерный массив с 10 числами в каждой строке. Поэтому р можно индексировать как обычный двухмерный массив. Разница только в том, что здесь память выделена с помощью malloc(), а для обыкновенного массива память выделяет компилятор.

В языке С++ предусмотрены два оператора динамического распределения памяти new и delete. Они выделяют и освобождают память в ходе выполнения программы.

Общий вид операторов выглядит следующим образом.

указатель = new тип;

delete указатель;

  1. Индивидуальные задания

Вариант 1

1. В одномерном динамическом массиве, состоящем из n вещественных элементов вычислить сумму отрицательных элементов массива.

 

 

Вариант 2

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

 

Вариант 3

1. В одномерном динамическом массиве, состоящем из n целых элементов вычислить произведение элементов массива с четными номерами.

Вариант 4

 

1. В одномерном динамическом массиве, состоящем из n вещественных элементов вычислить сумму элементов массива с нечетными номерами.

 

Вариант 5

1. В одномерном динамическом массиве, состоящем из n вещественных элементов найти максимальный элемент массива.

 

Вариант 6

1. В одномерном динамическом массиве, состоящем из n вещественных элементов найти минимальный элемент массива.

 

Вариант 7

1. В одномерном динамическом массиве, состоящем из n целых элементов вычислить номер максимального элемента массива.

Вариант 8

1. В одномерном динамическом массиве, состоящем из n целых элементов вычислить номер минимального элемента массива.

 

Вариант 9

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

 

Вариант 10

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

Вариант 11

1. В одномерном динамическом массиве, состоящем из n вещественных чисел вычислить номер минимального по модулю элемента массива.

 

Вариант 12

1. В одномерном динамическом массиве, состоящем из n вещественных чисел вычислить номер максимального по модулю элемента массива.

 

Вариант 13

1. В одномерном динамическом массиве, состоящем из n вещественных чисел вычислить количество элементов массива, лежащих в диапазоне от А до В.

Вариант 14

1. В одномерном динамическом массиве, состоящем из n вещественных чисел вычислить количество элементов массива, равных 0.

 

Вариант 15

1. В одномерном динамическом массиве, состоящем из n вещественных чисел вычислить количество элементов массива, больших С.

 

Вариант 16

1. В одномерном динамическом массиве, состоящем из n вещественных чисел вычислить количество отрицательных элементов массива.

2. удаление списка перед выходом из программы.

 

Вариант 17

1. В одномерном динамическом массиве, состоящем из n целых чисел вычислить количество положительных элементов массива.

Вариант 18

1. В одномерном динамическом массиве, состоящем из n вещественных чисел вычислить количество элементов массива, меньших С.

 

Вариант 19

1. В одномерном динамическом массиве, состоящем из n вещественных чисел найти произведение отрицательных элементов массива.

 

Вариант 20

1. В одномерном динамическом массиве, состоящем из n вещественных чисел вычислить произведение положительных элементов массива.

 

 

<== предыдущая лекция | следующая лекция ==>
Технологічні процеси виконання робіт технічного обслуговування № 2 на автопідприємствах. | Технологічні процеси виконання робіт поточного ремонту автомобілів на автопідприємствах.


Не нашли, что искали? Воспользуйтесь поиском:

vikidalka.ru - 2015-2024 год. Все права принадлежат их авторам! Нарушение авторских прав | Нарушение персональных данных