C,C++,VCに関する雑多なこと



C,C++

出力オペレータ

vectorや自分で作ったクラスに対して画面出力オペレータを定義する. 以下はvectorに関してオペレータを作成した場合の例.

//! vectorの出力オペレータ
template<class T>
inline ostream &operator<<(ostream &out, const vector<T> &x)
{
	vector<T>::const_iterator i = x.begin();
	for(; i != x.end(); ++i){
		out << *i << " ";
	}
	return out;
}

Win32環境でのマルチスレッド

Win32でのスレッド生成には,

  • _beginthread
  • _beginthreadex
  • CreateThread

などがある.CreateThread はメモリリークがあるらしい. _beginthreadが一番簡単で,スレッドハンドルも_endthreadで閉じてくれる. _beginthreadexが一番安全なもよう.ただし,スレッドハンドルはCloseHandleで手動で閉じる必要がある.

以下は_beginthreadexを用いたもっとも単純な例

#include <process.h>
#include <Windows.h>

// スレッド関数
unsigned int func(void *x)
{
	for(int i = 0; i < 10; ++i){
		cout << "thread " << *((int*)(x)) << endl;
	}
	_endthreadex(0);
	return 0;
}

int main(void)
{
	HANDLE handle;
	int x = 1;

	// スレッドを開始
	handle = (HANDLE)_beginthreadex(NULL, 0, (unsigned int (__stdcall*)(void*))func, &x, 0, NULL);

	for(int i = 0; i < 10; ++i){
		cout << "thread 0" << endl;
	}

	// スレッド終了を待つ
	WaitForSingleObject(handle, INFINITE);

	// ハンドルを閉じる
	CloseHandle(handle);

	return 0;
}

メンバ関数ポインタ

クラスのメンバ関数を関数ポインタを使って扱う方法.
例えば,以下のようなクラスがあるとする.

class FuncPtrTest
{
public:
	void Func1(int i){ cout << i << endl; }
	void Func2(int i){ cout << 2*i << endl; }
};

メンバ関数ポインタ変数は,

void (FuncPtrTest::*pFunc)(int) = &FuncPtrTest::Func1;

のようにしてとることができる.

typedefで関数を定義する場合は以下.

typedef void (FuncPtrTest::*FUNC)(int);

オペレータ"->*",".*"を用いた呼び出し

呼び出す場合は,

FuncPtrTest *pFuncClass = new FuncPtrTest;
(pFuncClass->*pFunc)(1);

となる.ポインタでないクラスオブジェクトを用いる場合は以下のようになる.

FuncPtrTest funcClass;
(funcClass.*pFunc)(1);

typedefした場合は以下.

FUNC defFunc = &FuncPtrTest::Func2;
(pFuncClass->*defFunc)(8);

メンバ関数内から呼び出す場合はthisポインタを用いる.

class FuncPtrTest
{
public:
	void Func1(int i){ cout << i << endl; }
	void Func2(int i){ cout << 2*i << endl; }

	void Test(void (FuncPtrTest::*func)(int));
};

void FuncPtrTest::Test(void (FuncPtrTest::*func)(int))
{
	(this->*func)(2);
}

void main(void)
{
	void (FuncPtrTest::*pFunc)(int) = &FuncPtrTest::Func2;

	FuncPtrTest *pFuncClass = new FuncPtrTest;
	pFuncClass->Test(pFunc);

	delete pFuncClass;
}

テンプレートを用いた呼び出し

テンプレート関数を用いることでもクラスオブジェクトを指定することができる.

template<FuncPtrTest* P>
void FuncT(int i)
{
	P->Func1(i);
}

FuncPtrTest g_FuncClass;
void main(void)
{
	FuncT<&g_FuncClass>(8);
}

ただし,テンプレートに指定するクラスオブジェクトはグローバル変数でなければならない.

boost::bindを用いた呼び出し

boost::bindを用いることで,通常の関数ポインタとメンバ関数ポインタを 区別なくboost::functionとして扱うことができる.

まず,boost::functionを用いて関数を定義する.

#include <boost/bind.hpp>
#include <boost/function.hpp>

void TestBind(boost::function<void (int)> func)
{
    func(16);
}

関数の呼び出しでは,bindを用いてクラスオブジェクトを指定する.

FuncPtrTest *pFuncClass = new FuncPtrTest;
TestBind(boost::bind(&FuncPtrTest::Func2, boost::ref(pFuncClass), _1));

コード例

#include <boost/bind.hpp>
#include <boost/function.hpp>

class FuncPtrTest;
typedef void (FuncPtrTest::*FUNC)(int);

class FuncPtrTest
{
public:
	void Func1(int i){ cout << i << endl; }
	void Func2(int i){ cout << 2*i << endl; }

	void Test(void (FuncPtrTest::*func)(int));
};

void FuncPtrTest::Test(void (FuncPtrTest::*func)(int))
{
	(this->*func)(2);
}

template<FuncPtrTest* P>
void FuncT(int i)
{
	P->Func1(i);
}

void TestBind(boost::function<void (int)> func)
{
	func(16);
}


FuncPtrTest g_FuncClass;
void main(void)
{
	FuncPtrTest *pFuncClass = new FuncPtrTest;

	void (FuncPtrTest::*pFunc)(int) = &FuncPtrTest::Func1;
	(pFuncClass->*pFunc)(1);

	pFunc = &FuncPtrTest::Func2;
	(pFuncClass->*pFunc)(1);

	pFuncClass->Test(pFunc);

	FuncT<&g_FuncClass>(8);

	FUNC defFunc = &FuncPtrTest::Func2;
	(pFuncClass->*defFunc)(8);

	TestBind(boost::bind(&FuncPtrTest::Func2, boost::ref(pFuncClass), _1));

	delete pFuncClass;
}

実行結果

1
2
4
8
16
32

ビット演算

演算の種類演算子使用例
論理積(AND)&x & y, x &= 0x01
論理和(OR)|x | y, x |= 0x01
否定(NOT)~y = ~x;
排他的論理和(XOR)^y ^ x; y ^= x;
右シフト>>y = x >> 2;
左シフト<<y = x << 2;

実行例

1011&0101 = 0001
1011|0101 = 1111
~1011 = 0100
1011^0101 = 1110
1011>>2 = 0010
1011<<2 = 1100

メンバ関数テンプレート

class rxTemplateTest2
{
	Type data;

public:
	rxTemplateTest2();
	~rxTemplateTest2(){};

	template<typename T>
	void InlineFunc(Type x)
	{
		data = x;
	}

	template<typename T>
	void Func(Type y);
};


template<typename Type>
void rxTemplateTest2::Func(Type y)
{
	data = y;
	cout << data << endl;
}

テンプレートクラス

template<class Type>
class rxTemplateTest
{
	Type data;

public:
	rxTemplateTest();
	~rxTemplateTest(){};

	void InlineFunc(Type x)
	{
		data = x;
	}

	void Func(Type y);
};

template<class Type>
rxTemplateTest<Type>::rxTemplateTest()
{
	data = 0;
}

template<class Type>
void rxTemplateTest<Type>::Func(Type y)
{
	data = y;
	cout << data << endl;
}

Visual C++だとテンプレートクラスの実装をcppファイルに書くと テンプレートクラスを実体化できない設計になっている(inclusion-model)ので, 基本的にはヘッダにすべての実装を書くこと.

どうしてもcppファイルに書きたい場合は,テンプレートクラスの実体を明示する(明示的実体化). 上記の例だと,

#include "template_test.h"

template<class Type>
rxTemplateTest<Type>::rxTemplateTest()
{
	data = 0;
}

template<class Type>
void rxTemplateTest<Type>::Func(Type y)
{
	data = y;
	cout << data << endl;
}

template class rxTemplateTest<int>;
template class rxTemplateTest<float>;

プリプロセッサ

コンパイル前にソースに対して行われる前処理のこと. C言語のプリプロセッサ命令(ディレクティブ)には"#"が頭に付く.

#include

ヘッダファイルの読み込み

#include <stdio.h>
#include "vec.h"

基本的にはシステムで用意されているヘッダは<>で囲み, 自分で作成したヘッダを""で囲む.

#define

マクロ置換.数値や文字列などの定数として扱える.

#define 定数名 置換後の数値や文字列

例えば,

#define PI 3.14159265358979323846
#define GRAVITY 9.80665
#define FILENAME "test.dat"

マクロ関数として関数も定義できる.

#define AREA(r) (r*r*PI)
#define MAX(a, b) ((a > b) ? a : b)
#define FEQ(a, b) (fabs(a-b) < 1e-8)

複数行にわたるマクロ

マクロは基本的に1行に書かなければならないが,複数行にどうしてもなってしまう場合は,

#define RXFOR2(i0, i1, j0, j1) for(int i = i0; i < i1; ++i) \
                                   for(int j = j0; j < j1; ++j)

#define RXFOR3(i0, i1, j0, j1, k0, k1) for(int i = i0; i < i1; ++i) \
                                           for(int j = j0; j < j1; ++j) \
                                               for(int k = k0; k < k1; ++k)

のように"\"を行末に付ける."\"の後には何も書かないこと(コメントも×).

#undef

#defineで定義した記号定数,マクロ関数などを無効にする.

#undef PI

#if 〜 #else 〜 #endif

if文と同じようなもの.

#if 式1
 処理1
#elif 式2
 処理2
#else
 処理3
#endif

式には0や1などの数値を直接指定したり, defined()でマクロ定義されているかどうかの判別などを指定できる.

#if defined(DEF)
#endif

否定"!"も使える

#if !defined(DEF)
#endif

式にはC言語で使えるものはほとんど使える(==や<,<=,&&,||など)

また,#ifdefを使えば,#if defined()と同じとなる

#ifdef DEF
#endif

否定の場合は

#ifndef DEF
#endif

既定義マクロ

コンパイラによって事前に定義されているマクロがいくつかある.

  • __FILE__ : コンパイルしているファイル名
  • __LINE__ : コンパイルしている行の行番号
  • __DATE__ : 現在の日付
  • __TIME__ : 現在の時間
  • __STDC__ : コンパイラが規格に準拠しているかどうか.準拠していれば1,そうでなければマクロ自体が定義されていない. Visual Studio 2005の場合,プロジェクトのプロパティから「C/C++」->「言語」の「言語拡張を無効にする」を 「はい」にすれば定義される.

マクロ置き換え演算子"#","##"

  • # : マクロ実引数を文字列化する.
    #define str(x) #x
    
    char *xyz = "abc";
    printf("%s\n", xyz);
    printf("%s\n", str(xyz));
    表示は
    abc
    xyz
    となる.
  • ## : 前後の句を結合
    #define add(x, y) data##x + data##y
    
    int data1, data2;
    add(1, 2); // data1 + data2

プリプロセッサによるインクルードガード

ヘッダファイルなどが複数回読み込まれるのを防ぐのに#ifndefを用いる.

#ifndef _HEADER_H_
#define _HEADER_H_

ヘッダの内容

#endif // _HEADER_H_

"_HEADER_H_"はヘッダファイルごとにユニークなものを用いる.

C言語からC++のヘッダを読み込む

#ifdef __cplusplus
extern "C" {
#endif

#ifdef __cplusplus
}
#endif

入出力ストリームの書式指定

C++の入出力ストリームは変数の方を気にせずに使えるので便利だが, 小数点以下の表示制度などを操作したい場合などもある. そのときに使えるのがマニピュレータ.

#include <iomanip>

マニピュレータの一覧は以下. (using namespace std; にしていることを前提として記述)

マニピュレータ説明使用例
endl改行してバッファをフラッシュcout << "Hello world!" << endl;
flushバッファのフラッシュのみcout << "Hello world!" << flush;
ends文字列の終端文字'\0'を出力cout << ends;
ws空白文字を飛ばす入力cin >> ws >> str;
oct,dec,hexそれぞれ8,10,16進数で入出力cout << hex << x << endl;
setw値の入出力幅指定cout << setw(5) << x << endl; /* xを5桁で表示 */
left,right値を左寄せ,右寄せで出力cout << setw(5) << left << x << endl;
setfill出力幅に満たない部分を埋める文字を指定cout << setw(8) << setfill('0') << i << endl;

また,cout,cinにはメンバ関数setf(), unsetf()があり,これらによりまとめて設定できる.

引数説明
ios::skipws,ios::noskipws先頭の空白文字を飛ばすかどうか
ios::unitbuf,ios::nounitbuf出力処理のたびにバッファをフラッシュするかどうか
ios::dec,ios::hex,ios::octそれぞれ8,10,16進数で入出力
ios::showbase0xなどの基数表示
ios::fixed10進表記法(浮動小数点数)
ios::scientific指数表記法(浮動小数点数),1.0e-3など
ios::showpoint小数点を常に表示
ios::showpos正の符号も付ける
ios::uppercase16進表記のアルファベットを大文字に
ios::left,ios::right左寄せ,右寄せ
ios::internal符号を左寄せ,値を右寄せで表示
ios::boolalphabool型の出力に"true","false"を使用

引数は"|"で結合できる.

そのほか,coutのメンバ関数を用いた書式指定としては,

  • width : 最小出力幅指定
    cout.width(10);
  • precision : 精度指定(数値全体の桁数を指定)
    cout.precision(10);
  • form : printfと同じような書式指定
    cout.form("i = %d, x = %f\n", i, x);

スリープ

  • Linux
    #include <unistd.h>
    
    unsigned int sleep(unsigned int seconds);
    secondsにスリープする時間を秒単位で指定する. 指定された時間スリープしたら0を返し,割り込まれた場合などは残りの時間を返す. Manpage of sleep
    #include <unistd.h>
    
    int usleep(useconds_t usec);
    usecにスリープする時間をマイクロ秒単位で指定する. 指定された時間スリープしたら0,エラーなどがあったときは-1を返す. Manpage of usleep
  • Windows
    #include <windows.h>
    
    void Sleep(DWORD dwMilliseconds);
    dwMillisecondsはスリープする時間をミリ秒単位で指定する.
  • 標準ライブラリで
    http://www.bohyoh.com/CandCPP/FAQ/FAQ00071.htmlを参考.
    #include <time.h>
    
    int sleep(unsigned long x)
    {
        clock_t s = clock();
        clock_t c;
        do{
            if((c = clock()) == (clock_t)-1){
                return 0;
            }
        }while(1000UL*(c-s)/CLOCKS_PER_SEC <= x); 
        return 1;
    }
    ただし,マルチタスク系のOSではCPU使用率が100%になって他のアプリケーションが動けなくなるのであまり使わない方がよい.

時間計測

  • 標準ライブラリ(ANSI C) clockを用いる.精度はCPU時間であるが,実際には1/100秒ぐらい?
    #include <time.h>
    とインクルードして,
    clock_t t1, t2;
    	t1 = clock();
    
    	// 処理
    
    	t2 = clock();
    	printf("%f [sec]\n", (double)(t2-t1)/CLOCKS_PER_SEC);
  • Linux gettimeofdayを用いる.
    #include <sys/time.h>
    
    double gettimeofday_sec(void)
    {
    	struct timeval tv;
    	gettimeofday(&tv, NULL);
    	return tv.tv_sec + tv.tv_usec*1e-6;
    } 
    
    int main(void)
    {
    	double t1, t2;
    	t1 = gettimeofday_sec(void);
    	
    	// 処理
    	
    	t2 = gettimeofday_sec(void);
    	printf("%f [sec]\n", t2-t1);
    	
    	return 0;
    }
    もしくは,getrusageを用いる.
    #include <sys/time.h>
    #include <sys/resource.h>
    
    double getrusage_sec(void)
    {
    	struct rusage r;
    	struct timeval tv;
    	getrusage(RUSAGE_SELF, &r);
    	tv = r.ru_utime;
    	return tv.tv_sec + tv.tv_usec*1e-6;
    }
  • Windows Windowsでの時間計測には,GetTickCountやtimeGetTimeなどがある.
    #include <windows.h>
    
    int main(void)
    {
    	DWORD t1, t2;
    	t1 = GetTickCount();
    
    	// 処理
    
    	t2 = GetTickCount();
    	printf("%d [msec]\n", t2-t1);
    
    	return 0;
    }
    GetTickCount()はWindowsが起動してからの時間をミリ秒単位で返す. 精度的には10-20msぐらいのようである. さらに精度が必要なときは,timeGetTimeを用いる.
    #pragma comment (lib, "winmm.lib")
    #include <mmsystem.h>
    
    int main(void)
    {
    	DWORD t1, t2;
    	t1 = timeGetTime();
    
    	// 処理
    
    	t2 = timeGetTime();
    	printf("%d [msec]\n", t2-t1);
    
    	return 0;
    }
    winmm.libをリンクしている.精度は1-2msぐらい. マイクロ秒単位での精度が必要な場合はさらにQueryPerformanceCounterがある.
    #include <windows.h>
    
    int main(void)
    {
    	LARGE_INTEGER t1, t2;
    	LARGE_INTEGER f;
    
    	QueryPerformanceFrequency((LARGE_INTEGER*)&f);	// 高分解能パフォーマンスカウンタの周波数を取得
    	QueryPerformanceCounter((LARGE_INTEGER*)&t1);
     
    	// 処理
     
    	QueryPerformanceCounter((LARGE_INTEGER*)&t2);
    	printf("%f [msec]\n", (double)(t2.QuadPart-t1.QuadPart)/(double)(f.QuadPart));
     
    	return 0;
    }
    システムに高分解能パフォーマンスカウンタがあれば,それに応じた高分解能が得られる. なければ,各関数は変数に0を格納して返す. Windows上では最も高分解能が得られる方法の一つではあるが,カウンタに何が使われるのかが環境によって変わるので, QueryPerformanceFrequencyの値を一度見ておいた方がよいかもしれない (CPUクロックが使われていたらならば,分解能は高いがクロック可変なCPUの場合に問題があるかもしれない).

std::string で CString::Format や printf のように文字列を入力

std::ostringstream を使用.

#include <sstream>
#include <string>
std::ostringstream stream;
stream << "step : " << i;
std::string str = stream.str();

RTTI

RTTI(run-time type identification)は日本語では,実行時型情報となり,そのまんま実行時に型の情報を得る機能である. 最新のANSI C++ではサポートされているので,

#include <typeinfo.h>

として,

int x = 10;
cout << "type : " << typeid(x).name() << endl;

int x = 10;
if(typeid(int) == typeid(x)){
    xがint型だったときの処理
}

という風に使える. このRTTIは,クラスのポリモーフィズム(多態性)を使うときに便利である.

GLUTなどでDOS窓を出さない

#pragma comment(linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"")

複数モニタの検出 - Platform SDK GDI

#include <windows.h>
EnumDisplayMonitors(...);
GetMonitorInfo(...);

など MSDN参照.


トップ   編集 凍結 差分 履歴 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2024-03-08 (金) 18:06:02