C++テンプレートと標準ライブラリ勉強日記です。
使用テキストは、プログラミング言語C++第3版(C++3rd、バイブル)、標準C++ライブラリチュートリアル&リファレンス(ASCII)、標準講座C++(シルト著)、標準C++の基礎知識シリーズ(柏原著)などなど・・・他たくさん
コンパイラはBCC32を使用。(WindowsXP)
最新2004/8/26の日記
きっかけは、図書館で借りた「標準C++ライブラリチュートリアル&リファレンス(ASCII)」でした。
はじめの方にテンプレートが載っているのですが・・・忘れた・・・というか、わからない・・・というか、わかっていたのか?・・・型汎用性くらいだけはなんとか・・・
はじめのサンプルとしては、以下のようなものが有名なようです。
//関数テンプレート max と swap
#include <iostream>
template <typename T>
inline const T& max(const T& a, const T& b)
{
return a < b ? b : a;
}
template <typename T>
inline void swap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
int main(void)
{
int a=4, b=5;
std::cout << "a= " << a << " b= " << b << std::endl;
std::cout << max(a,b) << std::endl; //大きい方を表示
swap(a,b); //交換
std::cout << "a= " << a << " b= " << b << std::endl;
return 0;
}
実行結果
a= 4 b= 5
5
a= 5 b= 4
このあたりは、見れば思い出せる・・・
次回以降は、Stroustrup著のプログラミング言語C++第3版を参考にしていく予定。
Stroustrup著プログラミング言語C++第3版。P388
標準ライブラリを利用した語数計算プログラムですが、出力コードが載っていないので、自分で書き足してみました。イテレータを使ってFOR文でまわしてます。
for_eachなどで出力すればスマートなのでしょうが、わからないんです。
//標準ライブラリを利用したテンプレートの使用例:語数計算
#include <iostream>
#include <string>
#include <map>
using namespace std;
int main(void)
{
string buf;
map<string, int> m;
while (cin >> buf) m[buf]++;
for ( map<string, int>::iterator ite = m.begin(); ite != m.end(); ++ite) {
cout <<"key: " << (*ite).first << "\t語数: " << (*ite).second << endl;
}
return 0;
}
実行は標準入力からキー入力してCTRL+Zで中断すると、KEYと語数が出力されます。
実行例はファイルをリダイレクトしています。
実行例(実行ファイル名 < 読み込みファイル名)
D:\C++実験>template2 < template2.cpp
key: != 語数: 1
key: " 語数: 2
key: "\t語数: 語数: 1
key: #include 語数: 3
key: ( 語数: 1
key: (*ite).first 語数: 1
key: (*ite).second 語数: 1
key: (cin 語数: 1
key: ++ite) 語数: 1
・・・・
以下続く
mapなどの連想配列はPerlをやったら、ピンとくるようになった。
今日は、関数オブジェクトなどで行き詰った記憶がよみがえった・・・
C++は難しいなあ
Stroustrup著プログラミング言語C++第3版。P388
宣言の外で定義する記述方法が書いてある。しかし、テンプレートはヘッダファイルにインライン関数で書かれるため、このように書くことは、あまり無さそうだ。
コードの例は、11章のStringクラスを使っている。
このあとも出てきそうなので、必要な部分を作りながら、確認していくことにした。
Stringクラスを書くのは大変なので、Stringクラスの内部にあるSrepクラス(構造体)を作ることにした。
Srep構造体とStringクラスはP347に載っている。
//Srep構造体。テンプレートにする前にchar型で記述
//csrep.h
#ifndef CSREP_H_TEMPLATE_20040814_SUNA
#define CSREP_H_TEMPLATE_20040814_SUNA
struct Srep {
char* str; //要素へのポインタ
int size; //要素の数
int count; //参照カウンタ
//コンストラクタ、デストラクタ
Srep(int isize, const char* istr)
{
count = 1;
size = isize;
str = new char[size+1];
strcpy(str, istr);
}
~Srep() { delete[] str; }
private:
//コピーの禁止(コピーコンストラクタとコピー代入演算子を禁止する)
Srep(const Srep& );
Srep& operator=(const Srep&);
};
#endif //CSREP_H_TEMPLATE_20040814_SUNA
//テンプレートにする前にchar型で記述
#include <iostream>
#include "csrep.h"
using namespace std;
int main(void)
{
char* input= "こんにちは、ぼくどらえもn";
Srep n(strlen(input), input);
cout << n.str << endl;
cout << "サイズ:" << n.size << endl;
return 0;
}
以下、テンプレート化したもの。定義を宣言の外に書いた。
//Srep構造体。テンプレート化
//csrep.h
#ifndef CSREP_T_H_TEMPLATE_20040814_
#define CSREP_T_H_TEMPLATE_20040814_
template <typename C>
struct Srep {
C* str; //要素へのポインタ
int size; //要素の数
int count; //参照カウンタ
//コンストラクタ、デストラクタ
Srep(int isize, const C* istr);
~Srep();
private:
//コピーの禁止(コピーコンストラクタとコピー代入演算子を禁止する)
Srep(const Srep& );
Srep& operator=(const Srep&);
};
//定義
template<typename C> Srep<C>::Srep(int isize, const C* istr)
{
count = 1;
size = isize;
str = new char[size+1];
strcpy(str, istr);
}
template<typename C> Srep<C>::~Srep() { delete[] str; }
#endif //CSREP_T_H_TEMPLATE_20040814_
//テンプレート実行ファイル
#include <iostream>
#include "csrep_t.h"
using namespace std;
int main(void)
{
char* input= "こんにちは、ぼくどらえもn";
Srep<char> n(strlen(input), input);
cout << n.str << endl;
cout << "サイズ:" << n.size << endl;
return 0;
}
実行例はテンプレート化前と同じ。
Srepは、文字数を渡す作りになっているので、これだけで使うとメンドウだ。
strlenを内部で使えばいいと思ってしまうんだけど、たぶん深い理由があるんだろうなあ。
・・・と思ったが、
MAY94 Code Capsules
ここでは、内部にstrlen()を使ってるので、どっちでもいいのかな?
ネットで検索すると、この本を元にしているコードが多いので、変数名とか変えないほうがよさそうな気がした。
Stroustrup著プログラミング言語C++第3版。P390
テンプレート引数の文はちょっとわかりづらい。
Buffer<char, 10> とBuffer<char, 20>は違う型になるようだ。
ネットワークプログラミングの勉強をしてたら、バイトオーダーが気になったので、 アドレス配置を知るために書いてみた。ビット表記をするのにbitsetを使った。
//byteorder.cpp bitsetを使う。ネットワークバイトオーダー
//2004/8/15
#include <iostream>
#include <iomanip>
#include <bitset>
#include <winsock2>
using namespace std;
int main(void)
{
union uIP {
unsigned long lv;
unsigned char v[4];
};
uIP ip;
ip.lv = 0xc0a8000f; //192.168.0.15の16進数表記
for (int i=0; i < 4; ++i) {
bitset<8> bit = ip.v[i];
cout << i <<" :アドレス0x" << (int)&(ip.v[i])
<< " 値:" << setw(4) << (int)ip.v[i]
<< ": ビット列:" << bit << endl;
}
cout << " 16進:0x" << hex << setw(8) << setfill('0') << ip.lv << endl;
bitset<32> order(ip.lv);
cout << "CPUの並び:" << order << endl;
bitset<32> netorder(htonl(ip.lv));
cout << "network :" << netorder << endl;
return 0;
}
実行例
0 :アドレス0x1244980 値: 15: ビット列:00001111
1 :アドレス0x1244981 値: 0: ビット列:00000000
2 :アドレス0x1244982 値: 168: ビット列:10101000
3 :アドレス0x1244983 値: 192: ビット列:11000000
16進:0xc0a8000f
CPUの並び:11000000101010000000000000001111
network :00001111000000001010100011000000
IPアドレスとして、192.168.0.15を入れた。
IPアドレスは、上位アドレスから順に配置され、上位アドレスから読み込んでいる。
ネットワークバイトオーダーに変換すると、バイト配置が逆になった。
ネットワークバイトオーダーでは、ビットの読み方向も反対になるので、
newwork:11110000000000000001010100000011を反対から読むことになるようだ。
Stroustrup著プログラミング言語C++第3版。をもとに進めようとしているが、
サンプルコードが省略されているので、非常に進めづらい。
P405のVectorのswapを試そうと思ったがやっていることがよくわからず・・・苦しい。力不足だなあ
テンプレートの基本的なところは、大体わかったとおもうので、明日からはC++標準ライブラリチュートリアル&リファレンスを読んでいくつもり。
P29のmax関数は、日記の初めの方でやった。
以下、2章は読み飛ばし。3章の例外処理に進む。
標準ライブラリ自体は、range_error, out_of_range, invalid_argumentの例外を送出する。
コードの移植性のためにも、標準の例外クラスだけを使うべき。
使い方
throw std::out_of_range(const stirng& s)
sにwhat()で呼び出されるメッセージを格納できる。
例外は、使ったことがない。
次回は第4章。
template <class U, class V>
pair(const pair<U,V>& p):first(p.first), second(p.second){}
引数の型が一致しない時はこのコピーコンストラクタが呼び出され、テンプレートコンストラクタの確認
pairはstd::pairと名前がかぶるので、sun名前空間で定義した。
//C++チュートリアル&リファレンスのP55
//テンプレートコンストラクタの確認
//2004/8/18
#include <iostream>
using namespace std;
namespace sun{
template <class T1, class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair(const T1& a, const T2& b) : first(a), second(b) {
cout << "D引数コンストラクタ" << endl;
}
pair()
: first(T1()), second(T2()) {
cout << "Dデフォルトコンストラクタ" << endl;
}
/* コメントアウト。組み込みのコピーコンストラクタを使う。
pair(const pair& p)
: first(p.first), second(p.second){
cout << "コピーコンストラクタ" << endl;
}
*/
template <class U, class V> pair(const pair<U,V>& p)
: first(p.first), second(p.second){
cout << "テンプレートコンストラクタ" << endl;
}
};
}
int main(void)
{
sun::pair<int, double> p;
cout << "同じ型" << endl;
sun::pair<int, double> p1 = p;
cout << "違う型" << endl;
sun::pair<int, float> p2 = p;
return 0;
}
実行例
Dデフォルトコンストラクタ
同じ型
違う型
テンプレートコンストラクタ
違う型の時には、テンプレートコンストラクタが呼ばれている。
コピーコンストラクタのコメントアウトをはずすと、同じ型の時、このコピーコンストラクタが呼ばれた。
暗黙の型変換には、テンプレートコンストラクタが呼ばれている。
//C++チュートリアル&ライブラリのP56
//make_pair
//2004/8/18
#include <iostream>
#include <utility>
using namespace std;
namespace sun{
template <class T1, class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair(const T1& a, const T2& b) : first(a), second(b) {
cout << "D引数コンストラクタ" << endl;
}
pair()
: first(T1()), second(T2()) {
cout << "Dデフォルトコンストラクタ" << endl;
}
pair(const pair& p)
: first(p.first), second(p.second){
cout << "コピーコンストラクタ" << endl;
}
template <class U, class V> pair(const pair<U,V>& p)
: first(p.first), second(p.second){
cout << "テンプレートコンストラクタ" << endl;
}
};
/* make_pair関数 */
template <class T1, class T2>
pair<T1, T2> make_pair(const T1& x, const T2& y) {
return pair<T1, T2>(x, y);
}
}
void f(sun::pair<int,double>) {
cout << "function f" << endl;
}
int main(void)
{
cout << "引数:int,double 一時オブジェクトを作成" << endl;
f(sun::make_pair(4,5.6));
cout << "\n引数:int,int 一時オブジェクトをさらに型変換している。" << endl;
f(sun::make_pair(4,5));
cout << "\n引数:int,double(型指定)" << endl;
f(sun::pair<int,double>(4,5));
cout << "\n引数:int,float(型指定) これも型変換している。" << endl;
f(sun::pair<int,float>(4,5));
return 0;
}
実行結果
引数:int,double 一時オブジェクトを作成
D引数コンストラクタ
function f
引数:int,int 一時オブジェクトをさらに型変換している。
D引数コンストラクタ
テンプレートコンストラクタ
function f
引数:int,double(型指定)
D引数コンストラクタ
function f
引数:int,float(型指定)
D引数コンストラクタ
テンプレートコンストラクタ
function f
auto_ptrはMoreEffectiveC++で出てきたが、さらっと読むだけしかできなかった。
auto_ptrはC++3rdP430にものっている。
例外処理の、delete処理の面倒をなくすもの
auto_ptr_refのあたりは特殊な型変換だということだが、今の自分のレベルでは、何をやっているのかわからない。後で考えよう。以下引用
auto_ptrクラスの残り(補助型のauto_ptr_refとこれを使う関数群)は、
コピーコンストラクタと代入演算を定数ではないauto_ptrには使用可能にし、
定数のauto_ptrには使用できないようにするという少しトリッキーな変換を行う。
(詳細については、61ページの注意点を参照)続いて簡単に説明するが、要件は次の2つである。
通常のコピーコンストラクタはrvalueをコピーできるが、そのためにはコピーコンストラクタの引数をconstオブジェクトの参照として宣言する。(補足:auto_ptr(const auto_ptr & a)) 通常のコンストラクタを使ってauto_ptrをコピーするには、 実ポインタを含むデータメンバをmutableで宣言し、コピーコンストラクタで変更されるようにする。 しかし、このように宣言すると、実際にはcosntで宣言されているauto_ptrをコピーするコードの記述が可能になり、状態が変化しない定数として宣言しているのに所有権が渡されてしまう。(補足:const auto_ptrなのに所有権が渡されてしまう。)
代わりに、rvalueをlvalueに変換する仕組みを探そう。単純に参照型へ変換する演算子変換関数ではうまくいかない。 オブジェクトをオブジェクトそのものの型に変換するために演算子変換関数が呼び出されることはないからだ(参照の属性は型に含まれていないことを思い出してほしい)。 そこで、「lvalueへの変換」機能を提供するためにauto_ptr_refクラスが導入された。 この機能は、オーバーロードとテンプレート引数の推測の微妙な違いに依存する。この2つの機能の違いは一般的なプログラミングツールとして使うには微妙すぎるが、auto_ptrクラスを正しく動作させるには十分である。
コンパイラが、定数ではないauto_ptrと定数のauto_ptrを区別しなくても、驚かないでほしい。しかし、コンパイラがその違いを実装していないと、auto_ptrのインターフェイスの危険性が大きくなる。この場合、間違って所有権を譲渡することが多くなる。
これの定数は、整数型<climits><limits.h>
浮動小数点型は<cfloat><float.h>で、記述されている。
| NULL | cでは、void* 0,c++では、整数の0 |
| size_t | サイズの単位。bccでは、unsigned int |
| ptrdiff_t | ポインタの差をあらわす型。bccではint |
| offsetof() | (構造体、または共用体のメンバのオフセット)の定義。 bccではoffsetof( s_name, m_name ) (_SIZE_T)&(((s_name _FAR *)0)->m_name) |
| exit(int) | プログラムを終了する。 静的なオブジェクトの削除、バッファのフラッシュ、入出力チャンネルのクローズを完全に行う。 atexit()で登録された関数の実行を行う。 |
| EXIT_SUCCESS | プログラムの正常終了。bccでは0 |
| EXIT_FAILURE | プログラムの異常終了。bccでは1 |
| abort() | プログラムを中断する。後処理を行うことなく、即座にプログラムを終了させる。) |
| atexit(void (*f)()) | 終了時に関数を呼び出す。 |
//cstdlib 2004/8/20
#include <iostream>
//#include <cstdlib>
using namespace std;
void endfunc() {
cout << "おしまい" << endl;
}
int main(void)
{
atexit(endfunc);
return 0;
}
実行すると、「おしまい」が出力される。次章はいよいよSTL。
STLの中心のコンポーネントはコンテナ、反復子、アルゴリズム。
ほかのコンポーネントは、アダプタ、関数オブジェクト(functor)など。
STLの概念は、データと演算を分離することに基づいている。オブジェクト指向の本来の考え方と矛盾する。
任意の種類のコンテナを任意の種類のアルゴリズムと結びつけることができるため、柔軟性が高くなる。
常にソートした状態で使いたい時は、set、multisetを使えということか。
連想コンテナのイテレータの動きは2分木のとおりがけ順。
アルゴリズムは、反復子を使う、グローバル関数である。オブジェクト指向プログラミングのパラダイムではなく、関数型プログラミングのパラダイムである。
欠点:使用方法がわかりにくい。コンテナとアルゴリズムに組み合わせのできないものもある
//C++チュートリアル&リファレンスのP108あたり
//vectorとアルゴリズム 2004/8/21
#include <iostream>
#include <vector>
#include <algorithm>
//出力関数
template <typename T>
void print(const std::vector<T> & v, const std::string s ="")
{
std::cout << "要素の出力。要素数:" << v.size() << " : " << s <<std::endl;
for (std::vector<T>::const_iterator ite = v.begin(); ite != v.end(); ++ite) {
std::cout << *ite << " , " ;
}
std::cout << std::endl;
}
int main(void)
{
std::vector<int> coll;
std::vector<int>::iterator pos, pos1;
coll.push_back(11);
coll.push_back(12);
coll.push_back(13);
coll.push_back(14);
coll.push_back(15);
print(coll, "初期値");
pos = std::min_element(coll.begin(), coll.end());
std::cout << "min:" << *pos << std::endl;
pos1 = std::max_element(coll.begin(), coll.end());
std::cout << "max:" << *pos1 << std::endl;
std::swap(*pos, *pos1);
print(coll, "最小と最大を交換");
coll[1] = 22;
print(coll, "2番目書き換え");
std::sort(coll.begin(), coll.end());
print(coll, "並べ替え");
pos = std::find(coll.begin(), coll.end(),13);
std::reverse(pos, coll.end());
print(coll, "13を探して、13以降を逆順並べ替え");
++*pos;
print(coll, "13のあった位置の数値を1増やす。");
return 0;
}
実行結果
要素の出力。要素数:5 : 初期値
11 , 12 , 13 , 14 , 15 ,
min:11
max:15
要素の出力。要素数:5 : 最小と最大を交換
15 , 12 , 13 , 14 , 11 ,
要素の出力。要素数:5 : 2番目書き換え
15 , 22 , 13 , 14 , 11 ,
要素の出力。要素数:5 : 並べ替え
11 , 13 , 14 , 15 , 22 ,
要素の出力。要素数:5 : 13を探して、13以降を逆順並べ替え
11 , 22 , 15 , 14 , 13 ,
要素の出力。要素数:5 : 13のあった位置の数値を1増やす。
11 , 23 , 15 , 14 , 13 ,
2つの要素の探索プログラム。(P113上)
P113下にcompose_f_gx_hxがあるが、今は意味がわからないので何もしてない。P309に出てくる。
//C++チュートリアル&ライブラリのP108
//vectorとアルゴリズム
//2004/8/21
#include <iostream>
#include <vector>
#include <algorithm>
//出力関数
template <typename T>
void print(const std::vector<T> & v, const std::string s ="")
{
std::cout << "要素の出力。要素数:" << v.size() << " : " << s <<std::endl;
for (std::vector<T>::const_iterator ite = v.begin(); ite != v.end(); ++ite) {
std::cout << *ite << " , " ;
}
std::cout << std::endl;
}
//P113 2つの要素の探索、どちらが前にあるかわからない。
//end()だった場合、間接参照してはならないため、注意する。
template <typename T>
void find2element(const std::vector<T>& co, const T& fn1, const T& fn2)
{
std::vector<T>::const_iterator posf1, posf2;
posf1 = std::find(co.begin(), co.end(), fn1);
posf2 = std::find(co.begin(), posf1, fn2); //[begin, posf1)で探索
if (posf2 != posf1) {
std::cout << fn2 << "は、" << fn1 << " より前にあります。 " <<std::endl;
} else {
posf2 = std::find(posf1, co.end(), fn2); //[posf1, end)で探索
if (posf2 != posf1) {
std::cout << fn2 << "は、" << fn1 << " より後ろにあります。 " <<std::endl;
} else {
std::cout << fn2 << "は、"
<< fn1 << " と同じ位置にあります。(同じ数値か、end)" <<std::endl;
}
}
}
int main(void)
{
std::vector<int> coll;
std::vector<int>::iterator pos, pos1;
coll.push_back(11);
coll.push_back(12);
coll.push_back(13);
coll.push_back(14);
coll.push_back(15);
coll.push_back(14);
std::cout << "*** 2つの要素の探索 ***" << std::endl;
print(coll);
find2element(coll, 13, 15);
find2element(coll, 11, 13);
find2element(coll, 14, 14);
find2element(coll, 15, 30);
find2element(coll, 30, 15);
find2element(coll, 30, 32);
return 0;
}
実行結果
*** 2つの要素の探索 ***
要素の出力。要素数:6 :
11 , 12 , 13 , 14 , 15 , 14 ,
15は、13 より後ろにあります。
13は、11 より後ろにあります。
14は、14 と同じ位置にあります。(同じ数値か、end)
30は、15 より後ろにあります。
15は、30 より前にあります。
32は、30 と同じ位置にあります。(同じ数値か、end)
//C++チュートリアル&リファレンスのP114あたり
//resize() 2004/8/21
#include <iostream>
#include <vector>
//出力関数
template <typename T>
void print(const std::vector<T> & v, const std::string s ="")
{
std::cout << "要素の出力。要素数:" << v.size() << " : " << s <<std::endl;
for (std::vector<T>::const_iterator ite = v.begin(); ite != v.end(); ++ite) {
std::cout << *ite << " , " ;
}
std::cout << std::endl;
}
int main(void)
{
std::cout << "配列の要素を5個確保し、push_backで5個追加する。" << std::endl;
std::vector<int> coll(5);
coll.push_back(11);
coll.push_back(12);
coll.push_back(13);
coll.push_back(14);
coll.push_back(15);
print(coll, "初期値");
coll.resize(7);
print(coll, "要素数を7に変更resize(7)。");
return 0;
}
実行結果
配列の要素を5個確保し、push_backで5個追加する。
要素の出力。要素数:10 : 初期値
0 , 0 , 0 , 0 , 0 , 11 , 12 , 13 , 14 , 15 ,
要素の出力。要素数:7 : 要素数を7に変更resize(7)。
0 , 0 , 0 , 0 , 0 , 11 , 12 ,
次は反復子アダプタ
//C++チュートリアル&リファレンスのP119あたり
//ストリーム反復子 2004/8/22
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
//出力関数
template <typename T>
void print(const std::vector<T> & co, const std::string s ="")
{
std::cout << "要素の出力。要素数:" << co.size() << " : " << s <<std::endl;
copy(co.begin(), co.end(),
std::ostream_iterator<int>(std::cout, " , "));
std::cout << std::endl;
}
int main(void)
{
std::vector<int> co;
copy(std::istream_iterator<int>(std::cin),
std::istream_iterator<int>(), back_inserter(co));
print(co, "");
return 0;
}
実行結果
要素の出力。要素数:3 :
123 , 456 , 789 ,
標準入力の止め方をどうすればいいのかわからなかった。CTRL+R エンターなどと適当にやったらうまくいった。
//C++チュートリアル&リファレンスのP127あたり
//連想コンテナの要素の削除 2004/8/22
#include <iostream>
#include <set>
#include <algorithm>
//出力関数
template <typename T>
void print(const std::multiset<T> & co, const std::string s ="")
{
std::cout << "要素の出力。要素数:" << co.size() << " : " << s <<std::endl;
copy(co.begin(), co.end(),
std::ostream_iterator<T>(std::cout, " , "));
std::cout << std::endl;
}
int main(void)
{
std::multiset<int> co;
for (int i = 11; i < 20; ++i) {
co.insert(i);
}
co.insert(12);
print(co, "初期値");
std::set<int>::iterator ite = find(co.begin(), co.end(), 17);
std::cout << "17 から最後までの距離。distance:"
<< std::distance(ite, co.end()) << std::endl;
co.erase(ite, co.end()); //17から最後まで削除
int num = co.erase(12); //12を削除
std::cout << "削除した要素数:" << num << std::endl;
print(co, "");
return 0;
}
実行結果
要素の出力。要素数:10 : 初期値
11 , 12 , 12 , 13 , 14 , 15 , 16 , 17 , 18 , 19 ,
17 から最後までの距離。distance:3
削除した要素数:2
要素の出力。要素数:5 :
11 , 13 , 14 , 15 , 16 ,
PRINT_ELEMENT関数は今まで作ったprint関数と似ている
//C++チュートリアル&リファレンスのP129あたり
//ストリーム反復子 2004/8/22
#include <iostream>
#include <vector>
#include <algorithm>
//出力関数
template <typename T>
void print(const T & co, const std::string s ="")
{
typename T::const_iterator pos;
std::cout << s;
for (pos = co.begin(); pos != co.end(); ++pos) {
std::cout << *pos << ' ';
}
std::cout << std::endl;
}
int main(void)
{
std::vector<int> co;
for (int i = 11; i < 16; ++i) {
co.push_back(i);
}
print(co, "初期値:");
return 0;
}
std::for_each, std::transform
//C++チュートリアル&リファレンスのP130あたり
//補助関数 2004/8/22
#include <iostream>
#include <vector>
#include <algorithm>
template <typename T>
void print(T e)
{
std::cout << e << ' ';
}
template <typename T>
T square(const T& e)
{
return e*e;
}
int main(void)
{
std::vector<int> co, co1;
for (int i = 11; i < 16; ++i) {
co.push_back(i);
}
std::for_each(co.begin(), co.end(), print<int>);
std::cout << std::endl;
std::transform(co.begin(), co.end(),std::back_inserter(co1), square<int>);
std::for_each(co1.begin(), co1.end(), print<int>);
std::cout << std::endl;
return 0;
}
実行結果
11 12 13 14 15
121 144 169 196 225
//C++チュートリアル&リファレンスのP132あたり
//単項叙述関数 素数の判定 2004/8/22
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstdlib> //abs用
bool isprime(int num)
{
num = abs(num);
if (num == 0 || num ==1) {
return true;
}
int div;
for (div = num/2; num%div != 0; --div) {
;
}
return div == 1;
}
int main(void)
{
std::vector<int> co;
for (int i = 24; i <= 30; ++i) {
co.push_back(i);
}
std::vector<int>::iterator pos = std::find_if(co.begin(), co.end(), isprime);
if (pos != co.end()) {
std::cout << "素数が見つかった:" << *pos << std::endl;
} else {
std::cout << "素数が見つからない:" << std::endl;
}
return 0;
}
実行結果
素数が見つかった:29
//C++チュートリアル&リファレンスのP133あたり
//二項叙述関数 変なソート 2004/8/22
#include <iostream>
#include <vector>
#include <algorithm>
bool islarge(int num1, int num2)
{
//変な比較関数
//比較要素が2つとも50より大きければ昇順に比較。それ以外ならば降順に比較
return num1 > 50 && num2 > 50 ? num1 < num2 : num1 > num2;
}
//出力関数
template <typename T>
void print(const std::vector<T> & co, const std::string s ="")
{
std::cout << "要素の出力。要素数:" << co.size() << " : " << s <<std::endl;
copy(co.begin(), co.end(),
std::ostream_iterator<T>(std::cout, " , "));
std::cout << std::endl;
}
int main(void)
{
std::vector<int> co;
co.push_back(56);
for (int i = 45; i <= 55; ++i) {
co.push_back(i);
}
co.push_back(30);
print(co, "初期値");
std::sort(co.begin(), co.end(), islarge);
print(co, "変なソート後");
return 0;
}
実行結果
要素の出力。要素数:13 : 初期値
56 , 45 , 46 , 47 , 48 , 49 , 50 , 51 , 52 , 53 , 54 , 55 , 30 ,
要素の出力。要素数:13 : 変なソート後
51 , 52 , 53 , 54 , 55 , 56 , 50 , 49 , 48 , 47 , 46 , 45 , 30 ,
次回は関数オブジェクト。だんだん迷いの道に入ってきたよ。
//C++チュートリアル&リファレンスのP134あたり
//関数オブジェクト 2004/8/23
#include <iostream>
#include <vector>
//関数オブジェクト
class CPrint {
public:
void operator() (int e) const { //括弧の定義
std::cout << e << ' ';
}
};
int main(void)
{
std::vector<int> co;
for (int i = 11; i < 16; ++i) {
co.push_back(i);
}
std::for_each(co.begin(), co.end(), CPrint());
std::cout << std::endl;
return 0;
}
呼び出し時には括弧をつけている
//for_eachの定義
namespace std {
template <class Iterator, class Operation>
Operation for_each (Iterator act, Iterator end, Operation op)
{
while (act != end) {
op (*act);
}
return op;
}
}
//C++チュートリアル&リファレンスのP136あたり
//関数オブジェクト テンプレート引数に数値を代入??? 2004/8/23
//コンパイル時に加算する数値がわかっている場合 2004/8/23
#include <iostream>
#include <vector>
//関数
template <int val>
void add(int & num)
{
num += val;
}
//関数オブジェクト
class CPrint {
public:
void operator() (int e) const { //括弧の定義
std::cout << e << ' ';
}
};
int main(void)
{
std::vector<int> co;
for (int i = 11; i < 16; ++i) {
co.push_back(i);
}
std::for_each(co.begin(),co.end(), CPrint());
std::cout << std::endl;
std::for_each(co.begin(),co.end(), add<10>); //10を加算
std::for_each(co.begin(),co.end(), add<20>); //20を加算
std::for_each(co.begin(),co.end(), CPrint());
std::cout << std::endl;
return 0;
}
実行結果
11 12 13 14 15
41 42 43 44 45
//C++チュートリアル&リファレンスのP134あたり
//関数オブジェクト 2004/8/23
#include <iostream>
#include <vector>
//関数オブジェクト 加算
class AddValue {
private:
int val;
public:
AddValue(int v):val(v) { } //コンストラクタで初期化する。
void operator() (int& e) {
e += val;
}
};
//関数オブジェクト 出力
class CPrint {
public:
void operator() (int e) const { //引数はコンテナの値になる。
std::cout << e << ' ';
}
};
int main(void)
{
std::vector<int> co;
for (int i = 11; i < 16; ++i) {
co.push_back(i);
}
std::for_each(co.begin(), co.end(), CPrint());
std::cout << std::endl;
//AddValue::operator()(*act) *actはco.beginでインクリメントしていく
//AddValue(10)は一時オブジェクトのコンストラクタ。
std::for_each(co.begin(), co.end(), AddValue(10));
std::for_each(co.begin(), co.end(), CPrint());
std::cout << std::endl;
return 0;
}
std::for_each(co.begin(), co.end(), AddValue(10));
は、一時オブジェクトを使わないで、
AddValue ad(10);
std::for_each(co.begin(), co.end(), ad);
のように書ける。
//C++チュートリアル&リファレンスのP141あたり
//関数オブジェクト 2004/8/23
#include <iostream>
#include <set>
#include <deque>
//関数オブジェクト 出力
class CPrint {
public:
void operator() (int e) const { //引数は、コンテナの値になる。
std::cout << e << ' ';
}
};
//出力関数
template <typename T >
void print(const T & co, const std::string s ="")
{
std::cout << "要素の出力。要素数:" << co.size() << " : " << s <<std::endl;
copy(co.begin(), co.end(),
std::ostream_iterator<T::value_type>(std::cout, " , "));
std::cout << std::endl;
}
int main(void)
{
std::set<int, std::greater<int> > co;
std::deque<int> cot;
for (int i = 1; i <= 9; ++i) {
co.insert(i);
}
print(co, "初期値");
//正負の変換
transform( co.begin(), co.end(),
back_inserter(cot), std::negate<int>());
print(cot, "正負の符号反転");
cot.clear();
//変換元のコンテナの値を最初の引数、2番目の引数とする。(二乗)。
transform( co.begin(), co.end(), co.begin(),
back_inserter(cot), std::multiplies<int>());
print(cot, "multiplies計算後:二乗a*a");
cot.clear();
//変換元のコンテナの値を最初の引数、10を2番目の引数とする。
transform( co.begin(), co.end(),
back_inserter(cot), bind2nd(std::multiplies<int>(), 10));
print(cot, "multiplies計算後:10*a");
//変換元のコンテナの値が70と等しいものを42に変換。
replace_if( cot.begin(), cot.end(), bind2nd(std::equal_to<int>(), 70), 42);
print(cot, "70を42に変換");
return 0;
}
実行結果
要素の出力。要素数:9 : 初期値
9 , 8 , 7 , 6 , 5 , 4 , 3 , 2 , 1 ,
要素の出力。要素数:9 : 正負の符号反転
-9 , -8 , -7 , -6 , -5 , -4 , -3 , -2 , -1 ,
要素の出力。要素数:9 : multiplies計算後:二乗a*a
81 , 64 , 49 , 36 , 25 , 16 , 9 , 4 , 1 ,
要素の出力。要素数:9 : multiplies計算後:10*a
90 , 80 , 70 , 60 , 50 , 40 , 30 , 20 , 10 ,
要素の出力。要素数:9 : 70を42に変換
90 , 80 , 42 , 60 , 50 , 40 , 30 , 20 , 10 ,
transform(変換元の最初, 変換元の最後, 変換元2の最初, 変換先, 演算)
transform(変換元の最初, 変換元の最後, 変換先, 演算)
2つでてきた。
bind2ndとか出てきて難しい。じっくり!
bind2ndで呼び出される演算関数は引数2つに対応してないといけないんだな。
セマンティックスの意味を調べてみた。
semantics 意味論。反意語は、シンタックス(xml)
それぞれのトランザクションが、どのような処理を行うかを、トランザクションの「セマンティックス(意味論)」と呼ぶことがあります。
基本データ型と同じセマンティックス(この場合「意味」というよりも「動作」に近い感じがする。)
STLの標準のライブラリでは、参照のセマンティックスはサポートされていない。
参照のセマンティックスをサポートするには、参照カウンタ付のスマートポインタを使うと良い。
スマートポインタによって値の変更が行われる場合。要素の順序が不正になる可能性がある。これは、望ましくない。
auto_ptrはコピーを行わないので、つかえない。
STLの設計上の目標は、最大のセキュリティより最大のパフォーマンスである。
エラーのチェックには時間がかかるため、エラーチェックはほとんどない。
STLの模範的バージョンにSTLportがある。
標準で、例外を送出する場合があることを要求される関数は、vectorとdequeのat()だけで、[]の演算子をチェックする。
そのほかには、bad_allocなどの通常の例外が起こる可能性があることを要求している。
ノードをベースとするコンテナは、要素の構築に失敗した場合、コンテナは前の状態のままになる。
単独および複数の要素の消去は、成功が保証されている。
vector, dequeは、要素の構築に失敗した場合、前の状態に復旧しない。
復旧するには、挿入する場所以降の要素をコピーしなければならない。
ノードをベースとするコンテナは、要素の構築に失敗した場合、コンテナは前の状態のままになる。
単独および複数の要素の消去は、成功が保証されている。
要素のコピーを作ることは、常にコストが高くなる。
STLにないコンポーネントは、ハッシュテーブルとして実装されるコンテナ
5章が終了・・・次回は6章
| デフォルトコンストラクタ | 空のコンテナ生成 |
| コピーコンストラクタ | 引数はコンテナ |
| 範囲指定のコンストラクタ | co(begin, end) :[begin, end)をコピーして初期化する。 |
| デストラクタ | |
| size() | |
| empty() | 空なら1を返す。 (return size()==0)より、パフォーマンスが良いこともある。 |
| max_size() | 格納可能な最大の要素数。vectorで1073741823になった。要素の予約、確保とは関係ない模様。 |
| 比較演算子と代入演算子 ==, !=, <, >, <=, >=, = |
1.両方のコンテナは同じ型。 2.2つのコンテナの要素が等しくかつ同じ順序であれば等しい。等価性(==演算子)。 3.小さいかどうかは辞書順のチェックを行う。 |
| co1.swap(co2)、swap(co1, co2) | co1と、co2の値を交換。 |
| begin(), end(), rbegin(), rend() | イテレータを返す。 |
| insert(pos, elem) | elemのコピーをposに挿入する。 |
| erase(begin, end) | [begin,end)を削除 |
| clear() | すべての要素を削除し、コンテナを空にする。 |
| co.get_allocator() | コンテナのメモリモデルを返す。 |
//正しい
std::deque<int> c((std::istream_iterator<int>(std::cin)),
(std::istream_iterator<int>()));
//あやまり。関数宣言になる。
std::deque<int> c(std::istream_iterator<int>(std::cin),
std::istream_iterator<int>());
下は、dequeが戻り型、1番目の引数の名前がcin、2番目の引数の名前がない、関数になる。//C++チュートリアル&リファレンスのP158あたり
//vector 2004/8/24
#include <iostream>
#include <vector>
//出力関数
template <typename T>
void print(const T & co, const std::string s ="")
{
std::cout << s <<std::endl;
copy(co.begin(), co.end(),
std::ostream_iterator<T::value_type>(std::cout, " , "));
std::cout << std::endl;
}
int main(void)
{
std::vector<int> co; //デフォルトコンストラクタ
std::cout << "代入前。empty:" << co.empty() << std::endl;
std::cout << "代入前。max_size:" << co.max_size() << std::endl;
std::cout << "代入前。capacity:" << co.capacity() << std::endl;
for (int i = 1; i <= 9; ++i) {
co.push_back(i);
}
print(co, "デフォルトコンストラクタに値を代入");
std::cout << "代入後。empty:" << co.empty() << std::endl;
std::cout << "代入後。max_size:" << co.max_size() << std::endl;
std::cout << "代入後。capacity:" << co.capacity() << std::endl;
std::cout << "size:" << co.size() << std::endl;
return 0;
}
実行結果
代入前。empty:1
代入前。max_size:1073741823
代入前。capacity:0
デフォルトコンストラクタに値を代入
1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ,
代入後。empty:0
代入後。max_size:1073741823
代入後。capacity:256
size:9
//C++チュートリアル&リファレンスのP155あたり
//vector 格納メモリの割り当てswap 2004/8/24
#include <iostream>
#include <vector>
int main(void)
{
std::vector<int> co; //デフォルトコンストラクタ
co.reserve(8);
std::cout << "代入前。capacity:" << co.capacity() << std::endl;
for (int i = 1; i <= 9; ++i) {
co.push_back(i);
}
std::cout << "9個代入後。capacity:" << co.capacity() << std::endl;
std::cout << "size:" << co.size() << std::endl;
std::vector<int>(co).swap(co);
std::cout << "swap後。capacity:" << co.capacity() << std::endl;
return 0;
}
実行結果
代入前。capacity:8
9個代入後。capacity:16
size:9
swap後。capacity:9
この本はリファレンスがわかりやすくまとまってていいなあ。全部写そうかと思ったけど、大変そうだったので、やめた。
| co.flip() | bool型の要素の全てを反転させる。(すべてのビットの補数を求める。) |
| m[idx].flip() | インデックスの要素のビットを反転させる。 |
| m[idx] = val; | インデックスの要素にvalを代入する。(1ビット) |
| m[idx] = m[idx2] | インデックスの要素にm[idx2]を代入する。 |
| co.unique(); | 同じ値を持ち、連続する要素が重複する場合。重複を取り除く。 |
| c1.splice(pos, c2); | c2のすべての要素をc1の、posの位置の手前に移す。 |
| sort(); | <演算子でソート。 |
| c1.merge(c2); | ソートされている2つこのコンテナでc2の要素を全てc1にマージする。 |
| reverse() | すべての要素の順序を逆転させる。 |
検索が速い。2分木を用いて実装されることが多い。
要素の値を直接変更できない。反復子によるアクセスは定数になる。
if (!(elem1 < elem || elem2 < elem1))
2004/8/26図書の返却期限になったのでP185までで一旦終了。
//C++チュートリアル&リファレンスのP187あたり
//set 2004/9/11
#include <iostream>
#include <set>
int main(void)
{
std::multiset<int> co;
for (int i = 1; i < 8; ++i) {
co.insert(i);
}
co.insert(3);
co.insert(3);
std::cout << "count(3):" << co.count(3) << std::endl;
std::cout << "lower_bound(3):" << *co.lower_bound(3) << std::endl;
std::cout << "upper_bound(3):" << *co.upper_bound(3) << std::endl;
std::cout << "equal_range(3):" << *co.equal_range(3).first << " "
<< *co.equal_range(3).second << std::endl;
return 0;
}
*upper_boundは次に大きい要素が入っている位置の要素を返しているから気をつけて!
Generic Programmingに書き込むようにした。
| メンバ関数 | 戻り値 |
|---|---|
| get() | int(読み取った文字orEOF) |
| get(char& c) | istream& |
| メンバ関数 | 読取り終了条件 | 文字数 | 終端文字 | 戻り値 |
|---|---|---|---|---|
| get(s,num) | 改行またはEOFの手前 | num-1 | 付加する | istream& |
| get(s,num,t) | tまたはEOFの手前 | num-1 | 付加する | istream& |
| getline(s,num) | 改行またはEOFを含む | num-1 | 付加する | istream& |
| getline(s,num,t) | tまたはEOFを含む | num-1 | 付加する | istream& |
| read(s,num) | EOF | num | 付加しない | istream& |
| readsome(s,num) | EOF | 最大num | 付加しない | streamsize (文字数) |
| メンバ関数 | 機能 | 戻り値 |
|---|---|---|
| gcount() const | 最後に行った書式無しの読み取り演算で、 読み取られた文字数を返す。 |
streamsize |