最近、専ら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を書いてきます。