C++ with Windows API講習/関数基本

概要

今回はプログラムを読みやすく、管理しやすくするための重要な機能、関数の解説をします。

重要語

関数

結果を返す、処理のまとまり

関数の宣言

関数の型と名前を定義すること

関数の定義

関数の処理を決めること

引数

関数に渡す値と関数が受け取る値の総称

仮引数

関数に渡す値

実引数

関数が受け取る値又は変数

返り値

関数が返却するデータ

必要語

変数の宣言

変数の型と名前を定義すること

初期化

変数が最初に持つ値の定義

アドレス

メモリ内の位置を表わす数値

ポインタ変数

アドレスを値として持つ変数

間接参照

アドレスを変数のように扱うこと

値渡し

引数をコピーによって渡すこと

ポインタ渡し

引数をアドレスを通じて渡すこと

宣言/定義

まず関数を用意する方法についてです。
以下のサンプルコードを実行してみましょう。
関数基礎
#include <Windows.h>
#include <string>

int plus(int, int);

int wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
    int result = plus(30, 9);
    std::wstring str = std::to_wstring(result);

    MessageBoxW(NULL, str.c_str(), L"30 + 9", MB_OK);
    return 0;
}

int plus(int left, int right) {
    return left + right;
}

関数を使用するには

変数は使う前に宣言していました。これは関数にも同じことが言えます。
ところが、宣言は関数の処理の内容が含まれません。これで関数を使えるのでしょうか。
確かに、プログラム全体としては関数の処理の内容が必要なので、定義は必要です。
ですが、裏を返すと定義はプログラム全体のどこかに存在すれば良いのです。
そのため、宣言を関数の呼び出し前に記述すれば、定義はどこに書いても問題ありません。

宣言

変数同様に、関数も使う前にその名前が関数であることを宣言しなくてはなりません。
宣言には、型名 関数名(引数並び);という構文を用います。
型名は返り値の型、(引数並び)が引数についてを決定します。
宣言は定義とは異なり、同じ関数の宣言を複数記述することが出来ます。
なお、定義の前に書く宣言のことをプロトタイプ宣言と呼ぶことがあります。

定義

関数が行う「処理のまとまり」を宣言とともに記述すると、定義になります。
関数の定義は、型名 関数名(引数並び) { 処理 }という構文を用います。
型名 関数名(引数並び)は宣言と同じものです。
定義は宣言と違い、同じ関数の定義を複数記述することは出来ません。

引数

引数は今までも触れてきましたが、実は2種類に分けられます。
関数の定義の際に、引数として記述した引数並びを「仮引数」と言います。
また、関数を呼び出した際に、引数として渡す変数や値のことを「実引数」と言います。
なお、単に引数と言うときには仮引数と実引数を区別しない場合です。

関数宣言/定義詳細

関数の宣言/定義の詳細な文法についてになります。

返り値

宣言や定義での型名は返り値の型になります。
返り値を返すには、定義での処理においてreturn文を使います。
なお、返り値は値をコピーすることで返却を行います。

return文

前述の通り、返り値を返却するにはreturn文を使用します。
return 式や変数;と記述することで、式の結果や変数の値を返り値として返却します。
ただ、return文は関数の実行を停止するので、以降の処理は実行されません。

関数の名前

関数の名前は、変数の名前と同様にアルファベットと数字、アンダーバーが使用できます。
関数の名前は、関数の処理の内容を表わす名前にするのがよいでしょう。
なお、英単語とアンダーバーで名前を作るのがC++の一般的な命名方法です。

補足 - 変数や関数の名前

C++では、変数や関数の名前のことを識別子と呼びます。
識別子には、アルファベットと数字、アンダーバーが使用できます。
ただし、以下のようにはつけてはいけないと決められています。
1.数字から始まる
2.アンダーバーから始まる
3.ダブルアンダースコアが入っている
2と3はエラーが出るわけではありませんが、使うべきではない名前の付け方です。
なお、ダブルアンダースコアというのはアンダースコアを2つ繋げたものです。

引数

宣言や定義での(引数並び)が引数についてを決定します。
引数並びには、変数の宣言をコンマ区切りで記述します。
関数が呼ばれるとここで宣言する変数は渡された引数で初期化されます。
ただし、宣言では引数並びの変数を使わないので、変数の名前を省略できます。

処理

処理には、関数にさせたい処理をwWinMain関数で記述する時のように書いていきます。
関数の終わりにはreturn文を書きましょう。同時に返り値を返却できます。

コード「関数基礎」解説

コード「関数基礎」の解説です。

int plus(int left, int right);

返り値がint、引数にint,intを取るplus関数の宣言です。
宣言を始めに書くことで、定義を後から記述する方式を使っています。

int plus(int left, int right) {...}

返り値がint、引数にint,intを取るplusという関数の定義です。
見出しでは省略されている処理は、return left + right;となっています。
引数として取った int型のleftrightを足した値をreturn文で返却しています。

関数基本

次に返り値/引数がない関数、ポインタ渡しについてです。
では、サンプルコードを実行してみましょう。
関数基本
#include <Windows.h>
#include <string>

std::wstring ty_str() {
    std::wstring str = L"thank you 39";
    return str;
}

void link_and_add_ty(
    std::wstring* buffer,
    std::wstring* str1,
    std::wstring* str2
) {
    (*buffer) = (*str1) + (*str2) + ty_str();
    return;
}

int wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
    std::wstring str;

    link_and_add_ty(
        &str,
        &(std::wstring)L"hello",
        &(std::wstring)L", planet "
    );

    MessageBox(NULL, str.c_str(), L"result", MB_OK);
    return 0;
}

返り値がない関数

返り値がない関数は、宣言や定義の際の返り値の型名にvoidを記述することで実現します。
返り値がない場合、return文は省略することが出来ます。

引数がない関数

引数がない関数は、宣言や定義の引数並びに何も記述しないことで実現します。
なお、引数並びにvoidを記述することでも同じ事が行えます。

コード「関数基本」解説

コード「関数基本」についての解説です。

ty_str関数

この関数は文字列thank you 39というstd::wstringの変数を作り、返却しています。
引数を取らないので引数並びには何も書きません。

link_and_add_ty関数

この関数は返り値は無いのでvoid、引数並びはstd::wstringのポインタ変数が3つです。
1つ目はbufferで、完成した文字列を受け取る変数のアドレスを渡します。
2/3つ目はそれぞれstr1str2で、連結したいstd::wstringのアドレスを取ります。
処理では、str1str2ty_str関数の返り値の文字列を連結し、bufferに代入しています。

link_and_add_ty関数呼び出し

wWinMain関数内に入ります。
第1引数は純粋に、宣言しておいた変数strのアドレスを渡しています。
第2/3引数はL""の文字列をstd::wstringに変換し、そのアドレスを渡しています。
よって、変数strに関数の結果が代入されることになります

補足 - (std::wstring)L""

第2/3引数の説明で、std::wstringに「変換」と言いました。
確かにキャストの形をしているので、キャストしていると思うかもしれません。
しかし、厳密にはlink_and_add_ty関数の呼び出しが終了すると破棄される
std::wstringの変数を作り、その変数のアドレスを渡しています。

関数の型

関数にも型があります。
次のサンプルコードを実行してみましょう。
関数の型
#include <Windows.h>
#include <string>

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

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

int call(int a, int b, int(*func)(int, int)) {
    return func(a, b);
}

int wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
    std::wstring str;

    str = std::to_wstring(call(3, 9, add));
    str = str + L" " + std::to_wstring(call(128, 64, sub));

    MessageBoxW(NULL, str.c_str(), L"msgboxw", MB_OK);

    return 0;
}

関数の型

関数の型は、宣言から型の情報を抜き出した、型名 (引数並び)となります。
int add(int a,int b)なら、int (int, int)になります。

関数のポインタ型

関数のポインタ型は、関数の型にアスタリスクを付けて、型名(*)(引数並び)となります。
形が変ですね。これは、ポインタのためのアスタリスクが変数名関数名に係るためです。
すなわち、アスタリスクは宣言の型名 関数名(引数並び)関数名の位置に必要です。
ただし型名*(引数並び)とすると、型名*が返り値と認識されるので丸括弧が必要です。

関数へのポインタ

関数へのポインタを宣言する時は、型名(*変数名)(引数並び)となります。
当然ですが、変数名はアスタリスクの後に入ることになっています。
初期化は、関数名が特例としてアドレスを表すため、関数名で行うことが出来ます。
更に、関数を呼び出すときも特例で間接参照をしなくてもよいことになっています。

使い道

関数へのポインタは関数の引数として関数を受け取ることを可能にします。
そのため関数へのポインタは、呼び出し元が処理を決めることを可能にします。

コード「関数の型」解説

コード「関数の型」についての解説です。

call関数

call関数は、intを2つとint(int,int)の関数へのポインタfuncを引数に取ります。
funcから関数を呼び出し、その引数にcall関数が取った引数を渡しています。
返り値は、funcから呼び出した関数が返す値を返却しています。

add/sub関数

add関数とsub関数は、call関数に渡すための関数として用意しています。
それぞれintを2つとり、addは足し算、subは引き算した結果を返却します。

call関数呼び出し

wWinMain関数内に入ります。
call関数は第3引数にint(*)(int,int)を取るので、add関数やsub関数を指定しています。
先述の通り、関数は関数名がアドレスになるので、アドレス演算子は付けていません。

練習問題

以下のプログラムを、次に示すswap関数とmsgboxw関数を記述して完成させてください。
swap関数 2つのintの変数の値を入れ替える
msgboxw関数 第1引数を本文、第2引数をタイトルとしてメッセージボックスに表示する
解答の一部
#include <Windows.h>
#include <string>

void call(int a, int b, void(*func)(int, int)) {
    return func(a, b);
}

int wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
    int a = 12;
    int b = 39;

    call(a, b, msgboxw);

    swap(&a, &b);

    call(a, b, msgboxw);

    return 0;
}
解答
練習問題解答
#include <Windows.h>
#include <string>

void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
    return;
}

void msgboxw(int a, int b) {
    MessageBoxW(
        NULL,
        std::to_wstring(a).c_str(),
        std::to_wstring(b).c_str(),
        MB_OK
    );
    return;
}

void call(int a, int b, void(*func)(int, int)) {
    return func(a, b);
}

int wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) {
    int a = 12;
    int b = 39;

    call(a, b, msgboxw);

    swap(&a, &b);

    call(a, b, msgboxw);

    return 0;
}