ログインしてさらにmixiを楽しもう

コメントを投稿して情報交換!
更新通知を受け取って、最新情報をゲット!

C++ 超初心者の集いコミュのC#をはじめよう

  • mixiチェック
  • このエントリーをはてなブックマークに追加
こめさんが、C#もやってみない?ということだったので。

@C#って何?
 C#とは、マイクロソフトがC++とJavaのいいとこをとって作った新しい言語

@.NET Frameworkって?
 .NET Frameworkでは、プログラムをネイティブコードではなくILという中間コードにコンパイルします。このILは、CLIというプログラムから実行されます。つまり、どの言語を使って開発しても、最終的にILに変換されるため、CLIが実装されていればどのプラットフォームでも利用できるということになります。他にも、ガーベジコレクションなどの実装により、プログラムを簡素化できるようにも設計されています。

@開発環境
 C#言語で開発を行うには、.NET Frameworkが必要になります。VisualStudio.NETを持っている方はインストール時に同時にインストールされますが、持っていない方はマイクロソフトのホームページから入手しましょう。
URL:http://www.microsoft.com/japan/msdn/netframework/downloads/sdk.asp
また、開発ではなく実行させるだけが目的であれば、.NET Frameworkの実行環境が再配布されているのでそちらを。ただし、ウィンドウズアップデートを適切に行っていれば通常はインストールされています。


とりあえず最初はこんなもんでw

コメント(25)

ちなみに、C++の知識があるものとして、書いていきますw
さて、まずHello Worldプログラムを書いてみよう。

VS.NETでは、新規作成→Visual C# Project→空のプロジェクトを選ぶ。
プロジェクトを右クリックし、追加→新しい項目の追加を選ぶ。「コードファイル」を選択し、名前をつけて開こう。

コマンドプロンプトでコンパイルする人は、適当にテキストファイルを作り .cs という拡張子で保存すればOKです。
(ただし、ファイル名に日本語は使わないこと。)

さて、以下のコードが一番簡単な、サンプルコードです。


// 注意 //
以下のコードは、インデントに全角空白(スペース)を使っています。
もしも、以下のコードをコピーして利用しようと思うならば、インデントを半角空白もしくはTabに変換して利用してください。
// 注意 //

using System;

class Class1
{
  static void Main(string[] args)
  {
    Console.Write("Hello World!!\n");
  }
}

これをコンパイルして実行すると、コンソール画面に Hello World と表示されるはずです。

1行目の using System; です。Javaをやったことがある人は、import と同じ感覚ですね。C++の場合は、#include に似たものだと考えておきましょう。

3行目の class Class1 ですが、これがクラスになります。ちなみに、このクラス名は(Javaではファイル名と同じでなければならなかったが)自由につけてかまいません。

5行目の static void Main(string[] args) は、お分かりだと思いますが、メイン関数です。もしくは、C#のような完全なオブジェクト指向言語においては「メソッド」といったほうが良いかもしれません。このメイン関数がまず最初に実行されます。 static や string[] args などは最初のうちは無視しておきましょう。(static はわかるかな?)

7行目の Console.Write("Hello World!!\n"); は見たままだと思いますが、Console というのが C++ における cout 的なものですね。コンソール画面を操作するクラスです。C++では << 演算子を用いて出力しましたが、C#では Write というメソッドを利用して出力しています。
(C++をやっていた人は面倒だと感じるかもしれませんが、基本的に俺はこういうメソッドを介した方法が良いと思ってます。)

一応の説明は以上です。
なんとなくわかったでしょうか?
ちなみに、上を C++ で書くとこうなります。

#include <iostream>

void main(int argc, char * argv[])
{
  std::cout << "Hello World!!\n";
}

まあ、クラスがないですが、、、

次は変数を使ってみたいと思います。
変数は基本的に C++ のものと似ています。

組み込み型の種類は以下のものがあります。
符号あり 符号なし サイズ
sbyte   byte   1byte
short   ushort  2byte
int    uint   4byte
long   ulong   8byte
char         1byte
float        4byte
double        8byte
decimal       16byte
bool         1byte
string
object
(sbyteの s は、signed(符号付)の s で、ushortやuintなどの u は unsigned(符号無し) の u です。)

上のほうは見たことがあるのではないでしょうか?
byteはないかもしれないけど、C++では typedef byte char; と同じようなものです。
decimal は double の上ですね。
string は文字列型です。これのおかげで、char str[32]; なんてしなくてよくなりそう。
objectっていうのは、すべての型で継承される型です。詳しくは後で説明します。(というか詳しく知らないw)

また、C++では int a という変数があった場合、
 std::cout << "変数 a の値は " << a << "\n";
とし、Cでは
 printf("変数 a の値は %d \n", a);
としました。
しかし、C#では上に似た方法のどちらも使うことができます。
以下が実際の例です。
(ただし、前回のプログラムから static void Main( ... )の部分だけを書き換えた形で書いてあります。)

static void Main(string[] argv)
{
 int a = 10;

 Console.Write("変数 a の値は " + a + " です。\n");
 Console.Write("変数 a の値は {0} です。\n", a);
}

なんとなくわかるでしょうか?
上側は、文字列の間に + を使って挟みこんでいます。
下側は、{0}としたところに、以降に続く変数の値が入ります。
(printfでは %d %d としていたところが、{0} {1} となるわけです)

次は、コンソールから入力する方法をやってみたいと思います。
C++でコンソールからデータを入力するには、
int a;
std::cin >> a;
としていました。
しかし、この方法はあまりいいとはいえません。
なぜなら、どのような値が入力されるかわからないからです。

C#では、コンソールから一行読み込むには、ConsoleクラスのReadLineメソッドを利用します。
これは、必ず文字列を読み込みます。
数値に変換したいときは、その型の Parse というメソッドを使います。

Parse は簡単にいうと atoi のような文字列を変換するメソッドです。

実際にサンプルを書いてみましょう。
(例によって、Main関数だけを掲示します。)

static void Main(string[] argv)
{
 int a;
 double b;

 a = int.Parse( Console.ReadLine() );
 b = double.Parse( Console.ReadLine() );

 Console.Write("a = " + a + " , b = " + b + "\n);
}

なんとなくわかるでしょうか?
ちなみに Parse は object 型に定義されているメソッドなので、すべての型にあります(多分w)

// 注意 //
上のプログラムで、最初に入力するとき、数値ではなく test のような文を入力してしまうと…
プログラムがとまり、
例外 System.FormatException が発生しました。
と出ます。
これは、例外といって、正しくないことが発生したのでプログラムがストップされたことを示しています。
例外については、次にやります。
// 注意 //
今回は例外処理についてやります。

例外というのは、プログラムを実行しているとき、予期していなかったエラーが発生したときに、発生するものです。
たとえば、前回のもので言えば、数値が入力されることを期待しているのに、test が入力されてしまったために、例外が発生しています。

例外は少し難しい?と思うので、今回は検知するだけに留めます。

例外を処理するには、try-catch-finally構文を利用します。
try には、例外が発生してしまいそうな処理を書きます。
catch には、例外が発生したとき、その例外に対して行う処理を書きます。
finally には、例外が発生しようがしまいが、必ず行いたい処理を書きます。

さて、前回のプログラムを以下のようにしてみましょう。

static void Main(string[] argv)
{
 int a;
 double b;

 try {
  a = int.Parse( Console.ReadLine() );
  b = double.Parse( Console.ReadLine() );

  Console.Write("a = {0} , b = {1}\n", a, b);
 }

 // Parse で例外が発生すると FormatException が投げられる
 catch( FormatException )
 {
  Console.Write("例外が発生しました。\n");
 }
}

これを実行すると、変なウィンドウがでておかしいことにはなりません。
間違った入力をしても 例外が発生しました。 と表示されて終了します。

また、finallyですが、たとえば

try {
 a = int.Parse( Console.ReadLine() );
}
catch( FormatException )
{
 return ; // ここで Main 関数を終了させる

 // ↓実行されない(コンパイル時に警告がでるが、無視)
 Console.Write("例外が発生しました。\n");
}
finally
{
 Console.Write("finally が呼ばれました。\n");
}

こうすると面白いことがわかります。
try にて例外が発生すると catch に飛ぶのですが、ここで return 文を使って関数から抜け出そうとしても、正しく finally が実行されていることがわかります。
つまり、return が使われるかすべて終了した後に finally が呼ばれるわけです。
なので、たとえばファイルを閉じたりする処理をここに書くと良いかもしれません。

// 注意 //
一般的に、try-catch-finally を用いた例外処理は、処理が遅くなってしまうということが知られています。
例外の発生なくして、tryを終了すればさほど変わりはありませんが、基本的に try-catch-finally を多用するのはあまりよくないかもしれません。

なので、Parseではなく、たとえば StringToInt のような変換する関数を作っておきエラーが発生したら -1 を返すなどのような処置を行い、if文などでチェックするといった様式をとるのがいいでしょう。

例外は、ユーザの入力や、ファイルのオープンなど、実行するまで何が起こるかわからないという部分に使うのがいいでしょう。
// 注意 //
今回は、string型についてちょっとやってみます。

先ほどとあるC#入門のサイトを見ていて、string型の説明が
Unicode キャラクタの文字列への参照
とあったので、ちょっと勘違いがあったことに気づきました。

string型は、リファレンスタイプになります。
要するに、C++ でいう参照やポインタのようなもの。

詳しくいうと、
string str = "My name is";
str += "Yukiwiro.\n";
とすると、
1.まずメモリ上に My name is というバッファが取られる。
  strはそのアドレスを示すリファレンスになる。
2.+=演算子によってメモリ上に My name is Yukiwiro.\n
  というバッファが取られる。
  str はこのアドレスを示すようになる。
3.ここで、最初の My name is と My name is Yukiwir.\n
  は、まったく別もの。
となります。
文字列を結合したりするとそのたびにデータが作成され、str はそのたびに新しく作成されたデータを示すようになります。
ただし、どんどん作成されても、ガーベジコレクションによって開放されるので、気にせず操作することが可能です。

という感じになります。
しかし、何度も文字列を操作(結合したり)していると、データが何度も取られることによって、処理が遅くなってしまいます。
これは直接メモリを操作できないことのデメリットです。
もしも、文字列処理を行う場合に、高速な処理を求めるのであれば、
System.Text.StringBuilder
というクラスを利用するといいようです。
前回、データとかバッファとかいう言葉を使って、「インスタンス」を使うことを避けました。
が、これ、やっぱわかってないとだめな感じですねぇ。

インスタンスっていうのは、実体とよく言われます。
たとえば、
class Class1 { ... }
というクラスがあります。
このクラスは、こういう処理をする、というものをまとめただけの物なので実際に何かをするわけではありません。
ですが、プログラム中にて
Class1 cs;
とすることによって、Class1というクラスの「インスタンス」を生成します。
要するに、クラス型の変数ですね。

C#では、(多分)すべてのデータをインスタンスとして扱っています。
たとえば
Console.Write("test\n");
とすると、メモリ上に string 型のインスタンスが自動的に生成され、そのインスタンスは "test\n" という文字列を含みます。
Writeメソッドは string 型のインスタンスを受け取り、その文字列を出力します。

以上の理由から、string型のインスタンスには、文字列の長さを求める Length というプロパティがありますが以下のようにすることも可能です。
Console.Write( "test".Length );
結果的に 4 という数値が出力されるでしょう。
"test"とした時点で、string型のインスタンスが生成され、Lengthが呼び出されるわけです。
Lengthは文字列の長さを示します。
(プロパティについてはまた今度)
ちょwwwww

static void Main(string[] argv)
{
 string 変数;

 変数 = "Hello\n";

 Console.Write( 変数 );
}

これ正常に動くwwwwっうぇwwww
変数名に日本語使えるとかどうなんだwwww

うはwwwwww
関数名にも日本語使えるwwっうぇwww

class TestClass
{
 public string テスト() { return "テストです。\n"; }
}

class Test
{
 static void Main(string[] argv)
 {
  TestClass 変数 = new TestClass();
  string 文字列;

  文字列 = 変数.テスト();
  Console.Write( 文字列 );
 }
}

これが普通に動いたwwwっうぇwww
しかも VisualStudio.NET で 変数. まで打つとちゃんとメンバ一覧が出るwwwww
.NET Frameworkまじすごすwwwww
ちょっと変なテンションでした(;´・ω・)

さて、今回はコメントについてやります。
コメントは、基本的に C/C++ と同じです。
一行コメント // や、区間コメント /* */ を使うことができます。

C# では、このほかに、ドキュメンテーションコメントというのが追加されています。
たとえば C# で作ったプログラムやライブラリを公開しようとしたとき、そのクラスの概要や引数などのドキュメントを作成するのは非常に面倒です。
そこで、C# ではコメント文からXML形式のドキュメントを生成する機能が搭載されています。

具体的には、/// もしくは /** **/ がドキュメンテーションコメントとして判断されます。
普通のコメントよりちょっと多くなっただけです。
このコメントに、XML形式で説明などを書くと、コンパイル時にドキュメントを生成するよう指定すると、XMLのドキュメントが生成されます。

sample.cs
----------------
using System;

/// <summary>
/// コメントのサンプルプログラムです。
/// summaryタグで囲った部分が「概要」になります。
/// </summary>
class Test
{
 /// <summary>
 /// 2値の合計を求める関数です。
 /// </summary>
 /// <param name="val1">数値</param>
 /// <param name="val2">数値</param>
 /// <return>2値の合計値</return>
 public int sum(int val1, int val2)
 {
  return (val1 + val2);
 }
}
---------------
タグ名から予想はできると思います。
一応、これらのタグの一覧を示します。

<c> コード(summaryなどの文中に書くもの)
<code> コード(複数行にわたるもの)
<example> サンプル コードの説明(codeと組み合わせて使う)
<exception> 例外クラスの説明
<include> 別のファイルの内容を取り込む
<list> アイテマイズしたいときに使う
<param> そのメソッドの引数に関する説明
<paramref> summaryなどの文中で引数を参照したいときに使う
<permission> メンバへのアクセスのパーミッションを指定する
<remarks> クラスの説明
<returns> 戻り値の説明
<see> 他のメンバを参照したいときに使う
<seealso> 他に参照して欲しいものがあるときに使う
<summary> そのクラスやメソッドの概要
<value> プロパティの説明
(あるサイトのをこぴってしまいました。ごめんなさい。)

これらの詳しい使い方などは、各自調べてみてください。
メソッドの書き方をこのようにして書くようにしておくと、
インテリセンスに反映され、後々楽になるでしょう。

インテリセンスっていうのは、VisualStudio.NETの機能で、関数の ( を書くと引数リストなんかが表示されるアレです。
上の関数を使おうとすると、関数名にカーソルを合わせると関数名とsummaryで囲った部分が表示されます。
( を書いた時点で以下のようなインテリセンスが表示されるはずです。

int sum(int val1,int val2)
val1:数値

まあ、要するに、ドキュメンテーションコメントを書いておくととても便利だということですね。
いいことを知ったのでひとつ。

いままで、C/C++では複数行ある文字列を出力するのに、
case C
printf("こういう具合に\n"
    "して出力するように\n"
    "してましたよね。");

case C++
std::cout << "こういう具合に\n"
     << "して出力するように\n"
     << "してましたよね。";

これが、C# だと、逐次文字列というものを使うことによって
case C#
Console.Write(
@"こういう具合に
して出力することが
できるんですよ〜");

ちょっとわかりづらいかもしれませんが、 @" で始まり、 " で終わる文字列は、囲まれた部分がそのまま文字列として扱われます。つまり、改行やタブスペースなんかもそのまま。エスケープシーケンス(\nや\t)もそのまま出力されます。ちなみに、逐次文字列内で " を出力したければ、"" と2つ重ねることで " と出力されるようになります。

これを使えば、コンソール上で動作するプログラムを作るときは少し楽になりそうです。
C# にはガーベジコレクションという機能が実装されています。
これは、インスタンスが参照されなくなったら自動で解放する機構でこれにより new で動的に生成したインスタンスを delete する必要がなくなります。
(newについては後ほど)

しかし、ガーベジコレクションに管理されるため、基本的にC#ではポインタの扱いは厳しく制限されるようになっています。
生粋のC/C++プログラマからしたら、ポインタが使えないなんてプログラムが書けないに等しいものですが、C# では参照を使ってやりくりします。

さて、Cプログラム書くとき関数で処理したデータを戻り値ではなく、引数にポインタを使って返すことがあります。
たとえば
// Cのコード
void GetData(int * p)
{
 p = 10;
}
という関数があるとします。するとコード上では
int a;
GetData(&a);
とすると、a には 10 が入ることになります。

C# において、これを行うには参照というものを使います。
実際に書いてみると、
// C# のコード
int a = 0;
GetData(ref a);

void GetData(ref int a)
{
 a = 10;
}
となります。関数の宣言では(ref 型 変数名)とし、呼び出す側では(ref 変数名)とします。

ところで、C/C++のプログラムを書いているとき、こんな経験をしたことがないでしょうか?
int a;
// ここで何か処理するつもりだった。
// しかし実際は、変数 a は使われていない
printf("%d\n", a);
このコードを実行すると、思いもよらない数値が出力されます。
これは変数 a が初期化されなかったため、ごみが表示されてしまったわけです。
C# ではこのようなミスを避けるため必ず変数は初期化しないと使えないという決まりがあります。
そのため、上のコードでは int a = 0; と初期化することで、GetData関数に渡すことができますが、初期化しなければエラーが発生します。
しかしGetDataのようなデータ取得用の場合、初期化せずに渡して関数内で初期化するのがきれいなコードになります。
(意味わからんな・・・。。)

まあ要するに、初期化しなくても使える参照渡しの方法があります。
int a;// 初期化していない
GetData(out a);

void GetData(out int a)
{
 a = 10;
}
こうすると、エラーは吐きません。
(outは出力用という意味かな?)
ただし、outがつけられた引数は、必ず関数内で初期化されなければなりません。
void GetData(out int a)
{
 return ;// 何もしない
}
とすると、エラーが発生します。
いままで、
class Class1
{
 ...
}
について説明しませんでした。

今回は、このクラスを使うのに必要な、オブジェクト指向の基礎知識をやってみようと思います。
といっても、俺自身良くわかってない部分があるので、簡単な概要だけになります。

オブジェクト指向というのは、プログラムを物単位で作成し、最終的にそれらを組み合わせていく手段です。
いままでにC言語で少し大きなプログラムを書いたことがある人は以下のように思ったことがあるかもしれません。
・どんな関数があって何するのか忘れてしまった。
・関数が多くなり、どこに何を書いたのか忘れた。
・似た関数が多く、名前をつけるのに考えてしまう。
これらはクラスを使うことで、かなり解消されるでしょう。

オブジェクト指向のない、C言語でまるばつゲームを作るとします。
必要な処理は、
・盤データの保存
・盤の表示
・手を打つ処理
としときましょう。
これをCで書くと、まずデータはグローバル変数にて
// グローバル変数
int g_board[9];// 3x3マス
みたいな感じになります。
盤の表示は、Print() 手を打つときの処理は、Play() のような感じになります。
PrintやPlayはどんどん機能を追加しているとき、同じような名前の関数を作ってしまうとややこしくなるので、BoardPrint() BoardPlay() のようにして、盤を扱うことを示します。
これが一応Cで作った場合の簡単な例です。

これをクラスのあるオブジェクト指向言語で書いてみると…
ボードというクラスがある。
(クラスというのは大きな枠組み)
その中に、int board[9]; という変数があり、
Print() Play() という関数がある。
ただし、これらはボードクラスの Print() Play() なので、他のクラスでは同じ Print() Play() という名前が使える。

ちょっとわかりづらいかな…。

@まとめ@
オブジェクト指向とは、ある処理ごとにクラスとして一まとめにしておき必要に応じて使っていくという感じになります。
そういった点から、再利用性が高いとか言われます。
他に、クラス内にあるデータは、別のクラスからアクセスできるようにしたり、できないようにしたりすることが可能なので、重要なデータは外部から隠蔽することもできます。
つってもわからないと思うから、具体的に書いてみる。

とりあえず、最初はサンプルということで超簡単な分数を扱うクラスを書いて見ます。

/// <summary>
/// 分数を扱う簡単なクラスのサンプル
/// </summary>
class Fraction
{
 private int denominator;// 分母
 private int numerator;// 分子

 /// <summary>
 /// Fractionクラスのコンストラクタ
 /// </summary>
 /// <param name="n">分子</param>
 /// <param name="d">分母</param>
 public Fraction(int n, int d)
 {
  numerator = n;
  denominator = d;// d = 0 のときの対処はとりあえず放置
 }

 /// <summary>
 /// 分数同士の掛け算を行う
 /// </summary>
 /// <param name="obj">分数オブジェクト</param>
 public void Mult(Fraction obj)
 {
  numerator  = this.numerator * obj.numerator;
  denominator = this.denominator * obj.denominator;
 }

 /// <summary>
 /// 割り算を行う
 /// </summary>
 /// <param name="obj">分数オブジェクト</param>
 public void Div(Fraction obj)
 {
  // 分母分子を逆転して掛け算を行う
  Fraction reversed = obj.Reverse();
  this.Mult(reversed);
 }
 
 /// <summary>
 /// 分母と分子を逆転したオブジェクトを返す
 /// </summary>
 /// <return>分母と分子を逆転したオブジェクト</return>
 public Fraction Reverse()
 {
  Fraction result = new Fraction(this.denominator, this.numerator);
  return result;
 }
 
 /// <summary>
 /// 文字列として返す
 /// </summary>
 public override string ToString()
 {
  string frac;
  frac = numerator + "/" + denominator;
  return frac;
 }
}

長いなぁ…(ぁ
とりあえず、分数を作って、掛け算と割り算を出来るようにしただけです。
ちなみに約分してくれません。

次はこのクラスについて説明したいと思います。
前回のサンプルの解説をします。

class Fraction
{
}
は、{ 〜 } までが Fraction クラスであると定義します。

クラスの先頭にある
private int denominator;// 分母
private int numerator;// 分子
です。
コメントにもあるように、これらは分母と分子のデータです。
両方int型で宣言され、privateアクセス指定子が付いています。
privateのアクセスレベルは、このクラス以外からはアクセスできないと言う意味です。
なので、object.denominator = 10; なんてことはできません。

次は
public Fraction(int n, int d)
です。
これは、コンストラクタといわれるものです。
コンストラクタとは、クラスが動的静的に関わらず生成された時点で実行されるメンバです。
コンストラクタは戻り値を指定しないクラス名と同じ関数として定義されます。
また、引数を指定して、インスタンスが生成されたら分母と分子を設定するようにしています。
(分母が 0 になることは想定しません。めんどいので…)

Mult関数は、掛け算を行う関数です。
引数に Fraction クラスのオブジェクトを受け取ります。
左辺にある de~ と num~ は、関数が呼び出されたオブジェクトの分母分子です。
thisは私自身ということを表します。(具体的には違うが…)
obj.de~ と obj.num~ は引数の obj オブジェクトの分母分子です。
それぞれを掛け合わせているだけです。

Div関数は、引数に与えられたオブジェクトを逆転して掛け合わせています。
詳しくは Reverse関数にて。

Reverse関数は、分母分子を逆転したオブジェクトを新しく作り、そのオブジェクトを返します。

最後に、
public override string ToString()
ですが、これは object という基本クラスに存在する「文字列に変換して返す」関数をオーバーライドしています。
オーバーライド(Over Ride)とは、基本クラスに既にある関数の機能を書き換えてしまうことです。
(基本クラスとかはまた今度)
要するに、string 型の"分母/分子"というデータを作り、それを返します。
実際にクラスを使ってみます。

static void Main(string[] argv)
{
 Fraction a = new Fraction(1, 2);// 1/2
 Fraction b = new Fraction(3, 5);// 3/5

 // 1/2 * 3/5 = 3/10
 a.Mult(b);

 // 3/5 / 3/10 = 3/5 * 10/3 = 30/15
 b.Div(a);

 Console.Write("a = " + a.ToString() + "\n");
 Console.Write("b = " + b.ToString() + "\n");
}

最初の2行で Fraction クラスのオブジェクトを生成しています。
new というのは次で話ます。


a.Mult(b);
で、a = a * b; を行います。
a.関数名 でそのオブジェクトの関数にアクセスします。

(あ、言うの忘れてた(;´・ω・))
// 前回追加 //
関数の前についている、public というアクセス指定子は「メンバはクラス外からでも自由にアクセスできる」ということを示します。
// 前回追加 //

b.Div(a);
は、b = b / a; をしています。

基本的に、. を「の」に置き換えると良くわかります。
a.Mult(b);
は aのMult関数 と読めます。
つまり、Mult関数の中の numerator denominator は a のメンバになります。

最後に、ToString関数を用いて "分母/分子" の形にして出力しています。


詳しい部分はわからないと思いますが、なんとなくの感覚だけ掴めればいいかな??
前回の話の流れとはまったく関係ないけど最近やったので…。

今回は C# にて C/C++ の共用体の実装方法を紹介します。
といっても、MSDNの
http://msdn2.microsoft.com/ja-JP/library/acxa5b99.aspx
に書いてあることです。

C/C++

union TestUnion
{
 int i;
 double d;
 char c;
 byte b;
};

-------------

C#

[System.Runtime.InteropServices.StructLayout(LayoutKind.Explicit)]
struct TestUnion
{
[System.Runtime.InteropServices.FieldOffset(0)]
public int i;

[System.Runtime.InteropServices.FieldOffset(0)]
public double d;

[System.Runtime.InteropServices.FieldOffset(0)]
public char c;

[System.Runtime.InteropServices.FieldOffset(0)]
public byte b;
}

属性についてはまだやってませんが、[] で囲まれた部分が属性です。
これは良く知らなくてもいいでしょう。
一番知るべきなのは、
[System.Runtime.InteropServices.FieldOffset(0)]
です。
こいつの FieldOffset() で指定されたバイトから、その下に続く変数が展開されます。
つまり、上の例ではすべて 0 に設定されているので、すべての変数が同じメモリ番地から開始される(共用体と同じ)ようになります。

また、FieldOffset()を操作することで、一部のみを共有するようにも出来ます。

C/C++

struct TestTExplicit
{
 union {
  long lg;
  struct {
   int i1;
   int i2;
  }
 }
 double d;
 char c;
 byte b;
};

-------------
C#

[System.Runtime.InteropServices.StructLayout(LayoutKind.Explicit)]
struct TestExplicit
{
[System.Runtime.InteropServices.FieldOffset(0)]
public long lg;

[System.Runtime.InteropServices.FieldOffset(0)]
public int i1;

[System.Runtime.InteropServices.FieldOffset(4)]
public int i2;

[System.Runtime.InteropServices.FieldOffset(8)]
public double d;

[System.Runtime.InteropServices.FieldOffset(12)]
public char c;

[System.Runtime.InteropServices.FieldOffset(14)]
public byte b;
}

longは8バイトであり、intは4バイトなので、以上のような設定が可能です。
http://www-ise2.ist.osaka-u.ac.jp/~iwanaga/study/csharp/index.html

どーん
最近書いてないなぁ…。
というのも、いまC#でちょっとしたソフトを作ってるからなんですけどもね〜。

クラスの説明しようと思ったけど、なかなか難しい。
やっぱり俺自身があまり理解できていないからかも。

というわけで、クラスのことは少し置いておきます(;´・ω・)
今回はさくっと new 演算子について。
C++やJavaをやったことがある人はわかるかもしれません。
new 演算子は新しいオブジェクトを動的に生成する演算子です。
たとえば、クラスSampleというのがあったとすると、
Sample obj = new Sample();
という形になります。
最初の
Sample obj
というのが、Sample型のobjという変数を宣言しています。
次の
new Sample()
が、new演算子でSampleクラスの Sample() コンストラクタを用いてオブジェクトを生成しています。

ちなみに、C++をやっていた人からすれば気分が悪いと思いますが、C#には delete 演算子はありません。
ガーベジコレクションにより自動的に解放されます。
つまり、こんな書き方も可能になるわけです。

public static void Test(Sample obj)
{
 Console.Write(obj.ToString);
}
public static void Main()
[
 // new したものを直接渡す
 Test(new Sample());
}
あー、でもなんかそれっぽい雰囲気はするかもw
C#は手軽に使えてしかもちゃんと固めてあるから間違いしにくいってあたりがw
そだ、さっきこめさんが教えてくれた検証サイトに関連してひとつ。

C#は、中間ファイルを吐き出すため機械語ファイルを吐き出すC/C++より遅いのではないかと思われがちですが、そういうことはないんですよ、これがw
詳しいくは知らないんだけど、C#は結構な最適化が適用されるみたいです。
なので、単純な数値計算では最適化がほとんど関係ないのでC/C++のほうが早いんですが、複雑な計算などはC#のほうが速いことが多いそうです。
(VCで、吐き出されるアセンブリのことを考えながらプログラムを組めばわかりませんが。)

# ちなみに同じC/C++といっても、VC++ GNU BorlandC++など
# 複数のコンパイラがあり、複雑な処理の場合、
# それぞれのコンパイラによっても速度が変わってきます。
# (吐き出されるアセンブリが同一ではないため)


ですが、ここで注意してほしいのは計算処理上でということ。
GUIになるとちょっと違うようです。
詳しい理由は知りませんが、GUI描画はC#の方が確実に遅いです。
GUIが固定のツールなんかでは問題ないですが、頻繁に再描画されるGUIはアプリケーションはキツイかな。
予想では、C#がGDI+を使っているからだと思うけど…
そのほかにも要因はあるかも。


それでも俺は
C++は直接メモリを使ったりしてゴリゴリ書けるから高速だ!
C#は組み込み変数もクラス化されてて便利だけど速度でねーよ!
とか思ってたりしますがw
# もちろんチガイマスヨ
C#はかなりのレベルでフレームワークが最適化されてると予想してます。
なのでVBみたくランタイムエンジン使うから遅いってことにはならないようですね。(当然向き不向きはあるでしょうが・・・
VC++だってライブラリコールするから動き的には似たようなもんだと。

ただ、C#はメモリ食いそうですよねw
あんだけクラスで固められてオブジェクト構築してたら結構な使用量になると思いますがw

結果、用途に合わせて使いやすいほうを選ぶ(ぁ
確かにそんな感じw

だから俺としては、
・総合的に速度がほしい
・APIをガリガリ呼びたい
ものにはVC++を使い、
・お手軽に作りたい
・さほど複雑な処理を持たない
ものにはVC#を使う方向でやってますw

でもVC#は、GUIがVC++ほど自由に操作できないので、途中まで作ってちょっと拡張したい!って思ったときに出来ないとわかるとそこで投げてしまう...つД`)

.NETだけど、プラットフォームはWindows固定でいいので、手軽にWinAPI呼べるようなライブラリとかってないのかなぁ?
一応標準でも呼べるんだけど、構造体とか渡すときいろいろ手間が掛かってあまり現実的ではないんですよねぇ…。

ログインすると、残り3件のコメントが見れるよ

mixiユーザー
ログインしてコメントしよう!

C++ 超初心者の集い 更新情報

C++ 超初心者の集いのメンバーはこんなコミュニティにも参加しています

星印の数は、共通して参加しているメンバーが多いほど増えます。

人気コミュニティランキング