7839

雑草魂エンジニアブログ

【C/C++】C言語文法の復習(2)ポインタ

前回のC言語文法の復習の続き。

ポインタ

ポインタの話をする前に、変数に関する前提知識を確認しておく必要がある。(ここを抑えておくことで、ポインタに対する理解も深まる気がしている。)

変数は、メモリの中に保存されており、以下の2つのパラメータがある。

  1. アドレス
    • メモリのスタートアドレス。
    • 先頭アドレスから、変数の型に従い、変数を格納するのに、必要なメモリ領域を確保する。
    • メモリに保存されているデータ。

そして、ポインタは、ポインタ変数のことであり、「先頭アドレスを格納する変数」である。

変数とポインタ

通常の変数 ポインタ変数
宣言 int param; int * pointer;
param *pointer
アドレス &param pointer
  • 宣言
    • ポインタ変数は、 以下の形で定義する。
      <ポインタが参照する先のデータ型> <変数のデータ型としてポインタを指定 *> <変数名>
      int *foo
    
    • int* pointer; int *pointerどちらも同義である。
    • ポインタ変数は、アドレスを指定することで、間接的にメモリにアクセスし、値を参照する。
    • *は、「間接参照演算子」という。
  • アドレス
    • 通常の変数は、&<変数名>で先頭アドレスを参照する。
    • &は、「参照演算子」という。
    • ポインタ変数は、<変数名>で先頭アドレスを参照する。

Q. なぜポインタにデータ型が必要なのか?

A. ポインタ変数で、間接参照する際に、データ型がわかっていないと、先頭アドレスから、どこまでのメモリにアクセスすべきかわからないので、データ型を指定しておく必要がある。

int param;
int *pointer;

param = 23;
pointer = &param;

*pointer = 39;

上記のプログラム処理を図解すると、以下のような形になる。

f:id:serip39:20211129005418p:plain

ポインタの注意

  • NULLポインタへの間接参照はできないので、注意が必要。
  • アクセスすべきアドレスがわからないので、プログラムが異常終了する。

配列とポインタ

配列は、多数の変数の先頭アドレスを示す固定された変数である。
すなわち、配列の変数名だけの指定の場合、それはポインタ同様の意味を表すことになる。(これは、C言語で配列を実現する手段として、ポインタを利用しているためである。)

通常の配列変数 ポインタ変数
宣言 int arr[3]; int * pointer;
arr[i]
*(arr+i)
*pointer
アドレス arr pointer
    • iは要素番号とする。
    • 配列名またはポインタ変数に[i]をつけた場合、そのアドレスに要素番号の値だけ足し算を行い、要素番号の値を参照する。
  • アドレス
    • 配列名は、配列の先頭要素へのアドレス(ポインタ)である。&arr[0]と同義。
    • 各要素の任意のアドレスは、変数同様に、&arr[i]で参照できる。

ポインタ配列変数をインクリメントすることで、次の配列要素にアクセスすることができる。

double d[3] = {0.2, 0.4, 0.6};
double *p1 = NULL, *p2 = NULL;
int i;
p1 = d;  // p1 = &d[0]; と同義
p2 = d;
for (i = 0; i < 3; i++)
{
  // 4つは全く同じ値を出力する
  printf("%f %f %f %f\n", d[i], *(d + i), p1[i], *p2);  
  p2++; //  p2のアドレスをインクリメント
}

<注意>配列とポインタは全く別物

  • ポインタ変数は、好きな変数のアドレスを代入して、任意のメモリ領域にアクセスすることができる。
  • 配列は、あくまでも多数の変数の先頭アドレスを示す固定された変数である。

動的なメモリの生成

malloc()(マロック)は、メモリを動的に確保する処理を行う関数である。引数に確保したいメモリの大きさをバイト数で指定する。 それに対して、メモリを解放する関数が、free()である。引数に、確保したメモリのアドレスを持つポインタを与える。

  • malloc()関数

    ヒープ領域から、引数で指定したメモリを動的に確保する処理を行う関数

      #include <stdlib.h>
      void *malloc(size_t size);
    
    • 引数:確保したいメモリのバイトサイズ
    • 戻り値:確保したメモリを指すポインタ

    (利用例)int型、要素数10の配列変数としてメモリ確保

      p= (int*)malloc(sizeof(int)*10);
    

    注意として、mallocの先頭に、指定するポインタへのキャストを入れること。

  • free()関数

    malloc関数で、割り当てたメモリブロックを解放する関数

      #include <stdlib.h>
      void free(void *ptr);
    
    • 引数:解放するメモリのポインタ

文字列操作

C言語で文字列を扱う場合、char型の配列変数を使う。そして、文字列を操作する場合は、#include <string.h>することで以下のメソッドを使うことができる。

関数 説明
strcpy(char s1, char s2); s2をs1にコピーする。string copy.
strcat(char s1, char s2); s2をs1に追加・連結する。string concatenate.
strlen(char* s); sの文字列の長さの取得。string length.
strcmp(char s1, char s2); s1とs2を比較する。string compare.
s1=s2の場合、0.
それ以外は、s1-s2.
ASCIIコードの数字で計算される。

文字列から数値への変換

#include <stdlib.h>することで以下のメソッドを使うことができる。

関数 説明
atoi(chae* s); 文字列を整数に変える。ASCII to Integer.
atof(chae* s); 文字列を実数に変える。ASCII to Float.

数値から文字列への変換

#include <stdio.h>
int sprintf(char *buf, const char *format, foo...);

書式formatにしたがって、printf関数と同様の変換を行った出力を、文字列bufに格納する。ただし、文字列の出力先のメモリ領域は十分なサイズを確保した上で呼び出す必要がある。

char str[256];
int a = 100;
sprintf(str, "aの値は%dです。", a);

構造体

複数の変数をひとまとめにするものとして、「構造体」(struct)がある。

typedef struct  {
    // 変数定義の列挙
} hoge; // <-構造体変数名

構造体を定義する場合は、基本的にtypedefを使用する。そうすることで、実際の変数を定義する際に、通常の変数同様に、構造体の変数名のみでデータ型として扱うことができる。

typedef struct  {
    int id; // メンバーID
    char name[256]; // 名前
    int age;  // 年齢
} member_data;

int main() {
  member_data data[] = {
    {1, "田中太郎", 23},
    {2, "鈴木次郎", 39},
  }
  for (i = 0; i < 2; i++)
  {
    printf("ID:%d 名前:%s 年齢:%d\n", data[i].id, data[i].name, data[i].age);
  }
}

構造体変数の成分をメンバという。 メンバへのアクセスは、以下の通り。

(構造体変数名).(メンバ)

ただし、ポインタの構造体の場合は、->(アロー演算子)を使う。

通常の構造体 ポインタの構造体
定義 member_data data member_data *pData
メンバ data.id
data.name
data.age
pData->id
pData->name
pData->age

構造体を関数の引数として渡す場合に、「データ渡し」ではなく、基本的には「ポインタ渡し」を用いる。

データ渡しの問題点は以下の通りである。

  • メモリリソースを無駄にしてしまうため。構造体のデータのサイズは大きくなる傾向があり、引数としてそのままの値を渡すと、スタック領域を圧迫してしまったり、データのコピーという無駄な処理が発生する。
  • 構造体の中で、データを変更しても、呼び出しもとの値に反映されないため。ポインタ渡しであれば、関数の中で値の設定などが可能となる。

列挙型

順序などのなんらかの 秩序をもつデータを定数(列挙子)として定義するときに用いられる。<列挙子>=<値>で定義することで、任意の値を割り当てることができる。通常の整数の定数として扱う事が可能で、switch()文などと組み合わせて使われる。

enum GENDER{ // 性別の定義
  MALE,      // 男性(値は0)
  FEMALE,    // 女性(値は1)
};

enum COLOR{    // 色の定義
  RED=1,       // 赤(値は1)
  BLUE=2,      // 青(値は2)
  GREEN=3,     // 緑(値は3)
};

まとめ

C言語の基礎的な文法の復習はできた気がする。このままC++までやってしまって、最終的にはC++を極めたい。その上で、競技プログラミングを通して、アルゴリズムを一通りやってみたいと思っている。