Stałe funkcje składowe
Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 29 z 104.
W poprzedniej lekcji widziałeś słowo kluczowe const przy metodach pobierających. Ale co dokładnie oznacza, gdy const pojawia się po liście parametrów funkcji? Stała funkcja składowa gwarantuje, że nie zmodyfikuje żadnych zmiennych składowych obiektu.
class Rectangle {
int width;
int height;
public:
Rectangle(int w, int h) : width(w), height(h) {}
int getArea() const { // funkcja składowa const
return width * height; // Odczytywanie pól jest dozwolone
}
void setWidth(int w) { // Nie-const — modyfikuje obiekt
width = w;
}
};const po liście parametrów informuje kompilator: „Ta funkcja nie zmieni stanu obiektu”. Jeśli spróbujesz zmodyfikować zmienną składową wewnątrz funkcji const, kompilator zgłosi błąd.
Staje się to niezbędne podczas pracy z obiektami const lub referencjami const. Obiekt const może wywoływać tylko funkcje składowe const:
void printArea(const Rectangle& rect) {
std::cout << rect.getArea(); // OK - getArea() jest const
// rect.setWidth(10); // ERROR - setWidth() nie jest const
}Oznaczanie funkcji jako const, gdy nie modyfikują obiektu, jest dobrą praktyką. Dokumentuje Twoje intencje, umożliwia działanie funkcji na obiektach const i pomaga kompilatorowi wykrywać przypadkowe modyfikacje. Każda funkcja składowa, która tylko odczytuje dane, powinna być oznaczona jako const.
Wyzwanie
ŁatwyZbudujmy konwerter temperatur, który pokaże, kiedy i dlaczego używać metod składowych oznaczonych jako const. Utworzysz klasę, której niektóre metody tylko odczytują dane (i powinny być oznaczone jako const), a inne modyfikują stan obiektu.
Utworzysz dwa pliki, aby uporządkować kod:
Temperature.h: Zdefiniuj klasęTemperature, która przechowuje wartość temperatury i udostępnia różne sposoby jej odczytu i modyfikowania. Twoja klasa powinna zawierać:- Prywatne pole
celsius(double) do przechowywania temperatury - Konstruktor przyjmujący początkową wartość w stopniach Celsjusza
- Metodę
getCelsius(), która zwraca przechowywaną wartość. Powinna być oznaczona jako const, ponieważ tylko odczytuje dane - Metodę
getFahrenheit(), która oblicza i zwraca temperaturę w stopniach Fahrenheita, używając wzorucelsius * 9.0 / 5.0 + 32.0; ona również powinna być oznaczona jako const, ponieważ niczego nie modyfikuje - Metodę
getKelvin(), która zwraca temperaturę w kelwinach, używająccelsius + 273.15; ona także powinna być oznaczona jako const - Metodę
setCelsius(double value), która aktualizuje przechowywaną temperaturę. Nie może być oznaczona jako const, ponieważ modyfikuje obiekt - Metodę
adjustBy(double delta), która dodaje wartość delta do bieżącej temperatury; ona również nie powinna być oznaczona jako const
- Prywatne pole
main.cpp: Pokaż, jak działają metody składowe oznaczone jako const zarówno ze zwykłymi, jak i stałymi obiektami. Odczytaj początkową wartość temperatury z wejścia, a następnie:- Utwórz obiekt
Temperaturez podaną wartością - Wypisz
"Initial: <celsius>C = <fahrenheit>F = <kelvin>K" - Zwiększ temperaturę o
10.0stopni - Wypisz
"After adjustment: <celsius>C" - Utwórz funkcję pomocniczą
void printReadings(const Temperature& temp), która przyjmuje stałą referencję i wypisuje"Reading: <celsius>C, <fahrenheit>F". Ta funkcja może wywoływać dla temp tylko metody oznaczone jako const - Wywołaj
printReadings()dla swojego obiektu temperatury - Ustaw temperaturę na
0.0(punkt zamarzania) - Wypisz
"Freezing point: <celsius>C = <fahrenheit>F"
- Utwórz obiekt
Formatuj wszystkie wartości temperatury do jednego miejsca po przecinku, używając std::fixed i std::setprecision(1) z <iomanip>.
Najważniejsze jest to, że printReadings() otrzymuje stałą referencję, więc może wywoływać tylko metody oznaczone jako const. Dlatego ważne jest prawidłowe oznaczenie metod pobierających dane jako const. Dzięki temu mogą działać w kontekstach, w których obiektu nie można modyfikować.
Spróbuj swoich sił
#include <iostream>
#include <iomanip>
#include "Temperature.h"
using namespace std;
// TODO: Zaimplementuj tutaj metody klasy Temperature
// Konstruktor
Temperature::Temperature(double initialCelsius) {
// TODO: Zainicjalizuj celsius
}
// TODO: Zaimplementuj getCelsius() jako const
// TODO: Zaimplementuj getFahrenheit() jako const
// TODO: Zaimplementuj getKelvin() jako const
// TODO: Zaimplementuj setCelsius(double value)
// TODO: Zaimplementuj adjustBy(double delta)
// TODO: Utwórz funkcję pomocniczą printReadings, która przyjmuje const Temperature&
// i wypisuje "Reading: <celsius>C, <fahrenheit>F"
// Uwaga: Ta funkcja może wywoływać na temp tylko metody const!
int main() {
double initialTemp;
cin >> initialTemp;
// Ustaw formatowanie wyjścia
cout << fixed << setprecision(1);
// TODO: Utwórz obiekt Temperature z wartością wejściową
// TODO: Wypisz "Initial: <celsius>C = <fahrenheit>F = <kelvin>K"
// TODO: Zmień temperaturę o 10.0 stopni
// TODO: Wypisz "After adjustment: <celsius>C"
// TODO: Wywołaj printReadings() z utworzonym obiektem Temperature
// TODO: Ustaw temperaturę na 0.0 (punkt zamarzania)
// TODO: Wypisz "Freezing point: <celsius>C = <fahrenheit>F"
return 0;
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneBudowanie i kompilacja C++Pliki nagłówkowe i pliki źródłowePrzestrzenie nazw i zakresWprowadzenie do programowania obiektowego w C++Klasy a obiektyWskaźnik „this”Metody (funkcje składowe)Atrybuty (składowe danych)Podstawy konstruktorów i destruktorówPowtórzenie – prosty kalkulator4Właściwości klas
Elementy instancji a elementy statyczneGettery i setteryStałe funkcje składoweSłowo kluczowe mutableMetody i zmienne statyczneFunkcje i klasy zaprzyjaźnionePodsumowanie – menedżer konta bankowego7Dziedziczenie
Podstawy dziedziczeniaPoziomy dostępu w dziedziczeniuKolejność wywołań konstruktora i destruktoraNadpisywanie metodFunkcje wirtualne i VTableWielokrotne dziedziczenieDziedziczenie wirtualnePowtórzenie — hierarchia pracowników2Zarządzanie pamięcią
Pamięć stosu a stertyWskaźniki i referencjePamięć dynamiczna (new/delete)Inteligentne wskaźniki w C++RAII w C++Podsumowanie — menedżer tablicy dynamicznej5Hermetyzacja
Specyfikatory dostępu w C++Specyfikatory dostępu — szczegółowoUkrywanie informacjiStruktura a klasaKlasy zagnieżdżone i wewnętrznePodsumowanie — system ewidencji studentów8Polimorfizm
Polimorfizm czasu kompilacji i wykonaniaPrzeciążanie funkcjiFunkcje wirtualne — powtórkaCzysto wirtualne funkcjeKlasy abstrakcyjneProjektowanie interfejsów w C++Rzutowanie dynamiczne i RTTIPodsumowanie — kalkulator kształtów3Konstruktory i destruktory
Konstruktor domyślnyKonstruktor z parametramiKonstruktor kopiującyKonstruktor przenoszącyListy inicjalizacyjne konstruktoraKonstruktory delegująceDestruktor — szczegółowe omówienieZasada trzech / pięciu / zeraPodsumowanie — klasa String6Przeciążanie operatorów
Wprowadzenie do przeciążania operatorówPrzeciążanie operatorów arytmetycznychPrzeciążanie operatorów porównaniaOperatory strumieniowePrzeciążanie operatora przypisaniaPrzeciążanie operatorów [] i ()Operatory konwersji typówPodsumowanie — klasa Matrix9Szablony
Szablony funkcjiSzablony klasSpecjalizacja szablonówSzablony wariadycznePodstawy SFINAE i cech typówPodsumowanie — kontener generycznyPoćwicz samodzielnie: Kompilator C++ online