- •Конспект лекций Часть 2 Оглавление
- •Часть 2 1
- •8. Указатели
- •8.1. Указатели Понятие указателя
- •Работа с указателями
- •Арифметика указателей
- •Ошибки при работе с указателями
- •If (p1) // Если указатель не равен 0, то все в порядке
- •8.2. Указатели и массивы
- •9. Функции и структура программы
- •9.1. Создание и использование функций Процедурный подход к разработке программ
- •Int Максимум_строки (int Строка)
- •Int Минимум_строки (int Строка)
- •Int Максимум_столбца (int Столбец)
- •Int Минимум_столбца (int Столбец)
- •Определение функций в программе
- •Завершение работы функции (инструкция return)
- •If ( Ошибка )
- •Список параметров функций
- •Обращение к функциям в программе
- •Передача данных по значению
- •Передача данных с помощью указателей
- •Передача данных по ссылке
- •Перегружаемые функции
- •Параметры по умолчанию
- •Функции с переменным числом параметров
- •Рекурсивное использование функций
- •Передача функций в качестве параметров
- •Встраиваемые функции (inline - функции)
- •Прототипы функций
- •9.2. Структура программы. Глобальные и локальные данные (области видимости и время жизни) Структура программы
- •Глобальные и локальные данные
- •Классы памяти
- •Многофайловые проекты
- •10. Структуры, объединения, перечисления
- •10.1. Структуры Определение структур
- •Доступ к полям структур
- •Указатели на структуры
- •Структурные параметры функций
- •Битовые поля структур
- •10.2. Объединения Обычные объединения
- •Анонимные объединения
- •10.3. Перечисления
- •Void WriteColor (тип_Спектр c )
- •11. Организация работы с файлами
- •11.1. Потоки для работы с файлами Общие сведения
- •Пример работы с файлом
- •11.2. Работа с файлами Создание потока, открытие и закрытие файла
- •Запись и чтение данных в текстовых файлах
- •Запись и чтение данных в двоичном режиме
- •If ( !File ) // Проверили удалось ли открыть файл
- •Как обнаружить конец файла?
- •Прямой доступ при работе с файлами
- •If ( !File ) // Проверили удалось ли открыть файл
- •Статус потоков ввода-вывода
- •Некоторые другие функции управления потоками ввода-вывода
- •Примеры по работе с файлами
- •12. Работа с динамической памятью Распределение памяти при работе программы
- •Динамическое выделение и освобождение памяти в стиле c
- •Возможные ошибки при работе с динамической памятью
- •Динамические массивы
- •Одномерные однонаправленные списки
- •Одномерные двунаправленные списки
- •Многомерные списки
Передача данных по ссылке
Передача данных с использованием указателей несколько трудоемка, поскольку требует при вызове функций передавать адрес аргумента, а внутри функции разыменовывать параметры-указатели. Кроме того, неправильное выполнение этих дополнительных действий может привести к появлению ошибок.
В языке C++ имеется более простой способ передачи данных по адресу, а именно –передача данных по ссылке.
Переделаем пример предыдущего параграфа так, чтобы данные в нем передавались не через указатель, а по ссылке:
int Div (int N1, int N2, int &Ost) // int &Ost – параметр-ссылка
{
Ost = N1 % N2; // Разыменования параметра-ссылкиOstне требуется
return N1 / N2;
}
int main()
{
int I = 10, J = 3, R, O;
R = Div (I, J, O); // Используется сам аргумент О, а не его адрес
cout << I << “ / ” << J << “ = ” << R << “. Остаток равен “ << O << endl;
return 0;
}
Из этого примера видно, что для определения параметра, с помощью которого функция может вернуть остаток от деления, используется символ &, а не*. Именно так определяются параметры-ссылки. Внутри процедуры, для получения доступа к значению данных, разыменовывать параметр-ссылку не нужно. При вызове процедуры используется сам аргумент, а не его адрес. Таким образом, использование передачи данных по ссылке значительно проще, чем передача данных через указатели.
На самом деле в этом способе передача данных осуществляется точно так же, как и при использовании указателей, только операции взятия адреса аргумента и разыменования параметра-ссылки осуществляется компилятором автоматически, скрытно от нас.
Способ передачи данных по ссылке также можно отнести к передаче данных по адресу, и он полностью эквивалентен по своему эффекту передачи данных через указатели.
В целом, передача данных по адресу (с помощью указателей или по ссылке), является более эффективной, чем передача данных по значению. Это объясняется тем, что при передаче данных по значению осуществляется создание копии аргумента функции, а на это тратится и память и время. При передаче по адресу затраты памяти и времени существенно меньше, так как в функцию передается только адрес данных, а не сами данные, объем которых часто существенно превышает размер адреса.
Возникает вопрос: почему всегда не использовать передачу данных по адресу, поскольку это более эффективно? Недостатком передачи данных по адресу является скрытый побочный эффект, связанный с возможным непредвиденным изменением внутри функции значения аргумента переданного по адресу. Однако этого эффекта можно избежать, если определить соответствующий параметр функции как константу:
void Proc(const double *D)
{
……
*D = 3.14; // Ошибка в процессе компиляции
……
}
Перегружаемые функции
Поскольку при вызове функций типы данных подставляемых аргументов и их количество должны соответствовать типам данных и количеству параметров функций, в языке Cприходилось использовать множество функций с различными именами для выполнения одних и тех же действий над различными типами данных. Например, для вычисления абсолютного значения некоторого числа в библиотеках языкаCимелось несколько различных функций:abs,labs,fabs,fadsf. Использование этих функций определялось типами данных обрабатываемых чисел. Но все они выполняли одно и то же действие – вычисляли абсолютное значение аргумента. Это доставляло определенные неудобства.
В языке C++ появилось понятие перегруженных (перегружаемых) функций, которое позволило избавиться от этого неудобства.
Перегруженными функциями называются функции, имеющие одинаковые имена, но различающиеся количеством, типами данных или порядком следования разнотипных параметров. Например:
void f (char c)
{
…….
}
void f (int c)
{
…….
}
int f (char c, int i)
{
…….
}
void f (int c, char i)
{
…….
}
void f (char c, char i)
{
…….
}
При вызове таких функций компилятор сам определяет, какую из этих функций необходимо использовать применительно к использованному при вызове списку аргументов. Чтобы у компилятора не возникало “сомнений” по поводу выбора подходящего варианта перегруженной функции, списки параметров перегруженных функций должны однозначно различаться.
Нельзя перегружать функции, различающиеся только типами данных возвращаемых значений. Например, компилятор “не разрешит” перегрузить следующую функцию:
int f (char c, char i)
{
…….
}
Это объясняется тем, что функции можно вызывать без использования возвращаемого функцией значения. При таком вызове функций с совпадающим списком параметров компилятор не сможет определить, какую из функций использовать.