cpp豆知識 - nearfactory/2024-TOINIOT2 GitHub Wiki

cpp豆知識 #03 ヌルポインタ

C言語の入門書などではポインタの初期化にNULLが使用されていることが多いですが、この方法にはいくつかの問題点があります。
まず、NULL自体について、多くのコンパイラでは下記のように0として定義されていたりします。

#define NULL 0

定義自体には特に問題はありませんが、ポインタ演算に用いるとなると話は別です。
試しに下記のコードを実行してみると結果はどうなるでしょうか?

test.cpp

#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;
}

output

int
int

当たり前ですよね。コンパイラからするとNULLはただの0でしかないんですから。

このままポインタの初期化にNULLを使用していると、初期化したはずのヌルポインタにも値を書き込めてしまい、
意図せずメモリが破壊されてしまうなどの解析困難なバグの原因となります。

そこで、C++11から追加されたnullptrを用いる方法が推奨されます。
NULLをnullptrへ修正して再度さっきのコードを実行してみます。

test.cpp

#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;
}

output

int
void*

見事nullptrをポインタとして認識してくれました。
このように、nullptrはNULLと違って正しくヌルポインタとして認識されるため、

NULLの代わりとして積極的に使っていきましょう!


cpp豆知識 #02 範囲for文

配列を吐き出す

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文は、
for( {1.型名} <2.変数名> : <3.配列> )
と記述することで使用でき、
内部的には2.の変数へ配列から値を代入するのを繰り返して動作している。

各部分の詳細

  1. 2.の型
  2. 要素の代入先の変数名
  3. 参照する配列
     

・最初の吐き出す例

for( auto x : A ){
  Serial.print( x );
}

・標準入力による入力例

int A[10];
for( auto& x : A){
  std::cin >> x;
}

見慣れないautoauto& について

auto(型推論)とは

autoキーワードは、コンパイラによって初期化子から型を自動的に決定するのに使われる。
(今回の例だと A[] がint型配列であるから x はint型であると推論される。)

auto&とは

auto&における&は、xがAへの参照(別名)となることを意味している。(≒ポインタのイメージ)
参照を使うとxの変更がAにも影響し、直接Aの値を変更することができるようになるため、入力を受け取る場合などに使用することができる。
つまり、auto&autoによる型推論に加えて、その変数が参照であることを示しているといえる。

また、これらの型は範囲for文に限らず、一般的な変数にも使用できる。

参考文献

AtCoder C++ 範囲for文 https://zenn.dev/mkymdk/articles/ee27f06d2d9a46  
 
 
 
 
 
 
 

cpp豆知識 #01 型エイリアス

型エイリアスとは、型に別の名前をつけ宣言することである。
これを使用することで、コードの記述量が減る、可読性が高くなる、
型の変更が用意になるなどのメリットがある。

古い使い方

C言語ではtypedefキーワードを用いて、

typedef {OldType} {NewType};

のように書くことで型エイリアスを作成できる。
例えば、下記のように記述すると、型unsigned intを、u_intと記述することで使用できるようになる。

typedef unsigned int u_int

では、次のような型の場合はどうだろうか

  1. typedef static const unsigned int scu_int
  2. typedef unsigned short int (*func)(int, int, const char*, ...);

1つめの場合、複雑ではあるものの、scu_intが新しい型名であることはわかる。
だが、2つめに関してはどの部分が新しい型名なのかですら直感的にわかりにくく、コードの可読性が一気に落ちてしまう。

 

新しい使い方

そこで、C++にて追加されたusingキーワードを使い、型エイリアスを作成するのがよい。
usingキーワードは、

using {NewType} = {OldType};

と記述することで使用できる。
これを使用して先ほどのコードを書き直すと、

  1. using scu_int = static const unsigned int;
  2. using func = unsigned short int (*)(int, int, const char*, ...);

となり、段違いに直感的で読みやすいコードを書くことができる。
このため、現在ではtypedefは推奨されておらず、代わりとしてusingを使用するのが好ましい。

結論 : using使おう!

⚠️ **GitHub.com Fallback** ⚠️