第12回 static変数

前回の記事で作成したプログラムを、よりわかりやすく改良します。

第10回から第12回までの内容は、少し難しくなっています。
すべて理解できなくても、第13回以降の記事がわからなくなる、ということはありません。理解できるところまでチャレンジしてみましょう!
もし難しく感じたら、とりあえず第12回の記事まではスキップして、第13回の記事からお読みいただければと思います。

目次

前回のスケッチの問題点?

前回までの記事で、「スイッチを押したら、LEDがピカっと1回だけ光るスケッチ」を作成しました。

きちんと動作しますので、文句のつけようがないスケッチです。

あとは、「スイッチを押したら、文字データを1回だけPCに送るスケッチ」を作成すれば、オリジナルのキーボードができそうです。


でも…

ちょっと不思議に思われるかもしれませんが、このきちんと動作するスケッチに、まだちょっと問題点が残っているんです。

その問題点とは、今後ご自身で規模が大きいスケッチを作るようになると、目立ってきます。

今回の記事で習得する内容は、他の人が書いたスケッチでも見かけることが多いので、ぜひ一緒にその問題点と解決策を習得していきましょう!

スケッチの問題点とは?

前回のスケッチをもう一度確認してみましょう!

変数zenkaiに注目してみると、変数宣言の位置と変数を使用する位置が離れています

結果として、次のような分かりづらさがあります。

  • zenkaiをグローバル変数にしているので、変数の宣言はプログラムの最初の方にあり、パッとみただけだとこの変数がスケッチのどこで使われているのかわかりづらい
  • loop関数だけ見ると、関数内で変数zenkaiが使われているものの、loop関数内にこの変数の宣言がない(変数型がすぐにわからない、などの問題がある)
  • 変数zenkailoop関数だけで使用するものなのに、グローバル変数にしているので他の関係ない関数(setup関数)からも利用できてしまう

これらの問題点は、スケッチの規模が大きくなってくると目立つようになってきます。


これらの問題点の本質は、「変数の宣言」と「変数が使われるところ」が離れているという点にあります。

この問題点を解決するには、実際に変数が使われる関数内で宣言するしかありません。

ところが、変数zenkailoop関数内で宣言すると、loop関数の最後の}で変数は捨てられてしまいます。

ということは、loop関数内で、変数zenkaiを宣言しても捨てられない変数宣言」ができれば問題解決って感じですよね。

実は、C++言語ではこのような目的のための便利な仕組みが用意されているんです。

関数内で変数を宣言した場合でも、その関数の処理が終わっても(つまりその関数の最後の}にきても)、その変数を捨てられないようにしてもらえる仕組みです。(なんだかややこし表現ですみません…)

その仕組みを具体的に見ていきましょう!

static変数

変数を関数内で宣言すると、その変数の寿命はその関数が終わるまででした。(ローカル変数の特徴ですね)

ところが関数内で変数を宣言するときに、変数宣言の先頭にstaticを付けると、その変数は関数の処理が終わっても捨てずにそのままにされます。

この「static」は変数宣言を修飾していることから、「修飾子」と呼ばれることもあります。(語学の文法でも「修飾詞」という用語が出てきますよね。「子」と「詞」の違いがありますが…)

「static」は日本語で「静的」という意味ですが、ちょっとわかりづらいですよね。

staticをつけて宣言した変数は、関数の処理が終わってもずっとそのままじっと静かに存在している、ということで「静的」というようなイメージですね。

そのため、static変数は「静的変数」と呼ばれることもあります。(グローバル変数も「静的変数」です!)

一方、ローカル変数は関数内で用意され、関数が終わると捨てられるので「動的変数」と呼ぶこともあります)

static変数を使ってみる

それでは、実際に前回のスケッチを変更してみましょう。

変数zenkaiの宣言をグローバル変数ではなく、loop関数内に移します。

この変数は関数の処理が終わってもそのままにして欲しいので、次のように先頭にstaticをつければOKです。

static uint8_t zenkai;

それでは、static変数に変更したloop関数を確認してみましょう。2行目で変数zenkaistaticとして宣言しています。

void loop() {
  static uint8_t zenkai;  // 前回のスイッチ状態を記録する変数
  uint8_t konkai;  // 今回のスイッチ状態を記録する変数
  
  // 今回のスイッチ状態を読み取る
  konkai = digitalRead(SWITCH_HIDARI);

  // スイッチ状態が、前回OFF、今回ONの時、スイッチが押されたと判断する
  if( (zenkai == OFF) && (konkai == ON) ) {
    digitalWrite(LED_BLUE, HIGH);  // LEDを点灯
    delay(TENTOU_JIKAN);           // 一定時間待つ
    digitalWrite(LED_BLUE, LOW);   // LEDを消す
  }

  // 今回の値を前回の値として保存
  zenkai = konkai;
  
}

これでプログラムの変更は終わりました。


ところで、細かい話になりますが、ちょっと気になるところがありませんか?

Arduino Microの電源が入っている間、loop関数は何度も何度も繰り返し実行されます

ここで、変数konkaizenkaiを比較してみます。

変数konkaiは、普通の変数宣言ですので、loop関数の処理が始まるとuint8_t konkai;で変数が用意され、loop関数の最後の}でその変数は捨てられます。Arduino Microは、loop関数を処理するたびに、変数を用意して、それを捨てる、ということをしています。

一方で、変数zenkaiは、一度用意されたらloop関数の最後の}のところで捨てられることはなく、ずっと存在し続けます。でもloop関数は何度も何度も繰り返されるので、何度も何度もstatic uint8_t zenkai;という変数が宣言されてしまいますよね。何度も変数宣言しても大丈夫なのか?とちょっと不安になりませんか?

でも安心してください!Arduino Microはstaticの意味をちゃんとわかっていますので、static uint8_t zenkai;により変数zenkaiを一度用意したら、再度static uint8_t zenkai;に出会っても用意した変数zenkaiをずっと使い続けます。


static変数をうまく使うとスケッチが読みやすくなるので、ぜひ活用してみてください!

static変数の初期値

ところで、勘の鋭い方でしたら気付かれたかもしれません。

普通の変数の宣言をした場合は、適当な数字が書かれた変数が用意される、と説明しました。

今回変更したスケッチでは、static uint8_t zenkai;とただ宣言したので、適当な数字が用意されてしまうのではないか、という心配があります。


実は、static変数宣言の場合、0が代入された変数が用意されます。(グローバル変数と同じ扱いですね)

上のスケッチ例では、0が代入されたstatic変数zenkaiが用意されることになります。


ところで、今回使用する変数zenkaiはスイッチはOFFから始まりますので、初期値としては「1」が代入された変数を用意して欲しいところです。

このようにstatic変数を宣言するときに、初期値を指定したい場合は次のように=で初期値を代入すればOKです。

static uint8_t zenkai = 1;

loop関数の中にこのように書いても、この変数宣言が実行されるのは1回だけです。

loop関数の処理のたびに毎回1が代入されることはありませんので安心してください。

staticのその他の働き

今回の記事では、変数宣言の修飾子として使用する「static」を説明しました。

これから他の人が作ったプログラムを見る機会もあると思いますが、この「static」は関数定義にも使用されることがあります

関数の前に「static」をつけるわけですが、その時の「static」は今回説明したstatic変数とは別の意味を持ちますので注意してください。(関数定義に「static」をつけた場合の働きについては基礎編の範囲を超えますので説明は省略します…すみません…)

static変数を使用したスイッチ検知スケッチ

前回のスケッチを、static変数を使用して次のように変更してみました。

動作は変わりませんが、できれば動作確認してみてください。

/*
 *  スイッチ制御プログラム static変数版
 *    スイッチを押すとピカッとLEDを光らせる
 */

// 青色LEDのピン接続番号
#define LED_BLUE  12

// 左側スイッチのピン接続番号
#define SWITCH_HIDARI  A5

// スイッチの状態
#define OFF 1
#define ON  0

// 点灯時間(単位:ms)
#define TENTOU_JIKAN 50


void setup() {
  pinMode(LED_BLUE, OUTPUT);             // LEDのピンを出力に設定
  pinMode(SWITCH_HIDARI, INPUT_PULLUP);  // スイッチのピンを入力(プルアップ)に設定

  digitalWrite(LED_BLUE, LOW);  // 最初はLEDをOFFに設定しておく
}


void loop() {
  static uint8_t zenkai = OFF;  // 前回のスイッチ状態を記録する変数
  uint8_t konkai;  // 今回のスイッチ状態を記録する変数
  
  // 今回のスイッチ状態を読み取る
  konkai = digitalRead(SWITCH_HIDARI);

  // スイッチ状態が、前回OFF、今回ONの時、スイッチが押されたと判断する
  if( (zenkai == OFF) && (konkai == ON) ) {
    digitalWrite(LED_BLUE, HIGH);  // LEDを点灯
    delay(TENTOU_JIKAN);           // 一定時間待つ
    digitalWrite(LED_BLUE, LOW);   // LEDを消す
  }

  // 今回の値を前回の値として保存
  zenkai = konkai;
  
}

更新履歴

日付内容
2021.9.12新規投稿
2025.2.10説明内容簡略化
Arduino IDE2対応
通知の設定
通知タイミング
guest
0 コメント
新しい準
古い順 一番投票が多い
本文中にフィードバック
全てのコメントを見る
目次