ぼんさい塾

ぼんさいノートと補遺に関する素材や注釈です.ミスが多いので初稿から1週間を経た重要な修正のみ最終更新日を残しています.

Cから見たC++ (3)

2010-10-11 18:21:08 | 暮らし
記事一覧(Cの文法(1)~(17),他)
まず覚えるCの文法(progC.pdf)

P2.h: 段級位の追加
//-------------------------------------
#include "P1.h"
typedef struct{
  int id, dan;
} member2;
class group2 : public group{
private:
  member2 *top2;
  int dan;
public:
  group2(member*, int, member2*);
  void set_dan(int, int);
  void show(int);
  void add(int, char*, char*, int);
  void del(int);
};
group2::group2(
  member *p, int m, member2 *p2
):group(p, m){
  top2=p2;
}
void group2::set_dan(int i, int d){
  member *p=addr(i);
  top2[k].id=i; top2[k].dan=d;
}
void group2::show(int i){
  group::show(i);
  if(top[k].id==top2[k].id){
    printf("%d, %d\n",
           top[k].id, top2[k].dan);
  }else{printf("?\n");}
}
void group2::add(
  int i, char *n, char *m, int d
){
  group::add(i, n, m); set_dan(i, d);
}
void group2::del(int i){
  member *p=addr(0);
  member2 *q=&top2[k-1];
  group::del(i); p=addr(q->id);
  if(p->id==q->id){
    top2[k].id=q->id;
    top2[k].dan=q->dan; q->id=0;
  }
}
//-------------------------------------

P2.cpp: 使用例
//-------------------------------------
#include "P2.h"
member igo[10]={
  {1, "Aoki", "aoki@xxx.ne.jp"},
  {2, "Itoh", "taro@yyy.jp"},
};
member2 igo2[10]={{1, 2}, {2, -1}};
int main(void){
  member *p=&igo[0], *q;
  member2 *p2=&igo2[0];
  group2 g_igo2(p, 10, p2);
  g_igo2.add(5, "Tanaka",
            "foo@zzz.ac.jp", 0);
  for(q=p; q->id!=0; q++){
    g_igo2.show(q->id);
  }
  g_igo2.set_dan(5, 3); g_igo2.del(2);
  for(q=p; q->id!=0; q++){
    g_igo2.show(q->id);
  }
  return 0;
}
//-------------------------------------

=================================================
2. 派生クラス

囲碁,将棋に限らず柔道,剣道,書道,珠算等にも段
級位があります.2段を 2,3級を -3,不明を 0 と
した段級位のデータを扱えるように, group を拡張し
た派生クラス group2 の例を P2.h に示します.
(1)「class group2 : public group」は group から
   派生させることを意味します.基本クラスのデー
   タメンバやメンバ関数をそのまま使うときは何も
   書く必要はありません.
(2) 追加したいデータメンバや変更あるいは追加した
   いメンバ関数は P2.h のように明示します. 基本
   クラスに不要なものがあっても削除はできません.
(3) 派生クラスの定義は基本クラスには影響しません.

・簡単のため P1A.h でなく P1.h を用いました.
・group2 のコンストラクタの処理に追加する処理のみ
  を P2.h のように書きます.
・group2::show() の処理で group の show() を利用
  したいときは「group::」を付けて呼び出します.
・set_dan() は段級位設定のために追加した関数です.
・show() は group::show() を使っているので途中で
  改行されますが,これくらいは我慢しましょう.

細かい処理はともかく 基本クラスを利用すると派生ク
ラスのプログラミングが非常に楽になります.

             ***********

P2A.cpp: おまけ(無視してよい)
//-------------------------------------
#include "P2.h"
member igo[10]={{1, "A", "x"}, {2, "B", "y"}};
member2 igo2[10]={{1, 1}, {2, -1}};
member tennis[20]={{3, "C", "z"}};
int main(void){
  group *p1=new group(&tennis[0], 20), *p; 
  group2 *p2=new group2(&igo[0], 10, &igo2[0]); 
  member *q; char c;
  printf("club="); scanf("%c", &c);
  switch(c){
    case 'i': p=p2; q=&igo[0]; break;
    case 't': p=p1; q=&tennis[0]; break;
  }
  for(/* q=p->top_is() */; q->id!=0; q++){
    p->show(q->id);
  }
  return 0;
}
//-------------------------------------

多態性の説明です. P2A.cpp の p は group,group2 
のいずれのオブジェクトを指すかは実行時に決まりま
す.この結果,p->show() が group,group2 のいずれ
のものであるかもコンパイル時には分かりません(late 
binding).このプログラムを実行して,キー入力によっ
て表示形式が異なることを確認してください.

・group2 *p; のときは p=p1; とはできません.情報
  不足で処理できないことが起こりうるからです.
・P2h では top_is() がないので q を直接設定しまし
  たが q=p->top_is(); が望ましい.
・どうして実現するか興味のある人は,まず[0-2]の仮
  想関数テーブルの説明を見てください.

[2-1] 第9章 オーバーライド (含. 多態性)
[2-2] 第32章 privateやprotectedな継承

Cから見たC++ (2)

2010-10-10 21:11:26 | 暮らし
記事一覧(Cの文法(1)~(17),他)
まず覚えるCの文法(progC.pdf)

P1A-1: group クラスの変更例
//-------------------------------------
class group{
protected:
  member *top;
  int max, k;
  member* addr(int);
public:
  group(member*, int);
  member* top_is(void){return top;};
  int max_is(void){return max;};
  virtual void show(void);
  virtual void show(int);
  virtual void add(int, char*, char*);
  virtual void del(int);
  virtual void load(char*);
  virtual void save(char*);
};
//-------------------------------------

P1A-2: 追加関数
//-------------------------------------
void group::show(void){
  member *p=top;
  for(k=0; k<max; k++,p++){
    if(p->id==0){break;}
    printf("%d, %s, %s\n", 
           p->id, p->name, p->mail);
  }
}
void group::load(char *s){
  FILE *fp=fopen(s, "r");
  member *p=top;
  int i; char n[10], m[30];
  if(fp==NULL){printf("-\n"); return;}
  for(k=0; k<max; k++,p++){
    fscanf(fp, "%d %s %s\n", &i, n, m);
    p->id=i; strcpy(p->name, n);
    strcpy(p->mail, m);
    if(p->id==0){break;}
  }
  fclose(fp);
}
void group::save(char *s){
  FILE *fp=fopen(s, "w");
  member *p=top;
  if(fp==NULL){printf("-\n"); return;}
  for(k=0; k<max; k++,p++){
    fprintf(fp, "%d\t%s\t%s\n",
            p->id, p->name, p->mail);
    if(p->id==0){break;}
  }
  fclose(fp);
}
//-------------------------------------

P1.h の group クラスの変更案を P1A-1 に示します.
変更理由は
(1) top と max を見えるようにする.
(2) 全体を表示する show() を追加.
(3) ファイル入出力用の関数を追加.
で,追加した関数の構成例を P1A-2 に示しています.

・top や max の値は見るだけで変更できません.
・show(void) と show(int) は引数が違うので同じ名
  前を使えます(printf や scanf は例外).関数の多
  重定義については別途説明します.

P1.h に変更を施したヘッダファイル P1A.h を作り,

P1A.txt: 入力ファイル
//-------------------------------------
1  Aoki    aoki@xxx.ne.jp
2  Itoh    taro@yyy.jp
5  Tanaka  foo@zzz.ac.jp
0
//-------------------------------------

を準備して,

P1A.cpp: ファイル入出力
//-------------------------------------
#include "P1A.h"
int main(void){
  member *p=new member[10];
  group igo(&p[0], 10);
  igo.load("P1A.txt");
  igo.show();
  igo.del(2);
  igo.save("P1B.txt");
  //delete[] p;
  return 0;
}
//-------------------------------------

をコンパイルして実行し,出力ファイル P1B.txt の内
容を確認してください.

・「member *p; p=new member[10];」によって member
  の配列が動的に作成され,その先頭番地が p に設定
  されます.
・new で作成した配列の削除は delete[] で行います.
  「int *p=new int;」のときは「delete p;」です.
・P1A.cpp では 終了直前に領域を削除しても無意味な
  のでコメントにしています.

[1-2] New演算子 - Wikipedia

 


Cから見たC++ (1)

2010-10-08 19:18:19 | 暮らし
記事一覧(Cの文法(1)~(17),他)
まず覚えるCの文法(progC.pdf)

P1.h: 基本クラス化
//-------------------------------------
#include <stdio.h>
#include <string.h>
typedef struct{
  int id; char name[10]; char mail[30];
} member;
class group{
protected:
  member *top;
  int max, k;
  member* addr(int);
public:
  group(member*, int);
  virtual void show(int);
  virtual void add(int, char*, char*);
  virtual void del(int);
};
group::group(member *p, int m){
  top=p; max=m;
}
member* group::addr(int i){
  member *p=top;
  for(k=0; k<max; k++,p++){
    if(p->id==i || p->id==0){break;}
  }
  if(k<10){return p;}
  else{return NULL;}
}
void group::show(int i){
  member *p=addr(i);
  if(p->id==i){
    printf("%d, %s, %s\n", 
           p->id, p->name, p->mail);
  }else{printf("?\n");}
}
void group::add(
  int i, char *n, char *m
){
  member *p=addr(i);
  if(p!=NULL && p->id==0){
    p->id=i;
    strcpy(p->name, n);
    strcpy(p->mail, m);
  }else{printf("-\n");}
}
void group::del(int i){
  member *p=addr(i), *q;
  if(p!=NULL && p->id==i){
    q=addr(0); q--;
    if(q!=p && q->id!=0){
      p->id=q->id;
      strcpy(p->name, q->name);
      strcpy(p->mail, q->mail);
    }else{q=p;}
    q->id=0;
    q->name[0]=q->mail[0]='\0';
  }else{printf("?\n");}
}
//-------------------------------------


=================================================
1. 基本クラス

P0 のプログラムを基本クラスを使って書き換えた例を
示します.P1.h と P1.cpp はテキストファイルの名前
です.P1.cpp の先頭行「#include "P0.h"」は プリプ
ロセッサによって P1.h の内容に置換されます(#66).

P1.cpp: P0 のプログラムの書き換え (10/9修正)
//-------------------------------------
#include "P1.h"
member igo[10]={
  {1, "Aoki", "aoki@xxx.ne.jp"},
  {2, "Itoh", "taro@yyy.jp"}
};
int main(void){
  member *p=&igo[0], *q;
  group g_igo(p, 10);
  g_igo.add(5, "Tanaka", "foo@zzz.ac.jp");
  for(q=p; q->id!=0; q++){
    g_igo.show(q->id);
  }
  g_igo.del(2);
  for(q=p; q->id!=0; q++){
    g_igo.show(q->id);
  }
  return 0;
}
//-------------------------------------

・P1.cpp と同じフォルダに P1.h があれば「#include 
  "P0.h"」と書きます.
・main() をいろいろ変えて実行したいときは,ファイ
  ルを分割しておくと便利です.

P0 の構造体 group を基本クラス group に変更すると
きの方針を以下に示します.
(1) top, max, addr() は派生クラスでも使えるように
   protected にしておく.また,addr() で使う k は
   派生クラスでの利用価値があるので addr() の外に
   置き,protected にする.
(2) 関数 show(), add(), del() は派生クラスで機能を
   拡張することに対処するため「virtual」を付けて仮
   想関数にしておく[0-2].

・C++等では「見せる必要のないものは見せない」こと
  を方針にしています.アクセス指定子には
    private, protected, public
  があり,それぞれ自クラス内でしか参照できないもの,
  自クラスと派生クラス内でしか参照できないもの,ど
  こからでも参照できるものを規定します.
・各クラスにはクラスと同じ名前を持つ関数(コンスト
  ラクタ)が存在して,インスタンスの初期化を行いま
  す.変数の宣言「int k=0;」に対応させると,int が
  クラス,k がインスタンス,「k=0;」がコンストラク
  タの処理に相当します.
・コンストラクタに対応するデストラクタは気にしなく
  て結構です.new で生成したものは delete が処理し
  てくれます[1-1].

[1-1] デストラクタ - Wikipedia

Cから見たC++ (0)

2010-10-07 22:13:17 | 暮らし
記事一覧(Cの文法(1)~(17),他)
まず覚えるCの文法(progC.pdf)

P0: Cの構造体と専用の関数
//-------------------------------------
#include <stdio.h>
#include <string.h>
typedef struct{
  int id; char name[10]; char mail[30];
} member;
member igo[10]={
  {1, "Aoki", "aoki@xxx.ne.jp"},
  {2, "Itoh", "taro@yyy.jp"}
};
//-------------------------------------
typedef struct{
  member *top; int max;
} group;
group g_igo={&igo[0], 10};
member *addr(group *g, int i){
  member *p=g->top; int k;
  for(k=0; k<g->max; k++,p++){
    if(p->id==i || p->id==0){break;}
  }
  if(k<g->max){return p;}
  else{return NULL;}
}
void show(group *g, int i){
  member *p=addr(g, i);
  if(p->id==i){
    printf("%d, %s, %s\n", 
           p->id, p->name, p->mail);
  }else{printf("?\n");}
}
void add(group *g, 
         int i, char *n, char *m){
  member *p=addr(g, i);
  if(p!=NULL && p->id==0){
    p->id=i;
    strcpy(p->name, n);
    strcpy(p->mail, m);
  }else{printf("-\n");}
}
void del(group *g, int i){
  member *p=addr(g, i), *q;
  if(p!=NULL && p->id==i){
    q=addr(g, 0); q--;
    if(q!=p && q->id!=0){
      p->id=q->id;
      strcpy(p->name, q->name);
      strcpy(p->mail, q->mail);
    }else{q=p;}
    q->id=0;
    q->name[0]=q->mail[0]='\0';
  }else{printf("?\n");}
}
//-------------------------------------
int main(void){
  member *p;
  add(&g_igo, 5,
      "Tanaka", "foo@zzz.ac.jp");
  for(p=g_igo.top; p->id!=0; p++){
    show(&g_igo, p->id);
  }
  del(&g_igo, 2);
  for(p=g_igo.top; p->id!=0; p++){
    show(&g_igo, p->id);
  }
  return 0;
}
//-------------------------------------

0. はじめに

C言語の知識を前提としてC++の概要を

1.基本クラス
2.派生クラス
3.多重定義
4.その他(10/15変更)

の順に説明します.したがって

#include <iostream.h>
int main(void){
  cout << "Hello, world";
  return 0;
}

の「iostream.h」「cout」等の説明は最後になります.

Cの代わりにC++を使う最大の利点は,基本クラスで
作られたプログラムを変更することなく,派生クラス
で機能を拡張できることです.
  簡単な例として「Cの文法(16)」で宣言した構造体
group を基本クラスに変更して,段位のデータ等を付
加した派生クラスを作ってみましょう.

準備として「Cの文法」で考えた P0 のプログラムを
復習しておいてください.なお,Cでも

  member *addr(group *g, int i);
  void show(group *g, int i);
  void add(group *g, int i, char *n, char *m);
  void del(group *g, int i);

あるいは

  member *addr(group*, int);
  void show(group*, int);
  void add(group*, int, char*, char*);
  void del(group*, int);

のような宣言文(プロトタイプ宣言)があります.C
のコンパイラは関数の定義の前に呼び出しが書かれて
いると int 型の関数として処理してしまいますが,
冒頭に上記の宣言文をおくと,関数の定義を main()
の後で書いても正しくコンパイルされます.また,大
きなプログラムを複数のファイルに分割してコンパイ
ルするときには,このような表現は必須です(先頭に
「extrn」を付けます)(@46).
  クラスの宣言に類似の表現を使うので,ついでに覚
えておきましょう.

参考:オブジェクト指向プログラミングの特徴や歴史
は[0-1]で説明されていますが,とりあえずオブジェク
トとはデータメンバだけで構成される構造体にメンバ
関数を付加したものと考えればいいでしょう.派生ク
ラスではデータメンバの追加やメンバ関数の変更・追
加が行われます.メンバ関数の扱いに興味のある人は
[0-2]を勉強してください.
[0-1] オブジェクト指向プログラミング - Wikipedia
[0-2] 仮想関数テーブル - Wikipedia
[0-3] 柴田望洋,プログラミング講義C++
[0-4] 講義資料(圧縮ファイル)

Cの文法 (17)

2010-10-05 18:56:08 | 暮らし
記事一覧
「まず覚えるCの文法」校正中)

P64: ファイル入出力
//-------------------------------------
#include <stdio.h>
#include <math.h>
int main(void){
  FILE *fp;
  int i, k; double x[10];
  fp=fopen("log10.txt", "w");
  if(fp==NULL){return 1;}
  for(i=1; i<10; i++){
    printf("%d, %f\n", i, log10(i));
    fprintf(fp, "%d, %f\n",
            i, log10(i));
  }
  fclose(fp);
  printf("quit="); scanf("%d", &i);
  if(i!=0){return 0;}
  fp=fopen("log10.txt", "r");
  if(fp==NULL){return 2;}
  for(i=1; i<10; i++){
    fscanf(fp, "%d, %lf\n", &k, x+i);
    printf("%d, %f\n", k, x[i]);
  }
  fclose(fp);
  return 0;
}
//-------------------------------------

P65: 強制終了
//-------------------------------------
#include <stdio.h>
double q(double x, double y){
  if(y==0){
    printf("div by 0\n"); exit(1);
  }
  return x/y; 
}
int main(void){
  double a, b;
  printf("a="); scanf("%lf", &a);
  printf("b="); scanf("%lf", &b);
  printf("a/b==%f\n", q(a, b));
  return 0;
}
//-------------------------------------

計算結果を画面に表示する代わりにテキストファイルに
出力しておくと,それをプリンタで印刷できます.また
キーボードの代わりに予め作っておいたテキストファイ
ルからデータを入力することもできます.具体的な方法
を常用対数表の作成と入出力を行う P64 のプログラムで
説明します.
(1) (#64)の数学関数を使うには「#include <ath.h>」が
   必要です.
(2)「FILE *fp; fp=fopen("log10.txt", "w");」はファ
   イル log10.txt に出力準備をシステムに要求します.
   準備に失敗すると fp の値は NULL になります.
(3)「fprintf(fp, "%d, %f\n", i, log10(i));」の意味
   は「printf("%d, %f\n", i, log10(i));」と同様です.
(4) 処理が終了すれば「fclose(fp);」でファイルを解放
   します.
(5)「fp=fopen("log10.txt", "r");」はファイル log10.
   txt からの入力の準備をシステムに要求します.
(6)「fscanf(fp, "%d, %lf\n", &k, x+i);」の意味は
   「scanf("%d, %lf\n", &k, x+i);」と同様です.
(7) 処理が終了すれば「fclose(fp);」でファイルを解放
   します.

・fopen() の引数 "w" は,ファイルが存在しなければ作
  成して出力し,存在すれば上書きします.ファイルの
  末尾に追加するときは "a" を用います.
・[#62]の getc(), fgets(), fread(), [#63]の putc(),
  fputs(), fwrite() については各自調べてください.

実行中のプログラムを強制的に終了させるときは exit() 
を実行します.引数は任意の整数値で結構です.(習慣的
には 0 は正常終了)

・[#65]の rand(), malloc(), difftime() の機能や使用
  例は各自調べてください.

問は省略します.

=================================================--===

Cの文法(0):
ぼんさいノート「まず覚えるCの文法」はオブジェクト指向
プログラミングの説明に必要な(オブジェクト以前の)予備
知識を与えるために作成した本文 12 頁の資料です.

ぼんさいノート progJ.pdf は未着手です.とりあえず構造体
の説明は済んだので,「Cから見たC++」を書きながら内容を
考えます.