cpp豆知識 - nearfactory/2024-TOINIOT2 GitHub Wiki
C言語の入門書などではポインタの初期化にNULLが使用されていることが多いですが、この方法にはいくつかの問題点があります。
まず、NULL自体について、多くのコンパイラでは下記のように0として定義されていたりします。
#define NULL 0
定義自体には特に問題はありませんが、ポインタ演算に用いるとなると話は別です。
試しに下記のコードを実行してみると結果はどうなるでしょうか?
#include<stdio.h>
void func(int val){
printf("int\n");
}
void func(void* val){
printf("void*\n");
}
int main(){
func(10);
func(NULL);
return 0;
}
int
int
当たり前ですよね。コンパイラからするとNULLはただの0でしかないんですから。
このままポインタの初期化にNULLを使用していると、初期化したはずのヌルポインタにも値を書き込めてしまい、
意図せずメモリが破壊されてしまうなどの解析困難なバグの原因となります。
そこで、C++11から追加されたnullptr
を用いる方法が推奨されます。
NULLをnullptrへ修正して再度さっきのコードを実行してみます。
#include<stdio.h>
void func(int val){
printf("int\n");
}
void func(void* val){
printf("void*\n");
}
int main(){
func(10);
func(nullptr);
return 0;
}
int
void*
見事nullptrをポインタとして認識してくれました。
このように、nullptrはNULLと違って正しくヌルポインタとして認識されるため、
NULLの代わりとして積極的に使っていきましょう!
int型配列 A[]
の全要素を吐き出す場合、下記のような書き方が一般的である。
for( int i = 0; i < A.length(); i++){
int x = A[ i ];
Serial.print( x );
}//
A.length();
と記述することで、
// 配列A
の要素数を取得できるものとする。
このコードは正しく動作するが、配列の中身を吐き出す度に書いていては面倒である。
そこで、C++11で追加された 範囲for文 を使用することで簡潔に記述できる。
(ちなみに、配列の全要素を探索することを「配列をなめる」と言うことがある)
範囲for文は、
for( {1.型名} <2.変数名> : <3.配列> )
と記述することで使用でき、
内部的には2.の変数へ配列から値を代入するのを繰り返して動作している。
各部分の詳細
- 2.の型
- 要素の代入先の変数名
- 参照する配列
・最初の吐き出す例
for( auto x : A ){
Serial.print( x );
}
・標準入力による入力例
int A[10];
for( auto& x : A){
std::cin >> x;
}
autoキーワードは、コンパイラによって初期化子から型を自動的に決定するのに使われる。
(今回の例だと A[]
がint型配列であるから x
はint型であると推論される。)
auto&
における&
は、xがAへの参照(別名)となることを意味している。(≒ポインタのイメージ)
参照を使うとx
の変更がA
にも影響し、直接A
の値を変更することができるようになるため、入力を受け取る場合などに使用することができる。
つまり、auto&
はauto
による型推論に加えて、その変数が参照であることを示しているといえる。
また、これらの型は範囲for文に限らず、一般的な変数にも使用できる。
AtCoder C++ 範囲for文
https://zenn.dev/mkymdk/articles/ee27f06d2d9a46
型エイリアスとは、型に別の名前をつけ宣言することである。
これを使用することで、コードの記述量が減る、可読性が高くなる、
型の変更が用意になるなどのメリットがある。
C言語ではtypedef
キーワードを用いて、
typedef {OldType} {NewType};
のように書くことで型エイリアスを作成できる。
例えば、下記のように記述すると、型unsigned int
を、u_int
と記述することで使用できるようになる。
typedef unsigned int u_int
では、次のような型の場合はどうだろうか
typedef static const unsigned int scu_int
typedef unsigned short int (*func)(int, int, const char*, ...);
1つめの場合、複雑ではあるものの、scu_int
が新しい型名であることはわかる。
だが、2つめに関してはどの部分が新しい型名なのかですら直感的にわかりにくく、コードの可読性が一気に落ちてしまう。
そこで、C++にて追加されたusing
キーワードを使い、型エイリアスを作成するのがよい。
using
キーワードは、
using {NewType} = {OldType};
と記述することで使用できる。
これを使用して先ほどのコードを書き直すと、
using scu_int = static const unsigned int;
using func = unsigned short int (*)(int, int, const char*, ...);
となり、段違いに直感的で読みやすいコードを書くことができる。
このため、現在ではtypedef
は推奨されておらず、代わりとしてusing
を使用するのが好ましい。
結論 : using使おう!