C/C++ ポインタ
何をしたいか不明瞭となってきたので、再確認。
- double 型の巨大な配列を、dll を使って処理したい!
- アプリケーション、dll 間の配列渡しが目的。
- ポインタの知識が必須となる。
- double 型の一時配列を、dll 側で高速、且つ、最小限のメモリ使用量で処理したい。
- 動的配列(動的なメモリ管理)が必須となる。
※以上、配列渡しと動的メモリ管理の習得が目的。他は如何でも良い。
※文字列処理(文字配列)は使わないので、さわり程度(最低限)に記載。
ポインタ
C/C++ で配列を利用仕様とすると、動的配列(動的メモリ確保)にぶち当たる。
思うような処理を実現するには、動的メモリの活用が必須となるのは理解できたが、動的メモリの利用にはポインタの知識が必要(思いっきり C/C++ 使いたく無くなる...)となってくる。
♪しっかた(仕方)ががないので〜(白ヤギさん風)、ポインタの頁を作ることにした...。
※内容非保証・表示アドレス環境依存
ポインタの応用
ポインタのポインタ(文字配列)
※配列を文字列のポインタとして一次元で宣言した場合でも、内部的に二次元配列として処理される。
char* s[3]; s[0] = "Hello"; s[1] = "C/C++"; s[2] = "World!!"; printf("s[0] = %s\n",s[0]); // Hello printf("s[1] = %s\n",s[1]); // C/C++ printf("s[2] = %s\n",s[2]); // World!! printf("s[1][1] = %c\n",s[1][1]); // / printf("*(s[2]+2) = %c\n",*(s[2]+2)); // r s[1] = "goooood by!!!"; printf("s[1] = %s\n",s[1]); // goooood by!!!
s[0]~[2] には、"Hello" "C/C++" "World!!" のそれぞれを記憶した先頭アドレスが、ポインタとして保持される。
s[1]="goooood by!!!"; 等代入すると、s[1] のポインタが代入文字列の先頭アドレスへ置き換えられる。
ポインタのポインタ
int a = 10; int* p = &a; // p は a のアドレスを保持するポインタ変数。 int** pp = &p; // pp は、p のアドレスを保持。そのアドレスの先は int型変数。 **pp = 20; // a に 20 が代入される。
※日が経ったら、自分でも訳解かんなくなるね!
※ポインタのポインタのポインタ ... なんてことも可能。
多次元配列とポインタ
sample1:通常の二次元配列
int arr[][3] = { { 00, 01, 02 }, { 10, 11, 12 }, { 20, 21, 22 } }; printf( "arr[2][1] = %d\n", arr[2][1] ); // 21 printf( "*(arr[2]+1) = %d\n", *(arr[2]+1) ); // 21 printf( "*(*(arr+2)+1) = %d\n", *(*(arr+2)+1) ); // 21
sample2:通常の多次元配列
int arr[][3][3] = { { { 000, 001, 002 }, { 010, 011, 012 }, { 020, 021, 022 } }, { { 100, 101, 102 }, { 110, 111, 112 }, { 120, 121, 122 } }, { { 200, 201, 202 }, { 210, 211, 212 }, { 220, 221, 222 } } }; printf( "arr[0][1][2] = %d\n", arr[0][1][2] ); // 10 printf( "*(arr[0][1]+2) = %d\n", *(arr[0][1]+2) ); // 10 printf( "*(*(arr[0]+1)+2) = %d\n", *(*(arr[0]+1)+2) ); // 10 printf( "*(*(*(arr+0)+1)+2) = %d\n", *(*(*(arr+0)+1)+2) ); // 10 // 12 を期待したが、10 が出た。 printf( "*(*(arr+2)) = %d\n", *(*(arr+2)) ); // 200 printf( "*(*(arr+2)+1) = %d\n", *(*(arr+2)+1) ); // 210※12 を期待し 10 が出る原因判明。{0,1,2},{10,11,12},{20,21,22} のように頭の 0(ゼロ)削除で回避された。
・・・作成中・・・
アドレス渡し
void main(void) { char chrArr[] = "abcde"; char* pc = chrArr; printf("chrArr のアドレス %x \n", chrArr); 12ff58 cout << "-----------------" << endl; c_out(chrArr); cout << "-----------------" << endl; } void c_out(char* iStr) { printf("iStr=%d\n",iStr); // iStr=1245016 (10進) printf("iStr=%x\n",iStr); // iStr=12ff58 (16進) printf("iStr=%s\n",iStr); // iStr=abcde cout << " iStr=" << iStr << endl; // iStr=abcde cout << "*iStr=" << *iStr << endl; // *iStr=a cout << "&iStr=" << &iStr << endl; // &iStr=0012FE78 ??意味不明 }※&iStr=0012FE78 の意味不明。確認中-->多分 chrArr の番地を間接参照している iStr 自体のアドレスと思われる。ポインタで受けた場合 &(アドレス参照演算子)は使えない...
※アドレス表示使用の %x は誤り、正しいフォーマットは %p とのこと。コンパイル時エラーの原因がようやくわかった。
iStr は、main() 内より chrArr のアドレスを受け取っている訳だが、上記例では受け取ったアドレス(12ff50)自体を cout で表示することが出来ない? "iStr=%p" << iStr とかすると出るのだろうか?(未確認)
▲上へ
ポインタと配列
C/C++ はポインタの理解が無いと、まともな(中規模程度〜)プログラムを組むのが難しい。
配列(ポインタの応用)、文字列処理(文字配列処理)、配列関数、文字列関数が貧弱... と言うことで覚えざる終えない。ポインタを利用したくない場合、C#,Java,VB など...前にも書いたので割愛...。
また、この頁内容は「アドレス渡し」「配列渡し」の基本となると思われる。十分理解しないと C/C++ を使ったプログラム作成が困難である。
※関数、サブルーチンを使わず、main() のみで完結出来れば良いが ... 多分、分割コンパイル不可、DLL 作成不可、C++ 機能の使いこなし不可 ... 意味が無い。
配列演算子・間接参照演算子
配列変数と、間接参照演算子(*)の関係について。アドレス演算子の関係も参考まで表示。
sample1:配列変数を宣言
int a[] = {10,20,30};
sample1 と各演算子の関係
記号・名称 | 指定記号 | ||||
取得値 | |||||
[] | 配列演算子 | a | a[0] | a[1] | a[2] |
- 先頭番地と値取得 -> | 12ff50(pointer) | 10 | 20 | 30 | |
* | 間接参照演算子 | *a | *(a+0) | *(a+1) | *(a+2) |
- 値を取得 --------> | 10 | 10 | 20 | 30 | |
& | アドレス演算子 | &a | &a[0] | &a[1] | &a[2] |
- 各番地を取得 -----> | - ?(未確認) | 12ff50(Address) | 12ff54(Address) | 12ff58(Address) |
※配列として宣言された定数ポインタ a に対して ”[]”、”*”伴に同様に取り扱い可能。
※違いは、ポインタ a が宣言時に定数となるか変数となるか?と言った違い。
配列とアドレス
int intArr[] = {10,20,30,40,50}; // --- 通常の配列からアドレス取得 --- printf("intArr[] のアドレス:%x\n", intArr); // 12ff50 printf("intArr[0] のアドレス:%x\n", &intArr[0]); // 12ff50 printf("intArr[1] のアドレス:%x\n", &intArr[1]); // 12ff54 printf("intArr[2] のアドレス:%x\n", &intArr[2]); // 12ff58 // --- 配列定数加算時のアドレス取得 --- printf("intArr+0 のアドレス:%x\n", intArr+0); // 12ff50 printf("intArr+1 のアドレス:%x\n", intArr+1); // 12ff54 printf("intArr+2 のアドレス:%x\n", intArr+2); // 12ff58 // --- 間接参照(ポインタ)からアドレス取得 int* p = intArr; printf("p のアドレス:%x\n", p); // 12ff50 &(*p) でも同 printf("p+1(++p) のアドレス:%x\n", ++p); // 12ff54 printf("P+2(++p) のアドレス:%x\n", ++p); // 12ff58 // --- ポインタを配列のように扱った際のアドレス取得 --- p = intArray; // <- ポインタ p 初期化 printf("p[0] のアドレス:%x\n", &p[0]); // 12ff50 printf("p[1] のアドレス:%x\n", &p[1]); // 12ff54 printf("p[2] のアドレス:%x\n", &p[2]); // 12ff58※アドレス演算子の優先順位に注意
▲上へ
配列変数とポインタ
C/C++ での配列は、結局のところポインタの応用で成り立っている。(...と思われる...)
int intArray[] = {10,20,30,40,50}; printf("intArray[] の開始アドレス:%08X\n", intArray ); printf("intArray[3] の内容を表示:%d\n", *(intArray+3) );
intArray はポインタとして扱っても動くが、intArray はポインタ変数では無い。
intArray = 0x0012ff50 などの代入は出来ず、配列の先頭アドレスを持ったポインタ定数と言える。
// --- 通常の配列処理 --- printf(intArray[4]=%d\n",intArray[4]); // 50 printf(intArray[5]=%d\n",intArray[5]); // <- 配列外メモリ参照 // --- ポインタを動的に扱い配列データを処理 --- int* p = intArray; printf("intArray[0]=%d\n",*(p)); // 10 printf("intArray[1]=%d\n",*(++p)); // 20 printf("intArray[2]=%d\n",*(++p)); // 30 printf("intArray[3]=%d\n",*(++p)); // 40 printf("intArray[4]=%d\n",*(++p)); // 50 printf("intArray[5]=%d\n",*(++p)); // <- 配列外メモリ参照 // --- 配列定数+index でポインタ風にデータ処理する --- printf("intArray+0=%d\n",*intArray); // 10 printf("intArray+1=%d\n",*(intArray+1)); // 20 printf("intArray+2=%d\n",*(intArray+2)); // 30 printf("intArray+5=%d\n",*(intArray+5)); // <- 配列外メモリ参照 // --- ポインタを配列のように扱う --- p = intArray; // <- ポインタ p 初期化 printf("p[0]=%d\n",p[0]); // 10 printf("p[1]=%d\n",p[1]); // 20 printf("p[2]=%d\n",p[2]); // 30 printf("p[5]=%d\n",p[5]); // <- 配列害メモリ参照※作成者が意識してメモリ(配列)外参照をブロックしないと深刻なバグが発生する。特にデータ書き込みには要注意。
※間接参照演算子(*)の優先順位に注意。
これを実行すると、そのまま配列の中身を取り出せる。
注意したいのが、配列外の判断が出来ないところが怖い使い方である。
(これは、読み出しなので害は無いが、intArray[5] なるところへ書き込みしたら何が起こるか...全く予想がつかない!)
▲上へ
配列とポインタサイズ
// sizeof() によるサイズ確認 // ------ char ------ char chrArr[] = {'a','b','c','d','e','\0'}; // or = "abcde"; char=1byte(通常) char* pc = chrArr; printf("chrArr ArrSize:%d\n", sizeof(chrArr)); // 6 All Array Size printf("chrArr[0] Size :%d\n", sizeof(chrArr[0])); // 1 1 Data Size printf("pc Size :%d\n", sizeof(pc)); // 4 Memory Address Size printf("*pc Size :%d\n", sizeof(*pc)); // 1 1 Data Size // ------ int ------ int intArr[] = {10,20,30,40,50}; // int=4byte(通常) int* pi = intArr; printf("intArr ArrSize:%d\n", sizeof(intArr)); // 20 All Array Size printf("intArr[0] Size :%d\n", sizeof(intArr[0])); // 4 1 Data Size printf("pi Size :%d\n", sizeof(pi)); // 4 Memory Address Size printf("*pi Size :%d\n", sizeof(*pi)); // 4 1 Data Size // ------ double ------ double dblArr[] = [10,20,30,40,50]; // double=8byte(通常) double* pd = dblArr; printf("dblArr ArrSize:%d\n", sizeof(dblArr)); // 40 All Array Size printf("dblArr[0] Size :%d\n", sizeof(dblArr[0])); // 8 1 Data Size printf("pd Size :%d\n", sizeof(pd)); // 4 Memory Address Size printf("*pd Size :%d\n", sizeof(*pd)); // 8 1 Data Size
配列変数(定数?)名 dblArr(等) で sizeof() によるサイズ取得をすると、配列データのサイズを返す。
通常のポインタ(pd等)で、sizeof() によるサイズ取得をすると、ポインタとしてのサイズを返す。
ここでは、配列変数(定数?)名で配列の全サイズが取得できることが特に重要。普通のポインタと大きく異なる仕様と言える。
纏め
- 配列変数は、ポインタ変数と同等程度の機能がある。
- 配列変数のポインタへの値代入不可。---> ポインタ部は、アドレス変数ではなくアドレス定数である。
- 配列アドレス定数を間接参照演算子(*:ポインタ)へ代入し普通のポインタとして利用することが簡単に出来る。
▲上へ
ポインタと通常の変数
ポインタ関連用語
- 参照(Reference)”&”
- 参照変数
データ型& 参照変数名 = 参照先(元)変数名; - &(ampersand)
参照演算子(Reference Operators)/アドレス演算子(Address Operator) - 参照渡し
参照元変数 a -> ref(参照変数定義 int& x) { x; // a を直接参照 }- ポインタ
ポインタとは参照先アドレスを保持するポインタ変数。参照も結局内部的にはポインタを使用して参照を実現している。
- ポインタ
- 参照変数
- 間接参照(Pointer)”*”
- ポインタ変数
データ型* ポインタ変数名:
ポインタ変数 = &通常の変数; // アドレス渡し
*ポインタ変数=値; // 通常の変数へ値を代入 - *(asterisk)
ポインタ演算子、又は、間接参照演算子(dereferencing operator) - 配列演算子”[]”
- 配列変数
データ型 配列変数名[配列要素数];
配列変数 のみで配列の先頭アドレスを示す。配列変数はそれのみでアドレスを表すが、定数である部分がポインタと大きく異なる。
- 配列変数
- ポインタ変数
●変数 a を定義->OS によるメモリ割当->a に割当てられたアドレス取得を &aで実現
●ポインタ変数 p を定義->&a を p に保持し、*p = 200; とすると、a も 200になる。
参照とポインタを同列に置くのがそもそもの間違いで、参照はポインタの一部機能(間接参照)を、より簡単に制限付きで利用できる...程度に思っていた方が無難である。
この辺は、卵が先か鶏が先か?といった感(...不毛...)もあり、発生はポインタが先、使用は参照優先ということで、お・わ・り!!!
追伸:
結局のところ、C++ で出来るだけ ポインタを使用しなくて良いように用意されたのが「参照」と考えても良さそうである。機能的には「参照」<「ポインタ」となるようだ。
しかし、C++ でもポインタ無しでまともなプログラムは組めないことも事実のようである。
ポインタを避けたプログラムスタイルを望むなら、C#, VB, Java とかを考えることとなるが、ネイティブコードは出せない。ちなみに、C# は、unsafe コードの利用でポインタを利用する方法もあるようで、ポインタも利用出来て最新言語(生産性の向上)を望む人にはお勧めかもしれない。(安全性の低下と引き換えに、操作の自由度が向上する。)
さらに! C/C++ では、ポインタと配列が表裏一体(やっぱり...)なので避け様が無い。文字列処理などポインタ漬けとなるようである。 C++ で「気の利いた関数(配列処理・文字列処理)でも準備してくれてるのでは?」と期待してたがそうではないらしく、構造化プログラムの概念の強化が主のようである。
▲上へ
再考:ポインタ渡し・参照渡し
個人的見解:「ポインタ渡し」も「参照渡し」も、アドレス渡しを実現する手段の種類の一つであり、参照渡しが C++ になって新たに追加されたということ。出来る限り参照渡し優先で利用すべきといった結論に達した。(混在が悪いわけではなく、ポインタでなければ出来ない処理も有る。)
passing_r(int& x) { cout << "Reference passing : a->int& x(参照変数) -> x(値処理変数)" << endl; cout << " Receipt Address : &x = " << &x << endl; cout << " Reference Value : x = " << x << endl; } passing_p(int* x) { cout << "Pointer delivery : &x->int* x(ポインタ) -> *x(値処理変数)" << endl; cout << " Receipt Address : x = " << x << endl; cout << " Reference Value : *x = " << *x << endl; } main() { int a = 10; cout << "Passing Value a : a = " << a << endl; cout << "Passing Address a : &a=" << &a << endl; passing_r(a); // 参照渡し passing_p(&a); // ポインタ渡し }
function | 渡し側・受取側の変数 | 値の変数 | 参照変数(アドレス) | ||
---|---|---|---|---|---|
main | swap_r(a)...a == 10 swap_p(&a)...&a == 0x0012FF60 | a == 10 | &a == (0x0012FF60) | ||
passing_r (参照渡し) | a | a のアドレス (0x0012FF60) | int& x (参照変数) | x == 10 | &x == (0x0012FF60) |
passing_p (ポインタ渡し) | &a | 0x0012FF60 | int* x (ポインタ変数) | *x == 10 | x == (0x0012FF60) |
※&(ampersand)は、参照演算子(又は、アドレス演算子/Address Operator)と呼ばれ、&x のように変数の前に付加しメモリアドレスを求める。 ※データ型& 参照変数名 = 参照先変数名 として &を使用し定義した参照先変数名の別名を、参照変数と呼ぶ。 ※ ポインタ演算子は、間接参照演算子とも呼ばれる。 |
※やればやるほどこんがらがる。
▲上へ
再考:ポインタ変数・参照変数
参照変数は C言語の ポインタに良く似ているが、ポインタより扱いやすくなっている。
sample1:参照変数は、他の変数の別名として利用することが可能
010:int a = 100; 020:int& rf_a = a; // rf_a(reference a) は a の別名として宣言される 030:rf_a = 900; // これは、a の値も 900 になる※020: の rf_a 参照変数宣言時、参照元変数の実態が伴わないと宣言できない。
sample2:ポインタを使って他の変数の参照(refarence)を行う
010:int a = 100; 020:int *p; 030:p = &a; // 結局 & で a のアドレスを取得し アドレス変数 p へ代入。 040:*p = 900; // 参照先アドレス内容( 変数 a )も、900 になる。※020: の *p ポインタ変数宣言は、参照変数宣言のような制限は無い。
※030: で参照先アドレスを p=&a で代入する。
sample1 と sample2 は、結果として同じ処理を行っているが、参照変数の方が簡単に実現(一部制限有)している。
▲上へ
「ポインタ渡し」と「参照渡し」の優先度
C++ の場合基本的に「参照渡し」優先。宣言時に実態が伴わないなど理由が有る場合「ポインタ渡し」となるようである。
C言語では、基本的に「値渡し」しかなく、ポインタと呼ばれる物も実態は変数のアドレスを「値渡し」することで、呼ばれた関数側がアドレス参照、操作することで「参照渡し」同等の処理を行っているとのこと。
C++ では、「参照渡し」その物の概念を取り入れているため、これまでのポインタを使用した擬似的な「参照渡し(ポインタ渡し)」を行う必要がないと言うことらしい。
▲上へ
ポインタの利用例 2
例1がやたら長くなったのでもっと簡単な例。void main( int argc, char *argv[] ) { // argc にパラメータ数を取得 // *argv[] でパラメータ文字列を取得 ... }※コマンドプロンプトからのパラメータ取得例。
※char *argv[] の宣言が特殊で参考にならないのでボツ。
▲上へ
ポインタの利用例 1
ポインタと参照を使った引数渡しの例// test_sample.cpp : main project file. #include "stdafx.h" #include <iostream> void PointerDelivery_swap(int *x, int *y); void ReferencePassing_swap(int& x, int& y); using namespace System; int main(array<System::String ^> ^args) { int a = 100; int b = 900; Console::WriteLine(L"ポインタ渡し、参照渡し"); // アドレス渡し ------------- Console::WriteLine(L" 1.ポインタ(アドレス)渡し"); printf(" 処理前: a=%d b=%d \n",a,b); PointerDelivery_swap(&a, &b); printf(" 処理後: a=%d b=%d \n",a,b); // 参照渡し ----------------- Console::WriteLine(L" 2.参照渡し"); printf(" 処理前: a=%d b=%d \n",a,b); ReferencePassing_swap(a, b); printf(" 処理後: a=%d b=%d \n",a,b); // -------------------------- return 0; } // ポインタを使った引数渡し void PointerDelivery_swap(int *px, int *py) { int temp; temp = *px ; *px = *py ; *py = temp; } // 参照を使った引数渡し void ReferencePassing_swap(int& x, int& y) { int temp; temp = x; x = y; y = temp; }※気分を変えて CLR console Application を使ったら、cout ,endl が使えない。iostream を include してもエラーが解消できないため、prntf 使ったら動いた。Console::WriteLine などという見慣れない出力方法がテンプレートで出てたのでおかしいとは思ったが...いらぬところで手間取った。こんな長いソースを乗せる積りではなかったが、参考までに実働ソースを、ほぼ全文メモ。
※cout, endl が使えない理由 ---> using namespace std; が抜けていたため。この場合 std::cout, std::endl で利用可能。
結果表示
ポインタ渡し、参照渡し 1.ポインタ(アドレス)渡し 処理前: a=100 b=900 処理後: a=900 b=100 <--- 入れ替わっている。 2.参照渡し 処理前: a=900 b=100 処理後: a=100 b=900 <--- 入れ替わっている。 続行するには何かキーを押してください...※たぶんこんなんで良いと思うが...関数が受け取ったアドレス内容(又はアドレス自体)を直接操作している。
※これにより、戻り値を1個しか返せないという関数の制限が撤廃される。(ローカル変数をグローバルに直接操作している)
※配列の先頭アドレスを渡してサブルーチンのように関数を使うことができそうである。しかし、動的配列のポインタ渡し、参照渡しで問題発生の有無についてはよくわからない。
※ここでは、値を戻さず(return)処理を完結しているが、ポインタ渡し、参照渡しで値を戻す場合注意すべき問題は、呼ばれた関数固有のローカル変数のアドレスを戻すことも出来てしまうこと。関数終了と同時に開放(破棄)されるアドレスは返すべきでないのは言うまでも無い。おそらく、動的配列のアドレス受け取りが、親となるプログラムから受け取った物であれば、問題無いものと思われる。(少なくとも親関数が存在する限り渡されたアドレスは破棄されない。(並列動作などさせた場合動作不明?))
※参照渡し も ポインタ渡し も同じ処理ができるが、ポインタ渡しは「C言語」、参照渡しは「C++」で新たに追加された概念のようである。何がどのように異なるか、まだよくわからない。
※CLR(.NET Framework) 使うと体感的にも遅い!
▲上へ
ポインタの概要
& の付加により変数のアドレスが取得できる。
* の付加により変数のアドレスを保持する変数(ポインタ)を宣言出来る。
class class_a { ... }; 〜 通常の変数 double a = 123.456; double *pa; pa = &a; // 変数 a のメモリアドレス &a をポインタ pa へ代入。 cout << " *pa = " << *pa << endl; // a の値 123.456 が表示。 cout << " pa = " << pa << endl; // &a の値 0x???? とか表示? double *pa2; pa2 = pa; cout << " pa2=pa は、pa2=&a と同じ。:pa2 = " << pa2 << endl; 配列型の変数 char arr[256]; char *parr; parr = &arr; // これは誤り。 parr = arr; // 配列 arr[] の先頭アドレス(&arr[0])を代入。 // pa+0==arr==&arr[0], pa+1==&arr[1], ..., pa+n==&arr[n] クラス型の変数 class_a cl_a; class_a *pcl_a; pcl_a = &cl_a; // class object のアドレス取得
※配列や、class で定義した型でも利用可能。
この辺で、めんどくなりやる気がうせるが...
変数の種類 | 宣言方法 | 値取出 | アドレス取得 | 備考 | |
---|---|---|---|---|---|
- | 一般の変数 | int a; | a | &a | 値を持つ普通の変数として宣言 |
[] | 配列変数 | int arr[10]; | arr[1] | arr/&arr[1] | 値を持つ普通の変数として宣言 [](添字演算子)は*(間接参照演算子)とほぼ同等の機能を持つ。 |
* | ポインタ変数 | int *a; | *a | a | 値の格納場所を示すポインタとして宣言(間接参照演算子) |
& | 参照変数 | int &a; | a | &a | アドレスを指す変数として宣言(アドレス演算子) |
※値=プログラムで利用する値。 ※アドレス=メモリアドレス(記憶場所)を指す。 |
※参照変数の宣言の値とアドレス取得が一般の変数と同じであることに留意、配列は別扱いすれば、一般の変数とポインタ変数について頭に叩き込めば混乱を回避しやすいかもしれない。
▲上へ
リンク
内部リンク
- C/C++ C++/CLI C# 関連
- VC++ 2005 Express のインストール
- C/C++ の簡単なプログラム例
- 変数・定数
- プログラムの分割/ダイナミックリンクライブラリ など
- その他
- C/C++ その他::書式文字/ESC code など
- VB2005リファレンス(覚え書き)
- SQL文:SQLステートメント
- VBA(VisualBasic for Applications)
外部リンク
- 現在ありません
▲上へ
2008年05月18日(日) 19:44:57 Modified by cafeboy1