DATE : 2006/09/21 (Thu)
(;^ω^)ある程度 C++ の勉強が進みましたので、さっそく勉強日記を付けていこうと思います。間違いがあった場合は、ちょこちょこと修正していきます。
C++ を勉強し始めたわけですが、Java を使っている身としてはやはり、クラスをどのように書くのか気になります。
とりあえず、次のように書かれた Java のクラスを C++ のクラスに書き直してみます。
(Java のクラス)
class Person { private String name; private int age; public Persion(String name, int age) { this.name = name; this.age = age; } public String getName() { return this.name; } public void setAge(int age) { this.age = age; } public int getAge() { return this.age; } }
name プロパティが読み出し専用で age プロパティが読み書き可能な Person クラスです。コンストラクタで両者を設定します。
このクラスを C++ で表現すると、次のようになります。
(C++ のクラス)
#include <string> using std::string; class Person { private : string name; int age; public : Person(const string& name, int age); string getName() const; void setAge(int age); int getAge() const; }; Person::Person(const string& name, int age) : name(name), age(age) { } string Person::getName() const { return this->name; } void Person::setAge(int age) { this->age = age; } int Person::getAge() const { return this->age; }
string を使っているので少々余計な文がありますが、クラスの定義だけを取り出すと次のようになります。
class Person { private : string name; int age; public : Person(const string& name, int age); string getName() const; void setAge(int age); int getAge() const; };
( ^ω^)セミコロンがクラス定義の最後に必要なところが、ちょっと C っぽいですね。
同じアクセス修飾子のフィールドやメソッドはまとめて書くところなどを除けば、それほど難しくはありません。
ちなみに、Java では「フィールド」「メソッド」と呼んでいる言葉は、C++ ではそれぞれ「メンバ変数」「メンバ関数」と呼ばれているようです。
( ^ω^)ここらへん、C の構造体の「メンバ」から派生したような言葉のような感じがしますね
なおに、メンバ関数の後ろについている「const」は、そのメンバ関数がメンバ変数を書き換えないことを示しています。このキーワードは、メンバ変数を書き換えないのなら書くように推奨されているようです。
ところで、C++ のクラスにはアクセス修飾子はありません。クラスの書かれたファイル外にクラスを公開する場合は、ヘッダファイルにクラス定義を記述します。
( ^ω^)Java と違って、C++ のクラスはファイルスコープが最大のスコープのようです。
クラスの実装部分は次の通りです。
Person::Person(const string& name, int age) : name(name), age(age) { } string Person::getName() const { return this->name; } void Person::setAge(int age) { this->age = age; } int Person::getAge() const { return this->age; }
クラス定義のところに実装を書くこともできるのですが、クラス定義をヘッダファイルに書く場合は、クラス定義とその実装とを分離した方がコンパイル時間が短くなったり、オブジェクトファイルのサイズの肥大化を防ぐことができるようです。というのも、ヘッダファイルに実装も書いてしまうと、実装を修正しただけでそのヘッダファイルを取り込んだソースファイルをコンパイルし直さなければなりません。また、実装がヘッダファイルにある場合、include 文によって実装までもが取り込み先のソースファイルに展開されてしまいます。そのため、クラス定義と実装は分離しておくことが推奨されているようです(あえて取り込み先に埋め込む場合や、今回のように同じファイルにあるのであれば分離する必要は特にないようです)
ちなみに、自分自身のオブジェクトを示す this キーワードは C++ にもあります。ただし、 C++ の場合は自分自身のオブジェクトのポインタなので、自分自身のオブジェクトにあるメンバにアクセスする場合は、アロー演算子(->)を使わなければなりません。(Java と同様、メンバにアクセスする場合の this キーワードは省略可能です)
ところで、次のコンストラクタの文は何でしょうか。
Person::Person(const string& name, int age) : name(name), age(age) { }
「:」以降の「name(name), age(age)」は、「初期化リスト」と言って、コンストラクタに渡された引数をメンバ変数に設定する役割があります。
これを Java 風に書くと、次のようになります。
Person::Person(const string& name, int age) { this->name = name; this->age = age; }
上の Java 風のやり方でも動作はするのですが、 C++ では初めに示したような「name(name), age(age)」というやり方が推奨されています。
というのも、Java 風の考えでいくと、メンバ変数の「string name」は string 型の参照を格納する変数に見えるのですが、 C++ では string 型のオブジェクトそのものを格納する変数を表します。つまり、 Java 風の書き方の場合、
- メンバ変数 name が初期化。
- メンバ変数 age が初期化。
- メンバ変数 name に引数 name のオブジェクトを代入。
- メンバ変数 age に引数 age の値を代入。
という形になります。これが初期化リストを使った場合は
- メンバ変数 name に引数 name のオブジェクトをコピー。
- メンバ変数 int に引数 age の値をコピー。
という順序で初期化が完了します。初期化リストを使うことで、効率よくメンバ変数を初期化できるわけですね。
それでは、コンストラクタの引数にある「const string& name」は何でしょうか。「&」の付いた型は参照型と呼ばれます。詳細はまた別の記事に書きますが、参照型には渡される変数の別名のような役割があります。つまり、この引数に渡される変数(やオブジェクト)を表す型になります。「string name」とした場合は値渡しとなって、コンストラクタの呼び出し元とコンストラクタ間でオブジェクトのコピーが行われますが、参照型の場合は行われず、そのまま扱えるイメージです。なお、const は、参照型として渡されたオブジェクトの内容を変えない(つまり、const の付けられたメンバ関数以外にはアクセスしない)という意味を表します。
DATE : 2006/09/19 (Tue)
いま C++ を勉強しているのですが、久々に新しい言語を一から勉強しているせいか、サンプルコードの出力に妙に感動してしまいます。
(;^ω^)例え標準出力に「Hello, World!」と出すだけのプログラムでも、動くとうれしかったりします
(;´Д`)以前には、PHP や Ruby 、Groovy も触ったことがあるのですが、こちらは初めからリファレンスマニュアルを見て書き散らしていたせいか、スクリプトが動いてもあまり感動しなかったんですよね
( ´∀`)初心に帰ってサンプルコード手打ち……というのもなかなかいいものですね。
ところで、Wikipedia の「Hello world」の項目にはいろいろな言語での Hello, World が載っていて面白いです。
(;^ω^)Whitespace での例が載っているあたり、なかなかシャレが効いてますね。
DATE : 2006/09/18 (Mon)
とうとう本格的に BREW に手を出すことになったので、C++ の勉強を始めました。
(;´Д`)本当は Java ME の方を先にするつもりだったのですが、Java ME の件は立ち消えになってしまいました
Java と C はそれなりにやってきましたので、主にそれらと C++ との違いを中心にした記事を書く予定です。
(´・ω・) C の方の腕は完全になまってますが……
とりあえず、最低限 BREW の開発ができるほどの分量を理解できればいいのですが……(C++ の言語仕様の膨大さに弱腰気味です)
(;^ω^)まあ、BREW はいざとなれば C でも開発できるので、気楽にいこうと思います。
DATE : 2006/09/16 (Sat)
(前回の記事)
メソッド抽出の結果
これまでに行ってきたリファクタリングの結果を以下に掲載します。
リファクタリング前のコード :
/** * 指定の画像にアルファチャンネルを加えます。 * * @param image アルファチャンネルを加える画像 * @return アルファチャンネルを加えた画画像 */ private static RenderedImage addAlphaChannel(RenderedImage image) { // 対象画像の大きさ int width = image.getWidth(); int height = image.getHeight(); // 対象画像のカラースペース ColorSpace sourceColorSpace = image.getColorModel(). getColorSpace(); // アルファチャンネルのあるカラーモデル ColorModel destColorModel = new ComponentColorModel( sourceColorSpace, true, false, Transparency.BITMASK, DataBuffer.TYPE_BYTE); WritableRaster destRaster = destColorModel. createCompatibleWritableRaster(width, height); // 色成分のバンド番号の配列 int[] colorBandList = getColorBandList(destRaster.getNumBands()); // 色成分のみの WritableRaster WritableRaster colorRaster = destRaster. createWritableChild(0, 0, width, height, 0, 0, colorBandList); WritableRaster alphaRaster = destRaster. createWritableChild(0, 0, width, height, 0, 0, new int[]{destRaster.getNumBands() - 1}); // 可視領域を表すアルファ成分のみの Raster WritableRaster visibleMaskRaster = createVisibleAlphaRaster(image); colorRaster.setRect(image.getData()); alphaRaster.setRect(visibleMaskRaster); return new BufferedImage(destColorModel, destRaster, false, null); } /** * 可視部分のアルファ成分のみを持つ WritableRaster を生成します。 * * @param source 生成対象の画像 * @return 可視部分のアルファ成分のみを持つ WritableRaster */ private static WritableRaster createVisibleAlphaRaster( RenderedImage source) { // 画像の大きさ int width = source.getWidth(); int height = source.getHeight(); // SampleModel SampleModel sampleModel = new BandedSampleModel( DataBuffer.TYPE_BYTE, width, height, 1); WritableRaster raster = Raster.createWritableRaster( sampleModel, new Point(0, 0)); int[] alphaSample = new int[width * height]; // データの最大値を設定(アルファ成分を最大にする) Arrays.fill(alphaSample, 0xff); raster.setSamples(0, 0, width, height, 0, alphaSample); return raster; } /** * 指定のバンド数から、アルファ成分を除いたバンド番号の配列を * 返します。 * * @param numBands バンド数 * @return アルファ成分を除いたバンド番号の配列 */ private static int[] getColorBandList(int numBands) { int[] bands = new int[numBands - 1]; for (int i = 0; i < bands.length; i++) { bands[i] = i; } return bands; }
リファクタリング後 :
private static int ALPHA_VALUE = Integer.MAX_VALUE;
/** * 指定の画像にアルファチャンネルを加えます。 * * @param image アルファチャンネルを加える画像 * @return アルファチャンネルを加えた画画像 */ private static RenderedImage addAlphaChannel(RenderedImage image) { ColorModel destColorModel = createColorModelWithAlpha(image.getColorModel()); WritableRaster destRaster = destColorModel. createCompatibleWritableRaster( image.getWidth(), image.getHeight()); setColorSamples(destRaster, image.getData()); setAlphaSamples(destRaster); return new BufferedImage(destColorModel, destRaster, destColorModel.isAlphaPremultiplied(), null); } /** * アルファチャンネルを追加したカラーモデルを生成します。 * * @param src アルファチャンネルを追加する元となるカラーモデル * @return アルファチャンネルを追加したカラーモデル * @throws NullPointerException src が null の場合 */ private static ColorModel createColorModelWithAlpha(ColorModel src) { return new ComponentColorModel(src.getColorSpace(), true, src.isAlphaPremultiplied(), Transparency.BITMASK, src.getTransferType()); } /** * 指定の WritableRaster の色成分を設定します。 * * @param dest 色成分を設定する WritableRaster * @param src 色成分を表す Raster */ private static void setColorSamples(WritableRaster dest, Raster src) { WritableRaster colorRaster = dest.createWritableChild( 0, 0, dest.getWidth(), dest.getHeight(), 0, 0, getBandListExceptAlpha(dest)); colorRaster.setRect(src); } /** * 指定のバンド数から、アルファ成分を除いたバンド番号の配列を * 返します。 * * @param raster アルファ成分を除いたバンド番号の配列を取得する Raster * @return アルファ成分を除いたバンド番号の配列 */ private static int[] getBandListExceptAlpha(Raster raster) { int[] bandList = new int[raster.getNumBands() - 1]; for (int i = 0; i < bandList.length; i++) { bandList[i] = i; } return bandList; } /** * 指定の WritableRaster にアルファ成分を設定します。 * ただし、WritableRaster には、アルファ成分を格納するバンドが * 存在しなければなりません。 * * @param raster アルファ成分を設定する WritableRaster */ private static void setAlphaSamples(WritableRaster raster) { int alphaBand = raster.getNumBands() - 1; for (int y = 0; y < raster.getHeight(); y++) { for (int x = 0; x < raster.getWidth(); x++) { raster.setSample(x, y, alphaBand, ALPHA_VALUE); } } }
(つづきます)
DATE : 2006/09/15 (Fri)
(前回の記事)
メソッドの抽出 (3)
前回に続いてさらに、「4 : 2の WritableRaster のアルファ成分を設定」の部分
WritableRaster alphaRaster = destRaster. createWritableChild(0, 0, width, height, 0, 0, new int[]{destRaster.getNumBands() - 1}); // 可視領域を表すアルファ成分のみの Raster WritableRaster visibleMaskRaster = createVisibleAlphaRaster(image); alphaRaster.setRect(visibleMaskRaster);
を setAlphaSamplesメソッドに抽出します。
/** * 指定の WritableRaster にアルファ成分を設定します。 * ただし、WritableRaster には、アルファ成分を格納するバンドが * 存在しなければなりません。 * * @param image アルファ成分を追加する元となる画像 * @param dest アルファ成分を追加する WritableRaster */ private static void setAlphaSamples( RenderedImage image, WritableRaster dest) { WritableRaster alphaRaster = dest. createWritableChild(0, 0, width, height, 0, 0, new int[]{destRaster.getNumBands() - 1}); // 可視領域を表すアルファ成分のみの Raster WritableRaster visibleMaskRaster = createVisibleAlphaRaster(image); alphaRaster.setRect(visibleMaskRaster); }
しかし、メソッドを抽出していてふと思いました。上のメソッドでは、アルファ成分のみの Raster を生成して、WritableRaster のアルファ成分として設定しています。ところが、ここで設定するアルファ値は全画素で共通のはずです。すると、わざわざアルファ成分のみの Raster を生成するよりも、直接アルファ成分を設定した方がコードは簡単になります。
そこで、上のコードを、直接アルファ成分を設定するように変更します。
/** * 指定の WritableRaster にアルファ成分を設定します。 * ただし、WritableRaster には、アルファ成分を格納するバンドが * 存在しなければなりません。 * * @param raster アルファ成分を設定する WritableRaster */ private static void setAlphaSamples(WritableRaster raster) { int alphaBand = raster.getNumBands() - 1; for (int y = 0; y < raster.getHeight(); y++) { for (int x = 0; x < raster.getWidth(); x++) { raster.setSample(x, y, alphaBand, 0xff); } } }
ここでは、最大のアルファ値として 0xff を設定しています。ところが、1バンクの幅が1バイトを超えると、0xff は最大のアルファ値とはなりません。例えば、WritableRaster(Raster)の転送型が java.awt.image.DataBuffer#TYPE_INT の場合は、符号付き4バイト値(int型)でアルファ値が表されることになります。そのため、0xff を int 型の最大値に変更します。(バンク幅を超えた値は、バンク幅に合わせてビットが切り捨てられます)
raster.setSample(x, y, alphaBand, Integer.MAX_VALUE);
また、Integer.MAX_VALUE に「アルファ値」としての意味を持たせるため、定数として抽出します。
結果、setAlphaSamples メソッドは次のようになります。
private static final int ALPHA_VALUE = Integer.MAX_VALUE;
/** * 指定の WritableRaster にアルファ成分を設定します。 * ただし、WritableRaster には、アルファ成分を格納するバンドが * 存在しなければなりません。 * * @param raster アルファ成分を設定する WritableRaster */ private static void setAlphaSamples(WritableRaster raster) { int alphaBand = raster.getNumBands() - 1; for (int y = 0; y < raster.getHeight(); y++) { for (int x = 0; x < raster.getWidth(); x++) { raster.setSample( x, y, alphaBand, ALPHA_VALUE); } } }
初めに抽出したメソッドと比べて、引数が1つ減りました。
また、createVisibleAlphaRaster メソッドを使う必要がなくなりました。そのため、このメソッドは消去しておきます。
なお、「4 : 2の WritableRaster のアルファ成分を設定」の部分は次のように変わります。
setAlphaSamples(destRaster);
最後に、「5 : BufferedImage の生成」の部分です。
return new BufferedImage(destColorModel, destRaster, false, null);
しかし、この部分はコメントの通り BufferedImage を生成しているだけなので、メソッドには抽出しません。
ただ、アルファの乗算状態を false と決め打ちにしているところは気になります。そこでこの部分を、処理対象の画像のカラーモデルと同じ状態になるように変更します。
return new BufferedImage(destColorModel, destRaster, destColorModel.isAlphaPremultiplied(), null);
(つづきます)