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

目次へ戻る

5.1

    char*    p = "abc";      // 文字を指すポインタ
    int      a[10] = {0};    // 10個の整数の配列
    int      (&r)[10] = a;   // 10個の整数の配列のリファレンス
    char*    dat[] = {"x"};  // 文字列配列
    char*    (*s)[1] = &dat; // 文字列配列を指すポインタ
    char**   pp = dat;       // 文字ポインタを指すポインタ
    const int n = 1;         // 整数定数
    const int* pn = &n;      // 整数定数を指すポインタ
    int* const pi = 0;       // 整数を指すポインタ定数

5.2

キャストすれば、全てのポインタにおいて負を含む任意の値を設定しても コンパイルエラーにはならない。 しかし、ある変数のアドレスを代入する場合、その値はある範囲に収まる。 (但し、任意のポインタで、0 を代入することは合法である。) また、アラインメントが x の場合、TYPE* の値は、sizeof(TYPE) と x の 小さい方の倍数になる。 VC++5 の場合、アラインメントは「アライメント」と表記されており、 値を変更することが可能である。 アラインメントの結果は、sizeof や offsetof などで調べられる。

参考:アライメントとは

5.3

typedef unsigned char uchar;
typedef const unsigned char cuchar;
typedef int* pint;
typedef char** pchar;
typedef char achar[];
typedef int* aint7[7];
typedef aint7* pint7;
typedef aint7 aint78[8];
なんか違うような気もする。

5.4

void swap1(int* a,int* b){int c = *a; *a = *b; *b = *a;}
void swap2(int& a,int& b){int c =  a;  a =  b;  b =  a;}

5.5

15 と 15。

5.6

void f(char c){}
void g(char& c){}
void h(const char& c){}
文法違反は、引数が c でない g の呼出し全ての場合。 警告となるのは、引数が 3300 の f と h の呼出し。 一時変数を用意しないのは、引数が c の g と h の呼出し。 それ以外は、全て一時変数が用意される。

5.7

#include <cstdio>

struct Month {
    char*   s;
    int     d;
};

int main()
{
    char*   month[] = {"Jan","Feb","Mar","Apr","May",
        "Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
    int     days[] = {31,28,31,30,31,30,31,31,30,31,30,31};
    Month   m[] = {{"Jan",31},{"Feb",28},{"Mar",31},{"Apr",30},
        {"May",31},{"Jun",30},{"Jul",31},{"Aug",31},{"Sep",30},
        {"Oct",31},{"Nov",30},{"Dec",31}};
    int     i;
    for (i=0;i<12;++i)
        printf("%2d: %s %d%s",i+1,month[i],days[i],(i+1)%4?"   ":"\n");
    for (i=0;i<12;++i)
        printf("%2d: %s %d%s",i+1,m[i].s,m[i].d,(i+1)%4?"   ":"\n");
}
Month の char* s は char s[4] でもいい、main の char* month[] は char month[][4] でもよい。 char* の場合、ポインタの指している先のライフスパンを気にしなければいけない。 配列は、そのままでは代入できないが、構造体のメンバにすることによって代入が可能となる。 でも実際には、char* や char配列を使うよりは string を使った方が良い。

5.8

次のプログラムで確認した。
int main()
{
    const         n = 1024;
    volatile int  i,a[n],*p;
    for (i=0;i<n;++i) a[i] = 0;
    for (p=a;p!=a+n;++p) *p = 0;
}
VC++ の Releaseモードでアセンブラを見た。

「最適化」オプションが「実行速度」の時
0040100A   xor         ecx,ecx                          : ecx = 0
0040100C   mov         eax,400h                         : eax = 1024
00401011   mov         dword ptr [esp],ecx              : i = ecx
00401015   cmp         dword ptr [esp],eax              : if (i >= eax)
00401019   jge         00401032                         :     goto 401032
0040101B   mov         edx,dword ptr [esp]              : edx = i
0040101F   mov         dword ptr [esp+edx*4+4],ecx      : a[i] = 0
00401023   mov         edx,dword ptr [esp]              : edx = i
00401027   inc         edx                              : --edx
00401028   mov         dword ptr [esp],edx              : i = edx
0040102C   cmp         dword ptr [esp],eax              : if (i < eax)
00401030   jl          0040101B                         :     goto 40101B
00401032   lea         eax,[esp+4]                      : eax = a
00401036   mov         dword ptr [eax],ecx              : *eax = ecx
00401038   add         eax,4                            : ++eax
0040103B   lea         edx,[esp+1004h]                  : edx = a+n
00401042   cmp         eax,edx                          : if (eax != edx)
00401044   jne         00401036                         :     goto 401036
コロン以降は私の注釈。 40100C 〜 401030 までが添字による代入であり、 401032 〜 401044 までがポインタによる代入である。

「最適化」オプションが「プログラムサイズ」の時
0040100D   xor         ecx,ecx                          : ecx = 0
0040100F   mov         eax,400h                         : eax = 1024
00401014   mov         dword ptr [ebp-4],ecx            : i = ecx
00401017   cmp         dword ptr [ebp-4],eax            : if (i >= eax)
0040101A   jge         0040102F                         :     goto 40102F
0040101C   mov         edx,dword ptr [ebp-4]            : edx = i
0040101F   mov         dword ptr [ebp+edx*4-1004h],ecx  : a[i] = ecx
00401026   mov         edx,dword ptr [ebp-4]            : edx = i
00401029   inc         edx                              : --edx
0040102A   mov         dword ptr [ebp-4],edx            : i = edx
0040102D   jmp         00401017                         : goto 401017
0040102F   lea         eax,[ebp-1004h]                  : eax = a
00401035   mov         dword ptr [eax],ecx              : *eax = ecx
00401037   add         eax,4                            : ++eax
0040103A   lea         edx,[ebp-4]                      : edx = a+n
0040103D   cmp         eax,edx                          : if (eax != edx)
0040103F   jne         00401035                         :     goto 401035
40100F 〜 40102D までが添字による代入であり、 40102F 〜 40103F までがポインタによる代入である。

「ポインタの方が速い」という結果は VC++ のケースであって、 他のコンパイラでは、異なるかもしれない。ついでだから、 反復子のインクリメントについてもコメントしよう。

従って反復子のインクリメント等は、前置でも後置でもよい場合、習慣として前置にすべきである。

5.9

自分自身の名前で意味のある「値」は、おそらく自分自身のアドレスであろう。 具体例は思い付かないが、Any x = {&x}; のように書くのであろう。
但し、このような初期化が必要ならば、コンストラクタに任せるべきだと思う。

5.10

#include <cstdio>

void PrintMonth(char* m[])
{
    for (int i=0;i<12;++i)
        printf("%2d %s\n",i+1,m[i]);
}

int main()
{
    char*   m[] = {"Jan","Feb","Mar","Apr","May","Jun",
                "Jul","Aug","Sep","Oct","Nov","Dec"};
    PrintMonth(m);
}

5.11

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

int main()
{
    string          s;
    set<string>     tab;
    while (cin >> s) {
        if (s == "Quit") break;
        if (tab.find(s) != tab.end()) continue;
        tab.insert(s);
        cout << s << endl;
    }
    cout << "Input word";
    set<string>::iterator i;
    for(i=tab.begin();i!=tab.end();++i) cout << " " << *i;
    cout << endl;
}

5.12

#include <cstdio>
#include <string>
#include <algorithm>
using namespace std;

static void Print(const char* s)
{
    int     i,x[26];
    fill(x,x+26,0);
    while ((i = *s++) != 0)
        if (i >= 'a' && i <= 'z') ++x[i-'a'];
    for (i=0;i<26;++i) if(x[i]) printf("%c: %d\n",i+'a',x[i]);
}

int main()
{
    char*    t = "this is sample.";
    string   s = t;
    Print(t);
    Print(s.c_str());
}
4.9.5 節によれば、static でなくても配列は初期化されるはずであるが、 VC++5 ではならない。よって、fill で初期化している。

5.13

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

struct Date {
    int     month;
    int     day;
    Date(tm* t){month = t->tm_mon+1; day = t->tm_mday;}
};

istream& operator>>(istream& i,Date& d)
{
    return i >> d.month >> d.day;
}

ostream& operator<<(ostream& o,const Date& d)
{
    return o << d.month << "/" << d.day;
}

int main()
{
    time_t  l;
    time(&l);
    tm*     p = localtime(&l);
    Date    d(p);
    cout << d << endl;
    cin >> d;
    cout << d << endl;
}
範囲チェックなどは省いた。
前章目次次章