頑固者の文字列処理~文字列編~

 前回は可変長配列クラスを作り、忘れていたvirtualをこっそり付け足しました。

 次にするべきは勿論、文字列クラスの作成――ではあるのですが、ここで先に文字列の上限数を決めておきます。

適当に2^10にしておきます。

 そして、これを最大数としてクラスを作ります。

出来ました。

 ここで重要なのは、コンストラクタの処理内容。最初の部分に0を代入しています。

 C++における文字列処理では、終端文字を使います。名の通り、文字列の終わりを指す文字です。本来ならchar型での表記'\0'と書くべきではあるのですが、私は面倒なのでintでの内部値の0を代入しています。

 そして、最初に終端を置くということは、文字列は即座に終わる――つまりは空の文字列が出来ます。なので、これを以って初期化とします。

 

 また、上記の終端文字の仕様上、文字列は最後の1枠を考えて組む必要があります。つまりは、

・最後の1枠を考慮した空きの判定

・何らかの操作の度に末尾に0を置く処理

が必要になります。

 

 都合が良いので、挙げた順に作りましょう。前回作った中ではIsFullが該当します。

それをString側で書き換えます。メソッドを書いてoverrideを加えるだけのお手軽改変です。エラーが出た方は、前回作った部分にこっそりとvirtualを足しておきましょう。

 しかし注目すべきは返す内容。1を引き忘れてはいけません。引き忘れた場合、最後に0を置く枠が保証されなくなります。ここで1を引きたくない拘りを持つ方々は、テンプレート引数の方に1を足しておきましょう。概ね同じことができます。

 

 次に、各処理の書き換えです。Clear、Add、Insert、Removeが該当しますが、全て工程は同じです。各処理の後に、末尾に0を置く処理を追加します。

 では、分かりやすいClearから。

こうなります。元の処理を行い、末尾に0を置きます。

 継承の基本ではありますが、C++では(staticはともかくとして)クラス名にコロン2つを付けてメソッドを呼び出すと、基底クラスにある処理を呼び出すことができます。これを付け忘れると、ずっと継承先のClearを呼び続ける再帰の無限ループに陥ります。

 

 さて、次はAddなのですが――

――Clearとは違い、if文がありますね。追加の成功時にのみ末尾に0を置いています。まさかとは思いますが、if文アンチの方々がいらっしゃるなら、どうぞ分岐せずに0を置いてください。

 

 残るはInsert及びRemoveですが――

――コピペです。普通にコピペの方が早いです。if文アンチの方々でもコピペで大丈夫です。タイピングに慣れておきたいという方々は打ち間違いに注意しつつ手打ちしましょう。

 

 

 ヨシ、これで完成……ではありません。文字列なので、C++に於ける文字列型であるcharのポインタ型として取り出せてこそ仕事を全うできます。

 では、如何にして作るのか。簡単です。

配列の先頭をポインタとして渡します。以上です。

 しかし、記号や括弧を省いて単にreturn obj;と書きたい人も居るでしょう。何故そう書いてはいけないのか……別に書いてもいいです。constであることも含め、私が宗教上の理由でこう書いているだけです。

 

 

 

 

 これにて、文字列クラスは完成です。頑固な部分を解すも良し、文字列に文字列を足すも良し、ネタにして宗教戦争を仕掛けるも良し。

 紙でも指は切れますし、蒟蒻ゼリーでも人は死にます。しかし、流石にこの内容を使って人を傷付けることはできないと思うので、完全にご自由にお使いください。

 

 

LucKee

頑固者の文字列処理~配列編~

 最近、専らC++でコードを書いている。C++で書いていると困るのが、文字列処理。基本的に固定長の文字列なので、std::stringに頼る羽目になる――

「string」?

――嫌です。文字列といえばString、大文字以外認めません。

 ということで、String型を作ります。用意するものは

・継承するための可変長配列クラス

・継承後の処理

です。

 

 とりあえず、前者から作っていきましょう。

 可変長ということで、『現在の配列の長さ』が必要になります。また、配列の本体も当然必要になりますが、これは思想の自由を振りかざして静的に確保します。そして、配列の型を固定してしまっては扱いづらくなります。

 

 つまり、

・配列本体

・現在の長さ

を持ちながら、

・配列の型

・長さの上限

を要求する配列クラスが欲しいわけです。

 

 

 そこで生まれるのが、こんなクラスです。テンプレートでクラスと最大数を受け取り、メンバ変数に本体と現在の長さを持っています。また、後々の継承のためにprotectedにしてあります。

 

 では、次に機能面を作ります。とりあえず、

・初期化

・現在の長さの取得

・参照及び変更

・追加及び挿入

・任意箇所の削除

を実装します。

 

 まず、初期化ですが――

――以上です。配列の仕様上、『現在の長さ』以降の要素は無いものとして扱います。つまり、無いものとして扱う範囲を全体にすることで、全てを無効化できるわけです。

 virtualを付けている理由は、継承を見越しているからです。

 

 次、長さの取得ですが――

――以上です。取得するだけですし。

 なお、これを弄ることは無いはずなので、virtualは使いません。

 

 続いて、参照関連です。

 Getです。良い名前は諦めました。引数として受け取った番号をもとに、参照型で返します。これにより、外側から値を変更することができます。

 もう1つ、配列括弧による参照も作りました。普通の配列のようにアクセスできるので便利です。

 これらを両立するメリットですが、この配列クラスをどのような変数で扱うかで使い分けられるのです。

通常の変数及び参照型では配列括弧、ポインタからであればGetを使うことで、ある程度見やすくなります。

(この例、メソッド呼び出しに対して代入するのは違和感があって嫌だな……)

 

 さて、次からの項目は若干長くなります。

 次は追加なのですが、ここで1つ思い出したことがあります。

「上限に到達した時、追加しようとするとfalseを返すのはいいけど、先に知りたいよな……」

そう、空きがあるかを調べたいのです。

特筆すべきほどではありませんが、追加時には考える必要がある機能です。配列の外側に追加するのはあまりに危険ですし。

 では、これを用いて追加機能を作りましょう。

とりあえず外側から作ります。値を受け取って、空きがなかった場合はfalse、あった場合は処理をした後trueを返します。処理内容はと言うと、

・現在の長さの増加

・末尾への代入

が必要になります。

 では、どう処理を書くかですが、こんな例を用意してみましょう。

この状態で'g'を追加する場合、代入位置は6番であるべきです。そして、ちょうど良くcountが6なのです。

つまり、代入後にcountを1増やす処理を行えば、それだけでいいのです。

 

 これで単純な末尾への追加は完成です。

 次は挿入にしましょう。挿入となると、引数に追加で挿入箇所が必要になります。

 まずはfalseとtrueに分けるのですが――

――条件が1つ多いですね。

 この条件は、挿入箇所に対するチェックです。配列にはマイナスの添え字はありませんし、現在の長さを超えて指定するとややこしくなります。

 とはいえ、なら長さと同じ値ならいいのか、と。私は許します。後々書く処理では、そんな値を指定した時にAddと同じことが起きるようにします。

「Insertなんだから挿入だけにしろ!」

「それを許したらAddが要らないじゃないか!」

という過激派の方々は同値も門前払いしましょう。

 さて、肝心の処理内容ですが……流石にAddよりは長いです。

再びのこの画像ですが、例えば'g'を3番に挿入したい場合、3番以降の内容を全て1つ後ろにずらし、最後に3番に代入することになります。

 つまり、処理としては

・ずらすループ

・現在の長さの増加

・指定位置への代入

となり、1つ増えました。

 次に、処理の順番を考えましょう。当然ながら、ずらす処理は代入処理より先に来ます。そのずらす処理が、頭で考えているだけでは絡まってしまうのです。

 ずらす処理とは、大体がゴール側から始まるものです。今回の場合、ずらす方向の後ろ側から始まります。

 また、指定位置に来た時に終わらなければなりません。つまり、

・ゴールであるcountから始まる

・隣り合う位置で前から後ろに代入する

・indexに辿り着いたら終わる

というループを回すことになります。

 これで、ループ開始時はcount番目にcount-1番目を入れ、最後の1周でindex+1番目にindex番目の要素を入れることができます。

 また、私もよくやらかすミスとして、iのインクリメント(i++)を書いてしまうことがあります。しかし、これは後ろから前へ行くループなのでiのデクリメント(i--)です。今一度、見直しましょう。

 ここまでくれば、あとは簡単です。

Addで書いた通り、代入するべき箇所に入れ、countを増やします。違いとしては、代入先の添え字がcountではなくindexであるというだけです。

 これで、挿入も完成です。

 

 次は削除なのですが、追加・挿入とは違い、メソッドは1つで済みます。

 まずはfalseとtrueに分けるのですが――

――色々と違います。

 先程までとは違い、空いているかどうかは関係ありません。

 そして範囲を制限する時、長さと同値の場合を許しません。そこに値はありませんし。

 さて、どう処理を組むかですが――

――3度目ですが、この画像です。

 例えば2番を消したいとします。この場合、3番以降が1つずつ前に寄ればそれだけで8割ほど解決します。

はい、寄せました。これで残るは長さのみですが、それもcountを1減らすだけで終わります。countを減らせば、最後の'f'は無かったことになりますから。

 それをfor文にして書いたものがこちらです。

 最後に、countを減らす処理をこの前後どちらかに入れるのですが……ループの内容を見て判断しましょう。

 この条件式だと、最後の1周でcount-1番目にcount番目を代入しています。しかし、元々count番目には意味の無いものが入っています。わざわざ動かす必要はありません。

 であれば、先にcountを減らすことで、最後の無駄な1回を削ることができます。先に書きましょう。

 これで、削除も完成です。

 

 お疲れ様です。これで、基礎の配列クラスは完成です。

 文字列クラスはここからもう一手間加えるのですが、それは次のお話。

 未だにブログというものの加減が分からないので、これが長いのか短いのかの判断は付きません。ただ、画像を使ったうえでのこの文字数か、と下書きを書きながら思っているので、一旦はここで止めます。

 皆様、良きプログラミングライフを!

 

 そして私は各メソッドに付け忘れたvirtualを書いてきます。

Experimental

私も就活シーズンなもので、記録としてブログを付けることとなりました。

大まかな仕様を知るため、とりあえずは様子見で……

というのも、TwitterやらFacebookやらをしていると、ブログなんぞ書くことが無いのです。

ところが、ここでは文字数に限りが無いと。アマグラマとしては喜ばしいことです。足りない言葉を足していては、早々に字数を食い尽くしてしまうのです。

 

いやはや、愚痴を吐いても何も得られませんな。とりあえずは、追記時の日付などがどうなるかを検証すべく、明日にでも修正してみますかな。

 

//2024/02/23追記

さて、追記です。

検証用なので特に書くことはありません。

唯々仕様を知るためだけです。