C++第3版 7章 練習問題解答

目次へ戻る

7.1

#include <iostream>
using namespace std;

typedef void (*FType)(char*,int&);
void dumy(char*,int&){cout << "test\n";}
FType func(FType f){return f;}

int main()
{
    FType   f = dumy;
    int     i;
    (*func(f))(0,i);
}

7.2

2つの int を引数に取り、int を返す関数の参照を型定義している。 参照なので必ず初期化しなければいけないので、 初期化を強制するメリットがある。 逆に言えば、融通が利かずわかりにくいというデメリットがある。 ところで、何故、
typedef int (*&rifii)(int,int);
では駄目なのであろうか?

7.3

#include <iostream>
using namespace std;

int main(int argc,char** argv)
{
    while (--argc)
        cout << "Hello, name " << *++argv << "!\n";
}

7.4

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

int main(int argc,char** argv)
{
    string      s;
    while (--argc) {
        ifstream    fi(*++argv);
        while (getline(fi,s))
            cout << s << endl;
    }
}

7.5

お茶濁し
int main(){}

7.6

void ssort(void* base,size_t n,size_t sz,CFT cmp)
{
    qsort(base,sz,n,cmp);
}

7.7

示された構造体では、空の状態を作れないので、入れ子クラスにした。 string を char* に変える場合は、string& と string を char* に、 比較を strcmp に変えれば良い。
#include <string>
#include <iostream>
using namespace std;

class Tree {
    struct Tnode {
        const string  word;
        int		      count;
        Tnode*        left;
        Tnode*        right;
        Tnode(const string& w) : word(w)
            {left = right = 0; count = 1;}
        ~Tnode(){delete left; delete right;}
        void Print() {
            if (left) left->Print();
            cout << word << ":" << count << endl;
            if (right) right->Print();
        }
        static void Push(Tnode*& p,const string& w) {
            if (!p) p = new Tnode(w);
            else if (w < p->word) Push(p->left,w);
            else if (p->word < w) Push(p->right,w);
            else ++p->count;
        }
    } *p;
    Tree(const Tree&);           // 定義なし
    void operator=(const Tree&); // 定義なし
public:
    Tree(){p = 0;}
    ~Tree(){delete p;}
    void Insert(const string& w){Tnode::Push(p,w);}
    void Print()const{if (p) p->Print();}
};

int main()
{
    Tree    t;
    t.Insert("cat");
    t.Insert("dog");
    t.Insert("panda");
    t.Insert("dog");
    t.Insert("lion");
    t.Print();
}

7.8

2次元配列の反転ってどうすればいいのだろう? 取りあえず、転置してみた。
#include <vector>
#include <iostream>
using namespace std;

template <typename T>
static void Print(const vector<vector<T> >& v)
{
    vector<vector<T> >::const_iterator  i;
    for (i=v.begin();i!=v.end();++i) {
        vector<T>::const_iterator  j;
        for (j=i->begin();j!=i->end();++j)
            cout << *j << " ";
        cout << endl;
    }
}

template <typename T>
void Trans(vector<vector<T> >& v)
{
    int     i,j, n = v.size();
    for (i=1;i<n;++i) for (j=0;j<n-i;++j)
        swap(v[j][i+j],v[i+j][j]);
}

int main()
{
    int                     i,j,n = 4;
    vector<int>             tmp(n);
    vector<vector<int> >    v(n,tmp);
    for (i=0;i<n;++i) for (j=0;j<n;++j)
        v[i][j] = i*n+j;
    Print(v);
    Trans(v);
    Print(v);
}

7.9

DOS だと暗号化された文字が ^Z になると複合するときに そこで入力終了になってしまう。 そこで setmode でバイナリモードにしている。 (UNIX なら必要ないはず。)
#include <io.h>
#include <fcntl.h>
#include <cstring>
#include <iostream>
using namespace std;

int main(int argc,char** argv)
{
    char    c, *key = argc > 1 ? argv[1] : "";
    int     i = 0, n = strlen(key);
    if (!n) n = 1;
    setmode(fileno(stdin),O_BINARY);
    setmode(fileno(stdout),O_BINARY);
    while (cin.get(c))
        cout << static_cast<char>(c^key[i++ % n]);
}

7.10

ヒントは見てない。 key を片っ端から試して、変な文字にならなかったら OK としている。 暗号データは、ファイル crypt.txt から読み込む。 key の文字数は、とりあえず 3文字で固定。
#include <io.h>
#include <fcntl.h>
#include <cstdio>
#include <cstring>
#include <sys/stat.h>

int Check(int n,const char* dat,char* est,
           const char* key,int sz)
{
    int     c,i;
    for (i=0;i<n;++i) {
        c = est[i] = dat[i]^key[i%sz];
        if (c != ' ' && c != '\r' && c != '\n' &&
            c != '.' && (c < 'a' || c > 'z')) return 0;
    }
    return 1;
}

int main()
{
    const   sz = 3;     // key の長さ
    int     i,n,fd;
    if (!(fd = open("crypt.txt",O_RDONLY | O_BINARY))) {
        fprintf(stderr,"no crypt file\n");
        return 1;
    }
    struct stat    st;
    if (fstat(fd,&st)) return 1;
    char   *dat,*est,key[sz+1];
    dat = new char[n = st.st_size];
    est = new char[n];
    read(fd,dat,n);
    close(fd);
    for (i=0;i<sz;++i) key[i] = 'a';
    while (true) {
        if (Check(n,dat,est,key,sz)) {
            printf("key = %s\n",key);
            for (i=0;i<n;++i) printf("%c",est[i]);
            break;
        }
        if (++key[(i = sz)-1] > 'z') {
            while (--i) {
                key[i] = 'a';
                if (++key[i-1] <= 'z') break;
            }
            if (i == 0) break;
        }
    }
    delete[] dat;
    delete[] est;
    return 0;
}

7.11

printf は使うなって書いてあるけど、vprintf は書いてないからなあ。
#include <cstdio>
#include <cstdarg>

void error(const char* fmt, ...)
{
   va_list va;
   va_start(va,fmt);
   vprintf(fmt,va);
   va_end(va);  
}

int main()
{
    error("%s %c %d\n","test",':',123);
}

7.12

例えば、function type のつもりで FType とする。

7.13

関数は大文字で始める。 何故なら、小文字で始めると、 標準ライブラリと名前がバッティングする可能性があるので。
変数名は小文字にする。メンバは m_ で始める。 それ以外は、アンダースコアはあまり使わず、 区切りは大文字にする。(例:GetWidth)
短い名前は、重要でない変数名で使う。

コーディングスタイルは、しばしば変えることがある。 使用するライブラリを変えるとき、そのライブラリに合わせて コーディングスタイルを変えたりする。
6.23参照。

7.14

PI は const double を用いた方がよい。 MAX は、((a)>(b)?(a):(b)) のように括弧をつけた方が安全であるが、 それでも副作用の可能性があるので、inline もしくは、template にした方がよい。 fac は再帰的定義になっており、マクロでは書けない。

7.15

チャレンジの解答を参照のこと。

7.16

void print(int v,int b = 10)
{
    char    buf[80];
    itoa(v,buf,b);
    printf("%s\n",buf);
}

7.17

6.18参照のこと。あっ、引数のチェックはしていない。

7.18

int fac(int n)
{
    int     i = 1;
    while (n > 1) i *= n--;
    return i;
}

7.19

AddYear では、実際には存在しない年月日になる可能性があるが対処していない。 (10.1参照)
struct Date {
	int     year;
	int     month;
	int     day;
	void Set(int y,int m,int d){year = y; month = m; day = d;}
	Date(int y=0,int m=1,int d=1){Set(y,m,d);}
	void AddYear(int i = 1){year += i;}
	void AddMonth(int i = 1){month += i; i = (month-1)/12 - (month<0?1:0);
		month -= i*12; if (i) AddYear(i);}
	void AddDay(int i = 1){int d; day += i;
		while (day < 1) {AddMonth(-1); day += Days();}
		while (day > Days()) {day -= Days(); AddMonth();}}
	int Days()const{int d[]={0,31,28,31,30,31,30,31,31,30,31,30,31};
		return month == 2 && IsLeap(year) ? 29 : d[month];}
	int Week()const // 0 is Sunday
		{int y = year, m = month-2;if (m < 1) {m += 12; --y;}
		return (y + y/4 - y/100 + y/400 + int(m*2.6-.1) + day)%7;}
	Date NextMonday()const{Date d = *this; int w = Week();
		d.AddDay((7-w)%7+1); return d;}
	Date CurrMonday()const{Date d = *this; d.AddDay(1-Week()); return d;}
	bool operator<(const Date& d)const{return year < d.year || year == d.year
		&& (month < d.month || month == d.month && day == d.day);}
	bool operator==(const Date& d)const{return year == d.year &&
		month == d.month && day == d.day;}
	static bool IsLeap(int y)
		{return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0);}
};

前章目次次章