- •Основы программирования на языке С
- •Введение
- •1. Базовые понятия программирования
- •1.1. Алгоритмизация задачи
- •1.2. Схема алгоритма программы
- •1.3. Пример алгоритмизации
- •1.4. Этапы трансляции программы
- •2. Особенности языка С
- •2.1. Характеристики языка
- •2.2. Элементы языка
- •2.3. Стандартные типы данных
- •2.4. Компоненты простой программы
- •Вопросы для самопроверки
- •3. Выражения и операции
- •3.1. Операция и выражение присваивания
- •3.2. Бинарные арифметические операции
- •3.3. Операции увеличения (++) и уменьшения (--)
- •3.4. Преобразования типов при вычислении арифметических выражений
- •3.5. Тернарная или условная операция
- •3.6. Логические операции и операции отношения
- •3.7. Поразрядные (побитовые) операции
- •3.8. Операции сдвига
- •3.9. Операция sizeof
- •3.10. Операция следования
- •3.11. Сводная таблица операций языка С
- •Вопросы для самопроверки
- •4. Операторы
- •4.1. Оператор выражение
- •4.2. Пустой оператор
- •4.3. Объявления и составной оператор
- •4.4. Условный оператор
- •4.5. Оператор выбора switch
- •4.6. Циклы
- •4.7. Оператор break
- •4.8. Оператор безусловного перехода goto
- •4.9. Оператор continue
- •4.10. Оператор return
- •Вопросы для самопроверки
- •5. Массивы
- •5.1. Одномерные статические массивы
- •5.2. Объявление массива. Обращение к элементу массива
- •5.3. Инициализация массива
- •5.4. Многомерные массивы
- •5.5. Выход индекса за границы массива
- •5.6. Приемы работы с массивами в вычислительных задачах
- •5.7. Строка как массив символов
- •Вопросы для самопроверки
- •6. Указатели и ссылки
- •6.1. Понятие указателя
- •6.2. Операция получения адреса &
- •6.3. Операция разыменования (*)
- •6.4. Арифметика указателей
- •6.5. Применение указателей в выражениях
- •6.6. Указатели и массивы
- •6.7. Ссылочный тип данных
- •Вопросы для самопроверки
- •7. Время жизни и область видимости переменной
- •7.1. Общие понятия
- •7.2. Классы памяти
- •7.3. Вложенные блоки в программе
- •8. Функции
- •8.1. Общие понятия
- •8.2. Определение функции
- •8.3. Прототип функции
- •8.4. Переменные в функции
- •8.5. Передача параметров в функцию
- •Вопросы для самопроверки
- •9. Пользовательские типы данных
- •9.1. Структурный тип данных
- •9.2. Битовые поля
- •9.3. Объединения или смеси
- •9.4. Перечисления
- •Вопросы для самопроверки
- •10. Динамическая работа с памятью
- •10.1. Универсальный указатель void
- •10.2. Принципы работы с динамическими массивами
- •Вопросы для самопроверки
- •11. Ввод-вывод данных
- •11.2. Функции ввода-вывода библиотеки iostream
Вопросы для самопроверки
1.Какие разновидности пользовательских типов данных Вы знаете?
2.Дайте определение структурного типа данных.
3.Приведите пример использования структур.
4.Назовите этапы создания структурного типа данных.
5.Чем отличается создание структурного типа данных от создания структурного объекта данных?
6.Приведите пример создание структуры с помощью оператора struct.
7.Приведите пример создание структуры с помощью оператора typedef.
8.Покажите разницу между использованием структур, созданных с помощью ключевых слов struct и
typedef.
9.Назовите два оператора доступа к элементам структур, приведите примеры.
10.Допускается ли вложение одной структуры в другую? Если да, то, как осуществляется доступ к элементам вложенной структуры.
11.Допустимо ли использовать операцию присваивания целиком к структурным объектам? Если да, то, при каких условиях?
12.Допустимо ли использовать операции сравнения целиком к структурным объектам? Если да, то, при каких условиях?
13.С помощью какого оператора можно определить реальный размер структуры?
14.Дайте определение понятию "битовые поля", для каких целей они используются?
15.Приведите пример определения битовых полей.
16.Дайте определение понятию "объединения", для каких целей оно используются?
17.Приведите пример определения объединения.
18.Может ли массив выступать в роли элемента объединения?
19.Для чего используются "перечисления"?
20.Приведите пример использования перечисления.
PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com
10. Динамическая работа с памятью
Все объекты, размещенные в памяти - переменные, массивы, структуры, указатели и тому подобное, можно разделить на две категории:
∙Объекты, создаваемые до начала работы программы, при компиляции.
∙Объекты, создаваемые в процессе работы программы.
Опервых говорят как о статических, постоянно размещенных в памяти компьютера объектах, о вторых
-как о динамических, или временных объектах, создаваемых и уничтожаемых в процессе работы программы.
К статическим объектам можно отнести глобальные переменные и static-переменные.
К динамическим объектам, с которыми мы уже успели познакомиться, относятся локальные переменные функций и блоков операторов.
Ранее мы научились работать со статическими массивами, создавали их, задавая размер либо явно,
либо списком инициализации, в обоих случаях размер массива был известен и задавался до начала выполнения программы, при компиляции.
На практике встречается очень много задач, когда заранее нельзя определить, сколько элементов массива потребуется для работы, например:
∙найти пересечение 2-х массивов;
∙найти элементы массива, значения которых больше заданного;
∙базы данных для хранения информации;
∙и тому подобное.
Такие задачи можно решить, создавая массивы или другие структуры данных "с запасом", но этот способ, во-первых, неэкономно использует память, и, во-вторых, не всегда решает поставленную задачу.
Правильнее в такой ситуации создать массив нужного размера динамически - непосредственно во время работы программы, когда этот размер уже известен.
Как статические, так и динамические объекты размещаютися в памяти компьютера в строгой последовательности, определяемой операционной системой компьютера и компилятором языка программирования. Распределение памяти в программах, написанных на языке С, представлено на рис. 10.1.
(Верхние адреса памяти) |
Локальные переменные и константы. |
|
СТЕК. |
||
|
||
|
|
|
СВОБОДНАЯ ПАМЯТЬ. |
Динамическая память (Куча). |
|
|
|
|
Глобальные переменные и |
Фиксированное место в памяти |
|
константы. |
(на все время работы программы). |
|
|
|
|
ПРОГРАММА |
|
|
ПОЛЬЗОВАТЕЛЯ |
|
|
(Нижние адреса памяти). |
|
|
|
|
Рис. 10.1. Распределение памяти компьютера
Операционная система позволяет получать (а также возвращать) память динамически ИЗ КУЧИ во время выполнения программы.
Для работы с динамической памятью в С предусмотрено четыре функции из библиотеки stdlib.h:
∙malloc(), calloc() - захватывают память или выделяют память из кучи;
∙realloc() - позволяет изменять (как правило, увеличивать) размер уже выделенной памяти без потери
её
∙содержимого;
∙free() - освобождает уже ненужную память (возвращает память в кучу).
Функция malloc() захватывает область памяти, при этом размер в байтах указывается в качестве аргумента, malloc() возвращает указатель на захваченную память, то есть, ее адрес. При этом выделяемая память не инициализируется, то есть не очищается.
Пример: создать динамический массив из ста символов, а затем заполнить его нулями: #include <stdlib.h>
main()
{
int i; char *p;
PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com
/* Резервируем 100 байтов под массив char */ p = malloc(100);
for (i=0; i<100; i++) p[i] = 0;
return 0;
}
Если нам нужен массив не char, а например, int, то придется определить размер памяти в байтах с помощью оператора sizeof(), это можно сделать прямо при вызове функции:
/* Резервируем место под 100 элементов int */ p = malloc( 100 * sizeof(int) );
Функция calloc(), не только захватывает память, но и очищает ее (заполняет байты нулями), у нее два аргумента – количество элементов и размер элемента в байтах:
#include <stdlib.h> main()
{ int i; char *сp; int *ip;
/* Резервируем и очищаем 100 байтов под массив char */
сp = сalloc(100, 1 );
/*Резервируем и очищаем 100 элементов int*/ ip = сalloc(100, sizeof(*ip) );
return 0;
}
Функция calloc(nelm, elmsize) выделит столько же байтов, сколько и malloc(nelm*elmsize) и, кроме того, очистит выделенную память.
В примерах выше мы пользовались динамической памятью только для создания массивов, но таким же образом можно выделять память и под структуры, переменные встроенных типов и тому подобное.
На самом деле все эти функции работают с "абстрактной" памятью, и им абсолютно безразлично, как программист ее будет использовать. А тот указатель (точнее тип указателя), в который вы записываете адрес области, лишь помогает программе интерпретировать эту область нужным вам образом. Поставьте вместо указателя на int указатель на структуру - и вы будете работать с динамически созданной структурой или массивом структур:
#include <stdlib.h> #include "compleh.h"
struct COMPLEX { double re, im; }; void main()
{ struct COMPLEX *ptr; ptr=malloc(sizeof(struct COMPLEX)); ptr->re = 1.0;
ptr->im=0.0;
}
Функция realloc() позволяет изменять размер выделенного участка динамической памяти.
После создния динамического массива вы можете "увеличить" или уменьшить его размер. #include <stdlib.h>
void main() { int *ip;
/* Резервируем 100 элементов int */ ip = malloc(100*sizeof(*ip) );
...; // не хватило - наращиваем до 200 элементов
ip = realloc(ip, 200*sizeof(*ip) );
...; /* Нам достаточно 150 элементов, убираем 50 лишних */
ip = realloc(ip, 150*sizeof(*ip) );
...; return 0;
}
У функции realloc() два параметра:
∙указатель на уже имеющуюся область динамической памяти;
∙новый размер этой области;
∙функция также использует возвращаемое значение – указатель на захваченную область нужного размера
вбайтах.
Рассмотрим три особенности realloc():
PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com
1.Функция realloc() может изменять размер только динамического массива. В качестве первого аргумента ей можно передать только указатель, который, как в примере выше, был получен от malloc(), calloc() или от предыдущего вызова самой realloc(). Если вы попытаетесь передать ей адрес статического массива, компилятор выдаст ошибку:
…
char s[100]; char *p; char *p1;
p=malloc(100);
p1=realloc(p, 150); // так правильно p1=realloc(s,150); //а так делать нельзя!!!
2.Обратим внимание на содержимое новой области. Если вы увеличиваете размер массива с помощью realloc(), то содержимое исходного массива у вас сохранится., новые же элементы будут содержатьнепредсказуемые значения или "информационный мусор".
…
int *ip;
ip=malloc(2*sizeof(*ip)); // Создали массив из 2-х целых ip[0]=0;
ip[1]=1;
ip=realloc(ip, 3*sizeof(*ip)); // Добавляем еще один элемент.
ip = realloc(ip, sizeof(*ip)); // А теперь уберем два элемента
Естественно, вместе с элементами ip[1] и ip[2] мы потеряли и их содержимое.
3. И наконец надо учитывать, что когда вы пользуетесь realloc(), система не изменяет размер самой области, с которой вы работали, а создает для вас её копию, а исходную область освобождает. А это означает, что содержимое массива вы сохраняете, а вот адрес этого массива чаще всего становится другим, поэтому передав realloc() указатель в качестве первого аргумента, вы должны забыть о нем, и в дальнейшем
использовать только тот новый указатель, который получите от realloc(): |
|
||
char *p1, *p2; |
|
|
|
p1=malloc(100); |
|
|
|
p2 = p1; // Сохраняем копию указателя в p2 |
|
||
/*После realloc() p1 скорее всего |
поменяется, поэтому пользоваться |
старой копией p2 нельзя, |
|
память под этим адресом уже освобождена в realloc()*/ |
|
||
p1=realloc(p1, 1000); |
|
|
|
p1[0] = 'c'; |
// Правильно |
|
|
p2[0] = 'c'; |
// Ошибка. Эта память уже не наша |
|
Во всех примерах, иллюстрирующих выделение памяти, предполагалось, что программа эту память получает. Однако ресурсы любого компьютера ограничены, и может оказаться, что в системе не окажется нужного количества свободной памяти. В этом случае все три функции сообщат вам об ошибке, вернув NULL. Так что полезно после вызова любой из этих функций проверить, не возникло ли ошибки при выделении памяти:
…
char *p;
//Хотим получить гигабайт p = malloc(1024*1024*1024); // Проверяем, удлось ли это? if (p == NULL)
{ printf("Нет нужного объема памяти \n"); exit(1);
};
Если вы используете в программе динамический массив, рано или поздно наступает момент, когда он перестает быть нужен. Если этот момент совпадает с завершением задачи, можно особо не беспокоиться на этот счет - современные системы, снимая задачу с выполнения, заодно и освобождают отведенную ей память. Однако чаще случается, что массив уже не нужен, а программа продолжает работать. И в этом случае совсем не вредно освободить выделенную память - во-первых, другим задачам может пригодиться, а во вторых, вашей же задаче может не хватить памяти для другого массива.
Для того, чтобы освободить динамическую память, надо вызвать функцию free(), передав ей в аргументе тот указатель, который вы получили от malloc(), calloc() или realloc(). После вызова free() этим указателем уже нельзя будет пользоваться.
…
int *p;
p = malloc(5 * sizeof(*p)); // Создаем динамический массив p[0] = 1;
free(p); //Освобождаем память
PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com