C++ で関数を定義して使用する

C++C++Intermediate
オンラインで実践に進む

💡 このチュートリアルは英語版からAIによって翻訳されています。原文を確認するには、 ここをクリックしてください

はじめに

この実験では、C++ の関数の世界を深く掘り下げます。関数はプログラミングの礎石であり、コードを管理可能で再利用可能なブロックに整理することができます。この実験では、さまざまな戻り値の型を持つ関数の作成から、再帰や関数のオーバーロードなどの高度な概念の活用に至るまで、関数に関する幅広いトピックを扱います。関数の定義方法、値渡しと参照渡しによるパラメータの渡し方、デフォルトパラメータ値の設定方法、関数プロトタイプの理解方法について学びます。また、構造体を使って複数の値を返す方法を学びます。この実験が終わるとき、モジュール化され、効率的で構造的に整った C++ コードを書くための堅牢な基礎を築くことができます。

まずは、整数型、浮動小数点数型、文字型、文字列型など、さまざまな戻り値の型を持つ関数を調べます。次に、値渡しと参照渡しによるパラメータの渡し方の重要な違いと、デフォルトパラメータ値が関数にどのように柔軟性をもたらすかを学びます。また、関数のオーバーロードについても説明します。これにより、同じ名前で異なるパラメータリストを持つ複数の関数を定義することができます。さらに、再帰関数を調べます。再帰関数は、自分自身を呼び出すことで問題を解決する強力なツールです。ヘッダーファイルで関数プロトタイプを使ってより整理されたコードを作成する方法を学びます。最後に、構造体を使って関数から複数の値を返す方法を学びます。

これは Guided Lab です。学習と実践を支援するためのステップバイステップの指示を提供します。各ステップを完了し、実践的な経験を積むために、指示に注意深く従ってください。過去のデータによると、この 中級 レベルの実験の完了率は 77%です。学習者から 89% の好評価を得ています。

さまざまな戻り値の型を持つ関数を作成する

関数はプログラミングにおける基本的な概念です。関数を使うことで、大きなプログラムを小さくて管理しやすいピースに分割することができ、コードの作成、理解、保守が容易になります。また、関数は値を返すこともでき、データをプログラムの呼び出し元に戻すことができます。

以下は、C++ における関数の基本構文です。

return_type function_name(parameters) {
    // 関数本体
    return value;
}
  • return_type:これは関数が返す値のデータ型を指定します。関数が値を返さない場合は、キーワード void を使います。
  • function_name:これは関数を呼び出す際に使う名前です。関数の目的を明確に表す名前を選びましょう。
  • parameters:これは関数に渡す入力値です。これらはオプションで、0 個以上のパラメータを持つことができます。
  • value:これは関数が返す値です。これは関数の return_type に一致する必要があります。return_typevoid の場合、return 文を省略するか、単に return; と値なしで使います。

このステップでは、さまざまなデータ型を返す関数を作成する方法を学びます。この機能は、多機能で機能的なプログラムを構築するために重要です。

WebIDE を開き、~/project ディレクトリに新しいファイル function_types.cpp を作成します。

touch ~/project/function_types.cpp

エディタで function_types.cpp ファイルを開き、以下のコードを追加します。

#include <iostream>
#include <string>

// 整数を返す関数
int addNumbers(int a, int b) {
    return a + b;
}

// 倍精度浮動小数点数を返す関数
double calculateAverage(double x, double y) {
    return (x + y) / 2.0;
}

// 文字を返す関数
char getGrade(int score) {
    if (score >= 90) return 'A';
    if (score >= 80) return 'B';
    if (score >= 70) return 'C';
    if (score >= 60) return 'D';
    return 'F';
}

// 文字列型を返す関数
std::string getStudentStatus(bool isEnrolled) {
    return isEnrolled? "Active" : "Inactive";
}

int main() {
    // さまざまな戻り値の型を示す
    int sum = addNumbers(5, 3);
    std::cout << "Sum: " << sum << std::endl;

    double average = calculateAverage(10.5, 20.5);
    std::cout << "Average: " << average << std::endl;

    char grade = getGrade(85);
    std::cout << "Grade: " << grade << std::endl;

    std::string status = getStudentStatus(true);
    std::cout << "Student Status: " << status << std::endl;

    return 0;
}

プログラムをコンパイルして実行します。

g++ function_types.cpp -o function_types
./function_types

出力例:

Sum: 8
Average: 15.5
Grade: B
Student Status: Active

関数の戻り値の型に関する要点:

  • 関数はさまざまなデータ型を返すことができ、コードに柔軟性をもたらします。
  • return_type は関数名の前に指定され、関数が返すデータの種類を示します。
  • return 文は宣言された return_type と一致しなければなりません。そうでない場合、コンパイラはエラーを報告します。
  • void 関数は値を返さないので、return; を使うか、void 関数の末尾で return 文を単に省略します。
  • プリミティブ型(intdoublechar など)、文字列、さらにはユーザー定義型を返すことができます。
  • 関数の戻り値の型を、さまざまな種類のコンテナと考えてみてください。箱、袋、かごを使ってさまざまな品物を運ぶのと同じように、関数はさまざまな種類のデータを返すためにさまざまな戻り値の型を使います。

値渡しによって関数にパラメータを渡す

このステップでは、C++ において値渡しによって関数にパラメータを渡す方法について学びます。値渡しでパラメータを渡すとき、元の引数のコピーが作られ、そのコピーが関数内で使用されます。関数内でパラメータに対して行われた変更は、関数外の元の変数には影響しません。

WebIDE を開き、~/project ディレクトリに新しいファイル pass_by_value.cpp を作成します。

touch ~/project/pass_by_value.cpp

エディタで pass_by_value.cpp ファイルを開き、以下のコードを追加します。

#include <iostream>

// 値渡しを示す関数
void modifyValue(int x) {
    // この変更はローカルコピーのみに影響し、元の変数には影響しません
    x = x * 2;
    std::cout << "関数内 - x の値:" << x << std::endl;
}

int main() {
    // 元の変数
    int number = 10;

    // 元の値を表示
    std::cout << "関数呼び出し前 - 元の値:" << number << std::endl;

    // 元の変数を使って関数を呼び出す
    modifyValue(number);

    // 元の変数は変更されず
    std::cout << "関数呼び出し後 - 元の値:" << number << std::endl;

    return 0;
}

プログラムをコンパイルして実行します。

g++ pass_by_value.cpp -o pass_by_value
./pass_by_value

出力例:

関数呼び出し前 - 元の値: 10
関数内 - xの値: 20
関数呼び出し後 - 元の値: 10

値渡しをさらに明確にするために別の例を作りましょう。

touch ~/project/swap_values.cpp

swap_values.cpp に以下のコードを追加します。

#include <iostream>
#include <string>

// 値を交換する関数(値渡しでは期待通りに機能しません)
void swapValues(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
    std::cout << "関数内 - a: " << a << ", b: " << b << std::endl;
}

int main() {
    int first = 5;
    int second = 10;

    std::cout << "交換前 - first: " << first << ", second: " << second << std::endl;

    // 交換関数を呼び出す
    swapValues(first, second);

    // 元の変数は変更されず
    std::cout << "交換後 - first: " << first << ", second: " << second << std::endl;

    return 0;
}

2 番目のプログラムをコンパイルして実行します。

g++ swap_values.cpp -o swap_values
./swap_values

出力例:

交換前 - first: 5, second: 10
関数内 - a: 10, b: 5
交換後 - first: 5, second: 10

値渡しに関する要点:

  • 引数のコピーが関数に渡され、元の値が保持されます。
  • 関数内でパラメータに対して行われた変更は、関数外の元の変数には影響しません。
  • 元の値を変更する必要がない場合、intcharfloat などの単純なデータ型に対して値渡しが適しています。
  • 大きなオブジェクトを値渡しする場合、コピーによるメモリ消費と時間のかかる処理が行われるため、参照渡しに比べて効率が悪くなります。

値渡しを、文書のコピーを作ることのように考えることができます。元の文書は変更されず、コピーを変更しても元の文書には影響しません。

&演算子を使って参照渡しを実装する

このステップでは、C++ で & 演算子を使って参照渡しによって関数にパラメータを渡す方法を学びます。参照渡しによると、関数に元の変数への直接的なアクセスを与えることができます。つまり、関数内でパラメータに対して行われた変更は、関数外の元の変数を直接変更します。

WebIDE を開き、~/project ディレクトリに新しいファイル pass_by_reference.cpp を作成します。

touch ~/project/pass_by_reference.cpp

エディタで pass_by_reference.cpp ファイルを開き、以下のコードを追加します。

#include <iostream>

// 参照渡しを示す関数
void swapValues(int& a, int& b) {
    // 直接元の変数を変更する
    int temp = a;
    a = b;
    b = temp;
}

// 参照を使って値を変更する関数
void incrementValue(int& x) {
    // 直接元の変数を増やす
    x++;
}

int main() {
    // 交換の例
    int first = 5;
    int second = 10;

    std::cout << "交換前 - first: " << first << ", second: " << second << std::endl;

    // 参照を使って交換関数を呼び出す
    swapValues(first, second);

    std::cout << "交換後 - first: " << first << ", second: " << second << std::endl;

    // 増分の例
    int number = 7;

    std::cout << "増分前:" << number << std::endl;

    // 参照を使って増分関数を呼び出す
    incrementValue(number);

    std::cout << "増分後:" << number << std::endl;

    return 0;
}

プログラムをコンパイルして実行します。

g++ pass_by_reference.cpp -o pass_by_reference
./pass_by_reference

出力例:

交換前 - first: 5, second: 10
交換後 - first: 10, second: 5
増分前: 7
増分後: 8

参照渡しの威力を示す別の例を作りましょう。

touch ~/project/string_reference.cpp

string_reference.cpp に以下のコードを追加します。

#include <iostream>
#include <string>

// 参照を使って文字列を変更する関数
void appendText(std::string& text) {
    text += " - Modified";
}

int main() {
    std::string message = "Hello, World!";

    std::cout << "元のメッセージ:" << message << std::endl;

    // 参照を使って直接文字列を変更する
    appendText(message);

    std::cout << "変更後のメッセージ:" << message << std::endl;

    return 0;
}

2 番目のプログラムをコンパイルして実行します。

g++ string_reference.cpp -o string_reference
./string_reference

出力例:

元のメッセージ: Hello, World!
変更後のメッセージ: Hello, World! - Modified

参照渡しに関する要点:

  • 型の後に & を使って参照パラメータを作成します(例:int& a)。
  • 値渡しのようにコピーを使うのではなく、直接元の変数を変更します。
  • 大きなオブジェクトのコピーにかかるオーバーヘッドを回避し、複雑なデータ型に対してより効率的です。
  • 元の値を変更する必要がある場合や、コピーせずに複雑なデータ型を扱う場合に便利です。

参照渡しを、コピーではなく元の文書を誰かに渡すことのように考えることができます。彼らが行う変更はすべて、直接元に影響します。

関数のパラメータにデフォルト値を設定する

このステップでは、C++ において関数のパラメータにデフォルト値を設定する方法を学びます。デフォルトパラメータを使うと、関数の引数にデフォルト値を割り当てることができ、関数をより多用途で使いやすくすることができます。呼び出し元がデフォルト値を持つパラメータを省略した場合、デフォルト値が自動的に使用されます。

WebIDE を開き、~/project ディレクトリに新しいファイル default_parameters.cpp を作成します。

touch ~/project/default_parameters.cpp

エディタで default_parameters.cpp ファイルを開き、以下のコードを追加します。

#include <iostream>
#include <string>

// デフォルトパラメータを持つ関数
void greetUser(std::string name = "Guest", int age = 0) {
    std::cout << "Hello, " << name << "!" << std::endl;

    if (age > 0) {
        std::cout << "You are " << age << " years old." << std::endl;
    }
}

// 計算用のデフォルトパラメータを持つ別の例
double calculateArea(double length = 1.0, double width = 1.0) {
    return length * width;
}

int main() {
    // 引数なしで関数を呼び出す(デフォルト値を使用)
    greetUser();

    // 一部の引数で関数を呼び出す
    greetUser("Alice");

    // すべての引数で関数を呼び出す
    greetUser("Bob", 25);

    // デフォルトパラメータを使った面積計算を示す
    std::cout << "\n面積計算:" << std::endl;

    // デフォルトの面積 (1x1)
    std::cout << "デフォルトの面積:" << calculateArea() << std::endl;

    // 1 つのパラメータでの面積
    std::cout << "長さ 5 の面積:" << calculateArea(5.0) << std::endl;

    // 両方のパラメータでの面積
    std::cout << "長さ 5 で幅 3 の面積:" << calculateArea(5.0, 3.0) << std::endl;

    return 0;
}

プログラムをコンパイルして実行します。

g++ default_parameters.cpp -o default_parameters
./default_parameters

出力例:

Hello, Guest!
Hello, Alice!
Hello, Bob!
You are 25 years old.

面積計算:
デフォルトの面積: 1
長さ5の面積: 5
長さ5で幅3の面積: 15

デフォルトパラメータに関する要点:

  • デフォルト値は関数宣言で、パラメータ型の直後に指定します(例:int age = 0)。
  • デフォルト値を持つパラメータは、パラメータリストの末尾になければなりません。デフォルトパラメータの後に非デフォルトパラメータを置くことはできません。
  • 関数をより少ない引数で呼び出すことができ、欠けている引数はデフォルト値を使用します。
  • デフォルト値は柔軟性を提供し、わずかに異なる入力で同じことをする複数の関数のオーバーロードの必要性を減らします。

もう 1 つの例を作って、より複雑なデフォルトパラメータを示しましょう。

touch ~/project/student_info.cpp

student_info.cpp に以下のコードを追加します。

#include <iostream>
#include <string>

// 複数のデフォルトパラメータを持つ関数
void printStudentInfo(
    std::string name = "Unknown",
    int age = 0,
    std::string grade = "N/A",
    bool isEnrolled = false
) {
    std::cout << "Name: " << name << std::endl;
    std::cout << "Age: " << age << std::endl;
    std::cout << "Grade: " << grade << std::endl;
    std::cout << "Enrollment Status: " << (isEnrolled? "Enrolled" : "Not Enrolled") << std::endl;
}

int main() {
    // 関数を呼び出すさまざまな方法
    printStudentInfo();  // すべてのデフォルト
    std::cout << "\n";
    printStudentInfo("John");  // 名前のみ
    std::cout << "\n";
    printStudentInfo("Alice", 20);  // 名前と年齢
    std::cout << "\n";
    printStudentInfo("Bob", 22, "A", true);  // すべてのパラメータ

    return 0;
}

2 番目のプログラムをコンパイルして実行します。

g++ student_info.cpp -o student_info
./student_info

出力例:

Name: Unknown
Age: 0
Grade: N/A
Enrollment Status: Not Enrolled

Name: John
Age: 0
Grade: N/A
Enrollment Status: Not Enrolled

Name: Alice
Age: 20
Grade: N/A
Enrollment Status: Not Enrolled

Name: Bob
Age: 22
Grade: A
Enrollment Status: Enrolled

デフォルトパラメータを、あなたがすべての品物を選ぶことも、いくつかだけ選ぶこともできるビュッフェのように考えることができます。指定しなければ、いくつかの品物は自動的に埋められます。

異なるパラメータを持つ関数のオーバーロードを作成する

このステップでは、C++ における関数のオーバーロードについて学びます。これは、パラメータリストが異なる(パラメータの数または型が異なる場合)限り、同じ名前の複数の関数を作成できる機能です。この機能により、同じ関数名を使って異なるデータ型や入力数に対して同様の操作を行うことができ、コードをより柔軟で読みやすくします。

WebIDE を開き、~/project ディレクトリに新しいファイル function_overloads.cpp を作成します。

touch ~/project/function_overloads.cpp

エディタで function_overloads.cpp ファイルを開き、以下のコードを追加します。

#include <iostream>
#include <string>

// 異なる型の数値を加算する関数のオーバーロード
int add(int a, int b) {
    std::cout << "2 つの整数を加算中" << std::endl;
    return a + b;
}

double add(double a, double b) {
    std::cout << "2 つの倍精度浮動小数点数を加算中" << std::endl;
    return a + b;
}

// パラメータの数が異なる関数のオーバーロード
int add(int a, int b, int c) {
    std::cout << "3 つの整数を加算中" << std::endl;
    return a + b + c;
}

// パラメータの型が異なる関数のオーバーロード
std::string add(std::string a, std::string b) {
    std::cout << "文字列を連結中" << std::endl;
    return a + b;
}

int main() {
    // 異なるオーバーロード関数を呼び出す
    std::cout << "整数の加算:" << add(5, 3) << std::endl;
    std::cout << "倍精度浮動小数点数の加算:" << add(5.5, 3.7) << std::endl;
    std::cout << "3 つの整数の加算:" << add(1, 2, 3) << std::endl;
    std::cout << "文字列の連結:" << add("Hello, ", "World!") << std::endl;

    return 0;
}

プログラムをコンパイルして実行します。

g++ function_overloads.cpp -o function_overloads
./function_overloads

出力例:

2つの整数を加算中
整数の加算: 8
2つの倍精度浮動小数点数を加算中
倍精度浮動小数点数の加算: 9.2
3つの整数を加算中
3つの整数の加算: 6
文字列を連結中
文字列の連結: Hello, World!

もう 1 つの例を作って、さらに多くの関数のオーバーロードを示しましょう。

touch ~/project/overloading_examples.cpp

overloading_examples.cpp に以下のコードを追加します。

#include <iostream>
#include <string>

// オーバーロードされた print 関数
void print(int value) {
    std::cout << "整数を表示中:" << value << std::endl;
}

void print(double value) {
    std::cout << "倍精度浮動小数点数を表示中:" << value << std::endl;
}

void print(std::string value) {
    std::cout << "文字列を表示中:" << value << std::endl;
}

// オーバーロードされた calculate 関数
int calculate(int a, int b) {
    return a + b;
}

double calculate(double a, double b) {
    return a * b;
}

int main() {
    // 関数のオーバーロードを示す
    print(42);
    print(3.14);
    print("Hello, Overloading!");

    std::cout << "整数の計算:" << calculate(5, 3) << std::endl;
    std::cout << "倍精度浮動小数点数の計算:" << calculate(5.5, 3.2) << std::endl;

    return 0;
}

2 番目のプログラムをコンパイルして実行します。

g++ overloading_examples.cpp -o overloading_examples
./overloading_examples

出力例:

整数を表示中: 42
倍精度浮動小数点数を表示中: 3.14
文字列を表示中: Hello, Overloading!
整数の計算: 8
倍精度浮動小数点数の計算: 17.6

関数のオーバーロードに関する要点:

  • 関数は、パラメータリストが異なる(パラメータの型または数が異なる場合)限り、同じ名前を持つことができます。
  • C++ コンパイラは、関数呼び出し時に提供される引数の型と数に基づいて、どのオーバーロード関数を呼ぶかを決定します。
  • 関数のオーバーロードにより、コードがより読みやすく直感的になり、同様の操作に対して名前を再利用できます。
  • 関数を戻り値の型だけで異なるようにオーバーロードすることはできません。オーバーロードが正しく機能するには、パラメータが異なる必要があります。

関数のオーバーロードを、さまざまな種類のデバイスと対応する万能リモコンのように考えることができます。同じボタン(関数名)は、コンテキスト(提供する引数)に応じて異なることを行います。

階乗計算のための再帰関数を書く

このステップでは、階乗計算プログラムを実装することで再帰関数について学びます。再帰は、関数が自身を呼び出して問題を解決するプログラミング手法です。無限ループを避けるために、再帰関数は再帰呼び出しを停止するベースケースと、ベースケースに向かって自身を繰り返し呼び出す再帰ケースを持たなければなりません。

WebIDE を開き、~/project ディレクトリに新しいファイル factorial_recursive.cpp を作成します。

touch ~/project/factorial_recursive.cpp

エディタで factorial_recursive.cpp ファイルを開き、以下のコードを追加します。

#include <iostream>

// 階乗を計算する再帰関数
unsigned long long calculateFactorial(int n) {
    // ベースケース:0 または 1 の階乗は 1
    if (n == 0 || n == 1) {
        return 1;
    }

    // 再帰ケース:n! = n * (n-1)!
    return n * calculateFactorial(n - 1);
}

int main() {
    // さまざまな数の階乗を計算する
    int numbers[] = {0, 1, 5, 7, 10};

    for (int num : numbers) {
        unsigned long long result = calculateFactorial(num);
        std::cout << num << "の階乗は:" << result << std::endl;
    }

    return 0;
}

プログラムをコンパイルして実行します。

g++ factorial_recursive.cpp -o factorial_recursive
./factorial_recursive

出力例:

0の階乗は: 1
1の階乗は: 1
5の階乗は: 120
7の階乗は: 5040
10の階乗は: 3628800

もう少し詳細な再帰関数を持つ別の例を作りましょう。

touch ~/project/factorial_steps.cpp

factorial_steps.cpp に以下のコードを追加します。

#include <iostream>

// 段階的な出力を伴う再帰関数
unsigned long long calculateFactorialWithSteps(int n, int depth = 0) {
    // 可視化のためのインデントを追加
    std::string indent(depth * 2,'');

    // ベースケース:0 または 1 の階乗は 1
    if (n == 0 || n == 1) {
        std::cout << indent << "ベースケース:factorial(" << n << ") = 1" << std::endl;
        return 1;
    }

    // 可視化付きの再帰ケース
    std::cout << indent << "factorial(" << n << ") を計算中" << std::endl;

    // 再帰呼び出し
    unsigned long long subResult = calculateFactorialWithSteps(n - 1, depth + 1);

    // 結果を結合
    unsigned long long result = n * subResult;

    std::cout << indent << "factorial(" << n << ") = "
              << n << " * factorial(" << n-1 << ") = "
              << result << std::endl;

    return result;
}

int main() {
    int number = 5;

    std::cout << 5 << "の階乗の計算手順:" << std::endl;
    unsigned long long result = calculateFactorialWithSteps(number);

    std::cout << "\n最終結果:" << number << "! = " << result << std::endl;

    return 0;
}

2 番目のプログラムをコンパイルして実行します。

g++ factorial_steps.cpp -o factorial_steps
./factorial_steps

出力例:

5の階乗の計算手順:
factorial(5)を計算中
  factorial(4)を計算中
    factorial(3)を計算中
      factorial(2)を計算中
        factorial(1)を計算中
        ベースケース: factorial(1) = 1
      factorial(2) = 2 * factorial(1) = 2
    factorial(3) = 3 * factorial(2) = 6
  factorial(4) = 4 * factorial(3) = 24
factorial(5) = 5 * factorial(4) = 120

最終結果: 5! = 120

再帰関数に関する要点:

  • 再帰関数は自身を呼び出す:関数は同じ型の小さな部分問題を解決するために自身を呼び出します。
  • ベースケース:再帰関数は再帰呼び出しを停止する条件であるベースケースを持たなければなりません。ベースケースがない場合、関数は無限に自身を呼び出し、スタックオーバーフローエラーにつながります。
  • 再帰ケース:再帰ケースは関数がベースケースに向かって自身を呼び出す部分です。
  • 問題の分解:再帰は複雑な問題を小さくて簡単な部分問題に分解し、管理しやすくします。
  • スタックの使用:各再帰呼び出しはプログラムの呼び出しスタックにスペースを占有します。深い再帰はスタックオーバーフローを引き起こす可能性があるので、呼び出しスタックの制限に注意してください。
  • 適した問題:再帰は、木の探索、分割統治法、フラクタル生成など、小さな似た部分問題で自然に定義できる問題に特に適しています。

再帰を、それぞれがより小さな自分自身のバージョンを含む一連のネストされたマトリョーシカのように考えることができます。最内側のマトリョーシカはネストプロセスを停止するベースケースを表しています。

ヘッダーファイルで関数プロトタイプを使用する

このステップでは、関数プロトタイプと、C++ で関数を整理および宣言するためにヘッダーファイルをどのように使用するかについて学びます。関数プロトタイプは、関数の実装(関数の本体)を提供することなく、関数の名前、戻り値の型、およびパラメータを宣言します。ヘッダーファイルを使用することで、関数のインターフェイス(宣言)とその実装を分離することができます。この分離により、コードの整理が改善され、プログラムの複数の部分や複数のプログラムで関数を維持して再利用することが容易になります。

WebIDE を開き、~/project ディレクトリに 3 つのファイルを作成します。

まず、math_functions.h を作成します。

touch ~/project/math_functions.h

math_functions.h に以下のコードを追加します。

#ifndef MATH_FUNCTIONS_H
#define MATH_FUNCTIONS_H

// 数学演算の関数プロトタイプ
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
double divide(double a, double b);

#endif // MATH_FUNCTIONS_H

.h ファイルは宣言に使用され、関数プロトタイプやその他の宣言を含みますが、実装は含まれません。このようにして、関数を宣言して実装を行わないことができます。#ifndef#define、および #endif ディレクティブはインクルードガードと呼ばれ、同じヘッダーファイルの多重インクルードを防ぎ、エラーを引き起こす可能性があります。

次に、math_functions.cpp を作成します。

touch ~/project/math_functions.cpp

math_functions.cpp に以下のコードを追加します。

#include "math_functions.h"

// 関数の実装
int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

int multiply(int a, int b) {
    return a * b;
}

double divide(double a, double b) {
    // ゼロ割り算をチェック
    if (b == 0) {
        return 0;
    }
    return a / b;
}

この .cpp ファイルには、ヘッダーファイルで宣言された関数の実際の実装が含まれています。

最後に、main.cpp を作成します。

touch ~/project/main.cpp

main.cpp に以下のコードを追加します。

#include <iostream>
#include "math_functions.h"

int main() {
    // ヘッダーファイルからの関数呼び出しを示す
    int x = 10, y = 5;

    std::cout << "加算:" << x << " + " << y << " = " << add(x, y) << std::endl;
    std::cout << "減算:" << x << " - " << y << " = " << subtract(x, y) << std::endl;
    std::cout << "乗算:" << x << " * " << y << " = " << multiply(x, y) << std::endl;
    std::cout << "除算:" << x << " / " << y << " = " << divide(x, y) << std::endl;

    return 0;
}

この main.cpp ファイルは、関数プロトタイプを利用可能にする math_functions.h ヘッダーファイルを含んでいます。そして、math_functions.cpp で実装された関数を使用することができます。

複数のソースファイルを使用してプログラムをコンパイルします。

g++ math_functions.cpp main.cpp -o math_operations
./math_operations

出力例:

加算: 10 + 5 = 15
減算: 10 - 5 = 5
乗算: 10 * 5 = 50
除算: 10 / 5 = 2

もう少し複雑なヘッダーファイルを持つ別の例を作りましょう。

calculator.h を作成します。

touch ~/project/calculator.h

calculator.h に以下のコードを追加します。

#ifndef CALCULATOR_H
#define CALCULATOR_H

class Calculator {
public:
    // 電卓演算の関数プロトタイプ
    int add(int a, int b);
    int subtract(int a, int b);
    int calculate(int a, int b, char operation);
};

#endif // CALCULATOR_H

このヘッダーファイルは、Calculator と呼ばれるクラスとそのパブリックメソッドを宣言しています。

calculator.cpp を作成します。

touch ~/project/calculator.cpp

calculator.cpp に以下のコードを追加します。

#include "calculator.h"

int Calculator::add(int a, int b) {
    return a + b;
}

int Calculator::subtract(int a, int b) {
    return a - b;
}

int Calculator::calculate(int a, int b, char operation) {
    switch (operation) {
        case '+': return add(a, b);
        case '-': return subtract(a, b);
        default: return 0;
    }
}

この calculator.cpp は、calculator.h ヘッダーファイルで宣言されたメソッドの実装を提供しています。

calculator_main.cpp を作成します。

touch ~/project/calculator_main.cpp

calculator_main.cpp に以下のコードを追加します。

#include <iostream>
#include "calculator.h"

int main() {
    Calculator calc;

    std::cout << "電卓演算:" << std::endl;
    std::cout << "10 + 5 = " << calc.calculate(10, 5, '+') << std::endl;
    std::cout << "10 - 5 = " << calc.calculate(10, 5, '-') << std::endl;

    return 0;
}

このメインファイルは Calculator クラスを使用して演算を行います。

電卓プログラムをコンパイルします。

g++ calculator.cpp calculator_main.cpp -o calculator
./calculator

出力例:

電卓演算:
10 + 5 = 15
10 - 5 = 5

関数プロトタイプとヘッダーファイルに関する要点:

  • ヘッダーファイル(.h) は関数プロトタイプ、クラス、およびその他の宣言を行います。利用可能な関数を示すインターフェイスとして機能します。
  • ソースファイル(.cpp) はヘッダーファイルで宣言された関数の実際の実装を行います。関数がどのように機能するかのコードを含んでいます。
  • #ifndef#define、および #endif(インクルードガード)は、同じヘッダーファイルの多重インクルードを防ぎ、潜在的なコンパイルエラーを回避します。
  • ヘッダーファイルを使用することで、モジューラリティとコードの再利用性が向上します。
  • ヘッダーファイルを使うことで、「何をするか」(宣言)と「どのようにするか」(実装)を分離することができます。
  • コードの整理を容易にし、保守と理解を促進します。

ヘッダーファイルをレストランのメニューのように考えることができます。メニュー(ヘッダー)は利用可能なものをリストし、キッチン(ソースファイル)は実際の料理を用意します。

構造体を使って複数の値を返す

このステップでは、C++ で関数から複数の値を返すために構造体をどのように使用するかを学びます。構造体は、ユーザー定義のデータ型で、異なる型のデータを単一の名前の下にグループ化することができます。構造体は、関数から複数の関連する値を構造化された形で返すのに理想的です。

WebIDE を開き、~/project ディレクトリの student_info.cpp ファイルを更新します。

既存のコードを以下のコードに置き換えます。

#include <iostream>
#include <string>

// 学生情報を保持する構造体を定義する
struct StudentResult {
    std::string name;
    int score;
    bool passed;
};

// 構造体を使って複数の値を返す関数
StudentResult evaluateStudent(std::string name, int testScore) {
    StudentResult result;
    result.name = name;
    result.score = testScore;
    result.passed = (testScore >= 60);

    return result;
}

// 複数の統計値を計算する関数
struct MathStats {
    int sum;
    double average;
    int minimum;
    int maximum;
};

MathStats calculateArrayStats(int numbers[], int size) {
    MathStats stats;
    stats.sum = 0;
    stats.minimum = numbers[0];
    stats.maximum = numbers[0];

    for (int i = 0; i < size; i++) {
        stats.sum += numbers[i];

        if (numbers[i] < stats.minimum) {
            stats.minimum = numbers[i];
        }

        if (numbers[i] > stats.maximum) {
            stats.maximum = numbers[i];
        }
    }

    stats.average = static_cast<double>(stats.sum) / size;

    return stats;
}

int main() {
    // 学生評価を示す
    StudentResult student1 = evaluateStudent("Alice", 75);
    StudentResult student2 = evaluateStudent("Bob", 45);

    std::cout << "学生の結果:" << std::endl;
    std::cout << student1.name << " - 点数:" << student1.score
              << ", 合格:" << (student1.passed? "はい" : "いいえ") << std::endl;
    std::cout << student2.name << " - 点数:" << student2.score
              << ", 合格:" << (student2.passed? "はい" : "いいえ") << std::endl;

    // 配列の統計を示す
    int numbers[] = {5, 2, 9, 1, 7};
    int size = sizeof(numbers) / sizeof(numbers[0]);

    MathStats stats = calculateArrayStats(numbers, size);

    std::cout << "\n配列の統計:" << std::endl;
    std::cout << "合計:" << stats.sum << std::endl;
    std::cout << "平均:" << stats.average << std::endl;
    std::cout << "最小値:" << stats.minimum << std::endl;
    std::cout << "最大値:" << stats.maximum << std::endl;

    return 0;
}

プログラムをコンパイルして実行します。

g++ student_info.cpp -o student_info
./student_info

出力例:

学生の結果:
Alice - 点数: 75, 合格: はい
Bob - 点数: 45, 合格: いいえ

配列の統計:
合計: 24
平均: 4.8
最小値: 1
最大値: 9

構造体と複数の値の返却に関する要点:

  • 構造体は関連するデータをグループ化する:異なる型(例えば、stringintbool)の変数を単一のユニットにグループ化することができ、関連する情報がある場合に便利です。
  • 複雑なデータ型を返す:構造体を使うことで、関数から複雑なデータ型を返すことができ、関連するデータのセットを管理して渡すことが容易になります。
  • 整理された返却値:構造体を返すことで、複数の関連する返却値を整理することができ、コードをクリーンに保ちやすくなります。
  • 構造化された情報を渡す明確な方法を提供する:構造体は、複数の関連する値を返すための明確な名前付きのコンテナを提供し、コードの読みやすさと理解しやすさを向上させます。

構造体を、別々の区画にさまざまな種類の食べ物を入れることができる複数区画のランチボックスのように考えることができます。

まとめ

この実験では、C++ で関数を定義および使用する方法について包括的な理解を得ました。さまざまな戻り値の型を持つ関数を作成し、値渡しと参照渡しによるパラメータの渡し方、デフォルトパラメータ値の設定、関数のオーバーロードの利用方法を学びました。また、再帰関数、ヘッダーファイルで関数プロトタイプを使用する方法、および構造体を使って複数の値を返す方法を探りました。

カバーされた主な概念は以下の通りです。

  • 関数の戻り値の型:関数は異なるデータ型を返すことができ、return 文は宣言された戻り値の型と一致する必要があることを学びました。
  • 値渡しと参照渡し:値渡し(コピーを作成すること)と参照渡し(元の変数を使用すること)によるパラメータの渡し方の違いを理解しました。
  • デフォルトパラメータ:関数パラメータにデフォルト値を割り当てることで関数をより汎用的にする方法を学びました。
  • 関数のオーバーロード:同じ名前で異なるパラメータリストを持つ複数の関数を定義する方法を見て、コードをより読みやすく直感的にしました。
  • 再帰関数:再帰関数の力を探りました。再帰関数は、小さな部分問題を解決するために自分自身を呼び出します。また、無限再帰を防ぐためのベースケースの重要性も学びました。
  • 関数プロトタイプとヘッダーファイル:ヘッダーファイルを使って関数宣言を整理し、モジューラリティとコードの再利用性を促進する方法を学びました。
  • 構造体を使った複数の値の返却:構造体を使って関数から複数の関連する値を返す方法を見つけました。構造体はデータのための整理されたコンテナを提供します。

これらの概念を身につけることで、よりモジューラーで効率的で構造的な C++ コードを書く準備ができており、より高度なプログラミング技術を探求するための堅牢な基礎を築きました。