- •1.1. Элементы языка программирования
- •Основные правила записи программы:
- •1.2. Алфавит языка
- •1.3. Лексемы
- •1.4. Концепция данных
- •2.2. Операции
- •2.2.1. Арифметические операции
- •2.2.2. Операции присваивания
- •2.2.3. Операции отношения
- •2.2.4. Логические операции
- •2.2.5. Поразрядные операции
- •2.2.6. Вычисление выражений
- •3. Структурное программирование
- •3.1. Общая характеристика операторов
- •3.2. Оператор-выражение
- •3.3. Условный оператор
- •3.4. Составной оператор
- •3.5. Операторы для программирования циклов
- •3.5.1. Оператор цикла for
- •3.5.2. Оператор цикла while
- •3.5.3. Оператор цикла do while
- •3.5.4. Оператор break
- •3.5.5. Оператор continue
- •3.6. Оператор goto
- •3.7. Пустой оператор
- •3.8. Оператор switch
- •3.9. Оператор return
- •4. Массивы
- •4.1. Объявление массива
- •4.2. Обращение к элементам массива
- •4.3. Типовые алгоритмы работы с массивами
- •4.4. Многомерные массивы
- •5. Строки
- •5.1. Объявление строки
- •5.2. Посимвольная обработка строк
- •5.3. Ввод строк
- •5.4. Библиотечные функции для работы с текстом
- •6. Указатели
- •6.1. Объявление указателей
- •6.2. Операции над указателями
- •6.3. Связь между указателями и массивами
- •6.4. Функция strtok для выделения лексем из текста
- •6.5. Динамические массивы
- •7. Структуры и объединения
- •7.1. Объявление структуры
- •Компонент структуры может быть любого типа, кроме типа объявляемой структуры.
- •7.2. Операции над структурами
- •7.3. Объявление объединения
- •8. Модульное программирование
- •8.1. Нисходящее проектирование и программирование
- •8.2. Определение и вызов функции
- •8.3. Место определения функции в программе
- •8.4. Обмен данными между функциями
- •8.4.1. Использование глобальных переменных
- •8.4.2. Использование аппарата формальных и фактических параметров
- •8.4.3. Передача массивов в функцию
- •8.5. Перегрузка функции
- •8.6. Шаблон функции
- •8.7. Рекурсивные функции
- •8.8. Функции с параметрами по умолчанию
- •8.9. Передача в функцию другой функции
- •9. Работа с файлами
- •9.1. Текстовые и двоичные файлы
- •9.2. Объявление файловых переменных
- •9.3. Чтение текстового файла
- •9.4. Создание текстового файла
- •9.5. Изменение данных в текстовом файле
- •9.6. Вывод в двоичный файл
- •9.7. Чтение данных из двоичного файла
- •9.8. Изменение данных двоичного файла
- •9.9. Организация файла с произвольным доступом
- •10. Данные с динамической структурой
- •10.1. Линейный список
- •10.1.1. Специальные типы линейных списков
- •10.1.2. Реализация линейного списка с помощью массива
- •10.1.3. Реализация линейного списка с помощью связанного однонаправленного списка
- •10.1.4. Реализация линейного списка с помощью связанного двунаправленного списка
- •10.2. Деревья
- •10.2.1. Основная терминология
- •10.2.2. Реализация двоичных деревьев поиска Для реализации дерева поиска используются массивы и связанные указателями элементы [3, 4].
- •10.2.3. Сбалансированные деревья
- •Основные достоинства в-дерева:
- •10.3. Графы
- •10.3.1. Определения
- •10.3.2. Реализация графа с помощью списков смежности
- •10.3.3. Реализация графа с помощью матрицы смежности
- •10.3.4. Поиск кратчайших путей. Алгоритм Дейкстры
- •10.3.5. Матрица достижимости. Алгоритм Уоршалла
6. Указатели
Указатель – это тип данных, значениями которых являются адреса оперативной памяти. Переменная-указатель (или просто указатель) содержит адрес ячейки памяти, в которой находится значение другой переменной или код функции.
Указатели используются для:
работы со строками и массивами;
работы с динамическими структурами данных, такими как очереди, списки, деревья, сети, графы;
передачи в функцию в качестве параметра адреса другой функции;
обмена данными между функциями в стиле языка С.
6.1. Объявление указателей
Синтаксис объявления переменной-указателя:
тип *имя_указателя;
где тип – это тип данных, на которые может ссылаться указатель.
Примеры объявлений указателей:
int *p1; //указатель на целое
float *p2, *р22; //указатели на вещественные данные
char *p3; // указатель на символ
char *p[15]; //массив из 15 указателей на символы
char (*p)[15] ;//указатель на массив из 15 символов
float **r; //указатель на указатель
Указателю при его объявлении можно присвоить начальное значение. Таким значением может быть адрес другой переменной или целое число 0. Указатель с нулевым значением не на что не указывает.
char c=’a’;
char *p3=&c; //& - операция получения адреса переменной
int *p4=0; //инициализация указателя нулем
int * const p=&x; //константный указатель, не может изменяться
const int * p1=&x; //указатель на константное целое значение
6.2. Операции над указателями
Присваивание
p1=p2
где р1 и p2 указатели одного типа.
Пример:
int x=2;
int y=1;
int *p1, *p2;
p1=&x; //p1 указывает на х
p2=p1; //р2 теперь также указывает на х
Операция косвенной адресации (разыменование)
*р
где р указатель.
Эта унарная операция (обозначаемая *) позволяет сослаться на переменную, адрес которой хранится в указателе р, и изменить или использовать значение переменной.
Пример:
int x=5;
int *p1; //объявление указателя
p1=&x; //р1 присваивается адрес х
cout<< *p1<<endl; //5 (обращение к х по адресу)
*p1=10; //изменение переменной х (обращение к х по адресу)
cout<<x; //10
Таким образом, к переменной можно обращаться по ее имени (прямо) и по ее адресу (косвенно).
Операции отношения
p1 p2
где p1 и p2 указатели одного типа, а знак операции: ==, !=, <, >, <=, >=
Пример:
float x=0.4;
float *p1=&x;
float *p2;
p2=p1;
if (p1==p2) cout<< “equal”
else cout<<“no equal”;
Операция получения адреса
&p
где р указатель.
Пример:
int x=5;
int *p1=&x;
int **p2;
p2=&p1;
cout<<*p1<<’ ‘<<p1<<’ ‘<<*р2<<’ ‘<<**p2<<’ ‘<<p2<<endl;
//5 0063FE00 0063FE00 5 0063FDC
Операция сложения
p+k
где p – указатель, k – целое число.
Результатом операции р+k является адрес памяти, расположенной на k единиц дальше от начальных адресов памяти, чем память, адресуемая левым операндом. Длина единицы памяти в операции измеряется длиной базового типа указателя.
Пример:
int a[5]={10,20,30,40,50};
int *p1=&a[1];
p1=р1+3; //р1 увеличивается на 12 байтов
cout<<*p1; //50
char c[6]=”abcdef”;
char *p2=&c[1];
p2=р2+3; //р2 увеличивается на 3 байтa
cout<<*p2; //e
Операция вычитания
p-k
р1-р2
где p, p1 и p2 – указатели, p1 и p2 одного типа, k – целое число.
Результатом операции р-k является адрес памяти, расположенной на k единиц ближе к начальным адресам памяти, чем память, адресуемая левым операндом.
Пример:
int a[5]={10,20,30,40,50};
int *p1=&a[1];
p1=р1-1;
cout<<*p1; //10
Результатом операции р1-р2 является количество единиц памяти, между двумя адресами. Длина единицы памяти измеряется в операции длиной базового типа указателей.
Пример:
int a[5]={10,20,30,40,50};
int *p1=&a[1];
int *p2=&a[3];
cout<<p2-p1; //2
Операции инкремента и декремента
p++ и p- -
где p указатель.
Операции изменяют значение указателя на единицу памяти (равную емкости базового типа).
Пример:
int a[5]={10,20,30,40,50};
int *p1=&a[1];
int *p2=&a[3];
p1++;
p2--;
cout<<*p1<<’ ‘<<*p2; //30 30
Операция new
В рассмотренных примерах указатели указывали на обычные переменные. Указатели в программах чаще используют для хранения адресов динамических переменных. Динамические переменные, в отличие от обычных переменных, не объявляются, а память под них выделяется во время работы программы. Для создания динамической переменной используется операция new.
Операция new может использоваться следующими способами:
new тип
new тип (начальное значение)
new тип [количество элементов]
В первом способе операция выделяет участок динамической памяти, размер которого определяется емкостью указанного для операции типа. Во втором способе выделенная память инициализируется начальным значением. В третьем способе операция выделяет память под указанное количество элементов. Результатом операции является адрес выделенной памяти.
Примеры:
int *p;
p=new int; //выделение памяти под данное целого типа
*p=5;
int *p1;
p1=new int [10]; //выделение памяти под массив из 10 чисел
//Заполнение выделенной памяти
*p1=10;
*(p1+1)=20;
for (int i=2;i<10; i++)
*(p1+i)=(i-1)*10;
float p2;
p2=new float(0.5); //инициализация выделенной памяти
Память для динамических переменных выделяется из области памяти, которая называется динамической памятью или кучей (heap). Хотя эта область большая, но и она может исчерпаться. Поэтому программист должен заботиться о своевременном освобождении памяти, если необходимость в ней исчезает.
Если память выделить не удается, то создается исключительная ситуация bad_alloc, которая при отсутствии обработчика события завершает программу. Исключительная ситуация – это ошибка выполнения программы, которую можно перехватить, распознать и обработать.
Программист должен заботиться о своевременном освобождении динамической памяти, если необходимость в ней исчезает.
Операция delete
delete p
delete [] p
где р указатель на динамическую память, выделенную операцией new.
Операция освобождает динамическую память (уничтожает динамические переменные). Если память выделялась под массив, то она должна освобождаться операцией delete []. При отсутствии [] будет освобождена память только от первого элемента массива, остальная выделенная под массив память останется помеченной как занятая. Кроме того адрес неосвобожденной памяти будет недоступен программе (поисходит «утечка памяти»).
Примеры:
int *p1;
p1=new int (5); //выделение и инициализация памяти под целое
//Здесь могут быть операторы, использующие р1
delete p1; //память возвращается в кучу для использования
p1=new int[3]; //выделение памяти под 3 целых числа
//здесь могут быть операторы, использующие р1
delete [] p1; //память возвращается в кучу для использования