HSPでは、数値型、文字列型の2つの種類を変数の内容として保持することができます。 このドキュメントでは、その中でも文字列型がどのようにHSPで処理されているかを説明しながら、 それぞれの命令の詳細と、その応用についての説明をしています。 文字列の取り扱いを理解することで、より細かく文字列の操作を行なうことができるはずです。 また、メモリ管理やファイルの扱いに関係する部分もあるので、知っておくときっと役に立つ時が 来るでしょう。
まず、HSPでの文字列の取り扱いの基本をおさらいしておきましょう。 文字列とは、"(ダブルクォーテーション)で囲まれた、文字の集合体です。 HSPでは、メッセージの表示や、ファイル名などあらゆる場面で文字列を使用します。 たとえば、
print "TEST MESSAGE"という命令では、「"」で囲まれた「TEST MESSAGE」 という文字列を画面に表示します。また、変数に文字列を記憶させておくこともできます。 たとえば、「a="TEST MESSAGE"」のように書けば、変数aに「TEST MESSAGE」という文字列が 記憶されます。そうすれば、「print 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」という表示になり、これは あくまで文字列として扱われます。
文字列には限界があります。どのような限界かと言うと、文字の長さ、つまり文字数に制限がある ということです。まず気をつけておかなければいけないのは、 変数に文字列を記憶される場合は、通常63文字までが限界ということです。 どうしてそんな意地悪をするのか疑問に感じるかもしれませんが、快適な速度でHSPのスクリプトが 実行できるための策だと思ってください。もちろん、文字数の制限なく文字列を記憶できると いいのですが、コンピュータのメモリは無限にはないので、すべての変数にたくさんの文字列を 記憶させておくためのメモリを与えてしまうと、あっという間にメモリが使われてしまうばかりか、 ムダが多くなってしまいます。ま、ここは苦しい土地行政みたいなものと思って63文字というのを覚えておいてください。 通常は63文字でこと足りることが多いと思いますが、中には長い文字列を使いたいこともあるでしょう。 そんな時は、sdim命令を使います。これは、たとえば、
sdim a,256のように使うと、変数aが256文字(正確には255文字)まで使えるようになります。 その気になれば、「sdim a,5000」(5千文字)とか、「sdim a,20000」(2万文字)とかいくらでも 大きな文字数を取ることができます。このように、あらかじめ長くなりそうな文字列型変数は、 sdim命令でサイズを宣言しておくと、ちゃんと使えるようになります。 なぜ使えるようになる文字数が、設定したサイズより1文字少ないのかは、ここでは謎ということに しておいてください。いずれその謎が解けます。
a+="TEST"のような直接文字列を足し算する代入です。これは、「a=a+"TEST"」と同じことですが、 HSPの内部では「a+="TEST"」の方が処理が軽く、しかも32000文字という制限がありません。 もちろん、「"TEST"」の部分が32000文字を越えてはいけないのですが、文字列を連結した 結果が、32000文字を越えても正常に動作します。
HSPでは文字列をどのように管理しているのでしょうか?
前にも言ったように、文字列は長さ不定の文字の集合体です。文字列型変数では、
決まった文字数(通常は63文字)のぶんだけ記憶できるように、メモリに場所を取ってあります。
ここで、「文字列」と「メモリ」、「バッファ」の関係を知っておくと、文字列の扱いが
よりスッキリとします。
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 | { | | | } | ~ |
文字は、0〜255までの数値で表わされるコードにすることができることがわかりました。
この、256種類という数字はコンピュータの世界ではよく出てくる1バイト(byte)という単位に
なります。バイトとは、数値をコンピュータのメモリに記憶させる際の基本となる単位で、
1バイトには、0〜255までの数値を記憶させておくことができます。1メガバイトのメモリは、
バイトに直すと、ざっと1,000,000バイトですからごくごく小さな単位です。
こんなハード寄りの話を出したのは、難しくしたいためじゃありません。
この1バイト、0〜255までの数値というところでHSPの、poke命令およびpeek命令のことを
思い出してもらいたかったからです。
poke命令、peek命令はメモリバッファの読み書きをするための命令で、ちょっと文字列とは
無縁な感じがします。しかしながら、
変数の内容が記憶されている場所もまた、メモリバッファの1つ
なのです。これは知っておくと役に立つ重要な概念です。文字列型変数に記憶されている文字列
の内容や、数値型の変数、そして配列までもがpoke命令、peek命令で読み書き可能である
ということです。同様に、メモリバッファを対象にした命令(bload命令やbsave命令)もすべて
通常の変数に対して使用できます。
ここでためしに、
a="*" peek b,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,p3 バッファから1byte読み出し p1=変数 : 内容を読み出す先の変数名 p2=変数 : バッファを割り当てた変数名 p3=0〜 : バッファのインデックス(Byte単位)ということなので、
a="ABCDE" peek b,a,2とすれば、変数aの3文字目にある文字のコードを読み出すことができます。 なぜ3文字目になるかと言うと、peek命令の3番目のパラメータは、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: (文字コードを取り出す) peek b,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" peek b,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." strlen size,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 | 全体の行数を取得 | |
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 notemax m repeat m noteget b,cnt mes "["+b+"]" loopこのスクリプトでは、変数aのすべての行の内容をカッコでくくった状態で表示させます。 notemax命令は、行がいくつあるかを調べて変数に返す命令です。ここで調べた回数だけ、 repeat〜loop命令でループさせます。このループ中は、システム変数cntが0から順番に数字が 上がっていくので、noteget命令で読み出す行インデックスの指定に、システム変数cntを 使えば各行を順番に取り出していけることになります。
HSP ver2.4dからは、新しく文字列の検索をするためのinstr命令と、文字列の一部を取り出す ためのstrmid命令が追加されました。このドキュメントを読むと、同様の処理を peek命令やpoke命令でも実現できることがわかると思いますが、そこは当然最初から機能が サポートされていた方がいいに決まってますよね。 この2つの新しい命令は、単純ながら多くの場所で使える便利な命令です。 まず、strmid命令はだいたい以下のようになっています。
strmid p1,p2,p3,p4 文字列の一部を取り出す p1=変数名 : 取り出した文字列を格納する変数名 p2=変数名 : 取り出すもとの文字列が格納されている変数名 p3=0〜(0) : 取り出し始めのインデックス p4=0〜(0) : 取り出す文字数MSXやPC98などのMicrosoft系のBASICを使ったことのある人であれば、文字列の取り出しに LEFT$、RIGHT$、MID$という3つの関数があったのを覚えているかもしれません。 これらは、それぞれ文字列の「左からn文字」「右からn文字」「n1文字目からn2文字」 を取り出して、その内容を返すというものでしたが、strmid命令は、その3つの機能を 合わせ持った命令です。
「左からn文字」を取り出す場合は、「strmid a,b,0,n」となり、
「右からn文字」の場合は、「strmid a,b,-1,n」となり、
「n1文字目からn2文字」は、「strmid a,b,n1-1,n2」
instr p1,p2,"string",p3 文字列の検索をする p1=変数名 : 検索の結果が代入される変数名 p2=変数名 : 検索される文字列が格納されている文字列型変数名 "string" : 検索する文字列 p3=0〜(0) : 検索を始めるインデックスというようになっています。 p2で指定した文字列型変数の中に、"string"で指定した文字列があるかどうか調べて、 p1で指定した変数に文字インデックスを代入します。 結果は数値になるという点に注意してください。文字インデックスでは、 文字列の始まり1文字目を0として、1,2,3...と順番に増えていきます。 (strmid命令で指定するインデックスと同様)。 もし、見つからなかった場合には-1が代入されます。
いままで説明したことは、とりあえず半角の文字列。アスキーコード表の文字に関しての話です。 HSPで用意された文字列操作に関する命令も、すべて文字の単位は半角文字になっています。 扱う文字列が、英文字だけであれば問題はありません。 しかし、これが日本語の全角文字を含んでいるとちょっとトリッキーになります。 前にも言ったように、日本語は1バイト(256種類)では表わしきれないので、 2バイト使って表現しています。 つまり、英文字2文字分で日本語1文字なのです。しかしこれらは、混在できるようになっている ために、ちょっと複雑なルールがあります。次のようなルールです。
コードが129〜159か、224〜252の範囲にある場合は、次の1バイトと合わせて1文字の全角コードとなる。
このドキュメントでは、文字列のしくみを理解することで、より細か効率的に文字列を操作する 方法について説明してきました。 改行コードを含まない文字列に対してのアプローチと、それにメモリノートパッド命令を加える ことで、複数行に渡る文字列であっても効率的に処理することができるようになると思います。 このドキュメントで、その作業が少しでも楽になってくれれば幸いです。