1 ポインタ変数の基本操作基本操作
int a,*iptr,*jptr,*kptr;
iptr = &a;
jptr = iptr;
*jptr = 100;
kptr = NULL;
図:
1.1 自己アドレスと自己空間
ポインタ変数もメモリ空間とメモリアドレスに対応する変数であり、ポインタ名がアドレスになります。メモリの空き容量はどれくらいありますか? マシン ワード (マシン ワード)、32 ビット CPU およびオペレーティング システムは 32 ビット、4 バイトであり、その値の範囲は 0x ~ 0xFFFFFFFF です。64 ビット CPU とオペレーティング システムは 64 ビットと 8 バイトを持ち、その値の範囲は 0x-0xFFFFFFFFFFFFFFFF です。
1.2 自己価値、別の住所、別の空間
ポインタ変数の値は、それが指す空間のアドレスであり、そのアドレスが指す空間のサイズは、ポインタ変数が指す型のサイズです。
1.3 宣言と初期化
ポインタ変数を初期化せずに宣言した場合、ポインタ変数は自身のメモリ空間を取得するだけで、ポインタはまだ決定されていないため、このときポインタ変数を左辺値として逆参照することは不正な操作となります。ポインター変数の逆参照を左辺値として使用する場合は、次の 3 つの方法があります。
int *ptr;
int *ptr_2;
int a = 1;
ptr_2 = &a;
// *ptr = 0; // 非法操作,其指向其指向的内存空间还未确定
ptr = &a; // ① 右值是一个变量地址
ptr = ptr_2; // ② 右值是一个同类型指针,且已初始化
ptr = (int*)malloc(sizeof(int));// ③ 右值是一个内存分配函数返回一个void指针
*ptr = 0; // 合法操作,ptr有了确定的指向及指向的内存空间;
1.4 関数間のポインタ値の受け渡し
関数(以下の例では funcForSpace())内で定義されたローカル変数(以下の例では a)は、関数のスタック フレームに保存され、ある関数が実行された後、別の関数(以下の例では stackFrame_reuse())が保存されます。この空間は stackFrame_reuse() によって再利用され、 a で使用していた空間は存在しなくなるため、ポインタ変数がローカル変数のメモリ空間を指している場合、そのアドレス値は有効な値ではありません。呼び出し元の関数に渡されます。
#include <stdio.h>
void funcForSpace(int **iptr) {
int a = 10;
*iptr = &a;
}
void stackFrame_reuse()
{
int a[1024] = {0};
}
int main()
{
int *pNew;
funcForSpace(&pNew);
printf("%d\n",*pNew); // 10,此时栈帧还未被重复使用
stackFrame_reuse();
printf("%d\n",*pNew); // -858993460,垃圾值
while(1);
return 0;
}
funcForSpace() でヒープ メモリの一部を割り当て、呼び出し関数に渡すことができます。
#include <stdio.h>
#include <malloc.h>
int g(int **iptr) { // 当试图修改主调函数的一级指针变量时,被调函数的参数是一个二级指针
if ((*iptr = (int *)malloc(sizeof(int))) == NULL)
return -1;
}
int main()
{
int *jptr;
g(&jptr);
*jptr = 10;
printf("%d\n",*jptr); // 10
free(jptr);
while(1);
return 0;
}
上記のコード ポインターの転送プロセスを示すことができます。
次の図 a はコンピューターのメモリを表し、b は関数の呼び出し時にスタック上に開かれるスタック フレーム スペースを表します。
2 ポインタ変数と配列名
配列名は、次のようなポインター算術演算を容易にするために、特定のコンテキストで配列の最初の要素を指すアドレスに変換されます。
#include <stdio.h>
int main()
{
int a[5] = {0};
char b[20] = {0};
*(a+3) = 10; // a+3是指相对于地址a,偏移sizeof(int)个字节
*(b+3) = 'x'; // b+3是指相对于地址b,偏移sizeof(char)个字节
printf("%d, %c\n",a[3],b[3]); // 10, x
while(1);
return 0;
}
上記のコード ポインターのオフセットの詳細を示すことができます。
3 呼び出し関数と呼び出される関数間のポインタ転送
次のコードを見てください。
#include <stdio.h>
void swap1(int x, int y) {
int tmp;
tmp = x; x = y; y = tmp;
}
void swap2(int *x, int *y) {
int tmp;
tmp = *x; *x = *y; *y = tmp;
}
void caller()
{
int a = 10;
int b = 20;
swap1(a,b);
printf("%d %d\n",a,b);
swap2(&a,&b);
printf("%d %d\n",a,b);
}
int main()
{
caller();
return 0;
}
上記のコードは、次の図で理解できます。
swap1 は値を渡します:
swap2アドレス転送(ポインタ転送):
4 関数パラメータとしての配列
2 次元配列は配列の配列であり、n 次元配列は n-1 次元配列の配列です。メモリは 1 次元のバイト シーケンスであり、いわゆる n 次元配列は実際には単なる論理表現であり、その物理構造は依然として 1 次元の線形です。
n 次元配列の要素は n-1 次元配列です。ポインターを使用して n 次元配列を指す場合、関数パラメーターとして使用する場合も同様に、ポインター型には n-1 次元の長さ情報が必要です。
void g(int a[][2]) { // void g(int(*a)[2]){是相同写法
a[2][0] = 5;
}
void caller()
{
int a[3][2];
int (*p)[2] = a;
*(*(p+2)+0) = 7; // p=2表示相对于地址p偏移sizeof(*p)
printf("%d\n",a[2][0]); // 7
g(a);
printf("%d\n",a[2][0]); // 5
}
次のコードは、次の図を使用して理解できます。
参考:Kyle Loudon《 Mastering Algorithms with C》