Cポインタの4:ポインタと配列を深く理解する

5676 ワード

原文:
Cポインタの4:ポインタと配列を深く理解する
配列はC内蔵の基本データ構造であり,配列表現法とポインタ表現法が密接に関連している.1つの一般的な誤り認識は、配列とポインタが完全に交換できることである.配列名はポインタとして使用できる場合があるが、配列の名前はポインタではない.配列とポインタの違いの1つは,配列の名前が配列アドレスを返すことができるにもかかわらず,名前が付与操作のターゲットにならないことである.
概要
配列はインデックスでアクセスできる同質要素の連続集合である.配列の要素はメモリ内で隣接しており、要素は同じタイプです.配列の長さは固定され,realloc関数と変長配列は長さの変化に対応する配列の技術を提供する.
1 D配列は、メンバーにインデックスでアクセスする線形構造です.メモリモデルによっては、配列の長さが異なる場合があります.
int vector[5];


配列の名前はメモリを参照しているだけで、配列にsizeofオペレータを使用すると、その配列に割り当てられたバイト数が得られます.配列の要素数を知るには、要素の長さで除算すればいいです.
int vector[5];

printf("vector is %p
size is %d
", &vector, sizeof(vector)); //vector is 0xbffb8c08 //size is 20

1つのブロック文で1次元配列を初期化できます.
int vector[5] = {1,2,3,4,5};


2 D配列を宣言します.2 D配列は配列の配列として使用でき、2 D配列にアクセスするには1つの下付き文字のみを使用して対応する行のポインタを得ることができます.
int matrix[2][3] = {{1,2,3},{4,5,6}};

printf("0 is %p
1 is %p
", &matrix[0], &matrix[1]); //0 is 0xbf984044 //1 is 0xbf984050

2つのアドレスがちょうど12バイト差、すなわちmatrix配列の1行の長さであることがわかります.
配列アドレスをポインタに割り当てる.
int* pv = vector;


この書き方は&vector[0]と等価であり、&vectorが返す配列全体のポインタとは異なることに注意してください.1つは配列ポインタ、1つは整数ポインタで、ポインタのタイプが違います.pvは実際にはアドレスを表し,*(pv+i)の書き方はpv[i]に等価であり,前者をポインタ表現法,後者を配列表現法と呼ぶ.
printf("pv[0] is %d
*(pv+1) is %d
",pv[0],*pv); //pv[0] is 1 //*(pv+1) is 1

pvポインタにはメモリブロックのアドレスが含まれており、カッコ表現ではpvに含まれるアドレスを取り出し、ポインタ演算子でインデックスiを加算し、新しいアドレスを引いてその内容を返します.ポインタに整数を付けるのは、実際には整数とデータ型の長さの積を加えることであり、配列名にも適用され、*(pv+1)と*(vector+1)は等価である.したがって、配列表現は、オフセットおよびインデックス解除操作として理解できます.vector[i]と*(vector+i)の2つの方法の結果は同じであり,実装にわずかな差があるだけで無視できる.注意ポインタは左値として使用できますが、配列名は左値として使用できません.vector=vector+1は間違った書き方です.
1 D配列
スタックからメモリを割り当て、ポインタにアドレスを割り当てると、ポインタに下付きを使用して、このメモリを配列として使用することができます.mallocで作成した既存の配列の長さはrealloc関数で調整できます.C 99は変長配列をサポートするが、変長配列は関数内部でしか宣言できない.したがって、配列のライフサイクルが関数より長い場合、またはC 99を使用していない場合はreallocしか使用できません.
次の関数は、ユーザーの入力を受け入れ、realloc関数を使用して動的に申請するために必要なメモリを使用して、車に戻って入力を終了します.
char* getLine(void)

{

const size_t sizeIncrement = 10;

char* buffer = malloc(sizeIncrement);

char* currentPosition = buffer;

size_t maximumLength = sizeIncrement;

size_t length = 0;

int character;

if(currentPosition == NULL){return NULL;}

while(1)

{

character = fgetc(stdin);

if(character == '
'){break;} if(++length >= maximumLength) { maximumLength += sizeIncrement; char* newBuffer = realloc(buffer,maximumLength); if(newBuffer == NULL){free(buffer); return NULL;} currentPosition = newBuffer + (currentPosition - buffer); buffer = newBuffer; } *currentPosition++ = character; } *currentPosition = '\0'; printf("buffer is %s
", buffer); return buffer; } getLine();

1次元配列をパラメータとして関数に渡すのは実際に値によって配列のアドレスを渡すので、関数配列の長さを教える必要があります.そうしないと、関数には1つのアドレスしかなく、配列がどれだけ長いか分かりません.文字列の場合、NUL文字で長さを判断できます.一部の配列では判断できません.
配列を指すポインタとポインタ配列、配列ポインタが異なることを宣言します.配列を指すポインタは、配列の下に0と表記された要素を指し、ポインタ配列は要素がポインタの配列であり、配列ポインタは配列タイプのポインタである.
int vector[2] = {1,2};

int* pv1 = vector;//       

int* pv2[2];//    ,                 

int (*pv3)[2];//       


次に,配列表現法とポインタ表現法のポインタ配列への応用を区別する.
int* array[5];

array[0] = (int*) malloc (sizeof(int));

*array[0] = 100;

*(array+1) = (int*) malloc (sizeof(int));

**(array+1) = 200;


ここでarray[1]と*(array+1)は等価であり、実際にはポインタタイプであり、malloc関数を用いてポインタにスタックにメモリを割り当て、インデックスポインタを解いてデータに値を割り当てるため、**(array+1)は理解に難くない.アドレスとインデックスを取る操作に相当します.もちろん、*array[0][0]の代わりにarray[0][0]を使用することもできます.
ポインタと多次元配列
多次元配列の一部をサブ配列と見なすことができます.たとえば、2次元配列の各行を1次元配列と見なすことができます.配列は行-列順に格納され、2行目の1番目の要素のメモリアドレスは1行目の最後の要素の後ろに続いています.
int matrix[2][3] = {{1,2,3},{4,5,6}};

int (*pmat)[3] = matrix;//3        

printf("size of pmat[0] is %d
", sizeof(pmat[0])); //size of pmat[0] is 12

配列ポインタの最初の要素の長さは12バイト、すなわち最初の行の長さであることがわかります.最初の行の最初の要素にアクセスするには、pmat[0][0]でアクセスする必要があります.array[i][j]はarray+i*sizeof(row)+j*sizeof(element)に等しい.sizeof(row)は1行の総サイズであり、sizeof(element)は単一要素のサイズであり、arrayはarray[0][0]のアドレスである.
関数に配列パラメータを渡す場合は、配列の次元数と各次元のサイズをどのように渡すかを考慮します.次の2つの方法は、2 D配列を渡す方法です.
void display2DArray(int arr[][3], int rows){}

void display2DArray(int (*arr)[3], int rows){}


どちらのメソッドも行数を指定します.渡された配列次元が2次元を超えている場合は、1次元の部分を除いて、他の次元の長さを指定する必要があります.array 3 D[3][2][4]の配列を渡すと:
void display3DArray(int (*arr)[2][4], int rows){}


mallocを使用して2 D配列の異なるサブ配列にメモリを割り当てると、メモリの割り当てが不連続になる可能性があります.ブロック文を使用して一度に初期化すると、この問題は発生しません.mallocを使用して2 D配列に連続したメモリを割り当てるには、2つのポリシーがあります.2 D配列には3行4列があると仮定します.1つ目は、すべてのメモリ3*4*sizeof(element)を一度に割り当て、前述したarray[i][j]へのアクセス方法でアクセスするメモリアドレスを手動で計算する方法です.2つ目の方法は2つのステップに分かれています.最初のステップでは、mallocを使用して、2 D配列の各行を指すポインタを格納するためのメモリを割り当てます.第2のステップはarray[0][0]のポインタにすべてのメモリを割り当て、array[1][0]とarray[2][0]の位置を計算し、この2つのキーポインタにそれぞれ値を割り当てることで、各行のポインタに基づいて下付きカーソルを使用してアクセスすることができます.
int rows = 3;

int columns = 5;

//     

int* matrixx = (int*) malloc (rows * columns * sizeof(int));

//     

int **matrixy = (int**) malloc (rows * sizeof(int*));

matrixy[0] = (int*) malloc (rows * columns * sizeof(int));

int i = 1;

while(i<rows)

{

i++;

matrixy[i] = matrix[0] + i * columns;

}


不規則配列は、各行の列数が異なる2次元配列です.複合字面量を使用して不規則配列を初期化できます.
(const int) {100}

(int [3]) {10, 20, 30}


不規則配列のアクセスとメンテナンスは面倒なので、使用前に慎重に考慮してください.