ポインタ
ポインタは、メモリサイズの決まっていない配列のことであり、プログラムを実行後に、サイズを設定しなくてはならない。
配列は、コンパイルする前にサイズを決めなくてはならないため、例えば、ファイルからデータを読み込みたいときに、ちょうど必要サイズの配列を用意できない不便さがある。
その点、ポインタは、ファイル容量に合わせた配列を作成できる。
これが、ポインタの基本的な考え方である。
ポインタには、アドレスと値がある。
アドレスは、その配列がメインメモリやハードディスクに保存されている場所の番地であり、値は、格納されているデータの事である。
変数や配列もポインタと同じようにメインメモリなどに保存されているため、番地と値を持っている。
変数のアドレスは、変数名の前に & を付ける。
配列やポインタの先頭アドレスは、配列名やポインタ名そのものである。
アドレスの前に * を付けると値になる。
C の場合は malloc 関数を使い、C++ の場合は new 演算子を使うことが多い。
ただし、new 演算子には realloc 関数に相当する関数がないため、C++ でも malloc 関数を使うこともある。
クリップボードなどメモリ空間を他のソフトと共有するような場合は、GlobalAlloc 関数を使う(これはウィンドウ プログラム専用)ことがある。Win32 では、グローバルメモリとローカルメモリという区別がなくなったから、これは Win16 の名ごりだろう。しかし、他に方法がないから使わざるを得ない。
malloc 関数でメモリを確保したら、free 関数で解放する。
new 演算子で確保したら、delete 関数で解放する。
メモリの解放を忘れると、
メモリリークになる。
解放は、確保と同じ
スコープで行うようにする。
ポインタは、宣言時に 0 で初期化されないため、極力、宣言時は 0 で初期化する。
NULL は 0 の事である。
既に解放された状態のポインタを Delete 演算子で解放するとエラーになる。
そのため、ポインタのメモリが確保されているかどうかの判定をした後、解放すれば良い。
そのときに、 0 で初期化する必要が出てくる。
配列も同様だが、メモリの解放をしなくても良いため、あまり重要ではない場合もある。
// 整数型のポインタ
main()
{
int *p = NULL; // ポインタの宣言
p = (int*) malloc (4 * sizeof(int)); // メモリの確保
p[0] = 2; // *p = 2; と書いても可
p[1] = 0; // *(p + 1) = 0; と書いても可
p[2] = 1; // *(p + 2) = 1; と書いても可
p[3] = 1; // *(p + 3) = 1; と書いても可
printf("%d %d %d %d", p[0], p[1], p[2], p[3]);
free(p); // メモリの解放
}
----- 出力結果 -----
2 0 1 1
--------------------
// new 演算子で同様の事をした場合
main()
{
int *p = NULL; // ポインタの宣言
p = new int [4]; // メモリの確保
p[0] = 2;
p[1] = 0;
p[2] = 1;
p[3] = 1;
printf("%d %d %d %d", p[0], p[1], p[2], p[3]);
delete [] p; // メモリの解放
}
// 文字列の場合
main()
{
char *p = NULL; // ポインタの宣言
p = (char*) malloc (13); // メモリの確保
strcpy(p, "good morning");
printf("%s", p); // 文字列の出力は、先頭アドレス
free(p); // メモリの解放
}
----- 出力結果 -----
good morning
--------------------
// 安全なメモリの解放
main()
{
int *p = NULL; // ポインタの宣言
p = new int [4]; // メモリの確保
// 安全なメモリの解放
if(p){
delete [] p;
p = NULL;
}
}
変数の代入は、値が代入されるが、ポインタの代入は、アドレスが代入される。
アドレスの代入とは、結局、メモリ領域を共有することである。
ポインタの代入を行った後であれば、ポインタのアドレスにも、インクリメントやデクリメントを使える。
その場合は、先頭アドレスが移動する。
++, --, +=, -= は、* よりも計算の優先順位が高いため、() を付けなかったら、アドレスがインクリメントやデクリメントされる。
main()
{
int arry[] = {5,4,3,2,1};
int *arry2 = arry; // ポインタの代入
arry2 ++; // アドレスのインクリメント
printf("%d\n", *arry2);
*arry2 ++; // アドレスのインクリメント(2 回目)
printf("%d\n", *arry2);
}
----- 出力結果 -----
4
3
--------------------
main()
{
int arr[] = {5,4,3,2,1};
(*arr) ++; // 値のインクリメント
printf("%d", *arr);
}
----- 出力結果 -----
6
--------------------
構造体
構造体は、複数の変数や配列やポインタなどをまとめて、1つの変数にするものである。
外部変数を構造体にすると、一括して初期化ができるため、便利である。
1つしか引数を渡せない関数に使う場合もある。
構造体を引数にする場合は、引数のサイズを小さくするために、基本的にアドレスを渡すようにする。
構造体内部の変数は、メンバと呼ぶ。
構造体の各メンバは . で指定するが、構造体がポインタの場合は -> で指定する。
ポインタのメンバがある場合は、構造体宣言時に 0 で初期化しないとバグになる場合がある。
struct STUDENT
{
int id;
char name[64];
};
STUDENT st = {0}; // 宣言時に 0 で初期化
main()
{
// ZeroMemory(&st, sizeof(st)); ← 関数内で初期化する場合
st.id = 5;
strcpy(st.name, "Michelangelo");
printf("%d %s", st.id, st.name);
}
----- 出力結果 -----
5 Michelangelo
--------------------
Show_Text(STUDENT *st)
{
printf("%d %s", st->id, st->name);
}
main()
{
st.id = 5;
strcpy(st.name, "Michelangelo");
Show_Text(&st);
}
ネスト・スコープ・外部変数・内部変数
{} で処理をくくる事をネストすると言う。
for 文 や if 文 などで使う場合も同様である。
変数や配列などの使用可能範囲は、スコープと呼ばれ、その変数などが定義された {} の内部に限られる。
関数(main も含む)内で定義された変数などは、内部変数と呼ばれ、スコープは、その関数の外を出ない。
関数の外で定義された変数などは、外部変数と呼ばれ、総ての関数で使用可能である。
内部変数はローカル変数、外部変数はグローバル変数と呼ばれる事もある。
2つ目の例は、スコープが違えば同名変数でも良いが、まぎらわしいから普通はしない。
char g_s[256] = "Hello"; // 外部変数
Show_Text()
{
printf("%s", g_s);
}
main()
{
strcat(g_s, " Mike"); // 文字列を付け足す関数
Show_Text();
}
----- 出力結果 -----
Hello Mike
--------------------
// ネストとスコープ
main()
{
int n = 5; // 内部変数
{
int n = 10; // 内部変数
printf("%d\n", n);
}
printf("%d\n", n);
}
----- 出力結果 -----
10
5 ← 普通はこうなるが、めじろ++ では 10 になるのは、おそらく、バグだろう
--------------------
静的変数
通常、変数はスコープの外に出ると破棄される(ただし、ポインタは明示的に解放しなくてはならない)。
しかし、static を付けて変数を定義すると、破棄されないでデータも保持されたままになる。
ただし、変数のスコープは変わらない。
これを静的変数と呼ぶ。
Show_Count1()
{
static int n = 1;
printf("%d\n", n ++);
}
Show_Count2()
{
int n = 1;
printf("%d\n", n ++);
}
main()
{
Show_Count1();
Show_Count1();
Show_Count2();
Show_Count2();
}
----- 出力結果 -----
1
2
1
1
--------------------
パスの設定・コマンドライン・バッチファイル
めじろ++ では、必要ないのだが、VC++ や Java などでは、ヘッダーファイル(.h)やライブラリファイル(.lib)が入っているディレクトリにはパスを通さなくては、それらのファイルを使用できない。
最初からそれらの開発環境に用意されているヘッダーやライブラリは、インストール時、あるいは実行時にパスが設定されるのだが、それとは別に外部から持って来た場合は、パスを設定する必要がある。
パスを通すには、SET という DOS のコマンドで環境変数 PATH などを設定するのだが、開発環境によってはGUIでできる。
VC++ の場合は、[ツール]→[オプション]→[ディレクトリ]タブで、インクルードファイル(ヘッダーのこと)とライブラリファイルのディレクトリを設定する。ここでの設定は、上にあるほど優先度が高い。
ただし、ソースファイルと同じディレクトリにヘッダーとライブラリを置く場合は、パスを通す必要はない。
パスが通っているヘッダーは、ソースファイルに
#include <??.h>
と書くが、同じディレクトリにある場合は、
#include "??.h"
と書く。?? はヘッダー名。
コマンドラインは、MS-DOS 時代にあった機能であり、実行ファイル名を打ち込んでリターンキーを押すと、そのプログラムを実行できた。
場合によっては、実行ファイル名の後にコマンドライン引き数を入れると、それがメイン関数で使用された。
つまり、DOS のコマンドラインは、プロンプト画面が 1 行分だけ表示されたものである。
ウィンドウズでは、このコマンドラインが、スタートメニューの[ファイル名を指定して実行]という名前になった。
この[ファイル名を指定して実行]というのも、その実行ファイルがあるディレクトリにパスが通っていなくてはならない。
Windows ディレクトリなどは、最初からパスが通っている。
パスが通っていないディレクトリの実行ファイルについては、フルパスを入力する。
例えば、以下のコードを、VC++ で作成し、ビルドして出来た sample1.exe を D ディレクトリ直下に置く。
次に[ファイル名を指定して実行]を実行し、
D:\sample1 8 hello
と入力して[OK]を押すと以下のような実行結果が表示される。
argc は、コマンドライン引数の個数であり、argv[0] にはプログラム名が入り、argv[1] 以降には、コマンドライン引数が入る。
これは、実行ファイルを作成しなければ出来ないので、めじろ++ ではできない。
ウィンドウプログラムのコマンドライン引数には、実行ファイルのアイコンにドラッグ&ドロップされたファイル名も入る(
二重起動防止とコマンドライン引数を参照)。
// sample1.c
#include <stdio.h>
main(int argc, char **argv)
{
int i:
for(i=0; i<argc; i++)
printf("%s\n", argv[i]);
getchar(); // プロンプトを表示状態にするため
}
----- 出力結果 -----
D:\sample1.exe
8
hello
--------------------
バッチファイルは、拡張子が .bat のテキストファイルであり、このファイルをダブルクリックすると、中に記述されたコマンドが上から順に総て実行される。
バッチファイルを実効ファイルと同じディレクトリに置けば、カレントディレクトリがそのディレクトリになるため、実行ファイルのフルパスを記述しなくて良いから便利である。
pause は、プロンプトを表示状態にするためのコマンドである。
バッチファイルに記述する実行ファイルは、コンソールプログラムだけでなく、ウィンドウプログラムも実行可能である。
----- sample2.bat -----
sample2 8 hello
pause
----- sample2.c -----
#include <stdio.h>
main(int argc, char **argv)
{
int i:
for(i=0; i<argc; i++)
printf("%s\n", argv[i]);
}
----- 出力結果 -----
D:\sample2.exe
8
hello
--------------------
バッチファイルでパスの設定も可能である。
PATH コマンドで環境変数 PATH にパスを追加した設定は、それ以下に記述されたコマンドが実行される間だけ有効である。常に有効にするには、[マイコンピュータ]を右クリックして[システムのプロパティ]で環境変数に追加する。例えば、バッチファイルで C:\Borland\Bcc55\bin を環境変数 PATH に追加する場合は、以下のように記述する。PATH には、実行ファイルの(入ったフォルダの)パスを設定するようである。
----- path_bcc.bat -----
SET PATH=%PATH%;"C:\Borland\Bcc55\bin"
プログラミング手法(フラグ)
プログラミング特有の考え方としてフラグがある。
これは、ある条件が発生した場合に、そのことを変数に記憶させることである。
これは、プログラミングにおいては、頻繁に使う。
フラグを使うことを、「フラグを立てる」と言うこともある。
main()
{
int flag = 0;
int i;
for(i=0;i<10;i++){
if(i == 5){
flag = 1;
}
}
if(flag){
printf("カウントが 5 まで来ました");
}
}
----- 出力結果 -----
カウントが 5 まで来ました
--------------------
変数は、少なくとも 1 バイトの容量を取るが、TRUE か FALSE を選択するだけのフラグの場合は、1 ビットあれば十分である。
unsigned char 型の変数を使う場合は、各ビットをフラグとして使えば、最大 8 個分のフラグを設定できることになる。
メモリ容量を節約したい場合は、このような使い方をする事もできる。
main()
{
unsigned char flag = 0;
int i;
for(i=0;i<10;i++){
if(i == 5){
flag |= 1;
}
if(i == 8){
flag |= 0x2;
}
if(i == 10){
flag |= 0x4;
}
}
if(flag & 1){
printf("カウントが 5 まで来ました\n");
}
if(flag & 0x2){
printf("カウントが 8 まで来ました\n");
}
if(flag & 0x4){
printf("カウントが 10 まで来ました\n");
}
}
----- 出力結果 -----
カウントが 5 まで来ました
カウントが 8 まで来ました
--------------------
プログラミング手法(オーバーロード)
関数名が同じで、引数の型や数が異なる別の関数を使うことができる。
これを、オーバーロードと呼ぶ。
関数名は別名にしても良いので、この手法は必須ではない。
Show_Text(int n)
{
printf("%d\n", n);
}
Show_Text(char *s)
{
printf("%s\n", s);
}
main()
{
Show_Text(4);
Show_Text("あいうえお");
}
----- 出力結果 -----
4
あいうえお
--------------------
プロトタイプで、引数にデフォルトの値を設定しておけば、その関数を呼び出すときに、その引数は必ずしも記述しなくて良い。これは、なんと呼ぶか知らない。これも、多少便利な程度である。
Show_Text(char *s, int n = 3);
Show_Text(char *s, int n)
{
printf("%s %d\n", s, n);
}
main()
{
Show_Text("かきくけこ");
Show_Text("たちつてと", 8);
}
----- 出力結果 -----
かきくけこ 3
たちつてと 8
--------------------