C++ における関数の定義と使用

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

はじめに

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

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

異なる戻り値の型を持つ関数を作成する

関数はプログラミングの基本的な概念です。これにより、大きなプログラムを小さく管理しやすい部分に分割でき、コードの記述、理解、保守が容易になります。関数は値を返すこともでき、データをプログラムの呼び出し元に渡すことができます。

C++ における関数の基本的な構文は次のとおりです。

return_type function_name(parameters) {
    // Function body
    return value;
}
  • return_type: 関数が返す値のデータ型を指定します。関数が値を返さない場合は、キーワード void を使用します。
  • function_name: 関数を呼び出すために使用する名前です。関数の目的を明確に説明する名前を選択してください。
  • parameters: 関数に渡す入力値です。これらはオプションであり、ゼロ個以上のパラメータを持つことができます。
  • 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(浮動小数点数)を返す関数
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 関数は値を返さず、void 関数の末尾にいる場合は return; を使用するか、単に 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 << "Inside function - Value of x: " << x << std::endl;
}

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

    // 元の値を表示
    std::cout << "Before function call - Original value: " << number << std::endl;

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

    // 元の変数は変更されないままです
    std::cout << "After function call - Original value: " << number << std::endl;

    return 0;
}

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

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

出力例:

Before function call - Original value: 10
Inside function - Value of x: 20
After function call - Original value: 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 << "Inside function - a: " << a << ", b: " << b << std::endl;
}

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

    std::cout << "Before swap - first: " << first << ", second: " << second << std::endl;

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

    // 元の変数は変更されないままです
    std::cout << "After swap - first: " << first << ", second: " << second << std::endl;

    return 0;
}

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

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

出力例:

Before swap - first: 5, second: 10
Inside function - a: 10, b: 5
After swap - first: 5, second: 10

値渡しに関する重要なポイント:

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

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

&演算子を使用した参照渡し(Pass by Reference)の実装

このステップでは、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() {
    // swap の例
    int first = 5;
    int second = 10;

    std::cout << "Before swap - first: " << first << ", second: " << second << std::endl;

    // 参照で swap 関数を呼び出す
    swapValues(first, second);

    std::cout << "After swap - first: " << first << ", second: " << second << std::endl;

    // increment の例
    int number = 7;

    std::cout << "Before increment: " << number << std::endl;

    // 参照で increment 関数を呼び出す
    incrementValue(number);

    std::cout << "After increment: " << number << std::endl;

    return 0;
}

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

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

出力例:

Before swap - first: 5, second: 10
After swap - first: 10, second: 5
Before increment: 7
After increment: 8

参照渡し pass by reference の力を実証するために、別の例を作成しましょう。

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 << "Original message: " << message << std::endl;

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

    std::cout << "Modified message: " << message << std::endl;

    return 0;
}

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

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

出力例:

Original message: Hello, World!
Modified message: Hello, World! - Modified

参照渡し pass by reference に関する重要なポイント:

  • 参照パラメータを作成するには、型 の後に & を使用します(例: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 << "\nArea calculations:" << std::endl;

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

    // パラメータが 1 つある場合の面積
    std::cout << "Area with length 5: " << calculateArea(5.0) << std::endl;

    // 両方のパラメータがある場合の面積
    std::cout << "Area with length 5 and width 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.

Area calculations:
Default area: 1
Area with length 5: 5
Area with length 5 and width 3: 15

デフォルトパラメータに関する重要なポイント:

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

より複雑なデフォルトパラメータを実証するために、別の例を作成しましょう。

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 << "Adding two integers" << std::endl;
    return a + b;
}

double add(double a, double b) {
    std::cout << "Adding two doubles" << std::endl;
    return a + b;
}

// パラメータの数が異なる関数オーバーロード
int add(int a, int b, int c) {
    std::cout << "Adding three integers" << std::endl;
    return a + b + c;
}

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

int main() {
    // 異なるオーバーロードされた関数を呼び出す
    std::cout << "Integer addition: " << add(5, 3) << std::endl;
    std::cout << "Double addition: " << add(5.5, 3.7) << std::endl;
    std::cout << "Three integer addition: " << add(1, 2, 3) << std::endl;
    std::cout << "String concatenation: " << add("Hello, ", "World!") << std::endl;

    return 0;
}

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

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

出力例:

Adding two integers
Integer addition: 8
Adding two doubles
Double addition: 9.2
Adding three integers
Three integer addition: 6
Concatenating strings
String concatenation: Hello, World!

関数オーバーロードをさらに実証するために、別の例を作成しましょう。

touch ~/project/overloading_examples.cpp

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

#include <iostream>
#include <string>

// オーバーロードされた print 関数
void print(int value) {
    std::cout << "Printing integer: " << value << std::endl;
}

void print(double value) {
    std::cout << "Printing double: " << value << std::endl;
}

void print(std::string value) {
    std::cout << "Printing string: " << 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 << "Integer calculation: " << calculate(5, 3) << std::endl;
    std::cout << "Double calculation: " << calculate(5.5, 3.2) << std::endl;

    return 0;
}

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

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

出力例:

Printing integer: 42
Printing double: 3.14
Printing string: Hello, Overloading!
Integer calculation: 8
Double calculation: 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 << "Factorial of " << num << " is: " << result << std::endl;
    }

    return 0;
}

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

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

出力例:

Factorial of 0 is: 1
Factorial of 1 is: 1
Factorial of 5 is: 120
Factorial of 7 is: 5040
Factorial of 10 is: 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 << "Base case: factorial(" << n << ") = 1" << std::endl;
        return 1;
    }

    // 可視化を伴う再帰ケース
    std::cout << indent << "Calculating 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 << "Factorial calculation steps for " << number << ":" << std::endl;
    unsigned long long result = calculateFactorialWithSteps(number);

    std::cout << "\nFinal result: " << number << "! = " << result << std::endl;

    return 0;
}

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

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

出力例:

Factorial calculation steps for 5:
Calculating factorial(5)
  Calculating factorial(4)
    Calculating factorial(3)
      Calculating factorial(2)
        Calculating factorial(1)
        Base case: 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

Final result: 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 << "Addition: " << x << " + " << y << " = " << add(x, y) << std::endl;
    std::cout << "Subtraction: " << x << " - " << y << " = " << subtract(x, y) << std::endl;
    std::cout << "Multiplication: " << x << " * " << y << " = " << multiply(x, y) << std::endl;
    std::cout << "Division: " << 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

出力例:

Addition: 10 + 5 = 15
Subtraction: 10 - 5 = 5
Multiplication: 10 * 5 = 50
Division: 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 << "Calculator Operations:" << 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

出力例:

Calculator Operations:
10 + 5 = 15
10 - 5 = 5

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

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

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

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

このステップでは、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 << "Student Results:" << std::endl;
    std::cout << student1.name << " - Score: " << student1.score
              << ", Passed: " << (student1.passed ? "Yes" : "No") << std::endl;
    std::cout << student2.name << " - Score: " << student2.score
              << ", Passed: " << (student2.passed ? "Yes" : "No") << std::endl;

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

    MathStats stats = calculateArrayStats(numbers, size);

    std::cout << "\nArray Statistics:" << std::endl;
    std::cout << "Sum: " << stats.sum << std::endl;
    std::cout << "Average: " << stats.average << std::endl;
    std::cout << "Minimum: " << stats.minimum << std::endl;
    std::cout << "Maximum: " << stats.maximum << std::endl;

    return 0;
}

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

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

出力例:

Student Results:
Alice - Score: 75, Passed: Yes
Bob - Score: 45, Passed: No

Array Statistics:
Sum: 24
Average: 4.8
Minimum: 1
Maximum: 9

構造体と複数値の返却に関する重要なポイント:

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

構造体は、異なる種類の食品を別々のセクションに保持できる、複数のコンパートメントを持つランチボックスのようなものだと考えることができます。

まとめ

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

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

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

これらの概念を習得することで、よりモジュール化され、効率的で、構造化された C++ コードを書くための十分な準備が整い、より高度なプログラミング技術を探求するための強固な基盤を得ることができました。