C の例の動的配列。 2次元動的配列

30.07.2019 その他

通常、特定の変数に必要なメモリ量は、コンパイル プロセスの前にその変数を宣言することによって指定されます。 サイズが事前に不明な変数を作成する必要がある場合は、動的メモリが使用されます。 予約 そして 解放 C++ プログラムのメモリの問題はいつでも発生する可能性があります。 操作が実行されます 分布 2 つの方法で記憶します。

  • 関数を使用して マロック, コールク, 再ロックそして 無料;
  • オペレーターを通して 新しいそして 消去.

関数 マロック 埋蔵量指定されたオブジェクトを格納するメモリ セルの連続ブロックを取得し、そのブロックの最初のセルへのポインタを返します。 関数の呼び出しは次のようになります。

void *malloc(サイズ);

ここ サイズ- 割り当てられたメモリ領域のサイズをバイト単位で決定する符号なし整数値。 メモリ予約が成功した場合、関数は戻ります。 型変数 空所 *、これは任意のポインタ型にキャストできます。

関数 - コールクメモリ割り当ても目的としています。 以下のエントリは強調表示されることを意味します 番号要素による サイズバイト。

void *calloc(nime, size);

この関数は、選択された領域へのポインタを返します。 ヌルメモリを割り当てられない場合。 この関数の特別な機能は、選択したすべての要素をゼロにリセットすることです。

関数 再ロックサイズ変更以前に割り当てられたメモリ。 彼らは彼女に次のように呼びかけます。

char *realloc (void *p, size);

ここ p- サイズを変更する必要があるメモリ領域へのポインタ サイズ。 関数の結果としてメモリ領域のアドレスが変更された場合、結果として新しいアドレスが返されます。 最初のパラメータの実際の値が ヌル、次に関数 再ロック関数と同じように動作します マロックつまり、サイズのメモリ領域を割り当てます。 サイズバイト。

割り当てられたメモリを解放するには、関数を使用します 無料。 彼らは彼女に次のように呼びかけます。

void free(void *p サイズ);

ここ p- 関数によって以前に割り当てられたメモリ位置へのポインタ マロック, コールクまたは 再ロック.

オペレーター 新しいそして 消去同様の機能 マロックそして 無料. 新しいメモリを割り当てます。その唯一の引数は、予約するバイト数を指定する式です。 オペレータは、割り当てられたメモリ ブロックの先頭へのポインタを返します。 オペレーター 消去メモリを解放します。その引数は、解放する必要があるブロックの最初のセルのアドレスです。

動的配列- 可変長の配列。プログラムの実行中にメモリが割り当てられます。 メモリの割り当ては関数によって実行されます コールク、マロックまたはオペレーター 新しい。 割り当てられたメモリ位置の最初の要素のアドレスは、ポインタとして宣言された変数に格納されます。 たとえば、次の文はポインタが記述されていることを意味します。 マスそして、連続領域の先頭のアドレスが割り当てられます。 動的メモリ、演算子を使用して強調表示 新しい:

int *mas=新しい int;

割り当てられたメモリの量は、10 個の int 値を格納するのに十分です。

実際、変数では マス動的配列のゼロ要素のアドレスが格納されます。 したがって、割り当てられたメモリ領域内の次の最初の要素のアドレスは、 マス+1、a マス+i は i 番目の要素のアドレスです。 動的配列の i 番目の要素には、通常どおり mas[i] を使用するか、別の方法でアクセスできます。 *(マス+イ) 。 割り当てられたメモリ領域の境界を超えないようにすることが重要です。

いつ 動的配列(プログラム動作中のいつでも) が不要になった場合は、関数を使用してメモリを解放できます。 無料またはオペレーター 消去.

この教訓を強化するいくつかのタスクを検討することを提案します。

問題 1

動的配列の実数要素の合計を求めます。

//malloc関数とfree関数の使用例 #include "stdafx.h" #include 名前空間 std を使用します。 int main() ( setlocale(LC_ALL,"Rus"); int i, n; float *a; //float へのポインタ float s; cout<<"\n"; cin>>n; //配列サイズを入力 //n 個の実数要素の配列にメモリを割り当てる a=(float *)malloc(n*sizeof(float)); コート<<"Введите массив A \n"; //ввод элементов массива for (i=0; i>*(a+i); ) // (s=0, i=0; i の配列要素の合計を累積します)

//malloc関数とfree関数の使用例

#include "stdafx.h"

#含む

名前空間 std を使用します。

int main()

int i、n;

float * a ; //float へのポインタ

float s ;

コート<< "\n" ; cin >> n ; //配列サイズを入力

//n 個の実数要素の配列にメモリを割り当てます

a = (float * ) malloc (n * sizeof (float ) ) ;

コート<< "配列 A を入力してください\n";

//配列要素を入力

for (i = 0 ; i< n ; i ++ )

cin >> * (a + i) ;

//配列要素の合計の累積

for (s = 0 , i = 0 ; i< n ; i ++ )

s += * (a + i) ;

//金額の値を出力する

コート<< "S=" << s << "\n" ;

//メモリを解放する

無料(a);

システム("一時停止");

0 を返します。

問題 2

整数の動的配列を変更して、正の要素が負になるように、またはその逆になるようにします。 この問題を解決するには、各要素に -1 を掛けます。

//new 演算子と delete 演算子の使用例 #include "stdafx.h" #include 名前空間 std を使用します。 int main() ( setlocale(LC_ALL,"Rus"); int i, n; //配列要素数を入力 cout<<"n="; cin>>n; //メモリ割り当て int *a=new int[n]; コート<<"Введите элементы массива:\n"; //ввод массива for (i=0; i>a[i]; //(i=0; i に対して指定された配列を出力します)

// new 演算子と delete 演算子の使用例

#include "stdafx.h"

#含む

名前空間 std を使用します。

int main()

setlocale(LC_ALL, "Rus");

int i、n;

//配列の要素数を入力

コート<< "n=" ; cin >> n ;

//メモリ割り当て

int * a = 新しい int [n] ;

コート<< "配列要素を入力してください:\n";

//配列を入力

// 10 要素の 2 次元動的配列の宣言:

float **ptrarray = 新しい float* ; // 配列内の 2 行

for (int カウント = 0; カウント< 2; count++)

ptrarray = 新しい浮動小数点数; // 5 つの列

// ここで、ptrarray は、float 型の実数の配列に割り当てられたメモリ領域へのポインタの配列です。

まず、2 次ポインター float **ptrarray が宣言されます。これは、float* ポインターの配列を参照します。配列のサイズは 2 です。 . その後、for ループで、宣言された配列の各行が 2行目メモリは 5 つの要素に割り当てられます。 結果は 2 次元の動的配列 ptrarray になります。2 次元の動的配列に割り当てられたメモリを解放する例を考えてみましょう。

// 2 次元動的配列に割り当てられたメモリを解放します。

for (int カウント = 0; カウント< 2; count++)

ptrarray を削除します。

// ここで 2 は配列内の行数です

#含む
#含む
#含む
ボイドメイン()
{

int *a; // 配列へのポインタ

システム("chcp 1251");

scanf("%d", &n);

scanf("%d", &m);

// メモリ割り当て

a = (int*) malloc(n*m*sizeof(int));

// 配列要素の入力

for(i=0; i

for(j=0; j

printf("a[%d][%d] = ", i, j);

scanf("%d", (a+i*m+j));

// 配列要素を出力します

for(i=0; i

for(j=0; j

printf("%5d ", *(a+i*m+j)); // 配列要素に対して 5 人の知人

getchar(); getchar();
}

実行結果

行数を入力してください: 3

列数を入力してください: 4

ポインターの配列を使用して、2 次元配列に動的にメモリを割り当てる別の方法も可能です。 これを行うには、次のものが必要です。
- ポインターの配列に RAM のブロックを割り当てます。
- 目的の行列の行である 1 次元配列に RAM のブロックを割り当てます。
- 行のアドレスをポインタの配列に書き込みます。

malloc() 関数は、動的に割り当てられたメモリ領域から割り当てられた size size のメモリ領域の最初のバイトへのポインタを返します。 動的メモリ領域に十分なメモリがない場合は、null ポインタが返されます。

#含む
#含む
#含む
ボイドメイン()
{

int**a; // 文字列へのポインタへのポインタ

システム("chcp 1251");

printf("行数を入力してください: ");

scanf("%d", &n);

printf("列数を入力してください: ");

scanf("%d", &m);

// 文字列へのポインタにメモリを割り当てます

a = (int**)malloc(n*sizeof(int*));

// 配列要素の入力

for(i=0; i

// 文字列を保存するためのメモリの割り当て

a[i] = (int*)malloc(m*sizeof(int));

for(j=0; j

printf("a[%d][%d] = ", i, j);

scanf("%d", &a[i][j]);

// 配列要素を出力します

for(i=0; i

for(j=0; j

printf("%5d ", a[i][j]); // 配列要素に対して 5 人の知人

無料(a[i]); // 文字列用のメモリを解放します

getchar(); getchar();
}

プログラムの実行結果は前の場合と同様です。

行ポインターの動的メモリ割り当てを使用すると、空き配列を割り当てることができます。 無料 は 2 次元配列 (行列) であり、行のサイズは異なる場合があります。 空き配列を使用する利点は、可能な最大長の文字列を収容するためにあまりにも多くのコンピューター メモリを割り当てる必要がないことです。 実際、自由配列は 1 次元データ配列へのポインターの 1 次元配列です。

ポインタ。

ポインタは、データが配置されているアドレスを値とする変数です。 アドレスは、データが配置されている、またはデータが配置されているメモリ セルの番号です。

SI のデータ型に応じて、ポインタは次のように分類されます。

型付きポインターは、特定の型 (システムまたはユーザー) のデータのアドレスを含むポインターです。

型なしポインタは、型が指定されていないデータ アドレス (単なるアドレス) を含むポインタです。

ポインタ宣言。

ポインタの設定;

ポインタにある値にアクセスします。 SI 言語でのポインタの宣言 (記述) は次の形式になります。

*名前 [=値] と入力します。

宣言された場合、SI 内のポインターは、代入記号を通じて対応する値を示すことによって初期化できます。 この値は、次のいずれかの形式で記述されたアドレスである必要があります。

NULL 値 (NULL 識別子)。

別のポインタ。

変数アドレス (アドレスを取得する操作による);

ポインタ演算を表す式。

動的メモリ割り当ての結果であるアドレス。

#含む

整数変数; // 通常の整数変数

int *ptrVar; // 整数ポインタ (ptrVar は int 型の変数を参照するため、int 型である必要があります)

ptrVar = // var 変数の値が存在するメモリ セルのアドレスをポインタに割り当てます

scanf("%d", &var); // 変数 var にはキーボードから入力された値が含まれます

printf("%d\n", *ptrVar); // ポインタ経由で値を出力

実行結果:6 6

講義その3。

機能。

関数は、特定のアクションまたはアクションのグループを実行する、構文的に識別された名前付きプログラム モジュールです。 各関数には独自のインターフェイスと実装があります。 関数インターフェイス – 関数の名前、そのパラメーターのリスト、および戻り値の型を指定する関数ヘッダー。

SI 言語での関数の記述は、プログラム内の他の関数の記述以外の任意の場所で実行され、次の 3 つの要素で構成されます。

1. 関数プロトタイプ。

2. 関数ヘッダー。

3. 関数本体。

関数プロトタイプは、関数記述のオプションの部分であり、指定されたプロトタイプに対応するインターフェイスを持つ関数を宣言することを目的としています。

型名(仮パラメータ型のリスト);

関数パラメータは、関数が呼び出されたときに関数に渡される値です。

関数ヘッダー – 関数のインターフェイス部分の説明。戻り値の型、関数の名前、関数の仮パラメータのリストが含まれます。 関数ヘッダーを宣言する構文は次のとおりです。

型名(仮引数のリスト)

関数ヘッダーの例:

Int func(int i, double x, double y)

Void func(int ind, char *string)

ダブル関数(void)

関数の本体は、関数が呼び出されたときに実行されるプログラム コードを含む実装部分です。 関数本体は常に関数頭部の直後に配置され (分離することはできません)、中括弧で囲まれます。

数値の階乗を計算する関数を SI に実装します。

二重階乗 (符号なし);

二重階乗(符号なし数値)

二重ファクト = 1.0;

For(符号なし i=1;i<=num;i++)

ファクト *= (double)i;

事実を返します。

構造物。

構造体は、メモリ内で順序付けられたさまざまなタイプの要素のセットを表す複合データ型です。 構造内の各要素には独自の名前があり、フィールドと呼ばれます。

構造体の SI での宣言は次のようになります。

構造体 [型名]

フィールド_1;

フィールド_2;

フィールド_N;

   ) [変数のリスト];

構造体フィールドの宣言は、初期化なしでのみ可能です。 構造体の説明内で連続する複数のフィールドが同じ型である場合、それらを記述するために、同じ型の複数の変数を宣言するための構文を使用できます。

ファイル。

ファイルは、何らかの記憶媒体上のデータの名前付き領域です。 ファイルの種類 (SI 言語に関連):
   文章;
   バイナリ。
ファイルに対して実行される基本的な操作:
1.ファイルを開きます。
2.データの読み取りと書き込み。
3.ファイルを閉じます。

追加の操作:
1.ファイルナビゲーション。
2. ファイルを操作するときのエラーの処理。
3.ファイルの削除と名前変更。
4.変数の説明

オープニングモード SI を含むファイル

ストリームのリダイレクト
  FILE * freopen(const char *ファイル名, const char *モード, FILE *ストリーム);

関数は以下を返します:
  ファイルへのポインタ - すべて問題ありません。
  NULL – オーバーライドエラー。

ファイルを閉じる
  int fclose(FILE *stream);

関数は以下を返します:
  0 – ファイルは正常に閉じられました。
  1 – ファイルを閉じる際にエラーが発生しました。

ファイルの終わりのチェック
  int feof(FILE *stream);
  stream - 開いているファイルへのポインタ。

関数は以下を返します:
  0 – ファイルの終わりにまだ到達していない場合。
  !0 – ファイルの終わりに達しました。

テキストファイルを開く
2 番目のパラメーターでは、文字 t (オプション) をさらに指定します。
  rt、wt、at、rt+、wt+、at+

テキストファイルからの読み取り

フォーマットされた読み方
  int fscanf(FILE *ストリーム, const char * フォーマット, ...);

関数は以下を返します:
  >0 – 正常に読み取られた変数の数、
  0 – どの変数も正常に読み取られませんでした。
  EOF – エラーまたはファイルの終わりに達しました。
一行読む

関数は以下を返します:
  バッファ - すべて問題ありません。
一行読む
  char * fgets(char * バッファ, int maxlen, FILE *stream);

関数は以下を返します:
  バッファ - すべて問題ありません。
  NULL – エラーまたはファイルの終わりに達しました。
シンボルを読む
  int fgetc(FILE *stream);
関数は次を返します。
  シンボルコード - すべてが正常であれば、
  EOF – エラーが発生したか、ファイルの終わりに達した場合。
キャラクターをストリームに戻す
  int ungetc(int c, FILE *stream);
関数は次を返します。
  シンボルコード – すべてが成功した場合、
  EOF – エラーが発生しました。

テキストに書き込む SIファイル

フォーマットされた出力
  int fprintf(FILE *ストリーム, const char *フォーマット, ...);
関数は次を返します。
  書かれた文字数 - すべてが正常であれば、
  負の値 – エラーがある場合。
文字列の書き込み
  int fputs(const char *string, FILE *stream);
関数は次を返します。
  書かれた文字数 - すべて問題ありません、
  EOF – エラーが発生しました。
シンボルを書く
  int fputc(int c, FILE *stream);
関数は次を返します。
  記録されたシンボルのコード - すべて問題ありません。
  EOF – エラーが発生しました。
バイナリファイルを開く
  2 番目のパラメーターはさらに記号 b (必須) を指定します: rb、wb、ab、rb+、wb+、ab+
バイナリファイルからの読み取り
  size_t fread(void *buffer, size_t サイズ, size_t num,FILE *stream);
この関数は、読み取られたブロックの数を返します。 数値未満の場合は、エラーが発生したか、エラーに達しました。
ファイルの終わり。

バイナリファイルへの書き込み
  size_t fwrite(const void *buffer, size_t サイズ, size_t num, FILE *stream);
この関数は書き込まれたブロックの数を返します。 num 未満の場合は、エラーが発生しています。

ファイルナビゲーション

ファイル内の現在のオフセットを読み取ります。
  long int ftell(FILE *stream);
ファイル内の現在のオフセットを変更します。
  int fseek(FILE *ストリーム、long int オフセット、int 原点);

SEEK_SET (0) – ファイルの先頭から。
  SEEK_CUR (1) – 現在の位置から。
  SEEK_END (2) – ファイルの終わりから。
関数は次を返します。
  0 – すべて問題ありません。
  !0 – エラーが発生しました。
ファイルの先頭に移動します。
  void rewind(FILE *stream);
ファイル内の現在位置を読み取る:
  int fgetpos(FILE *stream, fpos_t *pos);
ファイル内の現在位置を設定します。
  int fsetpos(FILE *stream, const fpos_t *pos);
関数は以下を返します。
  0 – すべてが成功しました。
  !0 – エラーが発生しました。
fpos_t 構造体:
  typedef struct fpos_t (
   遠い昔。
   mbstate_t wstate;
  ) fpos_t;

エラーサインが表示される場合:
  int ferror(FILE *stream);
エラーが発生した場合、関数はゼロ以外の値を返します。
エラーリセット機能:
  void clearerr(FILE *stream);
エラーメッセージ機能:
  void perror(const char *string);

バッファリング

バッファクリア機能:
  int fflush(FILE *stream);
関数は次を返します。
  0 – すべて問題ありません。
  EOF – エラーが発生しました。
バッファ管理機能:
  void setbuf(FILE *ストリーム, char *バッファ);

サイズ BUFSIZ のバッファを作成します。 ストリームへの入力または出力の前に使用されます。

一時ファイル

一時ファイル作成機能:
  ファイル * tmpfile(void);
wb+ モードで一時ファイルを作成します。 ファイルを閉じると、後者は自動的に削除されます。
一時ファイル名生成機能:
  char * tmpnam(char *buffer);

削除と名前変更

ファイル削除機能:
  int 削除(const char *ファイル名);
ファイル名変更機能:
  int rename(const char *fname, const char *nname);
関数は以下を返します。
  0 – 成功した場合、
  !0 – それ以外の場合。

講義その4。

スタック。

スタックは後入れ先出し (LIFO) ベースで動作するため、キューの逆です。 積み重ねを視覚化するには、プレートの積み重ねを思い浮かべてください。 テーブルに最初に置かれた皿が最後に使用され、最後に一番上に置かれた皿が最初に使用されます。 スタックは、コンパイラやインタープリタなどのシステム ソフトウェアでよく使用されます。

スタックを操作する場合、要素の挿入と取得の操作が主な操作になります。 これらの操作は伝統的に「プッシュ」および「ポップ」と呼ばれています。 したがって、スタックを実装するには、値をスタックに「プッシュ」する Push() と、スタックから値を「ポップ」する Pop() の 2 つの関数を作成する必要があります。 スタックとして使用されるメモリ領域を割り当てる必要もあります。 この目的のために、配列を割り当てるか、動的メモリ割り当て用に提供されている C 言語関数を使用してメモリを動的に割り当てることができます。 キューと同様に、フェッチ関数はリストから要素を取得し、要素がまだ他の場所に格納されていない場合は削除します。 以下は、整数配列を操作する Push() 関数と Pop() 関数の一般的な形式です。 他の種類のデータ スタックは、配列の基礎となるデータ型を変更することで編成できます。

int tos=0; /* スタックの最上位 */

/* 要素をスタックにプッシュします。 */

ボイドプッシュ(int i)

if(tos >= MAX) (

printf("スタックがいっぱいです\n");

/* スタックの最上位要素を取得します。 */

if(tos< 0) {

printf("スタックは空です\n");

スタックを返します。

tos 変数 (「スタックのトップ」) には、スタックのトップのインデックスが含まれます。 これらの関数を実装するときは、スタックがフルまたは空の場合を考慮する必要があります。 この場合、空のスタックの兆候は tos が 0 に等しいことであり、スタック オーバーフローの兆候は、tos が増加しすぎてその値が配列の最後のセルを超えたところを指していることです。

スタックを操作する例。

スタックは、固定サイズの配列ではなく、動的に割り当てられたメモリに配置されます。 このような単純な例では動的メモリ割り当てを使用する必要はありませんが、動的メモリを使用してスタック データを保存する方法を見ていきます。

/* 4 つのステップからなる単純な計算機。 */

#含む

#含む

int *p; /* 空きメモリ領域へのポインタ */

int *tos; /* スタックの先頭へのポインタ */

int *bos; /* スタックの一番下へのポインタ */

void Push(int i);

p = (int *) malloc(MAX*sizeof(int)); /* スタック用のメモリを取得 */

printf("メモリ割り当て中のエラー\n");

bos = p + MAX-1;

printf("4 ステップの計算機\n");

printf("終了するには「q」を押してください\n");

printf("%d\n", a+b);

printf("%d\n", b-a);

printf("%d\n", b*a);

printf("0 で割ります。\n");

printf("%d\n", b/a);

case ".": /* スタックの一番上の内容を表示します */

printf("スタック最上位の現在の値: %d\n", a);

) while(*s != "q");

/* 要素をスタックにプッシュします。 */

ボイドプッシュ(int i)

if(p > bos) (

printf("スタックがいっぱいです\n");

/* スタックから最上位の要素を取得します。 */

if(p< tos) {

printf("スタックは空です\n");

列。

キューは、先入れ先出しベースで処理される情報の線形リストです。 この原理 (およびデータ構造としてのキュー) は、FIFO と呼ばれることもあります。 これは、キューに配置された最初の要素が最初にキューから受信され、2 番目に配置された要素が次に削除されるということを意味します。 これがキューを操作する唯一の方法です。 個々の要素へのランダム アクセスは許可されません。

キューがどのように機能するかを想像するために、qstore() と qretrieve() (「ストア」 - 「保存」、「取得」 - 「受信」) という 2 つの関数を紹介します。 qstore() 関数はキューの最後に要素を配置し、qretrieve() 関数はキューの先頭から要素を削除してその値を返します。 表は、そのような操作のシーケンスを示しています。

アクション キューの内容
qstore(A)
qストア(B) AB
qストア(C) A B C
qretrieve() は A を返します BC
qストア(D) B C D
qretrieve() は B を返します CD
qretrieve() は C を返します D

フェッチ操作では、要素がキューから削除され、別の場所に保存されていない限り破棄されることに注意してください。 したがって、すべての要素が取得されると、キューは空になります。

プログラミングでは、多くの問題を解決するためにキューが使用されます。 このようなタスクの最も一般的なタイプの 1 つはシミュレーションです。 キューは、オペレーティング システムのタスク スケジューラや I/O バッファリングでも使用されます。

/* ミニイベントスケジューラ */

#含む

#含む

#含む

#含む

char *p、*qretrieve(void);

void enter(void)、qstore(char *q)、review(void)、delete_ap(void);

for(t=0; t< MAX; ++t) p[t] = NULL; /* иницилизировать массив

空のポインター */

printf("入力(E)、リスト(L)、削除(R)、終了(Q): ");

*s = toupper(*s);

/* 新しい予定をキューに挿入します。 */

ボイドエンター(ボイド)

printf("予定 %d を入力してください: ", spos+1);

if(*s==0) ブレーク; /* 録音は行われませんでした */

p = (char *) malloc(strlen(s)+1);

printf("メモリが不足しています。\n");

if(*s) qstore(p);

/* キューの内容を表示します。 */

無効なレビュー(無効)

for(t=rpos; t< spos; ++t)

printf("%d. %s\n", t+1, p[t]);

/* キューから予定を削除します。 */

void delete_ap(void)

if((p=qretrieve())==NULL) 戻り値;

printf("%s\n", p);

/* 予定を挿入します。 */

void qstore(char *q)

printf("完全なリスト\n");

/* 予定を取得します。 */

char *qretrieve(void)

if(rpos==spos) (

printf("もう会議はありません。\n");

p を返します。

リスト。

単一リンク循環リストは、構造体の再帰的宣言、または型構造自体内の構造体へのポインタです。

int データ;//データフィールド

s *next;//次の要素

) *first,*curr;//最初の要素と現在の要素

初期化:

最初->次=現在;

最初の要素を取得するには、first->data を使用します

新しい要素を追加するには:curr->next=new s;

curr=curr->next;//最後の項目に移動

たとえば、50 番目の要素を取得するには、リストをループします。

curr=first;//最初に移動

for(int i=0;i<50;i++)

if(curr->next!=NULL)

curr=curr->next;


関連情報。


講義の目的: 宣言、1 次元動的配列のメモリの割り当てと解放、要素へのアクセスを学習し、C++ 言語で 1 次元動的配列を使用して問題を解決する方法を学びます。

多くのデータ構造を使用する場合、データ構造のサイズに応じて可変サイズにする必要があることがよくあります。 リードタイムプログラム。 このような場合に使用する必要があるのは、 動的メモリ割り当て。 このようなデータ構造の中で最も一般的なものの 1 つは配列であり、そのサイズは最初は定義または固定されていません。

によると 言語標準配列は要素のコレクションであり、各要素は同じ属性を持ちます。 これらすべての要素は、配列の先頭に対応するアドレスから開始して、行内の隣接するメモリ位置に配置されます。 つまり、配列の要素の総数とそれに割り当てられるメモリのサイズは、配列の定義によって完全かつ一意に指定されます。 しかし、これは常に便利であるとは限りません。 場合によっては、特定の問題を解決するために配列に割り当てられたメモリのサイズを調整することが必要になりますが、そのサイズは事前に分からず、固定することもできません。 可変サイズの配列 (動的配列) の形成は、ポインターとツールを使用して編成できます。 動的メモリ割り当て.

動的配列は、サイズが事前に固定されておらず、プログラムの実行中に変更される可能性がある配列です。 サイズを変更するには 動的配列 プログラミング言語このような配列をサポートする C++ は、特別な機能を提供します。 組み込み関数または操作。 動的配列保存されているデータの量を予測するのではなく、実際に必要な量に応じて配列のサイズを調整できるため、より柔軟にデータを操作できる機会が得られます。

1次元の動的配列の宣言

一次元宣言のもとで 動的配列指定された型の変数へのポインタの宣言を理解して、この変数を次のように使用できるようにします。 動的配列.

構文:

「*配列名」と入力します。

Type – 宣言される要素のタイプ 動的配列。 要素 動的配列関数と要素の型を void にすることはできません。

例えば:

int *a; ダブル *d;

これらの例では、a と d は、割り当てられたメモリ位置の先頭へのポインタです。 ポインタは、int 型と double 型の値に対して、それぞれ割り当てられたメモリ領域のアドレスの値を受け取ります。

したがって、動的配列に対して動的にメモリを割り当てる場合は、割り当てられたメモリ領域の先頭アドレスの値が代入される対応するポインタを記述する必要があります。

1次元動的配列へのメモリの割り当て

1次元用にメモリを割り当てるには 動的配列 C++ には 2 つの方法があります。

1) 操作による new 。配列を格納するために適切なサイズの動的メモリのセクションを割り当てますが、配列要素の初期化は許可されません。

構文:

ArrayName = 新しいタイプ [ConstantTypeExpression];

ArrayName – 配列識別子、つまり、割り当てられたメモリ ブロックのポインターの名前。

型式定数– 要素の数を設定します ( 配列の次元)。 定数型の式はコンパイル時に評価されます。

例えば:

int *mas; マス = 新しい int ; /*100*sizeof(int) バイトの動的メモリを割り当てます*/ double *m = new double [n]; /*n*sizeof(double) バイトの動的メモリを割り当てます*/ long (*lm); lm = 新しいロング; /*2*4*sizeof(long) バイトの動的メモリを割り当てます*/

動的メモリを割り当てるときは、配列の次元を完全に指定する必要があります。

2) ライブラリ関数を使用する malloc (calloc) : 動的メモリを割り当てるために使用されます。

構文:

ArrayName = (Type *) malloc(N*sizeof(Type));

ArrayName = (Type *) calloc(N, sizeof(Type));

ArrayName – 配列識別子、つまり、割り当てられたメモリ ブロックのポインターの名前。

Type – 配列へのポインタのタイプ。

N – 配列要素の数。

例えば:

float *a; a=(float *)malloc(10*sizeof(float)); // または a=(float *)calloc(10,sizeof(float)); /*10*sizeof(float) バイトの動的メモリを割り当てます*/

malloc(calloc)関数が返すので 型なしポインタ void * の場合、結果を変換する必要があります。

この記事を書くために情報を収集しているときに、ポインターについて初めて知ったときのことを思い出しました。とても悲しかったです... したがって、C++ でのプログラミングに関するさまざまな本からこのトピックに関するいくつかのセクションを読んだ後、別のルートをたどってこの記事を発表することにしました。 C++ ポインターのトピックを必要と思われる順序で説明します。 すぐに簡単な定義を説明し、例を使用して実際の動作の指針を見ていきます。 次の記事 () では、ニュアンス、C スタイルの文字列 (文字配列) でのポインターの使用、および覚えておくべき主な事柄について概説します。

C++ のポインタは、データそのものではなく、メモリ内のデータ (値) のアドレスを格納する変数です。

次の例を見れば、プログラミングにポインタが必要な理由、ポインタを宣言して使用する方法という重要な点が理解できるでしょう。

プログラムで整数配列を作成する必要があるとします。その正確なサイズはプログラムの開始前にはわかりません。 つまり、ユーザーがこの配列にいくつの数値を入力する必要があるかわかりません。 もちろん、安全策を講じて、数千の要素 (たとえば、5,000) の配列を宣言することもできます。 (私たちの主観的な意見では) ユーザーが作業するにはこれで十分です。 はい、確かに、これで十分かもしれません。 ただし、この配列は RAM 内で多くのスペース (5,000 * 4 (int 型) = 20,000 バイト) を占有することを忘れないでください。 私たちは自分自身を保護しており、ユーザーは配列の 10 要素のみを入力します。 実際には 40 バイトが使用されており、19,960 バイトがメモリを無駄にしていることがわかります。

RAMの不当な使用

#含む 名前空間 std を使用します。 int main() ( setlocale(LC_ALL, "rus"); const int SizeOfArray = 5000; int arrWithDigits = (); cout<< "Массив занял в памяти " << sizeof(arrWithDigits) << " байт" << endl; int amount = 0; cout << "Сколько чисел вы введёте в массив? "; cin >>金額; コート<< "Реально необходимо " << amount * sizeof(int) << " байт" << endl; for (int i = 0; i < amount; i++) { cout << i + 1 << "-е число: "; cin >> arrWithDigits[i]; ) コウト<< endl; for (int i = 0; i < amount; i++) { cout << arrWithDigits[i] << " "; } cout << endl; return 0; }

#含む

名前空間 std を使用します。

int main()

const int 配列のサイズ = 5000 ;

int arrWithDigits [配列のサイズ] = () ;

コート<< 「配列がメモリを占有しています」<< sizeof (arrWithDigits ) << " байт" << endl ;

int 量 = 0 ;

コート<< 「配列にはいくつの数値を入力しますか?」;

シン >> 量;

コート<< 「本当に必要なもの」<< amount * sizeof (int ) << " байт" << endl ;

for (int i = 0 ; i< amount ; i ++ )

コート<< i + 1 << "-е число: " ;

cin >> arrWithDigits [i] ;

コート<< endl ;

for (int i = 0 ; i< amount ; i ++ )

コート<< arrWithDigits [ i ] << " " ;

コート<< endl ;

0 を返します。

標準ライブラリ関数へ のサイズ()宣言された配列を渡す arr数字付き 10 行目。この配列がメモリ内で占有しているサイズをバイト単位で呼び出した場所に返します。 「配列にはいくつの数値を入力しますか?」という質問に対して、 答えは 10 です。 15 行目の式は、 量 * sizeof(int)関数は次のとおり、10 * 4 と等価になります。 sizeof(int) 4 (int 型のバイト単位のサイズ) を返します。 次に、キーボードから数値を入力すると、プログラムがそれらを画面に表示します。 残りの 4990 個の要素にはゼロが格納されることがわかります。 したがって、それらを表示する意味はありません。

画面上の主な情報: 配列には 20,000 バイトかかりましたが、実際には 40 バイト必要です。 この状況から抜け出すにはどうすればよいでしょうか? ユーザーがキーボードから配列のサイズを入力し、値を入力した後、必要な要素数を含む配列を宣言するようにプログラムを書き換えたい人もいるかもしれません。 しかし、これはポインタなしでは実行できません。 ご存知のとおり、配列のサイズは定数でなければなりません。 つまり、整数定数は配列を宣言する前に初期化する必要があり、キーボードからの入力を求めることはできません。 実験して確認してください。


ここでオペレーターが赤く点灯します >> 定数値は変更できないためです。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


ここでは、配列のサイズを通常の変数の値にすることはできないと警告されています。 定数値が必要です!

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

次のコードでは、初めて使用するポインターと演算子を使用します。 新しい(メモリを割り当てます) および 消去(メモリを解放します)。

ポインタを使用した RAM のインテリジェントな使用

#含む #含む 名前空間 std を使用します。 int main() ( setlocale(LC_ALL, "rus"); int sizeOfArray = 0; // 配列サイズ (ユーザーが入力) cout<< "Чтобы создать массив чисел, введите его размер: "; cin >> 配列のサイズ; // 注意! int* arrWithDigits - // 割り当てられるメモリ部分へのポインタの宣言 new int* arrWithDigits = new int ; for (int i = 0; i< sizeOfArray; i++) { arrWithDigits[i] = i + 1; cout << arrWithDigits[i] << " "; } cout << endl; delete arrWithDigits; // освобождение памяти return 0; }

#含む

#含む

名前空間 std を使用します。

int main()

setlocale(LC_ALL, "rus");

int 配列のサイズ = 0 ; // 配列サイズ (ユーザーが入力)

コート<< "数値の配列を作成するには、そのサイズを入力します:";

cin >> 配列のサイズ ;

// 注意! int* arrWithDigits - ポインタ宣言

// new によって割り当てられるメモリ部分に

int * arrWithDigits = new int [sizeOfArray] ;

for (int i = 0 ; i< sizeOfArray ; i ++ )

arrWithDigits[i] = i+1;

コート<< arrWithDigits [ i ] << " " ;

コート<< endl ;

arrWithDigits を削除します。 // メモリを解放します

0 を返します。

ユーザーはキーボードから値を入力します (12 行目)。ポインタは以下で定義されています。 int * arrWithDigitsこのエントリが意味するのは、 arr数字付きはポインタです。 これは、整数が配置されるセルのアドレスを格納するために作成されます。 私たちの場合には arr数字付きはインデックス 0 の配列セルを指します。符号は * - 乗算に使用されるものと同じです。 コンテキストに基づいて、コンパイラはこれが乗算ではなくポインタ宣言であることを「理解」します。 次に標識が来ます = そしてオペレーター 新しい、メモリの一部を割り当てます。 メモリは単一の数値ではなく配列に割り当てる必要があることを思い出してください。 記録 新しい int [配列のサイズ]次のように解読できます。 新しい(メモリを割り当てます) 整数(整数の保存用) (量的には 配列のサイズ ).

したがって、16 行目で次のように定義されました。 動的配列。 これは、通常の配列のようにコンパイル中にではなく、プログラムの実行中にそのメモリが割り当てられる (または割り当てられない) ことを意味します。 つまり、メモリの割り当ては、プログラムの開発とその操作中に直接行われる決定に依存します。 私たちの場合、それはユーザーが変数に何を入力したかによって異なります。 配列のサイズ

25 行目では演算子を使用しています 消去。 割り当てられたオペレータを解放します 新しいメモリ。 なぜなら 新しい配列を保存するために割り当てられたメモリを解放するときは、配列のゼロ セルだけでなく、配列のメモリも解放する必要があることをコンパイラに明確にする必要があります。 arr数字付き。 したがって、間の 消去ポインタ名は角括弧内に置かれます arrWithDigits を削除します。メモリが割り当てられるたびに、 新しい、次を使用してこのメ​​モリを解放する必要があります 消去。 もちろん、プログラムが終了すると、プログラムが占有していたメモリは自動的に解放されます。 ただし、演​​算子を使用することを良い習慣にしてください 新しいそして 消去とペアリングしました。 結局のところ、プログラムには、たとえば 5 ~ 6 個の配列を含めることができます。 また、実行中のプログラムで今後メモリが必要なくなるたびにメモリを解放すると、メモリがより賢く使用されるようになります。

プログラムで配列に 10 個の値を入力したとします。 次に、それらの合計を計算し、変数に記録しました。 以上です。この配列はもう使用しません。 プログラムは動作し続け、何らかの目的のためにその中に新しい動的配列が作成されます。 この場合、最初の配列が占有しているメモリを解放することをお勧めします。 その後、他の配列にメモリを割り当てるときに、このメモリをプログラム内で再利用できます。

関数のパラメータとしてポインタを使用することを考えてみましょう。 まず、次のコードを入力してコンパイルします。 その中で、関数は 2 つの変数を受け取り、それらの値を変更することを提案します。

関数に渡された変数を変更しようとしました

#含む #含む 名前空間 std を使用します。 void changeData(int varForCh1, int varForCh2); int main() ( setlocale(LC_ALL, "rus"); int variableForChange_1 = 0; int variableForChange_2 = 0; cout<< "variableForChange_1 = " << variableForChange_1 << endl; cout << "variableForChange_2 = " << variableForChange_2 << endl; cout << endl; changeData(variableForChange_1, variableForChange_2); cout << endl; cout << "variableForChange_1 = " << variableForChange_1 << endl; cout << "variableForChange_2 = " << variableForChange_2 << endl; return 0; } void changeData(int varForCh1, int varForCh2) { cout << "Введите новое значение первой переменной: "; cin >>varForCh1; コート<< "Введите новое значение второй переменной: "; cin >> varForCh2; )

#含む

#含む

名前空間 std を使用します。

void changeData (int varForCh1 , int varForCh2 ) ;

int main()

setlocale(LC_ALL, "rus");

int 変数ForChange_1 = 0 ;

int 変数ForChange_2 = 0 ;

コート<< "variableForChange_1 = " << variableForChange_1 << endl ;

コート<< "variableForChange_2 = " << variableForChange_2 << endl ;

コート<< endl ;

ChangeData(変数ForChange_1、変数ForChange_2);

コート<< endl ;

コート<< "variableForChange_1 = " << variableForChange_1 << endl ;

コート<< "variableForChange_2 = " << variableForChange_2 << endl ;

0 を返します。

void changeData(int varForCh1, int varForCh2)

コート<< "最初の変数に新しい値を入力してください:";

cin >> varForCh1 ;

コート<< "2 番目の変数に新しい値を入力してください:";

cin >> varForCh2 ;

プログラムを実行し、新しい変数値を入力します。 その結果、関数の完了時に変数は変更されておらず、0 に等しいことがわかります。

覚えているとおり、この関数は変数を直接操作するのではなく、変数の正確なコピーを作成します。 これらのコピーは、関数の終了後に破棄されます。 つまり、関数は何らかの変数をパラメータとして受け取り、そのコピーを作成し、それを操作して破棄しました。 変数自体は変更されません。

ポインタを使用すると、変数アドレスを関数に渡すことができます。 これにより、関数はそのアドレスの変数データを直接操作できるようになります。 先ほどのプログラムに変更を加えてみましょう。

ポインタを使用して変数値を変更する

#含む #含む 名前空間 std を使用します。 void changeData(int* varForCh1, int* varForCh2); int main() ( setlocale(LC_ALL, "rus"); int variableForChange_1 = 0; int variableForChange_2 = 0; cout<< "variableForChange_1 = " << variableForChange_1 << endl; cout << "variableForChange_2 = " << variableForChange_2 << endl; cout << endl; changeData(&variableForChange_1, &variableForChange_2); cout << endl; cout << "variableForChange_1 = " << variableForChange_1 << endl; cout << "variableForChange_2 = " << variableForChange_2 << endl; return 0; } void changeData(int* varForCh1, int* varForCh2) { cout << "Введите новое значение первой переменной: "; cin >> *varForCh1; コート<< "Введите новое значение второй переменной: "; cin >> *varForCh2; )

このウェブサイトを初めて利用する方はここまでです。

私は C++ の初心者で、現在『Data Structures using C++ 2nd ed, D.S. Malik』という本に取り組んでいます。

この本の中で、Malik は動的な 2 次元配列を作成する 2 つの方法を紹介しています。 最初のメソッドでは、変数をポインターの配列として宣言します。各ポインターは整数型です。 例えば

Int *ボード;

次に、ポインターの配列を「行」として使用するときに、for ループを使用して「列」を作成します。

2 番目の方法では、ポインターへのポインターを使用します。

Int **ボード; ボード = 新しい int* ;

私の質問は、どの方法が優れているのかということです。 ** 方法の方がイメージしやすいですが、最初の方法もほぼ同じように使用できます。 どちらの方法も、動的な 2 次元配列を作成するために使用できます。

編集:上記のように十分に明確ではありませんでした。 私が試したコードは次のとおりです。

整数行、列; コート<< "Enter row size:"; cin >>行; コート<< "\ncol:"; cin >>列; int *p_board; for (int i=0; i< row; i++) p_board[i] = new int; for (int i=0; i < row; i++) { for (int j=0; j < col; j++) { p_board[i][j] = j; cout << p_board[i][j] << " "; } cout << endl; } cout << endl << endl; int **p_p_board; p_p_board = new int* ; for (int i=0; i < row; i++) p_p_board[i] = new int; for (int i=0; i < row; i++) { for (int j=0; j < col; j++) { p_p_board[i][j] = j; cout << p_p_board[i][j] << " "; } cout << endl; }

4 つの答え

最初の方法を使用して作成することはできません 動的 2D 配列の理由は次のとおりです。

Int *ボード;

基本的に 4 つのポインタの配列を int に割り当てました。 スタックあたり。 したがって、これら 4 つのポインターのそれぞれに動的配列を入力すると、次のようになります。

For (int i = 0; i< 4; ++i) { board[i] = new int; }

最終的に得られるのは 2D 配列です。 静的行数 (この場合は 4) と 動的列の数 (この場合は 10)。 したがって、ダイナミクスはそうではありません 完全に、スタックに配列を割り当てるときに、 しなければならない示す 一定のサイズ、つまり で知られています 時間. 動的配列が呼び出されます 動的、そのサイズは既知である必要がないため、 コンパイル時間、しかしむしろ、ある変数によって決定することができます 実行中.

もう一度実行すると、次のようになります。

Int *ボード;

定数 int x = 4; //<--- `const` qualifier is absolutely needed in this case! int *board[x];

で知られている定数を指定します コンパイル時間(この場合は 4 または x) なので、コンパイラは次のことを行うことができます。 事前選択このメモリは配列用であり、プログラムがメモリにロードされるとき、ボード配列用にこの量のメモリがすでに存在しているため、このメモリが呼び出されます。 静的、つまり サイズが ハードコードされたそして 動的に変更できない(実行時)。

一方、次の場合:

Int **ボード; ボード = 新しい int*;

Int x = 10; //<--- Notice that it does not have to be `const` anymore! int **board; board = new int*[x];

コンパイラはボード配列に必要なメモリ量がわからないため、 事前に割り当てます全て。 しかし、プログラムを実行すると、配列のサイズは変数 x の値 (実行時) によって決定され、ボード配列に対応するスペースがいわゆる - コンピュータ上で実行されているすべてのプログラムが割り当てることができるメモリ領域 事前に不明(コンパイル時に) 個人使用のためにメモリを要約します。

そのため、実際に動的 2D 配列を作成するには、2 番目の方法を使用する必要があります。

Int **ボード; ボード = 新しい int*; // int へのポインタの動的配列 (サイズ 10) for (int i = 0; i< 10; ++i) { board[i] = new int; // each i-th pointer is now pointing to dynamic array (size 10) of actual int values }

10 × 10 の正方形の 2D 配列を作成しました。これを調べて 1 などの実際の値を入力するには、ネストされたループを使用します。

For (int i = 0; i< 10; ++i) { // for each row for (int j = 0; j < 10; ++j) { // for each column board[i][j] = 1; } }

2 番目の方法について説明したものは、1D 配列のみを生成します。

Int *board = 新しい int;

これは単純に 10 個の要素を持つ配列を割り当てます。 おそらく次のような意味で言ったのでしょう。

Int **board = 新しい int*; for (int i = 0; i< 4; i++) { board[i] = new int; }

この場合、4 つの int* を割り当ててから、動的に割り当てられた 10 個の int の配列をそれぞれのポイントに割り当てます。

そこで、これを int* ボードと比較します。 。 主な違いは、このような配列を使用する場合、コンパイル時に「行」の数がわかっていなければならないことです。 これは、配列のコンパイル時のサイズが固定されている必要があるためです。 この配列を int* から返したい場合にも、配列はスコープの終わりで破棄されるため、問題が発生する可能性があります。

行と列の両方を動的に割り当てる方法では、メモリ リークを回避するためにより複雑な対策が必要になります。 次のようにメモリを解放する必要があります。

For (int i = 0; i< 4; i++) { delete board[i]; } delete board;

代わりに標準コンテナを使用することをお勧めします。 std::array を使用できます 4> またはおそらく std::vector > 適切なサイズで初期化します。

どちらの場合も、内部ディメンションは動的に設定できます (変数から取得するなど) が、違いは外部ディメンションにあります。

この質問は基本的に次の質問と同じです。

int* x = 新しい int です。 int x よりも「優れている」?

答えは、「配列サイズを動的に選択する必要がない限り、いいえ」です。

このコードは、外部ライブラリ要件がほとんどない場合でもうまく機能し、 int **array の基本的な使用法を示しています。

この回答は、配列が それぞれには、動的サイズと、動的サイズの線形配列を動的サイズのブラン​​チ配列に割り当てる方法が含まれます。

このプログラムは、STDIN から次の形式で引数を受け取ります。

2 2 3 1 5 4 5 1 2 8 9 3 0 1 1 3

プログラムのコードは以下の通りです...

#含む int main() ( int **array_of_arrays; int num_arrays, num_queries; num_arrays = num_queries = 0; std::cin >> num_arrays >> num_queries; //std::cout<< num_arrays << " " << num_queries; //Process the Arrays array_of_arrays = new int*; int size_current_array = 0; for (int i = 0; i < num_arrays; i++) { std::cin >> 現在の配列のサイズ; int *tmp_array = 新しい int; for (int j = 0; j< size_current_array; j++) { int tmp = 0; std::cin >> 一時; tmp_array[j] = tmp; ) array_of_arrays[i] = tmp_array; ) //クエリを処理します int x, y; x = y = 0; for (int q = 0; q< num_queries; q++) { std::cin >> x >> y; //std::cout<< "Current x & y: " << x << ", " << y << "\n"; std::cout << array_of_arrays[x][y] << "\n"; } return 0; }

これは int main の非常に単純な実装であり、 std::cin と std::cout にのみ依存します。 最低限の内容ですが、単純な多次元配列の操作方法を示すには十分です。