Константные функции-члены
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 29 из 104.
Вы видели ключевое слово const в методах-геттерах в предыдущем уроке. Но что именно это означает, когда const появляется после списка параметров функции? Константная функция-член обещает не изменять никакие переменные-члены объекта.
class Rectangle {
int width;
int height;
public:
Rectangle(int w, int h) : width(w), height(h) {}
int getArea() const { // константная функция-член
return width * height; // Чтение членов разрешено
}
void setWidth(int w) { // Неконстантная — изменяет объект
width = w;
}
};Ключевое слово const после списка параметров сообщает компилятору: «Эта функция не изменит состояние объекта». Если вы попытаетесь изменить переменную-член внутри константной функции, компилятор выдаст ошибку.
Это становится необходимым при работе с объектами const или ссылками const. Объект const может вызывать только функции-члены const:
void printArea(const Rectangle& rect) {
std::cout << rect.getArea(); // OK - getArea() является константным
// rect.setWidth(10); // ОШИБКА - setWidth() не является константным
}Пометка функций как const, когда они не изменяют объект, является хорошей практикой. Это документирует ваши намерения, позволяет функции работать с константными объектами и помогает компилятору отлавливать случайные изменения. Любая функция-член, которая только считывает данные, должна быть помечена как const.
Задание
ЛегкоДавайте создадим конвертер температуры, который продемонстрирует, когда и почему следует использовать константные функции-члены (const member functions). Вы создадите класс, в котором одни методы только считывают данные (и должны быть const), а другие изменяют состояние объекта.
Для организации кода вы создадите два файла:
Temperature.h: Определите классTemperature, который хранит значение температуры и предоставляет различные способы его чтения и изменения. Ваш класс должен содержать:- Приватный член
celsius(double) для хранения температуры - Конструктор, принимающий начальное значение в градусах Цельсия
- Метод
getCelsius(), который возвращает сохраненное значение — он должен быть const, так как только считывает данные - Метод
getFahrenheit(), который вычисляет и возвращает температуру в Фаренгейтах по формулеcelsius * 9.0 / 5.0 + 32.0— также const, так как он ничего не изменяет - Метод
getKelvin(), который возвращает температуру в Кельвинах по формулеcelsius + 273.15— тоже const - Метод
setCelsius(double value), который обновляет сохраненную температуру — он не может быть const, так как изменяет объект - Метод
adjustBy(double delta), который добавляет дельту к текущей температуре — также не является константным
- Приватный член
main.cpp: Продемонстрируйте, как константные функции-члены работают как с обычными, так и с константными объектами. Считайте начальное значение температуры из входных данных, затем:- Создайте объект
Temperatureс введенным значением - Выведите
"Initial: <celsius>C = <fahrenheit>F = <kelvin>K" - Измените температуру на
10.0градусов - Выведите
"After adjustment: <celsius>C" - Создайте вспомогательную функцию
void printReadings(const Temperature& temp), которая принимает константную ссылку и выводит"Reading: <celsius>C, <fahrenheit>F"— эта функция может вызывать только константные методы у объекта temp - Вызовите
printReadings()со своим объектом температуры - Установите температуру на
0.0(точка замерзания) - Выведите
"Freezing point: <celsius>C = <fahrenheit>F"
- Создайте объект
Форматируйте все значения температуры с одним десятичным знаком, используя std::fixed и std::setprecision(1) из библиотеки <iomanip>.
Ключевой момент здесь заключается в том, что printReadings() получает константную ссылку, поэтому она может вызывать только те методы, которые помечены как const. Вот почему важно правильно помечать методы-геттеры как const — это позволяет им работать в контекстах, где объект не может быть изменен.
Шпаргалка
Константная функция-член гарантирует, что она не будет изменять никакие переменные-члены объекта. Ключевое слово const ставится после списка параметров функции:
class Rectangle {
int width;
int height;
public:
int getArea() const { // константная функция-член
return width * height; // Чтение членов разрешено
}
void setWidth(int w) { // Неконстантная — изменяет объект
width = w;
}
};Константные функции-члены могут только читать переменные-члены, но не изменять их. Если вы попытаетесь изменить переменную-член внутри константной функции, компилятор выдаст ошибку.
Константный объект или константная ссылка могут вызывать только константные функции-члены:
void printArea(const Rectangle& rect) {
std::cout << rect.getArea(); // OK — getArea() является константной
// rect.setWidth(10); // ОШИБКА — setWidth() не является константной
}Помечайте функции как const, если они не изменяют объект. Это документирует ваши намерения, позволяет функции работать с константными объектами и помогает компилятору отлавливать случайные изменения. Любая функция-член, которая только считывает данные, должна быть помечена как const.
Попробуйте сами
#include <iostream>
#include <iomanip>
#include "Temperature.h"
using namespace std;
// TODO: Реализуйте методы класса Temperature здесь
// Конструктор
Temperature::Temperature(double initialCelsius) {
// TODO: Инициализируйте celsius
}
// TODO: Реализуйте getCelsius() как const
// TODO: Реализуйте getFahrenheit() как const
// TODO: Реализуйте getKelvin() как const
// TODO: Реализуйте setCelsius(double value)
// TODO: Реализуйте adjustBy(double delta)
// TODO: Создайте вспомогательную функцию printReadings, которая принимает const Temperature&
// и выводит "Reading: <celsius>C, <fahrenheit>F"
// Note: Эта функция может вызывать только const методы для temp!
int main() {
double initialTemp;
cin >> initialTemp;
// Настройка форматирования вывода
cout << fixed << setprecision(1);
// TODO: Создайте объект Temperature с входным значением
// TODO: Выведите "Initial: <celsius>C = <fahrenheit>F = <kelvin>K"
// TODO: Измените температуру на 10.0 градусов
// TODO: Выведите "After adjustment: <celsius>C"
// TODO: Вызовите printReadings() с вашим объектом температуры
// TODO: Установите температуру на 0.0 (точка замерзания)
// TODO: Выведите "Freezing point: <celsius>C = <fahrenheit>F"
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Итоги — Обобщенный контейнер