文字配列型(C-Strings)

C-String(またはcharacter string)はメモリ内でナル文字(日本ではヌル文字ともいう)'$\yen$0'で終了している文字配列である.例えば,

Y O K O T A $\yen$0

C-Stringsはchar*でアクセスできる.例えば,sがchar*型だとすると,

cout << s << endl;
はメモリ内に保管された番地sで始まりナル文字で終わるまでのすべての文字を出力する.つまり,
char s[] = "ABCD";
cout $<<$ s;
により,ABCDが出力される.

ヘッダファイル$<$cstring$>$は文字配列を扱うための特殊な関数を沢山用意している.例えば,strlen(s)は文字配列sのナル文字を含まない文字数を返す.これらの関数はその文字配列の引数を全てcharへのポインタとして宣言する.

文字配列型(C-Strings)

文字配列型とは,メモリ内でナル文字(日本ではヌル文字とよばれる)$'\yen0'$で終了している文字列で次のような特徴を持っている.

例題 8..1   次のプログラムを実行するとどんな結果が得られるか考えよ.その後,実際に実行せよ.

#include <iostream>
using namespace std;
int main()
{
char s[] = "ABCD";
for (int i = 0; i < 5; i++)
cout << "s[" << i << "] = '" << s[i] << "'" << endl;
}

解答 文字配列の最後にはNUL文字がついているので,s[4] = ' 'となる.

実行結果

\begin{figure}% latex2html id marker 2611
\centering\includegraphics[width=7cm]{CPPPIC/reidai8-1.eps}
\end{figure}

文字配列の入出力(I/O of C-Strings)

C++で文字配列の入出力を行う方法はいくつかある.その1つの方法として,標準のC++stringクラスを用いる方法がある.これについては次の章で紹介する.ここでは,クラシックなC-Stringsの入出力について紹介する.

例題 8..2   文字配列の入出力
キーボードから打ち込んだ文を
79文字のバッファに読み込み,空白文字で行換えして表示するプログラムを作成せよ.

解答 バッファをchar word[80]とすると,*wordは配列の先頭要素word[0]の別名なので,*wordは文字配列wordの文字長が0より大きければ0以外(true)で,文字長が0つまりナル文字の時は0となる.cin $>>$ word;は文字列の前にある余白文字を無視し,余白文字までの単語をwordに格納する.では,プログラムを作成しよう.

#include <iostream>
using namespace std;
int main( )
{
    char word[80];
    do {
        cin >> word;
        if(*word) {
            cout << "\t\"" << word << "\"" <<< endl;
        }
    }while(*word);
}

実行結果

\begin{figure}% latex2html id marker 2624
\centering
\includegraphics[width=7.7cm]{CPPPIC/reidai8-2.eps}
\end{figure}

Today'sの後に余白文字(スペース)があるので,cin $>>$ word;によりwordにはToday's'$\yen$0'が格納される.次に,isの前の余白文字は無視され,wordにはis'$\yen$0'が格納される. 表現*wordはループをコントロールしている.*wordは文字配列の最初の文字である.つまり,*wordはword[0]と同じ.よって,wordが長さ1より大きい文字配列を含んでいるならば,*wordは零でない.よって,条件は満たされる.もし,wordが空の文字配列('$\yen$0'が最初の文字)ならば,*wordは零となる.つまり,Ctrl+Z+Enter+Enterと打つと,*wordを'$\yen$0'に設定するので,ループを終了することができる.

句読点(アポストロフィ,ピリオド,カンマなど)は文字配列に含まれるが,余白文字(スペース,タブ,改行など)は文字配列に含まれない.

練習問題 8..1   上の例題をwhileループを用いて書き直せ.

オブジェクト指向言語

ここまでは,手続き型言語としてのC++の機能だけを用いてプログラムの作成を行なった.つまり,プログラミングパラダイムは次のようなものである.

必要な手続きを決める.
見つけられる限りで最良のアルゴリズムを使う.

これからは,C++のオブジェクト指向言語としての機能を用いてプログラムの作成を行なう.オブジェクト指向言語のプログラミングパラダイムは次のようなものである.

使いたい型を決める.
個々の型に対して演算の完全なセットを提供する.
継承を使って共通点を明示的に示す.

そこで,C++の最も重要な機能の一つであるクラス(class)について考えてみる.クラスとは,オブジェクトを生成するために使われる仕組みであり,クラスはC++の機能の核心にあたる.

クラスを宣言するには,classキーワードを使用する.クラスの宣言構文は以下のようになる.
class クラス名
{
public: //アクセス指定子を記述
変数 //データメンバを記述
関数 //メンバ関数を記述
private:
変数
関数
};

クラス宣言内で宣言される関数と変数は,そのクラスのメンバ(member)とよばれる.ディフォルトでは,クラス宣言内で宣言されたすべての関数と変数はそのクラスに対して非公開となる.つまり,これらの関数と変数は,そのクラスのメンバからだけしかアクセスすることができない.

公開クラスメンバを宣言するには,publicキーワードを指定し,その後にコロンを追加すればよい.次に,単純なクラス宣言の例を見てみよう.
class myclas
{
private:
double base, height;
public:
dobule area(double, double);
void display();
};

このクラスには,baseとheightという2つの非公開メンバ変数と,area()とdisplay()という2つの公開メンバ関数がプロトタイプ形式で宣言されている.

baseとheightは非公開メンバ変数なので,myclassクラスの外部にあるプログラムからはアクセスすることはできない.area()はmyclassクラスのメンバ関数なので,変数baseとheightにアクセスすることができる.

メンバ関数の定義

次に,このクラスを用いて実際にプログラムを作成するには,メンバ関数の定義が必要となる.まず,メンバ関数の定義には,そのクラスの型名を関数名にリンクする必要がある.このリンクとして用いるものが スコープ解決演算子(scope resolution operator)とよばれ,2つのコロン(::)で表される.これにより,どのクラス型のメンバ関数であるかが分かる.そこで,スコープ解決演算子を用いて,area()とdisplay()を定義すると以下のようになる.
double myclass::area(double b, double h)//myclass型のarea()メンバ関数の定義
 {
base = b;//bに渡された値をメンバ変数baseに代入
height = h;//hに渡された値をメンバ変数baseに代入
return base*height/2;
 }
double myclass::display()//myclass型のdisplay()メンバ関数の定義
 {
cout $<<$ "底辺" $<<$ base $<<$ "高さ" $<<$ height $<<$ "の三角形の面積="
   $<<$ area(base,height) $<<$ endl;
 }

このクラスは,三角形の底辺と高さを与えるとその面積を計算し表示するということを行なうことが分かる.

クラスの利用

定義したクラスは,新しい型の一つである.つまり,クラス型の変数を宣言することができる.クラス型の変数は次のようにして宣言する.

オブジェクトの作成

オブジェクトの作成の構文は,

クラス名 変数名;
となる. では,実際にmyclassクラス型のオブジェクトを作成しよう.myclass型のオブジェクトを作成するのは,基本型の変数の定義と同じで,
myclass obj1, obj2;
とすればよい.これにより,obj1とobj2はmyclass型となる.つまり,obj1とobj2はmyclassで宣言されたメンバ変数のコピーを持っているということである.

メンバのアクセス

ここで作成したオブジェクトをプログラムで利用することにする.オブジェクトを利用するには,オブジェクトの各メンバにアクセスして値を格納する必要がある.そのためには,ドット(.)演算子を利用して,myclassの公開メンバ関数を呼ぶことができる.例えば,obj1.area(3.5,4);
とすると,obj1オブジェクトの変数baseを3.5に,heightを4に設定し,3.5×4/2, つまり7を返す.
では,クラスを宣言し,実際に利用するプログラムをmain()関数を書いてみよう.

==============================================

#include $<$iostream$>$
using namespace std;
class myclass //myclassクラスの宣言
{
public: //公開メンバ
double area(double, double);
void display();
private:
double base, height;
};
double myclass::area(double b, double h) // areaメンバ関数の定義
{
base = b;
height = h;
return base*height/2;
}
void myclass::display() //displayメンバ関数の定義
{
cout $<<$ "底辺" $<<$ base $<<$ "高さ" $<<$ height $<<$ "の三角形の面積="
   $<<$ area(base,height) $<<$ endl;
}
int main ()//main関数
{
myclass obj1, obj2;
obj1.area(3.5,4);
obj2.area(12.4, 6.2);
obj1.display();
obj2.display();
}
==============================================

イベント,メソッド,プロパティ

あるオブジェクトの外部で発生した事象により,オブジェクトが実装した処理が呼び出されることをイベントとよぶ.また,ここで呼び出される処理のことをメソッドとよぶ.オブジェクト指向プログラミングでは,プログラムというものをあるオブジェクトが他のオブジェクトにメッセージを送ることの積み重ねによって生み出される相互作用であるという考え方をとる.ここで,「メッセージを送る(イベントを通知する)」ということを,「オブジェクトのメソッドを呼び出す」ともいう.具体的には,
「オブジェクトAにパラメタとしてCを持つメッセージBを送る」ことを,
A.B(C);
と記述する.また,
「オブジェクトAにパラメタとしてCを持つメッセージBを送ったところ,メッセージが送られてきたのでそれをDとして受け取った」ことを
D = A.B(C);
と記述する.さらに,
「オブジェクトAが保持する性質BにCという値を設定する」ことを
A.B = C;
と記述する.

cinメンバ関数(cin Member Functions)

入力ストリームオブジェクトcinは,cin.getline( ), cin.get( ),cin.ignore( ),cin.putback( ),そしてcin.peek( )の入力関数を含んでいる.これらの関数はみな頭にcinが付く.なぜなら,これらは皆cinオブジェクトのメンバ関数だからである.

cin.getline(str,n)はキーボードからの入力をn文字までstrに読み込み,その後は無視する.

例題 8..3   エコー
キーボード入力をそのまま出力するプログラムを作成せよ.

解答 キーボード入力を格納する変数を文字配列char line[80]とする.文字配列line[80]に文字が入っていたら,その文字を出力すればよい.文字がline[80]に入ってない場合,NULL文字'$\yen$0'が入っているので,line[0]の別名,$\ast$lineによりテストできる.では,プログラムを作成しよう.

#include <iostream>
using namespace std;
int main( )
{
    char line[80];
    do {
        cin.getline(line,80);
        if(*line) {
            cout << "\t[" << line << "]" << endl;
        }
    }while(*line);
}

実行結果

\begin{figure}% latex2html id marker 2693
\centering
\includegraphics[width=12.35cm]{CPPPIC/reidai8-3.eps}
\end{figure}

条件(*line)はlineが空でない文字配列を含んでいれば,"true"を返す.

メンバ関数cin.getline(str,n,ch)はキーボード入力を文字chが現れるまでstrに読み込む.もし,chが改行'$\yen$n'で置き換えられたならば, cin.getline(str,n,'$\yen$n')はcin.getline(str,n)と同じである.

cin.get( )関数は入力を1文字ずつ読むのに用いられる.cin.get(ch)は入力ストリームcinから次の文字を変数chにコピーし,end of fileでないかぎり1を返す.

練習問題 8..2   上のプログラムでcin.getline(line,10)とすると,出力はどうなるか考え,実際にプログラムを実行し確認せよ.

例題 8..4   文字検索
キーボードから入力される次の文章の中に含まれる文字eの個数を数えるプログラムを作成せよ.
The vector class provides an alternative representation to the built -in array.

解答 キーボード入力を1文字ずつ読み込むために,cin.get()関数を用いる.もし,読み込んだ文字がeならば,個数を数えるcountを1つ増やせばよい.では,プログラムを作成しよう.

#include <iostream>
using namespace std;
int main( )
    char ch;
    int count=0;
    while(cin.get(ch)){
        if(ch == 'e')     ++count;
    }
    cout << count << "個のeがあります" << endl;
}
このプログラムを実行し,キーボードから文章を打ち込んだあと,CTRL+Dを押すと,結果が表示される.

実行結果

\begin{figure}% latex2html id marker 2715
\centering
\includegraphics[width=13.0cm]{CPPPIC/reidai8-4.eps}
\end{figure}

cin.get()と反対のことを行うのがcout.put()である.cout.put( )関数は1文字ごと出力ストリームcoutに書き込む.

例題 8..5   小文字から大文字
キーボードから入力される次の文章のそれぞれの単語の先頭を大文字にして画面に出力するプログラムを作成せよ.
the vector class provides an alternative representation to the built -in array.

解答 単語の先頭であることを知るにはどうすればよいだろう.取り出した文字の1つ前の文字が空白文字(' ')または改行文字('$\yen$n')ならば,取り出した文字は単語の先頭文字である.次に,小文字を大文字に変換するにはtoupper()関数を用いる.この関数を用いるには,ヘッダファイル$<$cctype$>$をインクルードする必要がある.では,プログラムを作成しよう.

#include <iostream>
#include <cctype>
using namespace std;
int main()
{
    char ch, pre = ' ';
    while(cin.get(ch)){
        if(pre == ' '){
            cout.put(char(toupper(ch)));
        }
        else cout.put(ch);
        pre = ch;
    }
}

実行結果

\begin{figure}% latex2html id marker 2729
\centering
\includegraphics[width=14.55cm]{CPPPIC/reidai8-5.eps}
\end{figure}

cin.putback( )関数はcin.get(ch)で読まれた最後の文字を入力ストリームcinに戻すのに用いられる.cin.ignore( )関数は入力ストリームの文字を無視するのに用いられる.

例題 8..6   整数の取り出し
次の文章から整数を抜き出しその和を求めるプログラムを作成せよ.305たす945は?

解答 cin.get(ch)で読まれた文字が,数字だったらcin.putback(ch)で入力ストリームcinに戻す.その後,cinの文字列から整数だけをcin $>>$ nでnに読み込む.

#include <iostream>
using namespace std;
int nextInt();
int main()
{
    int m=nextInt(), n = nextInt();
    cin.ignore(80,'\n');
    cout << m << "+" << n << "= " << m+n << endl;
}
int nextInt()
{
    char ch;
    int n;
    while(cin.get(ch)){ //1文字読む
        if(ch >= '0' && ch <= '9'){ // 整数かどうかの判断
            cin.putback(ch); // 整数をcinに戻す
            cin >> n;
            break;
        }
    }
    return n;
}

実行結果

\begin{figure}% latex2html id marker 2740
\centering
\includegraphics[width=8.4cm]{CPPPIC/reidai8-6.eps}
\end{figure}

練習問題 8..3   cin.peek()関数
cin.peek()関数は入力ストリームcinの次の文字を,入力ストリームから除くことなく文字型変数chにコピーする.このことを利用して,次の文章から整数を抜き出しその和を求めるプログラムを作成せよ.
1235たす945は?

標準C文字関数(Standard Character Functions)

例題8.5でtoupper()関数について説明した.この関数は$<$cctype$>$で定義されている関数の1つである.以下の表に$<$cctype$>$で定義されている関数をまとめておく.

isalnum( ) int isalnum(int c);
  cがアルファベットまたは数字なら0以外を返し,そうでなければ0を返す.
isalpha( ) int isalpha(int c);
  cがアルファベットなら0以外を返し,そうでなければ0を返す.
iscntrl( ) int iscntrl(int c);
  cがコントロール文字なら0以外を返し,そうでなければ0を返す.
isdigit( ) int isdigit(int c);
  cが数字なら0以外を返し,そうでなければ0を返す.
isgraph( ) int isgraph(int c);
  cが空白でないプリント文字なら0以外を返し,そうでなければ0を返す.
islower( ) int islower(int c);
  cがアルファベットの小文字なら0以外を返し,そうでなければ0を返す.
isprint( ) int isprint(int c);
  cがプリント文字なら0以外を返し,そうでなければ0を返す.
ispunct( ) int ispunct(int c);
  cがアルファベット,数字,空白以外なら0以外を返し,そうでなければ0を返す.
isspace( ) int isspace(int c);
  cがブランク' ',改行'\n',リターン'\r',水平タブ'\t',垂直タブ'\v',
  フォームフィード'\f'を含む空白文字なら0以外を返し,そうでなければ0を返す.
isupper( ) int isupper(int c);
  cが大文字なら0以外を返し,そうでなければ0を返す.
isxdigit( ) int isxdigit(int c);
  cが0から9までの10個の数,'a', 'b', 'c', 'd', 'e', 'f' , 'A', 'B', 'C', 'D', 'E', 'F'の
  12個の16進数の数なら0以外を返し,そうでなければ0を返す.
tolower( ) int tolower(int c);
  cが大文字ならば,小文字のcを返し,そうでなければcを返す.
toupper( ) int toupper(int c);
  cが小文字ならば,大文字のcを返し,そうでなければcを返す.

これらの関数は整数型の引数cをとり,整数型を返す.

文字配列の配列(Arrays of C-Strings)

2次元配列はそれぞれの成分が1次元配列である1次元配列と考えることができる.これらの成分配列が文字配列であるとき,文字配列の配列という.例えば,char name[5][20]と宣言すると,縦5横20の合計100バイトのメモリを確保する.そして,文字配列はname[0], name[1], name[2], name[3], name[4]に格納される.

  0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
0                                        
1                                        
2                                        
3                                        
4                                        

例題 8..7   文字配列の配列
19文字以下の4人までの名前をキーボードから入力し,それらを文字配列の配列として格納し,ディスプレイに表示するプログラムを作成せよ.

解答 文字配列の配列をchar name[5][20]とし,cin.getline(str,n)でreturnキーを押すまでに入力された文字列を文字配列として,name[5][20]に読み込む.次に,文字配列の配列の要素を順に出力すればよい.では,プログラムを作成しよう.

#include <iostream>
using namespace std;
int main()
{
    char name[5][20];
    int count=0;
    cout << "19文字以下の4人の名前を入力してください" << endl;
    while(cin.getline(name[count++],20)){
        ;
    }
    --count;
    cout << "名前は" << endl;
    for(int i=0; i < count;i++){
        cout << "\t" << i << ". [" << name[i] << "]" << endl;
    }
}

実行結果 EOFはCtrl+ZまたはCtrl+Dで知らせる.

\begin{figure}% latex2html id marker 2775
\centering
\includegraphics[width=9.85cm]{CPPPIC/reidai8-7.eps}
\end{figure}

この結果を図で表すと,次のようになる.

  0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
0 G e o r g e   B u s h $\yen$0                
1 G e o r g e   W a s h i n g t o n $\yen$0    
2 G e o r g e   A n d r e w $\yen$0            
3 G e o r g e   J o h n s o n $\yen$0          
4                                        

文字列配列(String Array)

この宣言はメモリの確保を行わないので,自分でbuffer文字配列にまず格納する必要がある.

例題 8..8   文字列配列
19文字以下の4人までの名前をキーボードから入力し,それらを文字列配列char* name[4]に格納し,ディスプレイに表示するプログラムを作成せよ.

解答 まず,
char buffer[80];
cin.getline(buffer,80,'$')関数を用意する.この関数はキーボードから'$'が入力されるまで,空白文字を含むすべてをbufferに格納する.つまり,
George Bush
George Washington
George Andrew
George Johnson George Bush
$
と入力すると,cin.getline(buffer,80,'$')により,bufferに1行で
George Bush$\yen$nGeorge Washington$\yen$nGeorge Andrew$\yen$nGeorge Johnson$\yen$n
が格納される.さて,どうやれば文字配列bufferから名前だけを取り出すことができるだろうか.char* p = bufferで,bufferのポインタを用意し,*p=$\yen$nになったら,*p = $\yen$0に置き換えればよい.そこで,
char* name[5];
と文字列配列nameを用意し,name[0]にbufferを代入し,最初の改行文字をナル文字に置き換える.次に,改行文字の次の文字のアドレスをname[1]に格納する.これを最後の改行文字まで繰り返す.

#include <iostream>
using namespace std;
int main()
{
    char buffer[80];
    cout << "4人までの名前を入力してください" << endl;
    cin.getline(buffer,80,'$');
    char* name[5];
    name[0] = buffer;
    int count = 0;
    for(char* p=buffer; *p != '\0'; p++){
        if(*p == '\n'){
            *p = '\0';
            name[++count] = p + 1;
        }
    }
    cout << "名前は" << endl;
    for(int i=0; i < count;i++){
        cout << "\t" << i << ". [" << name[i] << "]" << endl;
    }
}

実行結果

\begin{figure}% latex2html id marker 2801
\centering
\includegraphics[width=7.8cm]{CPPPIC/reidai8-8.eps}
\vspace{-0.5cm}
\end{figure}

文字配列型を効率よく格納するには,ポインタを用いる.

例題 8..9   文字列配列の初期化
文字列 {日曜,月曜,火曜,水曜,木曜,金曜,土曜}を
weekDaysという変数名を持った文字配列に格納し,週の3番目の曜日を表示せよ.

解答 文字列の格納にはポインタを用いる.

#include <iostream>
using namespace std;
int main ()
{
    char* weekDays [] = {"日曜","月曜","火曜","水曜","木曜","金曜","土曜"};
    char* s = weekDays[2];
    cout << "weekDays[2]=" << s << "です\n";
}

実行結果

\begin{figure}% latex2html id marker 2812
\centering
\includegraphics[width=7.5cm]{CPPPIC/reidai8-9.eps}
\end{figure}

ここで, char* weekDays [] = {"日曜","月曜","火曜","水曜","木曜","金曜","土曜"};で
weekDays[0]="日曜"
weekDays[1]="月曜"
……
weekDays[6]="土曜"
と7つの変数が定義され, char* s = weekDays[2];で文字列 "火曜" がsに代入される.

標準文字配列関数(Standard C-string Functions)
Cヘッダファイル$<$cstirng$>$は下の表に示すように文字配列の操作を行うのに便利な関数を多く含んでいる.

memcpy( ) void* memcpy(void* s1, const void* s2, size_t n);
  s1の最初のnバイトをs2の最初のnバイトで置き換える.sを返す.
strcat( ) char* strcat(char* s1, const char* s2);
  s2をs1に追加する.s1を返す.
strchr( ) char* strchr(const char* s, int c);
  文字列s内でcがおきた場所をポインタで返す.
strcmp( ) int strcmp(const char* s1, const char* s2);
  文字列s1とs2を字引の順で比較し,s1がs2に比べて小さい,等しい,
  大きいの順に負の整数,零,正の整数を返す.
strcpy( ) char* strcpy(char* s1, const char* s2);
  s1をs2で置き換える.s1を返す.
strcspn( ) size_t strcspn(char* s1, const char* s2);
  s2に含まれる文字を1つも含まず,s1[0]で始まるs1の部分文字列で
  一番長いものの長さを返す.
strlen( ) size_t strlen(const char* s);
  文字列sの長さを返す.
strncat( ) char* strncat(char* s1, const char* s2, size_t n);
  文字列s2の最小のn文字をs1に追加する.
srncmp( ) int strncmp(const char* s1, const char* s2, size_t n);
  文字列s1とs2の最初のn文字を字引の比較し,s1がs2に
  比べて小さい,等しい,大きいの順に負の整数,零,正の整数を返す.
strncpy( ) char* strncpy(char* s1, const char* s2, size_t n);
  文字列s1の最初のn文字を文字列s2の最初のn文字で置き換える.
  n $\geq$ strlen(s2)ならば,strncpy(s1,s2,n)とstrcpy(s1,s2)は同じ効果がある.
strpbrk( ) char* strpbrk(const char* s1, const char* s2);
  文字列s1において,文字列s2の文字が最初に現れたときのアドレスを返す.
strrchr( ) char* strrchr(const char* s, int c);
  文字列sにおける最後にcが現れたときのポインタを返す.
strspn( ) size_t strspn(char* s1, const char* s2);
  文字列s2の文字だけを含む,s1[0]で始まる文字列s1の部分列のなかで
  一番長いものの長さを返す.
strstr( ) char* strstr(const char* s1, const char* s2);
  文字列s1の部分列として始めてs2が現れたときのアドレス.
strtok( ) char* strtok(char* s1, const char* s2);
  文字列s1を文字列s2で指定された文字を用いて書き直す.

練習問題 8..4   strcpy()関数
次のプログラムの実行結果はどうなるか考え,実際にプログラムを実行せよ.

#include $<$cstring$>$
#include $<$iostream$>$
using namespace std;
int main()
{
char s1[] = "ABCDEFG";
char s2[] = "XYZ";
cout $<<$ "strcpy(s1,s2)を実行する前" $<<$ endl;
cout $<<$ "s1 = " $<<$ s1 $<<$ endl;
cout $<<$ "s2 = " $<<$ s2 $<<$ endl;
strcpy(s1,s2);
cout $<<$ "strcpy(s1,s2)を実行した後" $<<$ endl;
cout $<<$ "s1 = " $<<$ s1 $<<$ endl;
cout $<<$ "s2 = " $<<$ s2 $<<$ endl;
}

構造体(Structures)

配列が同じデータ型の集まりを1つの変数として定義したものであるのに対して,構造体は複数の異なったデータ型の集まりを1つの変数として定義したものである.構造体の定義は,構造体の型と構造体変数を宣言することにより行なわれる.

struct 構造体タグ名
{
    データ型 メンバ名;
    データ型 メンバ名;
};
により構造体の型の宣言が行なわれる.次に,
struct 構造体タグ名 構造体変数名
により構造体変数の宣言が行なわれる.これらをまとめて,次のように構造体の型と構造体変数の宣言をすることもできる.
struct 構造体タグ名
{
    データ型 メンバ名;
    データ型 メンバ名;
}構造体変数名;
こうやって定義した構造体のメンバの参照は
構造体変数名.メンバ名;
により行なわれる.

ピリオド(.)は,構造体変数とメンバを結びつけるための演算子で,ドット演算子と呼ばれている.

構造体ポインタ

構造体はポインタ変数を用いて扱うことができ,構造体を指すポインタ変数を構造体ポインタという.

struct 構造体タグ名 *構造体変数名;
により,構造体ポインタの宣言が行なわれる.また,ポインタによる構造体メンバの参照は
構造体ポインタ変数名-> メンバ名;
により行なわれる.(-$>$)は,構造体ポインタ変数とメンバを結びつけるための演算子で,アロー演算子と呼ばれている.

C++のクラスはCの構造体を一般化したものである.構造体は公開なメンバ変数だけを持ったクラスである.そこで,クラスのことをメンバ変数により命を与えられ,非公開メンバ変数により情報隠蔽を行える構造体であると考えることができる.

C言語と互換性を保つため,C++言語にも構造体を定義するための識別子 structを用意している.しかし,C++の構造体は基本的にC++のクラスと同じである.C++の構造体とC++のクラスとの唯一つ違いは,C++のクラスでアクセス指定をしないと非公開であると設定され,C++の構造体では公開であると設定されることである.

オブジェクトへのポインタ(Pointers to Objects)

多くのアプリケーションの開発において,オブジェクトへのポインタを用いるほうがよいプログラム技法である.

アロー演算子-$>$は,単項の後置演算子として定義できる.

日付と時間(Date and Time)

日付や時間を知るには$<$ctime$>$で宣言されているtime関数とlocaltime関数を用いる.接頭語にcが付いているヘッダファイルはC言語のスタイルをそのまま用いていることを意味している. time関数は,現在のカレンダ時間を返す関数で型time_tはlong型を$<$ctime$>$でtypedef宣言している. localtime関数は構造体struct tmのポインタ型を返す関数であり,構造体変数tmのメンバは次のようになっている.

int tm_sec; //second after the minute - [0,59]
int tm_min; //minute after the hour - [0,59]
int tm_hour; //hours after the midnight - [0,23]
int tm_mday; //day of the month - [1,31]
int tm_mon; //months since January - [0,11]
int tm_year; //years since 1900
int tm_wday; //days since Sunday - [0,6]
int tm_yday; //days since January 1 - [0,365]
int tm_isdst; //daylight savings time flag

例題 8..10   時刻
プログラムを実行した時間が何時かを返すプログラムを作成せよ.

解答 日付や時間を知るには,まずtime関数でカレンダ時間を求め,この時間をlocaltime関数で読み込み構造体ポインタ変数tpに代入する.この後,時間が知りたければtp-$>$tm_hourを表示すればよい.

#include <iostream>
#include <ctime>
using namespace std;
int main()
{
    struct tm *tp;
    time_t t;
    t = time(&t);// カレンダ時間をtに代入
    tp = localtime(&t);//カレンダ時間をローカル時間に変更
    cout << "現在の時刻は" << tp-> tm_hour << "時です" << endl;    
}

実行結果

\begin{figure}% latex2html id marker 2885
\centering
\includegraphics[width=7.5cm]{CPPPIC/reidai8-10.eps}
\end{figure}

例題 8..11   日時
何年何月何日何時何分何秒を表示するプログラムを作成せよ.

解答 年数は1900年から何年経過したかなので,tm_year+1900とする.また,月は1月から何ヶ月で示すので,tm_month+1とする.

#include <iostream>
#include <ctime>
using namespace std;
int main()
{
    struct tm *tp;
    time_t t;
    t = time(&t);
    tp = localtime(&t);
    cout << tp -> tm_year+1900 << "年" << tp -> tm_mon+1
    << "月" << tp->tm_mday << "日" << tp-> tm_hour <<< "時" <<
    tp-> tm_min << "分" << tp-> tm_sec << "秒" << endl;
}
==============================================

実行結果

\begin{figure}% latex2html id marker 2894
\centering
\includegraphics[width=7.5cm]{CPPPIC/reidai8-11.eps}
\end{figure}

例題 8..12   午前か午後
プログラムを実行した時間が午前か午後かを判断して,午前なら午前です,午後なら午後ですと返すプログラムを作成せよ.

解答 まず,午前か午後かを判断する関数を作る.この関数をapre_midii()という名前にする.

apre_midii()は次のようになる.

int apre_mid(void)
{
    struct tm *tp;
    time_t t;
    if( (t = time(&t)) == -1){
        cerr << "time function error " << endl;
        exit(1);
    }
    tp = localtime(&t);
    return(tp -> tm_hour < 12);
}

では,プログラムを作成しよう.

#include <iostream>
#include <ctime>
using namespace std;
int apre_mid(void);
int main()
{
    if(apre_mid()){
        cout << "午前です" << endl;
    }
    else {
        cout << "午後です" << endl;
    }
}

int apre_mid(void)
{
    struct tm *tp;
    time_t t;
    if( (t = time(&t)) == -1){
        cerr << "time function error" << endl;
        exit(1);
    }
    tp = localtime(&t);
    return(tp -> tm_hour < 12);
}

実行結果

\begin{figure}% latex2html id marker 2903
\centering
\includegraphics[width=7.5cm]{CPPPIC/reidai8-12.eps}
\end{figure}

確認問題 8..1  

1. 変数sの宣言について以下の問いに答えよ.

char s[6];

char s[6] = {'H', 'e', 'l', 'l', 'o'};

char s[6] = "Hello";

char s[];

char s[] = new char[6];

char s[] = {'H', 'e', 'l', 'l', 'o'};

char s[] = new("Hello");

char* s;

char* s = new char[6];

char* s = {'H', 'e', 'l', 'l', 'o'};

char* s = "Hello";

char* s = new("Hello");

a. C++の文字配列の宣言として有効なのはどれか.

b. 長さが5のC++の文字配列で,"Hello"と初期化されコンパイル時にメモリが割り当てられるのはどれか

c. 長さが5のC++の文字配列で,"Hello"と初期化され実行時にメモリが割り当てられるのはどれか

d. 関数の形式的な引数として有効なのはどれか

2. 文字配列sに"Hello, World!"と読み込むのに,

cin $>>$ s;

はなぜいけないのか述べよ.

3. 次のコードは何を表示するか.

char s[] = "1326 2nd St., Brookings, SD";

for (char* p = s; *p; p++)

if(isupper(*p)) *p = tolower(*p);

cout $<<$ s $<<$ endl;

4. 次のコードは何を表示するか.

char s[] = "1326 2nd St., Brookings, SD";

int count = 0;

for (char* p = s; *p; p++)

if(ispunct(*p)) ++count;

cout $<<$ s $<<$ endl;

5. s1とs2がchar*型のとき,次のコードの違いを述べよ.

s1 = s2;

strcpy(s1,s2);

演習問題 8..1  

1. strcpy()関数を作成せよ.

2. strncat()関数を作成せよ.

3. strncpy()関数を作成せよ.

4. strcmp()関数を作成せよ.

5. 次のプログラムはなぜ動かないのかを説明しなさい.

int main()
 {
char name[10][20], buffer[20];
int count = 0;
while (cin.getline(buffer,20)){
name[count$++$] = buffer;
  }
  $–$count;
cout $<<$ "名前は" $<<$ endl;
for (int i = 0; i $<$ count; i++){
cout $<<$ "$\yen$t" $<<$ i $<<$ ".[" $<<$ endl;
 }