banner
Nanomoa

Nanomoa's Blog

"Bytes and Brackets: My Journey in Go and C++ Exploration."
github
zhihu
telegram
twitter

CS106L 2024年春学期 学習ノート

コースホームページ: CS106L

1. ようこそ#

この授業は主に CS106L コースの簡単な紹介、宿題の割り当て、コース内容の列挙、C++ の発展の歴史の紹介です。

コース紹介:#

このコースは C++ の現代的な特徴を主に紹介し、基本的な文法にはほとんど時間をかけず、一定の言語基礎が必要です。

宿題の割り当て:#

このコースでは 7 つの簡単な宿題があり、現在開放されている宿題は以下の通りです:

宿題番号宿題タイトル宿題リンク
Assignment 1SimpleEnrollこちらから完了
Assignment 2Marriage Pactこちらから完了
Assignment 3Make a class!こちらから完了
Assignment 4Weather Forecastこちらから完了
.........

コース内容:#

番号コース内容
1C++ の特徴の紹介
2初期化、参照、ストリーム
3コンテナ、イテレータ、ポインタ
4クラス、テンプレートクラス、const
5テンプレート関数、関数、lambda
6演算子、特殊メンバー関数
7move セマンティクス、型安全

発展の歴史:#

最初の授業のスライドを参照してください。

2. 型と構造体#

型:#

基本型:#

C++ は以下のいくつかの基本型を提供しています:

int val = 5; //32ビット
char ch = 'F'; //8ビット
float decimalVal1 = 5.0; //32ビット
double decimalVal2 = 5.0; //64ビット
bool bVal = true; //1ビット

string ヘッダーファイルで string 型のサポートが実装されています。

std::string str = "Nanomoa";

C++ は静的型付け言語であり、これはすべての変数 / 関数などが実行前に明示的にその型を宣言する必要があることを意味します。

動的型と静的型の違い:

  • 動的型: コードを行ごとに実行しながら型をチェックし、実行時にエラーが発生する可能性があります。
  • 静的型: 実行前にコンパイル時に型をチェックし、コンパイルエラーが発生すると実行できず、静的型言語のコードは可読性が高くなります。

C++ では同じ名前の関数を複数定義することはできませんが、関数オーバーロードを使用して異なるバージョンを実現できます。

関数オーバーロードは、関数名が同じである必要がありますが、戻り値の型 / 引数リストの引数の型 / 引数リストの引数の数のいずれかが異なる必要があります。

構造体:#

auto キーワードの導入:#

実際の型の代わりに使用し、コンパイラが変数の値に基づいてその型を自動的に推測します。

auto a = 1; // int
auto b = 3.14; // double
auto c = 'a'; // char
auto d = "Hello"; // char*

構造体:#

構造体は一組の変数の集合であり、各フィールドにはそれぞれの型があります。

例:

struct Student {
    std::string name;
    int age;
};

Student stu;
stu.name = "Nanomoa";
stu.age = 20;

構造体は一組の情報をパラメータとして渡したり、戻り値として使用したりできます(構造体を使用することで、関数が間接的に複数の戻り値を持つことができます)。

例:

void printStudentInfo(Student s) {
    std::cout << s.name << " " << s.age << std::endl;
}

Student getStudent() {
    Student s;
    s.name = "Test";
    s.age = 20;
    return s;
}

Student s = getStudent();
printStudentInfo(s);

構造体の初期化:

Student s;
s.name = "A";
s.age = 20;

// または
s = {"A", 20};

std::pair の導入:

std::pair は STL で提供されるテンプレートで、任意の異なる型のペアを受け入れ、2 つのフィールド firstsecond を含みます。

例:

std::pair<int, std::string> val = {1, "AA"};
std::cout << val.first << " " << val.second << std::endl; // "1 AA"

std::pair を使用して、操作状態を持つ戻り値を持つ関数を実装できます:

std::pair<bool, std::string> func() {
    bool is_success = false;
    // todo ...
    if(is_success) {
        return {true, "成功情報"};
    } else {
        return {false, "失敗情報"};
    }
}

std::pair<bool, std::string> res = func();
auto res1 = func();

3. 初期化と参照#

初期化:#

直接初期化:#

方法は以下の通りです:

int valInt = 1;
int valInt1(2);

注意: C++ は初期化に渡される値が整数であるかどうかを気にしません(浮動小数点数を渡すと整数に変換されます)。

例:

int valInt = 3.00; // valInt => 3
int valInt1(4.5); // valInt1 => 4

統一初期化:#

C++11 からサポートされており、より安全で型チェックが行われます。例:

int valInt{1}; // 正しい
int valInt1{3.14}; // コンパイルエラー

使用例:

// std::map
std::map<std::string, int> ages{
    {"Nanomoa", 20},
    {"Test", 21}
};

// std::vector
std::vector<int> vec{1, 2, 3, 4, 5};

// 構造体
struct Student {
    std::string name;
    int age;
};

Student stu;
stu.name = "Nanomoa";
stu.age = 20;
// 以下と同じ
Student s = {"Nanomoa", 20};

構造化バインディング:#

オブジェクトの要素またはメンバーを使用して複数のエンティティを初期化します。C++17 からサポートされています。例:

std::tuple<std::string, std::string, std::string> getClassInfo() {
    std::string className = "CS106L";
    std::string buildingName = "Turing Auditorium";
    std::string language = "C++";
    return {className, buildingName, language};
}

auto classInfo = getClassInfo();
std::string className = std::get<0>(classInfo);
std::string buildingName = std::get<1>(classInfo);
std::string language = std::get<2>(classInfo);
// 以下と同じ(構造化バインディング)
auto [className, buildingName, language] = getClassInfo();

参照:#

参照は既存のものの別名であり、記号 & を使用します。例:

int num = 5;
int& ref = num;
ref = 10;
std::cout << ref << ' ' << num << std::endl; // 10 10

参照を関数のパラメータとして使用する場合も同様の効果があります:

void func(int& n) {
    n = std::pow(n, 2);
}

int main() {
    int num = 2;
    func(num);
    std::cout << num << std::endl; // 4
    return 0;
}
// この関数内のnumsは変更されません
void func(std::vector<std::pair<int, int>> &nums) {
    for (auto [num1, num2] : nums) { // ここで構造化バインディングを使用してnumsの現在のペアの値をコピーしました
        num1++; // ここで変更されるのはコピーされたペアであり、numsのペアではありません
        num2++;
    }
}

// この関数内で nums が変更されます
void func1(std::vector<std::pair<int, int>> &nums) {
    for (auto& [num1, num2] : nums) { // ここで構造化バインディングを使用してnumsの現在のペアを参照しました
        num1++;
        num2++;
    }
}

左値 / 右値:#

  • 左値:等号の左側にも右側にも使用できます。
  • 右値:等号の右側にのみ使用できます。

例:

// x は左値として使用できます
int y = x;
x = 10;
// 10 は右値として使用できます
int y = 10;
10 = x; // エラー

注意: 右値を非 const の左値にバインドすることはできません。例:

int squareN(int& num) {
    return std::pow(num, 2);
}

auto val = squareN(2); // コンパイルエラー

Const:#

const で宣言されたオブジェクトは変更できません。例:

std::vector<int> vec{1, 2, 3}; // 非 const
vec.push_back(4); // 変更可能
std::vector<int>& ref_vec{vec}; // 参照
ref_vec.push_back(5);
const std::vector<int> const_vec{1, 2, 3}; // const
const_vec.push_back(4); // コンパイルエラー、変更不可
const std::vector<int>& ref_const_vec{const_vec}; // const 参照
ref_const_vec.push_back(4); // 変更不可

注意: const で宣言されたオブジェクトに対して非 const の参照を宣言することはできません。例:

const std::vector<int> vec{1, 2, 3};
std::vector<int>& ref_vec{vec}; // エラー
const std::vector<int>& const_ref_vec{vec}; // 正しい

C++ のコンパイル方法:#

g++ -std=c++11 main.cpp -o main

または

g++ -std=c++11 main.cpp

4. ストリーム#

ストリームとは:#

C++ の一般的な入出力の抽象

cin と cout:#

iostream ヘッダーファイルに含まれています。

cerr と clog#
  • cerr: エラーを出力するために使用されます。
  • clog: 非重要なイベントのログを記録するために使用されます。
cout と cin#

std::cout と IO ライブラリ:

std::cout << "Hello, World" << std::endl;

std::coutストリーム です。

std::cout ストリームは std::ostream のインスタンスであり、標準出力ストリームです。

入力ストリーム:

"3.14" -> 入力ストリーム -> 型変換 -> (double)(3.14)

double pi;
std::cin >> pi;
std::cout << pi / 2 << std::endl;

std::cin はコンソールの 入力ストリーム です。

std::cout ストリームは std::istream のインスタンスであり、標準入力ストリームです。

stringstreams:#

std::stringstream#

文字列をストリームとして扱う方法で、混合型データの処理に使用できます。

std::string initial_quote = "Bjarne Stroustrup C makes it easy to shoot yourself in the foot";
std::stringstream ss(initial_quote); // ss << initial_quote; でも可能
std::string first,
            last,
            language,
            extracted_quote;
ss >> first >> last >> language >> extracted_quote;
std::cout << first << " " << last << " " << language << " " << extracted_quote << std::endl;
first => Bjarne 
last => Stroustrup 
language => C 
extracted_quote => makes

extracted_quote => makes , 注意、>> 演算子を使用すると次の空白の前までしか読み取れません。

もし makes の後のすべての内容を読み取る必要がある場合は、getline() を使用する必要があります。例:

std::string initial_quote = "Bjarne Stroustrup C makes it easy to shoot yourself in the foot";

std::stringstream ss(initial_quote);

std::string first,
            last,
            language,
            extracted_quote;
ss >> first >> last >> language;
std::getline(ss, extracted_quote);
std::cout << first << " " << last << " " << language << " " << extracted_quote << std::endl;
first => Bjarne 
last => Stroustrup 
language => C 
extracted_quote => makes it easy to shoot yourself in the foot

出力ストリーム:#

データを書き込む方法の一つです。

double tao = 6.28;
std::cout << tao;

tao -> std::cout ストリーム -> "6.28" -> フラッシュ -> コンソール / 他

std::cout ストリームは ラインバッファ であり、バッファ内の内容は フラッシュ 前にターゲットに表示されません。

ラインバッファ:バッファは '\n' に遭遇したときにフラッシュされます。

例:

std::cout << "Starting intense computation...";
std::this_thread::sleep_for(std::chrono::seconds(5));
std::cout << "finished computation\n";

このコードの効果は、最初に "Starting intense computation..." を出力し、5 秒待ってから "finished computation" を出力することではありません。

むしろ、5 秒待った後に "Starting intense computation..." と "finished computation" が同時に出力されます。

なぜなら、 std::cout << "Starting intense computation..."; はフラッシュされていないからです。

この問題を解決する方法:

std::cout << "Starting intense computation...\n";
// または
std::cout << "Starting intense computation..." << std::endl;
// または
std::cout << "Starting intense computation...";
std::cout << std::flush;

std::ios::sync_with_stdio(false) は C++ の標準ストリーム(例えば cout と cin)と C の標準 IO(例えば printf と scanf)間の同期を解除するために使用され、IO のパフォーマンスを向上させることができます。

ただし注意:同期を無効にした後は C++ ストリームと C の標準 IO を混在させることはできず、そうすると未定義動作を引き起こす可能性があります。

出力ファイルストリーム:#

std::ofstream はファイルにデータを書き込む方法の一つで、一般的なメソッドには is_open()open()close()fail() があります。

例:

std::ofstream ofs("hello.txt"); // hello.txt にストリームを作成し、ファイルが存在しない場合は新しく作成します
if (ofs.is_open()) { // 開いているかどうかを確認
    ofs << "Hello CS106L" << '\n'; // 開いている場合はファイルに内容を書き込みます
}
ofs.close(); // 閉じる、閉じた後は操作できません
ofs.open("hello.txt"); // 開く
ofs << "Open Again" << '\n'; // 内容を書き込みます

私たちが期待するファイルの内容:

Hello CS106L
Open Again

実際には:

Open Again

内容を上書きするのではなく追加する必要がある場合は、次のように開く必要があります:

ofs.open("hello.txt", std::ios::app);

入力ファイルストリーム:#

std::ifstream はファイルから内容を読み取る方法の一つで、使用法は std::ofstream と基本的に同じです。

例:

std::ifstream ifs("hello.txt");
if (ifs.is_open()) {
    std::string line1;
    std::getline(ifs, line1);
    std::cout << line1 << '\n';
}
if (ifs.is_open()) {
    std::string line2;
    std::getline(ifs, line2);
    std::cout << line2 << '\n';
}

入力ストリーム:#

std::istream はデータを読み取る方法の一つです。

std::cinバッファード であり、次の ' ' or '\n' or '\t' の位置まで読み取ります。

例:

/*
3,14 6.28
Nanomoa AAAA
*/
double pi, tao;
std::string name;
std::cin >> pi >> tao;
std::cin >> name;
std::cout << pi << " " << tao << " " << name << '\n';
// 3.14 6.28 Nanomoa

もし name を "Nanomoa AAAA" にしたい場合は、std::getline() を使用する必要があります:

double pi, tao;
std::string name;
std::cin >> pi >> tao;
std::getline(std::cin, name);
std::cout << pi << " " << tao << " " << name << '\n';

しかし、pi と tao を読み取った後、プログラムは終了します。

なぜなら、ここでの std::getline(std::cin, name)tao の後の '\n' を読み取っているからです。

したがって、空白を読み取った後に name を読み取る必要があります:

double pi, tao;
std::string name;
std::cin >> pi >> tao;
std::getline(std::cin, name);
std::getline(std::cin, name);
std::cout << pi << " " << tao << " " << name << '\n';

ただし、std::getlinestd::cin を同時に使用することは推奨されません。なぜなら、二者のデータ解析方法が異なるからです。

どうしても共用する必要がある場合は、std::getlinestd::stringstream と組み合わせて代用することができます:

double pi, tao;
std::string name;
std::string line;
std::getline(std::cin, line);
std::stringstream ss(line);
ss >> pi >> tao;
std::getline(std::cin, line);
name = line;
std::cout << pi << " " << tao << " " << name << std::endl;

5. コンテナ#

(更新中...)

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。