このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。
C 言語では関数の内部で宣言された変数は、その関数の外部(他の関数など)か らはアクセスできません。 これをローカル変数と言います。 一方、関数の外側でも変数を宣言できます。 これをグローバル変数と言います。 グローバル変数へはどんな関数でもアクセスできます。 但し、別のファイルの中にあるグローバル変数にアクセスするには関数のプロ トタイプのように宣言をする必要があります。 グローバル変数を宣言するのがextern 宣言 です。構文は次の通りです。
extern 変数名
これは、グローバル変数であることを宣言しているだけで、変数そのものの宣 言になってません。そのため、一つのソースファイルでグローバル変数の宣言 をする必要があります。 一般に、このような extern 宣言はヘッダファイルに格納します。
但し、グローバル変数の利用には注意が必要です。 グローバル変数は全ての関数からアクセスが可能なので、プログラムミ スなどによりグローバル変数の値がおかしくなった時など、原因の追求が大変 です。プログラム分割をシンプルにしたい立場からも、グローバル変数という 全関数が知らなければならないような情報はない方が良いです。 但し、 C++ などのオブジェクト指向言語ではないので、関数の外部に情報を 蓄えるにはグローバル変数を使う必要があるでしょう。 逆に C++ 言語、Java 言語などのオブジェクト指向言語では、特定の変数とそ れにアクセスする関数をまとめてクラスとして扱えるので、グローバル変数を 使わないようにすべきです。 変数など、不必要な情報へのアクセスを制限することをオブジェクト指向では カプセル化と言います。
なお、グローバル変数と同じ名前の変数をローカル変数として宣言可能です。 おなじ名前になった時は、グローバル変数にはアクセスできず、ローカル変数 にだけアクセスできます。
また、関数の宣言の時に宣言する引数は、値を読み出すことはできますが、そ れは値が与えられたローカル変数なので、書き換えた値は呼び出した側には影 響しません。
次のプログラムがどのように動くか予想し、また、実際に動かして動作を確か めなさい。
#include <stdio.h>
int i;
int a(int i){
i++;
return i;
}
int b(int j){
i++;
return i;
}
int c(int k){
int i;
i=0;
return i;
}
int main(void){
i=1;
printf("%d\n",i);
printf("%d ",a(i));
printf("%d\n",i);
printf("%d ",b(i));
printf("%d\n",i);
printf("%d ",c(i));
printf("%d\n",i);
return 0;
}
C 言語で static 宣言には次の 3 つの用法があります。
ローカル変数に対する static 宣言は、関数を抜けて再度呼び出す際に前回の 値を保持する働きをします。 下記のプログラムにおいて関数 counter 内の static 変数 c は、プログラム が最初動作する際に 0 に初期化されます。 そして、値を返す際に、 c はひとつ増えます。 static 宣言されているため、次回に呼び出される時は、 0 にならずに 1 増 えて c==1 となります。 したがって、プログラムは 0 1 2 3 4 5 6 7 8 9 と出力するはずです。
#include <stdio.h>
int counter(void){
static int c=0;
return c++;
}
int main(void){
int i;
for(i=0;i<10;i++){
printf("%d ",counter());
}
printf("\n");
return 0;
}
通常の関数宣言をすると、他のソースファイルからでもプロトタイプ宣言をす れば利用できる。 しかし、関数宣言において static を付けると、他のソースファイルからその 関数へのアクセスができなくなる。 これは別ファイルからアクセスを禁止したいときに有用である。 さらに、 C 言語では一つのプログラム中で同じ名前の関数は一つしか存在で きないので、他のファイル中の関数の名前と衝突を回避できる。 ヘッダファイルにプロトタイプ宣言を入れない関数は static 宣言をした方が いい。
以下の二つのプログラムを考えます。
---------------staticf1.c----------------
void a(void){} /* 関数宣言 */
void b(void){ /* 関数宣言 */
a();
}
---------------staticf2.c----------------
void b(void); /* プロトタイプ宣言 */
void a(void){} /* 別の関数宣言 */
int main(void){
a();
b();
return 0;
}
これを次のようにコンパイルするとエラーが生じます。
> gcc -c staticf1.c > gcc -c staticf2.c > gcc staticf1.o staticf2.o staticf2.o: In function `a': staticf2.c:(.text+0x0): multiple definition of `a' staticf1.o:staticf1.c:(.text+0x0): first defined here collect2: ld returned 1 exit status >
staticf1.c での関数 a を外部から利用させる意志が無ければ、 a を static 宣言することで解決できます。
グローバル変数に対して static 宣言をすると、外部からの extern 宣言によ るアクセスを禁止できます。 二つの関数の共通領域を確保したい状況で、他の関数から共通領域を保護 したい場合などに使えます。 extern 宣言しないグローバル変数には static 宣言をした方が良いです。
---------------staticv1.c----------------
int a=0;
int b(void){
return ++a;
}
int c(void){
return --a;
}
---------------staticv2.c----------------
int a=1234;
---------------staticv3.c----------------
#include <stdio.h>
int b(void);
int c(void);
int main(void){
extern int a;
printf("%d,%d,%d\n",a,b(),c());
return 0;
}
これをコンパイルするとエラーが生じます。
> gcc -c staticv1.c > gcc -c staticv2.c > gcc -c staticv3.c > gcc staticv1.o staticv2.o staticv3.o staticv2.o:(.data+0x0): multiple definition of `a' staticv1.o:(.bss+0x0): first defined here collect2: ld returned 1 exit status >
staticv1.c における a を外部から参照させる意図がない場合、 static 宣言 をすることで解消します。
ある型を別の型として扱いたい時に、一時的に別の型を指定する方法があります。 例えば、整数(int)の値 1 と 2 を考えた場合、個数や合計は整数(int)になり ますが、平均値は実数(double)になります。 C 言語では(整数)/(整数)の結果は整数になってしまうため、割算をする時、 double だと指定する必要があります。そこで、「(型) 式」と書くと小数点以 下を切り捨てられず、結果が double 型になります。
#include <stdio.h>
int main(void){
int a[]={1,2,-1}
int i;
int goukei, kosuu;
double heikin;
kosuu=0;
goukei=0;
for(i=0;a[i]!=-1;i++){
kosuu++;
goukei+=a[i];
}
heikin=(double)goukei / kosuu;
printf("個数=%d, 合計=%d, 平均=%f\n",
kosuu, goukei, heikin);
return 0;
}
C 言語では、変数の値を格納しているメモリの番地を取り扱うことができます。
変数 a に対して、その変数の値のメモリの番地は &a で表します。
また、この番地を変数に代入して処理することもできます。
int 型の変数の番地を扱う変数の型は int * で表します。
int *x;
と宣言すると x は int の変数の番地を取り扱うこと
ができます。
そして int y;
と整数型変数 y を宣言しておくと、
x = &y
で y の番地を代入できます。
また、 *x とするとこれは x に格納されているメモリの番地に入っている値
を表します。
もし y の値が 1 ならば、*x も 1 になります。
このメモリの番地を扱う変数を ポインタ と言います。
このメモリの番地やポインタには様々な利用法があります。
なお、「どの変数やメモリも指していない無効な番地」を意味する定数として NULL が定義されています。 関数においてポインタの値の計算を失敗した時や、番兵などに使用できます。
関数を呼び出す時、引数の処理のしかたには次の 3 種類があります。
C 言語では値呼び出しだけがサポートされています。 また、C 言語では関数の中で宣言された変数はローカル変数です。 したがって、ある変数の値を表示する関数は何の工夫もなしに作ることができ ますが、その変数の値を変更するようなプログラムは何の工夫もなくは作れま せん。 例えば次のような関数に意味はありません。
int f(int x){
x=1;
return 0;
}
この関数を f(y) と呼び出しても、 y の値は変わりません。
さて、関数で変数の値を変えるにはどうすればよいでしょうか?
もし、一つの変数の値だけなら、 a=f(a)
のように関数の戻り
値を代入することで f から a の値を変更できます。
しかし、二つ以上の変数に対しては行えません。
例えば、変数 a と変数 b の値を交換することを考えます。
関数さえ呼び出さなければ次のようにすればできます。
int a=4,b=1;
int c;
c=a;
a=b;
b=c;
この処理はしばしば出てくるので、これを関数としたいところです。 ところが次のようにしても思うように動きません。
#include <stdio.h>
void swap(int x, int y){
int z;
z=x;
x=y;
y=z;
}
int main(void){
int a,b;
a=2; b=3;
swap(a,b);
printf("a=%d, b=%d\n",a,b);
return 0;
}
関数の引数の宣言での x, y はローカル変数です。 従って、呼び出す側の変数と値は同じですが、既に別の変数ですので、代入し ても呼出側の変数の値は変わりません。
そこで、ポインタを使います。 呼び出し側の変数の番地をサブルーチンに渡し、関数では、その番地に値を書 き込むのです。 次のようにすると思った通りの動きになります。
#include <stdio.h>
void swap(int *x, int *y){
int z;
z=*x;
*x=*y;
*y=z;
}
int main(void){
int a,b;
a=2; b=3;
swap(&a,&b);
printf("a=%d, b=%d\n",a,b);
return 0;
}
C 言語での配列名は実はポインタです。 配列 a の 0 番目の要素が格納されているメモリ上の番地が a になっていま す。つまり、 &a[0] と a は等しいです。ですから、a[0] と *a は等し いです。
#include <stdio.h>
int main(void){
double a[]={1.0,2.0,3.0,-1.0};
printf("&a[0]=%d, a=%d\n",(int)&a[0],(int)a);
printf("a[0]=%f, *a=%f\n",a[0],*a);
return 0;
}
では a[1] と a の関係はどうでしょうか? double のように一文字では表せないようなデータは、複数の番地に渡って格 納されているため、 a の隣の番地はまだ a[0] の内容の続きが入っています。 sizeof 演算子を使うと、その型が何バイトの記憶領域を使うかが 分かります。 筆者の環境では sizeof (double) の値は 8 になっています。 つまり、一つの double 型の変数を格納するのに 8 バイトの記憶領域が必要 になってきます。 ですから、 a[1] の番地は a[0] の番地から 8 バイト先の位置に記憶されて います。 結局、 C 言語の配列というのは次のようにして計算された値になります。
ところが、ポインタの計算では、何の型のポインタかが明らかなため、 +1 を すると、次の要素を指すように値が増えます。つまり、その型のサイズ分だけ 増えます。 結局 a[1]=*(a+1) となります。
#include <stdio.h>
int main(void){
double a[]={1.0,2.0,3.0,-1.0};
printf("&a[1]=%d, a=%d, sizeof double = %d\n",
(int)&a[1],(int)a,sizeof (double));
printf("a + 1 * sizeof a[0] = %d, a + 1 =%d \n",
(int)a + 1*sizeof a[0], (int)(a+1));
printf("a[1]=%f, *(a+1)=%f\n",a[1],*(a+1));
return 0;
}
これを使うと、配列の値を次々指し示す場合に ++ などのインクリメントが使えま す。 またループ変数を無くすこともできます。
#include <stdio.h>
int main(void){
double a[]={1.0,2.0,3.0,-1.0};
double *x;
for(x=a; *x != -1; x++){
printf("%f\n",*x);
}
return 0;
}
座標のように、複数の値を対に用いたい時に使うのが構造体です。 座標の構造体の型は次のように宣言されます(最後のセミコロン(;)は必須です)。
struct point {
double x;
double y;
};
そして、この後、座標の変数を宣言するには次のようにします。
struct point p;
struct point *q;
struct point r={1.0,2.0};
プログラムの中では、まず、構造体から構造体へは代入が可能です。
p=r
や q=&p
や *q=r
な
どできます。
また、構造体の内部の値はピリオド(.)の後にフィールド名を書きます。
p.x=3
や(*q).y=4
などが可能です。
また、特に、(*q).y
をq->y
と書くこ
とができます。
C 言語では文字配列の関数が提供されています。 利用するには string.h ヘッダファイルを読み込む必要があります。 その時、次を行うプログラムが以下に示すように簡単に書けます。 なお、文字配列の最後には必ず '\0' を入れておく必要があります。
#include <stdio.h>
int main(void){
char x[]="This is a pen.";
int i,k;
k=0;
for(i=0;x[i]!='\0';i++){
k++;
}
printf("%d\n",k);
return 0;
}
#include <stdio.h>
int main(void){
char x[]="This is a pen.";
char y='p';
int i,k;
k=0;
for(i=0;(x[i]!='\0')&&(x[i]!=y);i++){
k++;
}
if(x[k]=='\0'){
printf("Not found.\n");
return 1;
}
printf("%d\n",k);
return 0;
}
#include <stdio.h>
int main(void){
char x[]="This is a pen.";
char y='p';
int i,k;
k=0;
for(i=0;x[i]!='\0';i++){
if(x[i]==y){
k++;
}
}
printf("%d\n",k);
return 0;
}
#include <stdio.h>
int main(void){
char x[]="This is a pen.";
char y[]="That is a book.";
char z[50];
char *p,*q;
for(p=x,q=z;*p!='\0';*(q++)=*(p++));
for(p=y;*p!='\0';*(q++)=*(p++));
*q='\0';
printf("%s\n",z);
return 0;
}
#include <stdio.h>
#include <string.h>
int main(void){
char x[]="This is a pen.";
printf("%d\n",strlen(x));
return 0;
}
#include <stdio.h>
#include <string.h>
int main(void){
char x[]="This is a pen.";
char y='p';
char *z;
z=strchr(x,y);
if(z==NULL){
printf("Not found.\n");
return 1;
}
printf("%d\n",(int)(z-x));
return 0;
}
#include <stdio.h>
#include <string.h>
int main(void){
char x[]="This is a pen.";
char y='p';
char *z;
int k;
k=0;
for(z=strchr(x,y);z!=NULL;z=strchr(z+1,y)){
k++;
}
printf("%d\n",k);
return 0;
}
#include <stdio.h>
#include <string.h>
int main(void){
char x[]="This is a pen.";
char y[]="That is a book.";
char z[50];
strcpy(z,x);
strcat(z,y);
printf("%s\n",z);
return 0;
}
C++ 言語は C 言語に Simula67 で開発されたオブジェクト指向の考え方を導入 したものです。 これはプログラミングの方法論を変えてしまう仕組みです。 C++ 言語を使う以上、C++ の流儀に基づいてプログラミングをすべきです。
C 言語ではコメントとして/* と */ を使いましたが、 C++ 言語ではさらに // から行末までもコメントとして取り扱います。 /* */ は入れ子に出来ませんので、 // でコメントを付けた部分は、後で /* と */ で囲めるので便利です。 例えば、プログラムの注釈のコメントは // の後に書くように統一すると、プ ログラムの特定部分を無効にするのに /* と */ で括っても、 // のコメント は含むことができるので、便利です。
i=2; //初期値
/*
j=3 //これも初期値
*/
C++ 言語では引数の型だけが違う同じ名前の関数が許されています。 これをオーバーロードと言います。 これは、C 言語では禁止されています。
double square(double x){
return x*x;
}
int square(int x){
return x*x;
}
大きなプロジェクトにおいて関数、変数、クラスなどの名前は衝突する可能性 があります。 そのため、これらの名前に名字を付けるような感覚で、名前空間という手法を 使うことができます。
namespace sakamoto {
int i;
double f(double x);
class C {
C* create();
};
}
また namespace でプロトタイプを定義した場合、 namespace 外でスコープ演算子 :: を用いて定義できます。
double sakamoto::f(doube x){
return 1.2;
}
sakamoto::C* sakamoto::C::create(void){
return new sakamoto::C();
}
このように定義すると他で定義された変数 i や関数 f(x) やクラス C と分け ることができます。 実際に使う際には次のようにします。
int main(){
sakamoto::C x;
x.show();
}
using sakamoto::C;
int main(){
C x;
x.show();
}
using namespace sakamoto;
int main(){
C x;
x.show();
}
このうち最後の名前空間を登録する方法は簡便ですが、名前の衝突を避けると いう本来の名前空間の目的に反しているので推奨できません。
なお、C++ はもともとは名前空間を持たず、途中から導入されました。 名前空間を持たなかった頃のヘッダファイルには C 言語同様に .h の拡張子 が付いていました。 ですから、iostream.h は名前空間が定義されていません。 一方 iostream と .h の付かないヘッダファイルには std という名前空間が 定義されています。 従って、例えば iostream を使って標準出力に値を出力するには次のようにし ます。
#include <iostream>
int main(){
std::cout << "Hello World!" << std::endl;
}
#include <iostream>
using std::cout;
using std::endl;
int main(){
cout << "Hello World!" << endl;
}
#include <iostream>
using namespace std;
int main(){
cout << "Hello World!" << endl;
}
この講義では、名前空間の重要性を意識するため、なるべく using を使用し ない書式を使用します。
C 言語で使用したヘッダファイルに関しては、ファイル名から.h を除き、語 頭に c を付けたものが用意されています。 この中で使用する関数は名前空間 std に含まれています。
#include <cstdio>
int main(){
std::printf("Hello World\n");
}
C++ では C 言語と違い、宣言を自由な位置に置くことができます。 従って、条件に適合する時だけ巨大な配列を作り、必要ないときは領域を作ら ないように処理することもできます。 また、変数宣言はブロック内のみ有効で、ブロックの外で廃棄されます。
if(a==0){
int b=1;
} // この部分で b は廃棄される
a=b; // b は既に廃棄されているのでエラーになる
C++ では C 言語とのしがらみがない限りは、構造体を使いません。 その代わり、構造体の拡張のような形で クラス が導入されています。 クラスの内部では変数だけではなく、関数のプロトタイプを書くことができま す。
class Sample {
double x;
double y;
void show();
};
class はオブジェクト指向を実現するために導入されました。 オブジェクト指向とは、メッセージのやりとりを行うオブジェクトを用いてプ ログラムを作成する手法です。 オブジェクトとは変数とメソッドをもつものです。 オブジェクトを生成し、それに「メソッド+引数」というメッセージ を送ることで計算を行います。 このオブジェクトの設計図と言えるものがクラスです。 クラスでは生成したオブジェクトが持つ変数宣言と、メソッドの定義を行いま す。メソッドの定義はクラスの中では関数のように定義します。
class Sample {
double x;
double y;
void show(){ std::cout << x << y; }
};
但し、複数のファイルでクラスを共有するには注意が必要です。 クラス宣言をヘッダファイルに入れる時、メソッドの定義までヘッダファイル に入れると、複数のファイルを別々にコンパイルする際、それぞれのファイル 毎にメソッドができてしまいます。 そのため、 クラスの中にはプロトタイプ宣言のみを書き、外部で改めてメソッドを定義す べきです。 外部でメソッドを定義するには、クラス名とメソッド名の間にスコープ演算子 :: を入れます。 慣例ではクラス宣言は hpp ヘッダファイル、メソッドの実装は cpp ファイルに入 れます。
//----- sample.hpp ファイル ------
class Sample {
double x;
double y;
void show();
};
//----- sample.cpp ファイル ------
#include "sample.hpp"
void Sample::show(){
std::cout << x << y;
}
なお、オブジェクト指向プログラミングでは外部から変数への直接アクセスを 禁止し、必ずメソッドのみでオブジェクトを操作することが好まれます。 このようにオブジェクト指向で変数などを隠蔽することをカプセル化 と言います。 また、メソッドも外部に公開するものと内部だけで使うものに分かれますが、 特に外部に公開するメソッドをインターフェイスと言います。 このようにすると、オブジェクトの操作方法は限定されたものになるため、操 作に必要な情報が少なくて済みます。
C++ ではこのカプセル化のコントロールのため、外部からのアクセスを禁止す る private: というキーワードが用意されています。 その反対に外部に公開する public: というキーワードも用意されています。
//----- sample.h ファイル ------
class Sample {
private:
double x;
double y;
public:
void show();
};
クラスを作ると、新しい型としてオブジェクトの宣言が可能です。
生成したオブジェクトに対して、 public 宣言されたメンバ変数にアクセスできるこ とは構造体と同じです。 書式も構造体と同様に、メンバ変数名やメソッド(関数)はピリオドの後に書きます。 メソッド(関数)はメソッド名の後に丸カッコ()を付けます。
なお、 C++ 言語ではオブジェクトのポインタも用意されています。 宣言は通常と同じ * 付きの変数として宣言します。 ポインタからオブジェクトのメソッドを呼び出すには * と . の組合せでもで きますが、ポインタ専用に -> を使うことができます。
オブジェクトは変数宣言の他、 new 演算子によっても生成できます。 クラス名と同じ名前のメソッドをコンストラクタと言います。 これはオブジェクトを生成する時に初期化のために呼び出されるメソッドです。 new 演算子の後にコンストラクタを呼び出すと、オブジェクトを生成してポイ ンタを返します。 なお、オブジェクトの変数宣言をした場合もコンストラクタが呼ばれます。 なお、コンストラクタを省略した場合は、システムは空のコンストラクタ を自動生成します。 変数宣言をする際、()で引数を書くとコンストラクタに渡されます。この変数 宣言の書式は通常の型(int や double)にも適用され、初期化の新しい文法に なっています。
class Sample { ... };
int main(){
Sample a; // Sample のオブジェクトが作られ、
// コンストラクタ Sample()が呼ばれる
Sample b(1); // Sample のオブジェクトが作られ、
// コンストラクタ Sample(1)が呼ばれる
int c(2); // int c=2 と同じ
}
なお、 new で生成したオブジェクトは自動的に廃棄されませんので、
delete ポインタ
で消す必要があるときがあります。
オブジェクトのポインタの配列を廃棄する場合、delete [] ポインタ
と指定方法が違うので注意する必要があります。
変数が廃棄される際、デストラクタという特別なメソッドが呼ば
れます。
デストラクタは C++ では ~ にクラス名を付けたメソッドです。
これも省略すると特別なことをせずにオブジェクトを廃棄するデフォルトデス
トラクタが生成されます。
--------------- sample.hpp
class Sample {
private:
char *name;
int x;
public:
Sample(char * n); // コンストラクタ
~Sample(); // デストラクタ
void set(int n);
int get();
};
--------------- sample.cpp
#include <iostream>
#include "samp.hpp"
Sample::Sample(char * n){name=n;} // コンストラクタ
Sample::~Sample(){
std::cout << "object "
<< name
<< " is deleted."
<< std::endl;
}
// デストラクタ
void Sample::set(int n){x=n;}
int Sample::get(){return x;}
--------------- main.cpp
#include <iostream>
#include "samp.hpp"
int main(){
Sample b("b"); //コンストラクタが自動的に呼ばれる
b.set(3);
std::cout << b.get() << std::endl;
Sample *c; //ポインタが作られるだけでオブジェクトは作られない
c = new Sample("c"); // オブジェクトの生成
c->set(4);
std::cout << c->get() << std::endl;
delete c; // c が指すオブジェクトの明示的な廃棄
} // b の有効範囲が終了したのでオブジェクトが廃棄される
オブジェクトは生成する度いくつでも作ることができます。
一方で、複数のオブジェクトで共有させておきたいデータや手続きに関しては
キーワード staticを付けて定義できます。
これをクラス変数やクラスメソッドと呼びます。
静的〜と呼ぶこともあります。
これらはクラス名.変数名
、あるいはクラス名.メソッド
名
で参照します。
以下はこのクラス変数やクラスメソッドと使用した例で、
Singleton デザインパターンという定石です。
コンストラクタの代わりに Sample.getInstance クラスメソッドを呼び出すこ
とで、外部から何度 getInstance を呼び出しても、作成されるオブジェクトは単一
のままになります。
------- singleton.hpp
class Singleton {
private:
static Singleton * instance = NULL;
Singleton(){} // 勝手にインスタンスを作られないように
// コンストラクタを private にしておく
public:
~Singleton();
static Singleton * getInstance();
};
-------- singleton.cpp
Singleton::~Singleton(){ instance = NULL; }
static Singleton * Singleton::getInstance(){
if(instance==NULL){
instance = new Singleton();
}
return instance;
}
void f(){
Singleton *p=Singleton::getInstance();
p->someaction();
}
void g(){
Singleton *p=Singleton::getInstance();
p->someaction();
}
テンプレートは特定の関数やオブジェクトクラスを任意の型に対して生成した い時に使います。 例えば、数の二乗をする関数を次のようにテンプレートで作成します。
template <typename T>
T square(T a){
return a*a;
}
これをプログラムに組み込むと、C++ コンパイラは自動的 にその型に応じた関数を生成します。 また、生成する型を明示することもできます。
#include <iostream>
template <typename T>
T square(T a){
return a*a;
}
int main(){
std::cout << square(2) << std::endl
<< square(2.0) << std::endl
<< square(3.1) << std::endl
<< square<int>(3.1) << std::endl
<< square<double>(3) << std::endl;
}
また、クラスも生成できます。但し、テンプレートのメソッドはクラス宣言の 中に書きます。分割する方法もありますが、かなり複雑になります。
------------holder.hpp
template <typename T>
class Holder {
private:
T value;
public:
void set(T _value){
value = _value;
}
T get(){
return value;
}
};
------------holdermain.cpp
#include <iostream>
#include "holder.hpp"
int main(){
Holder<int> intholder;
Holder<double> doubleholder;
intholder.set(1.1);
doubleholder.set(1.1);
std::cout << intholder.get() << std::endl
<< doubleholder.get() << std::endl;
}
STL(Standard Template Library) は良く使われるデータ構造と関 数が集められたテンプレートです。 なお、STL の関数はアルゴリズムと呼ばれています。 STL に含まれるデータ構造や関数は、この講義でもしばしば取り上げます。
STL では要素を自由に追加、削除できる配列のテンプレートである vector が 用意されています。 C 言語の配列では領域外にアクセスした場合、動作は不定で暴走することもありまし た。 しかし、vector で定義した配列は x.at(n) のようにアクセスするとエラーを 発生し、暴走を防ぐことができます。 また、アルゴリズムと呼ばれるテンプレートを使うと高速に検索などのアクセ スができます。 以下は配列の合計と平均を求めるプログラムです。
#include <iostream>
#include <numeric> //accumulate に必要
#include <vector>
int main(){
int a[5]={ 3, 6, 2, 1, 4 };
std::vector<int> v(a, a+5);
int kosuu=v.size();
int goukei=accumulate(v.begin(),v.end(),0);
std::cout << "個数: " << kosuu << std::endl
<< "合計: " << goukei << std::endl
<< "平均: " << static_cast<double>(goukei)/kosuu << std::endl;
}
なお、ここで平均を取る際、キャストしています。 C++ にはキャスト演算子と言うものがあり、最低限の型チェックをします。 ここでは簡単に紹介するだけにとどめておきます。
C++ の STL では string テンプレートが用意されています。 これを用いて C 言語で示した文字列の処理を書くと次のようになります。
#include <iostream>
#include <string>
int main(){
std::string x="This is a pen.";
std::cout << x.size() << std::endl;
}
#include <iostream>
#include <string>
int main(){
std::string x="This is a pen.";
std::string y="p";
int z=x.find(y);
if(z>x.size()){
std::cout << "Not found." << std::endl;
return 1;
}
std::cout << z << std::endl;
}
#include <iostream>
#include <string>
#include <algorithm>
bool match(const char &c){
return (c == 'p');
}
int main(){
std::string x="This is a pen.";
std::string::iterator p;
int count=0;
for(p=find_if(x.begin(), x.end(), match);
p!=x.end();
p=find_if(p+1, x.end(), match)){
count++;
}
std::cout << count << std::endl;
}
#include <iostream>
#include <string>
int main(){
std::string x="This is a pen.";
std::string y="That is a book.";
std::string z=x+y;
std::cout << z << std::endl;
}
Java は C 言語と類似性を持たせつつ、 C++ とは異なる方針でオブジェクト 指向化がされています。 C++ では C 言語に存在していた int や double などの型と、オブジェクト型 の用法がなるべくおなじになるように作られていました。 変数の作成、代入、ライブラリの利用法等で差異は感じられませんでした。 しかし、 Java では明確に異なるものとして意識しなければなりません。
Java ではプリミティブ型では C や C++ と同じように変数宣言をすると値を 入れる領域が確保され、代入では値がコピーされます。 しかし、オブジェクト型では変数を宣言しても、オブジェクトを参照する変数 を作るだけで、オブジェクトそのものを作るわけではありません。
class Kazu {
private int value;
public void set(int _value){
value = _value;
}
public int get(){
return value;
}
}
class Rei {
private static void output(int x, int y){
System.out.println(x+" "+y);
}
public static void main(String[] arg){
int a,b;
a=3;
b=a;
a=4;
output(a,b);
Kazu c,d;
c=new Kazu();
c.set(3);
d=c;
c.set(4);
output(c.get(),d.get());
}
}
#include <iostream>
class Kazu {
private:
int value;
public:
void set(int _value){
value = _value;
}
int get(){
return value;
}
};
void output(int x, int y){
std::cout << x << " " << y << std::endl;
}
int main(){
int a,b;
a=3;
b=a;
a=4;
output(a,b);
Kazu c,d;
c.set(3);
d=c;
c.set(4);
output(c.get(),d.get());
Kazu *e,*f;
e= new Kazu();
e->set(3);
f=e;
e->set(4);
output(e->get(),f->get());
}
Java はシステムが豊富なクラスライブラリを用意しています。 そのため、通常のプログラミングではそのクラスライブラリを利用してプログ ラムを作成します。 Java ではクラスのパッケージを参照するときはパッケージ名とクラス名 を.(ピリオド)で区切って表現します。 システムパッケージは java で始まり、その中で lang, util, io, net など とパッケージが細分されます。
Java には全てのオブジェクトの親クラスとなる java.lang.Object クラスが 存在します。これはユーザが作成したクラスも暗黙のうちにこのサブクラスに なります。 これは java.lang.Object クラス型の変数は全てのオブジェクトを参照できる ということを意味します。
なお、 java.lang に属するクラスは java.lang を省略してアクセスできます。 java.lang.Object, java.lang.Integer, java.lang.Double, java.lang.Math などはそれぞれ Object, Integer, Double, Math などと書いてアクセスでき ます。
また、気を付けなければならないのはプリミティブ型は参照できないというこ とです。 int や double のようなプリミティブ型を java.lang.Object 変数が参照する ようにするには、ラッパークラス である java.lang.Integer や java.lang.Double を使用する必要があります。 Java 1.4 以前では必ず使用する必要があります。
class Rei {
public static void main(String[] args){
Object[] obj={new Integer(1),new Double(2.3),"abc"};
for(int i=0; i<obj.length; i++){
System.out.println(obj[i].getClass().getName());
}
}
}
全ての親クラスが java.lang.Object であるという性質を利用して、 Java 1.4 までのクラスライブラリでは、利用者からの値を保持する変数の型 は java.lang.Object で定義されてました。 java.util パッケージには豊富なデータ構造が含まれていますが、全て格納す る場合、 java.lang.Object の参照として格納されます。 したがって、取り出した値に対して、元のオブジェクトとして利用するには ダウンキャストを行う必要があります。
class Rei {
public static void main(String[] args){
Object obj="abc";
String str=(String)obj; // ダウンキャスト
System.out.println(str.length());
}
}
この java.lang.Object の機能を利用して、複数の情報を扱うためのデータ構 造が java.util パッケージのクラスライブラリで提供されています。 C++ の vector テンプレートに対応するのが java.util.ArrayList クラスに なります。 この場合も、値を取り出す時は java.lang.Object 型で取り出されることに注意す る必要があります。 したがって、 入れたオブジェクトを取り出す時キャストする必要があります。 ダウンキャストの異常は実行時に判明することがあるので、プログラミング的 には避ける必要があるのですが、 Java 1.4 以前では構造的に避けられません。
class Main{
public static void main(String[] arg){
int[] a = {3, 5, 0, 2, 4 };
java.util.List l = new java.util.ArrayList(); //変数は抽象クラス型にする
for(int i=0; i< a.length;i++){
l.add(new Integer(a[i]));
}
int kosuu = l.size();
int goukei=0;
for(java.util.Iterator i = l.iterator(); i.hasNext();){
goukei += ((Integer) i.next()).intValue(); //ダウンキャスト
}
System.out.println("個数: "+kosuu);
System.out.println("合計: "+goukei);
System.out.println("平均: "+(double)goukei/kosuu);
}
}
ところが Java version 5 から Generics(総称) という C++ の template と似たような機能が追加されました。 それにより、 java.util に含まれるクラスライブラリのデータ構造は自然な拡張 で使えるようになりました。 その結果、 Java 5 以降ではダウンキャストを回避できるようになりました。 またさらに Java 5 ではデータの集まり全部をアクセスする foreach 構文が あります。
class Main{
public static void main(String[] arg){
Integer[] a = {3, 5, 0, 2, 4 };
java.util.List<Integer> l
= new java.util.ArrayList<Integer>(
Arrays.asList(a));
int kosuu = l.size();
int goukei=0;
for(Integer i : l){ // foreach 構文
goukei += i ;
}
System.out.println("個数: "+kosuu);
System.out.println("合計: "+goukei);
System.out.println("平均: "+(double)goukei/kosuu);
}
}
前述したようにプリミティブ型である int や double のデータに対して豊富なクラ スライブラリを利用するには、それらに対応するオブジェクトに変換する必要 があります。 このプリミティブ型に対応するオブジェクトクラスをラッパークラ スと言います。 しかし、 Java は演算子のオーバロードがありません。 そのため、ラッパークラスのオブジェクトでは式を取り扱うことができません。 例えば 1 と 2 を値として持つ int のラッパークラスである java.lang.Integer のオブジェクト x と y を考えます。 この値の和を Integer 型の変数 z に入れ、値を表示するプログラムは以下の ようになります。
class Rei {
public static void main(String[] args){
Integer x = new Integer(1);
Integer y = new Integer(2);
Integer z = new Integer(x.intValue() + y.intValue());
System.out.println(z.intValue());
}
}
そこで、 Java 5 以降ではオートボクシング, オートアンボ クシングという機能が実現されました。 これは式中のラッパクラスに対して、ラッパクラスから値を取り出し、また、 ラッパクラスへの数値の代入を可能にします。 つまり上記のプログラムは Java 5 以降では次のように書けます。
class Rei {
public static void main(String[] args){
Integer x = 1;
Integer y = 2;
Integer z = x + y;
System.out.println(z);
}
}
Java には文字列型として java.lang.String(変更不可), java.lang.StringBuffer(変更可) があります。 文字列はオブジェクト型です。また、文字定数も java.lang.String 型です (C++ では const char[] 型)。
class Test1 {
public static void main(String[] args){
String x="This is a pen.";
System.out.println(x.length());
}
}
class Test2 {
public static void main(String[] args){
String x="This is a pen.";
String y="p";
int k=x.indexOf(y);
if(k<0){
System.out.println("Not found.");
return 1;
}
System.out.println(k);
}
}
class Test3 {
public static void main(String[] args){
String x="This is a pen.";
String y="i";
int k=0;
for(int i=x.indexOf(y); i>=0; i=x.indexOf(y,i+1)){
k++;
}
System.out.println(k);
}
}
class Test4 {
public static void main(String[] args){
String x="This is a pen.";
String y="That is a book.";
String z;
z=x+y;
System.out.println(z);
}
}