文字列を繰り返して出力したいときってありますよね?
例えば・・・
for ($i = 0; $i < 10; $i++) {
echo "w";
}
// --> "wwwwwwwwww"
でも、str_repeat
関数(PHP マニュアル)を使えば、もっとシンプルに書けますよ。
echo str_repeat("w", 10);
// --> "wwwwwwwwww"
文字ではなく文字列
なので、正月早々、友人1に爆弾投下したくなったときにも使えます。
echo str_repeat("あけおめ〜", 10);
// --> "あけおめ〜あけおめ〜あけおめ〜あけおめ〜あけおめ〜あけおめ〜あけおめ〜あけおめ〜あけおめ〜あけおめ〜"
マルチバイト文字いけますよ
関数名が「mb_*」でないからマルチバイト文字いけないとか、そういうわけではありません。
暇だったのでソース読んでみた
これだけだったらおもしろくないと思うので、PHP のソースコードを眺めてみました。初めて眺めたので詳しい仕組みは理解できていないし、最近触れてないのでC言語の理解度も怪しいけど、だいたいこんな感じかなと。
リポジトリを検索すると、php-src/ext/standard/string.c
に文字列関数が定義されているようです。その 5387 行目 を見ると、str_repeat
関数が定義されていました。
てっきり内部の実装も for
文でループしているのかと思いきや(そんな素人的な発想で作るわけないよね)、memset
や memcpy
、memmove
関数を使っています。
例えば、文字列長が 1 バイトならば、memset
関数で、チョチョイと入れちゃう。
/* Heavy optimization for situations where input string is 1 byte long */
if (ZSTR_LEN(input_str) == 1) {
memset(ZSTR_VAL(result), *ZSTR_VAL(input_str), mult);
}
文字列長が 1 バイトよりも長かった場合は、ちょっと複雑なことをしています。
const char *s, *ee;
char *e;
ptrdiff_t l=0;
memcpy(ZSTR_VAL(result), ZSTR_VAL(input_str), ZSTR_LEN(input_str));
s = ZSTR_VAL(result);
e = ZSTR_VAL(result) + ZSTR_LEN(input_str);
ee = ZSTR_VAL(result) + result_len;
while (e<ee) {
l = (e-s) < (ee-e) ? (e-s) : (ee-e);
memmove(e, s, l);
e += l;
}
初見では何をやっているのかよく分からなかったのですが(とくに while
文)、たぶんこういうことだろうと。なお、入力された文字列を "abc"
、回数を 3
とします。
memcpy(ZSTR_VAL(result), ZSTR_VAL(input_str), ZSTR_LEN(input_str));
まず、memcpy
関数で、入力文字列 input_str
を result
にコピーします。
このときのメモリー上の様子は以下のとおり。括弧内は、繰り返しが完了したあとのイメージです。
アドレス | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
result | a | b | c | ( a ) | ( b ) | ( c ) | ( a ) | ( b ) | ( c ) | |
↑変数s
|
↑変数e
|
↑変数ee
|
ポインタ変数 s
は、結果文字列の先頭を指していますが、memcpy
関数で先頭に入力文字列をコピーしたので、実質的には入力文字列の先頭を指し示しています。
ポインタ変数 e
は、ループの中で文字列を追加する位置を指し示すのに使われています。最初は入力文字列の長さだけ移動した位置を指し示していますが、ループの中で移動していることに注意です。
ポインタ変数 ee
は、文字列を回数分繰り返したあとのポインタの位置を指し示しています(こういうのを番兵と言うんですかね?)。
while (e<ee) {
l = (e-s) < (ee-e) ? (e-s) : (ee-e);
memmove(e, s, l);
e += l;
}
ここが実際に文字列を追加していっているところです。変数 l
の求め方が「なんでこんな面倒なことしてるの?」と思いましたが、要するに、これって、入力文字列の長さを求めているだけなんですよね・・・
1 回目の繰り返しでは、l = (3-0) < (9-3) ? (3-0) : (9-3)
なので、l = 3
となります。memmove
関数で先頭から 3 バイトを、変数 e
が指し示しているアドレスにコピーし、変数 e
が指し示す位置を移動します。
アドレス | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
result | a | b | c | a | b | c | ( a ) | ( b ) | ( c ) | |
↑変数s
|
↑変数e
|
↑変数ee
|
2 回目の繰り返しでは、l = (6-0) < (9-6) ? (6-0) : (9-6)
なので、l = 3
となります。memmove
関数で先頭から 3 バイトを、変数 e
が指し示しているアドレスにコピーし、変数 e
が指し示す位置を移動します。
アドレス | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|---|
result | a | b | c | a | b | c | ( a ) | ( b ) | ( c ) | |
↑変数s
|
↑変数ee, e
|
変数 e
の指し示す位置が変数 ee
と同じになり、ループ条件 e<ee
を満たさないため、ループ終了となります。
そして、文字列の最後に終端文字 \0
を追加して、呼び出し元に返却します。
まとめ
PHP マニュアルで時々どんな関数があるかを眺めてみることも大事ですね。まる。
-
友人がいれば、の話しだが! ↩