- 演習 1-0——章プログラムをテストする
- 演習 1-1——文字列の連結を理解する
- 演習 1-2—— 文字列の連結を理解する
- 演習 1-3—— 範囲を理解する
- 演習 1-4—— 範囲を理解する
- 演習 1-5—— 範囲を理解する
- 演習 1-6——「隠された」std::cin バッファー操作を調べる
1-0 この章のプログラムをコンパイル、実行、テストします。
解決:
プログラム 1:特定の人に挨拶します。人の名前を尋ね、出力として特定の挨拶を生成します。
// 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;
}
プログラム 2 : より手の込んだ挨拶。5 行の出力が生成されます。最初の行からフレームが始まります。これは、挨拶文と同じ長さの * 文字のシーケンスに、それぞれの末尾にスペースと * を加えたものです。2 行目は、両端に * を付けた適切な数のスペースになります。3 行目は *、スペース、挨拶、スペース、* です。最後の 2 行は、それぞれ 2 行目と最初の行と同じになります。
// 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;
}
1-1 次の定義は有効ですか? なぜ、あるいはなぜそうではないのでしょうか?
const std::string hello = "Hello";
const std::string message = hello + ", world" + "!";
解決:
はい、これらの定義は有効です。1 行目では、長さ 5 文字の文字列変数 hello (これは「Hello」) を定義します。2 行目では、連結演算子を使用して文字列変数メッセージを定義しています。ロジックは次のようになります。
message = ( ( hello + ", world" ) + "!")
= ( ( a string + a string literal ) + a string literal )
= ( ( a string ) + a string literal )
= ( a string + a string literal )
= ( a string )
この質問に答えるための鍵は、文字列連結演算子 + の使用を認識することです。
- 左結合です。
- + を使用すると、文字列と文字列リテラル (またはその逆)、または文字列と文字列を連結できますが、文字列と文字列リテラル (またはその逆) を連結することはできません。
このプログラムを実行することで結論を検証できます。
#include <iostream>
#include <string>
int main()
{
const std::string hello = "Hello";
const std::string message = hello + ", world" + "!";
std::cout << message << std::endl;
return 0;
}
1-2 次の定義は有効ですか? なぜ、あるいはなぜそうではないのでしょうか?
const std::string exclam = "!";
const std::string message = "Hello" + ", world" + exclam;
解決:
いいえ、連結演算子の使用は無効です。つまり、文字列リテラル + 文字列リテラルという「タブー」シナリオに遭遇しますが、これは無効です。
- 行 1 は文字列変数 exclam を定義しており、これは有効です。
- 2 行目では、連結演算子を使用して文字列変数メッセージを定義しています。ロジックは次のようになります。
message = ( ( "Hello " + ", world" ) + exclam)
= ( ( a string literal + a string literal ) + a string )
= ( ( compilation error! ) + a string)
このプログラムを実行することで結論を検証できます。
#include <iostream>
#include <string>
int main()
{
const std::string exclam = "!";
const std::string message = "Hello + ", world" + exclam;
std::cout << message << std::endl;
return 0;
}
コンパイル エラーは次のようになります。
error: invalid operands of types 'const char [6]' and 'const char [8]' to binary 'operator+'
1-3 次のプログラムは有効ですか?
#include <iostream>
#include <string>
int main()
{
{ const std::string s = "a string";
std::cout << s << std::endl; }
{ const std::string s = "another string";
std::cout << s << std::endl; }
return 0;
}
解決:
はい、プログラムは有効です。この質問の鍵は、スコープという用語を理解することです。中括弧の各ペアはスコープを形成します。メイン関数 (スコープ) 内には、2 対の中括弧で定義されている 2 つのサブスコープ (ブロック) があります。各ブロックは独自のスコープを構成します。各スコープ内のすべてのローカル変数とステートメントは互いに独立しています。このため、ブロック 1 に const std::string 変数があっても、 2 つの変数のスコープが異なるため、ブロック 2 に別のconst std::string 変数を定義しても問題ありません。
1-4 次のプログラムは有効ですか? 最後から 2 番目と最後から 3 番目の右中括弧の間にセミコロン (;) を追加するとどうなるでしょうか。
#include <iostream>
#include <string>
int main()
{
{
const std::string s = "a string";
std::cout << s << std::endl;
{
const std::string s = "another string";
std::cout << s << std::endl;
}
}
}
解決:
パート 1:
はい、プログラムは有効です。中括弧 {} の各ペアはスコープを形成します。スコープ内にスコープをネストしても問題ありません。わかりやすくするために、これらのスコープを視覚化するためにコードにいくつかのコメントを追加します。
#include <iostream>
#include <string>
int main()
{//scope main starts
{ //scope main-1 start
const std::string s = "a string";
std::cout << s << std::endl;
{ //scope main-1-1 starts
const std::string s = "another string";
std::cout << s << std::endl;
} //scope main-1-1 ends
} //scope main-1 ends
} //scope main ends
スコープ main-1 のconst std::string 変数は、スコープ main-1-1 (スコープ main-1 内にネストされている) のconst std::string 変数と同じではありません。スコープ main-1-1 がスコープ main-1 内にネストされている場合でも、スコープ main-1-1 内のすべてのローカル変数は main-1 からは隠されています。
特に、文字列は内部スコープで上書きされず、外部スコープから引き続き使用できます。
パート2:
最後から 2 番目と最後から 3 番目の右中括弧の間にセミコロン (;) を追加しても、有効なプログラムを構成します。わかりやすくするために、セミコロンを使用したプログラムは次のようになります。
#include <iostream>
#include <string>
int main()
{//scope main starts
{ //scope main-1 start
const std::string s = "a string";
std::cout << s << std::endl;
{ //scope main-1-1 starts
const std::string s = "another string";
std::cout << s << std::endl;
} //scope main-1-1 ends
; // the additional semi-colon
} //scope main-1 ends
} //scope main ends
追加のセミコロンは基本的に main-1 スコープ内に null ステートメントを作成します。つまり、コードには影響しません。著者がこの質問をする理由は、範囲に関する理解を確実にするためだと思います。つまり、そのセミコロンはどのスコープに属しますか? 上記の方法で C++ コードを記述すると、これを簡単に視覚化できます。
1-5 このプログラムは有効ですか?
#include <iostream>
#include <string>
int main()
{
{
std::string s = "a string";
{
std::string x = s + ", really";
std::cout << s << std::endl;
}
std::cout << x << std::endl;
}
return 0;
}
解決:
いいえ、プログラムは無効なので修正が必要です。特に、あるスコープは、他のスコープの内部にあるものを「認識」できない場合があります。わかりやすくするために、これらのスコープを視覚化するためにコードにいくつかのコメントを追加します。
// original program with comments added to visualise scope
#include <iostream>
#include <string>
int main()
{ // scope main starts
{ // scope main-1 starts
std::string s = "a string";
{ // scope main-1-1 starts
std::string x = s + ", really";
std::cout << s << std::endl;
} //scope main-1-1 ends
std::cout << x << std::endl;
} // scope main-1 ends
return 0;
} // scope main ends
外側のスコープ レベルで定義されたすべてのローカル変数は、内側のスコープ (すべてのレベル) で表示または使用できます。ただし、その逆は不可能です。つまり、内側のスコープ レベルで定義されたすべてのローカル変数は、外側のスコープ (すべてのレベル) やそれに隣接するスコープからは参照/使用できません。(つまり、同じレベルのスコープ)。変数の浸透は、外側のスコープから内側のスコープに進みます。同じレベルの他のスコープや内部スコープ (すべてのレベル) には浸透しません。
スコープ main-1-1 は、 std::string 変数 s (外側のスコープ main-1 で定義されている) と、独自に定義された std::string 変数 x を参照できます。
スコープ main-1 は、独自に定義された変数 std::string のみを参照できます。内部スコープ main-1-1 レベルに存在する std::string 変数 x を確認できません。x が何であるかわからないため、「std::cout << x << std::endl」ステップの実行は失敗します。スコープ main-1 の観点から見ると、変数 x は宣言されていません。
スコープ main は、内部スコープ main-1 または main-1-1 で定義された変数を認識しません。わかっているのは、実装が return ステートメントに到達すると、実装が完了するということだけです。
コンパイル エラーは次のようになります。
line(12): error: ‘x’ was not declared in this scope
std::string x をスコープ main-1 から見えるようにするには、中括弧を削除してスコープ main-1-1 を 1 レベル上に移動するだけです。このような:
// corrected
#include <iostream>
#include <string>
int main()
{ // scope main starts
{ // scope main-1 starts
std::string s = "a string";
std::string x = s + ", really";
std::cout << s << std::endl;
std::cout << x << std::endl;
} // scope main-1 ends
return 0;
} // scope main ends
1-6 次のプログラムは、入力を求められたときに、一度に 2 つの名前 (たとえば、Samuel Beckett) を入力するとどうなりますか? プログラムを実行する前に動作を予測してから試してください。
#include <iostream>
#include <string>
int main()
{
std::cout << "What is your name? ";
std::string name;
std::cin >> name; // first std::cin step
std::cout << " Hello, " << name
<< std::endl << "And what is yours?";
std::cin >> name; // second std::cin step
std::cout << "Hello, " << name
<< "; nice to meet you too!" << std::endl;
return 0;
}
解決:
最初の std::cin ステップで、「Samuel Beckett」という 2 つの単語を入力し、Enter キーを押します。次のようなことが起こります。
(1) 最初に 2 つの単語がバッファ (std::string 変数名に対応) に格納されます。
Samuel Beckett
バッファーは先頭と末尾の空白を自動的に破棄することに注意してください。各単語はスペース文字で区切られます。
(2) 最初の std::cin ステップにより、バッファは最初の単語「Samuel」を std::string 変数割り当てにフラッシュします。std::string 名の値は「Samuel」になりました。最初の単語がバッファからフラッシュされたため、name のバッファは次のようになります。
Beckett
最初の単語 Samuel が消えていることに注意してください。バッファからフラッシュされました。
(3) 2 番目の std::cin ステップでは、バッファー内に既に単語が存在するため、単純にバッファーにその値をフラッシュし、それを std::string 変数名に割り当てるように要求します。std::string 名の値は「Beckett」になりました (古い値「Samuel」は置き換えられました)。これで、name のバッファーは次のようになります。
バッファは空になりました。
3 番目の std::cin ステップがあることが判明した場合、ユーザーは、std::cin 機能がバッファーから読み取ることができるように、バッファーにさらに値を指定するように求められます。(ただし、この場合、プログラムには std::cin ステップが 2 つしかありません)。
要約すると、 std::cin は std::string 変数に対応するバッファをチェックします。
- バッファー内に少なくとも 1 つの単語が保存されている場合、バッファーから最初の単語がフラッシュされ、その単語が読み取られて std::string 変数に割り当てられます。プログラムを一時停止してユーザーに値の入力を求める必要はありません。
- バッファーが空の場合、プログラムは一時停止し、ユーザーにバッファーに値 (単語) を入力するよう求めます。バッファー内にワードがある場合にのみ、 std::cin はそこから読み取り、値を std::string 変数に割り当てます (結果として、そのワードはバッファーからフラッシュされます)。
完全を期すために、プログラムを実行して理解を確認してみましょう。
特に、最初の std::cin ステップで単語を指定しない場合、Enter ボタンを押してもどこにも到達できないことがわかります。プログラムは、バッファーに少なくとも 1 つの単語を指定するように要求します。