挨拶
前回に続き、今回も新・標準プログラマーズライブラリ C言語 ポインタ完全制覇 を教本に学習を進めて行きます。今回は const修飾子とtypedefについて学習していきます。
const修飾子について
まずは、const修飾子について学習していきます。
const修飾子とは?
教本には以下のように記載されています。
constは、ANSIで追加された型修飾子であり、型を修飾して"読み出し専用"であることを意味します。
どういうことか、実験しながら確認していきましょう。
constで変数を修飾したコード例
#include <stdio.h>
int main(void){
/*const修飾子*/
const int x = 10; //値の変更が不可な変数"x"を宣言
printf("x..%d\n",x); //xの値を出力
x = 20; //変更できないためコンパイルエラーになる
コード内のコメントにもあるように、constで宣言した変数の値は不変となり変更できないようになり、以下のようなメッセージが出てしまいます。
エラーメッセージ:式は変更可能な左辺値である必要があります
ただし、constはあくまで"読み出し専用"にするだけなので、値を処理に使うことはできる。
#include <stdio.h>
int main(void){
/*const修飾子*/
const int x = 10; //値の変更が不可な変数"x"を宣言
printf("x..%d\n",x); //xの値を出力
int y = x + 10; // xの値を使った新しい変数"y"を作成。constは"読み出し専用"なだけなので中身の値は使える
printf("y..%d\n",y); //結果:y..20
return 0;
}
ここで言う"読み出し専用"とは、変数(上記の場合は " x " が該当)の値を変えられないことを指す。値を変えられないだけなので変数の値を読み取って使うことは可能。
constを使用したポインタ変数を引数に持つ関数の宣言
関数の引数にconstをつける理由
調べた結果、以下のような利点を確認できました。
- データの安全性
- 可読性と意図の明確化
- より柔軟な引数
特にデータの安全性の観点では、引数にconstを使用することで、ポインタの指す先の値を誤って変更することを防げるという点は、かなり重要なポイントだと感じます。
コード例
以下は関数の引数にconstを付けた例です。
void test_src(char *dest,const char *src){ //引数にconstを付けた関数test_srcを宣言
src = NULL; //constで修飾したポインタ型変数"src"に値を再代入するが、問題なく実行・コンパイルされる
//*src = 'a'; //この場合は"変数srcの指す先(値そのもの)を変えることになるためコンパイルエラーになる" エラーメッセージ:assignment of read-only location ‘*src’
}
//変数そのものを"読み出し専用"にする場合
void test_src2(char *dest, char * const src){
//src = NULL; //変数そのものが読み出し専用なので、再代入ができずコンパイルエラーになる エラーメッセージ:assignment of read-only parameter ‘src’
*src = 'a'; //新しい値をポインタ変数が指す先の変更は可能(再代入は不可)
}
//変数と変数に格納された値の両方をconstにしたい場合
void test_src3(const char * const src);
コメントにあるように、constを使うことで、ポインタ変数への再代入を防止したり、ポインタ変数の指す先が変更されることを防止することができます。
typedef
typedefは、すでにある型や構造体(struct)に別名を付ける機能のことです。
コード例
まずは、基本的な宣言をしたコード例を見てみます。
/*typedef*/
typedef char String; //typedefを使ってchar型にStringという別名をつける
String X[6] = "hello"; //Stringの別名を使って配列Xに終端文字(\0)を含む文字列を格納
printf("x..%s\n",X); //結果x..helloと出力される
なぜ別名を付けるのか
私自身が初心者なので、実際はどのように使われるのかのコードは出せないのですが、
調べたところ、以下のようなメリットがあるとわかりました。
・構造体や関数にtypedefを使ってわかりやすい別名を付けることで、その用途を明確にできる
使い方は
typedef struct {
int x;
int y;
}Point; //Pointという名前の構造体を定義
Point p1 = {10,20}; //Point型の変数p1を宣言し、xに10、yに20を代入
printf("p1..x:%d y:%d\n",p1.x,p1.y); //結果:p1..x:10 y:20と出力される
このように、構造体に別名を付けることができます。更に具体的には、
typedef struct {
int id;
char name[50];
float salary;
}Employee; //Employeeという名前の構造体を定義
Employee emp1 = {1, "Alice", 50000.0}; //Employee型の変数emp1を宣言し、各メンバに初期値を代入
printf("Employee..id:%d name:%s salary:%.2f\n",emp1.id,emp1.name,emp1.salary); //結果:Employee..id:1 name:Alice salary:50000.00と出力される
このように定義することで、その構造体(struct)が何のためのデータを持つためにあるかを明示的に表すことができます。
まとめ
- const
- constを変数に付けることで、"読み出し専用"にして意図しないデータの改変を防ぐことができる
- ポインタ関数やポインタ変数に使用する際は、"変数が指す先(値)を読み出し専用にするのか"、"変数自体を読み出し専用にするのか"の点に注意が必要
- typedef
- データ型や構造体に別名を付けることができる
- 別名を付けることで、"何をするためのデータなのか"を判別しやすくなる
用語説明
記事内の用語についての解説です。
補足説明
再代入と変更
再代入と変更は紛らわしく厄介です。これらを箱とその中身に例えて説明すると
- 再代入は変数(データの入った箱)に別のデータを入れること
- 変更は変数(データの入った箱)の中身(データ自体)を変えること
をそれぞれ指しています。
null文字(終端文字)
null文字(終端文字)は文字列(char型の配列)の最後に付けられ、文字列の終わりを表す特別な文字(\0)。文字列を扱う関数(printf関数など)を使用する場合、null文字がその文字列の終わりを表していることに注意が必要です。例えば、
char X[6] = "hello";
この例でのポイントは、配列の大きさが6であることです。"hello"は5文字ですが、文字列ではnull文字(\0)が配列の最後に入るため、配列の大きさも6文字分必要となります。
その他の用語
・ポインタ変数:int *pと表記し、変数が格納されているメモリアドレスを保持
・ポインタの指す先:変数が保持しているメモリアドレスの中のデータそのもの