コンテンツにスキップ

C++/データ型と変数

出典: フリー教科書『ウィキブックス(Wikibooks)』
< C++

データ型の概念

[編集]

データ型とは

[編集]

データ型(data type)とは、プログラミング言語においてデータを表現する方法を定義するものです。データ型は、データがどのようなフォーマットで格納され、どのような操作が可能かを決定します。例えば、整数型は整数値を表現するためのデータ型であり、浮動小数点数型は実数値を表現するためのデータ型です。

データ型の重要性

[編集]

データ型は、プログラミングにおいて非常に重要な概念です。適切なデータ型を選択することで、プログラムのメモリ使用量を最適化し、予期しない動作を防ぐことができます。また、データ型を適切に使用することで、コードの可読性と保守性が向上します。

データ型を適切に使用しないと、様々な問題が発生する可能性があります。たとえば、整数型に実数値を代入しようとすると、値が切り捨てられてしまいます。また、メモリ上の値を意図しない型で解釈すると、予期しないデータにアクセスしてしまう可能性があります。

C++におけるデータ型の分類

[編集]

C++には、さまざまなデータ型が用意されています。大まかに分類すると、以下のようになります。

  1. 基本データ型
    • 整数型 (int, short, long, longlong)
    • 浮動小数点数型 (float, double, longdouble)
    • 文字型 (char)
    • ブール型 (bool)
    • ビットフィールド型
  2. 複合データ型
    • 配列型
    • ポインタ型
    • ユーザー定義型 (構造体、共用体、列挙型、クラス)
  3. void型
    • 値を持たない型
  4. その他
    • 型修飾子 (signed, unsigned, const, volatile)
    • リテラル型

プログラマは、データの性質に応じて適切なデータ型を選択する必要があります。次の節から、各データ型の詳細について説明していきます。

基本データ型

[編集]

C++には、さまざまな基本データ型があり、それぞれに異なる用途と特性があります。以下では、基本データ型について詳しく説明します。

整数型

[編集]

整数型には、符号あり整数型と符号なし整数型があります。整数型は、整数を扱うためのデータ型であり、メモリ使用量や数値範囲が異なる複数の種類が存在します。

符号あり整数型 (int, short, long, long long)

[編集]

符号あり整数型は、正の数値と負の数値の両方を表現できます。以下の型が存在します:

signed char
最小幅は8ビット
short int
最小幅は16ビット
int
最小幅は16ビット
long int
最小幅は32ビット
long long int
最小幅は64ビット

各型の幅は、少なくとも前の型と同じかそれ以上の記憶容量を持ちます。符号あり整数型の範囲は、-2^(N-1)から2^(N-1)-1までです。

符号なし整数型 (unsigned int, unsigned short, unsigned long)

[編集]

符号なし整数型は、正の数値のみを表現します。以下の型が存在します:

  • unsigned char
  • unsigned short int
  • unsigned int
  • unsigned long int
  • unsigned long long int

符号なし整数型の範囲は、0から2^N-1までです。符号なし整数型は符号あり整数型と同じ幅を持ちますが、負の数値を表現しません。

浮動小数点数型

[編集]

浮動小数点数型は、実数を表現するためのデータ型です。以下の型が存在します:

float型

[編集]
float
単精度浮動小数点数。通常32ビットで表現されます。

double型

[編集]
double
倍精度浮動小数点数。通常64ビットで表現されます。

文字型

[編集]

文字型は、文字を表現するためのデータ型です。

char型

[編集]
char
一般的には1バイト(8ビット)で表現されます。signed charunsigned charのどちらかとして実装されます。

ブール型

[編集]

ブール型は、真理値(真または偽)を表現するためのデータ型です。

bool型

[編集]
bool
真理値を表現するための型。true(真)またはfalse(偽)の値を取ります。

データ型の範囲と精度

[編集]

各データ型の範囲と精度は、以下のようになります:

符号あり整数型
最小幅は8ビットから64ビット。範囲は-2^(N-1)から2^(N-1)-1。
符号なし整数型
最小幅は8ビットから64ビット。範囲は0から2^N-1。
浮動小数点数型
floatは単精度(32ビット)、doubleは倍精度(64ビット)。

ISO/IEC 60559準拠の拡張浮動小数点数型

[編集]

標準に加えて、C++はISO/IEC 60559(IEEE 754)準拠の拡張浮動小数点数型もサポートすることが推奨されています。以下の型が存在します:

  • std::float16_t: 16ビット
  • std::float32_t: 32ビット
  • std::float64_t: 64ビット
  • std::float128_t: 128ビット

これらの型は、それぞれのビット幅と精度に応じて、異なる数値範囲と精度を提供します。

C++の基本データ型は、効率的かつ正確にデータを扱うための重要な要素です。各データ型の特性と用途を理解することで、プログラムの効率性と信頼性を向上させることができます。

複合データ型

[編集]

複合データ型は、基本データ型を組み合わせて作成されるデータ型で、より複雑なデータ構造を表現するために使用されます。C++にはいくつかの複合データ型が存在し、それぞれ異なる用途と特性を持っています。以下では、主な複合データ型について解説します。

配列

[編集]

配列は、同じデータ型の要素を連続的に格納するデータ構造です。配列は固定長であり、宣言時にその長さを指定する必要があります。

#include <iostream>

auto main() -> int {
    int numbers[5] = {1, 2, 3, 4, 5};

    for (auto number : numbers) {
        std::cout << number << " ";
    }

    return 0;
}

構造体 (struct)

[編集]

構造体は、異なるデータ型の要素をまとめて一つの複合データ型として定義することができます。構造体を使用すると、関連するデータを一つの単位として扱うことができます。

#include <iostream>
#include <string>

struct Person {
    std::string name;
    int age;
    double height;
};

auto main() -> int {
    Person john = {"John Doe", 30, 175.5};

    std::cout << "Name: " << john.name << "\n";
    std::cout << "Age: " << john.age << "\n";
    std::cout << "Height: " << john.height << "\n";

    return 0;
}

共用体 (union)

[編集]

共用体は、同じメモリ領域を複数のデータ型で共有するデータ構造です。共用体の各メンバは同じメモリを使用するため、同時に一つのメンバしか有効にできません。

#include <iostream>

union Data {
    int intValue;
    float floatValue;
    char charValue;
};

auto main() -> int {
    Data data{ .intValue = 42 };
    std::cout << "Int value: " << data.intValue << "\n";

    data.floatValue = 3.14;
    std::cout << "Float value: " << data.floatValue << "\n";

    data.charValue = 'A';
    std::cout << "Char value: " << data.charValue << "\n";

    return 0;
}

列挙クラス (enum class)

[編集]

列挙クラスは、名前付き定数の集合を定義するためのデータ型です。列挙クラスを使用すると、コードの可読性が向上し、特定の値のみを取る変数を簡単に定義できます。

またC言語風の古い列挙型は名前空間を持ちませんが、列挙クラスは Color::GREEN のように名前空間を持つので識別子の衝突が起こりません。

列挙クラス switch 文の組み合わせにおいてすべての enum class の値をカバーすることで、全称性が担保されます。このとき、default ケースを使わないようにすることで、すべての可能な値が明示的に処理されていることをコンパイラがチェックできます。以下にその例を示します。

#include <iostream>

enum class Color { RED, GREEN, BLUE };

auto main() -> int {
    Color const favoriteColor = Color::GREEN;
    switch (favoriteColor) {
        case Color::RED:
            std::cout << "赤がお好きなんですね!" << std::endl;
            break;
        case Color::GREEN:
            std::cout << "緑がお好きなんですね!" << std::endl;
            break;
        case Color::BLUE:
            std::cout << "青がお好きなんですね!" << std::endl;
            break;

        // default: は列挙型の全称性を担保することを確認するため書いてはならない
    }

    return 0;
}

このコードでは、default ケを使用していません。これにより、すべての Color enum の値 (RED, GREEN, BLUE) が明示的にカバーされていることが保証されます。コンパイラがすべてのケースがカバーされていることをチェックし、もし将来 Color enum に新しい値が追加された場合、コンパイラが警告を出してくれるので、新しい値の処理を追加するのを忘れずに済みます。

タプル (tuple)

[編集]

タプルは、異なるデータ型の要素をまとめて一つの単位として扱うデータ構造で、複数の値を関数から返すときなどに便利です。C++11以降で標準ライブラリに追加されました。

以下のコード例では、tupleのさまざまな機能を示しています。

#include <iostream>
#include <string>
#include <tuple>

auto main() -> int {
    // タプルの作成
    auto tuple1 =
        std::tuple{42, 3.14, std::string{"hello"}};  // C++17から可能な構文

    // タプルの要素へのアクセス
    std::cout << "tuple1: " << std::get<0>(tuple1) << ", "
              << std::get<1>(tuple1) << ", " << std::get<2>(tuple1)
              << std::endl;

    // タプルの要素の参照
    int& a = std::get<0>(tuple1);
    a = 100;
    std::cout << "tuple1 (after modification): " << std::get<0>(tuple1) << ", "
              << std::get<1>(tuple1) << ", " << std::get<2>(tuple1)
              << std::endl;

    // タプルのアンパック
    auto [x, y, z] = tuple1;
    std::cout << "x: " << x << ", y: " << y << ", z: " << z << std::endl;

    // タプルのサイズ
    std::cout << "tuple1 size: " << std::tuple_size<decltype(tuple1)>::value
              << std::endl;

    // タプルの連結
    auto tuple2 = std::tuple_cat(std::tuple{1, 2.0}, std::tuple{'a', false});
    std::cout << "tuple2: " << std::get<0>(tuple2) << ", "
              << std::get<1>(tuple2) << ", " << std::get<2>(tuple2) << ", "
              << std::get<3>(tuple2) << std::endl;

    return 0;
}
出力
tuple1: 42, 3.14, hello
tuple1 (after modification): 100, 3.14, hello
x: 100, y: 3.14, z: hello
tuple1 size: 3
tuple2: 1, 2, a, 0
説明
  1. std::tupleを使ってタプルを作成できます。異なる型の値を要素として持つことができます。
  2. std::get<N>(tuple)を使って、タプルの要素にアクセスできます。ここで、Nは0から始まるインデックスです。
  3. タプルの要素は参照を取得して変更できます。
  4. 構造化バインディングを使えば、タプルの要素を個別の変数にアンパックできます。
  5. std::tuple_size<T>::valueでタプルのサイズ(要素数)を取得できます。
  6. std::tuple_cat()関数を使えば、2つのタプルを連結できます。
  7. std::make_tuple()関数を使えば、値のリストからタプルを作成できます。

このように、C++のtupleは異なる型の値をまとめて扱えるため、関数から複数の値を返したり、複数の引数をまとめて渡したりするのに便利です。また、タプルのサイズは実行時に決定されるため、コンパイル時にはサイズを知る必要がありません。

スマートポインタ (smart pointer)

[編集]

スマートポインタは、動的メモリ管理を自動化するためのクラスであり、C++11以降に導入されました。これにより、メモリリークや未解放メモリの問題を防ぎ、メモリ管理をより安全かつ簡便に行えるようになります。代表的なスマートポインタには、std::unique_ptrstd::shared_ptrstd::weak_ptrがあります。

std::unique_ptr

[編集]

std::unique_ptr は、所有権が一意であることを保証するスマートポインタです。他のポインタに所有権を移すことができますが、コピーはできません。自動的にメモリを解放し、リソース管理が容易です。

#include <iostream>
#include <memory>

auto main() -> int {
    std::unique_ptr<int> ptr = std::make_unique<int>(10);
    std::cout << "Value: " << *ptr << "\n";

    // ptrのスコープを抜けると、自動的にメモリが解放される
    return 0;
}

std::shared_ptr

[編集]

std::shared_ptr は、複数のポインタが同じオブジェクトの所有権を共有できるスマートポインタです。オブジェクトが最後のshared_ptrから解放されると、メモリが自動的に解放されます。

#include <iostream>
#include <memory>

auto main() -> int {
    auto sharedPtr1 = std::make_shared<int>(20);
    {
        auto sharedPtr2 = sharedPtr1; // 所有権を共有
        std::cout << "Shared Value: " << *sharedPtr2 << "\n";
    } // sharedPtr2のスコープを抜けるが、sharedPtr1はまだ生きている

    std::cout << "Shared Value after block: " << *sharedPtr1 << "\n";
    return 0;
}

std::weak_ptr

[編集]

std::weak_ptr は、std::shared_ptr によって所有されているオブジェクトへの弱い参照を提供します。これにより、循環参照を防ぎ、メモリリークを回避することができます。

#include <iostream>
#include <memory>

auto main() -> int {
    auto sharedPtr = std::make_shared<int>(30);
    std::weak_ptr<int> weakPtr = sharedPtr; // weak_ptrを作成

    if (auto lockedPtr = weakPtr.lock()) { // 有効なshared_ptrを取得
        std::cout << "Weak Value: " << *lockedPtr << "\n";
    } else {
        std::cout << "Weak pointer is expired.\n";
    }

    sharedPtr.reset(); // shared_ptrをリセットしてメモリを解放

    if (weakPtr.expired()) {
        std::cout << "Weak pointer is expired after reset.\n";
    }

    return 0;
}
まとめ

スマートポインタは、基本データ型を組み合わせてより複雑なデータ構造を作成するための強力なツールです。std::unique_ptrstd::shared_ptrstd::weak_ptr などのスマートポインタを適切に使用することで、メモリ管理が効率的で可読性の高いプログラムを作成できます。各データ型の特性と使用方法を理解し、状況に応じて適切な型を選択することが重要です。

変数の宣言

[編集]

C++プログラムにおいて、変数はデータを保存するための基本的な手段です。変数の宣言方法、名前の付け方、データ型の指定、および初期化方法について詳しく説明します。

変数とは

[編集]

変数とは、プログラムが実行されている間にデータを保存するための名前付きの記憶場所です。変数には特定のデータ型が割り当てられ、その型に応じた値を保持します。変数を使用することで、プログラムは動的にデータを操作したり、保存したりすることができます。

変数名の規則

[編集]

C++では、変数名を付ける際にいくつかの規則があります。これらの規則を守ることで、プログラムが正しくコンパイルされ、予期しないエラーを防ぐことができます。

  1. 文字と数字を使用: 変数名は英文字(大文字と小文字の区別あり)、数字、アンダースコア(_)を使用できます。ただし、最初の文字は数字であってはいけません。
    • 有効な例: age, total_sum, _value
    • 無効な例: 2ndValue, -height
  2. キーワードの使用禁止: C++のキーワードは変数名として使用できません。
    • 無効な例: int, return, class
  3. 大文字と小文字の区別: 変数名は大文字と小文字を区別します。例えば、Valuevalueは異なる変数とみなされます。
  4. 読みやすさの確保: 変数名は、意味のある名前を付けることでコードの可読性を向上させることが推奨されます。
    • 良い例: totalAmount, numberOfStudents
    • 悪い例: a, n, x1

データ型の指定

[編集]

変数を宣言する際には、その変数が保持するデータの型を指定します。C++では、以下のような基本的なデータ型があります。

  • 整数型: int, short, long, long long
  • 符号なし整数型: unsigned int, unsigned short, unsigned long, unsigned long long
  • 浮動小数点数型: float, double, long double
  • 文字型: char
  • ブール型: bool
int age;
float height;
char initial;
bool isStudent;

変数の初期化

[編集]

変数を宣言する際には、その変数に初期値を割り当てることができます。初期化の方法は以下の通りです。

コピー初期化
int age = 30;
double pi = 3.14;
直接初期化
int age(30);
double pi(3.14);
統一初期化
int age{30};
double pi{3.14};

統一初期化は、より安全な初期化方法とされています。これは、暗黙の型変換が行われないため、意図しない型変換によるエラーを防ぐことができます。

例:

auto main() -> int {
    int age = 25;        // コピー初期化
    double pi(3.14159);  // 直接初期化
    char initial{'D'};   // 統一初期化
    bool isStudent = true;

    // 変数の使用
    std::cout << "Age: " << age << std::endl;
    std::cout << "Pi: " << pi << std::endl;
    std::cout << "Initial: " << initial << std::endl;
    std::cout << "Is Student: " << std::boolalpha << isStudent << std::endl;

    return 0;
}

束縛について

[編集]

C++における「束縛」は、変数が特定の値を保持することを指します。変数が宣言された際、その変数は特定のデータを保持する「束縛」を持ちます。この束縛によって、変数はメモリ上の特定のアドレスに関連付けられ、そのアドレスに格納された値にアクセスすることができます。束縛は変数のライフサイクルに密接に関わっており、変数がスコープを抜けるとその束縛は解除され、関連付けられたメモリは解放されます。

このように、変数の宣言と初期化は、プログラムの基本構造を理解し、正確にデータを操作するための重要なスキルです。変数の正しい使用は、プログラムの可読性と保守性を向上させます。

型付け規則

[編集]

C++における型付け規則は、プログラム内で使用されるデータの型の扱いを定義します。型変換は、あるデータ型の値を別のデータ型に変換するプロセスであり、これには暗黙の型変換と明示的な型変換があります。また、キャストを使用して明示的な型変換を行う方法についても説明します。

暗黙の型変換

[編集]

暗黙の型変換(または自動型変換)は、コンパイラが自動的にある型から別の型へ値を変換するプロセスです。これは、互換性のある型同士で行われ、特に基本データ型間で頻繁に発生します。

#include <iostream>

auto main() -> int {
  { // 初期化と自動型変換
    int a = 10;
    double b = a; // aは自動的にdoubleに変換される

    double x = 9.99;
    int y = x; // xは自動的にintに変換され、小数部分が切り捨てられる
  }
  { // 直接初期化と自動型変換
    int a(10);
    double b(a); // aは自動的にdoubleに変換される

    double x(9.99);
    int y(x); // xは自動的にintに変換され、小数部分が切り捨てられる
  }
  { // 統一初期化と狭小化の警告
    int a{ 10 };
    // double b{ a };
    //=> error: non-constant-expression cannot be narrowed from type 'int' to 'double' in initializer list [-Wc++11-narrowing]

    double x{ 9.99 };
    // int y{ x };
    //=> error: type 'double' cannot be narrowed to 'int' in initializer list [-Wc++11-narrowing]
  }
  return 0;
}

このコードは、C++における統一初期化と型の自動変換を示したものです。主に以下の3つの側面を扱っています。

初期化と自動型変換
最初のブロックでは、int型の変数aを10で初期化し、double型の変数baで初期化しています。この際、aの値は自動的にdouble型に変換されます。同様に、double型の変数xを9.99で初期化し、int型の変数yxで初期化すると、xの値はint型に変換され、小数部分が切り捨てられます。
直接初期化と自動型変換
2番目のブロックでは、直接初期化の構文を使用しています。int a(10);int a = 10;と同じ意味です。double b(a);の場合も、aの値は自動的にdouble型に変換されます。double x(9.99);int y(x);の振る舞いは、最初のブロックと同様です。
統一初期化と狭小化の警告
3番目のブロックでは、統一初期化の構文{}を使用しています。int a{10};double x{9.99};は問題ありません。しかし、double b{a};int y{x};はコメントアウトされています。これらの行をコメントアウトしない場合、コンパイラは狭小化の警告を出力します。統一初期化では、明示的な型キャストがない限り、異なる型への変換は許可されません。つまり、intからdoubleへの変換も、doubleからintへの変換も許可されず、エラーが発生します。

このコードは、C++における初期化の構文と、自動型変換や狭小化の扱いについて説明しています。統一初期化なら、より厳格な規則が適用されることに注意が必要です。

明示的な型変換

[編集]

明示的な型変換は、プログラマが意図的にある型を別の型に変換することを指します。C++では、明示的な型変換をキャストと呼びます。キャストを使用することで、型変換の意図を明確にし、安全性と可読性を向上させることができます。

キャストの使い方

[編集]

C++には複数のキャスト方法があり、それぞれ異なる用途と安全性を持っています。適切なキャストを選択することは、プログラムの安全性と可読性を確保する上で非常に重要です。以下に、主なキャスト方法とその特徴を説明します。

Cスタイルキャスト(非推奨)

[編集]

C++では、Cスタイルキャストを使用して明示的な型変換を行うことができますが、これは推奨されません。なぜなら、Cスタイルキャストは非常に強力であり、意図しない変換を引き起こす可能性があるためです。Cスタイルキャストは、以下のように括弧を使用します。

int a = 10;
double b = (double)a; // Cスタイルキャスト

static_cast

[編集]

static_castは、最も一般的に使用されるキャストで、基本データ型間の変換や互換性のあるクラス型間の変換に使用されます。static_castは、コンパイル時に型チェックが行われるため、安全性が高くなります。

int a = 10;
double b = static_cast<double>(a);

dynamic_cast

[編集]

dynamic_castは、ポインタや参照を基底クラス型から派生クラス型へ安全に変換するために使用されます。これは、ランタイム型情報(RTTI)を使用して、変換が安全かどうかを確認します。dynamic_castは、主に多態性を扱う際に使用されます。

class Base { virtual void func() {} };
class Derived : public Base { void func() override {} };

Base* basePtr = new Derived();
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr); // 安全な変換

const_cast

[編集]

const_castは、オブジェクトの定数性を除去するために使用されます。これは、定数メンバ関数内で非定数メンバにアクセスする必要がある場合などに使用されます。const_castは、慎重に使用する必要があります。

const int a = 10;
int* b = const_cast<int*>(&a); // aの定数性を除去
*b = 20; // aの値を変更

reinterpret_cast

[編集]

reinterpret_castは、ポインタ型やビットパターンを変更するために使用されます。これは、非常に低レベルな操作であり、慎重に使用する必要があります。reinterpret_castは、型の互換性がない場合にも使用できますが、プログラムの動作が未定義になる可能性があります。

int a = 65;
char* b = reinterpret_cast<char*>(&a);
std::cout << *b; // 'A'が出力される(ASCIIコード65に対応)

束縛の概念

[編集]

C++における「束縛」は、変数やオブジェクトが特定の型または値に関連付けられることを指します。例えば、ある変数が特定のデータ型で宣言されると、その変数はその型に属する値のみを保持することができます。この束縛は、プログラムの型安全性を向上させるために重要であり、間違った型の値を変数に代入しようとするとコンパイルエラーが発生します。

束縛の概念は、型変換を行う際にも関連しています。たとえば、static_castを使用することで、型の束縛を尊重しつつ安全な変換を行うことができます。このように、束縛はC++の型システムの中心的な要素であり、プログラムの正確性を維持するために不可欠です。

まとめ

C++における型変換は、プログラムの柔軟性を高めるために不可欠です。暗黙の型変換は自動的に行われる一方で、明示的な型変換はプログラマが意図的に行うものであり、適切なキャストを使用して安全かつ明確に実施されます。正しいキャストの選択とその慎重な使用により、プログラムの安全性と可読性を向上させることができます。

キャストを使用する際は、常に目的と意図を明確にし、適切なキャストを選択することが重要です。特に、Cスタイルキャストは避けるべきで、代わりにstatic_castdynamic_castを使用することをお勧めします。また、const_castreinterpret_castは、より慎重に使用する必要があります。

リテラル

[編集]

リテラルとは、プログラム中に直接記述された値そのものを指します。C++では、さまざまな型のリテラルが用意されており、それぞれ異なる形式で表現されます。以下に、主要なリテラルについて説明します。

整数リテラル

[編集]

整数リテラルは、整数型の値を表すリテラルです。整数リテラルには、10進数、8進数、16進数、2進数の形式があります。C++14以降では、アンダースコア(_)を用いて数値を区切ることができます。

10進数リテラル
int decimal{42};
8進数リテラル
先頭に0oを付けて表現します(C++14以降)。
int octal{0o52}; // 10進数の42と同じ
16進数リテラル
先頭に0xまたは0Xを付けて表現します。
int hex{0x2A}; // 10進数の42と同じ
2進数リテラル
先頭に0bまたは0Bを付けて表現します。
int binary{0b101010}; // 10進数の42と同じ

整数リテラルには、データ型を指定するためのサフィックスもあります。

  • uまたはU: unsigned
  • lまたはL: long
  • llまたはLL: long long
  • zまたはZ: size_t
unsigned int unsignedDecimal{42u};
long longDecimal{42L};
long long longLongDecimal{42LL};
size_t sizeDecimal{42z}; // size_tリテラル

浮動小数点リテラル

[編集]

浮動小数点リテラルは、小数点を含む数値を表します。通常の10進形式のほかに、指数形式でも表現できます。また、C++20以降では16進浮動小数点リテラルも使用可能です。

  1. 通常の10進形式
    double decimal = 3.14;
    
  2. 指数形式
    eまたはEを使用して指数部を表現します。
    double scientific = 3.14e2; // 3.14 × 10^2 = 314
    
  3. 16進浮動小数点リテラル
    先頭に0xまたは0Xを付けて表現します。
    double hexFloat = 0x1.91f0p+1; // 1.5 × 16^1 = 24.0
    

浮動小数点リテラルには、データ型を指定するためのサフィックスもあります。

  • fまたはF: float
  • lまたはL: long double
float floatLiteral = 3.14f;
long double longDoubleLiteral = 3.14L;

文字リテラル

[編集]

文字リテラルは、単一の文字を表します。文字リテラルは、シングルクォート(')で囲まれます。

char charLiteral = 'A';

文字リテラルには、エスケープシーケンスを使用して特殊文字を表現することもできます。

char newLine = '\n'; // 改行文字
char tab = '\t';     // タブ文字
char singleQuote = '\''; // シングルクォート
char backslash = '\\';   // バックスラッシュ

文字列リテラル

[編集]

文字列リテラルは、複数の文字を連続して表現するリテラルです。文字列リテラルは、ダブルクォート(")で囲まれます。

const char* str = "Hello, World!";

文字列リテラル内でも、エスケープシーケンスを使用することができます。

const char* str = "Hello,\nWorld!"; // 改行を含む文字列

C++11以降では、生文字列リテラルを使用することができ、エスケープシーケンスや改行をそのまま表現することができます。生文字列リテラルは、R"delimiter(文字列)delimiter"の形式で記述します。

const char* rawStr = R"(Hello,\nWorld!)"; // エスケープシーケンスが無視される
まとめ

リテラルは、プログラム中で直接使用される具体的な値を表現するための基本的な要素です。整数リテラル、浮動小数点リテラル、文字リテラル、および文字列リテラルの各形式を理解することで、C++プログラムの可読性と正確性を向上させることができます。

定数の宣言

[編集]

C++では、プログラム内で値を変更しない変数を「定数」として宣言することができます。定数の宣言には主にconst修飾子を使用します。定数を使用することで、コードの可読性が向上し、バグの発生を防ぐことができます。

const修飾子

[編集]

const修飾子を使用すると、その変数は初期化後に変更できなくなります。これは、意図しない変更を防ぐために役立ちます。

const int MAX_USERS{100};
const double PI{3.14159};
const char NEWLINE{'\n'};

上記の例では、MAX_USERSPINEWLINEという3つの定数が宣言されています。これらの値は、プログラムの実行中に変更することはできません。

定数の使用例

[編集]

const定数はさまざまな場面で使用されます。以下に、いくつかの典型的な使用例を示します。

配列のサイズを定数として宣言

[編集]

配列のサイズを定数として宣言すると、配列のサイズが変更される可能性を排除できます。

const int ARRAY_SIZE{10};
int numbers[ARRAY_SIZE];

定数を使用した計算

[編集]

定数を使用することで、計算式が分かりやすくなり、間違いを減らすことができます。

const double TAX_RATE{0.10};
double price{100.0};
double tax = {price * TAX_RATE};
double totalPrice{price + tax};

関数の引数に定数を使用

[編集]

関数の引数にconstを使用することで、その引数が関数内で変更されないことを保証できます。

void printArray(const int arr[], const size_t size) {
    for (int i = 0; i < size; i++) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;
}

ポインタとconst

[編集]

ポインタとconstを組み合わせることで、指し示す値が変更されないことを保証できます。

const int value{42};
const int* ptr{&value}; // ptrを通じてvalueを変更することはできない

さらに、ポインタ自体をconstにすることもできます。

int value1{42};
int value2{100};
int* const ptr{&value1}; // ptr自体は変更できないが、指し示す値は変更できる
*ptr = 50; // OK
ptr = &value2; // エラー

定数メンバ関数

[編集]

クラスのメンバ関数にconstを指定することで、その関数内でメンバ変数が変更されないことを保証できます。

#include <iostream>

class MyClass {
 private:
    const int value;

 public:
    MyClass(int val) : value{val} {}

    void display() const {
        std::cout << "Value: " << value << std::endl;
    }
    
    // void f(int x) { value = x; }
    //=> error: cannot assign to non-static data member 'value' with const-qualified type 'const int'
};

constexpr修飾子

[編集]

constexpr修飾子は、コンパイル時に評価される定数式を定義するために使用されます。constexprで宣言された変数や関数は、コンパイル時に値が確定し、実行時のオーバーヘッドが削減されます。

constexpr int getValue() { return 42; }
constexpr int result = getValue(); // resultはコンパイル時に42と評価される

constexprを使用することで、コンパイル時に計算できる定数や関数を定義し、プログラムのパフォーマンスを向上させることができます。

consteval修飾子

[編集]

consteval修飾子は、関数が必ずコンパイル時に評価されることを保証します。consteval関数は実行時に呼び出すことができず、常にコンパイル時に評価される必要があります。

consteval int getCompileTimeValue() { return 100; }
constexpr int compileTimeResult = getCompileTimeValue(); // OK: コンパイル時に評価される

constevalは、確実にコンパイル時に評価される定数を得たい場合に有用です。

まとめ
const
変数の値が不変であることを示します。初期化後に変更不可。
    const int maxValue{100};
constexpr
コンパイル時に評価される定数式を定義します。コンパイル時に値が確定するため、実行時のパフォーマンスが向上します。
    constexpr int getValue() { return 42; }
    constexpr int result = getValue();
consteval
必ずコンパイル時に評価される関数を定義します。
    consteval int getCompileTimeValue() { return 100; }
    constexpr int compileTimeResult = getCompileTimeValue();

これらのキーワードを適切に使い分けることで、C++プログラムの安全性、効率性、およびパフォーマンスを向上させることができます。

スコープと有効範囲

[編集]

C++におけるスコープと有効範囲の理解は、プログラムが意図した通りに動作するために重要です。スコープは変数や関数が有効な範囲を指し、有効範囲はそのスコープ内でそれらがアクセス可能な範囲を意味します。

スコープの概念

[編集]

スコープ(scope)は、プログラム内で識別子(変数名、関数名など)が認識され、使用できる領域を指します。スコープは大きく分けて以下の4種類に分類されます:

ブロックスコープ(Block Scope)
ブロック内({}で囲まれた範囲)で有効。
関数スコープ(Function Scope)
関数内で有効。
ファイルスコープ(File Scope)
ファイル内で有効。
グローバルスコープ(Global Scope)
プログラム全体で有効。

ローカル変数とグローバル変数

[編集]

ローカル変数とグローバル変数は、スコープの観点から見て重要な役割を果たします。

ローカル変数

[編集]

ローカル変数は、その変数が宣言されたブロック内でのみ有効です。関数やループ内で宣言されることが多く、ブロックを抜けると自動的に破棄されます。

void myFunction() {
    int localVar = 10; // ローカル変数
    // このブロック内でのみ有効
}

グローバル変数

[編集]

グローバル変数は、ブロック外で宣言され、プログラム全体からアクセス可能です。通常、ファイルの先頭で宣言され、全ての関数やブロックから参照できます。

int globalVar = 20; // グローバル変数

void myFunction() {
    globalVar = 30; // グローバル変数を変更
}

名前空間

[編集]

名前空間(namespace)は、識別子の衝突を避けるために使用されます。特に大規模なプロジェクトやライブラリでは、同じ名前の変数や関数が存在することがあります。名前空間を使用することで、これらの識別子をグループ化し、他のグループと区別することができます。

名前空間の宣言と使用

[編集]

名前空間を宣言するにはnamespaceキーワードを使用します。

namespace MyNamespace {
    int myVariable = 50;

    void myFunction() {
        // 名前空間内の関数
    }
}

名前空間内の要素にアクセスするには、名前空間の名前を指定します。

auto main() -> int {
    MyNamespace::myVariable = 60; // 名前空間を指定してアクセス
    MyNamespace::myFunction(); // 名前空間を指定して関数を呼び出し
    return 0;
}

usingキーワードの使用

[編集]

usingキーワードを使うことで、名前空間を明示的に指定せずに、その名前空間内の要素を使用することができます。

using namespace MyNamespace;

auto main() -> int {
    myVariable = 60; // 名前空間を指定せずにアクセス
    myFunction(); // 名前空間を指定せずに関数を呼び出し
    return 0;
}

ただし、大規模なプロジェクトでは、using namespaceを多用することは避けた方が良いです。理由は、名前の衝突を招きやすくなるためです。

まとめ

スコープと有効範囲は、プログラムの動作を理解し、バグを防ぐために重要な概念です。ローカル変数とグローバル変数の使い分けや、名前空間を使用して識別子の衝突を避けることが、C++プログラミングにおいて重要なスキルとなります。適切にこれらの概念を活用することで、より読みやすく、保守しやすいコードを書くことができます。

構造化束縛宣言 (C++17以降)

[編集]

構造化束縛宣言とは

[編集]

構造化束縛宣言(Structured Binding Declaration)は、C++17で導入された機能で、複数の変数を一度に初期化するための新しい方法です。これにより、タプルやペア、構造体のメンバ変数など、複数の値を簡潔に扱うことができます。従来の方法と比べて、コードの可読性や保守性が向上します。

構造化束縛宣言は、以下のようなデータ構造に対して使用できます:

  • タプル (std::tuple)
  • ペア (std::pair)
  • 配列
  • 構造体

構造化束縛宣言の使用例

[編集]

以下に、構造化束縛宣言の具体的な使用例を示します。

タプルを使用する例

[編集]
#include <tuple>
#include <iostream>

std::tuple<int, double, std::string> getValues() {
    return std::make_tuple(42, 3.14, "Hello");
}

auto main() -> int {
    auto [i, d, s] = getValues();
    std::cout << "Integer: " << i << std::endl;
    std::cout << "Double: " << d << std::endl;
    std::cout << "String: " << s << std::endl;
    return 0;
}

この例では、getValues関数がタプルを返し、構造化束縛宣言を使ってタプルの各要素を変数idsに分解しています。

ペアを使用する例

[編集]
#include <utility>
#include <iostream>

std::pair<int, std::string> getPair() {
    return std::make_pair(1, "example");
}

auto main() -> int {
    auto [number, text] = getPair();
    std::cout << "Number: " << number << std::endl;
    std::cout << "Text: " << text << std::endl;
    return 0;
}

この例では、getPair関数がペアを返し、構造化束縛宣言を使ってペアの各要素を変数numbertextに分解しています。

配列を使用する例

[編集]
#include <iostream>

auto main() -> int {
    int arr[3] = {1, 2, 3};
    auto [a, b, c] = arr;
    std::cout << "a: " << a << std::endl;
    std::cout << "b: " << b << std::endl;
    std::cout << "c: " << c << std::endl;
    return 0;
}

この例では、配列arrを構造化束縛宣言を使って分解し、各要素を変数abcに割り当てています。

構造体を使用する例

[編集]
#include <iostream>

struct Point {
    int x;
    int y;
};

auto main() -> int {
    Point p = {10, 20};
    auto [x, y] = p;
    std::cout << "x: " << x << std::endl;
    std::cout << "y: " << y << std::endl;
    return 0;
}

この例では、構造体Pointを構造化束縛宣言を使って分解し、メンバ変数xyをそれぞれの変数に割り当てています。

まとめ

構造化束縛宣言は、C++17で導入された非常に強力な機能です。これにより、タプル、ペア、配列、構造体などの複数の値を簡潔かつ直感的に扱うことが可能になります。従来の方法では、例えばタプルの要素を個別に変数に代入する際に、std::get<0>(tuple) のように記述する必要がありましたが、構造化束縛を使うことでコードがすっきりし、可読性が大幅に向上します。

この機能は特に、大規模なプロジェクトやライブラリでのデータ構造の操作において、コードの保守性を高めるのに役立ちます。C++17以降のプロジェクトでは、構造化束縛宣言を積極的に活用することをお勧めします。これにより、コードの可読性を保ちつつ、開発効率を向上させることができます。

応用例と演習問題

[編集]

応用例

[編集]

ここでは、構造化バインディングを活用したいくつかの応用例を紹介します。

  1. ファイル入出力における構造化バインディング
    #include <fstream>
    #include <string>
    #include <tuple>
    #include <iostream>
    
    std::tuple<bool, std::string> readFile(const std::string& filename) {
        std::ifstream file(filename);
        if (!file) {
            return std::make_tuple(false, "");
        }
        std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
        return std::make_tuple(true, content);
    }
    
    auto main() -> int {
        auto [success, content] = readFile("example.txt");
        if (success) {
            std::cout << "File content:\n" << content << "\n";
        } else {
            std::cout << "Failed to read the file.\n";
        }
        return 0;
    }
    
    この例では、readFile関数がタプルを返し、構造化バインディングを使ってファイル読み込みの成功可否と内容を受け取ります。
  2. データベースクエリ結果の処理
    #include <iostream>
    #include <tuple>
    #include <vector>
    
    using Record = std::tuple<int, std::string, double>;
    
    std::vector<Record> queryDatabase() {
        return {
            {1, "Alice", 95.5},
            {2, "Bob", 85.0},
            {3, "Charlie", 78.2}
        };
    }
    
    auto main() -> int {
        for (const auto& record : queryDatabase()) {
            auto [id, name, score] = record;
            std::cout << "ID: " << id << ", Name: " << name << ", Score: " << score << "\n";
        }
        return 0;
    }
    
    この例では、データベースから取得したレコードをタプルのベクタとして受け取り、構造化バインディングを使って各フィールドにアクセスしています。

演習問題

[編集]

以下の演習問題に取り組んで、構造化バインディングの理解を深めましょう。

問題 1:配列の要素の分解
次のコードを完成させて、配列の要素を構造化バインディングを使って分解し、各要素を出力してください。
#include <iostream>

auto main() -> int {
    int arr[4] = {10, 20, 30, 40};
    // 構造化バインディングを使って配列の要素を分解し、それぞれの要素を変数に代入してください。
    auto [a, b, c, d] = arr;
    std::cout << "a: " << a << "\n";
    std::cout << "b: " << b << "\n";
    std::cout << "c: " << c << "\n";
    std::cout << "d: " << d << "\n";
    return 0;
}
問題 2:構造体のメンバ変数の分解
次の構造体Personのインスタンスを構造化バインディングを使って分解し、各メンバ変数を出力するプログラムを書いてください。
#include <iostream>
#include <string>

struct Person {
    std::string name;
    int age;
    double height;
};

auto main() -> int {
    Person person = {"John Doe", 30, 175.5};
    // 構造化バインディングを使って構造体のメンバ変数を分解し、それぞれのメンバ変数を変数に代入してください。
    auto [name, age, height] = person;
    std::cout << "Name: " << name << "\n";
    std::cout << "Age: " << age << "\n";
    std::cout << "Height: " << height << "\n";
    return 0;
}
問題 3:関数の戻り値の分解
次のコードを完成させて、関数getPointの戻り値を構造化バインディングを使って分解し、各要素を出力してください。
#include <iostream>
#include <tuple>

std::tuple<int, int> getPoint() {
    return std::make_tuple(5, 10);
}

auto main() -> int {
    // 構造化バインディングを使って関数の戻り値を分解し、それぞれの要素を変数に代入してください。
    auto [x, y] = getPoint();
    std::cout << "x: " << x << "\n";
    std::cout << "y: " << y << "\n";
    return 0;
}

これらの演習問題を通じて、構造化バインディングの基本的な使い方をマスターしましょう。