HSPでは、数値型、文字列型などの型を変数の内容として保持することができます。 このドキュメントでは、その中でも文字列型がどのようにHSPで処理されているかを説明しながら、 それぞれの命令の詳細と、その応用についての説明をしています。 文字列の取り扱いを理解することで、より細かく文字列の操作を行なうことができるはずです。 また、メモリ管理やファイルの扱いに関係する部分もあるので、知っておくときっと役に立つ時が 来るでしょう。
まず、HSPでの文字列の取り扱いの基本をおさらいしておきましょう。 文字列とは、"(ダブルクォーテーション)で囲まれた、文字の集合体です。 HSPでは、メッセージの表示や、ファイル名などあらゆる場面で文字列を使用します。 たとえば、
mes "TEST MESSAGE"という命令では、「"」で囲まれた「TEST MESSAGE」 という文字列を画面に表示します。また、変数に文字列を記憶させておくこともできます。 たとえば、「a="TEST MESSAGE"」のように書けば、変数aに「TEST MESSAGE」という文字列が 記憶されます。そうすれば、「mes a」のように文字列を指定するかわりに変数名を指定 して、変数に記憶されている文字列をパラメータとして使うことができるようになります。
a="TEST"+" MESSAGE"上の例では、「TEST」という文字列と、「 MESSAGE」という文字列を「+」で足し算しています。 数値の計算ならば、加算されるところですが、文字列を足し算した場合は、文字列が連結されます。 つまり、2つの文字列をつなげて「TEST MESSAGE」という文字列になるのです。 文字列で使える式は、足し算のみですが「a="ABC"+"DEF"+"GHI"」のようにいくつも繋げることが できますし、「a="ABC"+b+"DEF"+c」のように間に変数をはさむことも可能です。 ですから、
a="TEST" b=" AND " c="MESSAGE" print a+b+cのようにすると、3つの変数の内容が連結されて「TEST AND MESSAGE」という文字列が画面に 表示されます。もしこれが、文字列型の変数でなく、数値が記憶されている数値型変数を途中で 足し算した場合には、どうなるのでしょうか? HSPでは、次のようなお約束があります。
a="TEST " b=12345 c=" MESSAGE" print a+b+cのように数値型の変数が間にあっても、「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="*" b=peek(a,0) mes "b="+bというスクリプトを実行してみてください。変数aに代入された「*」という文字列が記憶されている バッファから、1バイトを取り出して表示していますが、「*」の文字コードである42という数値 が取り出されています。このように、poke命令、peek関数を使うことで、記憶されている文字列に 対してより細かなアクセスができることになります。
文字列が記憶されているバッファには一定のルールがあります。 まずは、この重要なルールをまず最初に覚えておいてください。
1. 1バイトにつき1文字のコードが連続して置かれている。
2. 文字列の一番最後は、終わりを示すコードとして0が置かれている。
a="ABCDE"これが実行されると、変数aの内容が記憶されているバッファは次のようになります。
A | B | C | D | E | 終了コード |
65 | 66 | 67 | 68 | 69 | 0 |
peek(p1,p2) バッファから1byte読み出し p1=変数 : バッファを割り当てた変数名 p2=0〜 : バッファのインデックス(Byte単位)ということなので、
a="ABCDE" b=peek(a,2)とすれば、変数aの3文字目にある文字のコードを読み出すことができます。 なぜ3文字目になるかと言うと、peek命令の2番目のパラメータは、0から始まって1バイト ごとの指定なので、2を指定した場合は0・1・2の順で3文字目にあたるコードを取り出して いるためなのです。
poke p1,p2,p3 バッファに1byte書き込み p1=変数 : バッファを割り当てた変数名 p2=0〜 : バッファのインデックス(Byte単位) p3=0〜255 : バッファに書き込む値 または 文字列(Byte単位)ということなので、たとえば、
a="" poke a,0,65 poke a,1,66 poke a,2,67 poke a,3,0 mes aとすれば、変数aには何も文字列を代入していないにもかかわらず、バッファに直接データを 書き込んだために、「ABC」という文字列が記憶されています。 忘れてはいけないのは、最後に終わりを示すコードとして0を入れておくことです。 HSPは、0が出てくるまでバッファの内容を文字列として解釈します。もし、0を忘れてしまうと 余計なデータを文字列に加えてしまったり、最悪の場合、一般保護エラーが出るまでメモリを 読み続けてしまいます。
a="ABCDEF" poke a,3,0 mes aこのスクリプトでは、変数aに「ABCDEF」という文字列を代入していますが、その後でpoke命令 により、途中に終了コードを書き込まれています。その結果、「ABC」の後に終了コードがある ためにそこで文字列は終了と判断され、「ABC」という文字列だけしか表示されません。
例1: (文字コードを取り出す) b=peek(a,0) ; 変数aの最初の文字コードを変数bに代入 例2: (文字コードを文字列にする) a="_" b=65 poke a,0,b ; 文字列型変数aに、変数bの文字コードを入れる例2では、変数bに代入されている数値をpoke命令で文字コードとして書き込んでいます。 これは、変数aの1文字目に上書きされます。変数aは、最初に「_」という1文字だけが代入 されていますが、この「_」はなくなり、新しい文字コードに書き換わります。 文字列の最後にある終わりのコード(0)は、最初の「a="_"」ですでに設定されているので、 もう一度書き込む必要はありません。このような手順で、文字コードを1文字の文字列型変数 に変換することができます。
a="***TEST" b=peek(a,0) if b='*' : mes "*がありました"とすれば、変数aの1文字目が「*」のコードかどうかを手軽に調べることができます。 「'」は、文字列を扱う「"(ダブルクォーテーション)」に似ていますが、実際は数値を示す ので間違えないようにしてください。
1.peek関数で文字列の内容をコードとして取り出すことができる。
2.poke命令で文字列の一部を指定したコードに書き替えることができる。
メモ帳やテキストエディタなどで開いたり保存したりしているテキストファイルですが、
HSPでもこのテキストファイルを読み込んだり、作成することができるようになっています。
テキストファイルとは何なのでしょうか? 実は、テキストファイルとは文字列の
内容をそのままファイルにしただけのものなのです。
いままで説明してきたように、変数に記憶された文字列はメモリ上のバッファに、コードという
形で記憶されています。この内容をそのままbsave命令でファイルにセーブすると、
テキストファイルができあがります。逆に言えば、テキストファイルの内容は単なる文字列に
ほかならないのです。
たとえば、
a="TEXT FILE." bsave "test.txt",aのようなスクリプトでも「TEXT FILE.」という内容のテキストファイルを作成できます。 しかし、これだけだとファイルサイズがバッファのサイズと同じになってしまってムダが できてしまいます。文字列として使用している分だけをファイルにセーブするようにすれば 完璧です。そこで、
a="TEXT FILE." size=strlen(a) bsave "test.txt",a,size上のように、strlen命令を使って文字列の長さを調べてから、そのサイズだけセーブするように すれば適切な長さのテキストファイルになります。
sdim a,32000 bload "test.txt",a mes aこのようなスクリプトになります。最初のsdim命令は、読み込んでくるテキストファイルが 大きいかもしれないので約32000文字分のバッファを確保しています。これで、32000バイトの ファイルまでは読み込むことが可能です。bload命令でテキストファイルを読み込んで、 最後にmes命令でその内容を表示しています。 sdim命令で、文字列型の変数としてバッファを初期化しているので、mes命令ではバッファの 内容を文字列として単純に表示します。バッファに読み込まれたテキストファイルもまた、 文字列と同じなので、そのままちゃんと表示されるというわけです。
HSPが扱う文字列は大きく分けて、1行だけの単純な文字列と、複数行を含む文字列とに分ける
ことができます。この2つは、特に処理の上では違いがありません。単に、スクリプトを
作るユーザーが意識しておけばいいだけのものです。
複数行文字列とは、改行が含まれている文字列のことです。改行とは、それ以降を次の行に
もっていくための、しるしみたいなものです。
ためしに、
a="ABC\nDEF" mes aというスクリプトを実行してみると、
ABC DEFという2行にまたがって、メッセージが表示されます。 これは、変数aの中に「\n」という改行のしるしが含まれているためです。
A | B | C | \n | D | E | F | 終了コード | |
65 | 66 | 67 | 13 | 10 | 68 | 69 | 70 | 0 |
改行コードが入ることにより、文字列は非常に便利になりますが、複雑さは増します。
テキストファイルには、当然改行コードが含まれてたくさんの行があるでしょうし、
HSPのmesbox命令による入力ボックスも、改行を入れることができます。
また、dirlist命令や、listbox命令、combox命令で使用する文字列にも改行コードが入って
います。改行コードを入れることにより、たくさんの情報を整理することができる反面、
文字列としての取り扱いはどんどんやりにくくなっていきます。
そこでHSPでは、メモリノートパッド命令という一連の命令セットにより改行コードを
含んだ文字列を比較的簡単に扱えるようにしています。複数の行があるテキストを
扱う場合に問題となるのは、1行ごとに何かをチェックしたい場合や、1行単位で文字列を
操作したくなった場合です。この1行単位での仕事では、2つのプロセスに分けると
作業がすっきりとします。つまり、
1.複数行から任意の行の内容だけを取り出す
2.取り出した文字列に対してチェックをしたり、加工をする
3.必要ならば、それをもとの行に戻す
命令 | おもな機能 | 備考 |
notesel | メモリノートパッドとして扱う変数の指定 | |
notemax | 全体の行数を取得 | |
noteload | テキストファイル読み込み | |
notesave | テキストファイル書き出し | |
noteadd | 指定行に内容追加 | 挿入/上書きモードあり |
noteget | 指定行の内容読み出し | |
notedel | 指定行の削除 |
a="ABC\nDEF" notesel a noteget b,0 mes bのようになります。この時、注意しなければならないのは、noteget命令で指定する 行インデックスは0から始まるので、1行目の指定が0になるということです。 そして細かいことですが、noteget命令は読み出す先の変数を必ず文字列型にセットします。 上の例で言うと、変数bがそれまで数値型であってもnoteget命令を実行した後は、 文字列型になるということです。
a="ABC\nDEF\GHI" notesel a repeat notemax noteget b,cnt mes "["+b+"]" loopこのスクリプトでは、変数aのすべての行の内容をカッコでくくった状態で表示させます。 notemaxは、行がいくつあるかを調べて変数に返すシステム変数として使用することができます。 ここで調べた回数だけ、repeat〜loop命令でループさせます。このループ中は、 システム変数cntが0から順番に数字が上がっていくので、noteget命令で読み出す 行インデックスの指定に、システム変数cntを使えば各行を順番に取り出していけることになります。
a="ABC\nDEF\GHI" notesel a notesave "a.txt"このスクリプトでは、変数aに代入されている内容を「a.txt」という名前のテキストファイルとして 保存します。簡単ですね。
notesel a noteload "a.txt" mes anoteload命令は、指定されたファイルをnotesel命令で指定された変数に読み込みます。 文字列終端の終了コードや、変数のバッファ領域なども自動的に考慮されるようになっています。
HSPには、文字列の検索をするためのinstr関数と、文字列の一部を取り出すためのstrmid関数が 標準で用意されています。 このドキュメントを読むと、同様の処理をpeek命令やpoke命令でも実現できることが わかると思いますが、そこは当然最初から機能がサポートされていた方がいいに決まってますよね。 この2つの新しい命令は、単純ながら多くの場所で使える便利な命令です。 まず、strmid命令はだいたい以下のようになっています。
strmid(p1,p2,p3) 文字列の一部を取り出す p1=変数名 : 取り出すもとの文字列が格納されている変数名 p2=0〜(0) : 取り出し始めのインデックス p3=0〜(0) : 取り出す文字数Microsoft系のN-BASICを使ったことのある人であれば、文字列の取り出しに LEFT$、RIGHT$、MID$という3つの関数があったのを覚えているかもしれません。 これらは、それぞれ文字列の「左からn文字」「右からn文字」「n1文字目からn2文字」 を取り出して、その内容を返すというものでしたが、strmid関数は、その3つの機能を 合わせ持った命令です。
「左からn文字」を取り出す場合は、「strmid( a,0,n )」となり、
「右からn文字」の場合は、「strmid( a,-1,n )」となり、
「n1文字目からn2文字」は、「strmid( a,n1-1,n2 )」
instr(p1,p2,"string") 文字列の検索をする p1=変数名 : 検索される文字列が格納されている文字列型変数名 p2=0〜(0) : 検索を始めるインデックス "string" : 検索する文字列というようになっています。 p1で指定した文字列型変数の中に、"string"で指定した文字列があるかどうか調べて、 文字インデックスを返します。 結果は数値になるという点に注意してください。文字インデックスでは、 文字列の始まり1文字目を0として、1,2,3...と順番に増えていきます。 (strmid命令で指定するインデックスと同様)。 もし、見つからなかった場合には-1が代入されます。
いままで説明したことは、とりあえず半角の文字列。アスキーコード表の文字に関しての話です。 HSPで用意された文字列操作に関する命令も、すべて文字の単位は半角文字になっています。 扱う文字列が、英文字だけであれば問題はありません。 しかし、これが日本語の全角文字を含んでいるとちょっとトリッキーになります。 前にも言ったように、日本語は1バイト(256種類)では表わしきれないので、 2バイト使って表現しています。 つまり、英文字2文字分で日本語1文字なのです。しかしこれらは、混在できるようになっている ために、ちょっと複雑なルールがあります。次のようなルールです。
コードが129〜159か、224〜252の範囲にある場合は、次の1バイトと合わせて1文字の全角コードとなる。
このドキュメントでは、文字列のしくみを理解することで、より細か効率的に文字列を操作する 方法について説明してきました。 改行コードを含まない文字列に対してのアプローチと、それにメモリノートパッド命令を加える ことで、複数行に渡る文字列であっても効率的に処理することができるようになると思います。 このドキュメントで、その作業が少しでも楽になってくれれば幸いです。