備忘録と戯言

学生がプログラミングの備忘録となんか印象に残ったことを綴る

C#で簡単なゲームつくった

C++ばかり書いていてC#を忘れないように簡単なゲームをつくった。

コナンを数話観ながら作ったから制作時間は数時間・・・

クオリティも相応。

ランクなども作ったのでまぁ、遊んでみてください。


test_game.zip download←ここからDL


まぁ、C#はUnityで触ってるけど・・・

コードの見やすさ

今回はコードの見やすさに着目してみました。

ですが、コードの書き方、見やすさの判別は十人十色!グループで制作しない限り自分の見やすいコードの書き方で書くと思います。


が!今回はどちらが見やすいかハッキリと判断できると思います。(たぶん・・・)

本題

今回やったことは「変数名や関数名を日本語にするか英語にするか」です。


作業環境 VS2013 for Desktop

まずC++版から

 #include <iostream>

 void test_1();
 void テスト_2();

 int main()
 {
     test_1();
     テスト_2();

     return 0;
 }

 void test_1()
 {
     int num = 10;
     bool flag = true;
     float f_num = 0.7f;
     std::string str = "asdfg";

     if (flag)
     {
         num += 3;//13
         f_num -= 0.2f;//0.5f
         str = "qwerty";//"qwerty"
     }

     std::cout << "test_1" << std::endl;
     std::cout << num << std::endl;
     std::cout <<f_num << std::endl;
     std::cout << str.c_str() << "\n" << std::endl;
 }

 void テスト_2()
 {
     int 数字 = 10;
     bool 何か知らないけどなんかのフラグ = true;
     float f_数字 = 0.7f;
     std::string とある文字列 = "asdfg";

     if (何か知らないけどなんかのフラグ)
     {
         数字 += 3;//13
         f_数字 -= 0.2f;//0.5f
         とある文字列 = "qwerty";//"qwerty"
     }

     std::cout << "test_2" << std::endl;
     std::cout << 数字 << std::endl;
     std::cout << f_数字 << std::endl;
     std::cout << とある文字列.c_str() << "\n" << std::endl;
 }


結果は同じ値を示します。

test_1
13
0.5
qwerty

test_2
13
0.5
qwerty

続行するには何かキーを押してください . . .


次はC#

 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;

 namespace ConsoleApplication3
 {
     class Program
     {
         static void Main(string[] args)
         {
             test_1();
             テスト_2();
         }

         static void test_1()
         {
             int num = 10;
             bool flag = true;
             float f_num = 0.7f;
             string str = "asdfg";

             if(flag)
             {
                 num += 5;//15
                 f_num -= 0.4f;//0.3f
                 str = "zxcvb";//"zxcvb"
             }

             Console.WriteLine("test_1");
             Console.WriteLine("{0}\n{1}\n{2}\n", num, f_num, str);
         }

         static void テスト_2()
         {
             int 数字 = 10;
             bool 何か知らないけどなんかのフラグ = true;
             float f_数字 = 0.7f;
             string とある文字列 = "asdfg";

             if (何か知らないけどなんかのフラグ)
             {
                 数字 += 5;//15
                 f_数字 -= 0.4f;//0.3f
                 とある文字列 = "zxcvb";//"zxcvb"
             }

             Console.WriteLine("test_2");
             Console.WriteLine("{0}\n{1}\n{2}\n", 数字, f_数字, とある文字列);
         }
     }
 }


同じく結果同じ値を示した

test_1
15
0.3
zxcvb

test_2
15
0.3
zxcvb

続行するには何かキーを押してください . . .


上記2つは2通りの書き方で同じ値を示しました。

さて、どちらの方が見やすいでしょうか?


私は圧倒的に英語なのですが中には英語が苦手で日本語の方が見やすいという方がいるかもしれません。


英語で書くといつの間にかボキャブラリーが増えてる、という利点もあります。(私の場合は・・・)



では。

NULLとnullptrの違い

今回はNULLとnullptrについて調べてみました。

「NULL」と「nullptr」はポインタに格納されている値が「何もない」とか「空である」といった意味を示すキーワードですが、どのような違いがあるのかを調べてみました。

   int* p = NULL;
   int* q = nullptr;


以下のコードを書きました。

 #include <iostream>

 void function(int)
 {
     std::cout << "function with arguments of int was called" << std::endl;
 }

 void function(int*)
 {
     std::cout << "function with arguments of int pointer was called" << std::endl;
 }

 int main()
 {
     function(10);
     function(true);

     function(NULL);
     function(nullptr);

     return 0;
 }

関数のオーバーロードを使い、それぞれがどの関数を呼ぶかを検証しました。

結果

function with arguments of int was called
function with arguments of int was called
function with arguments of int was called
function with arguments of int pointer was called
続行するには何かキーを押してください . . .

nullptr以外は、int型を引数に持つ関数が呼ばれました。

10 , trueは置いておいて、「NULL」と「nullptr」で違いがでました。

ポインタを「空」であると示していたNULLがなぜポインタ側の関数を呼ばないのか・・・

ご存知のとおり、NULL は以下のようにマクロで定義されているのです。

#define NULL    0

NULLと言われても所詮は「0」なのです。

なのでNULLはint型の引数を持つ関数を呼んでしまうのです。

NULLを使うときの落とし穴の1つで、このことを知らないと、関数のオーバーロードで意図した関数を呼び出せなくて実行結果がおかしくなることかあるかもしれません。

私は詳しくないのですが「nullptr」はC++11から新しく出たキーワードだそうです。
(私は講師に「こんなのがあるよ」程度しか教わっていませんでした。)


ここで、気になるのが、nullptrは何型なのか?ということ。

調べてみるとnullptrはstd::nullptr_t型であると記載されていました。


ここでまた、検証してみました。

上のコードに少し付け足ししました。

 #include <iostream>

 void function(int)
 {
     std::cout << "function with arguments of int was called" << std::endl;
 }

 void function(int*)
 {
     std::cout << "function with arguments of int pointer was called" << std::endl;
 }

 void function(std::nullptr_t)
 {
     std::cout << "function with arguments of std::nullptr_t was called" << std::endl;
 }

 int main()
 {
     function(NULL);
     function((int*)NULL);

     function(nullptr);

     return 0;
 }

結果

function with arguments of int was called
function with arguments of int pointer was called
function with arguments of std::nullptr_t was called
続行するには何かキーを押してください . . .

これで、nullptrはstd::nullptr_t型であることが証明されました。


NULLにはもっと深い歴史がある?かはわかりませんが、私の教わった事と調べた事を綴りました。

何か付加情報などがあれば教えてください。

NULLとnullptrの(大雑把な(保険))違いでした。

ではこれで。

素数判定について

プログラムを学ぶ際におそらく多くの人は通る道が「素数判定」だと思います。

「100までの素数を求めなさい」とか出題されたことがあるかもしれません。

私もいろいろなサイトを見たり、プログラミングを始めた頃に戻ったつもりになってみました。


おそらく、多くの人は以下のように書くかもしれません。

「※1」

 int main()
 {
     int divisor_num;
     std::vector<int> prime_list;

     for (int i = 1; i <= 100; i++)
     {
         divisor_num = 0;
         for (int s = 1; s <= i; s++)
         {
             if (i % s == 0) divisor_num++;
         }

         if (divisor_num == 2)
             prime_list.push_back(i);
     }

     for (int i = 0; i < prime_list.size(); i++)
         std::cout << prime_list[i] << std::endl;

     std::cout << "全部で" << prime_list.size() << "個です" << std::endl;

     return 0;
 }

という書き方が思い浮かぶ?かもしれません。
思い浮かばなかったらすいません。

しかし、これでは、私でもムダが多いことが一目瞭然です。

ですので少し改良、以下のような書き方にしてみました。

「※2」

 int main()
 {
     int divisor_num;
     std::vector<int> prime_list;

     prime_list.push_back(2);

     for (int i = 3; i <= 100; i += 2)
     {
         divisor_num = 0;
         for (int s = 3; s < i; s += 2)
         {
             if (i % s == 0)
             {
                 divisor_num++;
                 break;
             }
         }

         if (divisor_num == 0)
             prime_list.push_back(i);
     }

     for (int i = 0; i < prime_list.size(); i++)
         std::cout << prime_list[i] << std::endl;

     std::cout << "全部で" << prime_list.size() << "個です" << std::endl;

     return 0;
 }

この書き方の利点としては、「」というのは素数であり、以降の偶数を「素数」でなくします。

ですので、2を最初に追加してしまい、「」から始めます。

ここで、もう、偶数を見る必要はないので、奇数だけを見ます。
そうすると、ループ回数を大幅に減らせますので早くなります。


また、このような書き方もありました。

「※3」

 int main()
 {
     int divisor_num;
     std::vector<int> prime_list;

     prime_list.push_back(2);

     for (int i = 3; i <= 100; i += 2)
     {
         divisor_num = 0;
         for (int s = 3; s < sqrt(i); s += 2)
         {
             if (i % s == 0)
             {
                 divisor_num++;
                 break;
             }
         }

         if (divisor_num == 0)
             prime_list.push_back(i);
     }

     for (int i = 0; i < prime_list.size(); i++)
         std::cout << prime_list[i] << std::endl;

     std::cout << "全部で" << prime_list.size() << "個です" << std::endl;

     return 0;
 }

「※2」と変わったところは、途中のfor文が「for (int s = 3; s < sqrt(i); s += 2)」になったことです。

このようにすると、素数のときにその数の手前まで計算する必要がない」のです。

「※2」だと53を計算するときに3,5,7,・・・49,51まで計算しますが、「※3」だと53の平方根まで(7.2801・・、要は「7」まで)の計算で事足りるのです。

なぜなら、平方根以下の数と、平方根以上の約数は対になるからです。

この3種類で、処理速度を計測してみました。

以下のようなプログラムを書きました

 #include <iostream>
 #include <Windows.h>
 #pragma comment(lib, "winmm.lib")

 #include <vector>

 int main()
 {
     int divisor_num;
     std::vector<int> prime_list;

     DWORD start_time = timeGetTime();

     //----------------------------------------------
     /*ここに「※1」、「※2」、「※3」をブチ込む!*/
     //----------------------------------------------

     DWORD end_time = timeGetTime();
     std::cout << "takes to " << (double)(end_time - start_time) / 1000 << " sec" << std::endl;

     return 0;
 }

ここで、純粋に素数判定だけの処理を計算したいので、途中の「for文ループのネスト」の部分だけを使用します。
素数の個数表示とネスト内にあるvectorの処理は計測外としました。(vector部分は削除しました。)

要は、途中のfor文の処理の開始から終了までの時間を計測してみました。

「結果」

※上限が「1000」の場合

「※1」

takes to 0.009 sec
続行するには何かキーを押してください . . .

「※2」

takes to 0.001 sec
続行するには何かキーを押してください . . .

「※3」

takes to 0 sec
続行するには何かキーを押してください . . .

となりました。

しかし、1000では小さすぎます、露骨に差が出てもらわなくては困るのでもっと大きな値に。

※上限が「1,00,000」の場合

「※1」

takes to 27.227 sec
続行するには何かキーを押してください . . .

「※2」

takes to 2.558 sec
続行するには何かキーを押してください . . .

「※3」

takes to 0.274 sec
続行するには何かキーを押してください . . .

となりました。

以上は全て私の所持しているノートPCで検証結果です。

それぞれの差は歴然としています。

1つの目的に辿り着くだけでも工夫すればあっという間に着いてしまいます。
ここがプログラミングの素晴らしいところですね♪
こういうのを考えたり知ったりするの大好きです♪

ここまで書きましたが、ここで1つ、ツールを作ってみました。

と言ってもただの、「範囲内に素数が何個あって、それはどんな数字か」をファイルに書き出すツールです。

サクッとC#で書いてみました♪
C#楽しい♪
C++の次にだけど・・・

ここにDL用のリンク貼っておきます。

Primality test.zip download←ここから


素数関係の記事なので、素数関係で面白いアルゴリズムがあれば教えてください♪

if文やfor文などの書き方


基本中の基本<if文やfor文など>

プログラムをしていれば必ず知っているであろうif文やfor文(など)ですが、疑問に思ったことがあったので・・・

ほとんどの参考書が私の書き方と違う・・・

何が違うかというと中括弧の位置が違う。

私は以下のように書くのですが・・

if (1)
{
   std::cout << "私はこのように書きます" << std::endl;
}
else
{
   std::cout << "・・・・・" << std::endl;
}

for (;;)
{
   std::cout << "・・・・・";
}

しかし、私が読んできた参考書のほとんどは、

if (1){
   printf("このように書いてある");
}
else{
   printf("・・・・・・・・・・");
}

for (;;){
   printf("・・・・・");
}

という書き方。

中括弧省略できるじゃん!というのはここでは論外です。


ちなみに私の受講している科目の講師も後者。

どのように違くて、どうしてそのような書き方をするのか色々調べた。


とりあえず、この2つのスタイルを調べた。

前者(私も使っている書き方)をBSD/オールマンのスタイル」と言うらしい。

利点は、中括弧の開始と終了が同じ位置なのでわかりやすい。
(私もここが使用している最大の理由です。)

欠点としては、中括弧のためだけに余分な1行を使ってしまう。


後者をK&Rのスタイル/カーネルスタイル」というらしい。

利点は、ソースファイル全体の行数が前者より少なくて済む。

欠点は、中括弧の開始の位置を探すのが困難・・・らしい。

ここまでは  字下げスタイル - Wikipedia
に書いてあったことを軽く書き写してみました。


利点、欠点とは書きましたが、ソースコードなんていうのは、見やすく書く事が重要だと思うので、これはあくまでも私が感じた事柄だと思って受け取ってください。

前者が見やすければ前者のスタイルで書くし、後者も然り。

個人差の激しい分野で、論争しても終わりが無いと思うので・・・


ここからは、様々なサイトに書いてあったり、受講している講師に聞いたことなのですが、

K&Rのスタイルで書いている人はコードに「適度な密度」を求めている人が多いようです。BSD/オールマンのスタイルで書くと中括弧だけの行が多くできて何か過疎感が感じられるようです。スカスカなコードに美しさを感じないようです。

1行ずつ見てく時に処理の記述を読んでいくのに急に「中括弧だけの行」があるのが堪らないらしいです・・・


私はなんとも感じないのですがね・・・。



私は学生なのでわかりませんが、会社などで複数人でプロジェクトを進めていく時は当然コードのスタイルを統一すると思うので、慣れとく必要がある・・・のかな?