前章(第4章)では、C++のメモリモデルの核心である「ポインタ」について学びました。ポインタは強力ですが、構文が複雑になりがちで、バグの温床にもなりえます。
C++では、C言語から受け継いだポインタに加え、より安全で直感的な「参照(Reference)」という概念が導入されています。本章では、関数の設計を通して、この「参照」がいかに強力な武器になるかを学びます。「データをどう渡すか」は、C++のパフォーマンスと設計の良し悪しを決める最も重要な要素の一つです。
PythonやJavaScriptのような言語では、関数をどこに書いても(あるいは実行時に解決されて)呼び出せることが多いですが、C++のコンパイラはコードを上から下へと一直線に読みます。そのため、「使用する前に、その関数が存在すること」をコンパイラに知らせる必要があります。
関数を main 関数の後に定義したい場合、事前に「こういう名前と引数の関数がありますよ」と宣言だけしておく必要があります。これをプロトタイプ宣言と呼びます。
#include <iostream>
// プロトタイプ宣言
// 戻り値の型 関数名(引数の型1 引数名1, 引数の型2 引数名2, ...);
// 本体({}の中身)は書かず、セミコロンで終わる
void greet(int times);
int main() {
std::cout << "main関数開始" << std::endl;
// 定義は下にあるが、宣言があるので呼び出せる
greet(3);
return 0;
}
// 関数の定義
void greet(int times) {
for (int i = 0; i < times; ++i) {
std::cout << "Hello C++!" << std::endl;
}
}main関数開始 Hello C++! Hello C++! Hello C++!
実際の開発では、プロトタイプ宣言をヘッダーファイル(.h)に書き、定義をソースファイル(.cpp)に書くことで、大規模なプログラムを管理します(これについては次章で詳しく解説します)。
関数が何も値を返す必要がない場合もあります。例えば、「画面にメッセージを表示するだけ」といった関数です。その場合、戻り値の型として void という特別なキーワードを使います。
void printMessage(std::string message);
第2章で学んだように、intやdoubleなどの型は変数を定義するために使えましたが、voidは「型がない」ことを示す特殊な型なので、void my_variable; のように変数を定義することはできません。あくまで関数の戻り値の型としてのみ使います。
ここが本章のハイライトです。他の言語では言語仕様として決まっていることが多い引数の渡し方を、C++ではプログラマが意図的に選択できます。
特に何も指定しない場合のデフォルトです。変数のコピーが作成され、関数に渡されます。
#include <iostream>
// 値渡し:xは呼び出し元のコピー
void attemptUpdate(int x) {
x = 100; // コピーを変更しているだけ
std::cout << "関数内: " << x << " (アドレス: " << &x << ")" << std::endl;
}
int main() {
int num = 10;
std::cout << "呼び出し前: " << num << " (アドレス: " << &num << ")" << std::endl;
attemptUpdate(num);
// numは変わっていない
std::cout << "呼び出し後: " << num << std::endl;
return 0;
}呼び出し前: 10 (アドレス: 0x7ff...) 関数内: 100 (アドレス: 0x7ff...) <-- アドレスが違う=別の領域(コピー) 呼び出し後: 10
C言語からある手法です。第4章で学んだポインタ(アドレス)を渡します。
& を付ける必要がある。関数内で * や -> を使う必要があり、構文が汚れる。nullptr チェックが必要になることがある。#include <iostream>
// ポインタ渡し:アドレスを受け取る
void updateByPointer(int* ptr) {
if (ptr != nullptr) {
*ptr = 200; // アドレスの指す先を書き換える
}
}
int main() {
int num = 10;
// アドレスを渡す
updateByPointer(&num);
std::cout << "ポインタ渡し後: " << num << std::endl;
return 0;
}ポインタ渡し後: 200
C++の真骨頂です。「参照(Reference)」とは、既存の変数に別の名前(エイリアス)をつける機能です。引数の型に & を付けるだけで宣言できます。
*や&を呼び出し側で意識しなくていい)。nullptr になることがないため安全性が高い。#include <iostream>
// 参照渡し:引数に & をつける
// ref は呼び出し元の変数の「別名」となる
void updateByRef(int& ref) {
ref = 300; // 普通の変数のように扱えるが、実体は呼び出し元
}
int main() {
int num = 10;
// 値渡しと同じように呼び出せる(&num と書かなくていい!)
updateByRef(num);
std::cout << "参照渡し後: " << num << std::endl;
return 0;
}参照渡し後: 300
これがC++で最も頻繁に使われるパターンです。「コピーはしたくない(重いから)。でも、関数内で書き換えられたくもない」という要求を満たします。
const 型& 引数名std::string、std::vector、クラスのオブジェクトなど、サイズが大きくなる可能性があるデータ。#include <iostream>
#include <string>
#include <vector>
// const参照渡し
// textの実体はコピーされないが、書き換えも禁止される
void printMessage(const std::string& text) {
// text = "Modified"; // コンパイルエラーになる
std::cout << "Message: " << text << std::endl;
}
int main() {
std::string bigData = "This is a potentially very large string...";
// コピーコストゼロで渡す
printMessage(bigData);
return 0;
}Message: This is a potentially very large string...
ガイドライン:
intやdoubleなどの基本型 → 値渡し でOK。- 変更させたいデータ → 参照渡し (
T&)。- 変更しないがサイズが大きいデータ(string, vectorなど) → const参照渡し (
const T&)。
C++には関数をより柔軟に使うための機能が備わっています。
引数の型や数が異なれば、同じ名前の関数を複数定義できます。C言語では関数名はユニークである必要がありましたが、C++では「名前+引数リスト」で区別されます。
#include <iostream>
#include <string>
// int型を受け取る関数
void print(int i) {
std::cout << "Integer: " << i << std::endl;
}
// double型を受け取る関数(同名)
void print(double d) {
std::cout << "Double: " << d << std::endl;
}
// 文字列を受け取る関数(同名)
void print(const std::string& s) {
std::cout << "String: " << s << std::endl;
}
int main() {
print(42);
print(3.14);
print("Overloading");
return 0;
}Integer: 42 Double: 3.14 String: Overloading
引数が省略された場合に使われるデフォルト値を設定できます。これはプロトタイプ宣言(または最初にコンパイラが見る定義)に記述します。 ※デフォルト引数は後ろの引数から順に設定する必要があります。
#include <iostream>
// power: 指数を省略すると2乗になる
// verbose: 詳細出力を省略するとfalseになる
int power(int base, int exponent = 2, bool verbose = false) {
int result = 1;
for (int i = 0; i < exponent; ++i) {
result *= base;
}
if (verbose) {
std::cout << base << " の " << exponent << " 乗を計算しました。" << std::endl;
}
return result;
}
int main() {
std::cout << power(3) << std::endl; // 3^2, verbose=false
std::cout << power(3, 3) << std::endl; // 3^3, verbose=false
std::cout << power(2, 4, true) << std::endl; // 2^4, verbose=true
return 0;
}9 27 2 の 4 乗を計算しました。 16
&) は、ポインタのような効率性を持ちながら、変数のエイリアスとして直感的に扱える。const 参照渡し (const T&) は、大きなデータを「読み取り専用」で効率的に渡すC++の定石である。2つの int 変数を受け取り、その値を入れ替える関数 mySwap を作成してください。
ポインタではなく、参照渡しを使用してください。
#include <iostream>
// ここにmySwap関数を実装してください
// main関数
int main() {
int a = 10;
int b = 20;
std::cout << "Before: a = " << a << ", b = " << b << std::endl;
mySwap(a, b);
std::cout << "After: a = " << a << ", b = " << b << std::endl;
return 0;
}(期待される実行結果) Before: a = 10, b = 20 After: a = 20, b = 10
std::vector<int> を受け取り、その中の「最大値」を見つけて返す関数 findMax を作成してください。
ただし、以下の条件を守ってください。
0 を返すなどの処理を入れてください。#include <iostream>
#include <vector>
#include <algorithm> // maxを使うなら便利ですが、for文でも可
// ここに findMax を作成
int main() {
std::vector<int> data = {10, 5, 8, 42, 3};
std::cout << "Max: " << findMax(data) << std::endl;
return 0;
}Max: 42