
- •2. Структура и основные элементы программы
- •3.Общее понятие типов данных
- •4. Переменные и константы
- •5.Основные типы данных
- •6. Спецификаторы типов данных
- •7. Определение переменных и констант в программе
- •8. Инициализация переменных различных типов
- •9.Целочисленные типы данных
- •10. Вещественные типы данных
- •11. Особенности представления вещественных типов данных
- •12.Логический тип данных
- •13. Символьный тип данных
- •14. Управляющие последовательности
- •15. Операции и выражения
- •16. Операция присваивания, составные операции присваивания
- •17. Понятие l-значения
- •18. Преобразование типов данных
- •19. Арифметические операции
- •20. Операции инкремента и декремента, их разновидности
- •21. Операции отношения
- •22. Логические операции
- •23. Побитовые операции сдвига
- •24. Побитовые логические операции
- •25. Примеры применения побитовых операций
- •26. Условная операция и ее использование
- •27. Определение объема памяти, необходимого для размещения объектов
- •28. Понятие приоритета операций и его влияние на результаты вычислений
- •31.Флаги форматирования потоков ввода-вывода
- •32. Форматирование ввода-вывода с помощью манипуляторов
- •33.Форматирование ввода-вывода с помощью функций потоков ввода-вывода
- •34. Управление шириной поля вывода и выравниванием данных при выводе
- •35. Управление форматом вывода вещественных значений
- •36. Основные понятия структурного программирования
- •37. Базовый набор управляющих структур
- •39.Условная инструкция (if)
- •40. Инструкция множественного выбора (switch)
- •42. Цикл с постусловием (do while)
- •43. Итерационный цикл (for)
- •46. Инструкция перехода goto
- •47. Понятие рекуррентных вычислений, примеры
- •48. Понятие инварианта цикла
- •49. Понятие и определение массива
- •52. Ввод элементов массивов с клавиатуры
- •53. Декларативная и программная инициализация массивов
- •54. Копирование массивов
- •55. Нахождение минимальных и максимальных значений в массивах
- •56. Сдвиг элементов массивов
- •57. Перестановка элементов в массивах
- •58. Поиск данных в массивах
- •59. Сортировка данных в массивах
- •60. Вычисление сумм и произведений элементов массивов
- •61. Представление текстовых строк в виде массива символов
- •62. Ввод-вывод символьных строк
- •63. Определение фактической длины строки
- •64. Копирование символьных строк
- •65. Основные функции обработки строк библиотеки cstring
- •66. Массивы текстовых строк (двумерные массивы символов)
- •67. Указатели Понятие указателя
- •Работа с указателями
- •68. Арифметика указателей
- •69. Индексирование указателей
- •70. Ссылки
- •71. Определение функции
- •72. Инструкция return
- •73. Завершение работы функции
- •74. Механизмы передачи данных через параметры функций
- •75. Передача данных по значению
- •76. Передача данных через указатели
- •77. Передача данных по ссылке
- •78. Параметры по умолчанию
- •79. Функции с переменным числом параметров
- •80. Inline функции
- •81. Перегрузка функций
- •82. Рекурсия
- •83. Прототипы функций
Работа с указателями
Присвоить указателю адрес некоторой переменной можно инструкцией присваивания и операцией &, например, так (возьмем предыдущий пример):
int A = 2351, *p1;
double B = 3.14, *p2;
p1 = &A; // Указателю p1 присваивается адрес переменной А
p2 = &B; // Указателю p2 присваивается адрес переменной В
cout << “Значение переменной А: ” << A << endl;
cout << “Адрес переменной А: ” << p1 << endl;
cout << “Значение переменной В: ” << В << endl;
cout << “Адрес переменной В: ” << p2 << endl;
Результат выполнения этого фрагмента программы будет таким же, как и раньше.
Однако, использование самих указателей (т. е. адресов) само по себе представляет сомнительный интерес. Более важно узнать значение того или иного объекта, на который ссылается указатель.
Получить значение объекта, на который ссылается некоторый указатель можно с помощью операции * (эту операцию обычно называют разыменованием указателя):
int A = 2351, *p1;
double B = 3.14, *p2;
p1 = &A; // Указателю p1 присваивается адрес переменной А
p2 = &B; // Указателю p2 присваивается адрес переменной В
cout << “Значение переменной А: ” << *p1 << endl;
cout << “Адрес переменной А: ” << p1 << endl;
cout << “Значение переменной В: ” << *p2 << endl;
cout << “Адрес переменной В: ” << p2 << endl;
Результат выполнения этого фрагмента программы будет таким же, как и в предыдущем примере.
Обращение к указателю с помощью оператора * (например, *p1) означает следующее: взять из памяти по адресу, хранящемуся в указателе (p1 равно 101), столько байт памяти, сколько требуется базовому типу указателя (в данном случае базовый тип указателя int, следовательно – взять 4 байта) и работать с этими байтами, как со значением базового типа указателя (в нашем примере это значение 2351 типа int). Таким образом, *p1 – это (в нашем примере) обычное значение типа int и с ним можно работать как с обычным целым числом.
С помощью указателей можно не только получать значения, расположенные по адресам, хранящимся в указателях, но и записывать нужные значения по этим адресам. Например: выполнение инструкции
*p1 = 4211;
приведет к тому, что переменная A, на которую ссылается указатель p1, станет равна 4211, а не 2351.
Указатели могут использоваться в различных выражениях наравне с обычными переменными и константами:
B = (*p1 – 1000) * 2; // Переменная В станет равна значению (4211 - 1000) * 2 = 6422.0
Или так:
*p2 = (*p1 – 1000) * 2; // Переменная B также будет равна значению 6422.0
Внимание. При использовании указателей в выражениях важно помнить, что операция * имеет наивысший приоритет по отношению к другим операциям (за исключением операции унарный – (минус)).
Значения переменных указателей можно инициализировать при их определении, как обычные переменные:
int A = 5, B = 10, *p1 = &A, *p2 = &B;
double D = 3.14, *d = &D;
Здесь указатели p1 и p2 указывают на переменные A и B соответственно. Указатель d – на переменную D.
Указателю можно присвоить значение другого указателя, если совпадают их базовые типы:
p1 = p2; // Теперь оба указателя p1 и p2 ссылаются на переменную B
d = p1; // Ошибка – базовые типы не совпадают
Последней ошибки можно было бы избежать с помощью явного преобразования типов, например, так:
d = (double *) p1;
Но теперь указатель d будет содержать адрес переменной B типа int, а попытка взять значение по этому адресу через указатель d приведет к тому, что с этого адреса будут взяты не 4, а 8 байт памяти и полученное значение *d типа double будет непредсказуемым. Еще более неприятная ситуация может возникнуть, если попытаться записать данные по указателю d. Такая попытка может завершиться тем, что будут изменены данные, расположенные за переменной B, а это приведет к неправильной работе программы. Обнаружить такие ошибки бывает очень сложно.
Явное преобразование указателей нежелательно. Но если это необходимо, то делать это надо очень аккуратно.
Хотя формально указатели представляют собой целые значения, присваивать им произвольные целые значения нельзя. Например, попытка присвоить указателю p1 значение 10000 (p1 = 10000;) приведет к возникновению ошибки на этапе компиляции программы. Единственным исключением является присвоение указателю нулевого значения:
p1 = 10000; // Ошибка
p1 = 0; // Все правильно
Принято соглашение о том, что нулевое значение указателя означает то, что указатель ни на что не ссылается (пустой указатель, не содержащий никакого адреса).
Указатели можно сравнивать с помощью операций отношения ==, !=, >, <. С помощью таких сравнений можно характеризовать взаимное расположение объектов, на которые ссылаются сравниваемые указатели, в памяти. Но сравнение указателей с помощью операций >, < имеет смысл только в том случае, если сравниваемые указатели содержат адреса связанных между собой переменных (например, элементов массива).