OriosのActiveBasic講座(1-4): 配列・構造体・ポインタ

3.文字列ポインタ

【目標】String型を使わないで文字列を扱う方法をマスターすることでコンピュータの構造に詳しくなりましょう。

この章で扱っている内容は、かなり難しいものです。 しかし、この章で扱うものは、実際にソフトを作るようになると非常に重要になってくるものばかりです。 分かりにくいとは思いますが、それでも頑張ってください。

それで、今回は「文字列ポインタ」をやります。 簡単にいえば「String型を使わず文字列を扱おう」という内容です。 実はActiveBasicで「String型」を用いて文字列を扱おうとすると、どうしても実行速度が遅くなってしまいます。 とは言っても、別に普通の簡単なプログラムではいくら遅くなるといっても気にするほどではありません。 しかし、大量の計算が必要になるようなプログラムではどうしてもこの壁にぶち当たってしまいます。 一方、今回学習する「文字列ポインタ」を使って文字列を扱うとは実行速度は非常に速いです。 ということで、今回はこの「文字列ポインタ」を学習して、文字列の別の扱い方を知り、さらには高速にプログラムを動かせるようになりましょう。

早速下のプログラムを実行してみてください。

'lesson1-4-3
#console
Dim str As BytePtr
Dim name As String
Dim age As Integer
Input "名前(半角)と年齢をコンマで区切って入力してください -->", name, age
str = calloc(Len(name) + Len(Str$(age)) + 36)
lstrcpy(str, name)
lstrcat(str, "は")
lstrcat(str, Str$(age))
lstrcat(str, Ex"歳です。その頭文字は\q")
lstrcat(str, Chr$(name[0]))
lstrcat(str, Ex"\qです。")
MessageBox(0, str, "lesson1-4-3", MB_OK)
free(str)
End

実行すると次のようなウィンドウが表示され、

C:\ActiveBasicCourse\lesson1-4-3.abp
名前(半角)と年齢をコンマで区切って入力してください -->

名前と年齢を入力すると、例えば -->Orios,16 のように入力すれば、 ダイアログウィンドウ のように、名前と年齢、そして名前の最初の文字が表示されます。

では今回のプログラムの説明です。 今回はいかにもコンピューターを扱っているということを実感させられるような少し難しい内容ですが、頑張って読んでください。

-*-*-*-*-説明-*-*-*-*-
【使い方】
( ) Dim 変数名  As 型名(〜Ptr) 
【意味】
( ) 変数名 をポインタ型の型名(〜Ptr) として使うことを宣言します。
【解説】
ポインタ変数
( ) ポインタは変数のアドレスを示すものであるということはこの講座で何回も言っていますが、変数の型の中には変数のアドレスを保存するための専用の型があります。 この型のことを「ポインタ型」と言います。 この「ポインタ型」には次の種類があります (実はこの表は前にも配列のところで出てきています)。
ポインタの型使用する変数の型備考
DoublePtrDouble8バイト・浮動小数点型
SinglePtrSingle4バイト・浮動小数点型
DWordPtrDWord, Long4バイト・整数型
WordPtrWord, Integer2バイト・整数型
BytePtrByte, String1バイト・整数型
このように、型によって扱える変数のアドレスが異なっています。 そして、今回は文字列を扱うのでBytePtrを利用します。 なぜ今回わざわざポインタを扱うかは後で説明します。
【使い方】
calloc(確保するバイト数 )
【意味】
確保するバイト数 バイト分のメモリーをヒープ領域から確保します。
【解説】
(1) calloc関数
(i) ここからいよいよString型を使わない文字列の使い方を本格的に扱います。 まず文字列に必要なものは何でしょうか? ──それは記憶領域です。 文字列を保存する記憶領域(メモリー)がなければ文字列を扱うことはできません。 なのでここではまず、必要な記憶領域を確保するための方法を学びます。
(ii) 記憶領域が必要なことが分かりましたが、それではどこから必要な記憶領域を確保すればいいでしょうか? 記憶領域なんて勝手にどこからでも取れるものではありません。 なぜかといえば、他のプログラムが使っているようなところから取ったら、そのプログラムの中の変数が勝手に変わってしまうわけだからです。 ということで、それぞれのプログラムにはあらかじめOS(Microsoftの"Windows"などのコンピュータ全体を制御するためのプログラム)から「この部分は使っていいよ」という領域が与えられています。 これが「ヒープ領域」です。
(iii) そしていよいよ関数の説明ですが、この「calloc関数」は、プログラムに割り当てられたヒープ領域から必要なバイト数を確保するための関数です。 つまり必要な分の記憶領域を得るための関数です。 確保するバイト数 に必要な記憶領域のバイト数を書けば、そのバイト数を確保してくれます。 そして記憶領域を確保すると、この関数はその記憶領域の先頭アドレスを返してきます。 なのでこの関数の戻り値を受け取るためにはアドレスを記憶するための専用変数──ポインタ型で宣言された変数を用意する必要があるのです。
(2) 文字列の仕組み
( ) ここで文字列の仕組みを簡単に勉強しておきましょう。 文字列は下のような構成しています。 "Orios"の例:
Orios
791141051111150
上のように、文字列は先頭アドレスから順にその文字の文字コードを1バイトごとに書いていきます。 しかし、文字をただひたすら書いただけでは問題があります。 それは「文字列がどこで終わるか?」ということです。 ひょっとしたら文字列は"Ori"だけで"os"は別の文字列の一部かもしれません。 ということで、文字列には必ず「ここで文字列が終わっていますよ」ということを示すために文字列の最後に文字コードが「0」という特別な文字をつけます。 この特別な文字のことをNULL文字と言います。 記憶領域を確保するときには最後にこの文字がつくことを考慮して、1バイト余分に確保する必要があります。
※ 「calloc関数」で確保された領域は全て文字コード「0」、すなわち「NULL文字」で初期化されています。
【使い方】
(1) lstrcpy(文字列ポインタ , 代入する文字列のポインタ )
【意味】
(1) 文字列ポインタ に代入する文字列のポインタ を代入します(厳密に言えばコピー)。
【解説】
(1) lstrcpy関数
(i) 今まで説明したことで「String型を使わない文字列」を使う準備ができました。 では実際に使ってみましょう……といきたいところですが、そう簡単にはいきません。 例えば文字列を代入することを考えてみましょう。 普通のString型のときのように「str = "aaa"」や「str = name」としようとしてもなかなかうまくいきません。 やっぱり「String型を使わない文字列」にはそれに適したやり方があるのです。 それが「lstrcpy関数」です。
(ii) 「lstrcpy関数」の使い方ですが、代入させたい文字列へのポインタを文字列ポインタ に書き、代入する文字列のポインタ に代入したい文字列のポインタを書きます。 といっても、「String型を使わない文字列」で使う変数は[1]で説明したとおり、もともとポインタ型を使っているわけですから特にポインタを気にする必要はありません。 またString型もポインタを気にする必要はありません。 これは下で説明します。
※ 引数が「ポインタ」であることから、以下のように文字列を「+」でくっつけたのをまとめて代入……ということはできません。
[例] × lstrcpy(str, name + "a")
× lstrcpy(str, name + Str$(age))
× lstrcpy(str, name + str_2) (str_2は文字列変数と考えてください。)
× lstrcpy(str, "abc" + "def")
(2) String型の二つの顔
(i) String型は一見ポインタ型ではないので「lstrcpy関数」を使うときにはポインタを気にするようにする必要が出てくると思います。 しかし実際にはポインタを気にせずそのまま書けばそのまま通りました。 実はこれこそが「String型の二つの顔」なのです。
(ii) String型には二つの顔があります。 一つは文字列としての顔です。 これはString型の変数に「Print」を使えば、それに保存されている文字列が表示されていることから分かると思います。 そしてもう一つ、これが重要なのですがString型はポインタとしての顔があります。 なのでポインタを書く必要があるところでString型を書くと、それは文字列としてではなくポインタとして扱われるのです。 このような2つの顔は「String型を使わない文字列」で使う変数にはありません。 「Print」を使って表示すると分かります。 文字列は表示されずにわけの分からない数字しか表示されません。 これは「String型を使わない文字列」で使う変数はあくまでポインタでしかないからです。 またString型は「Input」が使えますが、「String型を使わない文字列」は使えません。 これも「String型を使わない文字列」の方は結局はポインタだからです。
【使い方】
(1) lstrcat(文字列ポインタ , 連結する文字列のポインタ )
【意味】
(1) 文字列ポインタ で示された文字列に連結する文字列のポインタ で示された文字列を連結します。
【解説】
(1) lstrcat関数
( ) 普通String型なら「+」を使えば連結できるものをどうしてこんな関数を使うかというのは、さきほどのlstrcpy関数の理屈と同じです。 そして使い方ですが、文字列ポインタ にベースとなる文字列へのポインタ、そしてベースの文字列に連結される文字列へのポインタを連結する文字列のポインタ に書きます。 これで「ベースとなる文字列のポインタ」の後ろに「連結される文字列」がくっつきます。 つまり「a = a + b」と同じことをやっているのです。 あと引数がポインタであるということについてもlstrcpy関数と同じです。
(2) ""で囲まれた文字列の二つの顔
( ) なんかここでやっていることがさっきとほとんど同じような気がするのですが、「""で囲まれた文字列」にも二つの顔があります。 一つは「文字列としての顔」、もう一つは「ポインタとしての顔」です。 詳しくはさきほどのString型の二つの顔を見てください。 はっきり言って同じです。
【解説】
文字列を返す関数の戻り値の二つの顔
( ) 私は決してみなさまを笑わそうとしてこんなことをやっているわけではないのですが、Str$関数Mid$関数Chr$関数のように「文字列を返す関数」の戻り値には二つの顔があります。 一つは「文字列としての顔」、もう一つは「ポインタとしての顔」です。 詳しくはさきほどのString型の二つの顔を見てください。 (……手抜きしてるような気がしてきました。)
【使い方】
( ) Ex"文字列 "
【意味】
( ) 文字列 の中でエスケープシーケンスが有効になります。
【解説】
エスケープシーケンス
(i) 文字列を書くときは普通は"abc"のように書けば済みます。 しかし一部の文字列はそうはいきません。 たとえば「"」です。 これを文字列の中で書こうとすると「"a"b"c"」(←「a"b"c」のつもり)となってどこまでが本当の終わりなのかややこしくなり、エラーが出てしまいます。 しかし「"」を書きたいために「"」の文字コードが「34」であることを利用して「"a" + Chr$(34) + "b" + Chr$(34) + "c"」なんてするのも面倒です。 そこで役に立つのが「エスケープシーケンス」です。
(ii) 文字列の""の前に「Ex」という文字をつけた中では「\」は特別な記号として扱われます。 というのは、「\」の後に決められた文字を書くことで「"」が使えたり、文字列内で「改行」ができたり、先ほど説明したNULL文字が使えたりします。 以下にその「決められた文字」のリストを載せます。
表記表現されるもの
\0NULL文字
\tタブ文字
\qダブルクォーテーション(")
\r\n改行 (これは\rと\nの2つを並べることで改行になります)
これを見て疑問に思ったことはありませんか? 例えば単純に"\0"(円記号と"0")を表示したいときはどうしますか? NULL文字と紛らわしいですね。 他にもリストにはないものの"\x"なんて書いたら、これはこれで、間違いなのか「"\" + "x"」なのかやっぱり紛らわしいですね。 ということで、円記号自身を使いたいときはEx""の中では必ず「\\」と「\」を2つ並べて表現します。
【使い方】
( ) 文字列変数 [場所 ]
【備考】
(i) [ ]の代わりに( )を使っても構いません(非推奨)[2005/05/12]
(ii) 文字列変数 がString型の配列のときは、「str[1][2]」のようにすることでこれが使えます。
【意味】
( ) 文字列変数 の(場所  + 1)文字目の文字コードを表します。
【解説】
文字列へのアクセス(1)
(i) [2]の「文字列の仕組み」のところで、文字列はそれぞれの文字の文字コードを1バイトずつ格納して最後にNULL文字をつけると書きましたが、そのときのテーブルをみて何か気づきませんでしたか? そうです。 Byte型の配列のときとそっくりなのです。 つまり文字列というのはByte型配列を文字列として使っているようなものなのです。 なので、文字列のどれか一文字の文字コードを取得したいときは、配列を応用して「name[1]」のようにすることで、その格納されている文字コードを直接使用することができます。 この方法はString型変数であろうと「String型を使わない文字列変数」であろうと使えます。
(ii) この方法で取得できるのはあくまで「文字コード」つまり「数値型」の値です。 なので取得したものを文字列として扱いたいときはChr$関数で文字コードを文字列に変換する必要があります。
[例] 「str」は「String型を使わない文字列変数」だとします。 そして"Orios"という文字が入っていたとします。 すると0:「O」、1:「r」、2:「i」、3:「o」、4:「s」となるので下のようになります。
Chr$(str[0]) = "O", Chr$(str[1]) = "r", Chr$(str[2]) = "i", Chr$(str[3]) = "o", Chr$(str[4]) = "s"
これはたとえ「str」がString型変数であっても同じです。
※ 実はこの方法は高速化に非常に便利です。 一文字だけ取り出す場合、Mid$関数を使うよりもこの方法を使ったほうが断然速いです。 なので文字列を使う場合で高速化を考える場合は、この方法での文字の取得も検討してみましょう。
【使い方】
( ) MessageBox(0, 表示する文字列へのポインタ , タイトルバーに表示する文字列へのポインタ , タイプ )
【意味】
( ) タイトルバーにタイトルバーに表示する文字列へのポインタ の示す文字列、本文に表示する文字列へのポインタ が表示されるダイアログボックスを出します。
【解説】
MessageBox関数
(i) ダイアログボックスというのはよく見かけるとは思いますが下のようなものです。 ダイアログボックス
これのうち「lesson1-4-3」と表示されている部分が「タイトルバー」、「Oriosは16歳です。その頭文字は"O"です。」と表示されている部分が「本文」です。
(ii) 表示する文字列へのポインタ に「本文」に表示させたい文字列へのポインタ、タイトルバーに表示する文字列へのポインタ に「タイトルバー」に表示させたい文字列へのポインタ、そしてタイプ を適切に書けば(詳しくは後で説明)ダイアログボックスが表示されます。 「0」の部分は、本当はきちんとした意味のある部分なのですが、今のうちは「0」と書いておけば結構です。
(iii) タイプ
タイプ の部分にはダイアログボックスのスタイルを下に従って書きます。
書くものそれが示すスタイル
MB_OK「OK」ボタンだけが表示されます。
MB_YESNO「はい」ボタンと「いいえ」ボタンが表示されます。
MB_YESNOCANCEL「はい」ボタン、「いいえ」ボタン、「キャンセル」ボタンが表示されます。
MB_OKCANCEL「OK」ボタンと「キャンセル」ボタンが表示されます。
MB_ICONEXCLAMATION感嘆符 を同時に表示します。
MB_ICONINFORMATIONiマーク を同時に表示します。
MB_ICONQUESTION疑問符 を同時に表示します。
MB_ICONSTOP×マーク を同時に表示します。
※1 お使いのOSによって画像が違う場合があります。上はWindowsXPの例です。
※2 ここで使われているアイコン画像の著作権はMicrosoftが持っています。 あくまで「引用」です。 何と言ってこようと「引用」です。
赤の部分のものについて何も書かなかった場合は「MB_OK」を書いたものとして扱われます。 また青の部分のものについて何も書かなかった場合はアイコンは表示されません(ちょうど上のダイアログボックスの例のようになります)。 また赤の部分のものと青の部分のものを同時に書きたい場合は、同時に書きたいものを「Or」で結びます。 例えば「MB_OK」と「MB_ICONSTOP」を同時に書きたいときは「MB_OK Or MB_ICONSTOP」のように書きます。
※ もっと詳しく知りたい方はActiveBasicのヘルプから「リファレンス」→「Win32API」→「ウィンドウ」→「ダイアログボックス」→「MessageBox」を見てください。 より詳しい情報が載っています。
(iv) 戻り値
上の項目でいろいろなボタンの表示の仕方を学んだわけですが、実際問題ユーザがどのボタンを押したのかが分からないといくらボタンがあっても役に立ちませんよね。 で、そのユーザが何を押したかというのはこの関数の戻り値(Long型)で分かります。 その戻り値は押したボタンによって下のようになります。
戻り値押されたボタン
IDOK「OK」ボタン
IDYES「はい」ボタン
IDNO「いいえ」ボタン
IDCANCEL「キャンセル」ボタン
実際に押されたボタンを確認するには「ans = MessageBox(NULL, "かくかくしかじか", "まるまるうしうし", MB_OKCANCEL)」のようにして戻り値を取得して、「If ans = IDOK Then」のようにして判断します。
※1 MessageBox関数を使うと、表示されたダイアログボックスのボタンをユーザが押すまでプログラムは一時停止します。
※2 何で今回MessageBoxをわざわざ出してきたかというと、先ほども[3]の(2)(ii)で書きましたが、「String型を使わない文字列変数」は「Print」で使えないからです。
【使い方】
( ) free(使用済み文字列ポインタ )
【意味】
( ) 使用済み文字列ポインタ の示す文字列が使っていた記憶領域を解放します。
【解説】
free関数
( ) [2]で「String型を使わない文字列」は「ヒープ領域」を利用すると言いました(もちろんそれ以外の変数も利用しますが……)。 ところでこのヒープ領域というのは、OSや他のプログラムが使う領域を使わないようにするわけですから、地球資源のようにやはり限りがあるわけです。 ということは効率的に使う必要があるわけです。 すると、もう必要なくなった記憶領域というのは別の文字列・変数などで使えるようにする必要があるということになります。 そのために使うのがこの「free関数」です。 この使用済み文字列ポインタ のところにもう使わなくなった「String型を使わない文字列変数」を書くと、その文字列変数が使っていた記憶領域を他の変数が使えるようにします。 これは非常に重要なことなので必ず「String型を使わない文字列」を使うときは忘れずにしてください(……自分もこの講座を作るときに初めて解放の必要性を知った人間ですが……)。
※ これが必要なのはあくまでcallocでヒープ領域を確保したときだけです。 普通の変数やString型はActiveBasic側で自動的にしてくれます。

講座のキロバイト数が最も長い前回の記録をまた更新してしまったレッスンですがいかがでしたでしょうか? 今回は「String型を使わない文字列」の扱い方をいろいろやったわけですが、確かにString型の方がよっぽど簡単に使えます。 しかし、その中あえて難しい方法を学ぶことで、ポインタなどの概念を少しでも分かっていただこうということで今回これを扱いました。 もしポインタの概念が完全に理解できていなかったとしても、それはそれで別に構いません。 そんなものがあったなあということだけでも頭の片隅に留めていただければそれで結構です。

というわけで、難しいものばかり扱ってきた第四章はこれで最後です。 レッスンの最後、と同時に第四章の最後のまとめ「やってみよう」です。

-*-*-*-*- -*-*-*-*-

ユーザから入力された文字列(半角文字のみ)を、全ての文字の間に「・」をはさんで、それをダイアログボックスで表示するようなプログラムを作ってみましょう。 例えば「Orios」と入力されたなら、「O・r・i・o・s」と表示するようにします。


【ヒント】
  • 何文字入力されるか分からないものの全ての文字に対して何か処理を行うときはループを使うとうまくできます。
  • 「・」は全角文字、つまり2バイトです。

ご質問・ご意見・ご感想・苦情等はメールまたはメイン掲示板のActiveBasic板などでお気軽にどうぞ。

前に戻る - 次に進む

ホーム > OriosのActiveBasic講座(トップページ) >