「C++/標準テンプレートライブラリ」の版間の差分

出典: フリー教科書『ウィキブックス(Wikibooks)』
< C++
削除された内容 追加された内容
M added Category:C++ using HotCat
M 内容の調整
1 行 1 行
== はじめに ==
== はじめに ==
最後に、STLは、言語を問わずあらゆる場面でよく用いられるデータ構造等を提供することを目的として導入されました。STLの中には、[[w:連想配列|連想配列]]、[[w:リスト|リスト]]などのデータ構造が定義されています。これらは、[[w:Perl|Perl]]や、[[w:Python|Python]]などの言語では標準のデータ構造として言語に組み込まれています。これらのデータ構造は、Cのプログラムでもよく用いられます。しかし、Cはこのようなデータ構造を言語自身の機能として持ってはいなかったため、Cを用いるプログラマは、プログラムを作成するときに、まず用いるデータ構造を作成する必要がありました。例えば、[[w:Apache|Apache]] httpdや、[[w:glib|glib]]では、STLで定義されるようなデータ構造が作成されています。
'''標準テンプレートライブラリ''' ({{lang-en-short|Standard Template Library}}、STL) は、言語を問わずあらゆる場面でよく用いられるデータ構造等を提供することを目的として導入されました。STLの中には、[[w:連想配列|連想配列]]、[[w:リスト|リスト]]などのデータ構造が定義されています。これらは、[[w:Perl|Perl]]や、[[w:Python|Python]]などの言語では標準のデータ構造として言語に組み込まれています。これらのデータ構造は、Cのプログラムでもよく用いられます。しかし、Cはこのようなデータ構造を言語自身の機能として持ってはいなかったため、Cを用いるプログラマは、プログラムを作成するときに、まず用いるデータ構造を作成する必要がありました。例えば、[[w:Apache|Apache]] httpdや、[[w:glib|glib]]では、STLで定義されるようなデータ構造が作成されています。
STLは作業の重複を省く意味でも積極的に用いるのがよいでしょう。ただし、これらのデータ構造を用いることだけが目的なら、[[Perl]]、[[Python]]などを試してみることを勧めます。これらの言語はC++ほど速く実行されはしません。しかし、C++と比べて比較的簡潔にこれらを用いることが出来ます。そのため、STLはC++を用いる上でこれらのデータ構造が必要になった時に使われます。
STLは作業の重複を省く意味でも積極的に用いるのがよいでしょう。ただし、これらのデータ構造を用いることだけが目的なら、[[Perl]]、[[Python]]などを試してみることを勧めます。これらの言語はC++ほど速く実行されはしません。しかし、C++と比べて比較的簡潔にこれらを用いることが出来ます。そのため、STLはC++を用いる上でこれらのデータ構造が必要になった時に使われます。


==string==
== 文字列 ==
Cではchar型の列を用いる事で文字列を扱いまたが、
Cではchar型のポインタ (以下、C形式の文字) を用いる事で文字列を操作ます。
以下の例を見てください。
C++で標準ライブラリにあるstring型で文字列を扱います。


Cの機能を使った文字列操作には、
例1: C形式の文字列操作する
<syntaxhighlight lang="c">
次のようなものがあります。

<source lang="c">
#include <stdio.h>
#include <stdio.h>
#include <string.h>
#include <string.h>
16 行 14 行
int main()
int main()
{
{
char s1[] = "mojiretu";
const char * s1 = "mojiretu";
char s2[256];
char s2[256];
27 行 25 行
return 0;
return 0;
}
}
</syntaxhighlight>
</source>


C++では、C形式の文字列の代わりに{{code|std::string}} (以下、単に{{code|string}}) 型を用いて文字列操作を行うことができます。
これをC++のstring型を使って書き直すと、
次のようになります。
{{code|string}}型を使って上記のコードと同じ働きをするコードを書くと、次のようになります。


例2: 例1と同じ動作をするコード
<source lang="cpp">
<syntaxhighlight lang="c++">
#include <iostream>
#include <iostream>
#include <string>
#include <string>
49 行 48 行
return 0;
return 0;
}
}
</syntaxhighlight>
</source>


string型にはchar型列を使うのにくらべて、
{{code|string}}型にはC形式文字列を使うのにくらべて、次のような違いがあります。
次のようなポイントがあります。


#事前に文字列の最大数を指定する必要がなく、適宜メモリサイズが拡張される。
* 事前に文字列の最大数を指定する必要がなく、適宜メモリサイズが拡張される。
#コピーや追加等の操作を演算子により行える。
* コピーや追加等の操作を演算子により行える。
#現在の文字列のサイズを、オブジェクトの中に持っている。
* 現在の文字列のサイズを、オブジェクトの中に持っている。


ところで、例1を関数に分割すると、次のようになります。
上記例では同一関数内ですのであまりメリットが見えないかもしれませんが、
ソフトウェアの規模が大きくなるにつれて利便性が見えてきます。


Cバージョンを関数分割すると、
例3: 例1を関数分割したバージョン
<syntaxhighlight lang="c">
次のようになります。
#include <stdio.h>
#include <string.h>


void func(const char* s1)
<source lang="c">
{
#include <stdio.h>
char s2[256]; //<--s1の文字列サイズチェックがないとs2にコピー時オーバーフロー発生する可能性ある
#include <string.h>
void func(const char* s1)
{
char s2[256]; //<--s1の文字列サイズチェックがないとs2にコピー時オーバーフロー発生する可能性ある
strcpy(s2, s1); /* 文字列のコピー */
printf("%s\n", s2);
strcat(s2, s1); /* 文字列の追加 */
printf("%s\n", s2);
printf("%zu\n", std::strlen(s2)); /* 文字列のサイズ */
}
int main()
{
char s1[] = "mojiretu";
func(s1);
return 0;
}
</source>


strcpy(s2, s1); /* 文字列のコピー */
この例ではmain()からしかfunc()は呼ばれないので、
printf("%s\n", s2);
事実上問題はありませんが、
strcat(s2, s1); /* 文字列の追加 */
func()に渡された文字列サイズをチェックしないと、
printf("%s\n", s2);
s2に対して範囲外へのアクセスを行う可能性があるため危険です。
printf("%zu\n", std::strlen(s2)); /* 文字列のサイズ */
}

int main()
{
char s1[] = "mojiretu";

func(s1);

return 0;
}
</syntaxhighlight>

例3では{{code|func()}}は{{code|main()}}からしか呼ばれません。
しかし、{{code|func()}}に渡された文字列の長さをチェックしないと、s2に対して範囲外へのアクセスを行う可能性があるため危険です<ref group="注">257文字以上の文字列を{{code|func()}}に渡すとバグの原因になります</ref>。


これに対してC++バージョンを関数分割すると、
これに対してC++バージョンを関数分割すると、
次のようになります。
次のようになります。


例4: 例2を関数に分割したバージョン
<source lang="cpp">
<syntaxhighlight lang="cpp">
#include <iostream>
#include <string>
#include <iostream>
#include <string>

void func(const std::string& s1)
void func(const std::string& s1)
{
{
std::string s2;
std::string s2;

s2 = s1; /* 文字列のコピー */
s2 = s1; /* 文字列のコピー */
std::cout << s2 << std::endl;
std::cout << s2 << std::endl;
s2 += s1; /* 文字列の追加 */
s2 += s1; /* 文字列の追加 */
std::cout << s2 << std::endl;
std::cout << s2.size() << std::endl; /* 文字列のサイズ */
std::cout << s2 << std::endl;
std::cout << s2.size() << std::endl; /* 文字列のサイズ */
}
}

int main()
int main()
{
{
std::string s1 = "mojiretu";
std::string s1 = "mojiretu";

func(s1);
func(s1);

return 0;
return 0;
}
}
</syntaxhighlight>
</source>

{{code|string}}型は必要に応じて内部のバッファが拡張されるので、引数で渡された文字列のサイズを気にせずコピーや追加を行うことが出来ます。


== 注釈 ==
string型への操作は必要に応じて内部のメモリサイズが増えるので、
{{reflist|group=注}}
引数で渡された文字列のサイズを気にせずコピーや追加を行うことが出来ます。
<!--
(当然のことですが、プログラムを実行しているコンピュータ側の限界は超えられません)
-->


[[カテゴリ:C++]]
[[カテゴリ:C++]]

2021年4月28日 (水) 23:04時点における版

はじめに

標準テンプレートライブラリ (英: Standard Template Library、STL) は、言語を問わずあらゆる場面でよく用いられるデータ構造等を提供することを目的として導入されました。STLの中には、連想配列リストなどのデータ構造が定義されています。これらは、Perlや、Pythonなどの言語では標準のデータ構造として言語に組み込まれています。これらのデータ構造は、Cのプログラムでもよく用いられます。しかし、Cはこのようなデータ構造を言語自身の機能として持ってはいなかったため、Cを用いるプログラマは、プログラムを作成するときに、まず用いるデータ構造を作成する必要がありました。例えば、Apache httpdや、glibでは、STLで定義されるようなデータ構造が作成されています。 STLは作業の重複を省く意味でも積極的に用いるのがよいでしょう。ただし、これらのデータ構造を用いることだけが目的なら、PerlPythonなどを試してみることを勧めます。これらの言語はC++ほど速く実行されはしません。しかし、C++と比べて比較的簡潔にこれらを用いることが出来ます。そのため、STLはC++を用いる上でこれらのデータ構造が必要になった時に使われます。

文字列

Cではchar型のポインタ (以下、C形式の文字列) を用いる事で文字列を操作します。 以下の例を見てください。

例1: C形式の文字列を操作する

 #include <stdio.h>
 #include <string.h>
 
 int main()
 {
   const char * s1 = "mojiretu";
   char s2[256];
 
   strcpy(s2, s1);    /* 文字列のコピー */
   printf("%s\n", s2);
   strcat(s2, s1);    /* 文字列の追加 */
   printf("%s\n", s2);
   printf("%zu\n", strlen(s2));    /* 文字列のサイズ */
 
   return 0;
 }

C++では、C形式の文字列の代わりにstd::string (以下、単にstring) 型を用いて文字列操作を行うことができます。 string型を使って上記のコードと同じ働きをするコードを書くと、次のようになります。

例2: 例1と同じ動作をするコード

 #include <iostream>
 #include <string>
 
 int main()
 {
   std::string s1 = "mojiretu";
   std::string s2;
 
   s2 = s1;    /* 文字列のコピー */
   std::cout << s2 << std::endl;
   s2 += s1;    /* 文字列の追加 */
   std::cout << s2 << std::endl;
   std::cout << s2.size() << std::endl;    /* 文字列のサイズ */
 
   return 0;
 }

string型にはC形式の文字列を使うのにくらべて、次のような違いがあります。

  • 事前に文字列の最大数を指定する必要がなく、適宜メモリサイズが拡張される。
  • コピーや追加等の操作を演算子により行える。
  • 現在の文字列のサイズを、オブジェクトの中に持っている。

ところで、例1を関数に分割すると、次のようになります。

例3: 例1を関数に分割したバージョン

#include <stdio.h>
#include <string.h>

void func(const char* s1)
{
  char s2[256]; //<--s1の文字列サイズチェックがないとs2にコピー時オーバーフロー発生する可能性ある

  strcpy(s2, s1);    /* 文字列のコピー */
  printf("%s\n", s2);
  strcat(s2, s1);    /* 文字列の追加 */
  printf("%s\n", s2);
  printf("%zu\n", std::strlen(s2));    /* 文字列のサイズ */
}

int main()
{
  char s1[] = "mojiretu";

  func(s1);

  return 0;
}

例3ではfunc()main()からしか呼ばれません。 しかし、func()に渡された文字列の長さをチェックしないと、s2に対して範囲外へのアクセスを行う可能性があるため危険です[注 1]

これに対してC++バージョンを関数分割すると、 次のようになります。

例4: 例2を関数に分割したバージョン

#include <iostream>
#include <string>

void func(const std::string& s1)
{
  std::string s2;

  s2 = s1;    /* 文字列のコピー */
  std::cout << s2 << std::endl;
  s2 += s1;    /* 文字列の追加 */
  std::cout << s2 << std::endl;
  std::cout << s2.size() << std::endl;    /* 文字列のサイズ */
}

int main()
{
  std::string s1 = "mojiretu";

  func(s1);

  return 0;
}

string型は必要に応じて内部のバッファが拡張されるので、引数で渡された文字列のサイズを気にせずコピーや追加を行うことが出来ます。

注釈

  1. ^ 257文字以上の文字列をfunc()に渡すとバグの原因になります