2. 変数、型、スコープ

ローカル変数、整数範囲、boolとスコープを学びます。

Wave Foundation

値を名前で扱う

価格を複数の場所に直接書くと、価格を変更するときにすべての場所を見つける必要があります。変数は、値に名前を付け、その名前を介して読み取りおよび変更するストレージスペースです。この章では、宣言、代入、型の範囲、ブロック内で名前が見える範囲を学びます。

以下のプログラムはそれぞれ別々のmain.wave全体です。 1つの例を保存してから、wavec run main.waveとして実行し、次の例に置き換えます。

宣言と初期化

fun main() {
    var price: i32 = 1200;
    var quantity: i32 = 3;
    println("price={}", price);
    println("quantity={}", quantity);
    println("total={}", price * quantity);
}

実行結果:

price=1200
quantity=3
total=3600

宣言を4つの部分に分けて読みます。

部分 この例 役割
宣言キーワード var ローカル変数の生成
名前 price 以降に使用する識別子
タイプ i32 保存する値の種類と範囲
初期値 1200 初めて保存する値

タイプの前のコロンと初期値の前の等号は役割が異なります。名前に意味を込めてください。この例では、priceは単価で、quantityはカウントです。同じ i32でも入れ替えて使うと文法エラーなしで誤った計算をすることができます。

代入は関係を維持する式ではありません

変数に計算結果を保存すると、その時点の値が入ります。計算式を覚えて、後で自動的に再評価しません。

fun main() {
    var price: i32 = 1200;
    var quantity: i32 = 3;
    var total: i32 = price * quantity;
    quantity = 5;
    println("before recalculation={}", total);
    total = price * quantity;
    println("after recalculation={}", total);
}

実行結果:

before recalculation=3600
after recalculation=6000

quantity = 5は、すでに存在する変数に新しい値を書き込みます。 var quantityのように再宣言することと区別する必要があります。 totalも再代入する前は3600です。複数の変数間の関係をプログラムが維持する必要がある場合は、関係が変わったときに計算を実行するように作成する必要があります。

前の値で次の値を計算する

代入文の右側を最初に計算し、その結果を左の記憶領域に書き込みます。数学の式とは異なり、count = count + 1は有効な更新です。

fun main() {
    var count: i32 = 0;
    count = count + 1;
    count += 2;
    count *= 3;
    println("{}", count);
}

実行結果:

9

値の進行は0→1→3→9です。 +=、*=は計算と保存を一緒に表します。計算順序に沿って書き留めてみると、結果が予想と異なる場合、どの段階で考えが異なるかがわかります。

整数型の幅と符号

iで始まるとsigned、uで始まるとunsigned整数です。後の数字はビット数です。ビット数が増えると、表現できる範囲が増え、記憶スペースも増えます。

タイプ 最低値 最もコメント 使用例
i8 -128 127 小さい符号付き値
u8 0 255 1バイト
i16 -32768 32767 小さな整数データ
u16 0 65535 ポート・16ビットフィールド
i32 -2147483648 2147483647 一般的な小さな整数の計算
u32 0 4294967295 32ビットビットフィールド

Wave は、64、128、256、512、および 1024 ビットの符号付きおよび符号なし整数も提供します。幅の広い型を使用しても、すべての計算が安全になるわけではありません。結果は選択した範囲を超える可能性があります。最初に必要な範囲を選択します。 isz と usz はターゲットアドレス幅に従います。

大きなリテラルを小さなタイプに格納することと意図的にcastしてビットを捨てることは区別する必要があります。単にエラーを取り除くために小さな型に変換すると、値自体が変わることがあります。変換は次の章で説明します。

浮動小数点型とブール値

f32・f64は浮動小数点数です。整数とは異なり、小数部を表すことができますが、すべての10進数を正確に保存することはできません。金額を小さな整数単位で管理する理由の1つです。

boolは真と偽を示します。次のように比較結果を保存すると、条件に名前を付けることができます。

fun main() {
    var balance: i32 = 5000;
    var cost: i32 = 3600;
    var can_buy: bool = balance >= cost;
    if (can_buy) {
        println("purchase allowed");
    } else {
        println("not enough money");
    }
}

実行結果:

purchase allowed

can_buyがbalanceとcostの変化に自動的に従うわけでもありません。両方の値が変更された後に現在の状態が必要な場合は、比較を再実行します。

初期化されていないストレージスペース

var value: i32;は、記憶領域のみを宣言する形です。読み取り前に有効な値を書き込む必要があります。宣言したと自動的にゼロが入ると仮定しないでください。入門プロセスでは、値がすぐにわかれば、宣言と同時に初期化するほうがわかりやすくなります。

出力引数として値を受け取るライブラリ呼び出しでは、まずスペースを宣言してから成功したときに読み取る場合もあります。その場合は、関数の成功結果を確認する必要があります。失敗した呼び出しの後に初期化されていない出力値を読み取る間違いを避けてください。

ブロックと名前の有効範囲

ブロックは中かっこで囲まれたコード領域です。内側で同じ名前の変数を新しく宣言すると、そのブロック内では新しい変数が使用されます。これをshadowingといいます。

fun main() {
    var value: i32 = 10;

    if (true) {
        var value: i32 = value + 5;
        println("inner={}", value);
        value += 1;
        println("inner changed={}", value);
    }

    println("outer={}", value);
}

実行結果:

inner=15
inner changed=16
outer=10

内側宣言の初期式value + 5は、外valueを読みます。新しい変数の初期化が終わった後、内側valueは15です。内側の値を 16 に置き換えても、外部記憶領域は変わりません。ブロックが終わると、外側valueが表示されます。

逆に、内側のブロックでvarなしでvalue += 1のみを実行すると、表示される既存の変数が変更されます。新しい宣言であるか既存の値が変更されたかをキーワードで表示し、区切ります。

ローカル変数とトップレベルリポジトリ

関数外では、constとstaticを使用できます。 constは定数値を表し、staticは実行中に保持される記憶領域です。ローカル変数と同じ方法でどこでも宣言できるわけではありません。

const LIMIT: i32 = 3;
static visits: i32 = 0;

fun visit() {
    visits += 1;
    println("visit={}", visits);
}

fun main() {
    visit();
    visit();
    println("limit={}", LIMIT);
}

実行結果:

visit=1
visit=2
limit=3

visitを2回呼び出してもstaticは呼び出しごとに新しくゼロになりません。一方、関数内でvar visits: i32 = 0;と宣言すると、呼び出すたびにローカルストレージスペースが初期化されます。共有される変更可能な状態は動作を追跡しにくくすることができるので、関数の入力・出力で解決できるかまず考えます。

よくある間違い

  • 宣言と代入を混同して同じ名前を不必要に再宣言する場合。
  • 計算結果を保存した変数が入力変数の変更に自動的に従うと思う場合。
  • タイプが同じだから個数とバイト数同じ単位も同じだと思う場合。
  • 初期化なしで読み取った場合、または関数が失敗して出力引数を読み取った場合。
  • ローカル変数の名前がブロック外でも見えると思う場合。

エラーのある名前を見つけるときは、その名前の宣言位置と中括弧の範囲を一緒に確認してください。

練習:在庫変更の計算

初期在庫は20個、3個ずつ2回販売します。残りの在庫と販売した総数を印刷します。在庫を変更するたびに同じ変数を更新し、販売量も別々に累積します。

解答例

fun main() {
    var stock: i32 = 20;
    var sold: i32 = 0;
    var order: i32 = 3;
    stock -= order;
    sold += order;
    stock -= order;
    sold += order;
    println("stock={} sold={}", stock, sold);
}

実行結果:

stock=14 sold=6

在庫と販売量は一緒に変更する必要があります。どちらか一方のみを更新すると、値間の関係が壊れます。繰り返される注文処理は、関数と繰り返し文を学びながらまとめてみましょう。

整数と浮動小数点タイプ

整数型は次のとおりです。

  • 符号付き: i8, i16, i32, i64, i128, i256, i512, i1024
  • 符号なし: u8, u16, u32, u64, u128, u256, u512, u1024
  • アドレスサイズ整数:isz、usz
  • 浮動小数点:f32、f64

iszはアドレスサイズに合った符号付き整数タイプで、uszはアドレスサイズに合った符号なし整数タイプです。

その他内蔵タイプ

タイプ 用途
bool trueまたはfalse
char 符号なし8ビット文字値。任意のUnicodeコードポイントタイプではありません
byte 8ビットバイト値
str NULで終わる文字列バイト列
ptr<T> Tを対象とするポインタ
array<T, N> 要素タイプT、長さNの固定長配列

カスタム構造体と列挙型、型エイリアスも型の場所に使用できます。

varは、ローカル変数を宣言する文法です。型エイリアスは、同じ型をコードのコンテキストに合った名前で表現する文法です。