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

  • ОБЯЗАТЕЛЬНЫЕ РЕЗУЛЬТАТЫ ОБУЧЕНИЯ

  • ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ Понятие "указатель"

  • Описание переменных типа "указатель"

  • Примеры

  • Работа с демонстрационными примерами

  • Пример

  • Операции динамического распределения

  • Замечания

  • ЗАДАЧИ ДЛЯ САМОСТОЯТЕЛЬНОГО РЕШЕНИЯ

  • 1. Указатели

  • Обязательные результаты обучения


    Скачать 434.66 Kb.
    НазваниеОбязательные результаты обучения
    Дата10.04.2023
    Размер434.66 Kb.
    Формат файлаpdf
    Имя файлаLR05 (1).pdf
    ТипУказатель
    #1052434

    ЛАБОРАТОРНАЯ РАБОТА 5.
    УКАЗАТЕЛИ НА ПРОСТЕЙШИЕ ТИПЫ. ОПЕРАЦИИ НАД
    УКАЗАТЕЛЯМИ. НИЗКОУРОВНЕВОЕ ПРОГРАММИРОВАНИЕ
    Просвещение следует внедрять с умеренностью,
    по возможности избегая кровопролития.
    М.Е. Салтыков-Щедрин
    ОБЯЗАТЕЛЬНЫЕ РЕЗУЛЬТАТЫ ОБУЧЕНИЯ
    Знать понятия:
    - "указатель", "статическая переменная", "динамическая переменная",
    "операции над указателями".
    Знать семантику:
    - функций языка С: calloc(), malloc(), free();
    - операций языка C++: new, delete.
    ТЕОРЕТИЧЕСКИЕ СВЕДЕНИЯ
    Понятие "указатель"
    Указатель, всякий снарядъ, орудiе, приспособленье, для
    указанья чего-либо: рука на столбе или доска съ надписью,
    указывающая дорогу; всякая стрълка, на часахъ, на
    градусникахъ и пр; оглавленье книги, или опись книгамъ,
    дъламъ, бумагамъ, по коей можно отыскать, что нужно.
    В.Даль
    Среди многочисленных понятий языка C имеется одно, которое можно отнести к разряду особо сложных, речь идёт об указателях.
    Определение.
    Указатель — это переменная, значением которой является целое число, являющееся адресом некоторого программного объекта (в этом случае говорят, что
    указатель ссылается на программный объект).
    Наиболее важной идеей, которая является отправной точкой для освоения всего последующего учебного материала, является то, что указатель является
    переменной. Более точно, это не просто переменная, а специальный тип
    переменной, значения которой содержат адреса (в частности, указатель может содержать адрес переменной).
    Указатели должны быть проинициализированы либо при объявлении, либо с помощью оператора присваивания. Хорошим стилем программирования является такое программирование, при котором указатели указывают на что-то
    конкретное, прежде чем их переопределяют. Другими словами, всегда инициализируйте указатели.
    Указатель может быть проинициализирован нулём, символической
    константой NULL или значением адреса, при этом:
    (1) указатель со значением NULL не указывает ни на что;
    (2) значение 0 является единственным целым числом, которое может быть присвоено переменной-указателю непосредственно. Инициализация указателя значением 0 эквивалентна инициализации указателя константой NULL, однако
    использование NULL предпочтительнее (когда присваивается значение 0, то происходит его преобразование к указателю соответствующего типа).
    Определение.
    Операцией нахождения адреса называется унарная операция "&", которая, будучи применённой к идентификатору переменной, возвращает её адрес. Другими словами, сказанное можно записать так:
    <Адрес переменной>=&<Идентификатор переменной>
    Операндом операции нахождения адреса должна быть переменная; эта операция не может применяться к константам, выражениям или к переменным, объявленным с модификатором register (так, конструкции вида &(х-3) или &5 запрещены с точки зрения синтаксиса).
    Определение.
    Операцией косвенной адресации (операцией разыменования, операцией
    раскрытия ссылки) называется унарная операция "*", которая рассматривает свой операнд как адрес программного объекта и обращается по этому адресу, возвращая его содержимое.
    Другими словами, сказанное можно записать так:
    <Содержимое объекта>=*<Адрес объекта>.
    Иногда процесс использования операции * называется разыменованием
    указателя.
    Замечание (важное).
    Только в умелых руках указатели "ведут себя послушно и правильно", разрешая использовать всю мощь косвенных обращений.
    Новичок должен обращаться с указателями как "сапёр с неразорвавшейся миной": одно неосторожное "движение" - и программа "самоликвидируется".
    Недаром в языке Java от явных указателей просто отказались.
    Описание переменных типа "указатель"
    Кто вытверживает слова, не поняв их смысла, скоро
    их забывает, ибо слова, как говорит Гомер, крылаты
    и легко улетают, если их не удерживает груз смысла.
    А стало быть, в первую очередь, старайся понять
    существо дела, потом обдумай ещё и ещё раз.
    Эразм Роттердамский. Разговоры запросто
    Из определения понятия "указатель" следует, что указатель существенно связан с типом объекта, на который он ссылается.
    Если перед обозначением объекта в описании поставить символ "*", то будет описан указатель на объект того же типа и класса памяти, которые соответствуют данному обозначению без звёздочки.
    Синтаксис определения указателя имеет вид:
    <Спецификация_типа> *<Идентификатор>;
    Символ "*" в этом контексте означает "указатель на".
    Например, следующее описание типа переменных pa и pb: int *pa,*pb; говорит о том, что описаны два указателя на объекты типа int.

    Компилятор по подобному описанию резервирует некоторые участки памяти и называет их pa и pb, что графически можно изобразить так:
    Указатели можно инициализировать совместно с описанием их типа.
    Примеры (прямое указание при инициализации).
    1. Опишем переменную dec типа int с инициализацией (значением переменной должно стать целое число 10) и опишем указатель ptr на переменную типа int с инициализацией (значением указателя должен стал адрес &dec переменной dec): int dec=10; int *ptr=&dec;
    2. Программный фрагмент char a; char *pa=&a; позволяет описать символьную переменную a и указатель pa на объект типа char, а также инициализируют pa так, чтобы он указывал на a.
    Пример (косвенное указание при инициализации).
    Опишем переменную dec типа int с инициализацией (значением переменной должно стать целое число 10), опишем указатель ptr на переменную типа int с инициализацией (значением указателя должен стал адрес &dec переменной dec) и, наконец, опишем указатель ref на указатель на переменную типа int с инициализацией (значением указателя должен стать адрес &ptr переменной ptr): int dec=10; int *ptr=&dec; int **ref=&ptr;
    Пример (адресное указание при инициализации).
    Рассмотрим следующий программный фрагмент, в котором использован указатель специального вида, называемый пустым указателем (или указателем на
    пустое место), который указывает на определённый тип, неизвестный компилятору (никакой серьёзной работы выполнить с этим указателем невозможно: он является лишь маркером места в памяти и, причём, весьма полезным; а для того, чтобы сделать что-либо "полезное" с памятью, на которую мы указываем, необходимо использовать указатель "правильного" типа):
    int dec=10,*u; void *ptr=&dec; printf("По адресу %u хранится %d\n",u=ptr,*(u=ptr));
    Итак, указатель любого типа может быть присвоен указателю на void (т.е. типа void *), и void-указатель может быть присвоен указателю любого типа. В обоих случаях приведения типа не требуется.
    Указатель на void не может быть разыменован. Например, при разыменовании указателя на целое компилятор "знает", что тот ссылается на четыре байта памяти
    (на компьютере с целыми числами размером в 4 байта), но void-указатель содержит адрес памяти для неизвестного типа данных, размер которого неизвестен компилятору. Компилятор должен знать тип данных и, тем самым, размер элемента данных в байтах, чтобы правильно разыменовать указатель. В случае указателя на void размер элемента в байтах не может быть определён компилятором.
    Типичные ошибки программирования.
    1. Присвоение значения указателя одного типа указателю другого типа, когда ни один из них не является указателем на void *, приводит к синтаксической ошибке.
    2. Разыменование void-указателя.
    Работа с демонстрационными примерами
    См. Пример 1, Пример 6.
    Операции над указателями
    То, что вы были вынуждены открыть сами, оставляет в
    вашем уме дорожку, которой вы можете снова
    воспользоваться, когда в этом возникнет необходимость.
    Г.Лихтенберг
    1. Операция нахождения адреса.
    2. Операция косвенной адресации (операция раскрытия ссылки).
    Напомним, что: (а) выражение *px в правой части операции присваивания означает "извлечь значение из области памяти, на которую указывает значение
    переменной px"; (б) операция присваивания *px=10 читается как "присвоить 10 по
    адресу, содержащемуся в переменной px".
    3. "Операция" инициализации указателя.
    4. Операция присваивания значения указателя другому указателю того же
    типа.
    5. Операция присваивания указателю нуля (NULL).
    Указатели и целые не являются взаимозаменяемыми объектами. Константа 0
    - единственное исключение из этого правила: её можно присвоить указателю и указатель можно сравнить с нулевой константой. Чтобы показать, что 0 — это специальное значение для указателя, вместо числа 0, как правило, записывают
    NULL - константу, определенную в файле .
    Заметим, что ни один "правильный" указатель не может иметь значения 0, поэтому равенство нулю значения указателя может служить сигналом о ненормальном завершении выполнения функции.
    6. Операции сравнения указателей одного и того же типа:

    <, >=, >, <=, !=, ==.
    Например, если p и q - указатели на объекты одного типа, то отношение p!=q истинно, если p и q указывают на разные объекты, а отношение p==q истинно, если p и q указывают на один и тот же объект.
    7. Операция сравнение указателя с нулем (NULL).
    Например, p!=NULL истинно, если указатель p отличен от NULL.
    Это очень часто используемая операция.
    8. Операция сложения и вычитания указателей одного и того же типа.
    9. Операции сложения и вычитания указателя и целого.
    Если к значению указателя прибавить 1, то компилятор языка C добавит
    единицу памяти, т.е. если переменная имеет тип int, то значение указателя на эту переменную увеличится на два (переменная типа int занимает два байта), если переменная - типа float, то значение указателя увеличится на четыре (переменная типа float занимает четыре байта), и если переменная - типа char, то значение указателя увеличится на единицу (переменная типа char занимает один байт). Вот почему необходимо специально оговаривать тип объекта, на который ссылается указатель; одного адреса здесь недостаточно, так как компилятор должен знать, сколько байтов потребуется для запоминания объекта.
    Приведём несколько примеров: (а) пусть p - указатель на объект любого типа; тогда оператор p++; увеличивает p так, что он указывает на следующий объект того же типа; (б) пусть i - переменная целого типа; тогда оператор p+=i; увеличивает указатель p так, чтобы он указывал на объект, отстоящий на i "единиц" памяти, занимаемых объектом данного типа, от объекта, на который указывает p.
    Замечание [Дейтел,Дейтел,2002,с.293].
    Большинство компьютеров сегодня поддерживают 2-байтовые или 4- байтовые целые, а некоторые из новейших машин имеют ещё и 8-байтовые целые.
    Поскольку результат арифметики указателей зависит от размера объектов, на которые ссылается указатель, то результат арифметических выражений с указателями является машинно-зависимым.
    Работа с демонстрационными примерами
    См. Пример 2.
    Операция sizeof
    В языке C имеется специальная унарная операция sizeof, при помощи которой можно определить размеры любого типа данных в байтах во время компиляции программы.
    Операция sizeof может применяться к любой переменной, типу данных или константе. при определении размера переменной или константы возвращается число байтов, отводимых под тип переменной или константы. Скобки с sizeof обязательны, если в качестве операнда используется имя типа. Отсутствие скобок в этом случае приводит к сообщению об ошибке. Скобки не требуются, если операнд является именем переменной.
    Замечание [Дейтел,Дейтел,2002,с.291].
    Размер в байтах одного и того же типа данных может быть разным на различных системах. При написании программ, которые будут работать на различных платформах и при этом небезразличны к представлению данных, используйте операцию sizeof для определения размера типа данных.

    Работа с демонстрационными примерами
    См. Пример 3 1
    Важнейшие стандартные библиотечные функции
    Дело было в Год спокойного солнца. Как обычно в такое время,
    устраивали большую околосолнечную уборку, подметали и выметали
    массу железок, которые кружат на уровне орбиты Меркурия; за
    шесть лет, что ушли на строительство в его перигелии большой
    космической станции, в пустоте побросали целую кучу старых,
    ненужных ракет, потому что работы велись тогда по системе Ле
    Манса и, вместо того, чтобы сдавать ракетные трупы на слом, их
    использовали в качестве строительных лесов.
    С.Лем. Рассказ Пиркса
    Определение [Жешке,1994].
    Heap-область (куча) — это память, которая может быть динамически выделена и освобождена из программы пользователя посредством библиотечных функций calloc, free, malloc и realloc.
    1. Функция calloc().
    Определение функции имеет вид: void *calloc(unsigned size,unsigned n);
    Функция возвращает в качестве своего значения указатель на область памяти, размер которой достаточен для помещения туда n объектов, размер каждого из которых равен size. Если произошла ошибка, то функция возвращает значение
    NULL. Выделенный объём памяти заполняется нулями.
    В случае, когда отсутствует достаточный объём памяти, эта функция возвращает NULL (0).
    В дальнейшем часто будем выделять блок памяти в динамической области при помощи следующего оператора присваивания:
    2. Функция free().
    Определение функции имеет вид: void free(char *p);
    Функция освобождает по указателю p блок основной памяти, которая была предварительно выделена функцией calloc(). Память, находящаяся вне пределов области, распределённой с помощью calloc(), функцией free() не анализируется.
    3. Функция malloc().

    Определение функции имеет вид: void *malloc(unsigned n);
    Функция возвращает в качестве своего значения указатель на область памяти, размер которой достаточен для помещения туда n объектов, размер каждого из которых равен unsigned int.
    Если произошла ошибка, то функция возвращает значение NULL.
    В случае, когда отсутствует достаточный объём памяти, эта функция возвращает NULL (0).
    Пример (применения функции malloc).
    Выделим память для 50 целых чисел с помощью функции malloc(): int *p=(int *) malloc(50*sizeof(int));
    Работа с демонстрационными примерами
    См. Пример 3, Пример 4, Пример 5, Пример 7, Пример 8.
    Операции динамического распределения
    памяти в языке C++
    Так как резервирование и освобождение блоков памяти является очень распространенной операцией, в языке С++ введены две операции new и delete, освобождающие программиста от необходимости явно использовать библиотечные функции calloc(), malloc() и free().
    Операция new распределяет память для одного объекта или массива объектов любого типа (в том числе и определённого пользователем).
    Операция new имеет следующий синтаксис:
    <Тип> *<Идентификатор>=new <Тип>;
    Результатом операции new будет указатель на участок памяти, соответствующий типу <Тип>, или нулевой указатель в случае ошибки.
    Кроме этого, язык C++ допускает инициализацию динамического объекта, созданного с помощью операции new.
    Например, следующий фрагмент инициализирует объект типа double значением 3.14159: double *Ptr=new double(3.14159);
    Замечание (для знатоков).
    Для расположения массива в динамической памяти необходимо указать размерность массива после имени типа (в качестве размерности может быть использовано выражение). Например: int *arrayPtr=new int[10];
    Динамическая память, выделенная с помощью операции new, будет распределённой до тех пор, пока она не будет освобождена операцией delete. Это очень важный момент, т.к. при выходе из блока локальный указатель, содержащий адрес памяти, полученной операцией new, уничтожается, тогда как память остается распределённой, т.е. автоматически память не освобождается. В результате к этой памяти невозможно будет обратиться. Чтобы этого не случилось, перед выходом из блока такая память должна быть явно освобождена операцией delete.
    Операция delete имеет следующий синтаксис: delete <Указатель>;
    С помощью операции delete может быть освобождена только память, ранее распределённая операцией new.

    Замечания (для знатоков).
    1. Массивы нужно удалять с помощью операции delete[].
    2. С помощью операции new можно определить ссылку на динамическую память. Для этого используется конструкция вида
    <Тип> &<Идентификатор>=*new <Тип>;
    Знак операции * перед операцией new необходим, потому что результатом операции new является указатель, а ссылка должна инициализироваться переменной.
    Работа с демонстрационными примерами
    См. Пример 9.
    ДЕМОНСТРАЦИОННЫЕ ПРИМЕРЫ
    Они красноречивы, ибо придерживаются правил; они
    лишены красноречия, ибо придерживаются правил.
    "Блаженный" Августин (354-430)
    Пример 1.
    /* Демонстрация простейшей работы с указателями и адресами */
    /* ------------------------------------------------------- */
    #include
    #include int main()
    { int a,b,c,max; int *pa=&a, /* Переменная pa содержит адрес переменной a */
    *pb=&b, /* Переменная pb содержит адрес переменной b */
    *pc=&c, /* Переменная pc содержит адрес переменной c */
    *min;
    /* ----------------------------- */ printf("Введите значения a,b,c: "); scanf("%d %d %d",&a,&b,&c); printf("Посмотрим, по каким адресам лежат эти значения:\n"); printf(" в ячейке с адресом %p лежит %d\n",pa,*pa); printf(" в ячейке с адресом %p лежит %d\n",pb,*pb); printf(" в ячейке с адресом %p лежит %d\n",pc,*pc);
    /* ------------------------------------------------ */ printf("Найдем большее из чисел a, b, c и положим\n"); printf("в ячейку с меньшим адресом.\n");
    /* Поиск наименьшего адреса */ if (pa<=pb) min=pa; else min=pb; if (min<=pc)
    ; else min=pc;
    /* Поиск наибольшего значения */
    if (*pa<=*pb) max=*pb; else max=*pa; if (max<=*pc) max=*pc;
    /* Требуемое размещение */
    *min=max; printf("Значение max=%d расположено по адресу %p.\n\n",
    *min,min); getch(); return 0;
    }
    Результат работы программы:
    Введите значения a,b,c: 1 3 2
    Посмотрим, по каким адресам лежат эти значения: в ячейке с адресом FFF4 лежит 1 в ячейке с адресом FFF2 лежит 3 в ячейке с адресом FFF0 лежит 2
    Найдём большее из трех чисел и положим в ячейку с меньшим адресом.
    Итак, max=3 расположен по адресу FFF0.
    Пример 1
    1
    /* Демонстрация использования указателей для */
    /* моделирования следующих структур памяти: */
    /*
    */
    /* ----------------------------------------- */
    #include
    #include
    #include
    /* ------------- */ int main()
    { int **p,**q=&p,
    **r1,**q1=&r1,**p1=&q1; p=&q; printf("Проверка: %d %d\n",p==&q,q==&p); getch(); r1=&q1; printf("Проверка: %d %d %d\n",p1==&q1,q1==&r1,r1==&q1); getch();
    }
    Результат работы программы:
    Проверка: 1 1
    Проверка: 1 1 1

    Пример 2.
    /* Демонстрация операций с переменными-указателями */
    /* ----------------------------------------------- */
    #include
    #include int main()
    { int x=5,y; int *px,*py; /* Описание указателей px и py на целое */
    /* ------------------------------------------------- */ px=&x; /* Переменная px указывает на x */ printf(" (%u)=%d\n\n",px,*px);
    /* ------------------------------------------------- */ y=*px; /* Переменная y получает значение того */
    /* объекта, на который указывает px, */
    /* т.е. значение переменной x */ printf(" y=*px; => y=%d\n",y);
    /* ----------------------------- */ printf(" *px=0 => x=%d\n",*px=0);
    /* Переменная х получает значение 0 */
    /* --------------------------------------------- */ printf(" y=*px+1 => y=%d\n",y=*px+1);
    /* Переменная y получает значение */
    /* большее на 1 значения х */
    /* ------------------------------------------- */ printf(" *px+=1 => x=%d\n",*px+=1);
    /* Увеличение содержимого x на 1 */
    /* ------------------------------------------ */ printf(" *px++ => x=%d\n",*px++);
    /* Увеличение адреса px на 1; постфиксная */
    /* операция ++ не изменяет px, пока объект */
    /* по адресу px не будет получен */
    /* ---------------------------------------------------- */ printf(" *++px => x=%d\n",*++px);
    /* Префиксная операция ++ увеличивает px */
    /* до получения значения x */
    /* ---------------------------------------------------- */ printf(" *--px => x=%d\n\n",*--px);
    /* Префиксная операция -- уменьшает px до */
    /* получения значения x */
    /* ---------------------------------------------------- */ py=px; /* Копирование содержимого указателя px в */
    /* указатель py в результате py указывает */
    /* на тот объект, на который указывает px */
    /* ---------------------------------------------------- */ printf(" (%u)=%d\n\n",py,*px);
    getch(); return 0;
    }
    Результат работы программы:
    В ячейке с адресом 65524 содержится 5
    Значение переменной y равно 5
    Значение переменной x равно 0
    Значение переменной y равно 1
    Значение переменной x равно 1
    Значение переменной x равно 1
    Значение переменной x равно 347
    Значение переменной x равно 0
    В ячейке с адресом 65526 содержится 0
    Пример 3.
    /* Демонстрация синтаксиса и семантики функций */
    /* calloc() и free() */
    /* ------------------------------------------- */
    #include
    #include
    #include
    /* ------------- */ void main()
    { int *p; p=(int *) calloc(sizeof(int),1);
    /* ------------------------------------------ */
    /* Резервирование участка в heap-области для */
    /* динамической переменной, на которую указы- */
    /* вает p, схематично изобразим так: */
    /*
    */
    /* */
    *p=1; /* Присваивание значения переменной *p */
    /* */
    /*
    */
    /* */
    /* ------------------------------------------ */ printf("Значение динамической переменной: %d\n",*p); free(p);
    /* ------------------------------------------ */
    /* Освобождение участка в heap-области, кото- */
    /* торый занимало значение динамической пере- */
    /* менной */

    /*
    */
    /* */
    /* ------------------------------------------ */ if (!p) /* Вместо if (p==NULL) ... */ printf("Значение указателя равно NULL.\n"); else printf("Значение указателя не равно NULL.\n"); p=NULL; /* Только теперь значение указателя равно NULL */ getch(); return 0;
    }
    Результат работы программы:
    Значение динамической переменной: 1
    Значение указателя не равно NULL.
    Пример 3
    1
    /* Демонстрация синтаксиса и семантики операции sizeof */
    /* --------------------------------------------------- */
    #include
    #include int main()
    { char c; short s; int i; long l; float f; double d; long double ld;
    /* ------------------------------------------------- */ printf("Размер типа char = %2d\n",sizeof(char)); printf("Размер типа short = %2d\n",sizeof(short)); printf("Размер типа int = %2d\n",sizeof(int)); printf("Размер типа long = %2d\n",sizeof(long)); printf("Размер типа float = %2d\n",sizeof(float)); printf("Размер типа double = %2d\n",sizeof(double)); printf("Размер типа long double = %d\n\n", sizeof(long double));
    /* ---------------------------------------------------- */ printf("Размер переменной типа char =%2d\n",sizeof c); printf("Размер переменной типа short =%2d\n",sizeof s); printf("Размер переменной типа int =%2d\n",sizeof i); printf("Размер переменной типа long =%2d\n",sizeof l); printf("Размер переменной типа float =%2d\n",sizeof f); printf("Размер переменной типа double =%2d\n",sizeof d); printf("Размер переменной типа long double =%d\n\n", sizeof ld);
    getch(); return 0;
    }
    Результат работы программы:
    Размер типа char = 1
    Размер типа short = 2
    Размер типа int = 2
    Размер типа long = 4
    Размер типа float = 4
    Размер типа double = 8
    Размер типа long double = 10
    Размер переменной типа char = 1
    Размер переменной типа short = 2
    Размер переменной типа int = 2
    Размер переменной типа long = 4
    Размер переменной типа float = 4
    Размер переменной типа double = 8
    Размер переменной типа long double = 10
    Пример 4.
    /* Демонстрация определения свободного места в heap-облас- */
    /* ти с помощью библиотечной функции coreleft() */
    /* ------------------------------------------------------- */
    #include
    #include
    #include
    /* Файл заголовков alloc.h необходим для вызова функции */
    /* coreleft(), возвращающей количество свободного места */
    /* в heap-области */
    /* ---------------------------------------------------- */ int main()
    { int *p; printf("Свободное место в heap-области: %u\n",coreleft()); p=(int *)calloc(sizeof(int),1000); printf("Свободное место в heap-области: %u\n",coreleft()); free(p); printf("Свободное место в heap-области: %u\n",coreleft()); getch(); return 0;
    }
    Результат работы программы:
    Свободное место в heap-области: 63456
    Свободное место в heap-области: 61456
    Свободное место в heap-области: 63456
    Пример 4
    1
    /* Демонстрация определения свободного места в heap- */
    /* области с помощью библиотечной функции coreleft() */

    /* ------------------------------------------------- */
    #include
    #include
    #include
    /* ------------- */ int main()
    { long int f1,f2; int *p;
    /* ---------------------------------------- */ printf("Свободное место в heap-области: %p\n", f1=coreleft()); p=(int *)calloc(sizeof(int),3); printf("Свободное место в heap-области: %p\n", f2=coreleft()); printf("Занято в heap-области (в условных единицах):
    %lu\n", (f1-f2)/sizeof(int)/3); printf("Контрольный вывод: %d %d %d\n",
    *p=10,*(p++)=11,*(p++)=12); free(p-2); printf("Свободное место в heap-области:
    %p\n\n",coreleft()); getch(); return 0;
    }
    Результат работы программы:
    Свободное место в heap-области: F7B0
    Свободное место в heap-области: F7A0
    Занято в heap-области: 16
    Контрольный вывод: 10 11 12
    Свободное место в heap-области: F7B0
    Пример 5.
    /* Демонстрация работы с динамической памятью */
    /* ------------------------------------------ */
    #include
    #include
    #include
    /* ------------- */ int main()
    { int *a,*b; printf("Свободное место в heap-области: %u\n",coreleft()); a=(int *)calloc(sizeof(int),1); b=(int *)calloc(sizeof(int),1);
    *a=1; *b=2;
    /* Изобразим выполнение этих операторов на схеме: */
    /* ---------------------------------------------------- */

    /* int *a;
    */
    /* int *b;
    */
    /* a=(int *)calloc(sizeof(int),1);
    */
    /* b=(int *)calloc(sizeof(int),1);
    */
    /* *a=1;
    */
    /* *b=2;
    */
    * ---------------------------------------------------- */ printf("a+b=%d\n",*a+*b);
    /* --------------------------------------------------- */
    /* free(a);
    */
    /* free(b);
    */
    /* ---------------------------------------------------- */ free(b); free(a);
    /* Вызовы функции free() записаны в порядке, обратном */
    /* тому, в каком записана последовательность функций */
    /* calloc(), хотя это и не обязательно */
    /* ---------------------------------------------------- */ printf("Свободное место в heap-области: %u\n",coreleft()); getch();
    }
    Результат работы программы:
    Свободное место в heap-области: 63472 a+b=3
    Свободное место в heap-области: 63472
    Пример 6.
    /* Демонстрация разыменования указателя, который не */
    /* был инициализирован. Это может привести: */
    /* (1) к фатальной ошибке во время выполнения прог- */
    /* раммы; */
    /* (2) случайному искажению данных, в результате */
    /* чего программа завершается с неверным результатом */
    /* ------------------------------------------------- */
    #include
    #include
    int main()
    { int dec=10,
    *ptr=&dec, /* Указатель ptr инициализирован */
    *ptr1; /* Указатель ptr1 не инициализирован */
    /* --------------------------------------------- */ printf("Указатель инициализирован:\n"); printf(" (%p)=%d\n",ptr,*ptr); printf(" &*ptr=%p\n *&ptr=%p\n\n",&*ptr,*&ptr);
    /* ----------------------------------------- */ printf("Указатель не инициализирован:\n"); printf(" (%p)=%d\n",ptr1,*ptr1); printf(" &*ptr1=%p\n *&ptr1=%p\n\n",&*ptr1,*&ptr1);
    /* --------------------------------------------- */ printf("Инициализируем неинициализированный указатель:\n"); ptr1=&dec; printf("(%p)=%d\n\n",ptr1,*ptr1); getch(); return 0;
    }
    Результат работы программы:
    Указатель инициализирован:
    (FFF4)=10
    &*ptr=FFF4
    *&ptr=FFF4
    Указатель не инициализирован:
    (8E01)=12342
    &*ptr=8E01
    *&ptr=8E01
    Инициализируем неинициализированный указатель:
    (FFF4)=10
    Пример 7.
    /* Демонстрация использования указателя на "пустой" тип */
    /* Компилятор: Borland C 3.1 */
    /* ---------------------------------------------------- */
    #include
    #include
    #include int main()
    { int dec=10,*u,*v; void *ptr=&dec;
    /* ----------------------------- */
    /*
    */
    /* (void *) */

    /* */
    /* ----------------------------- */ printf("(%u)=%d\n",u=ptr,*(u=ptr)); printf("Свободное место в heap-области: %u\n",coreleft()); u=(int *)calloc(sizeof(int),1); printf("Значение указателя: %u\n",ptr);
    *u=12; ptr=u;
    /* ---------------------------------------------------- */
    /* */
    /*
    */
    /* ---------------------------------------------------- */ v=ptr;/* Нет необходимости в явном преобразовании типов */ printf("(%u)=%d\n",ptr,*((int *)ptr)); ptr=v=NULL; free(u);
    /* ------------------------------------------ */ printf("Свободное место в heap-области: %u\n\n", coreleft()); getch(); return 0;
    }
    Результат работы программы:
    По адресу 65524 хранится 10
    Свободное место в heap-области: 63424
    Значение указателя: 1548
    Свободное место в heap-области: 63424
    Значение указателя: 1548
    Пример 8.
    /* Демонстрация определения размера динамической памяти */
    /* ---------------------------------------------------- */
    #include
    #include
    #include
    /* ------------- */ int main()
    { int k; void *ref; for (k=1;;k++)
    {
    ref=calloc(k,1); if (!ref) break; free(ref);
    } printf("Размеры доступной heap-области: %u\n",--k); printf("Контроль свободного места: %u\n\n",coreleft()); getch(); return 0;
    }
    Результат работы программы:
    Размеры доступной heap-области: 63464
    Контроль свободного места: 63456
    Пример 9.
    // Демонстрация использование операций манипулирования
    // динамической памятью (new и delete)
    // Компилятор Borland C++ 3.1
    // ----------------------------
    #include
    #include
    #include int main()
    { printf("Свободное место в heap-области: %u\n",coreleft()); int *iPtr=new int; // Указатель iPtr содержит адрес динами-
    // ческой переменной типа int
    // --------------------------------- double *dPtr=new double (3.1415926);
    // Указатель dPtr содержит адрес динами-
    // ческой переменной типа double и ини-
    // циализирует ее значением 3.1415926
    // -----------------------------------------------------
    // Для знатоков строк в языке C int strlen=4; char *str; str=new char[strlen]; // Указатель str содержит адрес
    // массива элементов типа char str[0]='А'; str[1]='б'; str[2]='В'; str[3]='\0';
    // --------------------------------------------- if (iPtr&&dPtr&&str==0)
    { printf("Не хватает памяти для динамически "
    "размещаемых переменных!\n"); return 1;
    } else {
    *iPtr=1000;
    printf("По адресу iPtr=%p содержится *iPtr=%d\n", iPtr,*iPtr); delete iPtr;
    // ---------------------------------------------- printf("По адресу dPtr=%p содержится *dPtr=%f\n", dPtr,*dPtr); delete dPtr;
    // -------------------------------------------- printf("Строка по адресу str=%p содержит %s\n", str,str); delete[] str;
    // --------------------------------------------- printf("Свободное место в heap-области: %u\n\n", coreleft()); getch(); return 0;
    }
    }
    Результат работы программы:
    Свободное место в heap-области: 61872
    По адресу iPtr=0C10 содержится *iPtr=1000
    По адресу dPtr=0C18 содержится *dPtr=3.141593
    Строка по адресу str=0C24 содержит АбВ
    Свободное место в heap-области: 61872
    ЗАДАЧИ ДЛЯ САМОСТОЯТЕЛЬНОГО РЕШЕНИЯ
    Собственно говоря, вы всегда должны проверять все
    указатели, как только начнут появляться любые ошибки.
    Г.Шилдт
    1. Указатели
    1. Проверьте, соответствует ли описание типов приведённым рисункам? int dec=10; int *ptr1=&dec; int *ptr2=ptr1;
    int **ref=&dec;
    2. Дано следующее описание переменных: int *p,*q;
    Пусть переменные p и q имеют значения, показанные ниже:
    (a) Что является значением переменной p: ссылка на объект (переменную) целого типа или сам этот объект? (б) Что обозначает переменная *p: ссылку на объект целого типа, сам этот объект или целое число 5? (в) Каковы типы переменных p и *p?
    3. Дано описание переменных int *p,*q;
    Пусть переменные p и q имеют значения, показанные ниже:
    Что будет выдано на печать в результате выполнения следующих программных фрагментов?
    (1)
    *p=*q; if (p==q) p=NULL; else if (*p==*q) q=p; if (p==q)
    *q=4; printf("%d",*p);
    (2)
    *p=*q; if (p==q) p=NULL; else if (*p==*q) q=p; if (!p==q)
    *q=4; else *p=6; printf("%d",*p);
    (3)
    *p=*q; if (p!=q) p=NULL; else if (*p==*q) q=p; if (!p==q)
    *q=4; else *p=6; printf("%d",*p);
    4. Дано следующее описание переменных: int *p,*q; char *r;
    Какие из следующих операторов присваивания неправильны и почему?
    (а) p=q;
    (в) p=NULL;
    (д) q=*p;
    (б) q=r;
    (г) r=NULL;
    (е) *p=NULL.
    5. Дано следующее описание переменных: int *p,*q; char *r;
    Какие из следующих операторов неправильны и почему?
    (а) *r=*p;
    (б) *q=(int)*r;
    (в) if (r!=NULL)
    *r=*NULL;
    (г) if (q>NULL)
    *q=*p;
    (д) if (q==p) printf("%d",q);
    (e) if (q!=r) scanf("%c",r);

    6. Допустимы ли в языке C конструкции
    *q+2, **q, ++*q, *++q? Ответ обоснуйте.
    7. Верно ли утверждение о том, что операторы присваивания px=&x; y=*px; семантически эквивалентны одному оператору y=x;?
    8. Рассмотрим следующую программу:
    #include
    #include int main()
    { int *x,y; x=(int *) calloc(sizeof(int),1); /* A */
    *x=1; y=-*x; /* B */ free(x); /* C */ printf("Значение y: %d",y); /* D */ return 0;
    }
    (a) Какие из переменных существуют в каждой из точек A, B, C, D и каковы их значения в эти моменты? (б) Почему объекты (переменные), создаваемые функцией calloc() и уничтожаемые функцией free(), называют динамическими
    объектами? Почему им не дают имена? (в) Можно ли переменной x присвоить ссылку на переменную y? Можно ли с помощью функции free() уничтожить переменные x и y?
    9. [Дейтел,Дейтел,2002,с.311,№7.4]
    Выполните каждую из следующих задач при помощи одиночного оператора.
    Считайте объявленными переменные с плавающей точкой number1 и number2, причём number1 присвоено значение 7.3.
    (а) Объявите указатель fPtr на тип float.
    (б) Присвойте адрес переменной number1 указателю fPtr.
    (в) Выведите значение величины, на которую ссылается fPtr.
    (г) Присвойте значение величины, на которую указывает fPtr, переменной number2.
    (д) Выведите значение number2.
    (е) Выведите адрес number1. Используйте спецификатор формата %p.
    (ж) Выведите адрес, сохранённый в fPtr. Используйте спецификатор формата
    %p. Совпало ли выведенное значение адреса с адресом number1?
    10. [Дейтел,Дейтел,2002,с.313,№7.10]
    Выполните каждое из следующих заданий, используя для этой цели только один оператор. Считайте объявленными переменные value1 и value2 типа long, причём переменной value1 присвоено значение 20000.
    (а) Объявите указатель lPtr на объект данных типа long.
    (б) Присвойте значение адреса переменной value1 указателю lPtr.
    (в) Выведите значение объекта, на который ссылается lPtr.
    (г) Присвойте значение объекта, на который ссылается lPtr, переменной value2.
    (д) Выведите значение value2.
    (е) Выведите адрес value1.

    (ж) Выведите значение адреса, находящееся в lPtr. Совпадает ли выведенное значение с адресом value1?
    11. (По [Дейтел,Дейтел,2002,с.321-322,№7.23])
    Найдите ошибку в каждом из следующих программных фрагментов. Если ошибку можно исправить, объясните, как это сделать.
    (а) int *number; printf("%d\n",*number);
    (г) short *numPtr,result; void *genericPtr=numPtr; result=*genericPtr+7;
    (б) float *realPtr; long *integerPtr; integerPtr=realPtr;
    (д) float x=19.34; float xPtr=&x; printf("%f\n",xPtr);
    (в) int *x,y; x=y;
    (е) char *s; printf("%s\n",s);
    12. Определите, правильно ли представлен графически следующий программный фрагмент: int *p=NULL,
    **q=&p,
    ***r=&q; p=(int *)&r; printf("%p %p %p %p %p %p %p\n", p,*q,r,*p,q,*r,***r);
    Если фрагмент правилен, то определите, что будет напечатано в результате его работы.
    13. Напишите программный фрагмент, который представлен графически следующим образом:
    14. Правилен ли приведённый ниже программный фрагмент: int a=4,*p=&a; char b='d',*q=&b; q=p; printf("%d %d %c\n",*p,*q,b); который позволяет перейти от графического представления к представлению
    Если фрагмент правилен, то определите, что будет выведено на экран в результате его работы.


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