木曜日, 1月 31, 2008

C++解説8(template)

今回はtemplateです。はっきり言ってこれには感動します。

1. templateとは
私自身がC++で最も素晴らしい機能と思っているのがtemplateです。
templateというのは直訳すれば「型」という意味です。

言葉の定義をする意味で「型」を辞書で引くと
(1)外見に現れたかたち。かっこう。《形》
(2)相対的な特性によって区別される性質や形態。タイプ。《型》
(3)同種類の物を幾つも作る時、基にする枠や紙。ひながた・鋳型・型紙など。《型》

このあたりで良いでしょう。
C++で言うtemplateとは(3)が一番しっくりくると思います。

template機能は「基にする枠」を作成するという機能になります。
枠ですから、それは関数かもしれませんし、クラスかもしれません。

具体的にする1段階前の抽象的な状態で枠を作成し
使用する時に具体化するという事になります。


2. 型が違うだけの処理はtemplateで書け!
C++で型というと「int」「char」「long」などがそれに該当します。
「型が違うだけの処理」とは「int、longという型だけが違う処理」を指します。

ここでMAXという関数を作成します。
とりあえず「int」という型で作成すると

int max(int lhs, int rhs) {
return (lhs >= rhs ? lhs : rhs);
}

こんな感じですね。
float、double、char、longなど様々な型においても
同じように作成する事が行えます。

少し、C言語に慣れている人であれば
#define MAX(X,Y) X >= Y ? X : Y

なんてマクロを作成するかもしれませんね。
しかし、マクロは型の安全性においてまったく頂けないため
C++の型の安全性という考え方にはまったく合わず
型毎に作成するなどはメンドクサクテ行えません。
そもそも、maxなんて単純な関数だから良いですが
複雑な関数だった場合、かなり不都合が生じる事でしょう。


templateはこのような問題を解決してくれる考え方、機能となります。

templateの文法を利用して上の関数を書き直します。

template
T max(T lhs, T rhs) {
return (lhs >= rhs ? lhs : rhs);
}

という事になります。
難しい事は一つもありません。
型の箇所を「T」という型で統一し、それを「template」と書く事で
template宣言している事を示しているのみです。
また、使う場合も以下の書き方となります。

int n1=1, n2=2, n3;
long l1=1, l2=2, l3;

n3=max(n1,n2);
l3=max(l1,l2);

n3=max(n1,n2);
l3=max(l1,l2);

cout << "n3 =" << n3 << endl;
cout << "l3 =" << l3 << endl;

これを見ると一目瞭然ですが
呼ぶ方は何も気にする必要がないという事です。
これで、templateから対応する型に対して関数が作成され、
実行時にそれが動作する事になります。


max関数を眺めると「>=」という演算子しか使用していませんから
「>=」だけ定義されていれば型が何であれ使用ができるという事になります。

これが最大限発揮するのは対象がユーザー定義型となった場合でしょう。

たとえば、以下のような動物クラスがあります。

#include
#include

using namespace std;

class CAnimal {
string sName;
long nSpeed;

public:
CAnimal(string InName, long InSpeed = 1) {
sName = InName;
nSpeed = InSpeed;
}

CAnimal() {
sName = "";
nSpeed = 0;
}

bool operator >=(CAnimal &rhs) {
return (this->nSpeed >= rhs.nSpeed ? true : false);
}

string GetName() {
return sName;
}

};

これは、名前と移動スピードを管理しているだけのクラスです。
さらに「>=」という演算子をオーバーロードしていて
移動スピードの速い、遅いで演算子を評価しています。
つまり、max関数にこの動物クラスを与えると
スピードの速い動物を特定する事になります。
それでは実際に使ってみます。

template
T max(T lhs, T rhs) {
return (lhs >= rhs ? lhs : rhs);
}

int main(void) {
CAnimal obj1("Lion", 10);
CAnimal obj2("Tiger",15);
CAnimal obj3;

obj3 = max(obj1, obj2);
cout << obj3.GetName() << endl;
}

この実行結果は「Tiger」となります。

このようにユーザー定義型にも、templateとは使用できるのが
非常に効果的で、効率的なプログラムを作成する事ができます。
これの詳しい話は「STL」のところが一番良いと思いますので
「STL」の方で詳細を書きたいと思います。

3. 真髄はこちらにある。クラステンプレート
関数に対して、クラスのテンプレートという事になります。

文法は関数の時と同じで、テンプレート化する型を前方で宣言をし
後はその記号を使用して書き進めるという事になります。

C言語の型をラップするクラスをテンプレートで作成してみます。

template
class Rapper {
T m;
public:
Rapper (T in=1) { m = in; }

void SetValue(T in=1) { m = in; }
void Add (T in=1) { m += in; }
void Subtract(T in=1) { m -= in; }

T GetValue () { return m; }
};

簡単すぎるラップクラスです。
値を直接書き換える事ができません。
基本的に加算、減算のみが行えます。

これは「int,long,float,double」などC++に存在する
プリミティブな型をラップする事ができます。

Rapper IntRapper(10);
Rapper LongRapper(10);
Rapper FloatRapper(10.1);
Rapper DoubleRapper(10.1);

IntRapper.Add();
IntRapper.Subtract(4);
IntRapper.GetValue();
こんな感じで使用します。


クラステンプレートそのものは特に難しい話ではありません。
また、テンプレート引数とは1つである必要もなければ
関数の引数を与えるように使用する事も可能です。

template
class Rapper {
T m[MAX];
int nPos;
public:
Rapper () { nPos = 0; }

void SetValue(T in) { m[nPos] = in; }
void Add () { nPos++; }
void Subtract() { nPos--; }

T GetValue () { return m[nPos]; }

void PrintMax() { cout << "MAX=" << MAX << "\n"; }
};

これは、配列の要素数MAXをテンプレート引数で与えられる事になります。
「= 10」が表しているのはデフォルト引数ですから
引数として何も与えなければ「10」がMAXという事になります。

Rapper IntRapper1;
Rapper IntRapper2;

IntRapper1.PrintMax();
IntRapper2.PrintMax();

この実行結果は
MAX=10
MAX=20
となります


4.templateの特殊化
templateには特殊化という考え方が存在します。
ある特定の条件において特別な処理を行うケースを指します。

「copy」という関数について考えます。

template
void copy(T *lhs, T *rhs) {
*lhs = *rhs;
}

int main(void) {
int i = 0,j = 1;
cout << "(i,j)=(" << i << "," << j << ")\n";
copy(&i,&j);
cout << "(i,j)=(" << i << "," << j << ")\n";

char a[10], b[10];
memset(a, 0, 10);
memset(b, 0, 10);
memcpy(b, "test", 4);
cout << "(a,b)=(" << a << "," << b << ")\n";
copy(a, b);
cout << "(a,b)=(" << a << "," << b << ")\n";
}

この実行は期待する通りにはいきません。
文字列のコピーは代入演算子(=)では処理ができないからです。
そこで、char型についてだけ特殊なコピーを用意する事にします。

template<>
void copy(char *lhs, char *rhs) {
memcpy(lhs, rhs, strlen(rhs));
}

これはtempalte関数コピーを「char」についてのみ特殊処理を記述した事になります。
これをtemplateの特殊化と言います。
この特殊関数が存在すると上のプログラムは期待通りに動作すると思います。


今回説明をしたtemplateとは、
今後登場するSTLの説明には知識として不可欠となります。
書くことでイメージがはっきりすると思いますので
プログラムを作成する事をお勧めします。

酒が飲めない

最近めちゃくちゃ酒が弱くなった。
飲んだ量が以前の半分にも満たないにも関わらず
ほろ酔い状態になってしまう。
次の日は二日酔いではないが、体にだるさが残っている。

これはまずいと思うのだが、どうしようもないのも事実。
何か、悲しくなってしまいます。

ただ、ここ2回酔っ払っている店が同じであるため
そこの酒が非常に強いのではないか?
という思いもあるが、それにしても全てが原液だったとしても
以前であれば、飲めた量だと確信してしまう。

飲みすぎは体に良い事ではないが、
飲めないのも心に良くないと思ってしまいます。

火曜日, 1月 29, 2008

選択の基準

生活をしていると、様々な場面で物事を選択する場面に出くわす。
私が思うには選択した結果が正しかろうが、間違っていようが
ほとんどの選択状況においてはたいした問題は発生しない。

だからほとんどの選択は直感的に行っている場合が多いし
それが間違っていたところで「間違ってたんだ」くらいにしか思ってない。

しかし、それが非常に重要な結果を生み出す場合もある。
正しければ良いのだが、間違っていた時は、後悔する事もあったりする。

そこで選択を行う場合、必ず以下の事を
必ず満たす選択肢を優先して選ぶことにした。

優先順番は項番が小さいものを優先とする。

1.子供に取って良い方を選択する。
 これは子供が望むものというわけではない。
 私自身が子供の気持ちにたってそれがベストだと思ったものを選択する

2.自然である方を選択する
 ほとんどの選択肢は、自分、相手、会社などの利益が考えに入ってきて邪魔をしている。
 これを無くすには、それらを全て捨てる必要がある。
 「そこで、それを選択する事に無理がないか?」という事を必ず自問する。
 ここで言う、無理とは「それが自然ですか?」という意味で考える
 ※これはテレビ東京の「カンブリア宮殿」だったと思いますが、そこに出演した誰かの言葉です。
   非常に共感を覚えたため、自分の中での格言としました。


とりあえず、この2つです。
これは不変にする事はまったく意味がないので
常に共感を覚える言葉、考え方、などがあれば変えて行きます。
変化し続ける事が一番大事なので。

月曜日, 1月 28, 2008

アドマイヤメインは...

今回のAJCCは惨敗でしたね。
エアシェイディとアドマイヤメインの2頭軸での勝負だったのですが
3コーナーから4コーナーにかけて早々と失速。
買い続けているエアシェイディの
待望重賞初勝利にあやかる事はできませんでした。

それにしてもアドマイヤメインって...
騎手が悪いのか、調教師が悪いのか、馬が悪いのか
やっぱり馬主が悪いのか...

あの負け方はショックですね。
本当に強い馬だと思っているのですが結果が付いて来ませんね。
気性的に難しい馬なのですかね。
やっぱりこの馬の強さを引き出せるのは武豊だけなのですかね。
アドマイヤの馬に武豊が騎乗する事もなくなってしまいましたから
残念ですが、この馬がレースを勝つ事はもう難しいのかもしれませんね。

しつこいですが、あの負け方はショックです。
直線までも持たないとは。

エアシェイディは逆に完璧でしたね。
さすがは後藤騎手です。
正直、直線に向いた時には出るところがないかなっても思ったのですが
馬の強さ、騎手の度胸など完璧にハマった感じがしました。

この馬は横山騎手よりは、確実に後藤騎手の方が向いている気がします。
次は、何処を目標にするのかは分かりませんが頑張って欲しいですね。

馬券はアホな事に京都11Rと中山11Rを間違って購入する始末。
余計な投資もかさみ、不必要な大負けでした。

付きがない週末

先週末は去年購入したパソコンのCDドライブが壊れるという
ハプニングが発生してしまいました。

DVDのリッピングソフトをインストールして
使用していたら、突然の無反応です。

メーカーに問合せしたところ、
どうにもリッピングソフトというところに
引っかかったのでしょうね。

いろいろと試しましたが、復旧せず
再セットアップを行う事になってしまいました。
しかし、2時間かけて行った作業はそれでも復旧せず
結局メーカー保証中であるため修理になりました。

本日サポートから電話があって、
パソコンを取りに来る日程等の調整を行ったのですが
その中で、「お客様の過失があった場合は有料になる場合もある」
でもって「修理を拒否した場合でもお金がかかる」とのお話。

まったく意味が分からなかった私は
「過失とはどのような事を指すのですか?」と質問をすると
「お客様の場合はリッピングソフトをインストールしたりしていますので...」
とのお話...
「インストールしてはいけなかったのですか?」と続けて質問をすると
「そうではないのですが...」と良く理解のできない応対。

「では過失とはどのような事を指すのですか?」と初めの質問に逆戻り...
で向こうの答えは変わらずで同じ。

まったく日本語が通用しないサポートの人間に
「他の人に代わってくれ」とお願いすると
「他の人間に代わっても同じです」とのお答。
本当に人を怒らせるのが上手い人です。

だんだん頭に来た私は、最後には電話を切ってしまいました。

どうして、こんな人間にサポートデスクをやらせるのか意味が不明です。

明らかにリッピングソフトが原因だと言いたげでしたね。
リッピングソフトがきっかけでいろいろな犯罪が発生しているのは認識していますが
リッピングソフトを否定する事はやめて欲しいですね。

もし、インストールして事がハード障害を発生させ
それが使用者の過失と言うのであれば、
メーカーとしてインストールするなと言う義務があるのではないかと...

さらにメーカーサポート期間中であるにも関わらず、
故障の修正が有料なのは一歩譲って良いとしても
「有料と判断された時に、お金を出してまで修理する必要はない」と
判断した時に、お金がかかるというのが
まったく理解できません。
メーカーサポート期間って一体なんだって感じです。

C++解説7(参照)

1.1 参照とは
ポインタについては理解できたと思います。
しかしC++ではポインタより便利な概念の参照というものが導入されました。
これは非常に有効な機能であるため覚えておくと大変いいでしょう。
参照とは関数側で関数呼び出し元で引数に与えている変数のアドレスを参照する事を宣言します。
まあ、言葉で聞くより1回プログラムを書いてしまうのが
これについては一番いいと思いますので、簡単なswap関数を記述します。

#include "stdio.h"

void c_swap( int *x, int *y ) {
int z;
z = *x;
*x = *y;
*y = z;
}

void cpp_swap( int &x, int &y ) {
int z;
z = x;
x = y;
y = z;
}


int main(void) {
int a, b;
a = 1; b = 2;

printf("a = %d, b = %d\n", a, b );
c_swap( &a, &b );
printf("a = %d, b = %d\n", a, b );
cpp_swap( a, b );
printf("a = %d, b = %d\n", a, b );

return 0;
}

実行結果
a = 1, b = 2 // 代入した値
a = 2, b = 1 // c_swap後
a = 1, b = 2 // cpp_swap後


内容は簡単に理解できると思いますので、参照の文法についてから触れます。
参照とは変数宣言に&を使用します。
ポインタは*に対して、参照は&と記述するだけなので
そんなにここについては抵抗がないと思います。
参照された引数については、実際のアドレスを参照する事になります。
そのため、値を変更すると参照先のアドレスである実際に
引数として設定した値が変更される事になるのです。
また、参照で指定した引数は通常に宣言したものとして同じように使用できるため
「->」(アロー演算子)など必要とせず「.」という演算子でアクセスできます。
これは効率の面からも可読性の面からも大変すぐれた仕組みだと言ってよいでしょう。

関数呼び出しとは一般的にオーバーロードが発生します。
C言語で変更する必要がないためアドレスは渡さないと言った手法が存在します。
その場合、引数すべての値のコピーが発生します。
しかし、アドレス渡しとは参照するためのアドレスを格納する箱4Byte固定となります。
今ではC言語もconst付きで引数をアドレス渡しを行えば、
この事象は防ぐ事はできますが
それでも参照の可読性の高さには到底及びません。

1.2. ポインタから参照へ

ここまで説明してきたように参照とはどこかの変数に成り変る事です。
上では関数の引数について参照を扱ってきましたが、
もちろん通常に参照変数を宣言する事も可能です。

int i = 1;
int &r = i;

こんな感じですね。もちろん「r」を出力すれば「1」が出力される事になります。
この何がいいのかというのがと言う事について考えてみます。

まず何と言っても可読性が違います。
よくプログラムを分かり易くするためにコメントを書くと言った事が言われます。
これについて勿論異論がある訳ではありません。
しかしコメントを書いてプログラムが分かり易くなるでしょうか?
ここはずばり「ノー」と言うしかありません。
コメントを入れて分かり易くなるのは、そこでのコードが何を行おうとしているのかであって
プログラム事態は分かり易くはなりません。
何を行おうとしているのか分かる事によってプログラムを読む手助けになると言う訳です。
プログラムを分かり易く書くというのとは、まったく別次元の話だと思います。
それにこの事を理解しないで以下のようなコメントを見た事があります。

// メモリーを確保する
p = (char*)malloc( 10 );

ふざけてるとしか思えません
これにコメントが必要ですか?
プログラムのコメントを書くという事と解説を書くという事が同じ事になっています。
プログラムを教材として使用する場合にその解説に記述するのであれば
多少は許せますが、自分で書くプログラム、職業として書くプログラムには不必要なコメントです。
少し脱線し過ぎました。

要するに、可読性が高いというのは
人間の目に主観的に捉えやすいという事だと思います。
C++がC言語より可読性が高いのはもちろん参照が全てを決める訳ではありません。
参照なんて、どちらかと言うと大して効果が出ない方の部類でしょう。
それでも、プログラムから「*」や「&」が圧倒的に少なくなる
プログラムを一度見てしまうと全然違います。

参照はめちゃくちゃ意識して書いて下さい。
いつか、それがスタンダードとなって自然に書くようになります。

金曜日, 1月 25, 2008

C++解説6(string型)

今回は一旦文法的な話から離れてC++は便利だって事に説明を割きます。
最初にタイトルになっているstring型とはありません。
stringとはクラスの名前です。
つまり、クラス宣言をしているようなものです。
しかし、ここではstring型と表現します。

文字列を表現する上でC言語というのは非常にダメ言語でした。
何を行うにも、演算子が使えず
strcmp、strcpy、strcat
など様々な関数を使用して処理の実現を行ったきました。

これはC++においても同じで結局プログラミング言語というのは
文字列を扱うのが難しいのでしょう。

それでもいろいろなプログラミング言語は様々な工夫を行って
文字列の扱いを簡単にする努力を行ってきたようです。
C++ではstringがそれにあたります。

stringとはクラスです。
クラスとは一言でいうと型です。

int i;
という文があれば、これは「i」という変数名で「int」型を宣言した事になります。
クラスとは型ですから、
string str;
という文があれば、「str」という変数名で「string」型を宣言したという事です。
準備はこれだけで終わりです。

それではC言語で苦労、又は著しく可読性を下げた
プログラムの簡素化のスタートです。


1.1.文字列比較
char str1[4] = "abc";
char str2[4] = "abc";

if (strcmp(str1, str2) == 0) {
}

C言語では上記のように記述しますが、stringを使用すると以下のようになります。

string str1("abc");
string str2("abc");

if (str1 == str2) {
}

これはどう考えても下の方が良いでしょう。
数値を比較しているかのようです。
どうしてこのような比較ができるのかは
クラス、演算子オーバーロードの2つを理解すれば分かります。
ここでは、上記比較が行えるという事が重要であり
char[]ではなくstringを使用した方が良いという点です。


1.2.文字列複写
文字列比較が「==」という演算子を使用して記述できると
いろいろと試して見たくなる事があるでしょう。
そして、そのほとんどが期待通りに動作すると思います。
string str1("abc");
string str2("def");
char *str3 = "ghi"
char str4[4] = "jkl";

str1 = str2;
str1 = str3;
str1 = str4;
この全てが成立します。
数値型では代入といい、文字列だけ複写という表現を使用すると思います。
同じ事を行うのですから、代入という表現で何も問題がなかったにも関わらず
そこに抵抗があったのは、記述方法に違いがあったからでしょうね。


1.3.文字列連結
連結も何の事はありません。
string str1("abc");
string str2("def");
char *str3 = "ghi"
char str4[4] = "jkl";

str1 += str2;
str1 += str3;
str1 += str4;
特に説明は不要だと思います。


1.4.要素へのアクセス
要素へのアクセスも同じ事です。
char str1[4] = "abc";
string str2("abc");

if (str1[0] == str2[0]) {
cout << "agree" << endl;
} else {
cout << "disagree" << endl;
}

は成立します。
本当に簡単だと思いませんか?
「strxxx」なんて関数は一切使用しなくなってしまいます。
しかも可読性が非常に高い。
C++はここへの拘りが非常に強いと思います。


1.5.検索
文字列からある特定の文字を探したい場合は
C言語ではどのようにするか?

ポインタを取得したい場合は
char *p
char str[] = "abcde";
char c = 'c';
p = strchr(str, c);

要素位置を確認したい場合は
int i;
char str[] = "abcde";
char c = 'c';
for(i = 0; i < strlen(str); ++i) {
if (str[i] == c) {
break;
}
}
とか書くのでしょうね。
下の例はポインタを知りたい場合でも有効ですが、
上の例と比べると見た目が今一です。

これが文字列検索に変わると
「strstr」という関数を使用するようになり
下の例では「strcmp」などを使用して書くことになります。

当たり前の事で何も思わないかもしれませんが
今一な事は常に頭に置いておく事で新しい事への発見ができます。
はっきり言って今一です。
結局、行っているのは「検索」です。
それを何で、何を検索するかで使い分けなくてはいけないのか?
頂けませんね。

上と同じ事を実現する場合のC++の例です。
string str("abcde");
char str1[] = "cd";
char c = 'c';
long l;

l = str.find(c, 0);
l = str.find(str1, 0);
見ただけで何を行っているのかピンと来る事でしょう。
findしているのです。
また、findにはいくつか種類があります。

find_first_of : 前方から検索し、最初に検出された箇所の位置を返します。
find_first_not_of : 前方から検索し、最初に違った箇所の位置を返します。
find_last_of : 後方から検索し、最初に検出された箇所の位置を返します。
find_last_not_of : 後方から検索し、最初に違った箇所の位置を返します。

また、探し方も文字でも文字列でも可能で本当に便利になりました。
細かく理解をしょうとするとここでも覚える事は沢山あります。
でも難しい事は後にして、便利になった箇所を沢山使っていきましょう。
最後に理屈を知っていれば十分です。


1.6.挿入、削除
文字列操作において、良く使用する機能って何なのか?
人によるのでしょうが難しい問題です。
文字列を操作する事よりは文字列を一つのデータとして扱う場合に
非常に扱いづらかっただけで
ここから記述する事に関しては、C言語だけが抱える問題ではない気がします。
様々な言語は容易にする仕組みを考えています。

挿入とは文字列のある位置に別の場所に保存している
文字、文字列を入れる事を指します。
C言語であれば、以下のようなプログラムになるかと思います。
char str1[10] = "abcf";
char str2[] = "de";
char *strInsPos;

strInsPos = strchr(str1, 'c');
strInsPos++;
memmove(strInsPos + strlen(str2), strInsPos, strlen(strInsPos));
memcpy(strInsPos, str2, strlen(str2));
こんな感じでしょうか?
さらに安全に記述すると
if ((strlen(str1) + strlen(str2)) < 10) {
memmove(strInsPos + strlen(str2), strInsPos, strlen(strInsPos));
memcpy(strInsPos, str2, strlen(str2));
} else {
/* 拡張サイズ不足 */
}
要するにメモリ範囲外へのアクセスを未然防止ってロジックが必要になります。


C++のstringを使用するとになると、以下の通りで済みます。
string cppstr("abcf");
char cppstr2[] = "de";
long l;

l = cppstr.find('c', 0);
cppstr.insert(l + 1, cppstr2);
メモリに関するいろいろな処理が無くなっています。
そんな事に苦労する必要がないって事ですね。

反対に削除の場合はどうするかと言うと
string cppstr("abcf");
long l;

l = cppstr.find('c', 0);
cppstr.erase(l + 1);
これは'f'が削除される例です。

この辺りになってくると頻繁に使用するかどうかは微妙なところもありますが
使う場合はこんなにも違うって事ですね。


1.7.文字列トークン分割
私の経験では、それなりに使用する場面がありましたが
一般的にはどうなのか分りません。
csvファイルを解析する際に、必要となる機能です。
C言語ではこれを「strtok」関数で実現していました。
char str[] = "abc,def,hij,klm";
char *tmp;

tmp = strtok(str, ",");
while(tmp != NULL) {
printf("%s\n", tmp);
tmp = strtok(NULL, ",");
}
C++では...
実はこいつが非常にイケてません。
はっきり言って相当するものはありません。
C言語を知っている人であれば、strtokを使用して下さい。
もちろん、これを実現するクラスを作成すれば良いのですが
ここでは見送りです。


と言う事でstringについてはお終いです。
char*より優れている事は分って頂けましたでしょうか?
分かって頂ければ光栄です。

木曜日, 1月 24, 2008

C++解説5(クラスについて2)

1. インスタンス変数/関数とクラス変数/関数

クラスとは型宣言である事は述べました。
しかし、そこはユーザー定義型のためいろいろな事ができるようになっています。
その中の一つにクラス変数とクラス関数というものがあります。

最初にインスタンスという言葉について触れておきましょう。
インスタンスとは「例えば」何て訳を学校で習った覚えがあるのは私だけではないでしょう。
普通、「例えば~」と説明する場合、~には具体的な例などを上げるのだと思います。
何となく近づいてきましたかね。
ここではインスタンスとは実態を指します。
int i;

という宣言文があった場合、iをインスタンスと言います。
classの宣言はintの宣言と言う事になります。
C++において、int,doubleと言った型はクラスではありません。
しかし、C++の仕様においてintと宣言した場合は32bitで表現可能範囲を示すとか、
unsigned intと記述された場合は最初に符号ビットを必要としないため、
intより+範囲に大きく数値を管理できるなど、すべて仕様として決められています。
この仕様を決めるのがクラスの定義となります。しかし、定義だけでは使用できません。
構造体でも定義だけではなく、実態を作成する宣言と言った事が必要となります。
この実態宣言されたものをインスタンスと言います。

class Human {
private:
char *Name;
int Age;
char Birthday[9];
public:
Human(char*, int, char*);
~Human();
};

とクラス定義があった場合に

Human obj1;
Human obj2;
と宣言されると、obj1のName,Age,Birthdayとobj2のName,Age,Birthdayは
全く別のものになります。もちろん、Human()も~Human()も同様です。
これらをインスタンス変数、インスタンス関数と表現します。

では一体、クラス変数、クラス関数とは何でしょうか?
これはインスタンスのものではなく、クラスのものという事になります。
C++ではこのような変数又は関数をstaticを付けて表現します。
たとえば、世の中の人口を管理すると仮定します。
そんな値を管理するのは何処が適しているでしょうか?
いろいろと分析方法は異なるため、何処が一番適しているという答えはありません。
しかし一つの答えに人間の事は全部人間のクラスで管理しろ!という考え方があっても良いでしょう。
ここではそれをクラス定義に盛り込み、実際に使用してます。

--------------------------------------------------------------------------------
class Human {
private:
static int HumanCount; // クラス変数
static void HumanCountAdd(); // クラス関数非公開
static void HumanCountSubtract(); // クラス関数非公開

char *Name;
int Age;
char Birthday[9];
public:
Human(char*, int, char*);
~Human();

static void HumanCountPrint(char*); // クラス関数公開
};

int Human::HumanCount = 0;

void Human::HumanCountAdd() {
HumanCount++;
}

void Human::HumanCountSubtract() {
HumanCount--;
}

void Human::HumanCountPrint(char* Title) {
std::cout << Title << ":HumanCount = " << HumanCount << "\n";
}

Human::Human(char *InName, int InAge, char *InBirthday) {
Name = new char(strlen(InName));
strcpy(Name, InName);
Age = InAge;
strcpy(Birthday, InBirthday);
// std::cout << "(" << Name << ") Human::Human()\n"; // ←今回は削除
Human::HumanCountAdd(); // ここにカウントアップを追加
}

Human::~Human() {
// std::cout << "(" << Name << ") Human::~Human()\n"; // ←今回は削除
delete Name;
Human::HumanCountSubtract(); // ここにカウントダウンを追加
}

int main(void) {
Human::HumanCountPrint("no create"); // この関数で現在の数をプリントします

Human obj1("semona", 0, "20040101");

Human::HumanCountPrint("obj1 create");

Human *obj2 = new Human("yuuna", 1, "20030101");

Human::HumanCountPrint("obj2 create");

delete obj2;

Human::HumanCountPrint("obj2 delete");
}
-------------------------------------------------------------------------------
実行結果
-------------------------------------------------------------------------------
no create:HumanCount = 0
obj1 create:HumanCount = 1
obj2 create:HumanCount = 2
obj2 delete:HumanCount = 1
-------------------------------------------------------------------------------
という結果が得られます
「う~ん全部なくなってるはずなのに0にならない」という問題はありますが
クラス変数、関数については理解できたと思います。
ちなみに、「HumanCountPrint」をprivate宣言に変えると、コンパイルエラーが出ます。
クラス関数を非公開にすると言う事は当クラスのインスタンス外からのアクセスを禁止する
と言う事になります。変数についても同様です。
という説明をするために、「HumanCountPrint」を公開関数にしたんですけどね。

最後はかっこよく0にしたいから、「HumanCountPrint」をコンストラクタ又はデストラクタで呼び出すように変更してみます。
--------------------------------------------------------------------------------

#include "iostream"
#include "cstring"
#include "cstdio"

class Human {
private:
static int HumanCount;
static void HumanCountAdd();
static void HumanCountSubtract();
static void HumanCountPrint(char*);

char *Name;
int Age;
char Birthday[9];
public:
Human(char*, int, char*);
~Human();

};

int Human::HumanCount = 0;

void Human::HumanCountAdd() {
HumanCount++;
}

void Human::HumanCountSubtract() {
HumanCount--;
}

void Human::HumanCountPrint(char* Title) {
std::cout << Title << ":HumanCount = " << HumanCount << "\n";
}

Human::Human(char *InName, int InAge, char *InBirthday) {
Name = new char(strlen(InName));
strcpy(Name, InName);
Age = InAge;
strcpy(Birthday, InBirthday);
// std::cout << "(" << Name << ") Human::Human()\n"; // ←今回は削除
Human::HumanCountAdd(); // ここにカウントアップを追加

char str[32];
memset(str, 0, 32);
std::sprintf(str, "%s create", Name);
Human::HumanCountPrint(str);
}

Human::~Human() {
// std::cout << "(" << Name << ") Human::~Human()\n"; // ←今回は削除
char str[32];
memset(str, 0, 32);
std::sprintf(str, "%s delete", Name);
delete Name;
Human::HumanCountSubtract(); // ここにカウントダウンを追加

Human::HumanCountPrint(str);
}

int main(void) {
Human obj1("semona", 0, "20040101");
Human *obj2 = new Human("yuuna", 1, "20030101");

delete obj2;
return 0;
}

-------------------------------------------------------------------------------
実行結果
-------------------------------------------------------------------------------
semona create:HumanCount = 1
yuuna create:HumanCount = 2
yuuna delete:HumanCount = 1
semona delete:HumanCount = 0
--------------------------------------------------------------------------------
と言う事になります。

火曜日, 1月 22, 2008

C++解説4(クラスについて)

今回はオブジェクト指向と言われるもので
このクラス表現と言われるものがかなりの比重を占めています。
一言で言ってしまうとクラスとは構造体に関数を持たせたものです。
もちろん、それだけではない、様々な機能を提供してくれます。
クラスの文法は構造体と基本的に変わりません。

struct 構造体名 {};
に対して
class クラス名 {};
という感じです。

アクセス修飾子のpublic,protected,privateというものがありますが
順々に覚えていけばいいので、はじめは無視してしまいましょう。

class CHuman {
};

終わりです。もちろんこれだけでは何も使用できません。
そこで、名前と年齢という変数を持たせてみると

class CHuman {
char *strName;
int nAge;
};

#include "stdlib"
#include "string"

int main(void) {
CHuman obj;
char *Name = "名前";
obj.strName = (char*)malloc( strlen(Name) + 1 );
strcpy( obj.strName, Name );
obj.nAge = 20;
return 0;
}

となります。まあ、これだけであればクラスなんてものは必要ありません。
構造体と何も変わらないからです。
まあ、構造体の場合はtypedefしないと「struct 構造体名 変数名」としていた箇所を、
クラスの場合はtypedefしなくても
「クラス名 変数名」記述すれば良い分少しだけ良くなったかも!
何て言ったところで覚える価値はありませんね。
それではどんどん拡張して覚える価値のあるものにして行きましょう。

まず最初にアクセス修飾子に触れてみます。

class CHuman {
public:
char *strName;
int nAge;
};

と記述してみます。
先ほど、記述したメインと合わせてコンパイルすると何も変化がありません。
そうです。何もありません。

class CHuman {
private:
char *strName;
int nAge;
};

次にpublicと記述した箇所をprivateと変更してみましょう。
コンパイルエラーとなってしまいました。
privateとはずばり、クラス外には見えなくする修飾子となります。
それに対して、publicとは見えるようにする修飾子です。
つまり、もともと省略していたためpublicと解釈されていただけだったのです。
ビックリしました? まあ、ビックリしませんね。
protectedに付いては、継承のところで説明します。今はこの2つで十分です。

ところで何のために必要なの?
なんて考えてしまった人のために少しだけ補足しましょう。
今回は人間をクラスとして考えていますのでこれを例に取ります。
C言語の構造体の考え方は人間に関係する変数を
一括管理できる事がすばらしい事でした。
しかし、よ~く考えてみると人の年齢を誰が上げたのかが(更新した)
構造体ではまったく分かりません。
もっと言うと、そもそも人の年齢って下がるものなのでしょうか?
現実世界で考えると、人の年齢は誕生日が来る事によって上がる事になります。
これは、不変な事で毎日年を取るとか何て事があると困ってしまいますね。
長寿記録などあっさり更新ですね。
このように、誰でも変更できたものをそうではなくした事が重要なんです。
でもどうやって変更するの?っと思ったら少し考えれば解決です。
なぜなら、publicと修飾されていた時は、誰でも変更できました。
これは、publicで修飾されたものは公開アクセスだったからですね。
公開アクセスとは、誰でも参照できますよって事です。
たっだらいっその事変更する関数をpublicに置けばいいんじゃないの!
何て考えが浮かべばいいですね。
一番最初にクラス説明した時に、
「一言で言ってしまうとクラスとは構造体に関数を持たせたものです。」って
ところがありました。構造体では事ですね。
※実際には関数ポインタなどを駆使して近いものを作成する事ができます

class CHuman {
private:
char *strName;
int nAge;
public:
void UpdateAge(void) {
nAge++;
}
};

こんな感じで書いておけば良いです。年をとる事しかできません。
でも、誰でも変更できるじゃん!と思うかもしれませんが
さっきと決定的に違うのは、変更された時が分かるという事です。
N歳になったら何かあるのであれば、これは問題なく対応可能と言う事です。
ちなみに、privateのような修飾子で変数の内容を隠す事ができます。
このようにする事を一般的に情報隠蔽、カプセル化などど表現します。

で簡単に文法を説明しておきますと

//
// クラス記述方法
//
class クラス名 {
private:
// ここに非公開変数、関数を記述する
// 関数は基本的に宣言のみ。簡単なコードが記述可能
// inlineのからみがあって、複雑なのは書けません
// いずれ機会を作って、inline関数については説明します
public:
// ここに公開変数、関数を記述する
// 関数については、private同様簡単なものだけです
};

//
// クラスメソッド(関数)記述方法
//
返却値 クラス名::関数名() {
// 関数の内容を記述
}

簡単ですが、こんな感じです。


クラスはコンストラクタで初期化を行い、デストラクタで後処理を行います。
クラスとは簡単に言ってしまえば型の定義です。
つまり、プリミティブ型としてlong,doubleなどが用意されているにも関わらず
そんな型じゃ話にならん!!ってな状況ってありますよね。
言ってしまえば、C言語であった構造体だって
型の定義じゃないですかてな話ですよ。
でもC言語の構造体っていろいろと問題があったわけですよ。

たとえば、説明しましたが構造体はすべての構造体の中の変数がグローバルですよね。
これって以外と使えね~って思いませんか。

「構造体の初期化ってどうやってたの?」
「構造体を破棄するときって、何もしなくていいの?」
などなど...
(ここがコンストラクタとデストラクタの説明箇所だから強調させて下さい)

たとえば、平面のポイントを表す構造体を宣言すると

struct Point {
int x_;
int y_;
};

struct Point p;

でその後に、実際今宣言したポイントは何処なのかってな話で

p.x_ = 1;
p.y_ = 1;

って書いていましたですよ! でもこれっておかしくね~~~。
だって宣言する時にはもうポイントが何処かなんて分かっているんですよ!!
だったら、宣言時に初期化させて下さいよ。

すると以下のように書き直します。
struct Point p = { 1, 1 };

でも以下の場合はどうでしょうか?

struct Point {
int x_;
int y_;
};

struct Human {
char Name[12 + 1];
int Age;
struct Point *p;
};

こいつは先ほどの流れで書くと

struct Human h;

strcpy( h.Name, "name" );
h.Age = 20;
h.p = ?????; // あれ、これって先に宣言しておかなければ駄目じゃん

となって

struct Point p = { 1, 1 };
struct Human h = { "name", 20, &p } ;

ってな感じになりますかね。
でもって何が言いたいかと言いますとそもそも構造体を作成する時に
「必要な情報を構造体とは別に私どもが情報を管理するなどど言う事はナンセンスだ!!」
と言う事ですよ。
上の例で言えば、人を作成するまでは、(「struct Human h」の箇所)
何処に人がいるかなどど言う事はまったく関係ない!(「struct Point p」の箇所)
人をあるポイントに作成するのであって、ポイントと人を作って結びつけるなどはあんたがやってくれって感じですよ。
さらに、人が死んだ(消滅した)時に何だってポイントを私どもが消さなくてはならないのですか?
そんなの人が死ぬ時に責任持って消してくれって感じですよ!(2回目)

そんなあなたの要望に答えてくれるものは
コンストラクタでありデストラクタなんですよ。

でコンストラクタから説明します。
ずばり、コンストラクタとはクラス(構造体)の初期化です。
というかその前に、C++においてクラスと構造体の違いを説明しておきましょう。

ずばり、違いはありません。
本当は少しだけあるのですが、無いと言って問題ありません。

class Point {
int x_;
int y_;
public:
int getPosX(void);
int getPosY(void);
};
と書いている人がいたら、思いっきり違いがあります。
なぜなら、構造体はデフォルトがpublicなんです。だから
struct Point {
int x_;
int y_;
public:
int getPosX(void);
int getPosY(void);
};
なんて書こうものなら、すべてpublicと言う事になります。

つまり、クラスを
class Point {
private:
int x_;
int y_;
public:
int getPosX(void);
int getPosY(void);
};
と書いていれば、classをstructに変えても何も違いはありません。
と言う事で極力、デフォルトを使用せずに明記するようにしましょう。
でも、C++ではstructと記述はあまりしませんね。
C言語の構造体のようなものを定義する必要性がある時に
structと記述するのが良いと思います。(定番ですかね)
でもそんな時ってないと思います。

で話を戻してコンストラクタですが初期化です。
この 「= 1」に相当します。
またまた少し挫折します。

// 初期化
int i = 1;

// 代入
i = 1;

C言語において上の例を初期化といい、下の例を代入と言います。
はっきり言って、最終的な結果は何も変わりません。
しかし、代入の「i = 1」の前に「if ( i == 1 )」何て挿入しようものなら
iの値は不定です。
代入と初期化の一番大きい違いは、代入は邪魔できるけど初期化はできないって事です。
そして、コンストラクタとはクラスの初期化です。
つまり、クラス変数を定義した時にはコンストラクタとは常に実行され
定義直後に参照したところで、コンストラクタとの間には入れないって事になります。
コンストラクタ有りのクラスを作成してみます。

class Human {
public:
Human();
};

このように、クラスと同じ名前を持つメソッド(関数)となります。
返却型はありません。何も返却しないからです。
このコンストラクタという関数の中にクラスの初期化処理を記述します。

少し上のクラスを拡張します。

class Human {
private:
char Name[12];
int Age;
char Birthday[9];
public:
Human();
};

このようなprivate変数を管理している場合、コンストラクタでprivate変数を
初期化してしまうのが最もポピュラーな使用方法となるでしょう。

Human::Human() {
strcpy( Name, "NameValue");
Age = 0;
strcpy( Birthday, "20000101" );
}

てな感じです。
う~ん、でもこれではあんまり使えないコンストラクタですね。
名前も、年齢も、誕生日も固定なんて!!
その場合は、コンストラクタに引数として貰えば良いだけです。

public:
Human( char*, int, char* );

何て宣言方法に変わって、そんでもって
Human::Human( char *InName, int InAge, char *InBirthday ) {
strcpy( Name, InName );
Age = InAge;
strcpy( Birthday, InBirthday );
}

ちなみに、引数のまったく持たないコンストラクタを
一般的にデフォルトコンストラクタと言います。
覚えておきましょう!

次はデストラクタです。
これは初期化に対して終了処理、後処理という言い方が正しいと思います。
つまり、クラスを破棄する時にただ破棄して問題がある場合に
ここで後処理を行うという事になります。
たとえば、上のクラスでは名前がName[12]という形式で宣言していました。
しかし、名前なんてどのくらいの長さなのかは分かりません。
そこで、名前はchar*という形式に置き換え、
コンストラクタで指定されたサイズを格納できるように設定するように変更します。

class Human {
private:
char *Name;
int Age;
char Birthday[9];
public:
Human( char*, int, char* );
~Human();
};

Human::Human( char *InName, int InAge, char *InBirthday ) {
Name = new char(strlen(InName));
strcpy( Name, InName );
Age = InAge;
strcpy( Birthday, InBirthday );
}

Human::~Human() {
delete Name;
}

このようにすると単純にクラスを破棄した場合、Nameはメモリーリークする事になります。
そこでデストラクタの登場という訳です。
このデストラクタによってメモリーリークを防ぐという事になります。
それでは実際にprint分を入れて実験してみましょう。

#include "iostream"
#include "cstring"

class Human {
private:
char *Name;
int Age;
char Birthday[9];
public:
Human( char*, int, char* );
~Human();
};

Human::Human( char *InName, int InAge, char *InBirthday ) {
Name = new char(strlen(InName));
strcpy( Name, InName );
Age = InAge;
strcpy( Birthday, InBirthday );
std::cout << "(" << Name << ") Human::Human()\n";
}

Human::~Human() {
std::cout << "(" << Name << ") Human::~Human()\n";
delete Name;
}

int main(void) {
Human obj1( "semona", 0, "20040101" );
Human *obj2 = new Human( "yuuna", 1, "20030101" );

delete obj2;
}

--------------------------------------------------------------------------------
実行結果
--------------------------------------------------------------------------------
(semona) Human::Human() // Human obj1( "semona", 0, "20040101" );
(yuuna) Human::Human() // Human *obj2 = new Human( "yuuna", 1, "20030101" );
(yuuna) Human::~Human() // delete obj2;
(semona) Human::~Human() // }
--------------------------------------------------------------------------------
特に解説は不要だと思いますが、
実行結果の後ろに付けたコメントは実際に
何処で動作しているかのポイントを示しています。
クラスをポインタで定義したobj2はnewでコンストラクタ、
deleteでデストラクタがobj1は宣言時にコンストラクタ、
そしてそれがスコープ外となった時にデストラクタがそれぞれ動いています。

月曜日, 1月 21, 2008

土曜日の的中でプラスに転じた

今週は、土曜日の中山11R、京都11R、12R
日曜日の中山11R、京都10R、11Rと合計6Rの購入でした。
的中は土曜日の中山11Rと京都12Rで今年の収支がプラスになりました。

惜しい事をしたのは、中山10Rのコンラッドと中山11Rのマイネルチャールズの2頭で
単勝馬券か馬連かで迷い馬連を選択。
相手不明の10Rは購入を見送り。
マイネルチャールズの相手も不明だったのですが
購入しないのも寂しいので、唯一の牝馬リトルアマポーラを指名。
馬連1点の勝負でしたが、残念ながら...
幸四郎君の追い出しが遅かったんではないか?とか思ったりしました。
もしかしたら馬がずぶかったのかもしれないけど...
見ていたら、ここだってところで行かずに後で追い上げるも
勝負は決した後って感じに見えました。残念です。

京都11Rは3週連続というところが気になったところではあったのですが
つまらない予想で岩田さんのアドマイヤジュピタから、ヒラボクロイヤルを相手に指名。
まったく的外れな予想となり、くやしくも何ともない大負けです。

負け分も何とか取り戻したため、来週のAJCCから再スタートですね。

C++解説3(ポインタの確認)

C言語を勉強している多くの人がポインタで挫折するようですね。
何でポインタが難しいのか考えてみます。

int i;
このような変数宣言があるとiはint型の変数である事が宣言されています。
int型とは一般には32bitで構成される数値を管理する型となりますよね。

ポインタもこれとまったく同じ考え方です。
int *p;
このような変数宣言があるとpはint*型の変数である事が宣言されています。
int*型とは32Bitで構成されるアドレスを管理する型となります。

でも宣言は基本的にこれですべてです。
整理して考えれば難しい話は一つもありません。

void swap( int *p, int *q ) {
  int s;
  s = *p;
  *p = *q;
  *q = s;
}

上の関数は入れ替え(swap)を行っていますよね。
ポインタを使っていますが、多分これを難しいと思う人はいないと思います。
簡単に説明するとC/C++において「*」は演算の意味を持っています。
int型に*と書かれていれば、相手ありきとなりますが掛け算となります。
intポインタ型に*と書かれていれば、そのアドレスが指す値と言う事になります。
そのため、上のswap関数は引数としてもらったアドレスが指す要素の値を
書き換える事ができているのです。
つまり、C言語ではアドレスを渡さないと呼び出された関数側では
値を書き換える事ができないという事になります。
関数で引数として宣言している変数は、呼び出し元とは別の領域が確保されます。
関数側ではその値を書き換える事になります。
よって関数側にアドレスを渡し、引数として受け取った関数は
そのアドレスが指す値を変更する事で値の変更を行えるのです。


もう一つ重要な話にC言語において配列はポインタとなるという話があります。

int i;
この宣言がスタック領域に詰まれると以下のようになるとします

「iの領域」
ここにintで宣言されていれば32Bit格納できる大きさで領域が確保されます。
0 = 00000000 00000000 00000000 00000000
1 = 00000000 00000000 00000000 00000001
ってな感じになりますね。

int array[4];
この宣言が行われれば当然intの箱を4つ作れと宣言されているので
32Bit格納できる大きさの領域を4つ分確保します。
つまり上の「iの領域」が連続して4つ確保される事になります。

ここで問題があります。
「i」と宣言したものは当然「i = 0;」と書かれれば
「i」の領域に代入すればいいのですが
「array = 0;」と書かれた場合は
何処に代入すればいいのか分からないという事になります。
そこで代入する時には修飾子として「array[0] = 0;」
といった感じでindexを指定します。
では「array」とは何を指しているのでしょうか?
C言語では配列の変数名は先頭のアドレスを指している事になっています。
つまり「array == &array[0]」という事になります。

では実際に配列を関数に渡してみます。

#include "stdio.h"

void PrintArray( int *Array, int ArraySize ) {
  int i;
  for( i = 0; i < ArraySize; ++i ) {
    printf("%d,", Array[i] );
  }
}

int main(void) {
  int i, Size, Array[10];

  Size = 10;
  for( i = 0; i < Size; ++i ) {
    Array[i] = i;
  }
  PrintArray( Array, Size );

  return 0;
}

実行結果
0,1,2,3,4,5,6,7,8,9,


同様に文字列を関数で出力してみます

#include "stdio.h"

void PrintArray( char **Array, int ArraySize ) {
  int i;
  for( i = 0; i < ArraySize; ++i ) {
    printf( "%s,", Array[i] );
  }
}

int main(void) {
  int Size;
  char *Array[2] = { "senna", "yuuna" };

  Size = 2;
  PrintArray( Array, Size );

  return 0;
}

実行結果
senna,yuuna,

intの配列と何も変わりませんね。
配列はポインタとなるところはそのままなので、
ポインタの配列でポインタのポインタと
なってダブルポインタという事になります。
特に難しい話ではないと思います。

このダブルポインタというものは混乱する人も多いようですが
C言語は文字列はcharの配列になる。
charの配列と言えば、配列なのでポインタとなる。
ポインタの配列と言えば、ポインタのポインタとなる。
何て順序立てて考えれば、難しい事はないと思います。

文字列の配列を関数に渡す事に成功しました。
と言う事は多次元配列(文字列はcharの配列のため)について渡す事に
成功したという事になります。
であれば、int _2DArray[10][10];で宣言されている配列を渡す事も問題なく
理解できると思います。何ていっときながら失敗しちゃうんですよ!
多分、上記の流れでは以下のプログラムをイメージする事でしょう。

#include "stdio.h"

void PrintArray( int **Array, int _1DArraySize, int _2DArraySize ) {
  int i, j;
  for( i = 0; i < _1DArraySize; ++i ) {
    for( j = 0; j < _2DArraySize; ++j ) {
    printf( "%s,", Array[i][j] );
   }
  }
}

int main(void) {
  int _1DSize, _2DSize;
  int Array[3][3] = { 1, 2, 3, 4, 5, 6 };

  _1DSize = _2DSize = 3;
  PrintArray( Array, _1DSize, _2DSize );

  return 0;
}

コンパイルエラーという結果がでたと思います。
2次元配列は文字列の配列とは異なって、同じような引渡しができません。
このような配列を引数として渡す場合、配列数を解決しておく必要があります。
配列とはポインタになる事は問題ないと思いますが、
2次元配列だからダブルポインタという考え方が間違っている事になります。
配列は2次元だろうが、3次元だろうがポインタです。
もちろん、intポインタを格納する配列を作成する事も可能です。
intポインタとは配列を指すため(まあ配列とは限りませんが)
ポインタの配列となり、先ほどの文字列の配列と同じ扱いになります。
それでは、実際にコンパイルエラーとなったプログラムを
コンパイルエラーとならない状態に修正して実行しましょう。

#include "stdio.h"

void PrintArray( int Array[3][3] ) {
  int i, j;
  for( i = 0; i < 3; ++i ) {
    for( j = 0; j < 3; ++j ) {
      printf( "%d,", Array[i][j] );
    }
  }
}

int main(void) {
  int Array[3][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
  PrintArray( Array );
  return 0;
}

実行結果
1,2,3,4,5,6,7,8,9,


最後にintポインタの配列を格納するプログラムを書いてみます。

#include "stdio.h"

void PrintArray( int **Array, int _1Size, int _2Size, int _3Size ) {
  int i, j, ArraySize[3];

  ArraySize[0] = _1Size;
  ArraySize[1] = _2Size;
  ArraySize[2] = _3Size;

  for( i = 0; i < 3; ++i ) {
    for( j = 0; j < ArraySize[i]; ++j ) {
      printf( "%d,", Array[i][j] );
    }
  }
}

int main(void) {
  int *Array[3];
  int Array0[3] = { 1, 2, 3 };
  int Array1[4] = { 4, 5, 6, 7 };
  int Array2[5] = { 8, 9, 10, 11, 12 };
  int _1Size, _2Size, _3Size;
  _1Size = 3;
  _2Size = 4;
  _3Size = 5;

  Array[0] = Array0;
  Array[1] = Array1;
  Array[2] = Array2;

  PrintArray( Array, _1Size, _2Size, _3Size );

  return 0;
}

実行結果
1,2,3,4,5,6,7,8,9,10,11,12,

金曜日, 1月 18, 2008

あまりにもあっけなく壊れた

子供にクリスマスプレゼントとして買って上げた
アンパンマンのハンバーガー屋さんが
1ヶ月もしないのに壊れた。
何が原因か分からないが、選択したメニューで止まるはずが
まったく止まらなくなってしまった。
子供のおもちゃとしては物凄く高い買い物だっただけに
非常にショックである。

値段については高すぎる気もするけど
単純なおもちゃではなく、コンピューターが考えるような
仕組みも存在しているため
それなりの値段がするのも納得はできるが
あまりにも早く壊れ過ぎである。

やっぱり今後は、子供のおもちゃについては
あんまり高いのは買ってはいけないなって思ってしまいました。
子供の扱い方もあるのかもしれないけど
対象が子供のおもちゃだから
それなりに作るべきだと思いますので...

無料対応はして頂けるようですが
送ったりするのはやっぱり手間です。

火曜日, 1月 15, 2008

姑獲鳥の夏

京極夏彦さんのデビュー作という事らしいです。
私にとっても京極夏彦さんの初作品です。
文面が少し難しいところもありましたが
内容は非常に面白いもので、一瞬でその世界に引き込まれました。
正直、本筋のストーリーより仮想現実についての話が
物凄く面白かったです。
考え方の幅が少し広がった気がします。

第2弾は現在、映画だかドラマになる「魍魎の匣」です。
デビュー作がこんなに面白かったので非常に楽しみです。

C++解説2(ストリーム)

1.streamのイメージを掴む(ストリーム)
C++ではstreamという考えた方が基本である。
streamとはかなり高機能の入出力クラスです。
おなじみにプログラム「Hello World!」を書いてみます
---------------------------------------------------------------------
#include

using namespace std;

int main(void) {
cout << "Hello World!\n"; return 0; } --------------------------------------------------------------------- 「using namespace std;」はおまじないだと思ってここでは無視して下さい。 いずれ解説をします。 何処が違うかは分かったと思います。 「cout」ってのが「printf」に相当するものであるって理解で十分です。 これだけだと何で変更する必要があるのかって事が今一ですよね。 「cout」の最大の特徴は「printf」のように「%d,%s,%c」などと言った 出力する型の指定を行わなくて良いところです。 どんな型であれ、「<<」と使用する事でストリームに出力されます。 --------------------------------------------------------------------- char aName[32]; printf("What your first name?\n"); scanf("%s", aName); printf("My name is %s", aName); --------------------------------------------------------------------- だったり --------------------------------------------------------------------- char c; printf("What do you like alphabet?\n"); scanf("%c", c); printf("I like spell %c, too", c); --------------------------------------------------------------------- します。 ここで何が言いたいかと言えば、printfは出力する変数の型を 指定する必要があるという事です。 当たり前のようでいて、とても頂けない文法だと思いませんか? その点、C++のストリームは何も考えずに出力できます。 2.何でも流し込め!(ストリーム) ストリームに流しこむイメージは掴めたと思います。 こうなると何でもかんでも流し込みたいのが人間の嵯峨というものです。 ※私だけでしょうか? という事でいろいろと流し込んでみます。 最初に思いつくのは、ファイルでしょうね。 C言語には「fprintf」という関数があります。 ファイルポインタを指定して、出力先を指定すれば 後は「printf」と何一つ違いがない関数です。 この話の流れはファイルに出力する方法も同じって事です。 C言語のプログラムとC++のプログラムを比較しながら見てみます。 --------------------------------------------------------------------- C言語 --------------------------------------------------------------------- #include

int main(void) {
FILE *fp;

char c1 = 'a';
char c2 = 'b';
char a1[] = { "cdefg" };
char a2[] = { "hijkl" };
int i = 1;
long l = 2;

fp = fopen("sample.txt", "w");
if (fp == NULL) {
printf("File Open Error!\n");
return -1;
}

fprintf(fp, "%c", c1);
fputc(c2, fp);

fprintf(fp, "%s", a1);
fputs(a2, fp);

fprintf(fp, "%d", i);

fprintf(fp, "%ld", l);

fclose(fp);

return 0;
}

---------------------------------------------------------------------
C++
---------------------------------------------------------------------

#include

using namespace std;

int main(void) {
ofstream fout;

char c1 = 'a';
char c2 = 'b';
char a1[] = { "cdefg" };
char a2[] = { "hijkl" };
int i = 1;
long l = 2;

fout.open("sample.txt");
if (!fout) {
printf("File Open Error!\n");
return -1;
}

fout <<>というヘッダをincludeする必要があります。
以下にマニピュレータの一覧を示します。(抜けがあるかもしれません)

マニピュレータ  概要
endl 改行文字を出力し、ストリームをフラッシュする
ends ヌルを出力
flush バッファをフラッシュする
ws 先頭から連続しているspaceを破棄する
setiosflags(fmtflags) 引数に指定したフラグを全て有効とする
resetiosflags(fmtflags) 引数に指定したフラグを全て無効とする
setw(int) 次に出力値について出力幅の最低サイズを指定する
setfill(char) 出力幅に満たない場合、不足分を指定した文字で補う
setbase(int) 数値型を出力する場合、基数を同時に出力する。
設定できる値は「8」「10」「16」の何れかとなる。
setprecision(int) 実数で定義されている変数に対して精度の桁数を指定
boolalpha bool値の入出力にtrue、falseの使用を有効にする
showbase 数値型を出力する場合、基数を同時に出力する事を有効にする
showpoint 実数の出力時に小数点の出力を有効とする
showpos 数値出力時に先頭に符号出力を有効とする
skipws stream入力時の先頭から連続しているspaceを破棄するのを有効にする
unitbuf 挿入操作の度にバッファをflushするのを有効にする
uppercase 基数の文字xを大文字出力するのを有効にする
dec 10進数出力
hex 16進数出力
oct 8進数出力
fixed 実数を通常表記で出力する
scientific 実数を科学技術表記で出力する
internal 出力幅が満たされるまで数値の追加が行われる
right 出力を右寄せにする
left 出力を左寄せにする
noboolalpha bool値の入出力にtrue、falseの使用を無効にする
noshowbase 数値型を出力する場合、基数を同時に出力する事を無効にする
noshowpoint 実数の出力時に小数点の出力を無効とする
noshowpos 数値出力時に先頭に符号出力を無効とする
noskipws stream入力時の先頭から連続しているspaceを破棄するのを無効にする
nounitbuf 挿入操作の度にバッファをflushするのを無効にする
nouppercase 基数の文字xを大文字出力するのを無効にする


C++のマニピュレータを使用するとどのように書かれるのか例を示します。

---------------------------------------------------------------------
プログラム:setw(int)
---------------------------------------------------------------------
char a1[] = { "abcde" };
cout << "<" <<>" <<>
---------------------------------------------------------------------

---------------------------------------------------------------------
プログラム:setfill(char)
---------------------------------------------------------------------
char a1[] = { "abcde" };
cout << "<" <<>" <<>
---------------------------------------------------------------------

---------------------------------------------------------------------
プログラム:setbase(int)
---------------------------------------------------------------------
int n = 10;
cout << d1 =" 3.14159;" d2 =" 3.14;">\n";
cout << "<" <<>\n";
cout << "<" <<>\n";

---------------------------------------------------------------------
出力結果
---------------------------------------------------------------------
<3.1,3.1>
<3.142,3.14>
<3.14159,3.14>
---------------------------------------------------------------------

---------------------------------------------------------------------
プログラム:internal/right/left
---------------------------------------------------------------------
int n = 10;
cout << "<" <<>" <<>" <<>" <<>
<10>
<>
---------------------------------------------------------------------

---------------------------------------------------------------------
プログラム:endl
---------------------------------------------------------------------
cout << "abcde" << n =" 11;" b =" true;" b=" << boolalpha << b << endl; cout << " b=" << noboolalpha << b << endl; --------------------------------------------------------------------- 出力結果 --------------------------------------------------------------------- b=true; b=1; --------------------------------------------------------------------- --------------------------------------------------------------------- プログラム:showbase/noshowbase --------------------------------------------------------------------- int n = 1; cout << showbase << hex << n << " d1 =" 3.14159;" d2 =" 1;" i =" 1;" l =" 2;" f =" 3.1;" d =" 4.2;">> skipws >> c1 >> c2 >> c3;
cout <<>> noskipws >> c1 >> c2 >> c3;
cout << c1 << ":" c2 << ":" c3 << endl;

---------------------------------------------------------------------
出力結果
---------------------------------------------------------------------
a:b:c
: :a
---------------------------------------------------------------------

---------------------------------------------------------------------
プログラム:unitbuf/nounitbuf
---------------------------------------------------------------------
ofstream outfile ("test.txt");
outfile << unitbuf << "test";
outfile.close();

---------------------------------------------------------------------
補足
---------------------------------------------------------------------
これは出力結果がnounitbufでも何も変わりません
fflushを駆使してプログラムを組んだ事があると
使用局面が分かると思いますが、私なりの考えを補足します。
ファイルを例に取って説明をします。
ファイルにデータを出力する場合「<<」という演算子を使用して
出力を行うが、厳密に言うとその命令文が完了しても
実際にはファイルに内容の出力は行われていない。
その度にファイルに出力していては、パフォーマンス劣化が激しいため
バッファに蓄えれている。
ファイルがclose、フラッシュ、バッファ使い切りなどのイベントが
発生すると実際にファイルへ出力されている。
今回説明している「unitbuf」というマニピュレーションは
毎回フラッシュするように定義する操作となる。
これは基本的にはパフォーマンス劣化に繋がるため
「nounitbuf」としておくのが通常である。
---------------------------------------------------------------------

---------------------------------------------------------------------
プログラム:uppercase/nouppercase
---------------------------------------------------------------------
int n = 11;

cout << showbase << hex << uppercase << n << endl;
cout << showbase << hex << nouppercase << n << endl;

---------------------------------------------------------------------
実行結果
---------------------------------------------------------------------
0XB
0xb
---------------------------------------------------------------------



最後に「setiosflags」と「resetiosflags」について記述します。
実は今まで書いてきた例はフラグを直接操作するもので
ほとんどの内容は「setiosflags」で行えます。

例えば以下の例を考えてみます。
---------------------------------------------------------------------
例1)1~9までの数値を同じ幅で符号付きで出力する
---------------------------------------------------------------------
// 直接フラグ操作
cout << "直接フラグ操作" << endl;
cout << showpos << internal;
for(int n = 1; n != 10; n++) {
cout << setw(4) << n << endl;
}

// setiosflagsを使用
cout << "setiosflagsを使用" << endl;
cout << setiosflags(ios::showpos | ios::internal);
for(int n = 1; n != 10; n++) {
cout << setw(4) << n << endl;
}
---------------------------------------------------------------------
実行結果
---------------------------------------------------------------------
直接フラグ操作
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
setiosflagsを使用
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
---------------------------------------------------------------------

---------------------------------------------------------------------
例2)1~9までの実数を同じ幅で符号付きで出力する
---------------------------------------------------------------------
// 直接フラグ操作
cout << "直接フラグ操作" << endl;
cout << showpos << internal << showpoint;
for(float f = 1.0; f != 10.0; f++) {
cout << setprecision(2) << setw(4) << f << endl;
}

// setiosflagsを使用
cout << "setiosflagsを使用" << endl;
cout << setiosflags(ios::showpos | ios::internal | ios::showpoint);
for(float f = 1.0; f != 10.0; f++) {
cout << setprecision(2) << setw(4) << f << endl;
}
---------------------------------------------------------------------
実行結果
---------------------------------------------------------------------
// 直接フラグ操作
cout << "直接フラグ操作" << endl;
cout << showpos << internal << showpoint;
for(float f = 1.0; f != 10.0; f++) {
cout << setprecision(2) << setw(4) << f << endl;
}

// setiosflagsを使用
cout << "setiosflagsを使用" << endl;
cout << setiosflags(ios::showpos | ios::internal | ios::showpoint);
for(float f = 1.0; f != 10.0; f++) {
cout << setprecision(2) << setw(4) << f << endl;
}
---------------------------------------------------------------------

まったく同じ結果が得られます。
どちらが良いという事はないので、自分の好みに合う方を使って下さい。

金曜日, 1月 11, 2008

C++解説1(最初に)

何か分からないけど自分の知識が抜けないようにというのが
一番の目的でC++の解説を書きます。
書いていると予想以上に忘れている事があるような気がするので
その時は調べて記述。
そうすれば復習にもなりますし

1.C++って
C++ってオブジェクト指向言語? というのは正確ではない言葉です。
C++はオブジェクト指向でも表現可能な言語であって
オブジェクト指向で絶対表現しなければならない言語ではありません。

オブジェクト指向と言えば、classという考えた方があります。
ではclassを使用しなければならないか?
もちろん、答えはNoです。
C言語しかしらないのであれば、それでいいと思います。
少しづつC++に移行すれば良いのですから。

C++って正直難しいと思います。
だから始めるにはかなり苦労すると思います。
でもそれは全体を見るからであって、登れる山を少しだけ登れば
それだけでも良い言語だと思います。

今C言語でプログラムを書く人の中でC言語の事を100%知っている人って
どのくらいいるでしょうか?
プログラムミング言語なんてそんなものです。
所詮言葉ですから。日本語、英語、中国語などと何も変わらないって事です。
日本語全てを理解しなくても、日本語が話せるように
C++全てを理解しなくてもC++で話せる(書ける)わけですね。

さらにC言語を知っている人がいるのであれば
それはさらにC++を始めない理由はありません。
C言語がC++の一部なのですから、C言語を知っているという事が
C++の一部を知っているという事イコールなので。

それではさっさと移行を始めましょう。


2.少しだけC++の世界へ

2.1.型チェック
最初に戸惑って下さい。
C++ってC言語と比べるとめんどくさいじゃないか!
って思わせるポイントに型チェック厳密化があります。
何かと言えば「char*」と「void*」は違うという事です。
あたり前と言えば、あたり前なのですが
以外と気付かないところで許されていたはずです。

char *p;
p = malloc(10);

って書いてませんか?
mallocはvoid*を返却する関数ですので
p = (char*)malloc(10);
と書かなければなりません。


これ以降はC言語が書ける人に対して
さっさとC++に移行してしまえポイントを書きます。
こんな事は今日からでも行ってしまえば良いって事です。
もしかしたら既に行っている人もいるかもしれません。

2.2.コメントは//を使え!
C言語においてコメントとは「/*」~「*/」で囲まれている範囲となります。
これはこれで良いのですが、C++では「//」を使います。
C99では「//」もコメントとして認められるようになったと認識してます。
何で「//」がコメントとして作られたのか?
もちろん、簡単な話です。「/*」~「*/」では問題があるからです。

-----------------------------------------------------------------------------------------
1: int main(void) {
2: int i = 0;
3: int j = 0;
4:
5: for(i = 0; i != 10; i++) {
6: printf("i=%d\n", i);
7: }
8:
9: printf("j=%d\n", j);
10:
11: return 0;
12: }
-----------------------------------------------------------------------------------------
上記プログラムの5行目から7行目までコメントにする場合
どのようにするでしょうか?
人それぞれなのでしょうが私的には2通りあると思います。
①4行目に「/*」8行目に「*/」を付ける方法
②5~7行目の行頭に「/*」行末に「*/」を付ける方法

このどちらが間違っている事ではないですしどちらでも良いと思います。
言ってしまえば、個人的な癖や学んできた環境によるものでしょう。
ただ6行目が以下の内容だったとします。
-------------------------------------------------------------------------------
6: printf("i=%d\n", i); /* 変数iの内容を出力 */
-------------------------------------------------------------------------------
いろいろと都合が悪くなります。
1の方法ではコメントが正しく行われなくなります。
言うまでもなく6行目でコメントが終わってしまうからです。
よって2の方法しかなくなってしまいます。
ただ2の方法も単純ではありません。
ようは行末では間違ったコメントになってしまうからです。
-------------------------------------------------------------------------------
6: /* printf("i=%d\n", i);*/ /* 変数iの内容を出力 */
-------------------------------------------------------------------------------
こんな感じとなりますね。
この例は大した問題ではありませんね。たかだか3行程度のコメントなので。
でも桁が1つ2つと違いがでたら重労働です。
基本的に、こんな作業は多くの方が一括置換で作業していると思いますが
この一括置換が行えないという場面に遭遇してしまうからです。
またコメントをする時は良いですが、その修正等に間違いがあり
コメントを解除する場合、かなりものを考える必要があります。
たかだかコメントでここまで苦労したくはないですね。

その点「//」コメントなら行末がコメント最終となりますので
何所までなんて考える必要がありません。
行頭にコメントする行全てに「//」を付ければ良いわけです。
もともとコメントされている行が存在し「////」となっても何の問題もないのです。
う~ん素晴らしいですね。というかもう常識ですね。
この程度のコメントはUNIX、LINUXの各shell、Windowsのbatch、OfficeのVBAなどで
すら採用されている事です。
C言語で「/*」「*/」でコメントを書く事が時代に逆行している。
と言い切ってしまって問題ないと思います。
C言語が悪いのではなく、C言語で「//」を使わないプログラムマーが悪いのです。
こんな簡単な事が今日から実践です。


2.3.変数の宣言は関数の先頭で行うな!
C言語は変数の宣言が関数の先頭である必要があります。
これも頂けない仕様です。
なぜ頂けないというと、バグを埋め込む可能性があるからです。

そもそも、loopで使用するindexを
なぜ関数の先頭で宣言しなくてはならないのか?
これもプログラミング言語が発展している今では考えられない事です。
script言語と言われるものは宣言すら必要ありません。
これが究極の姿でしょう。
しかし、パフォーマンスなどを考えると宣言しないまで行くのは
非常に困難なようですね。
そこで宣言するまでは良いとしても「最初に宣言しちゃって下さい」という
コンパイラーの都合は頂けません。

私が知っている限り、Fortran、Cobol、C言語など
古いプログラミング言語はこの傾向があります。
昔はしょうがなかったのでしょうね。

ただ、C++はその必要がありません。
って事は古い方法に縛られる必要はないって事です。
-------------------------------------------------------------------------------
1: int main(void) {
2: int i = 0;
3:
4: for(; i != 10; i++) {
5: printf("i=%d\n", i);
6: }
7:
8: return 0;
9: }
-------------------------------------------------------------------------------
上記プログラムは危険を持っています。
3行目に後でプログラムを追加された場合、
iが0ではなくなってしまうかもしれません。
i = 0;を初期化として入れる事でこの事態は防ぐ事ができますが
7行目に3行目で処理したiを使用する場合、iの値が保障されなくなってしまいます。
このような事態を防ぐには、新しい変数を追加するしかありません。
そもそも初めからそのようにすれば良いのが
そうされないのは2行目に変数宣言がある事で
無駄なworkを作成する事にならないかという考え方が発生するからです。
この考え方は非常に重要で、これを考えないと意味もなくworkを増やしていき
最後には理解不能なプログラムとなってしまいます。

ではどこがいけなかったのでしょう?

「無駄なworkを作成する事にならないか」と考えさせた事がいけないのです。
なぜそのように考えたたのかは状況によりますが
最低限、同一型が既に存在しているという理由は含まれます。
であれば、存在していなければ良いのです。
ようは使う範囲が分かっている変数は
その範囲でしか有効でない場所に変数宣言するべきなのです。
これは非常に重要なポイントです

C言語にもこの考え方は存在します。
誤解している人もいるようなので確認しておくと
C言語で変数宣言できる箇所はブロックの先頭です。
関数の先頭ではありません。
ブロックとは「{」でブロックを開始し「}」でブロックを終了します。
つまり、関数の先頭とはブロックの先頭でもある訳です。

C++で出来るようになった事はブロックの何処でも変数宣言可能となった事です。

-------------------------------------------------------------------------------
1: int main(void) {
2: {
3: int i;
4: for(i = 0; i != 10; i++) {
5: printf("i=%d\n", i);
6: }
7: }
8: }
-------------------------------------------------------------------------------
上は立派なC言語のプログラムです。
あんまりこのように書くことはしないと思いますが、
2行目にブロック開始を埋め込んでいるため
3行目の変数宣言は有効となります。
そして、ここで宣言した変数は7行目までが有効な範囲です。
実は、これだけでもかなりの変数使いまわしを防ぐ事ができます。
ただ、ネストが進みプログラムの可読性が著しく低下する事になってしまいます。

この「変数使い回し防止」「可読性」といった2つの観点で
クリアしたのが変数は何処でも宣言可能といったところでしょう。
これも直ぐに実践あるのみです。

ただのIndexが沢山宣言されていて、Indexの変数名が「i,j,k,l,..」などど
進んでいませんか?
まったくもって無駄な事です。

まだまだあるのかな?
私が思うにこれだけでも...

木曜日, 1月 10, 2008

ブラジル蝶の謎

ブラジル蝶の謎。
短編小説なので内容が薄いってのが正直な感想です。
特に面白かった作品もありませんでした。
正直、あんまり書くことがありません。
タイトルになっているブラジル蝶の謎は犯人を特定できる材料の一つが
昔読んだ、綾辻行人さんの殺人方程式だったと思うんですが、
それと同じだった事を思い出しました。
(記憶違いでなければ...)
トリックが同じというわけではありませんが、何か印象的でした。

有栖川有栖さんの作品は少なくとも「スウェーデン館の謎」や「46番目の密室」
など面白い作品があったので次読む作品に期待します。

短編小説が今一好きになれない私って、トリックの素晴らしさ以上に
作者の手の中で踊らされる事を楽しんでいるのかもしれません

日曜日, 1月 06, 2008

ロシア幽霊軍艦事件

面白かったような、面白くなかったような作品でした。
訳のわかならい事を、事実として解明していく姿は眩暈を思い出させました。
あれ程、長編ではないためスケールは小さかったですが
意味不明な事がらが解明されていく進みは
御手洗潔シリーズを彷彿させる内容です。

直近で読んだ島田荘司さんの作品がアトポスだった事もあり
すべてにおいてスケールが小さくなった感覚が否定できません。

私的には読む順番を間違えた感じがします。

カスッたスタート

今年の金杯は京都にアドマイヤオーラが出走という事で勝負にでました。
相手はエイシンデュピティで、3着候補にサクラメガワンダー、キンシャサノキセキ
いろいろと買い目を考えた結果、
アドマイヤオーラの1着固定、馬単流し、3連単、2着、3着のボックスという選択。
これが結果的には失敗でした。
馬券そのものは、それぞれ、馬連、3連複と押さえていたので馬連のみのヒット。
たいした儲けにはならず、不完全燃焼のスタートでした。

ただ、金杯が当たったというのはあんまり記憶がないため
そおいう意味では幸先の良いスタートだったかもしれません。

昨日の不完全燃焼を解消するために本日、メインレースを購入。
京都はフェラーリピサから、ドンクール、トラストジュゲム、スリープレスナイトに
1着固定の3連単2、3着ボックス、と馬単
中山はトップディアマンテ、ワイルドファイアー、トップオブツヨシの馬連ボックスです。