プログラマーの卵の日記

備忘録になってます!小難しそうなことから超簡単なことまでやってます!!

読書による知見①(C++の文関係)

※怒られたら消します。

 

再帰

  再帰は綺麗ではあるが、多くの場合、不要な関数呼び出しのオーバーヘッドを招く。再帰を用いず、明示的にスタック構造を取り除けるのであれば、再帰は非再帰アルゴリズムに書き換えた方が良い。


#include <iostream>

void Num(int& num)
{
	if (num < 10)
	{
		Num(++num);
	}

	return;
}

int main()
{
	int number = 0;
	Num(number);

	std::cout << number << std::endl;

	return 0;
}

#include <iostream>

int main()
{
	int number = 0;
	
	for (int i = 0; i != 10; ++i)
	{
		number++;
	}

	std::cout << number << std::endl;

	return 0;
}

 

 

ポインタによる配列走査

  配列の要素を順番に処理する際に、ポインタ変数を使えば処理を早くすることが出来る。


#include <iostream>

int main()
{
	int value[10] = { 0,1,2,3,4,5,6,7,8,9 };

	for (int i = 0; i != 10; ++i)
	{
		std::cout << value[i] << std::endl;
	}

	return 0;
}

#include <iostream>

int main()
{
	int value[10] = { 0,1,2,3,4,5,6,7,8,9 };

	for (int i = 0; i != 10; ++i)
	{
		std::cout << (*value + i) << std::endl;
	}

	return 0;
}

 

 

if文

 最初に頻度の高いものを使う

   たくさんの異なった条件をテストする場合、一番よく起こる場合を最初にテストするのが最良の方法。

 

 最初に簡単なものを行う

   最初にテストするのは最も簡単な条件です。最初のテストが成功すれば、複雑な方の計算を避けることが出来る。

 

 

乗除算とビットシフト

  乗除算よりもビット演算子の方が効率的であることがしばしばあります。したがって、2の累乗による乗除算はビットシフトに置き換えることによって最適化できる。しかし、整数の乗除算の場合しかこの最適化を行うことが出来ない。左シフトは整数の乗算に、右シフトは整数の除算に対応する。


#include <iostream>

int main()
{
	int a = 1;

	std::cout << a * 2		 << "\t" << (a << 1) << std::endl;
	std::cout << a * 4		 << "\t" << (a << 2) << std::endl;
	std::cout << a * 8		 << "\t" << (a << 3) << std::endl;
	std::cout << a * 16		 << "\t" << (a << 4) << std::endl;
	std::cout << a * 32		 << "\t" << (a << 5) << std::endl;
	std::cout << a * 64		 << "\t" << (a << 6) << std::endl;
	std::cout << a * 128	 << "\t" << (a << 7) << std::endl;
	std::cout << a * 256	 << "\t" << (a << 8) << std::endl;
	std::cout << a * 512	 << "\t" << (a << 9) << std::endl;
	std::cout << a * 1024	 << "\t" << (a << 10) << std::endl;


	a = 1024;

	std::cout << a / 2		 << "\t" << (a >> 1) << std::endl;
	std::cout << a / 4		 << "\t" << (a >> 2) << std::endl;
	std::cout << a / 8		 << "\t" << (a >> 3) << std::endl;
	std::cout << a / 16		 << "\t" << (a >> 4) << std::endl;
	std::cout << a / 32		 << "\t" << (a >> 5) << std::endl;
	std::cout << a / 64		 << "\t" << (a >> 6) << std::endl;
	std::cout << a / 128	 << "\t" << (a >> 7) << std::endl;
	std::cout << a / 256	 << "\t" << (a >> 8) << std::endl;
	std::cout << a / 512	 << "\t" << (a >> 9) << std::endl;
	std::cout << a / 1024	 << "\t" << (a >> 10) << std::endl;

	return 0;
}

 

 

% と &

  %は暗黙の内に除算を行ってしまうので、%に比べてビットごとの倫理積の方が効率的である。2の累乗による除算があれば、それはビットマスク


#include <iostream>

int main()
{
	int a = 124;

	std::cout << a % 2		 << "\t" << (a & 1) << std::endl;
	std::cout << a % 4		 << "\t" << (a & 3) << std::endl;
	std::cout << a % 8		 << "\t" << (a & 7) << std::endl;
	std::cout << a % 16		 << "\t" << (a & 15) << std::endl;
	std::cout << a % 32		 << "\t" << (a & 31) << std::endl;
	std::cout << a % 64		 << "\t" << (a & 63) << std::endl;
	std::cout << a % 128	 << "\t" << (a & 127) << std::endl;
	std::cout << a % 256	 << "\t" << (a & 255) << std::endl;
	std::cout << a % 512	 << "\t" << (a & 511) << std::endl;
	std::cout << a % 1024	 << "\t" << (a & 1023) << std::endl;

	return 0;
}

 

 

インクリメントと代入

  代入よりも++演算子の方が早いコンピュータもある。このようなコンピュータは、フラグを「真」に設定するのに++演算子を使うと良いでしょう。ブーリアン変数に値を代入する代わりに、int や char の変数をインクリメントすれば良い。

 

 

除算と乗算

  乗算は除算に比べて多少速い場合が多いので、場合によっては除算を逆算の乗算に置き換えることが出来る。


#include <iostream>

int main()
{
	double a = 124;

	std::cout << a / 5 << "\t" << a * 0.2 << std::endl;
	std::cout << a / 10 << "\t" << a * 0.1 << std::endl;

	return 0;
}

 

 

 const と #define

  記号定数を宣言する場合には、const変数を使うよりも#defineを使う方が効率的でしょう。#defineとconstの両方が付けるところでは、コンパイラが「定数の畳み込み」(定数式のコンパイラ時における評価)を行うことが出来る#defineの方が効率的です。また、constを使うと大部分のコンパイラはその変数にアクセスするためのコードを生成してしまう

 

 

inline

  inlineは実行分の数が非常に少ない関数に対してのみ使うべきです。ただし、小さい関数かどうかは実際に実行される文の数をもって判断すべきでしょう。実際のソースコードの行数によってすべきではないことに注意しましょう。逆に大きい関数に対してはinline指定子を使ってはいけません


#include <iostream>

class A
{
private:
	int a;

public:
	A() :a(10) {}
	~A() {}

	inline const int GetA()
	{
		return a;
	}
};

int main()
{
	A a = A();

	std::cout << a.GetA() << std::endl;

	return 0;
}

 

 

 仮想関数のメモリ必要量

  virtual関数の使用によって必要となるメモリ必要量の増加分は以下の2つです。

 ・各オブジェクト毎に置かれる、隠れポインタデータメンバ

 ・各クラス毎に置かれる、関数ポインタ表

 

 

基底オブジェクトやメンバオブジェクトの初期設定

  この特別な初期設定構文は、コンストラクタの中でデータメンバに代入することに比べ、決して効率が悪くなることはなく、また効率が良くなることもあるので、この形式を適用できるところでは、常にこれを使うべき。


#include <iostream>

class A
{
public:
	A();
	~A() {}

	int a;
};

A::A() : a(10)
{
}

int main()
{
	A a = A();

	std::cout << a.a << std::endl;

	return 0;
}


#include <iostream>

class A
{
public:
	int a;

	A(int a) :a(a) {}
	~A() {}
};

class B : public A
{
public:
	B(int b) :A(b) {}
	~B() {}
};

int main()
{
	B b = B(10);

	std::cout << b.a << std::endl;

	return 0;
}

 

C++の静的データメンバ

  C++でクラスオブジェクトのサイズを減らす一つの方法は、メンバのいくつかをstaticと宣言することです。

  staticデータメンバは大域変数として効率的に実装されますが、クラスのスコープに囲まれたときの制約は受けます。これは、static局所変数が関数中で受ける、単一関数のスコープ制約に大変よく似ています。


#include <iostream>

class A
{
private:
	static int a;
public:
	A(int a) { A::a = a; }
	~A() {}

	const int GetA()
	{
		return a;
	}
};

int A::a;


int main()
{
	A a = A(10);

	std::cout << a.GetA() << std::endl;

	return 0;
}

 

ソートアルゴリズムの選択

  通常目的のソートアルゴリズムとしては、クイックソートアルゴリズムが最良です。しかし、次のような場合はほかのアルゴリズムの方が良いでしょう。

 ・ソート数要素数が少ない。

 ・配列の大部分がソート済みである。

  もし、素数が少ないなら、挿入ソートなどの簡単なアルゴリズムが好まれる。大部分がソート済みであれば、もっと簡潔なアルゴリズムが好まれる。