Const の妥当性
CoddyのCジャーニー「Object Oriented Programming」セクションの一部 — レッスン 8/61。
メソッドとして機能する関数を記述する場合、オブジェクトを変更すべきもの(player_take_damage のようなもの)もあれば、何も変更せずにデータを読み取るだけのものもあります。const キーワードを使用すると、コンパイラレベルでこの区別を強制できます。
ポインタパラメータを const としてマークすることで、その関数が指し示しているデータを変更しないことを約束することになります。誤ってメンバを変更しようとすると、コンパイラがエラーを生成します:
typedef struct {
int x;
int y;
} Point;
// この関数はPointを読み取ることしかできません
void point_print(const Point *self) {
printf("(%d, %d)\n", self->x, self->y);
// self->x = 10; // エラー: constポインタ経由で変更することはできません
}
// この関数はPointを変更できます
void point_move(Point *self, int dx, int dy) {
self->x += dx;
self->y += dy;
}
const Point *self 引数は、コンパイラと他のプログラマの両方に対して、point_print が「ゲッター」または読み取り専用の操作であることを伝えます。これは const correctness(constの正しさ)と呼ばれ、変更が必要ない場所ではどこでも const を使用することを指します。
この手法はバグを早期に発見します。表示用の関数を記述し、誤ってフィールドを変更してしまった場合、コンパイラが即座にエラーを出し、プログラムにバグが紛れ込むのを防ぎます。
チャレンジ
簡単温度の値を保持し、読み取り専用操作と変更操作の両方を提供するTemperatureモジュールを作成しましょう。これにより、constポインタを使用して、データを読み取るだけの関数とデータを変更する関数を明確に区別する練習ができます。
3つのファイルを作成します:
temperature.h:double celsiusという単一のメンバを持つTemperature構造体を宣言します。また、以下の3つの関数を宣言します:temp_display— Temperatureへのconstポインタを受け取り、現在の温度を表示します(読み取り専用)temp_adjust— Temperatureへの通常のポインタとdouble型の量を受け取り、celsiusにその量を加算します(変更用)temp_to_fahrenheit— Temperatureへのconstポインタを受け取り、華氏に変換した値を返します(読み取り専用)
TEMPERATURE_Hというシンボルでインクルードガードを使用してください。temperature.c: 3つの関数すべてを実装します。temp_display関数は、"Current: %.1fC\n"という形式で出力する必要があります。temp_to_fahrenheit関数は、celsius * 9.0 / 5.0 + 32.0を返す必要があります。constポインタを使用する関数は、構造体のデータを変更できないことに注意してください。main.c: Temperatureを作成し、操作を実行して、結果を表示します。
2つの入力を受け取ります:初期の摂氏(celsius)の値(double型)と、調整する量(double型)です。
mainファイルでの手順:
- 初期の摂氏の値で
Temperatureを初期化します temp_displayを呼び出して、開始時の温度を表示しますtemp_adjustを呼び出して、調整量だけ温度を変更します- 再度
temp_displayを呼び出して、更新された温度を表示します temp_to_fahrenheitを使用して、華氏への変換結果を出力します
結果を以下の形式で出力してください:
Current: {celsius}C
Current: {celsius}C
Fahrenheit: {fahrenheit}Fすべての温度値には、小数点以下1桁を表示するために%.1fを使用してください。
例えば、初期値が20.0で調整量が5.0の場合、出力は以下のようになります:
Current: 20.0C
Current: 25.0C
Fahrenheit: 77.0Fチートシート
const キーワードをポインタ引数に使用することで、関数が指し示されているデータを変更しないことを示すことができます。これは const 正当性 (const correctness) と呼ばれます。
ポインタ引数が const としてマークされている場合、コンパイラはそのポインタを介したデータの変更を禁止します。
typedef struct {
int x;
int y;
} Point;
// 読み取り専用関数 - ポイントを変更できません
void point_print(const Point *self) {
printf("(%d, %d)\n", self->x, self->y);
// self->x = 10; // エラー: const ポインタを介して変更することはできません
}
// 変更用関数 - ポイントを変更できます
void point_move(Point *self, int dx, int dy) {
self->x += dx;
self->y += dy;
}
const Point *self を使用することで、その関数が読み取り専用の操作であることをコンパイラと他のプログラマの両方に伝えることができます。これはバグの早期発見に役立ちます。何も変更すべきではない関数内で誤ってデータを変更しようとすると、コンパイラが即座にエラーを出力します。
ベストプラクティス: 関数がデータを変更する必要がない場合は、常にポインタ引数に const を使用してください。これにより、コードの意図がより明確になり、誤った変更を防ぐことができます。
自分で試してみよう
#include <stdio.h>
#include "temperature.h"
int main() {
double initial_celsius, adjustment;
scanf("%lf", &initial_celsius);
scanf("%lf", &adjustment);
// TODO: Temperature 構造体を作成し、initial_celsius で初期化する
// TODO: temp_display を呼び出して、開始時の温度を表示する
// TODO: temp_adjust を呼び出して、adjustment の量だけ温度を調整する
// TODO: 再度 temp_display を呼び出して、更新された温度を表示する
// TODO: temp_to_fahrenheit を使用して、華氏に変換した値を出力する
// Format: "Fahrenheit: %.1fF\n"
return 0;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。