Главная страница
Навигация по странице:

  • Общие задания

  • Указатели. Динамическое распределение памяти


    Скачать 109.5 Kb.
    НазваниеУказатели. Динамическое распределение памяти
    Дата03.06.2022
    Размер109.5 Kb.
    Формат файлаdoc
    Имя файлаLR7_Ukazateli.doc
    ТипЛабораторная работа
    #567640

    Программирование на С\С++


    Лабораторная работа 7

    Тема: Указатели. Динамическое распределение памяти

    Цель лабораторной работы


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

    Краткие теоретические сведения


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

    int *pointer;

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

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

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

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

    Пример:

    #include

    #include

    void main()

    {

    //объявляем и инициализируем переменную some Variable

    int someVariable = 4,k;

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

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

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

    // на который ссылается

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

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

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

    getch();

    }
    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, во втором применяются правила адресной арифметики. В обоих случаях получается один и тот же результат.

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

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

    /* Индексация */

    int t, mass[10];

    for(t=0; t<11; ++t) scanf(“%d”, &mass[t]);

    for(t=0; t<11; ++t) printf(“mass[%d]=%d”, t+1,mass[t]);

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

    int *t, *mass;

    mass=(int*)malloc(10*sizeof(int));

    t=mass;

    for(t= mass; t
    for(t= mass; t
    free(mass);

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

    a

    &a[0][0]

    Более того, к элементу (0,4) можно обратиться двумя способами:

    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
    printf("%d ", *(p+t));

    }
    /* ... */
    void f(void)

    {

    int num[10][10];

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

    }

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


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

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

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

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

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

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

    Здесь количество_байтов — размер памяти, необходимой для размещения данных. Тип size_t определен в как некоторый целый без знака. Функция 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() ни в коем случае нельзя вызывать с неправильным аргументом, это мгновенно разрушит всю систему распределения памяти.

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

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

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

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

    int *t, *mass;

    mass = new int [10];

    t=mass;

    for(t= mass; t
    for(t= mass; t
    delete [] mass;
    1. Пример динамического выделения памяти для массивов


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

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

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

    #include

    #include

    #include

    #include
    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

    #include

    #include
    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(), а для обыкновенного массива память выделяет компилятор.

    Задание


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

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

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

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

    Методические указания


    Выполните общие задания.

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

    1. словесная постановка задачи;

    2. анализ задачи и формальная постановка задачи;

    3. проектирование (разработка алгоритма, представление его в виде блок-схемы)

    4. реализация (кодирование, отладка);

    5. тестирование.

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

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

    2. Объявите целочисленные переменные a иb. Создайте указатели pa и pb, хранящие адрес этих переменных соответственно. Считайте значение этих переменных с клавиатуры. Произведите вычисления суммы и разности этих переменных через указатели (получите их значения из указателей) и сохраните результаты в переменные sum и razn. Сделайте так, чтобы указатели pa и pbтеперь ссылались на sum и razn. Выведите результаты через указатели.

    3. Напишите функцию f, которая принимает вещественное значение по адресу (через указатель). Увеличьте принятую переменную на 10. Функция не должна возвращать значения. В main() создайте вещественную переменную t считайте ее значение с клавиатуры и вызовите функцию f с переменной t в качестве аргумента. После этого выведите t на экран.

    4. Повторите предыдущее задание, но с передачей значения по ссылке.

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

    6. Напишите функцию f, которая принимает целое число N. Данная функция должна возвращать целочисленный указатель. Внутри функции должно происходить выделение памяти под массив из N чисел. После этого массив должен заполняться 0 и возвращаться в качестве результата работы функции. В main() используйте функцию f для демонстрации ее работы.

    7. Создайте обычный целочисленный массив m. Считайте его значения с клавиатуры. После этого посчитайте количество положительных (Kp) и отрицательных (Ko) элементов массива. Динамически выделите память под массив из Kp элементов, после этого запишите в него все положительные элементы из массива m. Также выделите память под массив из Ko элементов, после этого запишите в него все отрицательные элементы из массива m. Выведите полученные массивы на экран.

    8. Напишите функцию f, которая принимает целочисленный указатель m (понадобится для принятия массива) и число N(количество элементов массива m). Данная функция должна возвращать целочисленный указатель. Посчитайте количество ненулевых элементов массива (Nn). Создайте массив из Nn элементов и запишите в него все ненулевые элементы массива m. Верните полученный массив в качестве результата работы функции. В main() продемонстрируйте работу функции f.



    Варианты индивидуальных заданий


    Вариант 1

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

    2. Написать функцию, которая принимает строку. Создает новую строку, в которую записываются все символы принятой строки, но при этом они дублируются («а» - «аа»). Возвращает полученную строку. Для этого используется динамическое выделение памяти.


    Вариант 2

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

    2. Написать функцию-процедуру, которая принимает строку, числовую переменную N и символ. Заменяет в строке каждый N-ый символ на символ-аргумент. После этого сохраняет количество замененных символов в N.


    Вариант 3

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

    2. Написать функцию, которая принимает строку и переменную. Делит строку на две строки. В первой строке – первая половина принятой строки, и адрес этой строки запоминается в переменной-аргументе. Во второй строке – вторая половина, и она возвращается функцией в качестве результата. Для этого используется динамическое выделение памяти.


    Вариант 4

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

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


    Вариант 5

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

    2. Написать функцию-процедуру, которая принимает две строки и две переменные. Считает, сколько символов в строках совпадают, и сохраняет результат в первой переменной-аргументе. Во второй переменной-строке сохраняет длину самой короткой строки. Меняет строки местами.


    Вариант 6

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

    2. Написать функцию, которая принимает две строки. Объединяет две строки в третью: вначале идет короткая строка, потом более длинная. Для этого используется динамическое выделение памяти. Возвращает эту строку.


    Вариант 7

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

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


    Вариант 8

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

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


    Вариант 9

    1. Написать функцию-процедуру, которая принимает в качестве аргументов вещественный массив и переменную. Заменяет все элементы массива на элементы, возведенные в [-1] степень. Считает количество элементов, которые стали больше по значению после возведения в [-1] степень. Сохраняет это количество в переменную-аргумент.

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


    Вариант 10

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

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


    Вариант 11

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

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


    Вариант 12

    1. Написать функцию-процедуру, которая принимает в качестве аргументов целочисленный массив и две переменные. Находит номера первого и последнего элемента, равного 0. Сохраняет эти номера в переменные-аргументы.

    2. Написать функцию, которая принимает строку. Удаляет из строки все повторяющиеся символы («ааа оо» - «а о»). Возвращает количество удаленных символов.


    Вариант 13

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

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


    Вариант 14

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

    2. Написать функцию, которая удаляет из строки все знаки препинания «!.,?» и возвращает количество удаленных символов.


    написать администратору сайта