Funzioni membro const
Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 29 di 104.
Hai visto la parola chiave const nei metodi getter nella lezione precedente. Ma cosa significa esattamente quando const compare dopo l’elenco dei parametri di una funzione? Una funzione membro const promette di non modificare alcuna variabile membro dell’oggetto.
class Rectangle {
int width;
int height;
public:
Rectangle(int w, int h) : width(w), height(h) {}
int getArea() const { // funzione membro const
return width * height; // È consentito leggere i membri
}
void setWidth(int w) { // Non const - modifica l'oggetto
width = w;
}
};Il const dopo l'elenco dei parametri comunica al compilatore: "Questa funzione non modificherà lo stato dell'oggetto." Se provi a modificare una variabile membro all'interno di una funzione const, il compilatore genererà un errore.
Questo diventa essenziale quando si lavora con oggetti const o riferimenti const. Un oggetto const può chiamare solo funzioni membro const:
void printArea(const Rectangle& rect) {
std::cout << rect.getArea(); // OK - getArea() è const
// rect.setWidth(10); // ERROR - setWidth() non è const
}Contrassegnare come const le funzioni che non modificano l'oggetto è una buona pratica. Documenta le tue intenzioni, consente alla funzione di lavorare con oggetti const e aiuta il compilatore a rilevare modifiche accidentali. Qualsiasi funzione membro che si limita a leggere i dati dovrebbe essere contrassegnata come const.
Sfida
FacileCostruiamo un convertitore di temperature che mostra quando e perché usare le funzioni membro const. Creerai una classe in cui alcuni metodi leggono soltanto i dati (e dovrebbero essere const), mentre altri modificano lo stato dell'oggetto.
Creerai due file per organizzare il codice:
Temperature.h: definisci una classeTemperatureche memorizza un valore di temperatura e offre diversi modi per leggerlo e modificarlo. La classe dovrebbe avere:- Un membro privato
celsius(double) per memorizzare la temperatura - Un costruttore che accetta un valore iniziale in Celsius
- Un metodo
getCelsius()che restituisce il valore memorizzato. Dovrebbe essere const, dato che legge soltanto i dati - Un metodo
getFahrenheit()che calcola e restituisce la temperatura in Fahrenheit usando la formulacelsius * 9.0 / 5.0 + 32.0, anch'esso const perché non modifica nulla - Un metodo
getKelvin()che restituisce la temperatura in Kelvin usandocelsius + 273.15: anche questo const - Un metodo
setCelsius(double value)che aggiorna la temperatura memorizzata. Non può essere const perché modifica l'oggetto - Un metodo
adjustBy(double delta)che aggiunge il delta alla temperatura corrente, anch'esso non const
- Un membro privato
main.cpp: mostra come funzionano le funzioni membro const sia con oggetti normali sia con oggetti const. Leggi un valore iniziale della temperatura dall'input, poi:- Crea un oggetto
Temperaturecon il valore di input - Stampa
"Initial: <celsius>C = <fahrenheit>F = <kelvin>K" - Regola la temperatura di
10.0gradi - Stampa
"After adjustment: <celsius>C" - Crea una funzione di supporto
void printReadings(const Temperature& temp)che accetta un riferimento const e stampa"Reading: <celsius>C, <fahrenheit>F". Questa funzione può chiamare soltanto metodi const su temp - Chiama
printReadings()passando il tuo oggetto temperatura - Imposta la temperatura a
0.0(punto di congelamento) - Stampa
"Freezing point: <celsius>C = <fahrenheit>F"
- Crea un oggetto
Formatta tutti i valori di temperatura con una cifra decimale usando std::fixed e std::setprecision(1) da <iomanip>.
L'idea fondamentale è che printReadings() riceve un riferimento const, quindi può chiamare soltanto metodi contrassegnati come const. Ecco perché è importante contrassegnare correttamente come const i metodi getter. In questo modo possono essere usati in contesti in cui l'oggetto non può essere modificato.
Provalo tu
#include <iostream>
#include <iomanip>
#include "Temperature.h"
using namespace std;
// TODO: Implementa qui i metodi della classe Temperature
// Costruttore
Temperature::Temperature(double initialCelsius) {
// TODO: Inizializza celsius
}
// TODO: Implementa getCelsius() come const
// TODO: Implementa getFahrenheit() come const
// TODO: Implementa getKelvin() come const
// TODO: Implementa setCelsius(double value)
// TODO: Implementa adjustBy(double delta)
// TODO: Crea una funzione di supporto printReadings che accetti un const Temperature&
// e stampi "Reading: <celsius>C, <fahrenheit>F"
// Nota: questa funzione può chiamare solo metodi const su temp!
int main() {
double initialTemp;
cin >> initialTemp;
// Imposta la formattazione dell'output
cout << fixed << setprecision(1);
// TODO: Crea un oggetto Temperature con il valore di input
// TODO: Stampa "Initial: <celsius>C = <fahrenheit>F = <kelvin>K"
// TODO: Regola la temperatura di 10.0 gradi
// TODO: Stampa "After adjustment: <celsius>C"
// TODO: Chiama printReadings() con il tuo oggetto Temperature
// TODO: Imposta la temperatura a 0.0 (punto di congelamento)
// TODO: Stampa "Freezing point: <celsius>C = <fahrenheit>F"
return 0;
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti della programmazione orientata agli oggetti
File esterniBuild e compilazione in C++File header e file sorgenteNamespace e ambitoIntroduzione alla programmazione orientata agli oggetti in C++Classi e oggetti a confrontoIl puntatore 'this'Metodi (funzioni membro)Attributi (membri dati)Fondamenti di costruttori e distruttoriRiepilogo - Calcolatrice semplice4Proprietà delle classi
Membri di istanza e staticiGetter e setterFunzioni membro constParola chiave mutableMetodi e variabili staticiFunzioni e classi friendRiepilogo - Gestore di conti bancari7Ereditarietà
Ereditarietà di baseLivelli di accesso nell’ereditarietàOrdine di chiamata di costruttori e distruttoriRidefinizione dei metodiFunzioni virtuali e VTableEreditarietà multiplaEreditarietà virtualeRiepilogo - Gerarchia dei dipendenti2Gestione della memoria
Memoria Stack vs HeapPuntatori e riferimentiMemoria dinamica (new/delete)Puntatori intelligenti in C++RAII in C++Riepilogo - Gestore di array dinamico5Incapsulamento
Specificatori di accesso in C++Specificatori di accesso in dettaglioOccultamento delle informazioniStruct vs classClassi annidate e interneRiepilogo - Sistema di registrazione degli studenti8Polimorfismo
Polimorfismo a compile time e a runtimeOverload delle funzioniFunzioni virtuali: ripassoFunzioni virtuali pureClassi astratteProgettazione delle interfacce in C++Dynamic casting e RTTIRipasso: calcolatrice di forme11Concetti avanzati di OOP
Composizione vs ereditarietàMixin tramite CRTPIdiom PimplType ErasureEnum class e tipizzazione forteGestione delle eccezioni in OOPGerarchie personalizzate di eccezioni14Pattern di progettazione - Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeRAII come pattern3Costruttori e distruttori
Costruttore predefinitoCostruttore con parametriCostruttore di copiaCostruttore di spostamentoListe di inizializzazione dei costruttoriCostruttori delegantiApprofondimento sui distruttoriRegola del Tre / Cinque / ZeroRipasso - classe String6Sovraccarico degli operatori
Introduzione al sovraccarico degli operatoriSovraccarico degli operatori aritmeticiSovraccarico degli operatori di confrontoOperatori di flussoSovraccarico dell'operatore di assegnazioneSovraccarico degli operatori [] e ()Operatori di conversione di tipoRipasso - Classe Matrix9Template
Template di funzioneTemplate di classeSpecializzazione dei templateTemplate variadiciBasi di SFINAE e dei trait di tipoRiepilogo - Contenitore genericoEsercitati da solo: Compilatore C++ online