- •Основы программирования на языке С
- •Введение
- •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
p[0]=1; /* Ошибка! Массива, на который указывал p, уже нет ! */ /* Создаем, используем и уничтожаем еще один массив */
p = malloc(2 * sizeof(int)); p[0] = 7;
free(p);
Данный фрагмент программы показывает, что освободившийся после возврата динамической памяти в кучу, указатель p может быть использован для создания нового массива или же для любых других целей.
10.1. Универсальный указатель void
Если в программе необходимо работать с данными разных типов, то часто требуется преобразование указателей к различным типам, например:
…
int *ip; double *dp;
dp = ip; // будет предупреждение транслятора
Предупреждение выдается не зря - подобные операции с указателями, если это сделано не осознанно, приводят к трудно выявляемым ошибкам. Поэтому и рекомендуют в подобных случаях явно приводить один тип указателя к другому. Ошибку в предыдущем примере можно исправить следующим образом: dp = (double*) ip;
Казалось бы, при таком внимательном отношении транслятора к типам указателей примеры, приведенные выше, должны пестреть от операторов приведения типов, например таких:
…
char *cp; double *dp;
cp = (char *) malloc(100);
dp= (double*)calloc(100, sizeof(double));
Однако во всех примерах работы с динамической памятью мы ни разу не пользовались операторами приведения типов! Почему это было возможно, и какой тип указателя используют функции calloc(), malloc(), realloc()? Для ответа на этот вопрос, рассмотрим уже извесный нам тип void.
Свойства типа void.
∙Переменных этого типа не существует.
∙Первое применение void - отсутствие значения, указание того, что функция ничего не возвращает или не содержит параметров.
∙Второе применение типа void - указатели.
Для указателя void не работает арифметика указателей, поскольку отсутствует само понятие размера элемента. Транслятор не позволит написать для такого указателя ptr[2] или ptr++.
Указатель void нужен для хранения значения указателей различных типов. По существу транслятор
позволяет использовать операции присваивания между указателем void и любыми другими типами указателей без явного приведения типов:
Пример: Присваивание между указателем типа void и любым другим типом указателя.
…
void *vp; char *cp; double *dp;
/* Несмотря на отсутствие оператора приведения типа, ни одна из следующих строк не даст предупреждения транслятора */
cp = vp; vp = cp; dp = vp; vp = dp;
Теперь пришло время ответить на ранее поставленный вопрос об указателях при работе с динамической памятью.
Функции работы с динамической памятью - calloc(), malloc(), realloc() возвращают указатель типа void, который можно приравнять к указателю любого типа, а значит, при выделении динамической памяти указатели справа и слева от операции присваивания могут иметь разные типы.
Прототипы функций имеют следующий вид: void* malloc(int size);
void* сalloc (int qu, int size); void *free(void *p);
PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com
10.2.Принципы работы с динамическими массивами
Взаключении, перечислим правила "хорошего тона" при работе с динамической памятью, соблюдение этих несложных правил позволит избежать типичных ошибок, возникающих у начинающих программистов, внесёт в Вашу работу систему и порядок, а программный код сделает более надежным.
1. Не забывайте об общих правилах работы с указателями, эти правила желательно соблюдать всегда где бы ни использовались указатели:
∙Указатели должны содержать либо NULL, либо какой-нибудь разумный адрес, то есть адрес существующей переменной, принадлежащий выполняемой программе. В частности, освободив память функцией free(), не забудьте стереть уже недействительный адрес, то есть занести в указатель NULL,
иначе вы рискуете по ошибке использовать уже не принадлежащую задаче память.
∙Почаще проверяйте значение указателя перед использованием, NULL должен сигнализировать о том, что указатель "свободен", то есть не указывает ни на один объект:
…
char *p = malloc(100); char c;
free(p);
p=NULL;
/* Так легко ошибиться, так как p не установлен! */ c = *p;
/*А так безопасно, так как программа проверила, что p содержит адрес переменной */ if (p!=NULL)
{ c = *p;
}
2. Всегда следует сохранять параметры выделенной динамической памяти:
∙Не теряйте длину выделенной области - иначе вы не сможете узнать, сколько же у вас элементов в динамическом массиве.
∙Не теряйте исходный адрес, который вам вернули malloc(), calloc() или realloc() - без него вы не сможете освободить выделенную область, а также организовать многопроходную работу с выделенной памятью.
Приведем пример, который иллюстрирует неправильную работу с динамической памятью.
…
char *p;
int size=k; // размер требуемой памяти p = malloc(size);
size=10;
p++;
После выполнения инструкции size=10 - потерян размер выделенной памяти, после инструкции p++ - потерян начальный адрес выделенной памяти. После этих действий невозможно работать с динамической памятью, так как неизвестен ни её начальный, ни конечный адрес, также невозможно будет вернуть память в кучу.
Следующий пример демонстрирует стандартные подходы, помогающие избегать перечисленных проблем.
Рассмотрим сортировку динамического массива. Алгоритм сортировки методом "пузырька" подробно рассмотрен в разделе "Функции", где мы сортировали статический массив, воспользуемся этим же алгоритмом для решения поставленной задачи. Обратите внимание, что определения функций swab() и mas_print() не изменились. Так как после выделения памяти из кучи, обращение к отдельным элементам массива ничем не отличается от обращения к статическому массиву через указатель.
В функции main() работа идет по следующему сценарию:
∙Для полноценной работы с массивом через указатели необходимо два указателя - один для хранения начала массива (begin), второй для текущей работы (tek). Следует отметить, что требование этого пункта относится равно как к статическим, так и к динамическим массивам, работающим с указателями.
∙Проходом обработки массива (неважно, в чем эта обработка заключается) называется последовательный "перебор" всех элементов массива (в нашем примере от начала к концу).
∙Перед каждым проходом обработки массива необходимо записывать адрес начала массива в текущий указатель, инструкция tek= begin в заголовке цикла for выполняет эту работу.
∙Условие выполнения цикла for - tek<(begin+n) заключается в следующем: пока адрес в текущем указателе не превышает адреса последнего элемента массива (begin+n)
∙Переход к очередному элементу происходит по инструкции tek++ в заголовке цикла for.
PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com
Пример. Создать динамический массив, заполнить его случайными числами и отсортировать по возрастанию.
#include <iostream.h>
#include <stdlib.h> |
|
#include <time.h> |
|
#define n 101 |
// размер массива |
#define col 10 |
// печать по 10 элементов в строке |
void swab (int *p1 , int *p2)
{/* создание локальной переменнрой f и записть в нее значение из переменной указателя p1 */ int f=*p1;
*p1=*p2; *p2 =f;
}
void mas_print (int *p_beg, int *p_end, int k)
{int i=0;// счетчик выведенных элементов for (i=0 ; p_beg < p_end ; i++)
{ cout<<*p_beg++;
if ((i+1)%k==0) cout<<'\n'; else cout<<'\t';
} |
|
|
} |
|
|
void main() |
|
|
{ int *begin,*tek,i; //определение указателей |
|
|
srand(time(0)); |
|
|
begin=(int*)malloc(n*sizeof(int)); // выделение памяти из кучи |
|
|
// формирование исходного массива |
|
|
for (tek=begin ; tek<(begin+n) ; tek++) |
|
|
*tek=rand(); |
|
|
mas_print(begin , (begin+n) , col);// печать массива до сортировки |
|
|
cout<<"\n!!! сортировка !!!!\n"; |
|
|
for(i=0 ; i<n ; i++) //______сортировка___ |
|
|
for( tek=begin ; tek<(begin+n-i-1) ; tek++) |
|
|
if (*tek > *(tek+1)) |
|
|
|
swab ( tek , (tek+1) ); |
//__________________________| |
mas_print(begin , begin+n,col);//печать отсортированного массива |
|
|
free (begin); |
// возвратить память в кучу |
|
} |
|
|
При выделении памяти из кучи некоторые компиляторы требуют явного приведения типов указателей правой и левой части равенства, что и сделано в примере: begin=(int*) malloc(n*sizeof(int));
PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com
Вопросы для самопроверки
1.Чем отличаются статические переменные от динамических?
2.Какие программные объекты могут выступать в качестве динамических объектов?
3.Поясните термин "куча", для чего используется "куча"?
4.Какие функции динамического выделения памяти Вы знаете?
5.Приведите примеры динамического выделения памяти.
6.Можно ли изменить размер динамического массива?
7.Какая из функций calloc() или malloc() очищает память при создании динамического массива?
8.Какая из функций calloc() или malloc() не инициализирует (не очищает) память при создании динамического массива?
9.Какая функция уничтожает динамический массив? Для чего это нужно?
10.Универсальный указатель void? Для чего он нужен?
11.Приведите схему многопроходной работы с динамической памятью.
PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com