C++

用語

インスタンス

実体。ある型のオブジェクトを生成したことを「インスタンスを生成した」という。

インターフェイス(関数)

データへの直接アクセスや、 管理できないデータアクセスを避ける為に使う。

インターフェイス関数により、オブジェクトが正しく使われることを保証する為に必要な制御を行なえる。

インライン関数

インライン関数を使うと、その関数の呼び出しを記述している位置に、 その関数本体のコピーが置かれる。

C言語での#defineマクロの代替。

クラス内で定義された関数は自動的にインライン関数になる。

virtual

仮想基本クラス

class classA {
 //
};

class classB : virtual public classA {
 // 仮想基本クラスとしてclassAを継承
};

class classC : virtual public classA {
 // 仮想基本クラスとしてclassAを継承
};

class classD : public classB, public classC{
 //
};

仮想関数

基本クラスのポインタと参照で、 派生クラスのオブジェクトを操作できる。

基本クラスポインタ(参照)を仮引数にした関数を作れば、全ての派生クラスオブジェクトを受け取ることができる。
静的結合(static binding)
非仮想関数では、実態が派生クラスだろうが基底クラスだろうが、その入れ物を示す ポインタや参照の型を見て、コンパイラがどちらのクラスの関数を呼び出すかを決めます。
動的結合(dynamic binding)
仮想関数では、コンパイラがどちらのクラスの関数を呼び出すのか決める のではなく、実行時にポインタや参照が、その実態は派生クラスなのか基底クラスなのかを調べ、 動的にどちらの関数を呼び出すかを決定します。

基底クラスの関数が仮想関数の場合、その派生クラスでオーバーライドする 関数に「virtual」を付けなくても、自動的にその関数も仮想関数となる。 しかし、一般的に省略せずに、派生クラスでも「virtual」を 付ける場合が多い。

virtual_function0.png

chk()が非仮想関数のとき、クラス型は静的に決定される。今の場合、ポインタpaはclsA型で宣言されているので、pa->chk()はclasA型で実行される。

chk()が仮想関数のとき、クラス型は動的に決定される。今の場合、ポインタpaはclsA型で宣言されているが、派生クラスのclsB型をさしているので、pa->chk()はclsB型で実行される。

純粋仮想関数

仮想関数は、オブジェクトの型に応じて、クラスのメンバ関数を選択してくれる。 上の例では、

  • 基本クラスのchk()関数
  • 派生クラス1のchk()関数
  • 派生クラス2のchk()関数
  • ・・・ を適切に選択してくれる。

基本的に

基本クラスは本当に基本形として使うだけで、実際のオブジェクトは派生クラスしか使わない

ことが多い。

上の場合、基本クラスのchk()関数が未使用になることになる。 これは

  • 基本クラスのメンバ関数にアクセスできるのは危険
  • エレガントでない の理由により、避けるべきである。

そこで、

  • 基本クラスのchk()はvirtualにしてプロトタイプ宣言だけする(本体は存在しない)
  • chk()の本体は、派生クラスの中で必ずする。 方法がとられる。これを純粋仮想関数という。
class clsA {
protected:
 int a ;
public:
 clsA(int n){ a = n ; }
 virtual void chk() = 0 ;
};

class clsB : public clsA {
protected:
 int b ;
public:
 clsB(int n1, int n2) : clsA(n1) { b = n2 ; }
 virtual void chk() ;
};

void clsB::chk()
{
 //
}

純粋仮想関数を含むクラスは不完全である為、オブジェクト宣言はできない。

clsA oba(100) ; // 不可
clsA *pa ;      // 可

ポインタ宣言はオブジェクト本体を確保するものではないので、可能である。

抽象クラス

純粋仮想関数を一つでも含むクラスを抽象クラスという。

具体的な記述の欠けている、抽象的なクラス

抽象クラスである、派生クラスを作るときは、 必ず仮想関数の関数実態部を記述する必要がある。 そうしないと、 その派生クラスもまた抽象クラスとなる。

純粋仮想関数の効用の一つに

実装の強要

がある。

オーバーロード/オーバーライド

C++は仮引数の型あるいは個数が異なるなら、 同じ名前の関数をいくつも定義することができる。 これを関数のオーバーロード(overload)という。

また基本となる関数があり、その関数の名前と仮引数構成を維持したまま、 まるで上書きするように、新しい機能の関数を再定義する機能がある。 これを関数のオーバーライド(override)という。

override上書き基底クラスの関数を派生クラスで書き換える
overload多重定義関数の仮引数の数と型

継承に於けるオーバーライド

基底クラスの関数を派生クラスで書き換える(横取りする、上書きする)ことをオーバーライド と言う。 つまり基底クラスのある関数と同じ名前、同じ引数、同じ戻り値で、中身の違う 関数を派生クラスで定義することを言う。 仮想関数とは、オーバーライドをするための メカニズムである。 つまり基底クラスの動作または機能をあとから作成した派生クラスに よってカスタマイズする手段を提供する。

オブジェクト

広義。

データを書き込んだり、そのデータを参照したりできる、名前のついた記憶領域のこと。

オブジェクトのある場所をアドレスといい、 オブジェクトを識別する名前が変数。

オブジェクトコード

機械語の形式となったコード

OOP(Object-Oriented Programing)

オブジェクト指向プログラミング。

オブジェクト指向プログラミングとは相互にメッセージ(message)を送りあうオブジェクト(Object)の集まりとしてプログラムを構成する技法である。

この技法をサポートするプログラミング言語をオブジェクト指向プログラミング言語(OOPL)と呼ぶ。

オペレータ、オペランド

operator=演算子とは演算を指示するもの。

operatorのかかる対象をoperand=作用対象という。

a + 2

「+」がオペレータ、「a」と「2」がオペランド。

カプセル化

カプセル化とは、抽象化の実装の詳細にユーザがアクセスできないようにすること。

仮引数と実引数

int func( int a) ; // aは仮引数

int main()
{
  int a = 100 ;
  func( a) ;       // aは実引数
 ..........
return 0 ;
}

クラス

クラスはユーザーが定義できる型。 従来の構造体とは異なり、 クラスにはデータとそのデータを操作する関数を含ませることができる。

「機能の集合」

このデータをデータメンバ(メンバ変数)、 関数のことをメンバ関数という。

この為、クラスはそれ自体でひとつのまとまった機能を実現する。

データに直接アクセスさせず、関数を通してのみアクセスを許す(インターフェイス)クラス構造を作り、 そのメンバ関数の記述を工夫することにより、データの安全性を高める。

C++オブジェクト指向プログラミングでは、「独自の振る舞いを持つデータ型」を使って、プログラミングを行なう。 それ自体が独自の振る舞いを持つデータ型とは、クラスのことである。 「クラス型」を使って宣言した変数こそが「オブジェクト指向プログラミング」のオブジェクトに他ならない。

C++ OOPではもはやCの様にその都度手続きを記述することなく、 クラスの定義の中に組み込まれた手続き(つまりメンバ関数)を呼び出すだけでプログラムを進めていける。 その際の中心的な役目を果たすのがオブジェクトである。

つまり、C++ OOPでは「クラスからオブジェクトを作成し、オブジェクトを操作する」という方法でプログラミングを行なう。

C言語での構造体とは「メンバ関数の無いクラス」である。

class classname{
 public:
   ・・・・・・・・
 private:
   ・・・・・・・・
};
class_struc.png

C++ではグローバルな関数を定義しないことと、 関数を主に「クラスのメンバ関数として定義する」ことが重要。

継承

基準となるクラスが既に存在し、それに少し機能を加え新しいクラスを作成したいときに、最初から新しいクラスを作るのではなく、モデルとなるクラスを土台にして新クラスを作成手段を、継承(inheritance=インヘリタンス)という。

元になるクラスを「基本クラス」、機能を追加して作成される新クラスを「派生クラス」という。

  • 基本的な機能を全て含んだ基本クラスを先に作っておく。
  • ユーザーはその基本クラスを利用して、ユーザーが必要な個別機能を追加して用いる

継承図

継承の階層関係をグラフで表した物を継承図という。 継承図は、「閉路を持たない矢印つきグラフ」で「有向非循環ず(DAG: Directed Acyclic Graph)と呼ばれる。」

AからBが派生し、更にBからCが派生した例。

グラフの上部に親クラス、下に子クラスが配置される。

class_inheritance0.png

仮想基本クラス

同じ基本クラスを多重に持つクラス

keisyou0.png

解決

class classA {
 //
};

class classB : virtual public classA {
 // 仮想基本クラスとしてclassAを継承
};

class classC : virtual public classA {
 // 仮想基本クラスとしてclassAを継承
};

class classD : public classB, public classC{
 //
};
keisyou1.png

継承クラス間のポインタ変換

クラスオブジェクトは、同型のポインタで指し示すことができる。 あるクラス方のポインタを使って、まったく別のクラス型オブジェクトを指し示すことはできない。

しかし、継承関係にあるクラス間には多少の融通が利き。

class classB : public classA {
//
};

classA oba, *pa ;
classB obb, *pb ;

pa = &oba ;
pb = &obb ;

pa = pb ;   // OK
pa = &oba ; // OK

pb = pa ;   // NG
pb = &oba ; // NG
理屈
obbオブジェクト(classB)内にある、classAオブジェクトをさす。

継承クラス間の参照変換

継承クラス間でポインタ変を変換できるように、 参照を変換することも可能である。

つまり基本クラスの参照変数を使って、派生クラスオブジェクトを参照することができる。

この機能は、参照仮引数を使う関数呼び出しに良く使われる。

記憶クラス

識別子

記憶クラス指定子

auto
static
extern
register

局所変数(ローカル変数)

関数内で定義され、その関数内のみで参照できる。

  • auto
  • register
  • 関数内宣言のstatic

大域変数(グローバル変数)

関数外で定義され、どの関数からも参照できる。

  • extern
  • 関数外宣言のstatic

自動変数(auto)

関数の中で定義して、その関数の中だけで使用できる。 関数がコールされるとメモリ上に配置される。 その関数の処理が終わったら変数は消滅する。

静的変数(static)

関数の中で定義して、その関数の中だけで使用できる。 プログラムが起動されるとメモリ領域の一部に常に占有する。 その関数の処理が終わっても変数は消滅せず、その値を何時までも保持している。

  • 値を保持する(記憶の永続性)
  • 変数のプライベート化

外部変数(extern)

関数外で定義され、どの関数からでも参照できる大域変数である。 プログラムが起動されるとそのの変数用のメモリ領域を常に占有する。 その関数の処理が終わっても変数は消滅せず、 その値を何時までも保持している。

外部変数をあえて使うのは以下の場合に限るのが良い。

  • プログラム全体を統括する変数
  • プログラム全体の状況を記憶する変数

例:

  • プログラムが現在どのモードで走っているかを示す変数。
  • それまで発生したエラーの総数を記憶しておく変数。
外部変数の定義
その関数の性質を外部変数とする。そして実際に記憶領域を割り当てる。識別子「extern」を付けない。外部変数の実態定義。
外部変数の宣言
その変数の性質を外部変数とする。しかし記憶領域は割り当てない。どこか他のモジュールでグローバル変数として識別子を付けず定義されている。外部変数の参照宣言。

識別子「extern」を付ける。

グローバルなstatic

静的変数は大域変数としても使うことができる。 通常、関数内では「staticが付けば静的、付かなければ自動変数」であるが、 大域変数の場合、「staticが付けばグローバルな静的変数、付かなければ外部変数(extern)」という区別になる。

レジスタ変数(register)

関数内で定義され、その関数内だけで使用できる。 その関数がコールされると可能ならレジスタに割り当てられる。 その関数の処理が終わったら変数は消滅する。 機能は自動変数と同じ。

最近のコンパイラは自動的に、独自にレジスタを巧みに使う「速いコード」を作成するようにできている。 よって人間が意図してregisterを使うことは少なくなっている。

キャスト

明示的型変換

構造体

  • C++では構造体名は型名を表す。
    struct node{
     char name[100] ;
     node *pnode ;
    };
  • 一方、Cではタグ名に過ぎない。
    struct node{
     char name[10] ;
     struct node *pnode ;
    };

コンストラクタ、デストラクタ

コンストラクタは特殊なメンバ関数。 クラスオブジェクト宣言時に実行される。 よって初期値を設定する際に使われる。

引数の数を変えて、多重定義することができる。

デストラクタは、コンストラクタの逆。 あまり使われない。

デフォルトコンストラクタ

引数無しで呼び出される形式のもの。必ず用意する。

コピーコンストラクタ

宣言時に他のオブジェクトの値をコピーする。

classname( const classname &obj){
 x = obj.x ;
}

単純にコピーするコンストラクタは準備する必要は無い。

特別な処理をするようなときだけ宣言する(変数xはコピーしたいが、変数yはゼロに初期化したいなど)。

参照

オブジェクトを表現する機能として、変数とポインタがある。 C++ではこれらに加えて参照と呼ばれる機能がある。

参照は簡単にいって、「オブジェクトの別名」に過ぎない。 機能的にはポインタと同様の物であり、 関数の仮引数に用いると絶大な効果を発揮する。

参照とポインタの比較

  1. 参照のアドレス設定は初期化のときだけである。 ポインタはいつでもアドレス設定ができる。
  2. 参照と初期化したオブジェクトアドレスとの結合は、 プログラムが終了するまで解かれることは無い。 ポインタはいつでも設定変更できる。
  3. ポインタはアドレスと値とを操作できるが、 参照は値を操作できるだけである。 参照自身を操作する演算子は存在しない。
int dt ;
int &rf = dt ;
int *pt = &dt ;

rf = 100 ;  //これは dt=100 と同じ。
*pt = 200 ; //ポインタを使って値を操作。

rf = &dt ;  //エラー
pt = &dt ;  //ポインタはアドレスの変更もできる。
pt = &rf ; //これは pt=&dtとみなされる。
            //参照自身のアドレスは取得できない。

参照は定義時以外では、アドレス参照演算子、間接参照演算子を必要としない。

  • ポインタ
    int *pfunc(int *pn)
    {
     *pn += 150 ;
     return pn ;
    }
int dt = 100 ;
cout << *pfunc( &dt) << "\n" ;
  • 参照
    int &rfunc(int &n)
    {
     n += 100 ;
     return n ;
    }
int dt = 100 ;
cout << rfunc( dt) << "\n" ;

ジェネリック

C++では「ジェネリックな関数」、「ジェネリックなクラス」という表記を使うことがある。ジェネリック(generic)=汎用、であり、 「色々な型に対応する」という意味。

スコープ

有効範囲

ストリーム

データの入出力をC++ではストリームという概念で操作する。 ストリームはデータの入出力を処理し、 また入出力に伴う状態の設定・保持を行なう。

ストリームを使うには、入力用または出力用ストリームオブジェクトを宣言し、 論理デバイスと物理デバイスを結び付けてやる必要がある。

こうして両者の結びつきができると、 後はストリームオブジェクト名(cinやcout)を使うことで、ユーザーは簡単にデータ入出力をすることが可能となる。

又、ストリームを使って処理する為、 キーボードやディスプレイやファイルなどの異なる物理デバイスに対して、 統一された方法で操作することが可能、というメリットがある。

コンソール

#include <iostream>
cin
cout

ファイル

#include <fstream>
ifstream fin ;
ofstream fin ;
fstream fboth ;

文字列

#include <sstream> 
istringstream stin ;
ostringstream stout ;
stringstream stio ;

抽象化、具象化

トークン

意味のある文字又は、文字列。

トークン種類
演算子* / t - 等
分離子() {} [] . ; :
識別子変数名や関数名など
予約語if for long 等
定数100 12.345 0xff3a 'a' "abcd"

データの隠蔽

メンバ変数・関数が非公開状態になっている状態を、 「隠蔽された」という。

テンプレート

関数やクラスを、どのデータ型にも対応できるように特殊なスタイルで記述したものを、テンプレートと呼ぶ。 テンプレートを使って記述された関数を汎用関数(generic funtion)、 同じくクラスを、汎用クラス(generic class)と呼ぶ。

特殊文字

ヒープ

ヒープ=heap。 自由メモリ領域。

プリプロセッサ

C++言語はコンパイルに先立って、色々なサービス処理を行なってくれる機能がある。 これを「プリプロセッサ」と呼ぶ。

  • 他のプログラムを併合する
  • 字面上のテキストの置換を行なう
  • コンパイラにコンパイル条件を与える
  • コンパイラにその他の情報を与える

ファイルの挿入 #include

#include <filename>

とすると、あらかじめ設定された標準ディレクトリからファイルを探す。

#include "filename"

とすると、先ずはカレントディレクトリや指定されたディレクトリを探し、 そこに無いときは標準ディレクトリを探す。

#includeにはネストが許されている。

この二種類のファイルして指定法の目的は、 コンパイラ付属の標準ヘッダファイルと、 自作のヘッダファイルとを混在させない為である。

標準のヘッダは標準のディレクトリに置き、そこを聖域扱いし、他のファイルと混在しないようにする。

この標準ヘッダを使うときだけ

#include <filename>

を使う。自作のヘッダファイルは自作のソースファイルと同じ扱いにして、 現在のディレクトリや、特定のディレクトリに置くと管理しやすくなる。 このファイルを指定するときには、

#include "filename"

を使う。

フレンド

フレンド関数を使うと、クラスと無関係の一般的な関数から、 非公開データメンバをアクセスできるようになる。

あるクラス内でフレンド宣言された関数はそのクラスの真のメンバーではない。

どこかで定義されたこの関数は、一般関数であるが、 フレンド宣言されることにより、特別に非公開メンバにアクセスできる。

ポインタ

ポインタとはアドレスを保持する変数である。 従来の変数に加えてポインタ機能を許すと、 両者の整合をとる為、次の処理が可能でなければならない。

  1. 変数のアドレスを取得し、ポインタに代入する-> &演算子
  2. ポインタの示すアドレスにある値を取り出し、変数に入れる-> *演算子

又、ポインタは変数と情報を交換できなければならない。 つまり型の情報が必要。

ここで、

char *cp ;

と書いて、「cpはポインタである」と解釈させるのは自然ではない。

「*cp」という記述が「char」型であるなら、間接参照演算子「*」を取り去った「cp」はcharをさすポインタでなければならない。
宣言データ参照アドレス参照
int a;a&a
int *a;*aa

ポインタの初期化

変数のアドレスを&演算子で代入する

int a ;
int *b ;

b = &a ;
int a ;
int *b = &a ;

この二つは同じ。二つ目は、

データ型 *ポインタ名=初期値アドレス ;

の形。

又ポインタにはNULLを指定することができる。 意味は「意味のあるアドレスがまだ設定されていない」特別な状態を示す。

int *p ;
p = 0 ;

配列とポインタ

配列名はポインタである。

int a[5] ;

と宣言した場合、「a」は「a[0]〜a[4]の先頭アドレス」を示す定数である。 又、

&a[2]

は、「a[2]」のアドレスである。

ポインタのアドレス計算

int a[10] ;
int *ptr ;

ptr = a ;
ptr++ ;

ポインタに1加えることは、アドレスを1番地進めるという意味ではない。 「ptr++」は一要素分アドレスをすすめることである。 アドレスがどれだけ進むかは、ポインタの型による。

優先順位

while( *a++ = *b++ )
    ;

後置インクリメントの結合が強い。 ポインタbの中身をaの中身に代入し、お互いアドレスを1要素分すすめる。 「*(a++)」と書いた方が分かりやすい。

一般に、

  • ポインタ演算子は算術演算子より結合が強い
  • 単項演算子は右から左に評価される である。
表記その解釈コメント
*p+1(*p)+1値を+1する
*(p+1)要素を+1進める
*p+=1(*p)+=1*pを+1する
*p++*(p++)要素を+1進める
(*p)++値を+1する
*++p*(++p)要素を+1進める
++*p++(*p)値を+1する

ポインタと文字列

char a[] = "abc" ;
char *ptr = "abc" ;

配列aには「abc」が直接代入される。 一方ポインタの方は、先ず「abc」がアドレスのどこかに配置され、 その先頭アドレスが、ptrに代入される。

char s1[10], s2[]="abc" ;
char *p1, *p2="xyz" ;

strcpy( s1, s2) ;
p1 = p2 ;

上記の場合、配列s1には、文字列「abc」が実際にコピーされるが、 ポインタp1には、「xyz」をさす番地の値が代入される。

ポインタのポインタ

宣言説明
char a;「a」はchar型変数である
char *b;「*b」がchar型である。「*」をとった「b」はchar型を指すポインタである
char **c;「**c」がchar型である。「*」をとった「*c」はchar型の値を指すポインタである。「**」をとった「c」はそのポインタを指すポインタである

仮引数

#include <iostream>
using namespace std ;

int main( int argc, char **argv)         // char *argv[]
{
 if( argc<=3 )
   return 1 ;

 cout << "argv0=" << *(argv+0) << "\n" ; // argv[0]
 cout << "argv1=" << *(argv+1) << "\n" ; // argv[1]
 cout << "argv2=" << *(argv+2) << "\n" ; // argv[2]
 cout << "argv3=" << *(argv+3) << "\n" ; // argv[3]

 return 0 ;
}
./hoge aaa bbb ccc
argv0=./hoge
argv1=aaa
argv2=bbb
argv3=ccc

関数を指すポインタ

int (*fptr)() ;

の意味は

「*fptr」という表記がint型を返す関数を意味するのだから、「fptr」はそれへのポインタである。

と理解できる。一方

int *fptr() ;

は、

int型ポインタを返す「fptr関数」

の意味である。

関数を指すポインタの利用

.............
void func1(int dt) ;
void func2(int dt) ;
.............
int main()
{
  void (*fptr)( int dt) ;

 fptr = func1 ;
 fptr(100) ;

fptr = func2 ;
fptr(200) ;
...........
return 0 ;
}

実際は

fptr = &func1 ;
(*fptr)(100) ;

であるが、通常は省略しても構わない。

関数へのポインタの配列

void (*fptr[5])(int a, int b) ;

の意味は、

「弐つのint型引数をもち、返り値がvoid型の関数」への入り口アドレスを5つ持つことができるポインタ配列

である。

ポインタを用いて複数の戻り値を持つ関数を実現

#include <iostream>
using namespace std ;

void func( int *a, int *b) ;

int main()
{
 int a=100, b=200 ;
 cout << "a=" << a << "\n" ;
 cout << "b=" << b << "\n" ;

 func( &a, &b) ;
 cout << "a=" << a << "\n" ;
 cout << "b=" << b << "\n" ;

 return 0 ;
}

void func( int *a, int *b)
{
 *a = (*a)*2 ;
 *b = (*b)*2 ;
 return ;
}

ポリモーフィズム

同じ名前(関数)を使って、 異なる処理を実現する概念をpolymorphism=ポリモーフィズムという。

多相性、多態性。

ポリモーフィズムを実現する機能として、 関数オーバーロード、関数オーバーライド、テンプレートといった機能がある。

名前空間

グローバル領域を分割する。 ディレクトリのような物。

複数の同一名前空間は統合されて処理される。

無名の名前空間

グローバルな静的変数の変わり。

グローバルな静的変数=ファイルスコープな変数。 つまり、そのファイル内だけで有効な、静的変数。

C++ではグローバルな静的変数の変わりに、無名の名前空間が推奨される。

namespace{
 int dt = 1 ;
} // セミコロンはいらない。

std名前空間

標準C++ライブラリはstd名前空間に属している。

#include <iostream.h>

は名前空間非対応のヘッダー。

#include <iostream>

は名前空間対応のC++ヘッダー。

#include <stdio.h>
#include <cstdio>

C言語から引き継いだ伝統的なヘッダーファイルの名前は

#include <xxx.h>

#include <cxxx>

にすればよい。

ネスト

nest。プログラムの中に別の小さなプログラム(ループやサブルーチン)を組み込んで入れ子にする。

ネームマングリング(name mangling)

C++ではネームマングリングと呼ばれる、「関数名の自動装飾」機能が働く。

mypow@@YANNN@Z  // name manglingにより装飾されたmypow

C++の関数オーバーロードは、ネームマングリングの仕組みによって支えられている。

メソッド

メソッド(meyhod)とは特定の種類のメッセージの処理方法を記述したものである。

C++ではメソッドはメンバ関数、関数メンバと呼ばれる。

メッセージ

メッセージ(message)とはオブジェクト間の通信でやり取りされる情報である。

リテラル

リテラル(literal)とはソースコードに値を直接表記したもの。

int x = 7 ;
string s = "hello" ;

「7」や「"hello"」がリテラルである。

リンケージ指定

CはC++のサブセットなので、C++プログラム中にC流の記述をすることは可能である。 C言語用にコンパイルされたライブラリをC++で使う為に、C++のコンパイラに明示的に示す必要がある。

extern "C"
{
 #include "hoge.h"
}

始からC,C++の両方をサポートしているようなライブラリの使用に関しては、指定しなくても良い場合がある。

例えばGSLはC++でインクルードされれば自動的に「extern "C"」が付けられるようになっている。

演算子

new/delete演算子

C言語のmalloc/free系関数と同じ役目。 動的メモリ確保。

単純データ

int *dt ;
dt = new int ; //int型オブジェクトを確保
delete dt ;

配列データ

int *dt ;
dt = new int[10] ;
delete [] dt ;

多次元配列

int (*p)[4] = new int[3][4] ;
delete [] p ;
int *p[4] .....

だとint型ポインタが四つ集まった配列。

インクリメント/デクリメント演算子

前置型、と後置型。

  • 前置型はその式全体の処理の前に一を加算(減算)する。
  • 後置型はその式全体の処理の後に一を加算(減算)する。
    a++ ;
    --b ;

カンマ演算子

カンマ演算子は本来一つしか式をかけない所に、 複数の式を書くことを可能にする。

a = 10 ;
b = 20 ;

a = 10, b = 20 ;

と書ける。この式全体の値は一番右の20である。

例1(while loop)

cin >> dt ;

while( dt!=999){
   ........
cin >> dt ;
}
while( cin >> dt, dt!=999){
   ........
}

例2(for loop)

sum = 0 ;
for( i=0; i<100; i++){
  .........
}
for( sum=0, i=0; i<100; i++)
  .........
}

例3(カンマが既に使われている所)

#include <iostream>
using namespace std ;
void function( int dt1, int dt2, int dt3) ;

int main()
{
 int d1 ;
 int d2 ;

 function( 10, ( d1=10, d2=20, d1+d2), 20) ;
.........
return 0 ;
}

スコープ解決演算子

mycls::disp() ;
mycls「の」disp

ポインタ演算子

*ポインタのさしているアドレスの内容を取り出す
&値の格納されているアドレスを取り出す
int a, b ;
int *p ;

p = &a ;
b = *p ;

メンバ参照演算子

.ドット演算子構造体や共用体やクラスが通常の実態表現されたときのメンバ参照に用いる
->アロー演算子構造体や共用体やクラスがポインタとして表現されたときのメンバ参照に用いる
#include <iostream>
using namespace std ;

struct smpst{
   char cc ;
   int aa ;
};

int main()
{
 smpst dt ;
 smpst *p ;

 p = &dt ;

 dt.aa = 1000 ;
 p->aa = 2000 ;
 ..............
 return 0 ;
}

規則

break

  • switch, for, while, do-whileの中で用いられる。
  • switch文の中で用いられると、特定のcaseラベルから始まった実行を打ち切り、そのswitch文全体を終了する。
  • for, while, do-whileループの中で用いられると、そのループ処理を途中で打ち切る。
  • 多重ループ構造の中で用いられると、そのbreak文の存在するループを一つだけ打ち切り、直ぐ外のループに処理を移す。

NULL

C++ではNULLは「0」が推奨される。

string型

string型は、可変長の文字列処理を行なう。 ユーザーは格納される文字列の長さに注意する必要はない。

string型はクラスで提供される物であった、 通常の型とは異なる。

string型の初期化

#include <iostream>
#include <string>
using namespace std ;

int main()
{
  char css[] = "ABCDEF" ;

  string s1 ;
  string s2 = "" ;
  string s3 = css ;
  string s4 = s3 ;
  string s5( 5, 'Z') ;
  string s6 = "abcdefgh" ;
  string s7( s6, 2, 3) ;
  string s8( css+1, 3) ;

  cout << "s1=[" << s1 << "]\n" ;
  cout << "s2=[" << s2 << "]\n" ;
  cout << "s3=[" << s3 << "]\n" ;
  cout << "s4=[" << s4 << "]\n" ;
  cout << "s5=[" << s5 << "]\n" ;
  cout << "s6=[" << s6 << "]\n" ;
  cout << "s7=[" << s7 << "]\n" ;
  cout << "s8=[" << s8 << "]\n" ;

  return 0 ;
}
s1=[]
s2=[]
s3=[ABCDEF]
s4=[ABCDEF]
s5=[ZZZZZ]
s6=[abcdefgh]
s7=[cde]
s8=[BCD]

添え字演算

#include <iostream>
#include <string>
#include <cstring>
using namespace std ;

int main()
{
  string s1, s2, s3 ;
  string s9 = "str_str" ;
  char css[80] = "ch_str" ;
  char ch ;

  s1 = "ccccc" ;
  s2 = css ;
  s3 = s9 ;
  cout << "substitution  : s1=" << s1 << "\n" ;
  cout << "substitution  : s2=" << s2 << "\n" ;
  cout << "substitution  : s3=" << s3 << "\n" ;

  s1 = "sss111" ;
  //strcpy( css, s1) ;       // error
  strcpy( css, s1.c_str()) ; // OK
  cout << "c_str         : s1=" << s1 << "\n" ;
  cout << "               css=" << css << "\n" ;

  s2 = "XXX" ;
  s1 = s1 + "aaa" + "bbb" + "ccc" ;
  s1 = s1 + "ddd" ;
  cout << "plus          : s1=" << s1 << "\n" ;

  s1 = "AAA" ;
  s1 += "BBB" ;
  cout << "+=            : s1=" << s1 << "\n" ;

  if( s1=="AAABBB")
    cout << "s1 is " << s1 << "\n" ;
  if( s1< "AAACCC")
    cout << "s1 < AAACCC\n" ;
  else
    cout << "s1 >=AAACCC\n" ;

  return 0 ;
}
substitution  : s1=ccccc
substitution  : s2=ch_str
substitution  : s3=str_str
c_str         : s1=sss111
               css=sss111
plus          : s1=sss111aaabbbcccddd
+=            : s1=AAABBB
s1 is AAABBB
s1 < AAACCC

stringクラスの入出力機能

#include <iostream>
#include <fstream>
#include <string>
using namespace std ;

int main()
{
  string fname, ss ;
  ifstream fin ;

  cout << "filename=" ;
  cin >> fname ;

  fin.open( fname.c_str()) ;
  if( !fin )
    return 1 ;

  while( getline( fin, ss) ){
    cout << ss << "\n" ;
  }

  fin.close() ;

  return 0 ;
}

関数形式の初期化

int dt = 100 ; // 伝統的な初期化
int dt(100) ;  // 関数形式の初期化 

関数形式の初期化は、クラスの初期化と統一されたスタイルである。

関数戻り値の無視

関数の戻り値を無視することができる。

ch = cin.get() ;
cin.get() ;

これを意図的に示す為に(void)をつける。

(void)cin.get() ;

行の連結

「\」は続く改行文字を無視する。

cout << "a\n" ;

と、

cou\
t << "a\n" ;

は同じ意味。勿論空白にも意味があるので、

cou\
  t << "a\n" ;

は「cou t」の意味になる。

自動型変換

int < unsigned int < long int < unsigned long int < float < double < long double

セミコロン

C++ではセミコロンはマルチステートメント記号ではなく、 文の一部である。 そしてセミコロンは、文の終端マークである。

a = 1000; b = 2000;

注釈

コメントアウト・注釈は

  • 一行
    //
  • 複数
    /*
    
    */

配列名

配列名はそれ自身アドレスである。

char ss[80] ;
char *p = ss ;

多次元配列

後ろから読むと、意味が分かりやすい。

int a[3][10]

配列長10の配列を3つ作る。

ビット処理

シフト処理

シフト対象が正の整数知の場合、

  • 左に1ビットシフトするごとに値は二倍
  • 右に1ビットシフトするごとに値は1/2倍 になる。

ポインタ

ポインタを戻り値にする

戻り値をポインタにする場合は注意が必要。 次のプログラムは、文法的には正しいが、完全に間違ったプログラムである:

char *getstring(void);

int main(void)
{
	char *p;
	
	p = getstring();
	puts( p );  /* 受け取った文字列を出力 */

	return 0;
}

/* 80文字以内の文字列を入力させ、その文字列のアドレスを返す */
char *getstring(void)
{
	char str[81];
	
	puts( "80文字以内の文字列を入力して下さい" );
	fgets( str, 81, stdin );
	
	return str;
}

問題なのは、戻り値として返しているアドレス。このアドレスは、変数strのアドレスである。 変数strはローカル変数なので、 関数が終了した時点で、メモリ上から消えてなくなってしまう。 従って、これから消えてしまう変数のアドレスを返しているわけである。 そのアドレスを受け取った呼び出し側は、何も知らずにそのアドレスをアクセスしてしまう。 すると、そこにはもう変数が無いので、ゴミデータにアクセスする結果となる。

このようなプログラムは、コンパイルが通ってしまう。

ローカル変数のアドレスを返してはならない」ということ。 問題なのは関数を終了したときにメモリから消えてしまうという点にある。 よって、関数を終了しても消えない変数を使えばいい。 すると方法としては、「グローバル変数のアドレスを返すようにする」か「static変数のアドレスを返すようにする」ということになる。