HSP ver3文字列のひみつ(TIPS)



はじめに

HSPでは、数値型、文字列型などの型を変数の内容として保持することができます。 このドキュメントでは、その中でも文字列型がどのようにHSPで処理されているかを説明しながら、 それぞれの命令の詳細と、その応用についての説明をしています。 文字列の取り扱いを理解することで、より細かく文字列の操作を行なうことができるはずです。 また、メモリ管理やファイルの扱いに関係する部分もあるので、知っておくときっと役に立つ時が 来るでしょう。


文字列の基本

まず、HSPでの文字列の取り扱いの基本をおさらいしておきましょう。 文字列とは、"(ダブルクォーテーション)で囲まれた、文字の集合体です。 HSPでは、メッセージの表示や、ファイル名などあらゆる場面で文字列を使用します。 たとえば、

という命令では、「"」で囲まれた「TEST MESSAGE」 という文字列を画面に表示します。また、変数に文字列を記憶させておくこともできます。 たとえば、「a="TEST MESSAGE"」のように書けば、変数aに「TEST MESSAGE」という文字列が 記憶されます。そうすれば、「mes a」のように文字列を指定するかわりに変数名を指定 して、変数に記憶されている文字列をパラメータとして使うことができるようになります。
以上は基本ですが、この応用として文字列に式を加えることができるようになっています。
たとえば、 上の例では、「TEST」という文字列と、「 MESSAGE」という文字列を「+」で足し算しています。 数値の計算ならば、加算されるところですが、文字列を足し算した場合は、文字列が連結されます。 つまり、2つの文字列をつなげて「TEST MESSAGE」という文字列になるのです。 文字列で使える式は、足し算のみですが「a="ABC"+"DEF"+"GHI"」のようにいくつも繋げることが できますし、「a="ABC"+b+"DEF"+c」のように間に変数をはさむことも可能です。 ですから、 のようにすると、3つの変数の内容が連結されて「TEST AND MESSAGE」という文字列が画面に 表示されます。もしこれが、文字列型の変数でなく、数値が記憶されている数値型変数を途中で 足し算した場合には、どうなるのでしょうか? HSPでは、次のようなお約束があります。

「文字列型」に「数値型」を足し算した場合には、「数値」を文字列にして連結される。
「数値型」に「文字列型」を足し算した場合には、「文字列」を数値にして加算される。

つまり、計算式で最初の項になっている型に自動的に合わせてくれるということです。 ですから、 のように数値型の変数が間にあっても、「TEST 12345 MESSAGE」という表示になり、これは あくまで文字列として扱われます。


文字列とは何か?

HSPでは文字列をどのように管理しているのでしょうか? 前にも言ったように、文字列は長さ不定の文字の集合体です。 文字列型変数には、メモリの許す限り無制限の文字数を記憶することができます。 ここで、「文字列」と「バッファ」の関係を知っておくと、文字列の扱いが よりスッキリとします。
HSPでは、文字列の1文字1文字を「コード」という数値で扱って管理しています。 これは、どんなコンピュータでも内部では数値で管理されているためです。 下の表を見てみてください。



コード(10進数) コード(16進数) 0 1 2 3 4 5 6 7 8 9 a b c d e f
32 $20   ! " # $ % & ' ( ) * + , - . /
48 $30 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
64 $40 @ A B C D E F G H I J K L M N O
80 $50 P Q R S T U V W X Y Z [ \ ] ^ _
96 $60 ` a b c d e f g h i j k l m n o
112 $70 p q r s t u v w x y z { | } ~  

これは、「アスキーコード表」と呼ばれているものです。 どんな文字にも、それに対応する数値(コード)があります。その中でも標準的に使われている、 半角のアルファベットや記号だけを抜き出したものをアスキーコード ( ASCII : American Standard Code for Information Interchange ) といいます。英語ではこれで十分なのですが、日本語ではこの他に全角の文字が多数あり、 それぞれにやはりコードがあります。 これは何も難しい概念ではなくて、単純に「A」という文字はコード65(10進数)、「$」は 36(10進数)というように数字に直すことができるよ、というだけの話です。 この表の見かたは、たとえば「@」であれば左の「コード」にある数値の通り「64」になり、 以後右にずれるたびに1づつふえていきます。つまり、「A」は65、「B」は66…というぐあいです。 「コード」のところに10進数と、16進数が書いてありますが、コンピュータの世界では16進数だと 区切りがいいということで、HSPでは10進数でもかまいませんし、16進数でももちろんOKです。 (HSPで16進数は「$48」など$を頭につけて表記します) ですから、「N」は10進数では78、16進数では$6eになります。混乱すると困るので、ここでは 特に表記がない限り、コードは10進数で扱っていきます。
ちなみに、スペースキーを押して出てくるスペースは、コード32になります。 他にスペースになっている場所もありますが、通常は使われません。
さて、文字列は、このコードの集まりだと考えることができます。 コードは、0から255までの数値になります。つまり256種類の文字を表わすことができるという わけです。しかし、日本語の漢字などを含めると膨大な量になり、とても256種類では足りません。 そこで、HSPでは、日本語はコードを2つ使用して256種類×256種類=65536種類(そんなにないけど)ぶんの コードを使うことにしています。 日本語の文字が全角と呼ばれ、英文字が半角と呼ばれるのは、 英文字(アスキーコード)が日本語文字に対して半分サイズのコードだからなのです。 日本語コードの表わしかたには色々な種類がありますが、HSPが使用しているものは、 シフトJIS(SJIS)と呼ばれているものです。このドキュメントでも、シフトJISに基づいた説明を行なっています。
まずは、「文字を表わすコードが、いくつか並んでいるものが文字列」なのだということを ここでは覚えておいてください。これが何の役に立つか、もうすぐわかるはずですよ。


文字列とバッファ

文字は、0〜255までの数値で表わされるコードにすることができることがわかりました。 この、256種類という数字はコンピュータの世界ではよく出てくる1バイト(byte)という単位に なります。バイトとは、数値をコンピュータのメモリに記憶させる際の基本となる単位で、 1バイトには、0〜255までの数値を記憶させておくことができます。1メガバイトのメモリは、 バイトに直すと、ざっと1,000,000バイトですからごくごく小さな単位です。
こんなハード寄りの話を出したのは、難しくしたいためじゃありません。 この1バイト、0〜255までの数値というところでHSPの、poke命令およびpeek命令のことを 思い出してもらいたかったからです。
poke命令、peek命令はメモリバッファの読み書きをするための命令で、ちょっと文字列とは 無縁な感じがします。しかしながら、 変数の内容が記憶されている場所もまた、メモリバッファの1つ なのです。これは知っておくと役に立つ重要な概念です。文字列型変数に記憶されている文字列 の内容や、数値型の変数、そして配列までもがpeek関数、poke命令で読み書き可能である ということです。同様に、メモリバッファを対象にした命令(bload命令やbsave命令)もすべて 通常の変数に対して使用できます。
ここでためしに、

というスクリプトを実行してみてください。変数aに代入された「*」という文字列が記憶されている バッファから、1バイトを取り出して表示していますが、「*」の文字コードである42という数値 が取り出されています。このように、poke命令、peek関数を使うことで、記憶されている文字列に 対してより細かなアクセスができることになります。
これからいよいよ、その細かなアクセスの詳細について説明していきましょう。


文字列のしくみ

文字列が記憶されているバッファには一定のルールがあります。 まずは、この重要なルールをまず最初に覚えておいてください。

この2つのルールさえわかってしまえば、あとはこれに従ってバッファをどういじっても問題 ありません。まず簡単な例として次のようなスクリプトがあったとします。 これが実行されると、変数aの内容が記憶されているバッファは次のようになります。

A B C D E 終了コード
65 66 67 68 69 0

文字列「ABCDE」は、バッファの中では「65」「66」「67」「68」「69」「0」という数値に なって記憶されることになります。peek関数は、バッファの中の好きな場所から1バイト、 データを取り出すことのできる命令です。ヘルプによれば、 ということなので、 とすれば、変数aの3文字目にある文字のコードを読み出すことができます。 なぜ3文字目になるかと言うと、peek命令の2番目のパラメータは、0から始まって1バイト ごとの指定なので、2を指定した場合は0・1・2の順で3文字目にあたるコードを取り出して いるためなのです。
poke命令は逆にバッファにデータを書き込みための命令です。ヘルプによれば、 ということなので、たとえば、 とすれば、変数aには何も文字列を代入していないにもかかわらず、バッファに直接データを 書き込んだために、「ABC」という文字列が記憶されています。 忘れてはいけないのは、最後に終わりを示すコードとして0を入れておくことです。 HSPは、0が出てくるまでバッファの内容を文字列として解釈します。もし、0を忘れてしまうと 余計なデータを文字列に加えてしまったり、最悪の場合、一般保護エラーが出るまでメモリを 読み続けてしまいます。
この終わりを示すコードを逆手に取って次のようなこともできます。 このスクリプトでは、変数aに「ABCDEF」という文字列を代入していますが、その後でpoke命令 により、途中に終了コードを書き込まれています。その結果、「ABC」の後に終了コードがある ためにそこで文字列は終了と判断され、「ABC」という文字列だけしか表示されません。
このように、文字列のしくみを理解することで、文字列の各文字に対してのアクセスが 容易になります。
バッファの中を読み出したり、書き込んだりする場合には注意しなければならない点があります。 変数バッファに文字列は無制限に代入することができますが、最初から大きなサイズのバッファを 用意するわけにはいきません。そこで、最初は小さな領域を確保しておき、必要に応じて 領域を拡張しています。
sdim命令により文字列の宣言を行なう際に、最初に確保する領域サイズを指定することができます。 領域の拡張は自動で行なわれますが、最初から大きなサイズの文字列を使うことがわかっている場合や、 2次元以上の配列を使う場合には指定をしておくといいでしょう。 ただし、256バイトのバッファを確保したとしても、255文字までしか使うことはできません。 最後に終了コード0を入れる場所が必要だからです。「sdim a,256」は、255文字+終了コード までは領域を確保するという意味になります。
peek関数やpoke命令は、現在確保されている領域内のみにしか使用することができません。 たとえば、「sdim a,256」のように256バイトのバッファを確保していたとすれば、 0〜255の範囲しか利用できないので注意してください。

文字列の一部を文字コードにして取り出したり、また逆に文字コードを文字列にするようなことは 何かの時にあると便利です。よく使うテンプレートとして次の2つをあげておきます。 例2では、変数bに代入されている数値をpoke命令で文字コードとして書き込んでいます。 これは、変数aの1文字目に上書きされます。変数aは、最初に「_」という1文字だけが代入 されていますが、この「_」はなくなり、新しい文字コードに書き換わります。 文字列の最後にある終わりのコード(0)は、最初の「a="_"」ですでに設定されているので、 もう一度書き込む必要はありません。このような手順で、文字コードを1文字の文字列型変数 に変換することができます。
これらの例は、Microsoft系N-BASICを使った人であれば、ASC、CHR$といった関数と 同じような働きと言えば分かりやすいかもしれません。文字コードを扱うことは、 通常あまりないかもしれませんが、いざという時に小回りのきく手段として覚えておくといいでしょう。

もう1つ便利な例をご紹介しましょう。 peek関数を使えば、文字列の中から任意の場所にある1文字をコードとして取ってくることが できます。しかし、取り出すのはあくまでコード(数値)でありいちいちアスキーコード表と てらし合わせるのは面倒です。そんな時に使うと便利なのが「'(シングルクォーテーション)」 による表記です。これは、「'A'」のように1文字を「'」で囲んで使用します。こうすると、 「'A'」は「65」と同じ意味になります。つまり、「'」で囲んだ文字をコードに直してくれる のです。ですから、 とすれば、変数aの1文字目が「*」のコードかどうかを手軽に調べることができます。 「'」は、文字列を扱う「"(ダブルクォーテーション)」に似ていますが、実際は数値を示す ので間違えないようにしてください。

ここで説明した内容を最後に、おさらいしておくと、

ということになります。 この概念と、文字コードの存在を知っておくだけでもいいと思います。


テキストファイルのしくみ

メモ帳やテキストエディタなどで開いたり保存したりしているテキストファイルですが、 HSPでもこのテキストファイルを読み込んだり、作成することができるようになっています。 テキストファイルとは何なのでしょうか? 実は、テキストファイルとは文字列の 内容をそのままファイルにしただけのものなのです。 いままで説明してきたように、変数に記憶された文字列はメモリ上のバッファに、コードという 形で記憶されています。この内容をそのままbsave命令でファイルにセーブすると、 テキストファイルができあがります。逆に言えば、テキストファイルの内容は単なる文字列に ほかならないのです。
たとえば、

のようなスクリプトでも「TEXT FILE.」という内容のテキストファイルを作成できます。 しかし、これだけだとファイルサイズがバッファのサイズと同じになってしまってムダが できてしまいます。文字列として使用している分だけをファイルにセーブするようにすれば 完璧です。そこで、 上のように、strlen命令を使って文字列の長さを調べてから、そのサイズだけセーブするように すれば適切な長さのテキストファイルになります。
同じ要領で、テキストファイルの読み込みもbload命令で行なうことができます。 たとえば、 このようなスクリプトになります。最初のsdim命令は、読み込んでくるテキストファイルが 大きいかもしれないので約32000文字分のバッファを確保しています。これで、32000バイトの ファイルまでは読み込むことが可能です。bload命令でテキストファイルを読み込んで、 最後にmes命令でその内容を表示しています。 sdim命令で、文字列型の変数としてバッファを初期化しているので、mes命令ではバッファの 内容を文字列として単純に表示します。バッファに読み込まれたテキストファイルもまた、 文字列と同じなので、そのままちゃんと表示されるというわけです。

テキストファイルは、文字列型変数のバッファをファイルにしたものと言ってきましたが、 実は1つだけ異なる部分があります。それは、

という点です。これは、ファイルにセーブされたサイズが、すなわち文字列のサイズということ なので、終わりを示すコードが必要ないためです。 (ただし、一部のテキストエディタではわかりやすいようにテキストファイルにも終了コード を入れてくれるものがあります。しかし、これは[EOF]と呼ばれるコードで、0ではなく、 26になります)
HSPでテキストファイルを読み込んできた場合には、最後に0を入れる必要があるのに、そのまま 使えています。これには、深ーいわけがあります。 そのわけは、sdim命令のように、変数のバッファが確保された直後は、その内容がすべて 0でクリアされているのです。このバッファに、終了コードの入っていないテキストファイルを 読み込んでも、読み込まれたデータの続きには、必ず終了コードである0が入っているというわけです。 そんなに深いわけでもなかったですね。ですから、一度テキストファイルを読み込んできたバッファに、 もう一度別なテキストファイルを読み込んだ場合には、以前のテキストファイルよりも小さなサイズだった 場合には、以前のデータが残ってしまいます。 そうならないためには、テキストファイルを読み込む前に必ずsdim命令でバッファを 確保するようにして、0でクリアする必要があるのです。
実は、テキストファイルを読み込むには、もっと単純な方法があります。 「メモリノートパッド命令」という仕組みを使えば、サイズや終端のコードを気にせず、 テキストファイルを扱うことができます。この方法については、後で説明します。


複数行文字列のしくみ

HSPが扱う文字列は大きく分けて、1行だけの単純な文字列と、複数行を含む文字列とに分ける ことができます。この2つは、特に処理の上では違いがありません。単に、スクリプトを 作るユーザーが意識しておけばいいだけのものです。 複数行文字列とは、改行が含まれている文字列のことです。改行とは、それ以降を次の行に もっていくための、しるしみたいなものです。
ためしに、

というスクリプトを実行してみると、 という2行にまたがって、メッセージが表示されます。 これは、変数aの中に「\n」という改行のしるしが含まれているためです。
いままで、文字にはコードがあり数値の形で、バッファに記憶されているということを 言ってきましたが、改行はどうなっているのでしょうか。 上の例にあげたスクリプトの場合、変数aの内容はバッファの中では、

A B C \n   D E F 終了コード
65 66 67 13 10 68 69 70 0

というデータが入っています。「\n」が改行を示すものですが、その部分は「13」「10」 という2つのコードになっています。そして、これがまさに「改行コード」なのです。 改行コードは、キーボードから入力できないので、HSPではかわりに「\n」という文字で 表わすようにしていますが、実際は「13」と「10」の2つのコードが文字列の中に 置かれています。
困ったことに、改行コードにはいくつかの方言があります。MS-DOSやWindowsでは「13」「10」 というコードですが、MacintoshのMacOSでは「13」のみです。また、UNIXでは「10」に なっています。HSPでは、改行コードとして、MS-DOSやWindowsの「13」「10」の他にも、 MacOSの「13」だけの場合にも対応していますが、UNIXの改行コードには対応していません。 そもそも多くのUNIXでは日本語のコードも違っているので、DOS形式へのコンバーターなどで あらかじめテキスト全体を変換しておいた方がいいでしょう。


メモリノートパッド命令のしくみ

改行コードが入ることにより、文字列は非常に便利になりますが、複雑さは増します。 テキストファイルには、当然改行コードが含まれてたくさんの行があるでしょうし、 HSPのmesbox命令による入力ボックスも、改行を入れることができます。 また、dirlist命令や、listbox命令、combox命令で使用する文字列にも改行コードが入って います。改行コードを入れることにより、たくさんの情報を整理することができる反面、 文字列としての取り扱いはどんどんやりにくくなっていきます。
そこでHSPでは、メモリノートパッド命令という一連の命令セットにより改行コードを 含んだ文字列を比較的簡単に扱えるようにしています。複数の行があるテキストを 扱う場合に問題となるのは、1行ごとに何かをチェックしたい場合や、1行単位で文字列を 操作したくなった場合です。この1行単位での仕事では、2つのプロセスに分けると 作業がすっきりとします。つまり、

このように、複数行の文字列データから、1行だけの改行コードを含まない文字列を 取り出してから作業をして、結果をふたたび戻すという手順であれば、改行コードについての 面倒なチェックも必要なくなり効率もよくなります。 そのための命令セットがメモリノートパッド命令です。 これは、以下のような命令から成っています。

命令おもな機能備考
noteselメモリノートパッドとして扱う変数の指定 
notemax全体の行数を取得 
noteloadテキストファイル読み込み 
notesaveテキストファイル書き出し 
noteadd指定行に内容追加挿入/上書きモードあり
noteget指定行の内容読み出し 
notedel指定行の削除 

命令がいくつもありますが、使い方は難しくありません。 基本的には、最初に文字列操作の対象となる変数名をnotesel命令で指定します。 それから、もし1行読み出す場合にはnoteget命令を、行の内容を変更・追加する場合には、 noteadd命令を使います。たとえば、複数行ある文字列を含む変数aで、最初の行にある 文字列を取り出したい場合は、 のようになります。この時、注意しなければならないのは、noteget命令で指定する 行インデックスは0から始まるので、1行目の指定が0になるということです。 そして細かいことですが、noteget命令は読み出す先の変数を必ず文字列型にセットします。 上の例で言うと、変数bがそれまで数値型であってもnoteget命令を実行した後は、 文字列型になるということです。
notemaxとrepeat〜loop命令を使うことで、すべての行に対しての処理を効率的に 記述できます。たとえば、 このスクリプトでは、変数aのすべての行の内容をカッコでくくった状態で表示させます。 notemaxは、行がいくつあるかを調べて変数に返すシステム変数として使用することができます。 ここで調べた回数だけ、repeat〜loop命令でループさせます。このループ中は、 システム変数cntが0から順番に数字が上がっていくので、noteget命令で読み出す 行インデックスの指定に、システム変数cntを使えば各行を順番に取り出していけることになります。

メモリノートパッド命令により、テキストファイルを扱うことも可能です。 noteload命令は、テキストファイルを読み込む場合に、 notesave命令は、テキストファイルを保存する場合に使います。 たとえば、 このスクリプトでは、変数aに代入されている内容を「a.txt」という名前のテキストファイルとして 保存します。簡単ですね。
逆に、テキストファイルを変数に読み込む場合には、以下のように使います。 noteload命令は、指定されたファイルをnotesel命令で指定された変数に読み込みます。 文字列終端の終了コードや、変数のバッファ領域なども自動的に考慮されるようになっています。


文字列操作関数

HSPには、文字列の検索をするためのinstr関数と、文字列の一部を取り出すためのstrmid関数が 標準で用意されています。 このドキュメントを読むと、同様の処理をpeek命令やpoke命令でも実現できることが わかると思いますが、そこは当然最初から機能がサポートされていた方がいいに決まってますよね。 この2つの新しい命令は、単純ながら多くの場所で使える便利な命令です。 まず、strmid命令はだいたい以下のようになっています。

Microsoft系のN-BASICを使ったことのある人であれば、文字列の取り出しに LEFT$、RIGHT$、MID$という3つの関数があったのを覚えているかもしれません。 これらは、それぞれ文字列の「左からn文字」「右からn文字」「n1文字目からn2文字」 を取り出して、その内容を返すというものでしたが、strmid関数は、その3つの機能を 合わせ持った命令です。
p1に取り出す元の文字列が記憶されている変数名を指定して、 p2で取り出し始める文字インデックス(0から始まる)、p3で取り出す文字数を 指定するというものです。注意しなければいけないのは、p3の文字インデックスは、 1文字目が0になり、2文字目が1…という0からの順番になっているということ。 これで、たとえば変数aの一部を取り出す場合、

になります。
もう1つ追加されたのは、文字列の中に、指定した文字列が含まれているかどうかを 調べるinstr関数です。これはヘルプでは、 というようになっています。 p1で指定した文字列型変数の中に、"string"で指定した文字列があるかどうか調べて、 文字インデックスを返します。 結果は数値になるという点に注意してください。文字インデックスでは、 文字列の始まり1文字目を0として、1,2,3...と順番に増えていきます。 (strmid命令で指定するインデックスと同様)。 もし、見つからなかった場合には-1が代入されます。
また、p2で調べ始める文字インデックスを指定することができます。(指定を省略 した場合は、最初(0)からになります) この場合、検索の結果は調べ始めた場所からのインデックスが返されるということに 注意してください。文字列の最初からのインデックスではなくなります。


日本語文字列のしくみ

いままで説明したことは、とりあえず半角の文字列。アスキーコード表の文字に関しての話です。 HSPで用意された文字列操作に関する命令も、すべて文字の単位は半角文字になっています。 扱う文字列が、英文字だけであれば問題はありません。 しかし、これが日本語の全角文字を含んでいるとちょっとトリッキーになります。 前にも言ったように、日本語は1バイト(256種類)では表わしきれないので、 2バイト使って表現しています。 つまり、英文字2文字分で日本語1文字なのです。しかしこれらは、混在できるようになっている ために、ちょっと複雑なルールがあります。次のようなルールです。

つまり、コードの数値がある範囲にある時だけは、2文字分で1つとする特殊なルールを作ってあるわけです。 これは、WindowsやMS-DOSにおいて有効なルールで、他のOSやインターネット上から持ってきたテキストでは 通用しないことがあります。 このような日本語文字コードのことを普通、「シフトJISコード」と呼んでいます。日本語は、 2バイトで1文字ですから、日本語の文字コードを取り出す場合などはpeek命令を2回行なうか、 wpeek命令を使う必要があります。ただし、wpeek命令は、「$81」「$24」(それぞれ16進数) という2バイトのコードがあった場合、「$2481」(16進数)のように、2バイトを逆転して 1つの数値にしてしまうので注意が必要です。
そんなこんなで、日本語コードを扱うのは英文字よりも少しやっかいです。 ただし、英文字と日本語を混在させないなどの制限をつけてやれば、必ず2バイトで1文字という 法則になり、文字を取り出したり、長さをチェックしたりする時には容易になるでしょう。


最後に

このドキュメントでは、文字列のしくみを理解することで、より細か効率的に文字列を操作する 方法について説明してきました。 改行コードを含まない文字列に対してのアプローチと、それにメモリノートパッド命令を加える ことで、複数行に渡る文字列であっても効率的に処理することができるようになると思います。 このドキュメントで、その作業が少しでも楽になってくれれば幸いです。





[前のメニューに戻る]
Go to HSPTV page
ONION software