参照型の中でも特殊な動作をするstring型ですが、利用頻度はとても多いです。
例えば画面上に文字を表示する、動作記録をファイルに残すとか、これらは全て文字列を経由します。
ここでは、そんな文字列についての知識を深め、より色々な場面で活用できるようになりましょう。
参照型と値型のどっち?
結論としてstring型は参照型です。ただし、以前にも伝えた通り動作的には値型です。
これはいくら悩んでも意味がなく、C#の言語仕様で決まってるからです。
関数(※1)を学んだことで、ついに値型と参照型の違いを知ることができます。ここではC#で最重要とも言える値型と参照型の違いを理解しましょう。 ※1現在の知識を踏まえて、ここではメソッドではなく関数と呼びます。 値型と参照型の復習 まずは値型と参照型について復習しましょう。この2つは値を保存する場所が大きなポイントになります。 値型 値型とは用意した領域に直接的に値を保存します。 参照型 参照型は値を別の領域に格納し、それを...
それを確認するサンプルコードがこちらです。
namespace Sample
{
internal class Program
{
static void Main(string[] args)
{
string s1 = "ABC";
string s2 = s1; // 参照値を代入
s2 = "XYZ"; // 書き換える
Console.WriteLine(s1);
Console.WriteLine(s2);
}
}
}
参照型なら両方ともXYZになるはずが、どちらも違う文字列を表現してますよね。
これにはstring型の言語仕様が関係してます。実はC#における文字列とは変更不可です。
つまり文字列を作成後、その文字列の内容を変更することはできないのです。
では、何が起きてるのか。これは単純で、変更できないから新しく作成して参照先を変更してます。
この結果、参照型でありながらも値型と同じような挙動になるんです。
string s1 = "ABC"; // 参照先A
string s2 = s1; // 参照先A
s2 = "XYZ"; // 新しく文字列を作って参照先を変更する
初心者ほど困惑すると思いますが、残念ながら仕様なので諦めましょう。
文字列は文字の集合
当たり前のことを言いますが文字列は文字の集合です。そしてC#では文字をchar型で表現します。
では、ここで次のサンプルコードを確認してください。
namespace Sample
{
internal class Program
{
static void Main(string[] args)
{
string s = "ABCDEFG";
for (int i = 0; i < s.Length; i++)
Console.WriteLine(s[i]); // 文字にアクセス
}
}
}
このように文字列にインデックス経由でアクセスすると要素の文字を取得できます。
ただし、文字列には前述した変更不可の制約があるので、内容の変更はできません。
null文字列と空文字列
参照型なので初期値はnullです。つまり、まだ文字列を作ってない状態ですね。
これに関しては、どこも参照してない状態なので分かりやすいと思います。
実は文字列には、もう1つの初期値みたいな扱いで空文字列と呼ばれる状態があります。
これは文字列として作成したけど、中身が空の文字列を意味します。
空の文字列?
文字列の領域は確保したけど、中に文字が一切ない空っぽの状態だよ。
何のメリットがあるんですか?
処理の共通化ができるよ。
最初に理解して欲しいのは、nullって状態は気軽にアクセスしていい状態ではありません。
例えば、このnullの状態でfor文を使って文字列内の文字を順番に表示しようとすると、例外が発生してプログラムが落ちます。
ここでは例外については触れませんが、nullはそれだけ危険な状態ってことです。
ここではC#の例外処理について学びます。すでに少しだけ触れてますが、これは実行時エラーへの対策となります。ただ、例外処理もクラスの知識を必要とします。そのため60%くらいの解説になりますが、今はここまでいいと思ってます。 例外 (Exception) C#では実行時に発生するエラーを例外と言います。僕が知ってる他の言語も大半が同じだった気がします。この例外とは、特定の条件において処理を完了できない問題が起きると発生し、その例外を誰もcatch(キャッチ)しないとプログラムが強制終了し...
では、空文字列にしたらどうなるのか。空文字列はnullではないので普通にfor文で回せるんです。
つまり、文字列がない状態を空文字にしておけば、文字列に対する処理でnullチェックが不要になります。
ここでは空文字列の作成方法と実際に利用するサンプルコードを確認しましょう。
namespace Sample
{
internal class Program
{
static void Main(string[] args)
{
string s1 = null;
string s2 = "ABC";
// 空文字列の作り方 (全部結果は同じ)
string s3 = "";
string s4 = string.Empty;
string s5 = String.Empty;
// 表示
//Display(s1); // nullはプログラムが落ちる
Display(s2);
Display(s3);
Display(s4);
Display(s5);
}
static void Display(string s)
{
for (int i = 0; i < s.Length; i++)
Console.WriteLine(s[i]);
}
}
}
空文字列の作成方法はこれです。どれでも好きな記述を使ってください。
string s3 = "";
string s4 = string.Empty;
string s5 = String.Empty;
コメントを解除して動作を確認することをオススメします。その場合は例外が発生してプログラムが落ちますよ。
対して空文字列は普通にfor文で回せます。まぁ、回せると言っても長さ0なので内部には一切進んでませんがね。
このように空文字列にしたほうが処理が美しい場面が必ずあります。
どういう時に使うとかありますか?
残念だけどこれは経験です。nullが望ましい時もあれば空文字列が望ましい時もあります。
重要なのは両者は状態として全く違うということ。これだけは必ず理解してください。
文字列で足し算
正確には文字列でも+演算子が使えるって話です。処理も単純で2つの文字列を足し合わせます。
これは例を見たほうが早いので、次のサンプルコードを実行してください。
namespace Sample
{
internal class Program
{
static void Main(string[] args)
{
string s1 = "ABC";
string s2 = "DEF";
string s3 = "GHI";
string s4 = "XYZ";
string s5 = s1 + s2 + s3 + s4; // 文字列同士を足す
Console.WriteLine(s5);
}
}
}
たくさん足してみました。結果の通りで普通に文字列が繋がってます。
ただし、この処理には注意が必要で、文字列は変更不可と言いましたよね。
実は先程の例では、+演算子が処理される度に文字列が生成されてます。
1行で書いてもプログラム的には同時に演算されないので、合計で3回分の文字列が生成されます。
(((s1 + s2) + s3) + s4)
それって影響出るんですか?
燃えそうな感じのいい質問だね!
聞かなければよかった...
結論を言うと昨今のコンピュータなら問題になりません。当然に数千とか結合したら変わりますが、所詮は10個くらいでしょうか。
まぁ、僕もベンチマークしたわけじゃないのですが、今のコンピュータって異次元の速さなので気にするまでもないでしょう。
文字列補間
コードの見た目を考えると1番推奨したい記述方法です。+演算子を使うよりも遥かに美しい見た目になります。
では、先程の例を文字列補間に書き直してみましょう。次のサンプルコードを見てください。
namespace Sample
{
internal class Program
{
static void Main(string[] args)
{
string s1 = "ABC";
string s2 = "DEF";
string s3 = "GHI";
string s4 = "XYZ";
string s5 = $"{s1}{s2}{s3}{s4}"; // 文字列補間
string s6 = $"文字列(s1={s1})と文字列(s2={s2})を表示"; // こんな感じに文字列とも複合できる
Console.WriteLine(s5);
Console.WriteLine(s6);
}
}
}
具体的にはこの辺です。
string s5 = $"{s1}{s2}{s3}{s4}";
string s6 = $"文字列(s1={s1})と文字列(s2={s2})を表示";
$""で文字列を記述します。そして途中に変数を加えたい時は{}で囲みます。
これの優れてる部分は文字列を複合できること、それと数値も記述できることです。
そうなんです。何とこの記述方法はint型などの数値も記述できます。
しかも、書式指定と呼ばれる方法で出力形式を選べたりします。
その動作を次のサンプルコードで確認してください。
namespace Sample
{
internal class Program
{
static void Main(string[] args)
{
int n = 10000;
double d = 3.14d;
string s = "ABC";
Console.WriteLine($"n={n}, d={d}, s={s}"); // 普通に表示
Console.WriteLine($"n={n:D}, n={n:D6}"); // 10進数表示、桁数指定
Console.WriteLine($"n=0x{n:X}, n=0x{n:X8}"); // 16進数表示、桁数指定
Console.WriteLine($"n={n:N}, n={n:N4}"); // ','区切り、小数点以下の桁数指定
}
}
}
どうですか? 記述が簡単でしょ。書式指定する場合は:(コロン)を使って、決められた書式指定子を記述します。
この書式指定子を全部書いてたら夜が明けるので、今回は一部だけ紹介しました。
+演算子より使いやすそうです。
複合書式指定
古来の記述方法です。文字列補間が登場する前の標準でした。
実際のサンプルコードはこちらです。こちらも書式指定子が多いので1個だけ。
namespace Sample
{
internal class Program
{
static void Main(string[] args)
{
int n = 10000;
double d = 3.14d;
string s = "ABC";
// 複合書式指定
Console.WriteLine(String.Format("n={0}, d={1}, s={2}", n, d, s));
}
}
}
これの問題は変数の対応してる位置が分かりづらいこと。
後は記述ミスしてもビルドエラーにならないのでバグの元になりやすいことです。
StringBuilderの紹介
本当に数百回と文字列を繋ぐ場合、今まで紹介した方法は適切ではないです。
この場合はC#で推奨された機能があり、それがStringBuilderと呼ばれるクラスを使う方法です。
まだクラスを説明してないので紹介するか迷いました。なので、今は最適な方法があるくらいの認識で大丈夫です。
namespace Sample
{
internal class Program
{
static void Main(string[] args)
{
// StringBuilder作成
System.Text.StringBuilder sb = new System.Text.StringBuilder();
// 文字列を繋ぐ
for (int i = 0; i < 100; i++)
sb.AppendLine($"{i}"); // 1行単位で繋ぐ
// 表示
Console.WriteLine(sb);
}
}
}
先に文字列を繋いで最後にまとめて文字列にする感じです。
あとがき
GUIでもCLIでも文字列を利用しないプログラムはないでしょう。例え自動化して画面に何も表示しない場合でも、内部的には動作ログを残します。
つまり文字列の扱いは必須の知識です。特に文字列補間は凄く便利なので、絶対に覚えたほうがいいですよ。
◆ C#に関する学習コンテンツ
この記事は参考になりましたか?
コメント