Указатели и ссылки
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 13 из 104.
Теперь, когда вы понимаете, как работает стековая и кучевая память, давайте изучим инструменты, которые позволяют работать с адресами памяти напрямую: указатели и ссылки.
Указатель хранит адрес памяти другой переменной. Вы объявляете его с помощью * и получаете адрес, используя &.
int value = 42;
int* ptr = &value; // ptr хранит адрес value
std::cout << ptr; // Выводит адрес памяти
std::cout << *ptr; // Разыменование: выводит 42Ссылка — это псевдоним для существующей переменной. После привязки она всегда ссылается на эту переменную. Она объявляется с помощью &.
int value = 42;
int& ref = value; // ref — это псевдоним для value
ref = 100; // Изменяет value на 100
std::cout << value; // Выводит 100Ключевые различия:
| Указатели | Ссылки |
|---|---|
| Могут принимать значение null | Должны быть инициализированы |
| Могут быть переназначены | Не могут быть перепривязаны |
Используйте * для доступа к значению | Доступ к значению напрямую |
| Могут ни на что не указывать | Всегда валидны |
Оба механизма важны для эффективной передачи объектов в функции без копирования и для работы с памятью, выделенной в куче. Ссылки безопаснее и проще, в то время как указатели обеспечивают большую гибкость, когда требуется переназначение или проверка на null.
Задание
ЛегкоДавайте создадим простую систему манипулирования значениями, которая демонстрирует, как по-разному работают указатели и ссылки при изменении данных.
Вы создадите два файла для организации вашего кода:
ValueOps.h: Определите три функции, которые изменяют целые значения различными способами:doubleByPointer(int* ptr)— принимает указатель и удваивает значение, на которое он указываетtripleByReference(int& ref)— принимает ссылку и утраивает значениеswapValues(int* a, int* b)— принимает два указателя и меняет местами значения, на которые они указывают
main.cpp: Считайте два целых числа из входных данных, затем продемонстрируйте работу как указателей, так и ссылок:- Выведите исходные значения:
"Original: <first> <second>" - Используйте
doubleByPointerдля первого значения, затем выведите:"After double: <first>" - Используйте
tripleByReferenceдля второго значения, затем выведите:"After triple: <second>" - Используйте
swapValues, чтобы поменять значения местами, затем выведите:"After swap: <first> <second>"
- Выведите исходные значения:
При вызове функций с указателями не забудьте использовать оператор взятия адреса (&), чтобы передать адрес переменной. Ссылки работают более естественно — просто передавайте саму переменную напрямую.
Подключите ваш заголовочный файл в main.cpp с помощью #include "ValueOps.h".
Шпаргалка
Указатель хранит адрес памяти другой переменной. Объявите его с помощью * и получите адрес, используя &:
int value = 42;
int* ptr = &value; // ptr хранит адрес value
std::cout << ptr; // Выводит адрес памяти
std::cout << *ptr; // Разыменование: выводит 42Ссылка — это псевдоним для существующей переменной. Объявите ее с помощью &:
int value = 42;
int& ref = value; // ref — это псевдоним для value
ref = 100; // Изменяет value на 100
std::cout << value; // Выводит 100Ключевые различия:
| Указатели | Ссылки |
|---|---|
| Могут быть нулевыми (null) | Должны быть инициализированы |
| Могут быть переназначены | Не могут быть перепривязаны |
Используйте * для доступа к значению | Доступ к значению напрямую |
| Могут указывать в никуда | Всегда валидны |
При вызове функций с указателями используйте оператор взятия адреса (&), чтобы передать адрес переменной. Ссылки работают напрямую — просто передайте переменную.
Попробуйте сами
#include <iostream>
#include "ValueOps.h"
using namespace std;
int main() {
// Считать два целых числа из входных данных
int first, second;
cin >> first;
cin >> second;
// Вывести исходные значения
cout << "Original: " << first << " " << second << endl;
// TODO: Используйте doubleByPointer для первого значения (передайте адрес с помощью &)
// TODO: Вывести после удвоения
// TODO: Используйте tripleByReference для второго значения (передайте переменную напрямую)
// TODO: Вывести после утроения
// TODO: Используйте swapValues, чтобы поменять значения местами (передайте адреса с помощью &)
// TODO: Вывести после обмена значениями
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнер