「C++ 高度なプログラミング」読書メモ (1: C++ と標準ライブラリの短期集中コース)

1. 参考文献

2. 始めるには、「21 Days to Learn C++」という本を読むことをお勧めします。ノートのリンクは次のとおりです。

1. C++ の基礎知識

1.1 アプレット「ハローワールド」

// helloworld.cpp
/*
    helloworld.cpp
*/
#include <iostream>

int main() {
    
    
    std::cout << "Hello, World!" << std::endl;
    
    return 0;
}
  • ノート

    • このプログラムの最初の 4 行は単一行のコメントと複数行のコメントであり、プログラマが読むための単なるメッセージであり、コンパイラによって無視されます。
  • 前処理ディレクティブ

    • C++ プログラムの生成には 3 つのステップがあります: 最初に、コード内のメタ情報を認識するプリプロセッサでコードが実行されます。2 番目に、コードがコンパイルまたはコンピュータが認識できるオブジェクト ファイルに変換されます。最後に、個別のオブジェクト ファイルはアプリケーションにリンクされます。
    • #include <iostream> は前処理ディレクティブであり、前処理ディレクティブは # 文字で始まります。
    • include ディレクティブは、プリプロセッサに次のことを指示します。 <iostream> ヘッダー ファイル内のすべてのコンテンツを抽出し、それを現在のファイルに提供します。
    • ヘッダー ファイルの最も一般的な使用法は、他の場所で定義された関数を宣言することです。
    • 関数宣言は、コンパイラーに関数の呼び出し方法を通知し、関数内のパラメーターの数と型、および関数の戻り値の型を宣言します。関数定義には、関数の実際のコードが含まれています。C++ では、宣言は通常、ヘッダー ファイルと呼ばれる拡張子 .h を持つファイルに配置され、その定義は通常、ソース ファイルと呼ばれる拡張子 .cpp を持つファイルに含まれます。
    • C の標準ライブラリ ヘッダーは C++ にもまだ存在しますが、次の 2 つのバージョンを使用します。
      • .h 接尾辞を使用せず、代わりに <cstdio> などの接頭辞 c を使用します。これらのバージョンは std 名前空間に配置されます。
      • .h 接尾辞を使用します。これは、名前空間を使用しない <stdio.h> などの古いバージョンです。
    • 以下は、二重包含を回避するためにプリプロセッサ ディレクティブを使用する例です。
    #ifndef MYHEADER_H
    #define MYHEADER_H
    //...the contents of this header filef
    #endif
    // 或(上下等价)
    #pragma once
    //...the contents of this header filef
    
  • メイン機能

    • main() 関数型プログラムのエントリ ポイント。明示的な return ステートメントは無視できます。
    • argc はプログラムに渡される引数のリストを提供し、argv にはこれらの引数が含まれます。argv[0] はプログラムの名前または空の文字列である場合があることに注意してください。
  • I/Oストリーム

    // std::cout 用于输出信息
    // std::cerr 用于输出错误信息
    // std::endl 代表序列末尾,换行,表明一行末尾的另一种方法是使用 \n 字符(转义字符)
    // std::cin 接受键盘输入信息
    // 注:printf() 和 scanf() 未提供类型安全,不建议使用
    

1.2 名前空間

  • 名前空間は、異なるコードセグメント間の名前の競合を処理するために使用されます。
    // 头文件 namespaces.h
    namespace mycode {
          
          
        void foo();
    }
    
    // namespaces.cpp
    // 名称空间中还可实现方法或函数
    #include <iostream>
    #include "namespaces.h"
    
    void mycode::foo() {
          
          
        std::cout << "foo() called in the mycode namespace" << std::endl;
    }
    
    // usingnamespaces.cpp
    #include "namespaces.h"
    
    using namespace mycode;
    
    int main() {
          
          
        mycode::foo();	// 调用 mycode 名称空间中的 foo() 函数
        foo();			// 使用 using 后也可隐式调用
        return 0;
    }
    

ヘッダー ファイルでは using ディレクティブや using 宣言を決して使用しないでください。使用しないと、ヘッダー ファイルを追加する全員がそれを使用する必要があります。

  • C++17 では、ネストされた名前空間を便利に使用できます。つまり、ある名前空間を別の名前空間の中に入れることができます。
    namespace MyLibraries::Networking::FTP {
          
          }
    
  • C++17 は、別の名前空間に新しい短い名前を与える名前空間エイリアスも使用します。
    namespace MyFTP = MyLibraries::Networking::FTP;
    

1.3 リテラル

  • リテラルは、コード内に数値または文字列を記述するために使用されます
  • 次のリテラルは数値 123 を指定します。
    • 10 進リテラル 123
    • 8 進リテラル 0173
    • 16 進数リテラル 0x7B
    • バイナリリテラル 0b1111011
  • 他のリテラル
    • 浮動小数点値 (例: 3.14f)
    • 倍精度浮動小数点値 (例: 3.14)
    • 単一の文字 (例: 'a')
    • ゼロで終わる文字配列 (「文字配列」など)
    • 引数の型をカスタマイズすることもできます

1.4 変数

  • C++ では、変数はどこでも宣言でき、変数は宣言された行以降のどこでも使用できます。
  • 初期化されていない変数がコード内で使用されると、ほとんどのコンパイラは警告またはエラー メッセージを表示します。
  • C++ の一般的な変数の型
    ここに画像の説明を挿入

ここに画像の説明を挿入

  • 型変換方法
    float myFloat = 3.14f;
    int i1 = (int)myFloat; // 方法一,目前最常使用,但不建议
    int i2 = int(myFloat); // 方法二,很少使用
    int i3 = static_cast<int>(myFloat); // 方法三,最建议使用
    

1.5 演算子

ここに画像の説明を挿入

1.6型

  • 列挙型

    • 列挙型は単なる整数値であり、PieceTypeKing の実際の値は 0 です。
    • PieceType 変数に対して算術演算を実行しようとした場合、または変数を整数として処理しようとした場合、コンパイラは警告またはエラー メッセージを発行します。
    enum PieceType {
          
           
        PieceTypeKing,
        PieceTypeQueen,
        PieceTypeRook,
        PieceTypePawn
    };
    
  • 列挙型メンバーには整数値も指定できます

    • PieceTypeKing の整数値は 1、コンパイラは整数値 2 を PieceTypeQueen に割り当て、PieceTypeRook の値は 10、コンパイラは自動的に値 11 を PieceTypePawn に割り当てます。
    enum PieceType {
          
           
        PieceTypeKing = 1,
        PieceTypeQueen,
        PieceTypeRook = 10,
        PieceTypePawn
    };
    
  • 厳密に型指定された列挙型

    • 上記の列挙型は厳密に型指定されていないため、型安全ではありませんこれらは常に整数データとして解釈されるため、まったく異なる列挙型の列挙値を比較できます。厳密に型指定された列挙型クラスの列挙型は、これらの問題を解決します。
    enum class PieceType {
          
          
        King = 1,
        Queen,
        Rook = 10,
        Pawn
    }
    
    • enum クラスの場合、列挙値の名前は自動的に外側のスコープから外れることはありません。これは、常にスコープ解決演算子を使用することを意味します。
    PieceType piece = PieceType::King;
    
    • デフォルトでは、列挙値の基本型は整数ですが、これは次の方法で変更できます。
    enum class PieceType : unsigned long {
          
          
        King = 1,
        Queen,
        Rook = 10,
        Pawn
    }
    

    タイプセーフでない enum 列挙の代わりに、タイプセーフな enum クラス列挙を使用することをお勧めします。

  • 構造

    • 構造体 (struct) を使用すると、1 つ以上の既存の型を新しい型にカプセル化できます。
    // employeestruct.h
    #pragma once
    
    struct Employee {
          
          
        char firstInitial;
        char lastInitial;
        int employeeNumber;
        int salary;
    }; 
    
    // structtest.cpp
    #include <iostream>
    #include "employeestruct.h"
    
    using namespace std;
    
    int main() {
          
          
        // Create and populate an employee.
        Employee anEmployee;
    
        anEmployee.firstInitial = 'M';
        anEmployee.lastInitial = 'G';
        anEmployee.employeeNumber = 42;
        anEmployee.salary = 80000;
    
        // Output the values of an employee.
        cout << "Employee: " << anEmployee.firstInitial << anEmployee.lastInitial << endl;
        cout << "Number: " << anEmployee.employeeNumber << endl;
        cout << "Salary: $" << anEmployee.salary << endl;
    
        return 0;
    }
    

1.7 条件文

  • if/else ステートメント

    • if ステートメントのかっこで囲まれた式はブール値であるか、評価の結果がブール値である必要があります。
    • 0 は false、ゼロ以外は true
    if (i > 4) {
          
          
        // Do something.
    } else if {
          
          
        // Do something else.
    } else {
          
          
        // Do something else.
    }
    
  • C++17 では if ステートメントに初期化子を含めることができます

    if (<initializer> ; <conditional expression>) ( <body> }
    // 示例:初始化器获得一名雇员,以及检查所检索雇员的薪水是否超出 1000 的条件
          // 只有满足条件才执行 if 语句体
    if (Employee employee = GetEmployee(); employee.salary > 1000) {
          
          
        ...
    }
    
  • switch ステートメント

    • switch ステートメントの式は、整数、整数に変換できる型、列挙型、または厳密に型指定された列挙である必要があり、定数値と比較する必要があります。各定数値は「ケース」を表します。式パターンがこのケースに一致すると、break ステートメントが見つかるまで後続のコード行が実行されます。
    • さらに、デフォルトのケースを指定することができ、式の値に一致するケースが他にない場合、式の値はデフォルトのケースに一致します。
    switch(menuItem) {
          
          
        case OpenMenuItem:
            // Code to open a file
            break;
        case SaveMenuItem:
            // Code to save a file
            break;
        default:
            // Code to give an error message
            break;
    }
    
    • if ステートメントと同様に、C++17 は switch ステートメントでのイニシャライザの使用をサポートしています。
    switch (<initializer>; <expression>) {
          
          <body>}
    
  • 条件演算子

    • 3 つのオペランドを使用するため、三項演算子とも呼ばれます
    // i 大于 2 吗?如果是真的,结果就是 yes,否则结果就是 no
    std::cout << ((i > 2) ? "yes" : "no");
    

1.8 論理比較演算子

  • C++ は式を評価するときに短絡ロジックを使用します。これは、最終結果が決定されると、式の残りの部分は評価されないことを意味します。
    ここに画像の説明を挿入

1.9 機能

  • C++ では、他のコードで関数を使用するには、最初に関数を宣言する必要があります。
    • 関数が特定のファイル内で使用される場合、通常はソース ファイル内で宣言および定義されます。
    • 関数が他のモジュールまたはファイルで使用されることを目的としている場合は、ヘッダー ファイルで関数を宣言し、ソース ファイルで関数を定義するのが一般的です。
    // 函数声明
    void myFunction(int i, char c);
    // 函数定义
    void myFunction(int i, char c) {
          
          
        std::cout << "the value of i is" << i << std::endl;
        std::cout << "the value of c is" << c << std::endl;
    }
    // 函数调用
    myFunction(8,a');
    
  • 関数の戻り値の型の推論
    • この機能を使用するには、戻り値の型として auto を指定する必要があり、コンパイラーは return ステートメントで使用される式に基づいて戻り値の型を推測します。
    auto addNumbers(int number1, int number2) {
          
          
        return number1 + number2;
    }
    
  • 現在の関数の名前
    • すべての関数には、現在の関数の名前を含む事前定義されたローカル変数 _func_ があります。この変数の用途の 1 つはログ記録です。
    int addNumbers(int number1, int number2) {
          
          
        std::cout << "Entering function " << _func_ << std::endl;
        return number1 + number2;
    }
    

1.10 C スタイルの配列

  • C++ では、C スタイルの配列はできる限り避け、代わりに STL の std::array および std::vector を使用する必要があります。
    int myArray[3];
    myArray[0];
    myArray[1];
    myArray[2];
    
    int myArray[3] = {
          
          0};
    int myArray[3] = {
          
          };
    int myArray[] = {
          
          1, 2, 3, 4};
    
    char ticTacToeBoard[3][3];
    ticTacToeBoard[1][1] = 'o';
    

1.11 std::array

  • C++ には、<array> ヘッダー ファイルで定義される固定サイズの特別なコンテナ std::array があります。C スタイルの配列の代わりに std:array を使用すると、以下に示すように多くの利点が得られます
    • それは常に自分自身のサイズを知っています
    • 自動的にポインタに変換されないため、特定の種類のバグが回避されます。
    • 要素を簡単に横断するためのイテレータがあります
    array<int, 3> arr = {
          
          9, 8, 7};
    cout << "Array size = " << arr.size() << endl;
    cout << "2nd element = " << arr[1] << endl;
    

1.12 std::vector

  • 配列のサイズを動的にしたい場合は、std:vector を使用することをお勧めします。新しい要素がベクターに追加されると、ベクターのサイズは自動的に増加します。
    #include <vector> // 包含头文件
    
    vector<int> myVector = {
          
          11, 22};
    myVector.push_back(33); // 向 vector 中动态添加元素
    myVector.push_back(44);
    
    cout << "1st element: " << myVector[0] << endl;
    

1.13 構造化バインディング

  • C++17 では構造化バインディングの概念が導入され、配列、構造体、ペア、またはタプルの要素で初期化された複数の変数の宣言が可能になります。たとえば、次の配列があるとします。
    std::array<int, 3> values = {
          
          112233};
    
  • 3 つの変数 x、y、z を宣言し、後続の配列内の 3 つの値で初期化できます。構造化バインディングには auto キーワードを使用する必要があることに注意してください
    auto [x, y, z] = values;
    
  • 構造化バインディングで宣言された変数の数は、右側の式の値の数と一致する必要があります

1.14 ループ

  • while ループ

    • while ループは、条件式が true と評価される限り、コードのブロックを繰り返し実行します。
    int i = 0;
    while (i < 5) {
          
          
        std::cout << "This is silly." << std::endl;
        ++i;
    }
    
  • do/while ループ

    • コードは最初に 1 回実行され、実行を継続するかどうかを決定する条件チェックが最後に配置されますこのループ バージョンは、コード ブロックを少なくとも 1 回実行したい場合、および複数回実行するかどうかの条件に応じて使用できます。以下のコードは、条件が false であるにもかかわらず 1 回出力します。
    int i = 100;
    do {
          
          
        std::cout << "This is silly." << std::endl;
        ++i;
    } while (i < 5);
    
  • for ループ

    • for ループの構文は、ループの初期式、終了条件、各反復後に実行されるステートメントが確認できるため、一般に単純になります。
    for (int i = 0; i < 5; ++i) {
          
          
        std::cout << "This is silly." << std::endl;
    }
    
  • 範囲ベースの for ループ

    • この種のループを使用すると、コンテナ内の要素に対する便利な反復が可能になり、C スタイルの配列、初期化子リスト、さらに反復子を返す begin() 関数と end() 関数を持つ型にも使用できます: 例: std::array 、std:: ベクトル
    std::array<int, 4> arr = {
          
          1, 2, 3, 4};
    for (int i : arr) {
          
          
        std::cout << i << std::endl;
    }
    

1.15 初期化リスト

  • 初期化リストは <initializer list> ヘッダー ファイルで定義されており、初期化リストを使用すると、可変数のパラメーターを受け取ることができる関数を簡単に作成できます。
  • イニシャライザ リスト クラスは、ベクトルに格納されているオブジェクトのタイプを指定するのと同様に、リスト内の要素のタイプを山括弧で囲んで指定する必要があるテンプレートです。
    #include <iostream>
    #include <initializer_list>
    
    using namespace std;
    
    int makeSum(initializer_list<int> lst) {
          
          
        int total = 0;
        for (int value : lst) {
          
          
            total += value;
        }
        return total;
    }
    
    int main() {
          
          
        int a = makeSum({
          
          1, 2, 3});
        int b = makeSum({
          
          10, 20, 30, 40, 50, 60});
    
        cout << a << endl;
        cout << b << endl;
    
        return 0;
    }
    

2. C++ を深く掘り下げる

2.1 C++ の文字列

  • C++ で文字列を使用するには 3 つの方法があります
    • 1 つは C スタイルで、文字を文字配列として扱います。
    • 1 つは C++ スタイルで、文字列を使いやすい文字列型にカプセル化します。
    • 非標準の共通クラスもあります (第 2 章で紹介)
    #include <string>
    
    string myString = "Hello, World";
    cout << "The value of myString is " << myString << endl;
    cout << "The second letter is " << myString[1] << endl;
    

2.2 ポインタと動的メモリ

  • 動的メモリを使用すると、コンパイル時に可変サイズのデータ​​を使用してプログラムを作成できます。ほとんどの複雑なプログラムは、何らかの方法で動的メモリを使用します。
2.2.1 スタックとヒープ

C++ プログラムのメモリは、スタックとヒープの 2 つの部分に分割されます。

  • スタック (スタックも同じ意味です)

    • スタックはトランプのようなもので、現在の一番上のカードはプログラムの現在のスコープ、通常は現在実行中の関数を表します。現在の関数で宣言されたすべての変数は、最上位のスタック フレーム (つまり、最上位のカード) のメモリを占有します現在の関数 foo() が別の関数 bar() を呼び出す場合、新しいカードが裏返されるため、bar() はそれを実行するための独自のスタック フレームを持ちます。
    • foo() から bar() に渡される引数はすべて、foo() スタック フレームから bar スタック フレームにコピーされます。
    • スタック フレームは、関数ごとに独立したメモリ空間を提供します。変数が foo() スタックで宣言されている場合、特に要求されない限り、bar() 関数を呼び出してもその変数は変更されません。
    • foo() 関数の実行が終了すると、スタックが消え、関数内で宣言されたすべての変数がメモリを占有しなくなります。
    • スタック上にメモリを割り当てる変数では、プログラマが手動でメモリを解放 (削除) する必要はなく、このプロセスは自動的に行われます。
  • スタックフレーム

    • スタック フレームは、現在実行中の関数のためにスタック上に割り当てられた領域 (またはスペース) を指します渡されるパラメータ、戻りアドレス (関数の終了時にジャンプする必要がある)、および関数によって使用されるメモリ ユニット (つまり、関数によってスタック上に格納されるローカル変数) はすべてスタック フレーム内にあります。
    • スタック フレームは通常、新しい関数が呼び出されたときに作成され、関数が返されたときに破棄されます端的に言えば、スタックはスタック フレームで構成されており、関数が呼び出されたときにスタック フレームがスタックにプッシュされ、関数が返されたときにスタック フレームがスタックからポップされます。
  • ヒープ

    • ヒープは、現在の関数やスタック フレームとはまったく関係のないメモリ領域です関数呼び出しで宣言した変数を終了後に保存したい場合は、変数をヒープに置くことができます
    • ヒープの構造はスタックほど複雑ではありません。ヒープはビットの山と考えることができます。プログラムはいつでもヒープに新しいビットを追加したり、ヒープ内の既存のビットを変更したりできます。
    • ヒープに割り当てられたメモリがすべて解放 (削除) されていることを確認する必要があります。スマート ポインターが使用されない限り、このプロセスは自動的に実行されません。
2.2.2 ポインタの使用
  • 明示的にメモリを割り当てることで、あらゆるものをヒープ上に配置できます。たとえば、整数をヒープに配置するには、その整数にメモリを割り当てる必要がありますが、最初にポインタを宣言する必要があります。
    • ポインタは、整数値を指す単なるアドレスです。この値にアクセスするには、ポインタを逆参照する必要があります。
    • 逆参照は、ヒープ内でポインタの矢印の方向に実際の値を探すことと考えることができます。
    • ポインタは逆参照される前に有効でなければなりませんnull または初期化されていないポインターを逆参照すると、不定の動作が発生します
    // 应避免使用未初始化的变量/指针
    int *myIntegerPointer = new int; // 使用 new 操作符分配内存
    int *myIntegerPointer = nullptr; // 如果不希望立即分配内存,可以把它们初始化为空指针
    
  • ヒープ上に新しく割り当てられた整数に値を割り当てます
    *myIntegerPointer = 8; // 没改变指针,只是改变指针指向的内存
    
  • 動的に割り当てられたメモリを使用した後、削除演算子を使用してメモリを解放する必要があります。ポインタが指すメモリを解放した後にポインタが使用されないようにするために、ポインタを nullptr に設定することを推奨します。
    delete myIntegerPointer;
    myIntegerPointer = nullptr;
    
  • ポインタは必ずしもヒープ メモリを指すわけではありません。スタック上の変数や他のポインタへのポインタを宣言することもできます。
    • ポインタが変数を指すようにするには、「アドレス」演算子 & を使用する必要があります。
    int i = 8;
    int *myIntegerPointer = &i;
    
  • ポインタが構造体を指している場合は、まず * を使用してポインタを逆参照し、次に通常の . 構文を使用して構造体のフィールドにアクセスできます。
    Employee *anEmployee = getEmployee();
    cout << (*anEmployee).salary << endl; // 同下等价
    cout << anEmployee->salary << endl; // -> 运算符允许同时对指针解除引用并访问字段
    
2.2.3 動的に割り当てられた配列
  • ヒープを使用して配列を動的に割り当てることもできます。new[] 演算子を使用して配列にメモリを割り当てる
    • 以下の図からわかるように、ポインタ変数はまだスタック上にありますが、動的に作成された配列はヒープ上にあります。
    int arraySize = 8;
    int *myVariableSizedArray = new int[arraySize];
    

ここに画像の説明を挿入

  • メモリが割り当てられたので、myVariableSizedArray を通常のスタックベースの配列として使用します。
    myVariableSizedArray[3] = 2;
    // 使用完这个数组后,应该将其从堆中删除,这样其他变量就可以使用这块内存
    delete[] myVariableSizedArray;
    myVariableSizedArray = nullptr;
    

C では malloc() と free() を避け、new と delete を使用するか、new[] と delete[] を使用してください。

2.2.4 NULL ポインタ変数
void func(char* str) {
    
    cout << "char* version" << endl;}
void func(int i) {
    
    cout <<"int version" << endl;}

int main() {
    
    
    func(NULL); // NULL 不是指针,而等价于 0,所以调用整数版本
    func(nullptr); // 真正的空指针常量 nullptr,调用 char* 版本

    return 0;
}
2.2.5 スマート ポインタ
  • スマート ポインター オブジェクトは、スコープ外になると、たとえば関数の実行が終了した後、自動的にメモリを解放しますC++ には最も重要なスマート ポインターが 2 つあります。

  • std::unique_ptr

    • 通常のポインタと同様ですが、スコープ外になったり削除されたりすると、メモリやリソースが自動的に解放されます。
    • unique_ptr は、それが指すオブジェクトにのみ属します
    • 長所: メモリとリソースは常に解放されます (return ステートメントが実行されたり、例外がスローされた場合でも)
    • unique_ptr を作成する
    /* 
        unique_ptr 是一个通用的智能指针,它可以指向任意类型的内存
        所以它是一个模板,而模板需要用尖括号指定模板参数
        在尖括号中必须指定 unique_ptr 要指向的内存类型
        make_unique 为 C++14 引入
    */
    auto anEmployee = make_unique<Employee>(); // 不再需要调用 delete,因为会自动完成
    unique_ptr<Employee> anEmployee(new Employee); // C++11 标准
    
    • unique_ptr は C スタイルの配列の保存にも使用できます
    auto employees = make_unique<Employee[]>(10);
    cout << "Salary: " << employees[0].salary << endl;
    
  • std::shared_ptr

    • 共有 ptr により、データの分散「所有権」が可能になります。共有 ptr が指定されるたびに、参照カウントが増加し、データにもう 1 人の「所有者」があることが示されます。共有 ptr がスコープ外になると、参照カウントが減ります。参照カウントが 0 の場合は、データに所有者がなくなったことを意味するため、ポインタによって参照されているオブジェクトは解放されます。
    • 共有ptrを作成する
    auto anEmployee = make_shared<Employee>();
    if (anEmployee) {
          
          
        cout << "Salary: " << anEmployee->salary << endl;
    }
    
    • C++17以降、配列もshared_ptrに格納できるようになりました。
    shared_ptr<Employee[]> employees(new Employee[10]);
    cout << "Salary: " << employees[0].salary << endl;
    

2.3 const の複数の使用

2.3.1 const を使用した定数の定義
  • 定数を定義するには #define の代わりに const を使用します
    const int versionNumberMajor = 2;
    const int versionNumberMinor = 1;
    const std::string productName = "Super Hyper Net Modulator";
    
2.3.2 const を使用してパラメータを保護する
void mysteryFunction(const std::string *someString) {
    
    
    *someString ="Test"; // 不允许修改
}

int main() {
    
    
    std::string myString = "The string";
    mysteryFunction(&myString);
    
    return 0;
}

2.4 参考文献

  • 型に & を追加すると、対応する変数が参照であることを示します舞台裏では、実際には元の変数へのポインタです。変数 x と参照変数 xReference は同じ値を指します。一方の値を変更すると、その変更はもう一方の値にも反映されます。
    int x = 42;
    int &xReference = x;
    
2.4.1 参照渡し
  • 通常、変数を関数に渡すときは、値を渡します。関数が整数パラメータを取る場合、実際に渡されるのは整数のコピーであるため、元の変数の値は変更されません。スタック変数内のポインタは、関数が別のスタック フレーム内の変数を変更できるようにするために C で一般的に使用されます。
  • C++ では、ポインタを関数に渡す代わりに、パラメータが参照によって渡されます。
    // 不会影响传递给它的变量,因为变量是按照值传递的
    // 因此函数接收的是传递给它的值的一个副本
    void addOne(int i) {
          
          
        i++;
    }
    // 使用了引用,因此可以改变原始变量的值
    void addOne(int &i) {
          
          
        i++;
    }
    
2.4.2 const参照による渡し
  • 値を関数に渡すと、完全なコピーが作成されます。参照渡しの場合、実際には元のデータへのポインタを渡すだけなので、コピーを作成する必要はありません。const 参照を渡すことで、コピーが必要ないことと、元の変数が変更されないことの両方を実現できます。
    void printString(const std::string &myString) {
          
          
        std::cout << myString << std::endl;
    }
    
    int main() {
          
          
        std::string someString = "Hello world!";
        printString(someString);
        printString("Hello World!");
    
        return 0;
    }
    

2.5 異常

#include <stdexcept>

double divideNumbers(double numerator, double denominator) {
    
    
    if (denominator == 0) {
    
    
        throw invalid_argument("Denominator cannot be 0."); // 函数立刻结束而不会返回值
    }
    return numerator / denominator;
}
// 捕获异常并处理
try {
    
    
    std::cout << divideNumbers(2.50.5) << std::endl; // 返回 5
    std::cout << divideNumbers(2.30) << std::endl; // 抛出异常,不返回值,并直接跳到 catch 块
    std::cout << divideNumbers(4.52.5) << std::endl; // 程序已跳转,该行不执行
} catch (const invalid_argument &exception) {
    
    
    std::cout << "Exception caught:" << exception.what() << std::endl;
}

2.6 型推論

2.6.1 キーワード auto
  • auto を使用すると、コンパイラにコンパイル時に変数の型を自動的に推測するよう指示できます。
  • ただし、auto を使用すると、reference 修飾子と const 修飾子が削除されます。
#include <string>

const std::string message = "Test";
const std::string &foo() {
    
    
    return message;
}
// 因为 auto 去除了引用和 const 限定符,且 f1 是 string 类型,所以建立一个副本
auto f1 = foo();
// 如果不需要副本,可使用 auto& 或 const auto&
const auto &f2 = foo();
2.6.2 decltype
  • キーワード decltype は、式を実際のパラメータとして受け取り、式のタイプを計算します。
    // 编译器推断出 y 的类型是 int,因为这是 x 的类型
    int x = 123;
    decltype(x) y = 456;
    
  • auto と decltype の違いは次のとおりです。
    • decltype は参照と const 修飾子を削除しません
    • f2 は次のように decltype で定義されており、f2 の型は const string& となり、コピーは作成されません。
    decltype(foo()) f2 = foo();
    

3. オブジェクト指向言語としての C++

3.1 クラスを定義する

  • C++ では、通常、クラスはヘッダー ファイル (.h) で宣言され、その非インライン メソッドと静的データ メンバーは対応するソース ファイル (.cpp) で定義されます。
// AirlineTicket.h
#pragma once

#include <string>

class AirlineTicket {
    
    
public:
    AirlineTicket(); // 当创建类的对象时会自动调用构造函数
    ~AirlineTicket(); // 当销毁对象时会自动调用析构函数
    // 最好将不改变对象的任何数据成员的成员函数声明为 const
    double calculatePriceInDollars() const;
    
    const std::string &getPassengerName() const;
    void setPassengerName(const std::string& name);
    
    int getNumberOfMiles() const;
    void setNumberOfMiles(int miles);
    
    bool hasEliteSuperRewardsStatus() const;
    void setHasEliteSuperRewardsStatus(bool status);

private:
    std::string mPassengerName;
    int mNumberOfMiles;
    bool mHasEliteSuperRewardsStatus;
};
  • この定義では、まずクラス名を宣言し、中括弧内でクラスのデータ メンバー (属性) とメソッド (動作) を宣言します。
  • 各データ メンバーとメソッドには、パブリック、プロテクト、プライベートなどの特定のアクセス レベルがあります。これらのトークンは任意の順序で表示でき、再利用できます。
    • public メンバーはクラス外からアクセス可能
    • プライベート メンバーにはクラスの外部からアクセスできません (すべてのデータ メンバーをプライベートとして宣言することをお勧めします。必要に応じて、パブリック リーダーおよびセッターを通じてアクセスできます)。
3.1.1 コンストラクターの初期化
  • 方法 1: コンストラクター初期化子
    AirlineTicket::AirlineTicket() : mPassengerName("Unknown Passenger"),
                                     mNumberOfMiles(0),
                                     mHasEliteSuperRewardsStatus(false) {
          
          }
    
  • 方法 2: 初期化タスクをコンストラクター本体に配置する
    AirlineTicket::AirlineTicket() {
          
          
        mPassengerName = "Unknown Passenger";
        mNumberOfMiles = 0;
        mHasEliteSuperRewardsStatus = false;
    }
    
  • コンストラクターがデータ メンバーを初期化するだけの場合、データ メンバーはクラス定義で直接初期化できるため、実際にはコンストラクターを使用する必要はありません。クラスがファイルを開く、メモリを割り当てるなど、他のタイプの初期化も実行する必要がある場合は、それを処理するコンストラクターを作成する必要があります。
    private:
        std::string mPassengerName = "Unknown Passenger";
        int mNumberOfMiles = 0;
        bool mHasEliteSuperRewardsStatus = false;
    
3.1.2 いくつかのAirlineTicketクラスメソッドの定義
// AirlineTicket.cpp
#include "AirlineTicket.h"
using namespace std;

AirlineTicket::AirlineTicket() : mPassengerName("Unknown Passenger"),
                                     mNumberOfMiles(0),
                                     mHasEliteSuperRewardsStatus(false) {
    
    }
AirlineTicket::~AirlineTicket() {
    
    }

double AirlineTicket::calculatePriceInDollars() const {
    
    
    if (hasEliteSuperRewardsStatus()) {
    
    
        // Elite Super Rewards customers fly for free!
        return 0;
    }

    return getNumberOfMiles() * 0.1;
}

const string &AirlineTicket::getPassengerName() const {
    
    
    return mPassengerName;
}
...

3.2 クラスの使用

// AirlineTicketTest.cpp
#include <iostream>
#include <memory>
#include "AirlineTicket.h"

using namespace std;

int main() {
    
    
    // 1. 基于堆栈的类的使用方法
    AirlineTicket myTicket;
    myTicket.setPassengerName("Sherman T. Socketwrench");
    myTicket.setNumberOfMiles(700);
    double cost = myTicket.calculatePriceInDollars();
    cout << "This ticket will cost $" << cost << endl;
    
    // 2. 基于堆的类的使用方法(使用智能指针)
    auto myTicket2 = make_unique<AirlineTicket>();
    myTicket2->setPassengerName("Laudimore M. Hallidue");
    myTicket2->setNumberOfMiles(2000);
    myTicket2->setHasEliteSuperRewardsStatus(true);
    double cost2 = myTicket2->calculatePriceInDollars();
    cout << "This other ticket will cost $" << cost2 << endl;
    // No need to delete myTicket2, happens automatically
    
    // 3. 基于堆的类的使用方法(不使用智能指针)(不推荐使用该方式)
    AirlineTicket *myTicket3 = new AirlineTicket();
    // ... Use ticket 3
    delete myTicket3;  // delete the heap object!
    
    return 0;
} 

4. 均一な初期化

struct CircleStruct {
    
    
	int x, y;
	double radius;
};

class CircleClass {
    
    
public:
	CircleClass(int x, int y, double radius)
		: mX(x), mY(y), mRadius(radius) {
    
    }
private:
	int mX, mY;
	double mRadius;
};
  • C++11 より前では、構造体とクラスの初期化メソッドは異なっていました。
    CircleStruct myCirclel = {
          
          10102.5};
    CircleClass myCircle2(10102.5);
    
  • C++11 以降では、型を初期化するために {…} 構文を使用できるようになりました。
    // 其中 = 号是可选的
    CircleStruct myCirclel = {
          
          10102.5};
    CircleClass myCircle2 = {
          
          10102.5};
    
  • 均一初期化は、動的に割り当てられた配列の初期化にも使用できます。
int *pArray = new int[4]{
    
    0123};
  • 均一初期化では、コンストラクター初期化子のクラス メンバー配列も初期化できます。
class MyClass {
    
    
public:
    MyClass() : mArray{
    
    0123} {
    
    }
private:
    int mArray[4];
};
  • 直接リストの初期化とコピーリストの初期化
    • コピーリストの初期化: T obj = {arg1, arg2, ...};
      • コピーリスト初期化の場合、中括弧で囲まれた初期化子のすべての要素は同じ型である必要があります。
    • 直接リストの初期化: T obj {arg1, arg2, ...};

5. 最初の便利な C++ プログラム

  • 従業員データベースを作成する

5.1 従業員記録システム

  • 企業の従業員記録を管理するプログラムは柔軟であり、次の機能を含む効率的な機能を備えている必要があります。

    • 従業員を追加する
    • 消防職員
    • 従業員の昇進
    • 過去および現在の全従業員を表示
    • 現在の従業員をすべて表示
    • 元従業員をすべて表示
  • プログラムのコードは 3 つの部分に分かれています

    • Employee クラスは、単一の従業員に関する情報をカプセル化します。
    • データベースクラス運営会社社員全員
    • 個々のユーザー インターフェイス プロバイダーのインターフェイス

5.2 従業員クラス

  • 従業員.h
#pragma once // 防止文件被包含多次
#include <string>

// 自定义 Records 名称空间
namespace Records {
    
    
    const int kDefaultStartingSalary = 30000; // 设置新雇员默认起薪
    
    class Employee {
    
    
    public:
        Employee() = default; // 显式的默认构造函数
        // 包含接收姓名的构造函数
        Employee(const std::string& firstName, const std::string& lastName);
    
        void promote(int raiseAmount = 1000); // 设定了默认值
        void demote(int demeritAmount = 1000); // 设定了默认值
        void hire(); // Hires or rehires the employee
        void fire(); // Dismisses the employee
        void display() const;// Outputs employee info to console
    
        // 提供修改 set 或查询 get 雇员信息的机制
        void setFirstName(const std::string& firstName);
        const std::string& getFirstName() const;
    
        void setLastName(const std::string& lastName);
        const std::string& getLastName() const;
    
        void setEmployeeNumber(int employeeNumber);
        int getEmployeeNumber() const;
    
        void setSalary(int newSalary);
        int getSalary() const;
    
        bool isHired() const;
    
    private:
        std::string mFirstName;
        std::string mLastName;
        int mEmployeeNumber = -1;
        int mSalary = kDefaultStartingSalary;
        bool mHired = false;
    };
}
  • 従業員.cpp
#include <iostream>
#include "Employee.h"

using namespace std;

namespace Records {
    
    
    Employee::Employee(const std::string &firstName, const std::string &lastName)
                      : mFirstName(firstName), mLastName(lastName) {
    
    }
    // 只是用一些新值调用 setSalary() 方法
    // 注意:整型参数的默认值不显示在源文件中,只能出现在函数声明中,不能出现在函数定义中
    void Employee::promote(int raiseAmount) {
    
    
        setSalary(getSalary() + raiseAmount);
    }
    void Employee::demote(int demeritAmount) {
    
    
        setSalary(getSalary() - demeritAmount);
    }
    // 正确设置了 mHired 成员
    void Employee::hire() {
    
    
        mHired = true;
    }
    void Employee::fire() {
    
    
        mHired = false;
    }
    // 使用控制台输出流显示当前雇员的信息
    void Employee::display() const{
    
    
        cout << "Employee: " << getLastName() << ", " << getFirstName() << endl;
        cout << "-------------------------" << endl;
        cout << (isHired() ? "Current Employee" : "Former Employee") << endl;
        cout << "Employee Number: " << getEmployeeNumber() << endl;
        cout << "Salary: $" << getSalary() << endl;
        cout << endl;
    }
    // 许多获取器(get)和设置器(set)执行获取值以及设置值的任务
    // 使用这些获取器和设置器的方式要优于将数据成员设置为 public
    // 1. 方便设置断点,简化调试
    // 2. 修改类中存储数据的方式时,只需修改这些获取器和设置器
    void Employee::setFirstName(const string &firstName) {
    
    
        mFirstName = firstName;
    }
    const string& Employee::getFirstName() const {
    
    
        return mFirstName;
    }
    
    void Employee::setLastName(const string& lastName) {
    
    
        mLastName = lastName;
    }
    
    const string& Employee::getLastName() const {
    
    
        return mLastName;
    }
    
    void Employee::setEmployeeNumber(int employeeNumber) {
    
    
        mEmployeeNumber = employeeNumber;
    }
    
    int Employee::getEmployeeNumber() const {
    
    
        return mEmployeeNumber;
    }
    
    void Employee::setSalary(int salary) {
    
    
        mSalary = salary;
    }
    
    int Employee::getSalary() const {
    
    
        return mSalary;
    }
    
    bool Employee::isHired() const {
    
    
        return mHired;
    } 
}

5.3 データベースクラス

  • データベース.h
#pragma

#include <iostream>
#include <vector>
#include "Employee.h"

namespace Records {
    
    
    const int kFirstEmployeeNumber = 1000;

    class Database {
    
    
    public:
        Employee &addEmployee(const std::string &firstName,
                              const std::string &lastName);
        // 1. 允许按雇员号进行检索
        Employee &getEmployee(int employeeNumber);
        // 2. 要求提供雇员姓名
        Employee &getEmployee(const std::string &firstName,
                              const std::string &lastName);
        // 输出所有雇员、当前在职雇员和离职雇员的方法
        void displayAll() const;
        void displayCurrent() const;
        void displayFormer() const;

    private:
        std::vector<Employee> mEmployees;
        // 跟踪新雇员的雇员号
        int mNextEmployeeNumber = kFirstEmployeeNumber;
    };
}
  • データベース.cpp
#include <iostream>
#include <stdexcept>
#include "Database.h"

using namespace std;

namespace Records {
    
    
    Employee &Database::addEmployee(const string &firstName,
                                    const string &lastName) {
    
    
        // 使用输入参数初始化成员变量
        Employee theEmployee(firstName, lastName);
        // 数据成员 mNextEmployeeNumber 值递增,因此下一个雇员将获得新编号
        theEmployee.setEmployeeNumber(mNextEmployeeNumber++);
        theEmployee.hire(); // 将其聘用状态设置为 "已聘用"
        mEmployees.push_back(theEmployee);
        // 返回 mEmployees 向量中的最后一个元素,即新添加的员工
        return mEmployees[mEmployees.size() - 1];
    }

    Employee &Database::getEmployee(int employeeNumber) {
    
    
        // 基于区间的 for 循环遍历 mEmployees 中所有雇员
        for (auto &employee : mEmployees) {
    
    
            if (employee.getEmployeeNumber() == employeeNumber) {
    
    
                return employee;
            }
        }
        throw logic_error("No employee found.");
    }
    Employee &Database::getEmployee(const string &firstName,
                                    const string &lastName) {
    
    
        for (auto &employee : mEmployees) {
    
    
            if (employee.getFirstName() == firstName &&
                employee.getLastName() == lastName) {
    
    
                    return employee;
            }
        }
        throw logic_error("No employee found.");
    }
    void Database::displayAll() const {
    
    
        for (const auto &employee : mEmployees) {
    
    
            employee.display();
        }
    }
    void Database::displayCurrent() const {
    
    
        for (const auto &employee : mEmployees) {
    
    
            if (employee.isHired()) {
    
    
                employee.display();
            }
        }
    }
    void Database::displayFormer() const {
    
    
        for (const auto &employee : mEmployees) {
    
    
            if (!employee.isHired()) {
    
    
                employee.display();
            }
        }
    }
}

5.4 ユーザーインターフェース

  • ユーザーインターフェイス.cpp
#include <iostream>
#include <stdexcept>
#include <exception>
#include "Database.h"

using namespace std;
using namespace Records;

int displayMenu();
void doHire(Database& db);
void doFire(Database& db);
void doPromote(Database& db);

int main(int argc, char *argv[]) {
    
    
    Database employeeDB;

    bool done = false;
    while (!done) {
    
    
        int selection = displayMenu();
        switch (selection) {
    
    
        case 0:
            done = true;
            break;
        case 1:
            doHire(employeeDB);
            break;
        case 2:
            doFire(employeeDB);
            break;
        case 3:
            doPromote(employeeDB);
            break;
        case 4:
            employeeDB.displayAll();
            break;
        case 5:
            employeeDB.displayCurrent();
            break;
        case 6:
            employeeDB.displayFormer();
            break;
        default:
            cerr << "Unknown command." << endl;
            break;
        }
    }
    return 0;
}

int displayMenu() {
    
    
    int selection;

    cout << endl;
    cout << "Employee Database" << endl;
    cout << "-----------------" << endl;
    cout << "1) Hire a new employee" << endl;
    cout << "2) Fire an employee" << endl;
    cout << "3) Promote an employee" << endl;
    cout << "4) List all employees" << endl;
    cout << "5) List all current employees" << endl;
    cout << "6) List all former employees" << endl;
    cout << "0) Quit" << endl;
    cout << endl;
    cout << "---> ";

    cin >> selection;

    return selection;
}
// 获取用户输入的新的雇员的姓名,并通知数据库添加这个雇员
void doHire(Database &db) {
    
    
    string firstName;
    string lastName;

    cout << "First name?";
    cin >> firstName;
    cout << "Last name?";
    cin >> lastName;

    db.addEmployee(firstName, lastName);
}
// 要求数据库根据雇员号找到雇员的记录
void doFire(Database &db) {
    
    
    int employeeNumber;

    cout << "Employee number? ";
    cin >> employeeNumber;

    try {
    
    
        Employee &emp = db.getEmployee(employeeNumber);
        emp.fire();
        cout << "Employee " << employeeNumber << " terminated." << endl;
    } catch (const std::logic_error &exception) {
    
    
        cerr << "Unable to terminate employee: " << exception.what() << endl;
    }
}

void doPromote(Database& db) {
    
    
    int employeeNumber;
    int raiseAmount;

    cout << "Employee number? ";
    cin >> employeeNumber;
    cout << "How much of a raise? ";
    cin >> raiseAmount;

    try {
    
    
        Employee& emp = db.getEmployee(employeeNumber);
        emp.promote(raiseAmount);
    } catch (const std::logic_error& exception) {
    
    
        cerr << "Unable to promote employee: " << exception.what() << endl;
    }
}

おすすめ

転載: blog.csdn.net/qq_42994487/article/details/131065119