全ての定数は初期化されなければならない. |
4.4 break文により,ループの途中でもループを終了することができる.
演習問題解答
a. while(e) s
b. while(true) {s; e;}
int i = 1;
while(i n) {
cout i*i " ";
i++;
}
i | x |
4.15 | |
0 | 8.3 |
1 | 16.6 |
2 | 33.2 |
例えば,123を入力したとする.このとき,末尾の3を取り出すには,123%10より3を得る.また,123から3を取り除くには,123/10より12となり,3を除ける.
int main()
{
long m,n,n=;
cout "正の数を入力してください";
cin m;
while(m 0){
d = m%10;
m /= 10;
}
n = 10 * n + d;
cout "反転すると" n endl;
}
int main()
{
int m,n,r;
cout "2つの正の数を入力してください";
cin m n;
if(m n){
int w = m;
m = n;
n = w; }
cout m "と" n "の最大公約数は";
while(n 0){
r = m%n;
m = n;
n = r;
}
cout m endl;
}
確認問題解答
別々にコンパイルされた関数は,ブラックボックスとして扱うことができる.関数が隅々まで一度チェックされれば,ユーザは関数がどのように働くかを気にする必要がなくなり,メイン関数の開発に集中することができるようになる.また.将来関数が改良されても,メイン関数には影響を与えることなく関数だけを入れ替えればよい.
関数は呼ばれる前ならどこでも宣言することができる.
関数の定義を別のファイルで行なっておくことの利点は,その関数を呼ぶ関数に変更があったとしても,エディタに呼び込む必要がない.
引数による受け渡しは,対応する仮引数は実引数のコピーである.しかし,参照による受け渡しでは,対応する仮引数は実引数の別名である.
この関数は,ディフォルトの値を持っている変数bがディフォルトの値を持っていない変数cの前にある.これは,全てのディフォルト値は他の引数の後に来なければならないという関数の約束に違反している.
演習問題解答
y % 4 == 0 && (y % 100 != 0 y % 400 == 0)の方が効率がよい.なぜなら,もしyが4で割り切れなければそれ以上のテストを行なう必要がないからである.
int min(int, int, int, int);
int main()
{
cout "4つの整数を入力";
int x, x, y, z;
cin w x y z;
cout "最小値は" min(w,x,y,z) endl;
}
int min(int n1, int n2, int n3, int n4)
{
int min = n1;
if(n2 min) min = n2;
if(n3 min) min = n3;
if(n4 min) min = n4;
return min;
}
long comb(int n, int k);
int main()
{
for (int i = -1; i 6; i++){
for (int j = -1; j i+1; j++){
cout " " comb(i,j);
}
cout endl;
}
}
long fact(int n);
long comb(int n, int k)
{
if (n 0 k 0 k n) return 0;
return fact(n)/(fact(k)*fact(n-k));
}
long fact(int n)
{
if (n 2) return 1;
long f = 1;
for(int i=2; i n; i++)
f *= i;
return f;
}
確認問題解答
6.1
配列の要素は全て同じ型でなければならない.したがって1通り.
6.2
配列の添え字は整数型で,その範囲は0からn-1である.ただし,nは配列の大きさ.
6.3
配列の大きさより初期化の値が少ないときには,配列の添え字の小さいほうから初期化され,残りは0に初期化される.
6.4
enumは符号なしの整数型である列挙型を定義する.typedefはすでにある型の別名を宣言する.
演習問題解答
void selection_sort(float a[], int n)
{ //a[k] = max{a[0],a[1],...,a[n-i]}
for (int i=1; i n; i++){
int k = 0;
for(int j=1; j n-i;j++){
if (a[j] a[k]) k = j;
}
swap(a[k], a[n-i]);
}
}
void insertion_sort(float a[], int n)
{
for (int i=1; i n; i++){//a[i]を挿入
float x = a[i];
int j = 1;
while(j 0 && a[j-1] x)
a[j] = a[j-1]l
a[j] = x;
}
}
確認問題解答
7.3 int n1 = n;はn1をnのクローンとして定義する.つまり,n1はnと同じ値を持つ別のオブジェクトである.int& n2 = n;はn2をnの別名として定義する.したがって,n2とnは同じアドレスを持ったオブジェクトである.
7.4 int& r = n;はrを整数型変数nの別名として宣言する.代入p = &nはnのアドレスをポインタpに代入する.
a. &xと&yはxとyの別名であるから,&x == x,&y == y.したがって,x == yならば&x == &y
b. 間違い.異なるオブジェクトでも同じ値を持つことができる.
a. 定数のアドレスはアクセスできないので,定数の参照(別名)を持つことはできない.
b. a.と同じ理由
c. 変数pはchar型である.&cはcharへのポインタ型を持っている.pを&cで初期化するには,pはchar*型で初期化されなければならない.
7.7 配列名aは定数ポインタなので,増加することができない.したがって,
short a[20];
short* p = a;
for(int i = 0; i 20;i++)
*p++ = i*i;
と書き直せばよい.
x p d q よりpはfloat型へのポインタで,qはshort型へのポインタである.したがって,qをpに代入することはできない.
7.9 p + qは不可能.なぜなら,ポインタどうしの和は取れない.
a. float a[8];
b. float* a[8];
c. float(*a)[8];
d. float* (*a)[8];
演習問題解答
===================================================
double* copy(double a[], int n)
{
double* p = new double[n];
for (int i=0; i n; i++){
p[i] = a[i];
}
return p;
}
void print(double [], int)
int main()
{
double a[8] = {22.2, 33.3, 44.4, 55.5, 66.6, 77.7, 88.8, 99.9}
print(a,8);
double* b = copy(a,8);
a[2] = a[4] = 11.1;
print(a,8);
print(b,8);
}
===================================================
7.2 riemann()関数は端点aとbの値と,h = (b-a)/nよりnの値を用いて f(a)*h + f(a+h)*h + + f(b-h)*hの値を戻す.よって,
double riemann(double (*)(double), double, double, int);
と宣言する. より,
===================================================
double riemann(double (*)(double), double, double, int);
double cube(double);
int main()
{
cout riemann(cube, 0, 2, 100) endl;
}
double riemann(double (*pf)(double t), double a, double b, int n)
{
double s= 0, h = (b-a)/n, x;
int i = 0;
for(x = a, i = 0; i n; x += h, i++)
s += (*pf)(x);
return s*h;
}
double cube(double t)
{
return t*t*t;
}
===================================================
7.3 公式より関数 をで微分するには,小さいhが必要である.そこで,微分係数を求める関数をderivative()関数とすると,
===================================================
double derivative)double (*)(double), double, double);
double cube(double);
int main()
{
cout derivative(cube, 1, 0.01) endl;
}
double derivative(double (*pf)(double t), double a, double h)
{
return ((*pf)(a + h) - (*pf)(a - h)/(2 * h));
}
double cube(double t)
{
return sqrt{t};
}
===================================================
a. n = 100のとき,4.65165
b. n = 100のとき,1.00782
c. n = 100のとき,1.7097
d. n = 100のとき,0.991312
練習問題解答
===================================================
#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(ch = cin.peek()){
if(ch '0' && ch '9'){
cin n;
break;
}
else cin.get(ch);
}
return n;
}
===================================================
確認問題解答
8.1
a. 次の8個が文字配列の宣言である.
char s[6];
char s[6] = {'H', 'e', 'l', 'l', 'o'};
char s[6] = "Hello";
char s[] = {'H', 'e', 'l', 'l', 'o'};
char s[] = "Hello";
char* s;
char* s = new char[6];
char* s = "Hello";
b. 次の4個が文字配列"Hello"と初期化される.
char s[6] = {'H', 'e', 'l', 'l', 'o'};
char s[6] = "Hello";
char s[] = "Hello";
char* s = "Hello";
c. 実行時にこのように初期化することは不可能である.
d. 次の2つが関数の引数として有効である
char s[];
char* s;
8.2 cin sは最初の空白文字までしか読み込まない.したがって,"Hello,"がsに読み込まれる.
8.3 これは,文字配列s内の大文字を小文字に変えるので,1326 2nd st, brooking, sdと表示される.
8.4 これは,文字配列s内の大文字の個数を数えるので,4が表示される.
8.5 代入s1 = s2により,s1はs2の別名となる.つまり,両方とも同じ文字のポインタである.strcpy(s1,s2)は文字配列s2の文字を文字配列s1にコピーする.したがって,s1はs2の複製である.
演習問題解答
8.1 文字配列s2を文字配列s1にコピーする関数
char* Strcpy(char* s1, const char* s2)
{
char* p;
for (p=s1; *s2; ){
*p++ = *s2++;
}
*p = '0';
return s1;
}
8.2
char* Strncat(char* s1, const char* s2, size_t n)
{
char* end;
for (p=s2; *p && p-s2 n; ){
*end++ = *p++;
}
*end = '0';
return s1;
}
8.3
char* Strncpy(char* s1, const char* s2, size_t n)
{
char* p = s1;
for (; n 0 && *s2;n ){
*p++ = *s2++;
}
for ( ; n 0; n){
*p++ = 0;
}
*p = '0';
return s1;
}
確認問題解答
9.1 文字配列は文字の配列であり,ナル文字を用いて文字列の終わりを表している.C++文字列はstringファイルで定義されたstring型のオブジェクトである.stringファイルにはlength()関数やreplace()関数など多くの文字列関係の関数がある.
char cs[8] = "ABCDEFG"; // csは文字配列
string s = "ABCDEFG"; // sはC++文字列
9.2 書式付入力は余白文字を無視する抽出演算子 を用いる.書式付でない入力はget()関数とgetline()関数を用いる.get()関数は余白文字を無視せずに文字を読み込み,getline()関数は改行文字'n'に出会うまで全ての文字を読み込む.
9.3 抽出演算子は全ての余白文字を無視するので余白文字(空白,タブ,改行,etc)を読むことができない.
9.4 ストリームとはプログラムとデータソースの間の入力や出力を管理しているオブジェクトである.C++ではcin, coutにはiostreamオブジェクトを,外部ファイルの入出力にはfstreamオブジェクトを,そして,内部ファイルの入出力にはsstreamを用いる.
9.5 文字列,外部ファイル,内部ファイルの処理に同じような関数を定義することにより簡素化を行なっている.
9.6 read()関数とwrite()関数は直接入力や直接出力に用いられる.例えば,input.read(s.c_str(),n)によりnバイト文字列sにファイルinputから読み込む.
演習問題解答
char cs1[] = "ABCDEFGHIJ"; // cs1を文字配列として定義
char cs2[] = "ABCDEFGH"; // cs2を文字配列として定義
cout cs2 endl; // 表示: ABCDEFGH
cout strlen(cs2) endl; // 表示:8
cs2[4] = 'X'; // cs2を"ABCDXFGH"に変更
if (strcmp(cs1,cs2) 0) cout cs1 " " cs2 endl;
else cout cs1 " " cs2 endl; // 表示:ABCDEFGHIJ ABCDXFGH
char buffer[80]; // bufferを80文字以下の文字配列として定義
strcpy(buffer, cs1); // bufferを"ABCDEFGHIJ"に変更
strcat(buffer, cs2); // bufferを"ABCDEFGHIJABCDXFGH"に変更
char* cs3 = strchr(buffer, 'G'); // cs3をbuffer[6]のポインタに変更
cout cs3 endl; // 表示:"GHIJABCDXFGH"
確認問題解答
10.1 公開メンバはクラスの外からもアクセス可能であるが,非公開メンバはクラスの外からはアクセスできない.
10.2 クラスメンバ関数はクラスの要素の一つなので,クラスの非公開部分にもアクセル可能である.アプリケーション関数はクラスの外部で宣言されているので,クラスの非公開メンバへのアクセスはできない.
10.3 ディフォルトコンストラクタは引数を持たない唯一のコンストラクタである.
10.4 C++におけるclassとstructは実質的に同じものである.大きな違いは,クラスではディフォルトでprivateであるのに対し,構造体ではディフォルトでpublicなところである.
10.5 コンストラクタはクラスと同じ名前でなければならない.また,ディストラクタもクラスと同じ名前であるが,名前の前にが付く.
10.6 個数の制限はないが引数で区別が付かなければならない.
10.7 全部で7回.fを一回呼ぶとコピーコンストラクタが3回呼ばれる.
演習問題解答
10.1
#include cmath
#include iostream
using namespace std;
class Point
{
public:
Point(float x = 0, float y = 0, float x=0): x_(x), y_(y), z_(z) { }
Point(const Point& p) : x_(p.x), y_(p.y), z_(p.z) { }
void negate() {x_ *= -1; y_ *= -1; z_ *= -1;}
double norm() {return sqrt(x_*x_ + y_*y_ + z_*z_); }
void print()
{cout '(' x_ y_ "," z_ ")";}
private:
float x_, y_, z_;
};
10.2
class Stack
{
public:
Stack(int s=10) : size(s), top(-1) {a = new int[size];}
Stack() {delete [] a;}
void push(const int& item) {a[++top] = item;}
int pop() {return a[top–];}
bool isEmpty() const {return top == -1;}
bool isFull() const {return top == (size-1);}
private:
int size;
int top;
int* a;
};
確認問題解答
11.1
予約語operatorは演算子を多重定義する関数名を作成するのに用いられる.例えば,代入演算子=を多重定義する関数名は"operator="となる.
11.2
*thisは常にメンバ関数をよぶオブジェクトを意味している.
11.3
thisポインタはメンバ関数を呼び出すオブジェクトのポインタである.したがって,メンバ関数以外ではthisポインタを用いることができない.
11.4
同じである.両方ともコピーコンストラクタを用いてxの複製であるyを作成する.
11.5
宣言Ratio y = x;はコピーコンストラクタを呼ぶ.Ratio y; y = x;はまず,ディフォルトコンストラクタを呼び,次に代入演算子を呼ぶ.
11.7
左側オペランドはストリームオブジェクトでなければならないからである.
演習問題解答
11.1
#include iostream
#include stdlib.h
using namespace std;
class Fraction
{
friend Fraction operator+(Fraction, Fraction);
friend Fraction operator-(Fraction, Fraction);
friend Fraction operator*(Fraction, Fraction);
friend Fraction operator/(Fraction, Fraction);
friend istream& operator(istream &, Fraction &);
friend ostream& operator(ostream &, Fraction);
private:
int numerator;
int denominator;
int gcd(int,int);
public:
Fraction(int = 1, int = 1);
void reduce();
void display();
Fraction operator-();
};
Fraction::Fraction(int n, int d)
{
numerator = n;
denominator = d;
if(d 0) {
numerator = - numerator;
denominator = -denominator;
}
}
int Fraction::gcd(int x, int y)
{
while(xint r = xx = y;
y = r;
}
return y;
}
void Fraction::reduce()
{
if (numerator != 0 && denominator != 0){
int tmp = gcd(abs(numerator), denominator);
numerator /= tmp;
denominator /= tmp;
}
}
void Fraction::display()
{
if (numerator == 0){
cout "0";
}
else if (denominator == 1){
cout numerator;
}
else{
cout numerator "/" denominator 'n';
}
}
Fraction Fraction::operator-()
{
Fraction tmp;
tmp.numerator = -numerator;
tmp.denominator = denominator;
return tmp;
}
Fraction operator+(Fraction left, Fraction right)
{
left.numerator = left.numerator*right.denominator
+ right.numerator*left.denominator;
left.denominator *= right.denominator; left.reduce();
return left;
}
Fraction operator-(Fraction left, Fraction right){
left.numerator = left.numerator*right.denominator - right.numerator*left.denominator;
left.denominator *= right.denominator;
left.reduce();
return left;
}
Fraction operator*(Fraction left, Fraction right)
{
left.numerator *= right.numerator;
left.denominator *= right.denominator;
left.reduce();
return left;
}
Fraction operator/(Fraction left, Fraction right)
{
left.numerator *= right.denominator;
left.denominator *= right.numerator;
left.reduce();
return left;
}
istream &operator(istream &istrm, Fraction &f)
{
char c; istrm f.numerator; istrm c;
if(c == ' '){
f.denominator=1;
}
else if (c =='/'){
istrm f.denominator;
}
else{
cerr "Illegal input format!!n";
}
return istrm;
}
ostream& operator(ostream& os, Fraction f)
{
if (f.numerator == 0){
os "0";
}
else if (f.denominator == 1){
os f.numerator;
}
else{
os f.numerator "/" f.denominator;
}
return os;
}
int main()
{
Fraction f,g;
char c;
cout "分数の計算をします.例a/b + c/dの形で入力してください.n";
cin f c g;
switch(c)
{
case '+' : f = f + g;
f.display();
break;
case '-': f = f - g;
f.display();
break;
case '*': f = f * g;
f.display();
break;
case '/': f = f / g;
f.display();
break;
default: cout "入力された式が正しくありません";
break;
}
}
確認問題解答
12.1
クラスの合成とは,一つ以上のクラスを別のクラスの定義の中で用いることである.それに対して,継承は基本クラスから派生クラスを派生させることである.
12.2
privateメンバはクラス定義の外からはアクセスが不可能である.protectedメンバもクラス定義の外からはアクセスが不可能であるが,派生クラスからはアクセスが可能である.
12.3
仮想関数とは,基本クラス内で宣言され,派生クラス内で再定義されるメンバ関数のことである.
12.4
抽象基本クラスとは,少なくとも1つの純粋仮想関数を含んでいるクラスのことである.
12.5
多相とは,仮想関数を持ったクラスでオブジェクトポインタを用いたときにおきる動的結合のことである.
12.6
具象導出クラスは抽象クラスの派生クラスで,インスタンス化ができるものである.つまり,純粋仮想関数を含まないものである.
12.7
静的結合とは,コンパイル時にメンバ関数の呼び出しを関数自身にリンクすることである.また,動的結合は,これを実行時まで延期する.
演習問題解答
12.1
protectedメンバ変数aは,それが現行のオブジェクトのメンバであるときだけ派生クラスYからアクセスが可能である.したがって,Yはx.aにアクセスすることができない.
12.2
この階層図では,ものの形を2次元と3次元に分けている.ここで,面積を表すarea()関数と表示用のprint()関数は,どの階層でも同じである.そこで,この2つを純粋仮想関数とする.次に,2次元の物体は周囲の長さを持ち,3次元の物体は体積を持っている.これより,基本抽象クラスは次のようになる.
const double PI=3.14159265338979;
class Shape
{
public:
virtual void print() = 0;
virtual float area() = 0;
};
class TwoDimensional : public Shape
{
public:
virtual float perimeter() = 0;
};
class ThreeDimensional : public Shape
{
public:
virtual float volume() = 0;
};
次に7つの具象派生クラスのうちCircleクラスとConeクラスを実装する.
class Circle : public TwoDimensional
{
public:
Circle(float r) : radius(r) { }
void print() {cout "形は円" endl;}
float area() {return PI*radius*radius;}
float perimeter() {return 2*PI*radius;}
private:
float radius;
};
class Cone : public ThreeDimensional
public:
Cone(float r, float h) : radius(r), height(h) { }
void print();
float area();
float volume() {return PI*radius*radius*height/3;}
private:
float radius, height;
};
void Cone::print()
{
cout "円錐の半径は" radius "高さは" height endl;
}
float Cone::area()
{
float s = sqrt(radius*radius + height*height);
return PI*radius*(radius + s);
}
確認問題解答
13.1
関数テンプレートは汎用関数を作成するのに用いるテンプレートである.テンプレート関数はテンプレートによって作成された関数である.例えば,swap(T&, T&)は関数テンプレートであるが,swap(m,n)によりテンプレート関数が生成される.
13.2
クラステンプレートはクラスを生成するのに用いられるテンプレートである.テンプレートクラスはテンプレートによって作られたくラスである.例えば,stackはクラステンプレートであるが,型Stackintはテンプレートクラスである.
13.3
ベクタは添え字演算子を用いることにより,その要素に直接アクセスできる.よって,要素が順に並んでいれば2分探索法で簡単にどこにあるか調べることが出来る.それに対して,リストは動的であるという利点を持っている.つまり,必要以上の容量をとらない.また,コンピュータのメモリの制限以外のサイズの制限がない.したがって,ベクタは時間の優位性があり,リストは容量の優位性がある.
演習問題解答
13.1
最小関数miniは同じ型の2つのものを比較し,小さい方を返す関数である.そこで,型は汎用型Tとし次のように定義する.
template class T
T mini(T x, T y){
return (x y ? x: y);
}
この関数を実装するためのテストドライバを次のようにすると,
#includeiostream
using namespace std;
int main()
{
cout "min(22,44) = " mini(22,44) endl;
cout "min(34.43, 43.34) = " mini(34.43, 43.34) endl;
}
13.2
template class T
class Queue
{
public:
Queue(int s = 100): size(s+1), front(0), rear(0)
{data = new T[size];}
Queue() {delete [] data; }
void insert(const T& x) {data[rear++ % size] = x; }
T remove() {return data[front++ % size];}
int isEmpty() const {return front == rear;}
int isFull() const {return (rear + 1) % size == front; }
private:
int size, front, rear;
T* data;
};
確認問題解答
14.1
配列とC++ベクタの違いは
a. 配列は string[8] a;と宣言されベクタは vector<string> v(8);と宣言される.
b. 代入演算子はベクタには定義されているが,配列にはない.したがって,配列では1つ1つの要素をコピーしなくてはならない.
c. 比較演算子はベクタには定義されているが,配列にはない.
if (v == w) //2つのベクトルが等しければ真
if (v w) //ベクトルの辞書順序により比較
d. size()メンバ関数がベクタには使えるが,配列では無理
e. at()メンバ関数がベクタには使えるが,配列では無理
string v8 = v.at(8); //ポジション8でのvの要素
14.2
a. 両方とも要素の読み込み,書き出しが可能
b. 両方とも増加と減少が可能
c. 両方とも相対位置の基底として用いることが可能
x = a[i+3];
x = *(it + 3);
演習問題解答
14.1
int frequency(vectorint v, int x)
{
int n=0;
for (vectorint::iterator it=v.begin(); ; it++){
it = find(it, v.end(), x);
if (it==v.end()) return n;
++n;
}
return n;
}
14.2
void remove_duplicates(vector int& v);
{
for(vectorint::iterator it=v.begin()+1; it!=v.end()){
vectorint::iterator jt = find(v.begin(),it, *it);
if(jt == it) ++it;
else it=v.erase(it);
}
} 2