C言語で学ぶ「配列」と「ポインタ」の使い方
1.「配列」とは
「配列」とは、同じデータの型を並べてひとまとめにしたものを言います。プログラムにおける「配列」は、情報を記憶しておくための「箱(変数)」を指し、データの数が多いほど複数の箱が連なります。簡単にいうと、1つの変数の中に複数の値を入れることができ、その複数の器を1つのかたまりとして扱えます。
2.配列の使い方
1.配列の宣言方法
「配列」を宣言するには、配列名の前に「データの型」を記述します。配列名の後には「[ ]」で要素数を囲みます。
以下は構文です。
- 1.データ型名 配列名[要素数];
「配列」宣言
以下例文です。
- 1.int kokan[5];
- 2.char akt[5];
2.初期化方法
「配列」の初期化の方法は以下2パターンがあります。
- ・宣言の際に「{ }」を使用して要素を囲む方法
- ・「for文」を使用して要素を1つずつ代入していく方法
「{ }」内に各要素を記述して初期化する場合、「[ ]」内の要素数の記述省略が可能です。「{ }」を使用して要素を囲む場合、各要素を「,」(カンマ)で区切ります。
「for文」とは、ある回数処理を繰り返し行いたい場合に使用するものです。「配列」と「for文」の組み合わせの利点は、数値を1つずつ自動的にカウントアップ、つまり「繰り返し処理」してくれる点です。
「配列」の初期化の方法は以下は例文になります。
- 1.int kokan[5]={20,30,-20,5,15};
- 2.for(int i=0; i<=4; i=i+4) {
- 3.printf(“%d\n“,kokan[i]);
- 4.}
- 1.20
- 2.30
- 3.-20
- 4.5
- 5.15
実行結果
3.代入の方法
「配列」の要素に値を代入いる方法は、要素の番号を指定して1つずつ代入する方法と「for文」を使用して代入していくの2パターンがあります。
以下は例文です。
- 1.#include <stdio.h>
- 2.
- 3.int main(void) {
- 4.int kokan1[5]={0,1,2,3,4};
- 5.for(int i=0; i<5; i=i++) {
- 6.kokan1[i] = 4 - i; // for文を使って1個の要素ずつ代入
- 7.if(i < 4) {
- 8.printf(“%d“,kokan1[i]);
- 9.} else {
- 10.printf(“%d\n“,kokan1[i]);
- 11.}
- 12.}
- 13.
- 14.int kokan2[5]={};
- 15.int kokan2[2]=2; // 要素に代入
- 16.for(int i=0; i<5; i=i++) {
- 17.if(i < 4) {
- 18.printf(“%d“,kokan2[i]);
- 19.} else {
- 20.printf(“%d\n“,kokan2[i]);
- 21.}
- 22.return 0;
- 23.}
- 1.4,3,2,1,0
- 2.0,0,2,0,0
実行結果
4.配列で文字を扱う
「配列」で文字を扱う場合、データ型は「char型」を使用します。この型を使用することで「char型の配列」は文字列となります。文字(一文字)は「’ ’(シングルクォーテーション)」で文字を囲んで格納します。
文字列を初期化する場合は「” “(ダブルクォーテーション)」で文字列を囲む方法があります。
以下は例文です。
- 1.#include <stdio.h>
- 2.int main(void) {
- 3.char akt1[6] =”abcde” ;
- 4.printf("%s\n", akt1);
- 5.}
- 6.return 0;
- 7.}
- 1.abcde
実行結果
5.配列のコピー
以下は、とある配列「akt1」が持つ要素を別の配列「akt2」「akt3」にコピーを行う方法になります。配列のコピーを行う方法は2つあります。
1つが「for文を使って1つずつ要素を代入していく方法」です。
そして2つ目が「memcpy関数を使用した方法」です。
「memcpy関数」とは、あるメモリアドレスを起点として、一定の文字列を別のメモリアドレスへコピーする際に使用する関数になります。
「memcpy関数」を使用する場合は、必ずヘッダーファイル「string.h」をインクルードしておくことです。
また、コピーには「浅いコピー」と「深いコピー」の2種類があります。違いは参照先が「同じであるか」と「異なるか」のいずれかであることです。「浅いコピー」は「参照先が同じ」であり、「深いコピー」は「参照先が異なる」となります。注意点としては、「浅いコピー」の場合、参照先が同じなので元の値が変わってくるとコピー先もまた変わってくるということです。一方で「深いコピー」の場合は、参照先が異なるためコピー元の値が変化しようとコピー先では変化しないという点です。
以下は例文です。
- 1.#include <stdio.h>
- 2.#include <string.h>
- 3.
- 4.int main(void) {
- 5.int akt1[5] = {0,1,2,3,4};
- 6.int akt2[5];
- 7.int akt3[5];
- 8.
- 9.//要素を1個ずつ代入コピー(深いコピー)
- 10.for(int i = 0; i < 5; i++) {
- 11.akt2[i] = akt1[i];
- 12.}
- 13.
- 14. // memcpy関数を使用(深いコピー)
- 15.memcpy(akt3, akt1, sizeof(int) * 5);
- 16.for(int i=0; i < 5; i=i++) {
- 17.if(i < 4) {
- 18.printf(“%d“, akt3[i]);
- 19.} else {
- 20.printf(“%d\n“,akt3[i]);
- 21.}
- 22.}
- 23.
- 24.//浅いコピー
- 25.int *int_akt;
- 26.int_akt = akt1;
- 27.akt1[0] = 10;
- 28.printf(“%d\n“, int_akt[0]);
- 29.
- 30.return 0;
- 31.}
- 1. 0,1,2,3,4
- 2. 10
実行結果
上段の例文の10行目にて、for文を使用して要素を1つずつ代入していきます。これにより配列「akt1」を配列「akt2」に深いコピーを実行することができます。
次に15行目から「memcpy関数」を使用して、配列「akt1」を配列「akt3」に深いコピーをしていきます。
さらに25行目からはポインタ「int_akt」に配列「akt1」を代入してここで浅いコピーを実行していきます。この場合、コピー元である「akt1」の0番目の要素を「10」に変えることでポインタ「int_akt」の0番目も「10」に変化していることが実行結果より伺えます。
3.ポインタでアドレスアクセス
1.アドレス内の値を取得する
「C言語」では「ポインタ」に「*(アスタリスク)」を付けることで、アドレス内の値を取得することができます。
手順は、配列と同じ型のポインタを宣言します。宣言したポインタに配列の「0番目」のアドレスを指定します。すると、宣言したポインタに配列を代入することで、「0番目」のアドレスを指定したことになります。
アドレスを1つずつずらしながらアドレス内の値を取得することで、要素の値を順を追って取得することができます。
以下は例文です。
- 1.#include <stdio.h>
- 2.
- 3.int main(void) {
- 4.int banchi1[5]={0,1,2,3,4};
- 5.int *p1;
- 6.p1=int banchi1[
- 7.for(int i=0; i<5; i=i++) {
- 8.if(i < 4) {
- 9.printf(“%d“,*(p1+i));
- 10.} else {
- 11.printf(“%d\n“,*(p1+i));
- 12.}
- 13.}
- 14.
- 15.char akt2[16] = "Hello";
- 16.char *p2;
- 17.p2 = akt2; // p2 = akt2[0];
- 18. int num = sizeof(akt2) / sizeof(akt2[0]);
- 19.for(int i = 0; i < num; i++) {
- 20.if(i < num - 1) {
- 21.printf("%c,", *(p2+i));
- 22.} else {
- 23.printf("%c\n", *(p2+i));
- 24.}
- 25.}
- 26.
- 27.return 0;
- 28.}
上段の例文ではint型のポインタを「p1」、char型のポインタを「p2」として宣言します。「p1」ポインタは「banchi1」配列のアドレスを指定し、「p2」ポインタは「akt2」配列のアドレスを指定するように記述していきます。ポインタ「p1」「p2」それぞれに「*(アスタリスク)」を付けているので、アドレス内の値を取得することができます。
また、「char配列」からポインタを使用して値を取得する際、変数や型のメモリサイズを調べるために「sizeof演算子」という演算子を使用します。これにより、for文の繰り返し回数が算出され、文字で初期化していない要素の値取得ができます。
よって実行結果は以下のようになります。
- 1.0,1,2,3,4
- 2.H,e,l,l,o,,,,,,,,,,,
実行結果
4.多次元配列
配列には「多次元配列」というものがあります。これまで扱ってきた配列のイメージは箱が一列に並んでいる感じです。これから扱う「多次元配列」は、この列が拡張されたものになります。
多次元配列(2次元配列)の使い方は、アドレス帳や成績表などを作成するときに使用します。例えば「3×3」のマップあるとします。そこに「1~4」の数字を入れていくプログラムを構築していくと以下のようになります。
- 1.#include <stdio.h>
- 2.
- 3.int main(void) {
- 4.int a[3] [3]; //多次元配列(2次元配列)aを宣言
- 5.a[0] [0]=4;
- 6.a[0] [1]=2;
- 7.a[0] [2]=1;
- 8.a[1] [0]=3;
- 9.a[1] [1]=4;
- 10.a[1] [2]=2;
- 11.a[2] [0]=1;
- 12.a[2] [1]=3;
- 13.a[2] [2]=4;
- 14.
- 15.for(int i=0; i<=2; i=i+1) {
- 16.for(int j=0; j<=2; j=j+1) {
- 17.printf(“%d“,a[i] [j];
- 18.}
- 19.printf(“\n”);
- 20.}
- 21.return 0;
- 22.}
- 1. 421
- 2. 342
- 3. 134
実行結果
上段の例文の実行結果を表で表すと、以下のような形となり、それぞれのマス目に数字が入っていきます。
a[0] [0]=4 | a[0] [1]=2 | a[0] [2]=1 |
a[1] [0]=3 | a[1] [1]=4 | a[1] [2]=2 |
a[2] [0]=1 | a[2] [1]=3 | a[2] [2]=4 |
5.まとめ
「配列」を使用すれば、1つの変数で複数の値を扱えるようになり、長く記述も大変だったコードも、短く綺麗にまとめることができます。「配列」を扱っていく上で、配列の要素数を必要とすることが多々あります。例えば「for文」の繰り返し回数を決める際に、配列の要素数を使用するなどです。「構造体」とは違って「同じ型のデータ」しか扱えはしませんが、使いこなせるようになれば、多言語でも通用します。