Accelerated C++ 第 1 章—文字列の操作

第 0 章では、小さなプログラムを詳しく見ていき、コメント、標準ヘッダー、スコープ、名前空間、式、ステートメント、文字列リテラル、出力など、驚くほど多くの基本的な C++ のアイデアを紹介するために使用しました

この章では、文字列を使用する同様の単純なプログラムを作成することによって、基礎の概要を続けますその過程で、宣言、変数、初期化、さらに入力と C++ 文字列ライブラリについて学びますこの章のプログラムは非常に単純であるため、制御構造も必要ありません。これについては第 2 章で説明します。

1.1 入力

テキストを書くことができたら、次のステップはそれを読むことです。たとえば、Hello, world! を変更できます。特定の人に挨拶するプログラム:

// ask for a person's name, and greet the person
#include <iostream>
#include <string>
int main()
{
    // ask for the person's name    
    std::cout << "Please enter your first name: ";
    // read the name
    std::string name; // define name
    std::cin >> name; // read into
    // write a greeting
    std::cout << "Hello, " << name << "!" << std::endl;
    return 0;
}

このプログラムを実行すると、次のように書き出されます。

Please enter your first name:

標準出力上で。たとえば、次のように応答すると、

Vladimir

そうすればプログラムは書きます

Hello, Vladimir!

何が起こっているのか見てみましょう。入力を読み取るためには、それを置く場所が必要です。このような場所を変数と呼びます。変数は名前を持つオブジェクトです。 つまり、オブジェクトは、タイプを持つコンピューターのメモリの一部です。§3.2.2、§4.2.3、および§10.6.1 で説明するように、名前のないオブジェクトが存在する可能性があるため、オブジェクトと変数の区別は重要です。

変数を使用したい場合は、その変数にどのような名前を付け、どのような型を持たせたいかを実装に伝える必要があります。名前と型の両方を指定する必要があるため、実装ではプログラム用の効率的なマシンコードを生成することが容易になります。この要件により、スペルミスがプログラムで使用する予定だった名前の 1 つと偶然一致しない限り、コンパイラーはスペルミスのある変数名を検出することもできます。

この例では、変数の名前は name で、その型は std::string です。§0.5 と §0.7 で見たように、std:: の使用は、それに続く名前 string が、コア言語や非標準ライブラリの一部ではなく、標準ライブラリの一部であることを意味します。標準ライブラリのすべての部分と同様に、std::string には関連するヘッダー、つまり <string> があるため、適切な #include ディレクティブをプログラムに追加しました。

最初の発言は、

std::cout << "Please enter your first name: ";

これは、ユーザーの名前を尋ねるメッセージを書き込みます。このステートメントの重要な部分は、そこに存在しないもの、つまりstd::endl マニピュレータです。std::endl を使用しなかったため、プログラムがメッセージを書き込んだ後、出力は新しい行で始まりません。代わりに、コンピュータはプロンプトを書き出すとすぐに、同じ行で入力を待ちます。

次の発言は、

std::string name;   // define name

これは、std::string 型の name という名前の変数を定義する定義ですこの定義は関数本体内に出現するため、 name はローカル変数となり、中括弧内のプログラム部分が実行されている間のみ存在します。コンピュータが } に到達するとすぐに変数名を破棄し、その変数が占有していたメモリを他の用途のためにシステムに返します。ローカル変数の有効期間が限られていることが、変数と他のオブジェクトを区別することが重要である理由の 1 つです。

オブジェクトのタイプには、そのインターフェイス、つまりそのタイプのオブジェクトに対して実行できる操作の集合が暗黙的に含まれています。name を文字列型の変数 (名前付きオブジェクト) として定義することにより、文字列で実行できるとライブラリで指示されているすべてのことを name で実行できるようにすることを暗黙的に示しています。

それらの操作の 1 つは、文字列の初期化です標準ライブラリでは、すべての文字列オブジェクトは値から始まると規定されているため、文字列変数を定義すると暗黙的に初期化されます。文字列を作成するときに独自の値を指定できることをすぐに見ていきますそうしないと、文字列には文字がまったく含まれない状態から始まります。このような文字列を空文字列または null 文字列と呼びます。

名前を定義したら、実行します

std::cin >> name;        // read into name

これは、 std::cin から name に読み取るステートメントです<< 演算子と std::cout を出力に使用するのと同様に、ライブラリは >> 演算子と std::cin を入力に使用します。この例では、>> は標準入力から文字列を読み取り、読み取った内容を name という名前のオブジェクトに格納します。ライブラリに文字列の読み取りを依頼すると、まず入力から空白文字 (スペース、タブ、バックスペース、行末) を破棄し、次に別の空白文字または末尾が見つかるまで name に文字を読み込みます。ファイル。したがって、 std::cin >> name を実行すると、標準入力から単語が読み取られ、その単語を構成する文字が name に保存されます。

入力操作には別の副作用があります。ユーザー名を尋ねるプロンプトがコンピューターの出力デバイスに表示されます。一般に、入出力ライブラリは出力をバッファと呼ばれる内部データ構造に保存します。バッファは出力操作を最適化するために使用されます。ほとんどのシステムでは、書き込む文字数に関係なく、出力デバイスに文字を書き込むのにかなりの時間がかかります。各出力要求に応じた書き込みのオーバーヘッドを回避するために、ライブラリはバッファを使用して書き込まれる文字を蓄積し、必要な場合にのみその内容を出力デバイスに書き込むことによってバッファをフラッシュします。そうすることで、複数の出力操作を 1 回の書き込みに組み合わせることができます。

システムがバッファをフラッシュする原因となるイベントは 3 つありますまず、バッファがいっぱいである可能性があります。その場合、ライブラリはバッファを自動的にフラッシュします。2 番目に、ライブラリは標準入力ストリームから読み取るように要求される場合があります。その場合、ライブラリは、バッファがいっぱいになるのを待たずに、出力バッファを直ちにフラッシュします。バッファをフラッシュする 3 番目の機会は、フラッシュするように明示的に指示したときです。

プログラムがプロンプトを cout に書き込むと、その出力は標準出力ストリームに関連付けられたバッファーに入ります。次に、cin からの読み取りを試みます。この読み取りにより cout バッファがフラッシュされるため、ユーザーにはプロンプトが表示されることが保証されます。

出力を生成する次のステートメントは、ライブラリにバッファをフラッシュするよう明示的に指示します。このステートメントは、プロンプトを作成したステートメントよりも少しだけ複雑です。ここでは、文字列リテラル "Hello, " を記述し、その後に文字列変数名の値を記述し、最後に std::endl を記述します。std::endl の値を書き込むと、出力行が終了し、バッファーがフラッシュされます。これにより、システムは出力ストリームに直ちに書き込むようになります。

実行に時間がかかる可能性のあるプログラムを作成する場合、適切なタイミングで出力バッファをフラッシュすることは重要な習慣です。そうしないと、プログラムの出力の一部が、プログラムが出力してから表示されるまでに、システムのバッファー内で長時間滞留する可能性があります。

1.2 名前の構成

これまでのところ、私たちの番組は挨拶を控えめにしてきました。これを変更して、入力と出力が次のように見えるように、より複雑な挨拶を記述したいと思います。

Please enter your first name: Estragon
********************
*                  *
* Hello, Estragon! *
*                  *
********************

私たちのプログラムは 5 行の出力を生成します。最初の行からフレームが始まります。これは、挨拶文と同じ長さの * 文字のシーケンスに、それぞれの末尾にスペースと * を加えたものです。2 行目は、両端に * を付けた適切な数のスペースになります。3 行目は *、スペース、挨拶、スペース、* です。最後の 2 行は、それぞれ 2 行目と最初の行と同じになります。

賢明な戦略は、一度に 1 つずつ出力を構築することです。まず名前を読み取り、次にそれを使用して挨拶を作成し、次に挨拶を使用して出力の各行を作成します。この戦略を使用して問題を解決するプログラムを次に示します。

// ask for a person's name, and generate a framed greeting
#include <iostream>
#include <string>
int main()
{
    std::cout << "Please enter your first name: ";
    std::string name;
    std::cin >> name;

    // build the message that we intend to write
    const std::string greeting = "Hello, " + name + "!";
 
    // build the second and fourth lines of the output
    const std::string spaces(greeting.size(), ' ');
    const std::string second = "* " + spaces + " *";

    // build the first and fifth lines of the output
    const std::string first(second.size(), '*');

    // write it all
    std::cout << std::endl;
    std::cout << first << std::endl;
    std::cout << second << std::endl;
    std::cout << "* " << greeting << " *" << std::endl;
    std::cout << second << std::endl;
    std::cout << first << std::endl;
    return 0;
}

まず、プログラムはユーザーの名前を要求し、その名前を という名前の変数に読み取ります名前挨拶次に、書き込むメッセージを含む変数を定義します。次に、 という名前の変数を定義しますスペース。この変数には、 の文字数と同じ数のスペースが含まれます挨拶この変数を使用して、出力の 2 行目を含む というスペース名前の変数を定義し、プログラムは の文字数と同じ数の * 文字を含む変数として構築します最後に、一度に 1 行ずつ出力を書き込みます。2番初め2番

このプログラムの #include ディレクティブと最初の 3 つのステートメントには見覚えがあるはずです。一方、挨拶の定義には 3 つの新しい概念が導入されています。

1 つのアイデアは、変数を定義するときに変数に値を与えることができるということです。これを行うには、変数名とそれに続くセミコロンの間に、 = 記号の後に変数に設定したい値を置きます。変数と値の型が異なる場合 (§10.2 で文字列と文字列リテラルが示すように)、実装は初期値を変数の型に変換します。

2 番目の新しいアイデアは、「+」を使用して文字列と文字列リテラル、つまり 2 つの文字列 (ただし、2 つの文字列リテラルではありません) を連結できるということです第 0 章で、3 + 4 が 7 であることに注意しました。ここでは、+ がまったく異なるものを意味する例を示します。いずれの場合も、+ 演算子のオペランドの型を調べることで、その演算子が何を行うかを判断できます。演算子が異なる型のオペランドに対して異なる意味を持つ場合、その演算子はオーバーロード されていると言います。

3 番目のアイデアは、変数定義の一部として const を指定するというものです。 そうすることで、残りの存続期間中、変数の値を変更しないことが保証されます。厳密に言えば、このプログラムは const を使用しても何も得られません。ただし、どの変数が変更されないかを指摘すると、プログラムをはるかに理解しやすくなります。

変数が const であると言う場合は、後で初期化する機会がないため、その場で初期化する必要があることに注意してください。const 変数の初期化に使用する値自体が定数である必要はないことにも注意してください。挨拶この例では、値を に読み込むまで の値はわかりませんが 名前、プログラムを実行するまでは明らかにわかりません。このため、名前読み込むことで値が変更されるため、それが const であるとは言えません。

決して変更されない演算子のプロパティの 1 つは結合性です。第 0 章で、<< は左結合であるため、 std::cout << s << t は (std::cout << s) << t と同じ意味であることを学びました。同様に、 + 演算子 (さらに言えば、>> 演算子) も左結合です。したがって、「Hello, " + name + "!」の値は、「Hello, 」と name を連結し、その連結結果を「!」で連結した結果です。したがって、たとえば、変数名に Estragon が含まれている場合、「Hello, " + name + "!」という値が返されます。こんにちは、エストラゴンです!

この時点で、何を言うかが決まり、その情報をgreetingという名前の変数に保存しました。次の仕事は、挨拶を囲むフレームを構築することです。そうするために、1 つのステートメントでさらに 3 つのアイデアを紹介します。

std::string spaces(greeting.size(), ' ');

挨拶を定義するとき、= 記号を使用して初期化しました。ここでは、スペースの後に 2 つの式が続き、それらの式はカンマで区切られ、括弧で囲まれています。= 記号を使用すると、変数にどのような値を持たせたいかを明示的に指定することになります。ここで行うように、定義で括弧を使用することにより、変数の型に応じた方法で変数を構築するように実装に指示します。この場合、式から変数を構築します。スペース言い換えれば、この定義を理解するには、2 つの式から文字列を構築することが何を意味するのかを理解する必要があります。

変数がどのように構築されるかは、その型に完全に依存します。この特定のケースでは、何から文字列を構築していますか? どちらの表現もこれまでに見たことのない形式です。どういう意味でしょうか?

最初の式、greeting.size()は、メンバー関数を呼び出す例です実際、greeting という名前のオブジェクトには size という名前のコンポーネントがあり、これは関数であることが判明し、値を取得するために呼び出すことができます。この変数の挨拶型は std::string で、greeting.size() を評価すると挨拶の文字数を表す整数が得られるように定義されています。

2 番目の式 ' ' は文字リテラルです。文字リテラルは文字列リテラルとは完全に異なります。文字リテラルは常に一重引用符で囲まれます。文字列リテラルは常に二重引用符で囲まれます。文字リテラルの型は組み込み型 char です。文字列リテラルの型はさらに複雑なので、§10.2 まで説明しません。文字リテラルは単一の文字を表します。文字列リテラル内で特別な意味を持つ文字は、文字リテラルでも同じ特別な意味を持ちます。したがって、' または \ が必要な場合は、その前に \ を付ける必要がありますさらに言えば、'\n'、'\t'、'\"'、および関連する形式は、第 0 章で説明した文字列リテラルの場合と同様に機能します。

スペースについて完全に理解するには、整数値と char 値から文字列を構築すると、結果には整数値と同じ数の char 値のコピーが含まれることを知る必要があります。たとえば、次のように定義するとします。

std::string stars(10, '*');

その場合、stars.size() は 10 になり、それ出演者自体には ********** が含まれます。

したがって、スペースには と同じ数の文字が含まれますが挨拶、それらの文字はすべて空白になります。

の定義を理解するのに2番新しい知識は必要ありません。「 * 」、スペース文字列、および「 * 」を連結して、フレーム化されたメッセージの 2 行目を取得します。の定義に初めも新しい知識は必要ありません。初め秒の文字数と同じ数の * 文字を含む値が返されます。

プログラムの残りの部分はよく知られているはずです。§1.1 で行ったのと同じ方法で文字列を記述するだけです。

1.3 詳細

種類:

文字:

実装によって定義された通常の文字を保持する組み込み型。

wchar_t:

中国語などの言語の文字を保持するのに十分な大きさの「ワイド文字」を保持することを目的とした組み込みタイプ。

文字列型:

文字列タイプは 標準ヘッダー <string> で定義されます。文字列型のオブジェクトには、 0 個以上の文字のシーケンスが含まれます。n整数、cが文字、は が入力ストリーム、 がOS出力ストリームの場合、文字列操作には次のものが含まれます。

std::string s;

s最初は空である std::string 型の変数として定義します。

std::string t = s;

tの文字のコピーを最初に含む std::string 型の変数として定義します s。ここで、sは文字列または文字列リテラルのいずれかになります。

std::string z(n, c);

最初に文字のコピーをz含む std::string 型の変数として定義しますここで、は文字列や文字列リテラルではなく、文字である必要があります。ncc

os << s

に含まれる文字をs、書式設定を変更せずに、 で示される出力ストリームに書き込みますOS式の結果は ですOS

is >> s

は空白ではない文字が見つかるまで、で示されるストリームから文字を読み取り、破棄します。次に、連続する文字を から に読み取りはs次にs読み取られる文字が空白になるまで、値が持っていた可能性のあるものを上書きします。結果は ですは

s + t

sこの式の結果は、 の文字のコピーとそれに続く の文字のコピーを含む std::string ですtまたはsのいずれかt(両方ではありません) は、文字列リテラルまたは char 型の値にすることができます。

s.size()

の文字数s

変数

変数は、次の 3 つの方法のいずれかで定義できます。

std::string hello = "Hello";
// define the variable with an explicit initial value


std::string stars(100, '*');
// construct the variable according to its type and the given expressions


std::string name;
// define the variable with an implicit initialization, which depends on its type

一対の中括弧内で定義された変数はローカル変数であり、中括弧内のプログラム部分の実行中にのみ存在します。実装が } に到達すると、変数が破棄され、変数が占有していたメモリがシステムに返されます。変数をconstとして定義すると、その変数の値が存続期間中に変更されないことが保証されます。このような変数は、後で初期化する方法がないため、定義の一部として初期化する必要があります。

入力:

std::cin >> v を実行すると、標準入力ストリーム内の空白文字がすべて破棄され、標準入力から variable に読み込まれます v連鎖入力操作を可能にするために、型 istream を持つ std::cin を返します。

おすすめ

転載: blog.csdn.net/ycy1300585044/article/details/132745387