2006年01月14日

void型ポインタ

ポインタ型でのみ存在する特殊な型で汎用ポインタと呼ばれるものです
実は、型名はよく見なれていますがvoid *型なのです
* が付いているのが異なる部分ですね

void型は、しつこいようですがポインタ型でのみ使用できるものです
通常の変数にはvoid型は存在しません

これはどのような型かというと、あらゆるポインタ型に変換できるポインタ型です
多くはどのような型も受け取れる関数を作成するのに使用されます
汎用ポインタを使用すれば、ポインタ型であればどのような型でも受け取ることができます

void型のポインタを逆参照するには必ず型キャストします
キャストはC言語では任意ですが、C++では必ず必要となります
void型ポインタはどのデータ型にも変換することができます

次のプログラムは、関数outString()にいろんな型のポインタを渡しています
当然、サイズはそれぞれがことなるので結果は保証されないということを忘れないでください
汎用ポインタは、代入されてもエラーを出さないためのようなものです

#include

void outString(void *);

int main() {
int i = 65 ;
double d = 10.101;

outString(&i);
outString(&d);
outString("Kitty on your lap");

return 0;
}

void outString(void *text) {
char *str = (char *)text;
printf("%s\n" , str);
}

outString()関数は、あらゆるポインタ方を受け取ることができる関数です
受け取ったポインタの内容はchar型の文字列ポインタにキャストされ出力されます

void *
汎用ポインタを宣言します
const または volatile キーワードで宣言されていない任意の変数を指す事ができます
逆参照には、必ず目的の型にキャストしてからでなければ使えません
posted by シンビアン at 17:59| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

リソースファイル

リソースファイルとはアプリで使う、文字列や画像やダイアログのデザインなど動的に変更されない、実行コード以外のアプリの部品を一つのファイルにまとめたものです。リソースファイルは一つのアプリに一つである必要はありません。リソースファイルを持たないアプリも作成できますし、一つのアプリで2つ以上のリソースファイルを使用することもできます。
posted by シンビアン at 17:48| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

マクロ

ワープロソフトや表計算ソフトなどで、特定の操作手順をプログラムとして記述して自動化する機能。プログラムの記述に使う言語をマクロ言語という。よく使う処理をマクロとして保存しておけば、必要なときに誰でも簡単に実行できるようになる。マクロ機能を持ったアプリケーションソフトは、マクロの開発環境や動作環境が用意されている。できたプログラムは文書ファイルに他のデータと一緒に保存される。マクロ言語はアプリケーションによって異なるが、同じメーカーのアプリケーションでは、ユーザの便宜を図るために、基本的な仕様を統一していることもある。マクロの機能を悪用して作成されたコンピュータウイルスを「マクロウイルス」という。
posted by シンビアン at 17:44| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

参照オブジェクト

Q:「ポインタ変数と参照オブジェクトはどう違うのですか?」
A:「ポインタ変数は、それが指す先があろうが無かろうがメモリー上に実際に領域を確保された実在の変数ですが、参照オブジェクトはそれが定義されたときに別の変数を必ず参照している(別名となる)はずで、メモリー上は参照先と同一の領域を持ったいわば名前だけの存在です。」
ポインタ変数はそれが参照する先のオブジェクトを次々に切り替えることができますが、参照オブジェクトはそれが定義されてから使い終わるまでの間、参照先を変えることができません。
posted by シンビアン at 17:40| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

long long型

long long 型を使えば、64bitまでの整数を一つの変数として扱えます。unsigned long 型では、10桁台前半の4294967295が限度で、場合によってはオーバーフローが発生し、計算間違いが起こります。6けたどうしの掛け算での、積の最大の桁数は12けたです。

long long型なら19桁後半の9223372036854775807まで計算ができ、この問題を解決できます。

もっとこだわりたい人はunsigned long longを使う手もありますが、このプログラムが求める5桁の学生証番号を使った処理では必要ありません。

long long 型の値は、定数は a = 123456789012LL; のように書きます。

printfでは printf("%Ld\n",a); のように書きます。
posted by シンビアン at 17:38| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

列挙型

C言語の変数の型で、もうひとつ特殊な列挙型というものがあります
列挙型は定数のリストを定義することができます
列挙型を作成するにはenumキーワードを使用します

enum タグ名 {定数リスト} 変数;

タグ名には、この列挙型の名前を指定します
これは構造体と同様に、別の場所で変数を宣言する時に使用します
定数リストには、それぞれの定数名をカンマ「 , 」で区切って指定します
変数には、この列挙方の変数名を指定します

構造体や共用体をやってきた今となっては、enumの感覚もなんとなくわかるでしょう
例としては次のように定義します

enum TAG { ASAHINA , MIZUNO , HUZISAKI , KISARAGI } tokimemo;

これは列挙型変数tokimemoの定義例です
忘れてはいけないのが定数の本質は整数であるということです
列挙型のリストの定数は、一番左側を0として1ずつ増えていきます
つまり、ASAHINAは0を、MIZUNOは1を、HUZISAKIは2を、KISARAGIは3を表します

#include

enum Kitty { MIMI , YUKI , RENA };

int main() {
enum Kitty cats;

cats = MIMI;
printf("MIMI = %d\n" ,cats);
cats = YUKI;
printf("YUKI = %d\n" , cats);
cats = RENA;
printf("RENA = %d\n" , cats);

return 0;
}

結果は次のようになりました

MIMI = 0
YUKI = 1
RENA = 2

このプログラムでは、Kittyという列挙型を定義しています
列挙型の変数をmain()関数内で宣言し、それぞれの定数の値を代入しています

定数の値は、デフォルトで左から1ずつ加算した値になりますが
代入式で直接値を指定することもできます

enum Kitty {MIMI , YUKI = 5 , RENA};

この場合、YUKIは5になりますが、デフォルトのカウンタも5にセットされます
つまり、YUKI移行は5からカウントされて値が代入されます
結果としてRENAは6になるということに注意してください

#include

enum Kitty { MIMI , YUKI = 5 , RENA };

int main() {
enum Kitty cats;

cats = MIMI;
printf("MIMI = %d\n" ,cats);
cats = YUKI;
printf("YUKI = %d\n" , cats);
cats = RENA;
printf("RENA = %d\n" , cats);

return 0;
}

結果は次のようになります

MIMI = 0
YUKI = 5
RENA = 6

定数を使ったプログラムでは、switch制御文を使用すると大変便利です
関数の呼び出し時に定数を受け取り、その定数にしたがって処理するような場合に使えます
そういえば、サンプルソースの中でswitchを使うのってひさしぶりですね♪

#include

enum Kitty { MIMI , YUKI = 5 , RENA } cats;

int main() {
cats = RENA;

switch(cats) {
case MIMI:
printf("ミミが好き");
break;
case YUKI:
printf("ユキが好き");
break;
case RENA:
printf("レナが好き");
}

return 0;
}

列挙型はその仕様すら曖昧なものなので、多くの疑問が残るものです
たとえば、次の場合を考えてください

enum TAG { AKARI } hiro;
hiro = 10;
int i = AKARI;

列挙型に通常の数値型を代入したり
int型の変数にenumで定義した定数を代入しています

一見エラーが発生しそうですが、それは仕様上で定義されておらずコンパイラ依存になります
列挙型の局所性はコンパイラによって様々です
列挙型の定数値が他の型の変数に代入できるかどうかなど、ご自分の環境で試してください

enumはの定数は #define と同質の定数です
つまり、enumの定数値はメモリに保存されません(コンパイル時に展開)

enum [tag] {enum-list} [declarator]
列挙型の定義をします

tag - タグ名を指定します。省略可能です
enum-list - 定数リストを作成します
declarator - 列挙型変数名を指定します。省略可能です
posted by シンビアン at 17:35| Comment(1) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

デストラクタ

C++言語などのオブジェクト指向言語で作成したプログラムにおいて、オブジェクトが破棄される時に呼び出される特殊な関数。そのオブジェクトのために確保した占有メモリ領域を開放して再利用できるようにする処理などを行なう。Java言語ではオブジェクトの破棄は実行環境(Java仮想マシン)が自動的に行なうので、デストラクタは必要ない。
posted by シンビアン at 17:20| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

メモリリーク

コンピュータの動作中に、使用可能なメモリ容量がだんだん減っていく現象。OSやアプリケーションソフトが処理のために占有したメモリ領域を、なんらかの理由で解放しないまま放置してしまうために起きる。多くの場合、OSのメモリ管理方法に問題があったり、アプリケーションソフトに不具合(バグ)が残っていたりすることが原因である。メモリリークにより利用可能なメモリ領域が減少すると、システムの性能が低下したり、不安定になったりする。これを解消するには、システムを再起動する必要がある。
posted by シンビアン at 17:15| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

has-a関係

複合オブジェクトとそれを構成する部品オブジェクトとの関係をpart-of関係と呼ぶことがあります。部分は全体の一部なので「部分はpart-of全体」という表現方法が自然にできるからです。

 「Aはpart-of B」かつ「Bはpart-of C」ならば「A はpart-of C」なので、part-of関係は推移律が成立します。またis-a関係と同様、part-of関係も階層構造を持つことができます。この関係はhas-a関係と呼ぶこともあります。この場合、part-of関係と主語が反対になり「全体has-a部分」と表現することができます。全体が部分を保有しているという意味です。

【複合オブジェクトの例】

 例えば、車はボディ、シャーシ、エンジンなどさまざまな部品で構成されています。さらに、ボディは運転装置、内装、照明などで構成されています。つまり、車というオブジェクトは複合オブジェクトであり、車を構成する部品自体も、より小さな部品で構成される複合オブジェクトであると考えることができます
posted by シンビアン at 17:13| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

オーバーロード

プログラミングにおいて、戻り値や引数の数やデータ型(シグネチャ)が異なる同名の関数やメソッドを多重定義すること。

 オーバーロードにより、引数の型が異なる関数を複数定義することができ、多重定義された関数が呼び出される際には呼び出し側の引数の型に応じて適切な関数が呼び出すことができる。

 これは、関数を呼び出す立場から見ると関数に与えるデータ型を変えることで関数の挙動が変化するように見える。例えば、関数の引数が10個定義してあるものの、最低限必要なものは4個であり、それ以外の変数は必要に応じて用いる、といった状況に対応できる。
posted by シンビアン at 17:08| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

クリーンアップスタック

コンストラクション(Cクラス)

 次に、Cクラスのオブジェクトのコンストラクションに話を移しましょう。オブジェクトのコンストラクションに絡めて、クリーンアップスタックについて見ていきます。
前回の記事でも多少触れさせていただきましたが、クラスのインスタンスを作成する時は通常、「2フェーズコンストラクション」を行います。2フェーズコンストラクションは、具体的には

* new(ELeave)演算子を用いてクラスのインスタンスが必要とするメモリを確保する。この新たにオーバーロードされている演算子はリーブを発生させる可能性がある。

* 作成したインスタンスが他のオブジェクトを所有する場合(つまりhas-aポインタを持つ場合)は、概ねConstructLという名前の関数でポインタの参照先インスタンスの生成とポインタの値の設定を行う。

という流れになります。若干厄介に見えますが、これはメモリリーク対策になります。またまた繰り返しになりますが「メモリの確保が最もリソース不足によるエラーを起こしやすい」に注意してください。

携帯環境ですから、非常にリソースが限られています。ですから、小さなオブジェクトの確保でも失敗をする可能性があるわけです。小さなコードで、初期化に失敗する可能性を考えるのであれば(これは携帯環境では十分に考えられる)、各々のポインタに対して、nullチェックを行う必要があります。これでは、プログラムを書くのが手間になってしまいます。また、リーブの機構があるのですからこれを使わない手はありません。

そこで、例外処理をすっきりさせるため、先ほど出てきたリーブの仕組みを使うことを考えます。つまり、new(ELeave)を用いてメモリの確保を行います。このオペレータは先述の通り、メモリ確保に失敗するとリーブを発生させます。

つまり、リーブが発生した時点でTRAPマクロやTRAPDマクロで処理される箇所までスタックを遡るわけです。後のコードでリーブが発生した場合、前のコードが一時的な作業用のauto変数(コンストラクタの中であったとしても十分にあり得る)であれば、そのポインタが永遠に失われ、結果としてメモリリークの原因になります。

そこで、次のような対策を講じます。

1. CBaseクラスを継承したクラス(ClassA(前のコード))の使用するメモリのみを確保する(コンストラクタでは何も行わない)。メモリの確保に失敗すれば、リーブを発生させる。

2. ClassAによって所有される他のクラス(has-aポインタでの参照先)は、他のコンストラクション用の関数(一般にConstructL)で初期化する。ここでもリーブが発生する可能性がある。

3. 1または2でリーブが発生した場合、構築途中のオブジェクトへのポインタが失われないようにグローバルな領域(スタック)にポインタの値をコピーしておく(このスタックをクリーンアップスタックと呼ぶ)。

それでは、2フェーズコンストラクションのサンプルを見てみましょう

CMyObj* CMyObj::NewL(CConstructionContainer* aContainer )
{
CMyObj* ptr = new(ELeave)CMyObj; // インスタンスが使用するメモリの確保
CleanupStack::PushL( ptr );
// (1)以下の処理でリーブが発生したときに備えクリーンアップスタックにpush
ptr->ConstructL(aContainer); // インスタンスのメンバの初期化
CleanupStack::Pop();
// リーブの可能性がなくなったので、クリーンアップスタックからpop
return ptr ;
}

void CMyObj::ConstructL(CConstructionContainer* aContainer)
{
this->iContainer = aContainer ;
TInt reason = KErrNoMemory; // 勝手にメモリがないことにする
User::Leave(reason); // リーブを発生(2)
aContainer->UpdateMessage(_L("初期化完了") ); // この行は実行されない
}

CMyObj::~CMyObj()
{
this->iContainer->UpdateMessage(_L("CMyObj 削除完了"));
}

ここでは(1)にて構築途中のオブジェクトをクリーンアップスタックにプッシュし、第二フェーズのコンストラクションの(2)において意図的にリーブを発生させています。ここでクリーンアップスタックの重要な動作として、
リーブが発生したとき、クリーンアップスタックに詰まれた
全てのオブジェクトのデストラクタが呼び出される
という点に注意してください。つまり、2フェーズ目で構築に失敗し、リーブが発生しても、クリーンアップスタックにメモリ確保時に取得したポインタを積んでおけば、自動的にデストラクタが呼び出されるということです。そして、リーブ時にデストラクタを呼び出すためにCBaseクラスに仮想デストラクタが定義されているわけです。

ただし注意点として、リーブ発生時にはクリーンアップスタックに詰まれた全てのオブジェクトがデストラクトされます。つまり、

リーブが発生する可能性が無くなった時点でクリーンアップスタックから取り除く

必要があります。将来、全く別の理由でリーブが発生した場合、問題のないオブジェクトまでデストラクションする可能性があるためです。
posted by シンビアン at 17:01| Comment(0) | TrackBack(0) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

例外処理(リーブ)

例外処理を見ていきましょう。また、例外処理に関連して2フェーズコンストラクションについて見ていきたいと思います。このセクションをお読みになるにあたり、しつこいですが「携帯環境のリソースは非常に限られたものを想定している」というのを頭の片隅において置いてください。

 Series60では通常のC++によるtry-catch構文とは異なる例外処理の機構を利用します。これは通常のtry-catchによる例外処理が結果としてコードサイズの肥大化を招くためです。ですから、通常のtry-catchによる例外処理は採用されていません。

 そこで、try-catchの代用として出てくるのが「リーブ(Leave)」という考え方(機構、仕組みといっても良いかもしれません)です。リーブは、従来の例外と概念的にはほぼ同一です。以下にリーブについて概略をまとめます。

* C++の例外の代わりに使用される
* リソースエラー等が発生すると、コードはリーブする
* トラップハーネス(TRAPマクロやTRAPDマクロ)でリーブが処理されるまで関数のスタックをさかのぼる

リーブによる例外処理の例
次のサンプルを見てみましょう。このサンプルでは、メニューから「リーブのテスト」を選ぶと、意図的にメモリ不足のリーブが発生するようになってます。

// コード例(A)
void CLeaveSampleAppUi::HandleCommandL(TInt aCommand)
{
switch ( aCommand )
{
/// 中略….
case ELeaveSampleCmdAppTest:
{
// 次の関数の呼び出しはリーブを発生させる
this->iAppContainer->TestLeaveL();
break;
}
// 後は省略….

このTestLeaveL()関数の実装は次の通りです。

// コード例(B)
void CLeaveSampleContainer::TestLeaveL()
{
TInt reason = KErrNoMemory; // 勝手にメモリがないことにする
User::Leave(reason); // (1)例外を発生
this->iToDoLabel->SetTextL(_L("問題なく終了しました"));
// 上の一行は実行されない
}
このサンプルでは、(1)においてメモリ不足のリーブを意図的に発生させています(User::Leave関数でリーブを発生)。
それでは、このアプリケーションに対して例外処理のコードを加えたいと思います。上記コード例(A)を次のように書き換えてみてください。
// コード例(C)
void CLeaveSampleAppUi::HandleCommandL(TInt aCommand)
{
switch ( aCommand )
{
/// 中略….
case ELeaveSampleCmdAppTest:
{
// TRAPDマクロで例外を捕捉
// error に例外コードが入ってくる
TRAPD( error,
this->iAppContainer->TestLeaveL(); );
if( error != KErrNone ){ // 例外が発生しているかを検査
this->iAppContainer->UpdateMessage();
}
break;
}
// 後は省略…
TRAPDマクロの第一引数にあるerrorに例外が発生した理由が格納されます。これをKErrorNone(つまり例外が発生しなかったことを表す)と比較しているわけです。

 もう少しリーブについて見ていきましょう。例外処理自体は、このような形で行うことができます。次に残る問題は「作成した関数、またはAPIで提供されている関数がリーブを発生させるか否か、コンパイラレベルでは判断できない」点にあります。リーブの機構はC++の言語ではなく、Series60で採用されているものです。つまり、例外を適切に処理するのであれば、先のコード例(A)を本来はコード例(C)のようにしなくてはいけません。しかし、コード例(A)のままでも問題なくコンパイルが可能ですし、警告も表示しません。

 そこで出てくるのが関数のコードコンベンションです。今回はコード例(B)で定義した関数は最後に「L」の付く関数になっていますが、一般にリーブを発生させる可能性のある関数はその名前の最後に「L」を付ける習慣になっています。このような「L」の付く関数に関しては(製品としてプログラムを出す場合は)必ずTRAPマクロや今回の様にTRAPDで処理をする必要があります。

補足:今回はリーブによる例外処理を見ていきましたが、一部のAPIには戻り値によって正常/異常終了を表すもあります。
posted by シンビアン at 16:05| Comment(0) | TrackBack(1) | Series60プログラミングテクニック | このブログの読者になる | 更新情報をチェックする

メモリインターリーブ

 メモリのデータ転送を高速化する技術の一つ。複数のメモリバンクに同時並行で読み書きを行なうことにより高速化を行なう手法。

 メモリの読み書きにはいくつかの段階があり、CPUがアクセス要求を行なってから実際にデータが送られてくる(あるいは書き込みが完了する)までにはレイテンシ(遅延)と呼ばれる時間差が生じる。メモリへのアクセスは時間がかかるため、コンピュータの処理速度はこの「待ち時間」に足を引っ張られている。レイテンシを短縮する試みは常に行なわれているが、CPU内の記憶素子との差は埋めがたく、また、低レイテンシのメモリは高価である。

 一方、メモリへのアクセス要求は短期的には局所性が極めて強く、連続した領域に順番に読み書きを行なうことが多い。この特徴を利用して、複数のメモリバンクにまたがって連続したアドレスを交互に振っておき、あるデータにアクセスする遅延時間の最中に次のアドレスへアクセス要求を発行して時間を有効利用するのがメモリインターリーブである。バンクの数を増やせばその分高速にアクセスできるようになり、2つのバンクを用意すれば2倍、4つで4倍の高速化を図ることができる。ただし、実際にはコントローラのオーバーヘッドや、不連続なアドレスへのアクセスがあるため、バンクの数だけ性能が向上するわけではない。

 安価な高レイテンシのメモリで高い性能を得られる反面、メモリコントローラが複雑になり高価になることや、部品点数が増えて故障率が上がってしまうという欠点もある。高い性能が要求されるサーバなどでよく使われる技術だが、最近ではパソコンでもメモリインターリーブが採用されていることがあり、メモリの増設の際に同じ容量・種類のメモリモジュールを2枚ずつ増設しなければならないことがある。
posted by シンビアン at 15:48| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

参照を戻り値に

参照は引数としてだけでなく、戻り値として扱うことも可能です。
参照型の戻り値は、あるメンバ変数に対して、取得・設定の両アクセスを提供する場合などに使用します。
とは言っても、一般に参照型の戻り値を持つのはごく一部の演算子くらいです。
勿論、ユーザーは参照を返す関数を自由に定義することができますが、あまり需要がないので普通はしません (よね?)。
i. 要素参照

下のプログラムを見てください。
CIntArray はint型の配列をクラス化したもであり、要素へのアクセスは、GetElement (取得), SetElement (設定) で提供されています。

//要素値の取得
int CIntArray::GetElement(int nIndex) const {
return m_lpnElem[nIndex];
}

//要素値の設定
void CIntArray::SetElementint nIndex, int nValue){
m_lpnElem[nIndex] =nValue;
}

このように、データメンバへのアクセスを Get〜, Set〜 といった関数として提供するのは定石的な手法です。
しかし、このように取得, 設定の両アクセスが許されている場合、Get〜, Set〜 だけでは操作が面倒になる場合があります。
例えば、CIntArray のオブジェクト a に対して、i 番目の要素値をインクリメントするにはどうしたらよいでしょう。

a.SetElement(i, a.GetElement(i) + 1);

インクリメントにしてはずいぶん入り組んだコードです。
そこで通常、取得・設定の両方が可能 (かつ設定時の値チェックが不必要) な場合は、データメンバそのものへの参照を提供するようにします。
要素参照演算子 operator [] を用いて参照を返す例を以下に示します。

//要素の参照
int& CIntArray::operator [] (int nIndex){
return m_lpnElem[nIndex];
}

これにより、要素のインクリメント (デクリメント, 加算代入, etc.) も簡潔に記述することができるようになります。

++a[i];

稀にですが、この operator [] が Get〜 と等価な取得関数として定義されているのを見かけることがあります。

//要素の取得
int CIntArray::operator [] (int nIndex) const {
return m_lpnElem[nIndex];
}

この理由は、参照の使い方が分かっていないか、constオブジェクトからでも呼び出せるよう配慮しているかのどちらかでしょう。
前者は置いておくとして、後者の問題は簡単に解決することができます。
次のように、const用の演算子と、非const用の演算子の両方を定義して置けばO.K.です。

//要素の参照
int& CIntArray::operator [] (int nIndex){
return m_lpnElem[nIndex];
}

//要素の取得 (const)
const int& CIntArray::operator [] (int nIndex) const {
return m_lpnElem[nIndex];
}

こうすれば、非constオブジェクトからは前者の、constオブジェクトからは後者の operator [] が呼び出されるようになります。
const参照を返すのは、値を返すのと意味的にはあまり変わりませんが、演算子の本来の意味 (要素参照) を考えると、こちらがベターだと言えるでしょう。

ii. 代入

参照を返す演算子には、要素参照演算子 [] の他に代入演算子 = があります。
これが意外と知られていないらしく、演算子 operator = を戻り値なし (void) の関数として定義している例が結構あります。

//ベクトルの代入 (戻り値なし)
void CVector2D::operator = (const CVector2D& v){
x =v.x;
y =v.y;
}

普段はこの定義でも十分使えるのですが、代入演算子の使い方としては (厳密に言えば) 誤りです。
プリミティブ型、例えばint型の変数に対し次のような操作がなされているのを見たことはないでしょうか。

m = n = 0;

あるいは、fopen の成否チェックに使う次のようなコード。

if ((fp = fopen("test.dat", "r")) == NULL) return -1;

最初の例では、まず n に 0が代入され、その後 m にも 0が代入されます。
後の例では、まず fopen の戻り値を fp に代入し、次いでその値が 0 (NULL) かどうかの判定を行っています。
これらを見ても分かるとおり、代入演算子はその評価結果として、代入された値を返しているようです。
では、メンバ演算子 operator = の定義は次のようにすればよいのでしょうか?

//ベクトルの代入 (値を返す)
CVector2D CVector2D::operator = (const CVector2D& v){
x =v.x;
y =v.y;
return *this;
}

惜しい。
実は、代入演算子は自分自身の値 (コピー) ではなく、参照を戻り値としているのです。
その証拠に、int型の変数 n には次のような記述をすることもできます (あまり意味はありませんが)。

(n = 0) = 1;

最初の演算 (括弧の中) は、n に 0 を代入し、n への参照を返します。
次いで、括弧の外側の代入演算により、先の参照のターゲット (すなわち n) に 1 が代入されます。
つまり、n の最終的な値は 1になるわけです。
このことを踏まえた上で、改めてメンバ演算子 operator = を定義すると次のようになります。

//ベクトルの代入 (参照を返す)
CVector2D& CVector2D::operator = (const CVector2D& v){
x =v.x;
y =v.y;
return *this;
}

なお、これと同様に加算代入演算子 operator += なども、オブジェクトへの参照を返します。


//ベクトルの加算代入
CVector2D& CVector2D::operator += (const CVector2D& v){
x +=v.x;
y +=v.y;
return *this;
}

//ベクトルの減算代入
CVector2D& CVector2D::operator -= (const CVector2D& v){
x -=v.x;
y -=v.y;
return *this;
}
posted by シンビアン at 15:34| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

参照を引数に

引数を参照で渡す理由は大きく分けて2つ。
ひとつは、複数の戻り値を返すためです (ポインタでもやりますね)。
関数内で、引数として渡された参照に値をセットすることで任意の数の戻り値を返すことが出来ます。
以下に示す関数 Stat は、n 個のデータの平均と分散を求める関数です。

void Stat(int n, double* lpdData, double& rdAvr, double& rdVar){

double dSum =0.0;
double dTmp;

int i;
for (i=0; i
posted by シンビアン at 15:18| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

参照とは (ポインタとの相違)

そもそも、参照って何でしょう?
解説書などを見てみると「変数もしくはオブジェクトの "別名"」なんていう説明が載っています。
まぁ、これも正しい表現ですが、私はこう考えることにしています。

参照とはポインタもどきである。

つまり、参照というのは「ポインタによく似たナニモノか」であるということです。
次のコードを見てください。

//参照を使った例

void Foo(int& x){
x =200;
}

int main(){

int a, b;

int& r =a;
r =100;

Foo(b);

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

return 0;
}

//ポインタを使った例

void Foo(int * x){
*x =200;
}

int main(){

int a,b;

int* p =&a;
*p =100;

Foo(&b);

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

return 0;
}

全く同じ処理を、それぞれ参照とポインタを使って行っています。
このコードから、参照とポインタがなんだか似たモノだということが分かると思います。
そこで、まず参照とポインタは「同じようなモノ」だと考えた上で、両者の相違点を列挙していきます。

i. 「* 演算子」を使わなくてもターゲットを参照することができる。

これが「参照は変数やオブジェクトの別名である」といわれる所以です。
ポインタと異なり、あたかもターゲット (それが指す変数やオブジェクト) と同じ型のデータであるかのように振る舞います。

ii. ターゲットの設定は初期化構文でのみ可能 (変更は不可)。

これもコーディングをする上で重量な性質です。
ポインタは、スコープ内で何度でもターゲットを変更できますが、参照は宣言時に設定されたターゲットを変更することはできません。

//参照を使った例 (誤)

double dSum =0.0;
double& r; /* error */

int i;
for (i=0; i
posted by シンビアン at 14:55| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

インスタンス

オブジェクト指向プログラミングで、クラスを基にした実際の値としてのデータのこと。クラスと対比して用いられることが多く、クラスを「型」、インスタンスを「実体」として説明されることもある。

 「オブジェクト」とほぼ同義語のように用いられることが多いが、実際にメモリ上に配置されたデータの集合という意味合いが強く、データの実体をより具体的・直接的に捕らえた用語である。

 例えば「名前、身長、体重」というクラスがあるとすれば、そのインスタンスは「田中、175、65」というように作られる。一つのクラスから複数のインスタンスを作ることができ、それぞれのインスタンスは違った値を持ちうる。プログラムの中で実際に扱われるのはクラスではなく、こうして作られたインスタンスの方である。

 なお、ひな形となったクラスを表す際には「○○クラスに属するインスタンス」「○○クラスのインスタンス」という表現が用いられる。
posted by シンビアン at 14:38| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

グローバル変数

ログラムの中のもっとも外側のブロックで宣言された変数で、すべてのブロックで有効なもの。大域変数とも呼ばれる。プログラム全体に対して定義される変数で、プログラムのどこからでも参照・更新することができる。

 これに対して、宣言されたブロック内だけで有効な変数をローカル変数、または局所変数という。C言語ではグローバル変数と同名のローカル変数が存在した場合は、ローカル変数が優先される。
posted by シンビアン at 14:36| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

クラスの最初は大文字を使う

Javaでは、クラスの名前は小文字で、最初の文字と単語の区切りを大文字にするきまりになっています。小文字ではじめても構わないのですが、特に大きな理由がなければ、必ずこの規則に従ってください。
また、この規則になっているものはクラスだけなので、小文字で大文字で始まっている単語は全てクラス名ということができます。

Javaでは、大文字で始まってる単語はほとんどの場合クラス名です。
posted by シンビアン at 14:34| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

auto変数

ブロック内で宣言されている変数は そのブロックに入った時から そのブロックを出る時まで存在する. このような変数は 後述する static 変数に対し auto 変数という. 次の int a; は 「{」 と 「}」 で囲まれた main 関数内で寿命を持つ.

int main(void)
{
int a;

/* */
return 0;
}

関数 func 内で宣言している変数int b はその関数が呼び出されてから, 関数を抜け出す時まで存在している.

#include

/* int b; の領域が存在するのは */
void func(void)
{ /* ここから */
int b;

b = 3;
printf("b in func = %d\n", b);
/* ここまで */
}

int main(void)
{
int a;

a = 2;
printf("a in func = %d\n", a);

func();
return 0;
}

posted by シンビアン at 14:31| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

ローカル変数

プログラムの中のあるブロック内で宣言された変数で、そのブロック内でのみ有効なもの。局所変数ともいう。そのブロック内に設けられた新たなブロックの中では有効である。

 逆に、プログラム内のどのブロックでも有効な変数をグローバル変数といい、同名のローカル変数とグローバル変数があった場合、ローカル変数が宣言されたブロック内であればローカル変数の値が参照され、それ以外ではグローバル変数の値が参照される。
posted by シンビアン at 14:26| Comment(0) | TrackBack(0) | Symbian OS C++ 実践開発技法 | このブログの読者になる | 更新情報をチェックする

広告


この広告は60日以上更新がないブログに表示がされております。

以下のいずれかの方法で非表示にすることが可能です。

・記事の投稿、編集をおこなう
・マイブログの【設定】 > 【広告設定】 より、「60日間更新が無い場合」 の 「広告を表示しない」にチェックを入れて保存する。


×

この広告は1年以上新しい記事の投稿がないブログに表示されております。