
- •Часть 1
- •Общие сведения Сведения об эумк
- •Методические рекомендации по изучению дисциплины
- •Рабочая учебная программа
- •Учреждение образования
- •«Белорусский государственный университет
- •Информатики и радиоэлектроники»
- •Часть 2 __184__
- •Содержание дисциплины
- •1. Индивидуальные практические занятия, их характеристика
- •2. Контрольные работы, их характеристика
- •3. Курсовой проект, его характеристика
- •4. Литература
- •4.1. Основная
- •4.2. Дополнительная
- •5. Перечень компьютерных программ, наглядных и других пособий, методических указаний и материалов и технических средств обучения
- •Протокол согласования учЕбной программы по изучаемой учебной дисциплине с другими дисциплинами специальности
- •Теоретический раздел Введение
- •1. Основные типы данных
- •1.1. Общие сведения
- •1.2. Данные типа int
- •1.3. Данные типа char
- •1.4. Модификаторы доступа const и volatile
- •1.5. Данные вещественного типа (с плавающей точкой)
- •1.6. Элементарный ввод-вывод
- •1.7. Структура простой программы на языке Си
- •2. Операции и выражения
- •2.1. Выражение и его интерпретация
- •2.2. Основные операции
- •2.2.1. Арифметические операции
- •2.2.2. Побитовые логические операции
- •2.2.3. Операции сдвига
- •2.2.4. Операция присваивания
- •2.2.5. Операция sizeof
- •2.2.6. Преобразование типов в выражениях
- •2.2.7. Операция преобразования типов
- •2.2.8. Приоритеты в языке Си
- •3. Операторы управления вычислительным процессом
- •3.1. Оператор if
- •3.2. Операции отношения
- •3.3. Логические операции
- •3.4. Операция запятая
- •3.5. Операция условия ?:
- •3.6. Оператор безусловного перехода goto
- •3.7. Оператор switch
- •`` ` `3.8. Операторы цикла
- •3.8.1. Оператор for
- •3.8.2. Оператор while
- •3.8.3. Оператор do...While
- •3.9. Оператор break
- •3.10. Оператор continue
- •4. Массивы и указатели
- •4.1. Одномерные массивы и их инициализация
- •4.2. Многомерные массивы и их инициализация
- •4.3. Объявление указателей
- •4.4. Операции над указателями
- •1) Взятие адреса
- •2) Косвенная адресация или разыменование указателя
- •3) Увеличение или уменьшение значения указателя на целое число
- •4) Разность указателей
- •5) Сравнение указателей
- •6) Присваивание указателей друг другу
- •4.6. Связь между указателями и массивами
- •4.7. Динамическое распределение памяти
- •4.8. Массивы указателей
- •5. Функции
- •5.1. Общие сведения
- •5.2. Область видимости переменных
- •5.2.1. Локальные переменные
- •5.2.2. Глобальные переменные
- •5.3. Передача параметров в функцию
- •5.4. Рекурсивные функции
- •5.5. Использование функций в качестве параметров функций
- •5.6. Указатели на функции
- •5.7. Структура программы на Си
- •5.8. Передача параметров в функцию main()
- •6. Строки
- •7. Классы хранения и видимость переменных
- •7.1. Общие сведения
- •7.2. Автоматический класс хранения (auto)
- •7.3. Регистровый класс хранения (register)
- •7.4. Статический класс хранения (static)
- •7.5. Внешний класс хранения (extern)
- •7.6. Заключение
- •8. Структуры, объединения и перечисления
- •8.1. Общие сведения
- •8.2. Инициализация структурных переменных
- •8.3. Вложенные структуры
- •8.4. Указатели на структуры
- •8.5. Массивы структурных переменных
- •8.6. Передача функциям структурных переменных
- •8.7. Оператор typedef
- •8.8. Поля битов в структурах
- •8.9. Объединения
- •8.10. Перечисления
- •9. Динамические структуры данных
- •9.1. Общие сведения
- •9.2. Связные списки
- •9.2.1. Односвязные списки
- •9.2.2. Двусвязные списки
- •9.2.3. Циклические списки
- •9.3. Стеки
- •9.4. Очереди
- •9.5. Деревья
- •9.5.1. Понятие графа
- •9.5.2. Бинарные деревья
- •10. Файлы
- •10.1. Общие сведения
- •10.2. Открытие и закрытие файлов
- •10.3. Функции ввода-вывода для работы с текстовыми файлами
- •10.4. Произвольный доступ к файлу
- •10.5. Функции ввода-вывода для работы с бинарными файлами
- •11. Директивы препроцессора
- •11.1. Основные понятия
- •11.2. Директива #include
- •11.3. Директивы препроцессора #define и #undef
- •11.3.1. Символические константы
- •11.3.2. Макросы с параметрами
- •11.3.3. Директива #undef
- •11.4. Условная компиляция
- •11.5. Директивы # и ##
- •12. Модульное программирование
- •13. Введение в объектно-ориентированное программирование
- •13.1. Постановка задачи
- •13.2. Решение задачи средствами Си
- •13.5. Наследование
- •13.6. Перегрузка
- •13.7. Ссылочный тип
- •Литература
- •Приложение 1. Рекомендации по оформлению текстов программ
- •Тесты к теоретическому разделу Вопросы к разделу 1. Основные типы данных
- •Вопросы к разделу 2. Операции и выражения
- •Вопросы к разделу 3. Операторы управления вычислительным процессом
- •Вопросы к разделу 4. Массивы и указатели
- •Вопросы к разделу 5. Функции
- •Вопросы к разделу 6. Строки
- •Вопросы к разделу 7. Классы хранения и видимость переменных
- •Вопросы к разделу 8. Структуры, объединения и перечисления
- •Вопросы к разделу 9. Динамические структуры данных
- •Вопросы к разделу 10. Файлы
- •Вопросы к разделу 11. Директивы препроцессора
- •Вопросы к разделу 12. Модульное программирование
- •Вопросы к разделу 13. Введение в ооп
- •Правильные ответы на вопросы тестов к теоретическому разделу
- •Вопросы к теоретическому зачету
- •Варианты индивидуальных заданий
- •Контрольная работа №2
- •Варианты индивидуальных заданий
- •Индивидуальные практические работы Указания к выбору варианта индивидуальных практических работ
- •Индивидуальная практическая работа № 1. Массивы и строки
- •Варианты индивидуальных заданий
- •Индивидуальная практическая работа № 2. Динамические структуры данных
- •Варианты индивидуальных заданий
4.7. Динамическое распределение памяти
Для динамического распределения памяти применяются функции malloc() и free(). Их прототипы находятся в файле <stdlib.h>.
Прототип функции malloc():
void *malloc(unsigned s);
Функция возвращает указатель типа void на блок выделенной памяти длиной s байт. Если необходимого количества памяти нет в наличии – возвращает NULL.
Например:
char *p;
p = (char *) malloc(1000); // выделяет 1000 байт
Функция free() освобождает память, то есть память возвращается системе и в дальнейшем её можно выделить снова. Для высвобождения памяти, динамически выделенной ранее, используется функция free(). Её прототип:
void free(void *p);
То есть, чтобы освободить память, выделенную в предыдущем примере, нужно написать
free(p);
При динамическом распределении памяти для массивов следует описать указатель соответствующего типа и присвоить ему значение при помощи функции calloc. Вот прототип этой функции:
void *calloc(unsigned n, unsigned m);
Функция принимает два аргумента: число элементов n и размер каждого элемента m, инициализирует выделенный блок памяти нулями и возвращает указатель на его начало Если по каким-либо причинам (например, в случае нехватки физической памяти) память не может быть выделена, возвращается NULL.
Одномерный массив a[10] из элементов типа float можно создать следующим образом
float *a;
a = (float *) calloc(10,sizeof(float));
Для динамического создания двумерного массива вначале нужно распределить память для массива указателей на одномерные массивы, а затем распределять память для одномерных массивов. Пусть, например, требуется создать массив a[m][n]. Это можно сделать при помощи следующего фрагмента программы:
#include <stdlib.h>
void main()
{
double **a;
int n,m,i;
scanf("%d %d",&m,&n);
a = (double **) calloc(m,sizeof(double *));
for (i = 0; i < m; i++)
a[i] = (double *) calloc(n, sizeof (double));
}
Функция realloc изменяет размер объекта, выделенного в результате предыдущего вызова malloc, calloc или realloc. Содержимое первоначального объекта сохраняется при условии, что размер вновь выделяемой памяти больше, чем первоначальный. В противном случае содержимое остается неизменным лишь в пределах размера нового объекта. Прототип функции realloc выглядит следующим образом:
void *realloc(void *ptr, unsigned size);
Функция realloc принимает два аргумета: указатель на первоначальный объект (ptr) и новый размер объекта (size). Если ptr равен NULL, realloc работает аналогично malloc. Если size равен 0, а ptr не NULL, память, выделенная для объекта, освобождается. В противном случае, если ptr не NULL и размер больше нуля, realloc пытается выделить для объекта новый блок памяти. Функция realloc возвращает либо указатель на вновь выделенную память, либо NULL.
4.8. Массивы указателей
Массивы могут состоять из указателей. Обычный случай такого массива – это массив строк. Элементом такого массива является строка. В качестве примера рассмотрим массив name, который может быть использован для хранения имен.
char *name[4] = {“Nata”,”Lena”,”Masha”,”Katya”};
Выражение name[4] в объявлении означает массив из четырех элементов. Элементами данного массива будут являться указатели на тип char. Каждый указатель инициализируется адресом строки, указанной в фигурных скобках.
Каждое из значений переменной-указателя (“Nata”,”Lena” и т.д.) хранится в памяти как строка символов с конечным нулевым символом.
В name[0] содержится адрес “Nata”.
Для хранения тех же данных мы могли использовать двумерный массив. При таком подходе все строки должны быть одинаковой длины, равной размеру самой длинной строки символов. Это привело бы к неоправданному расходу памяти в случае, когда большинство сохраняемых строк короче, чем самая длинная строка. Также преимущество массива указателей состоит в том, что можно работать не с самими строками, а с их адресами, что дает выигрыш во времени.
Следующие объявления переменных
int a[] = {10,11,12,13,14,}; // массив целых чисел
int *p[] = {a, a+1, a+2, a+2, a+3, a+4}; // массив указателей
// на целые числа
int **pp = p; //указатель на указатель
порождают программные объекты, представленные на схеме рис.4.5.
Рис.4.5. Схема размещения переменных при объявлении
При выполнении операции pp-p получим нулевое значение, так как ссылки pp и p равны и указывают на начальный элемент массива указателей, связанного с указателем p (на элемент p[0]).
После выполнения операции pp+=2 схема изменится и примет вид, изображенный на рис.4.6.
Рис.4.6. Схема размещения переменных после выполнения операции pp+=2
Результатом выполнения вычитания pp-p будет 2, так как значение pp есть адрес третьего элемента массива p. Ссылка *pp-a тоже дает значение 2, так как обращение *pp есть адрес третьего элемента массива a, а обращение a есть адрес начального элемента массива a. При обращении с помощью ссылки **pp получим 12 - это значение третьего элемента массива a. Ссылка *pp++ даст значение четвертого элемента массива p т.е. адрес четвертого элемента массива a.
Если считать, что pp=p, то обращение *++pp это значение второго элемента массива a (т.е. значение 11), операция ++*pp изменит содержимое указателя p[0], таким образом, что он станет равным значению адреса элемента a[1].
Сложные обращения раскрываются изнутри. Например, обращение *(++(*pp)) можно разбить на следующие действия: *pp дает значение начального элемента массива p[0], далее это значение инкременируется ++(*p), в результате чего указатель p[0] станет равен значению адреса элемента a[1], и последнее действие это выборка значения по полученному адресу, т.е. значение 11.
Объявление переменных
int a[3][3] = { { 11,12,13 },
{ 21,22,23 },
{ 31,32,33 } };
int *pa[3] = { a, a[1], a[2] };
int *p = a[0];
порождает в программе объекты, представленные на схеме рис.4.7.
Рис.4.7. Схема размещения указателей на двумерный массив
Согласно этой схеме доступ к элементу a[0][0] можно получить по указателям a, path, pa при помощи следующих ссылок: a[0][0], *a, **a[0], *p, **pa, *p[0].