cv.cm

コーディング · プログラミング言語

C++入門チュートリアル(初心者向け)

約12分で読めます

結論

C++はコンパイル型の静的型付け言語で、Cの速度と低水準の制御に、クラス、テンプレート、大規模な標準ライブラリを組み合わせています。hello.cpp を g++ -std=c++20 hello.cpp -o hello でコンパイルし、./hello で実行します。モダンC++では、生の配列の代わりに std::string、std::vector、std::map を使い、手動の delete の代わりにスマートポインタを使い、RAIIによってリソースを自動的に解放します。

バイナリ変換を開く

C++とは何か、何に使われるか

C++は1979年にBell研究所のBjarne Stroustrupが「C with Classes」として開発を始めた言語です。ISOによって標準化されており、C++11から「モダンC++」の時代が始まり、C++17、C++20、C++23が続きました。C++は性能と制御が重要な分野で使われています。ゲームエンジン(Unreal Engine)、Webブラウザ(Chrome、Firefox)、データベース、取引システム、組み込み機器、コンパイラ、機械学習のランタイムなどです。基本思想はゼロオーバーヘッド抽象化で、使わない機能にはコストがかかりません。

コンパイラのインストールと Hello, world の実行

LinuxとmacOSではGCC(g++)またはClang(clang++)、WindowsではMicrosoft Visual C++(Visual Studio経由)を使います。規格のバージョンは明示的に指定しましょう。

#include <iostream>

int main() {
    std::cout << "Hello, world\n";
    return 0;
}
g++ -std=c++20 -Wall -Wextra hello.cpp -o hello
./hello
# Hello, world

std::cout は標準出力ストリームで、<< で書き込みます。標準ライブラリのものはすべて名前空間 std にあります。

変数、型、auto

C++にはCと同じ組み込み型があり、さらに bool がキーワードとして加わり、参照や std::string などのライブラリ型があります。auto は初期化子からコンパイラに型を推論させます。波括弧初期化 {} は、縮小変換を拒否します。

型例備考
int, long longint n{42};符号付き整数
doubledouble x{3.14};64ビット浮動小数点数
boolbool ok{true};true または false
charchar c{'A'};1バイト
std::stringstd::string s{"hi"};<string> で提供される、伸縮する文字列
autoauto v = 2.5;推論された型。ここでは double
T&int& r = n;参照:n の別名
#include <iostream>
#include <string>

int main() {
    int count{7};
    auto avg = count / 2.0;          // double, 3.5
    std::string name{"Ada"};
    name += " Lovelace";
    const int& ref = count;          // read-only alias of count
    std::cout << name << " " << name.size() << " " << avg << " " << ref << "\n";
    // Ada Lovelace 12 3.5 7
}

制御構文と関数

制御構文はCと同じで、範囲ベースの for ループが加わります。大きなオブジェクトはコピーを避けるため const 参照で渡し、関数がオブジェクトを変更する場合は非 const 参照で渡します。

#include <iostream>
#include <string>
#include <vector>

int square(int x) { return x * x; }

void addSuffix(std::string& s, const std::string& suffix = "!") {
    s += suffix;
}

int main() {
    int score = 72;
    if (score >= 90) std::cout << "A\n";
    else if (score >= 70) std::cout << "B\n";   // B
    else std::cout << "C\n";

    std::vector<int> nums{1, 2, 3};
    for (int n : nums) std::cout << square(n) << " ";  // 1 4 9
    std::cout << "\n";

    std::string word = "hi";
    addSuffix(word);
    std::cout << word << "\n";                  // hi!
}

クラス

クラスはデータメンバーとメンバー関数をまとめます。コンストラクタがオブジェクトを初期化し、private メンバーは外部のコードから隠されます。struct は、メンバーがデフォルトで public である点を除いてクラスと同じです。

#include <iostream>
#include <string>
#include <utility>

class Account {
public:
    explicit Account(std::string owner) : owner_(std::move(owner)) {}

    void deposit(double amount) {
        if (amount > 0) balance_ += amount;
    }
    double balance() const { return balance_; }
    const std::string& owner() const { return owner_; }

private:
    std::string owner_;
    double balance_ = 0.0;
};

int main() {
    Account acc{"Ann"};
    acc.deposit(50);
    acc.deposit(-5);   // ignored
    std::cout << acc.owner() << ": " << acc.balance() << "\n";  // Ann: 50
}

継承(class Savings : public Account)と virtual 関数により、実行時ポリモーフィズムが実現します。オーバーライドする関数には override を付けると、コンパイラが検査してくれます。

標準ライブラリのコンテナとアルゴリズム

標準ライブラリ(STLとも呼ばれます)は、イテレータを通じて連携するコンテナとアルゴリズムを提供します。

コンテナ似たもの用途
std::vector<T>動的配列標準的なシーケンスコンテナ
std::array<T, N>固定長配列サイズがコンパイル時に分かっている場合
std::map<K, V>ソート済み辞書キーが順序付き、検索はO(log n)
std::unordered_map<K, V>ハッシュテーブル平均O(1)の高速な検索
std::set<T>ソート済みの重複なし集合所属判定、順序付きの出力
#include <algorithm>
#include <iostream>
#include <map>
#include <string>
#include <vector>

int main() {
    std::vector<int> nums{3, 1, 2, 5};
    std::sort(nums.begin(), nums.end());
    auto evens = std::count_if(nums.begin(), nums.end(),
                               [](int n) { return n % 2 == 0; });
    std::cout << nums.front() << " " << nums.back() << " " << evens << "\n";  // 1 5 1

    std::map<std::string, int> ages{{"bob", 25}, {"ann", 31}};
    ages["cy"] = 40;
    for (const auto& [name, age] : ages) {     // structured bindings
        std::cout << name << "=" << age << " ";  // ann=31 bob=25 cy=40
    }
    std::cout << "\n";
}

[](int n) { ... } はラムダ式で、その場に書く無名関数です。テンプレートのおかげで、1つの std::vector があらゆる要素型で使えます。自分で書くには template <typename T> を使います。

特徴的な機能:RAIIとスマートポインタ

RAII(Resource Acquisition Is Initialization、リソース取得は初期化)とは、リソースをオブジェクトが所有するという考え方です。コンストラクタが取得し、デストラクタが解放します。オブジェクトがスコープを抜けるとき、例外が投げられた場合でもデストラクタが自動的に実行されるため、メモリ、ファイル、ロックがリークしません。スマートポインタは、このRAIIをヒープメモリに適用したものです。

#include <iostream>
#include <memory>

struct Noisy {
    int id;
    explicit Noisy(int i) : id(i) { std::cout << "create " << id << "\n"; }
    ~Noisy() { std::cout << "destroy " << id << "\n"; }
};

int main() {
    auto a = std::make_unique<Noisy>(1);       // sole owner
    {
        auto b = std::make_shared<Noisy>(2);   // shared, reference-counted
        auto c = b;                            // two owners now
        std::cout << "owners: " << b.use_count() << "\n";
    }                                          // destroy 2 happens here
    std::cout << "end of main\n";
}                                              // destroy 1 happens here

出力:create 1、create 2、owners: 2、destroy 2、end of main、destroy 1。モダンなコードでは、new や delete を自分で書くことはほとんどありません。

例外によるエラー処理

例外オブジェクトを throw し、const 参照で catch します。標準の例外型は <stdexcept> にあります。例外を投げない関数には noexcept を付けられます。

#include <iostream>
#include <stdexcept>
#include <string>

int parseAge(const std::string& text) {
    int age = std::stoi(text);   // throws std::invalid_argument on "abc"
    if (age < 0) throw std::out_of_range("negative age");
    return age;
}

int main() {
    try {
        std::cout << parseAge("41") << "\n";   // 41
        std::cout << parseAge("abc") << "\n";
    } catch (const std::invalid_argument&) {
        std::cout << "not a number\n";         // not a number
    } catch (const std::exception& e) {
        std::cout << "error: " << e.what() << "\n";
    }
}

想定内の失敗に対しては、例外を投げる代わりに std::optional<T>(C++17)や std::expected<T, E>(C++23)を返すコードベースも多くあります。

小さな完成プログラム:単語の出現頻度

#include <algorithm>
#include <cctype>
#include <fstream>
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>

int main(int argc, char* argv[]) {
    if (argc != 2) {
        std::cerr << "usage: " << argv[0] << " FILE\n";
        return 1;
    }
    std::ifstream in(argv[1]);
    if (!in) {
        std::cerr << "cannot open " << argv[1] << "\n";
        return 1;
    }
    std::unordered_map<std::string, int> counts;
    std::string word;
    while (in >> word) {
        std::string clean;
        for (unsigned char ch : word)
            if (std::isalpha(ch)) clean += static_cast<char>(std::tolower(ch));
        if (!clean.empty()) ++counts[clean];
    }
    std::vector<std::pair<std::string, int>> top(counts.begin(), counts.end());
    std::sort(top.begin(), top.end(),
              [](const auto& a, const auto& b) { return a.second > b.second; });
    for (size_t i = 0; i < top.size() && i < 5; ++i)
        std::cout << top[i].second << "  " << top[i].first << "\n";
}

g++ -std=c++20 -O2 words.cpp -o words でコンパイルし、./words notes.txt で実行します。in がスコープを抜けるとファイルは自動的に閉じられます。これもRAIIです。

初心者がよくやる間違い

  • ヘッダファイルに using namespace std; を書く。それをインクルードするすべてのファイルが汚染されます。
  • const& で足りるのに、std::vector などの大きなオブジェクトを値渡しする。
  • int i と v.size() の比較のように、符号付きと符号なしを混ぜて比較する。警告を有効にすると気づけます。
  • push_back の後も std::vector へのイテレータや参照を持ち続ける。要素が移動している可能性があります。
  • std::make_unique や std::vector などの所有する型を使わず、生の new と delete を使う。

次のステップ

言語とライブラリのリファレンスとしてcppreference.comを、良い書き方の指針としてC++ Core Guidelines(Bjarne StroustrupとHerb Sutterが編集)を使いましょう。Standard C++ Foundationのサイトisocpp.orgには、おすすめの書籍を載せた入門ページがあります。プロジェクトのビルドにはCMakeを学び、続いてムーブセマンティクス、テンプレート、constexpr、std::thread による並行処理へ進みます。

よくある質問

C++の前にCを学ぶべきですか?

必須ではありません。モダンC++の教え方は std::string、std::vector、クラスから始め、生ポインタやCスタイルの配列は後で扱います。ただし、Cを先に学ぶとメモリの理解には役立ちます。

どのC++規格を使うべきですか?

C++17とC++20は現行のGCC、Clang、MSVCで十分にサポートされており、C++23のサポートも広がっています。-std=c++20 のようなフラグで明示的に選びましょう。

ポインタと参照の違いは何ですか?

参照は、作成時に束縛する必要があり、付け替えたりnullにしたりできない別名です。ポインタはアドレスを保持し、nullにでき、別の対象を指すように変更できます。

C++は習得が難しいですか?

言語の規模が大きいため、PythonやGoより学習曲線は長くなります。モダンな機能、標準コンテナ、スマートポインタに絞って使えば、典型的な落とし穴の大半を避けられます。

コーディング講座の一覧