
- •Лекция 1 Основы алгоритмизации
- •1.1 Языки программирования
- •1.2 Этапы решения задач на компьютере
- •1.3 Понятие алгоритма и его свойства
- •1.4 Графическое описание алгоритмов. Схемы алгоритмов
- •Блоки для изображения схем алгоритмов и программ
- •1.5 Типы алгоритмов
- •Лекция 2 Начальные сведения о языке
- •2.3 Компиляция и выполнение программы
- •Лекция 3 Имена, переменные и константы
- •3.1 Имена
- •3.2 Переменные
- •3.3 Константы
- •Лекция 4 Операции и выражения
- •4.1 Выражения
- •4.2 Операция присваивания
- •4.3.1 Арифметические операции
- •4.3.2 Операции сравнения
- •4.4 Порядок вычисления выражений
- •Лекция 5 Операторы
- •5.1 Что такое оператор
- •5.1.1 Операторы-выражения
- •5.1.2 Объявления имен
- •5.1.3 Операторы управления
- •5.1.3.1 Условные операторы
- •5.1.3.2 Операторы цикла
- •5.1.3.3 Оператор возврата
- •5.1.3.4 Оператор перехода
- •Лекция 6 Функции
- •6.1 Вызов функций
- •6.2 Имена функций
- •6.3 Необязательные аргументы функций
- •6.4 Рекурсия
- •Лекция 7 Встроенные типы данных
- •7.1 Общая информация
- •7.2 Целые числа
- •7.3 Вещественные числа
- •7.4 Логические величины
- •7.5 Символы и байты
- •7.6 Кодировка, многобайтовые символы
- •7.7 Наборы перечисляемых значений
- •Лекция 8 Классы и объекты
- •8.1 Понятие класса
- •8.2 Определение методов класса
- •8.3 Переопределение операций
- •8.4 Подписи методов и необязательные аргументы
- •8.4.1 Запись классов
- •Лекция 9 Производные типы данных
- •9.1 Массивы
- •9.2 Структуры
- •9.2.1 Битовые поля
- •9.3 Объединения
- •9.4 Указатели
- •9.4.1 Адресная арифметика
- •9.4.2 Связь между массивами и указателями
- •9.4.3 Бестиповый указатель
- •9.4.4 Нулевой указатель
- •9.5 Строки и литералы
- •Лекция 10 Распределение памяти
- •10.1 Автоматические переменные
- •10.2 Статические переменные
- •10.3 Динамическое выделение памяти
- •10.4 Выделение памяти под строки
- •10.5 Рекомендации по использованию указателей и динамического распределения памяти
- •10.6 Ссылки
- •10.6 Распределение памяти при передаче аргументов функции
- •10.6.1 Рекомендации по передаче аргументов
- •Лекция 11 Производные классы, наследование
- •11.1 Виртуальные методы
- •11.1.1 Виртуальные методы и переопределение методов
- •11.2 Преобразование базового и производного классов
- •11.3 Внутреннее и защищенное наследование
- •11.4 Абстрактные классы
- •11.5 Множественное наследование
- •11.5.1 Виртуальное наследование
- •15.2 Проблема использования общих функций и имен
- •15.3 Использование включаемых файлов
- •15.4 Препроцессор
- •15.4.1 Определение макросов
- •Условная компиляция
- •15.4.2 Дополнительные директивы препроцессора
- •Лекция 16 Определение, время жизни и области видимости переменных в больших программах
- •16.1 Файлы и переменные
- •16.1.1 Общие данные
- •16.1.2 Глобальные переменные
- •16.1.3 Повышение надежности обращения к общим данным
- •16.2 Область видимости имен
- •16.3 Оператор определения контекста namespace
- •Лекция 17 Обработка ошибок
- •17.1 Виды ошибок
- •17.2 Возвращаемое значение как признак ошибки
- •17.3 Исключительные ситуации
- •17.3.1 Обработка исключительных ситуаций
- •17.3.2 Примеры обработки исключительных ситуаций
- •Лекция 18 Bвод-вывод
- •18.1 Потоки
- •18.3 Манипуляторы и форматирование ввода-вывода
- •18.4 Строковые потоки
- •18.5 Ввод-вывод файлов
- •Лекция 19 Шаблоны
- •19.1 Назначение шаблонов
- •19.2 Функции-шаблоны
- •19.3 Шаблоны классов
- •19.3.1 "Интеллигентный указатель"
- •19.3.2 Задание свойств класса
- •Список использованных источников
- •Содержание
9.4 Указатели
Указатель – это производный тип, который представляет собой адрес какого-либо значения. В языке C++ используется понятие адреса переменных. Работа с адресами досталась C++ в наследство от языка Си. Предположим, что в программе определена переменная типа int:
int x;
Можно определить переменную типа "указатель" на целое число:
int* xptr;
и присвоить переменной xptr адрес переменной x:
xptr = &x;
Операция &, примененная к переменной, – это операция взятия адреса. Операция *, примененная к адресу, – это операция обращения по адресу. Таким образом, два оператора эквивалентны:
int y = x;
// присвоить переменной y значение x
int y = *xptr;
// присвоить переменной y значение,
// находящееся по адресу xptr
С помощью операции обращения по адресу можно записывать значения:
*xptr = 10;
// записать число 10 по адресу xptr
После выполнения этого оператора значение переменной x станет равным 10, поскольку xptr указывает на переменную x.
Указатель – это не просто адрес, а адрес величины определенного типа. Указатель xptr – адрес целой величины. Определить адреса величин других типов можно следующим образом:
unsigned long* lPtr;
// указатель на целое число без знака
char* cp;
// указатель на байт
Complex* p;
// указатель на объект класса Complex
Если указатель ссылается на объект некоторого класса, то операция обращения к атрибуту класса вместо точки обозначается "->", например p->real. Если вспомнить один из предыдущих примеров:
void
Complex::Add(Complex x)
{
this->real = this->real + x.real;
this->imaginary = this->imaginary +
x.imaginary;
}
то this – это указатель на текущий объект, т.е. объект, который выполняет метод Add. Запись this-> означает обращение к атрибуту текущего объекта.
Можно определить указатель на любой тип, в том числе на функцию или метод класса. Если имеется несколько функций одного и того же типа:
int foo(long x);
int bar(long x);
можно определить переменную типа указатель на функцию и вызывать эти функции не напрямую, а косвенно, через указатель:
int (*functptr)(long x);
functptr = &foo;
(*functptr)(2);
functptr = &bar;
(*functptr)(4);
Для чего нужны указатели? Указатели появились, прежде всего, для нужд системного программирования. Поскольку язык Си предназначался для "низкоуровневого" программирования, на нем нужно было обращаться, например, к регистрам устройств. У этих регистров вполне определенные адреса, т.е. необходимо было прочитать или записать значение по определенному адресу. Благодаря механизму указателей, такие операции не требуют никаких дополнительных средств языка.
int* hardwareRegiste =0x80000;
*hardwareRegiste =12;
Однако использование указателей нуждами системного программирования не ограничивается. Указатели позволяют существенно упростить и ускорить ряд операций. Предположим, в программе имеется область памяти для хранения промежуточных результатов вычислений. Эту область памяти используют разные модули программы. Вместо того, чтобы каждый раз при обращении к модулю копировать эту область памяти, мы можем передавать указатель в качестве аргумента вызова функции, тем самым упрощая и ускоряя вычисления.
struct TempResults {
double x1;
double x2;
} tempArea;
// Функция calc возвращает истину, если
// вычисления были успешны, и ложь – при
// наличии ошибки. Вычисленные результаты
// записываются на место аргументов по
// адресу, переданному в указателе trPtr
bool
calc(TempResults* trPtr)
{
// вычисления
if (noerrors) {
trPtr->x1 = res1;
trPtr->x2 = res2;
return true;
} else {
return false;
}
}
void
fun1(void)
{
. . .
TempResults tr;
tr.x1 = 3.4;
tr.x2 = 5.4;
if (calc(&tr) == false) {
// обработка ошибки
}
. . .
}
В приведенном примере проиллюстрированы сразу две возможности использования указателей: передача адреса общей памяти и возможность функции иметь более одного значения в качестве результата. Структура TempResults используется для хранения данных. Вместо того чтобы передавать эти данные по отдельности, в функцию calc передается указатель на структуру. Таким образом достигаются две цели: большая наглядность и большая эффективность (не надо копировать элементы структуры по одному). Функция calc возвращает булево значение – признак успешного завершения вычислений. Сами же результаты вычислений записываются в структуру, указатель на которую передан в качестве аргумента.
Упомянутые примеры использования указателей никак не связаны с объектно-ориентированным программированием. Казалось бы, объектно-ориентированное программирование должно уменьшить зависимость от низкоуровневых конструкций типа указателей. На самом деле программирование с классами нисколько не уменьшило потребность в указателях, и даже наоборот, нашло им дополнительное применение, о чем мы будем рассказывать по ходу изложения.