学生がゲーム制作で使える小技集
はじめに
・このスクラップでは、個人的に「わざわざ紹介する程でも無いけど割と便利だと思う」小さい技をどんどんまとめていきます。
・各表題についている☆の数は、多ければ多いほど「難しいかも」という意味で使用しています。(難易度表記は全て筆者の独断と偏見です)
目次(タイトルを押すとその記事へ飛びます)
※zenn側の仕様?により別タブが開きます。新たにタブを開きたくない場合は
①タイトルの文字をコピー(ctrl + c)
②ページ内検索(ctrl + f → タイトルをペースト(ctrl + v))
で各自検索をお願いします。
【☆】
値の上限下限処理にはstd::minやstd::maxを使おう!
フラグ反転(bool型)は1行で書ける!
if文の条件式部分を短くしてみよう!
0~Nで値をループさせる時は余り算を使おう!
名前を付ける時は意味のある名前を付けよう!
else ifを使いこなそう!
【☆☆】
値の上限と下限を同時に制限するならclamp処理を使おう!
絶対値「abs」関数を活用しよう!
ローカル変数を上手に活用しよう!
割り算は極力避けよう!
構造体を上手に使いこなそう!
std::swapを使って値を入れ替えよう!
処理時間計測プログラムを組んでみよう!
constを使いこなそう!(const初級編)
【☆☆☆】
早期リターンやcontinueを使いこなそう!
用途ごとに細かく関数を作ろう!
用途ごとに細かく関数を作ろう!その2
switch文処理は配列でどうにかなるかも!?
メンバ"関数"にもconstが付けられる?!(const中級編)
【☆☆☆☆】
コンテナを全走査するなら「範囲for」を使おう!
関数の引数に配列を渡す場合はstd::arrayを使おう!
関数から複数の値を一括で受け取るためには?
【☆☆☆☆☆】
【☆】値の上限下限処理にはstd::minやstd::maxを使おう!
if(mHp < 0){
mHp = 0;
}
と書くよりかは…
mHp = std::max(mHp,0);
のがスマートかも!
参考リンク:
【☆☆】値の上限と下限を同時に制限するならclamp処理を使おう!
if(mHp < 0)
{
mHp = 0;
}
else if(mHp > 100)
{
mHp = 100;
}
と書くよりかは…
mHp = std::clamp(mHp,0,100);
のがスマートかも!
参考リンク:
c++17以前のバージョンの場合は…
std::clampは、c++17から実装された関数なので、皆さんの環境では使えないかもです。
その場合は、
int Clamp(int val, int low, int high){
return std::min(std::max(low, val), high);
}
のように自分で作ってしまえば便利かと思われます!
【☆】フラグ反転(bool型)は1行で書ける!
if(flag == true){
flag = false;
}
else if(flag == false){
flag = true;
}
と書きたいなら…
flag = !flag;
で済む!
参考リンク:
【☆☆☆☆】コンテナを全走査するなら「範囲for」を使おう!
std::vector<int> vec;
// vecには要素が入っているとして…
for(int i = 0; i < vec.size();i++){
vec[i] = 0;
}
と書くなら…
for(int& data : vec){
data = 0;
}
と書いた方が便利かも!
参考リンク:
範囲forのどこが便利?
・書く量が通常のforより少なくて楽。
・「コンテナを全走査している」というのが明確なコードになる。
・通常のforより速度が速い?(諸説あり)
といった点で、範囲forって便利だなぁと考えています。
【☆☆】絶対値「abs」関数を活用しよう!
例えば点Aと点Bの距離を求めたい時…
if(a < b){
dis = b - a;
}
else{
dis = a - b;
}
と書くかもしれませんが…
dis = abs(a - b);
と書くことも可能です!
参考リンク:
距離計算についてのお話
「距離」を計算する場合、値がマイナス値になってはいけません。
(例えば「目的地まであと-5km」というのは変ですよね?)
そのため、距離計算のように「値を必ず+にしなければならない」場合にはabs関数が便利!というお話でした。
【☆】if文の条件式部分を短くしてみよう!
例えば…
if(flag == true)
↓
if(flag)
if(flag == false)
↓
if(!flag)
で書ける!
【☆☆☆】早期リターンやcontinueを使いこなそう!
for(auto& enemy: mEnemys)
{
if(enemy->IsDead() == true)
{
mkillCount++;
enemy->Finalize();
}
}
と書くならば、
for(auto& enemy: mEnemys)
{
if(enemy->IsDead() == false) continue;
mkillCount++;
enemy->Finalize();
}
と書いた方がいいかも!
なぜ?
インデントが深くなるとコードが読みずらくなるからです。
【☆☆】ローカル変数を上手に活用しよう!
例えばこんな処理…
// プレイヤーと敵の座標とサイズを元に円と円の当たり判定を取る。
if(
Collision::CircleVsCircle(
mPlayer->GetPos(), mPlayer->GetSize(),
mEnemy->GetPos(), mEnemy->GetSize()
)
)
{
// 当たっていた場合はプレイヤーの位置にエフェクトを出す。
this->SpawnHitEffect(mPlayer->GetPos());
}
分かりずらい! そんな時は!
// プレイヤーと敵の座標とサイズを取得する
auto playerPos = mPlayer->GetPos();
auto playerSize = mPlayer->GetSize();
auto enemyPos = mEnemy->GetPos();
auto enemySize = mEnemy->GetSize();
// プレイヤーと敵の当たり判定(円と円)をとる。
bool isHit = Collision::CircleVsCircle(playerPos, playerSize, enemyPos, enemySize);
// 当たっていた場合はプレイヤーの位置にエフェクトを出す。
if(isHit){
this->SpawnHitEffect(playerPos);
}
のようにローカル変数を活用すると分かりやすいかも!
※今回は説明の都合上autoを使っていますが、実際にはInt2やVector3など、しっかり型名を書いた方がいいです。
【☆】0~Nで値をループさせる時は余り算を使おう!
例えば0から5で値をループさせる時は
mCount++;// mCountはint型
mCount %= 6;
と書けば0,1,2,3,4,5,0,1,2…とループします!
なぜ0から5なのに「6」で割る?
もし「5」で割ってしまった場合、
…
3 % 5 = 3
4 % 5 = 4
5 % 5 = 0
6 % 5 = 1
…
のように、値が5まで届かず0に戻ってしまいます。
そのため割る数に+1して、値が5まで行ってから0に戻るようにしています。
1~Nで値をループさせたければ?
mCount %= 5;
mCount++;
という書き方もできます!
【☆☆☆☆】関数の引数に配列を渡す場合はstd::arrayを使おう!
例えばこんなコードがあったとしましょう。
void PrintArray(const int* arr) {
for (int i = 0; i < 5; i++) {
std::cout << arr[i] << std::endl;
}
}
int main()
{
int array[5] = { 0,1,2,3,4 };
PrintArray(array);
}
何が危険?
・PrintArray関数側には「要素数は5」という情報が渡っていないため、簡単に配列オーバーを引き起こせてしまう。
・そもそも「int*」で受け取るだけだから配列先頭アドレスなのかポインタ変数なのか分からない。
今回のように「関数の引数に配列を渡したい」場合は、こう書いておきましょう。
void PrintArray(const std::array<int, 5>& arr) {
for (int a : arr) {
std::cout << a << std::endl;
}
}
int main()
{
std::array<int, 5> array = { 0,1,2,3,4 };
PrintArray(array);
}
こうすることで、明確に要素数5の配列を渡していると確定させることができます!
std::arrayを使う恩恵は他にもあるのでぜひ調べてみて下さい!
【☆☆☆】用途ごとに細かく関数を作ろう!
例えばこんな処理…
mPlayer->SetHp(mPlayer->GetHp() - 5);
これぱっと見で何してるかって分かりずらいですよね…
でもこれなら↓
mPlayer->ReceiveDamage(5);
void Player::ReceiveDamage(int damage){
mHp -= damage;
}
改善コードの方が「プレイヤーがダメージを受けた」というのが分かりやすいのではないでしょうか?
このように、関数名から処理を察せられるコードが書けるといいんじゃないかなと思います!
【☆☆☆】用途ごとに細かく関数を作ろう!その2
例えばこんな処理…
if(mPlayer->GetHp() == 0){
mPlayer->Finalize();
}
if文の条件式が、若干長くて分かりずらいですね。
一応「HPが0の場合…つまり死んだときにFinalize関数を呼ぶのか。」と推測できるコードではありますが、もっと分かりやすくできます。
if(mPlayer->IsDead()){
mPlayer->Finalize();
}
bool Player::IsDead(){
return (mHp == 0);
}
こうした方が「死んでたらFinalizeを呼んでる」というのが分かりやすいのではないでしょうか?
このように、コメントを書かなくても、関数名を見ればある程度処理が推測できるようにしておくと便利かなと思います!
【☆】名前を付ける時は意味のある名前を付けよう!
例えば「HPを初期値にする」時は…
#define START_HP (100)
~~~
mHp = START_HP;
↑のような定数名ならいいのですが、たまに↓のような恐ろしいコードを見かけます。
#define ONE_HUNDERD (100)
~~~
mHp = ONE_HUNDERD;
「…そんなんマジックナンバーと変わらんやないかい」と思いますよね。これでは定数にする意味がありません。
変数名や定数名、関数名にはしっかり「意味のある名前」をつけましょうね!!!
【☆☆☆☆】関数から複数の値を一括で受け取るためには?
①std::tupleを使う
サンプルコード(少し長いので畳んであります)
#include <tuple>
std::tuple<int, float, const char*> GetMultiDatas() {
int conbo = 0;
float hp = 55.5f;
const char* name = "プレイヤー";
return{ conbo,hp,name };
}
int main()
{
auto datas = GetMultiDatas();
std::cout << std::get<0>(datas) << " " << std::get<1>(datas) << " " << std::get<2>(datas);
}
②参照型で受け取った引数を変更させる。
サンプルコード(少し長いので畳んであります)
void SetMultiDatas(int& conbo, float& hp, const char*& name) {
conbo = 0;
hp = 11.1f;
name = "エネミー";
}
int main()
{
int conbo = 0;
float hp = 0.0f;
const char* name = {};
SetMultiDatas(conbo, hp, name);
std::cout << conbo << " " << hp << " " << name;
}
【☆☆】割り算は極力避けよう!
例えば値を半分にしたいのであれば
val /= 2.0f;
ではなく
val *= 0.5f;
と書きましょう!
理由は「割り算は処理が重たいから」です。同じく余り算も重たいので乱用は避けた方がいいかも。
【☆☆】構造体を上手に使いこなそう!
例えばこんな仕様があるとしよう。
プレイヤーが5人いる。
彼らには「スコア」と「名前」が与えられる。
この時、「スコア」が一番高いプレイヤーの「名前」を表示するプログラムを記述して下さい。
…という指示が与えられた時、どんなコードを組むでしょうか。
例えばこんなコードを書くかもしれません。
ダメコード(ちょっと長いので畳んであります)
std::array<int, 5> score{};
std::array<const char*, 5> name = { "サトウ","スズキ","タナカ","カトウ","ハヤシ" };
// スコアにランダムな値を設定
for (int& s : score) {
s = rand() % 10;
}
int maxScore = -1;
const char* winner = nullptr;
// スコアが一番高い人の名前を調べる
for (int i = 0; i < 5; i++) {
if (maxScore < score[i]) {
maxScore = score[i];
winner = name[i];
}
}
std::cout << "勝者は…" << winner << "でした!";
分かりずらいですよね。
なにがいけない?
・「名前」と「スコア」は密接に関係した要素なのに、別の配列になっているので関係性が掴みずらい。
この「スコア」と「名前」を構造体にまとめて処理を書いたパターンがこちらです。
改善コード(ちょっと長いので畳んであります)
struct Player {
int score;
const char* name;
};
std::array<Player, 5> players{};
// 名前表
std::array<const char*, 5> nameTable = { "サトウ","スズキ","タナカ","カトウ","ハヤシ" };
// スコアと名前を設定
for (unsigned i = 0; i < players.size(); i++) {
players[i].score = rand() % 10;
players[i].name = nameTable[i];
}
// プレイヤーたちをスコアが高い順に並び替える
std::sort(players.begin(), players.end(), [](const Player& lp, const Player& rp)
{
return (lp.score > rp.score);
}
);
std::cout << "勝者は…" << players.front().name << "でした!" << std::endl;
std::cout << "ドベは…" << players.back().name << "でした!" << std::endl;
(ついでにドベ枠も表示させてみた)
※並び替え部分が少し難しい書き方をしていますが、「スコアの高い順でPlayerたちが並び替えられている」とだけ認識して頂ければ大丈夫です!
とりあえず今回は、「スコア順でPlayer達を並び替え、先頭を取れば1位、最後尾を取ればドベを取得できる」というような処理にしてみました。
このように、「スコア」と「名前」がまとまっていると、「数値を基準に名前を使う」という処理が簡単に組めるようになるので、関係性のあるデータは構造体にまとめておきましょう!
参考リンク:
【☆☆☆☆☆】std::shuffleを使って配列をシャッフルしよう!
例えばこんな仕様があるとしよう。
アイテムが7種類ある。
全てのアイテムは1度だけ使用される。
この時、「実行するたびアイテムの出現順が変わる」ようなプログラムを書いてください。
僕はstd::iotaとstd::shuffleを使って実装してみました。
実行例
コード全容(長いので畳んであります)
static const int ITEM_NUM = 7;// アイテムの数
// アイテム名の表示
static const char* const ITEM_NAME_TABLE[ITEM_NUM] =
{
"キノコ","コウラ","バナナ",
"イカ","ボム","スター","サンダー"
};
// アイテム配列の型
using ItemArrayType = std::array<int, ITEM_NUM>;
// 全アイテムを表示する
void PrintAllItem(const ItemArrayType& itemArr)
{
for (int itemNum : itemArr) {
std::cout << (itemNum + 1) << "番:";
std::cout << ITEM_NAME_TABLE[itemNum] << std::endl;
}
std::cout << std::endl;
}
int main()
{
ItemArrayType itemArray{};// 配列を生成
std::iota(itemArray.begin(), itemArray.end(), 0);// 連番設定
std::cout << "Before shuffle" << std::endl;
PrintAllItem(itemArray);// 配列を順番に表示
std::random_device rndSeed;// ↓のシード値用
std::mt19937 rndMt(rndSeed());// 乱数生成器
std::shuffle(itemArray.begin(), itemArray.end(), rndMt);// 配列をシャッフル
std::cout << "After shuffle" << std::endl;
PrintAllItem(itemArray);// 配列を順番に表示
}
簡単なプログラムの解説
- std::arrayを使ってサイズ7の配列を作成する。
- std::iotaを使って配列の要素に連番を振る。
- std::shuffleを使って配列の順番をシャッフルする。
つまり「1度アイテムを順番に生成し、それをシャッフルすることでアイテムの出現順番を変更している。」ということです。
【☆☆☆☆☆】std::count_ifを使ってみよう!
例えばこんな仕様があるとしよう。
生徒が30人いる。
彼らにはランダムに点数が与えられる。(0~100点)
この時、点数が80点以上の生徒が何人いるかを数えてください。
皆さんならどんなコードを組むでしょうか。
私はstd::count_ifを使って、こんなふうに書いてみました。
サンプルコード(ちょっと長いので畳んであります)
// Studentデータ(今回は仮でスコアだけ)
struct Student {
int score;
};
// 生徒を30人生成
std::array<Student, 30> students{};
// スコアをランダムに設定(0~100)
std::random_device rnd;
for (auto& p : students) {
p.score = rnd() % 101;
}
// 合格者(80点以上)が何人いるか数える
int clearNum = std::count_if(students.begin(), students.end(),
[](const Student& p)
{
return (p.score >= 80);
}
);
std::cout << "合格者は" << clearNum << "人でした!" << std::endl;
このように、「条件に合ったモノの数を数える」という処理は割といろんな所でやると思うので、この便利な関数も推しておきます!
ちなみに、単に「この値のモノの数を数える」という単純な処理の場合は、std::countを使っても実装できますね!
【☆☆】std::swapを使って値を入れ替えよう!
「2つの変数の値を入れ替えたい」
そんなときはstd::swap関数を使うと便利です!
// 値を入れ替える変数
int gValA = 2;
int gValB = 5;
// 値を表示する関数
void Print() {
std::cout << "A:" << gValA << " ";
std::cout << "B:" << gValB << "\n";
}
int main()
{
std::cout << "入れ替え前" << std::endl;
Print();// 値表示
std::swap(gValA, gValB);// 値入れ替え
std::cout << "入れ替え後" << std::endl;
Print();// 値表示
}
入れ替え前
A:2 B:5
入れ替え後
A:5 B:2
参考リンク:
【☆☆☆】switch文処理は配列でどうにかなるかも!?
こんな仕様があったとしよう。
フルーツが7種類ある。
各フルーツには値段が決められている。
この時、「引数でフルーツの種類を指定し、値段を返す関数」を作成して下さい。
という問題があったら、どう組むでしょうか?
まずパッと思いつくのはswitch文を使う手法かと思います。
switch文バージョン(長いので畳んであります)
// 果物の種類
enum class eFruits {
apple,
strawberry,
orange,
banana,
peach,
lemon,
muskmelon,
};
#define FRUITS_PRICE_ERROR (-1)
// 各果物の値段を返す
int GetFruitsPrice(eFruits type)
{
int price = 0;
switch (type)
{
case eFruits::apple:
price = 200;
break;
case eFruits::strawberry:
price = 300;
break;
case eFruits::orange:
price = 80;
break;
case eFruits::banana:
price = 150;
break;
case eFruits::peach:
price = 250;
break;
case eFruits::lemon:
price = 120;
break;
case eFruits::muskmelon:
price = 5000;
break;
default:
price = FRUITS_PRICE_ERROR;
break;
}
return price;
}
int main()
{
int price = GetFruitsPrice(eFruits::muskmelon);
if (price != FRUITS_PRICE_ERROR) {
std::cout << "値段は" << price << "円でした。" << std::endl;
}
else {
std::cout << "エラー" << std::endl;
}
}
…長いですね。ただ単に種類に応じた値段を返すだけの処理なのに長すぎます。
「もっと処理を短く書きたい!」そんなときはstd::mapを使うのも手かもしれません!
std::mapバージョン(長いので畳んであります)
※GetFruitsPrice関数部分と、そこで使用する値段テーブル(std::map型)のみ記載します。
それ以外は上記switch文バージョンと変わりません。
// 全フルーツの値段表
static const std::map<eFruits, int> sFruitsPriceTable =
{
{eFruits::apple, 200},
{eFruits::strawberry, 300},
{eFruits::orange, 80},
{eFruits::banana, 150},
{eFruits::peach, 250},
{eFruits::lemon, 120},
{eFruits::muskmelon, 5000}
};
// 各果物の値段を返す
int GetFruitsPrice(eFruits type)
{
int price = 0;
if (sFruitsPriceTable.find(type) != sFruitsPriceTable.end()) {
price = sFruitsPriceTable.at(type);
}
else {
price = FRUITS_PRICE_ERROR;
}
return price;
}
かなり短くなったのではないでしょうか。
配列にしておくとすべての値段がパッとわかり、尚且つ関数実装が短くなって見やすくなりましたね。
ということで「switch文を使った処理はstd::mapで補えるかも」というお話でした。
よかったら使ってみてください!
参考リンク:
【☆☆】処理時間計測プログラムを組んでみよう!
よくプログラミング系のサイトを見ていると「処理が早い」「処理が重い」というワードを見かけるかと思います。でもこれってどうやって計ってるんでしょうか?
実は、簡易的な計測プログラムであれば学生でも簡単に作れてしまいます!
サンプルコード&実行結果例(長いので畳んであります)
// 10回計測する。
for (int loopCount = 1; loopCount <= 10; loopCount++)
{
std::cout << std::setw(3) << loopCount << "回目:";
clock_t start = clock();// 計測開始時間
{// 処理時間を計測する処理(今回は試しにたくさん足し算してみる)
long a = 0;
for (int i = 0; i < 10'000'000; i++) {
a += i;
}
}
clock_t end = clock();// 計測終了時間
clock_t result = end - start;// 経過時間計算
std::cout << result << "ミリ秒" << std::endl;
}
1回目:20ミリ秒
2回目:15ミリ秒
3回目:15ミリ秒
4回目:15ミリ秒
5回目:15ミリ秒
6回目:15ミリ秒
7回目:15ミリ秒
8回目:16ミリ秒
9回目:15ミリ秒
10回目:15ミリ秒
今回カギになってくる関数がclock関数です。これは「プログラム実行開始時点」からの「経過時間」を返すという特徴があるそうです。
そのため、処理直後の時間から処理直前の時間を引けば処理にかかった時間が計算できる、というわけですね。以上、処理時間計測プログラムを組んでみようというお話でした。
ぜひ皆さんも色々計ってみて下さい!
【☆☆】constを使いこなそう!(初級編)
例えば、一時的にローカル変数へ「画面のサイズ」を保存する処理を書く場合、
const int screenSizeX = DataBase::GetScreenSizeX();
その変数(screenSizeX)にはconstを付けることをおすすめします!
上記コードのようにconstを付けると、それ以降その変数の値は変更できなくなります。
(読み取ることはできます。)
const int screenSizeX = DataBase::GetScreenSizeX();
screenSizeX = 0;// エラー! 変更はできない
int value = screenSizeX;//OK. 値を受け取る事はできる
今回のような「その処理フレーム中に値が変わることが確実に無い」場合には、constが有用です。
理由としては「途中で値が変わらないことが保証されるため、バグ対策になるから」などです。
値変更不可が保証されるとなぜいい?
例えば、「画面中央にモノを表示する」コードを書く時、恐らく「画面サイズ/2」の位置にモノを表示するようにすると思います。その時に使う「画面サイズ変数」を誤って変更してしまっていた場合、モノの表示位置が狂ってしまいますよね?しかしその「画面サイズ変数」をconst化しておけば確実に変更されないので、こういったバグが起こらない(起こすことができない)ようになります。
正直、constをつけなくても人間側が超気を付けてプログラミングすればそれで済む話なのですが、どこかでミスはしてしまうのでconstを付けておいたら安全だよね☆というお話です。
【☆】else ifを使いこなそう!
else ifを使えば処理速度が上がる!?
if文が連続する場合「else if」と記述することができますが、場合によってはこのelse ifを上手に活用した方がわずかに処理速度が上がるようです。
実験内容・結果
forのループ回数を10で割った余りの数を変数に入れるというまったく意味のない処理を今回の実験では使用します。
for (int i = 0; i < 10'000'000; i++) {
int a = i % 10;
int mod = -1;
if (a == 0) {
mod = 0;
}
if (a == 1) {
mod = 1;
}
if (a == 2) {
mod = 2;
}
// 省略
if (a == 9) {
mod = 9;
}
}
上記コードの処理速度を計測した結果、平均32.7msという結果でした。
しかし、「if (a == 1)」以降のif文をelse ifに変更した結果、平均25.4msという結果になりました。
if(a == 1)
↓
else if(a == 1) // こうしたほうが処理が速くなった!
以上の結果から「else ifを使った方が処理速度が速い」と判断しています。
else ifで合理的なコードを!
とは言ったものの!else ifを使う最大の理由は処理速度ではなく「まず無駄な条件分岐はさせる必要が無いから」であると考えます。
例えば?
ジャンケンの手を調べるif文を書くとしましょう。
if (myHand == ROCK) {
std::cout << "グー";
}
if (myHand == PAPER) {
std::cout << "パー";
}
if (myHand == SCISSORS) {
std::cout << "チョキ";
}
これ、例えば手がROCKだった場合、PAPERやSCISSORSかって判定とる必要無いですよね?
なので↓のように
if (myHand == ROCK) {
std::cout << "グー";
}
else if (myHand == PAPER) {
std::cout << "パー";
}
else if (myHand == SCISSORS) {
std::cout << "チョキ";
}
else ifにして「上の段階でif文が通っているのであればそれ以降は判定を取らない」とした方が合理的ですよね。
else ifが絶対正義では無い?!
とは言ったものの!なんでもかんでもelse ifにすればいいかと言えばそういうわけでもありません。
「逆にelse ifにしたら不都合な場面」というのがゲーム制作には存在します。
例えば?
十字キーの入力によって座標を変更するプログラムを書くとしましょう。
vel = float2::ZERO;
if (currentInput & LEFT) {
vel.x -= VEL;
}
else if (currentInput & RIGHT) {
vel.x += VEL;
}
else if (currentInput & UP) {
vel.y -= VEL;
}
else if (currentInput & DOWN) {
vel.y += VEL;
}
pos += vel;
この書き方、例えば左と上を同時に入力しても左上に斜め移動できないのは読解できるでしょうか?
ゲームがそういう仕様なのであれば問題ないのですが、自由に移動できるタイプのゲームで斜め移動ができないのはかなりストレスですよね。
そのため、自由に斜めにも移動したいのであれば、これはelse ifではなく全てifにしておいた方が都合がいいかもしれません。
このように、else ifを使うことにはメリットもデメリットもありますが、場面に応じて使いこなせるようになれればと思います!ぜひたくさん使って良い点悪い点を発見してみて下さい!
【☆☆☆】メンバ"関数"にもconstが付けられる?!(const中級編)
今回使用するプレイヤークラス
class Player {
private:
int mHp;// hp
public:
Player()
:mHp(0)
{
}
// 普通のメンバ関数
int MemberFunc() {
mHp = 0; // OK. メンバ変数が変更可能
}
// constメンバ関数
int ConstMemberFunc() const {
mHp = 0; // エラー! メンバ変数は変更できない
int a = mHp; // OK. メンバ変数以外は関係ない。
}
};
上記コード内にある"ConstMemberFunc"関数
// constメンバ関数
int ConstMemberFunc() const {
mHp = 0; // エラー! メンバ変数は変更できない
int a = mHp; // OK. メンバ変数以外は関係ない。
}
関数名の後にconstが付いていますよね?
これは「constメンバ関数」と呼ばれ、"関数をconst化させる"技です。仕様として
①関数内でメンバ変数が変更できなくなる。
②constオブジェクトからも呼び出せる
というものがあります。
少し詳しめな説明
①メンバ変数が変更できなくなる
constメンバ関数内では、メンバ変数を変更することができなくなります。
デメリットのようにも聞こえますが、これは外から見れば「この関数内ではメンバ変数の書き換えが無い」と保証されることになるので、メリットとも捉えられます。
int MemberFunc1() {
mHp = 0;// OK. メンバ変数が変更可能
}
int MemberFunc2() const{
mHp = 0;// エラー! メンバ変数は変更できない
}
②constオブジェクトからも呼び出せる
通常、const状態で作られたオブジェクトからは、非constの関数は呼ぶことができません。
しかし、メンバ関数もconst化していれば、呼び出すことが可能になります。
Player normalPlayer; // 非const状態のプレイヤー変数
const Player constPlayer; // const状態のプレイヤー変数
normalPlayer.MemberFunc(); // OK.
normalPlayer.ConstMemberFunc(); // OK.
constPlayer.MemberFunc(); // エラー! const状態のプレイヤーから非constなメンバ関数は呼び出せない。
constPlayer.ConstMemberFunc(); // OK. const状態のプレイヤーであれ、constメンバ関数は呼び出せる。
特にゲッター系の関数は積極的にconstにすることをおすすめします!
少々複雑ですが、どんどん使ってマスターしてみてください!