0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Cの基本13 ~ポインタ~ 備忘録

Last updated at Posted at 2023-03-01

はじめに

私は初心者である。この備忘録を参考にされようとしている他の初心者の方は、ここに書かれていることを鵜呑みになさらぬようお願いしたい。何か間違いを見つけた方はコメント欄でご報告頂きたい。


ポインタ

ポインタ = アドレス + そこにあるデータの型
ポインタとはこのようにあるアドレスを格納する。
ポインタを使うとそれがさす領域にアクセスできる、らしい。

なぜポインタが必要なのか? 領域にアクセスするだけなら普通の変数が使える。ポインタが役立つ場面は以下。

  • 配列を操作する
  • 有効範囲の外から変数にアクセスする
  • 大きいデータをコピーすることなく渡す

使い方を見ていこう。

メモリのアドレスを取得する

ポインタに格納するアドレスを取得するにはアドレス演算子を使う。scanf関数で使用するやつだ。
&変数の形で使う。

#include <stdio.h>

int main(void){
    int x;
    printf("%p\n", &x);
    return 0;    // 出力は0x7ff7b4342348 
}
ポインタの型

C言語では、ポインタは入れる変数も用途(型)をあらかじめ決めといて使うようだ。下記のように宣言する。C言語では1行目のコードの方がメジャーらしいが、c++では下が好まれるようだ。

int *p;    // 型 *ポインタ名;
int* p;    // どちらでも良い

型にアスタリスクをつける方が好きかもしれない。

複数のポインタを宣言する場合はポインタ名ごとに*をつける必要があるから注意しよう。

ここでまた使用例。以下のコードでは同じ値が出力される。

#include <stdio.h>

int main(void){
    int x;
    int* p = &x;
    printf("x : %p\n", &x);
    printf("p : %p\n", p);
    return 0;
}
ポインタが指すメモリを読み書きする

ポインタが示すメモリに参照するには*ポインタのように書く。
*は間接演算子と呼ぶ。(乗算のとは別)

下記は、int型の変数xを10で初期化し、ポインタpをxのアドレスで初期化した上で各々を出力し、さらにポインタでxの値を書き換えた?上で出力したコード

#include <stdio.h>

int main(void){
    int x = 10;
    int* p = &x;
    printf("x : %d\n", x);
    printf("p : %d\n", *p);

    *p += 90;

    printf("x : %d\n", x);
    printf("p : %d\n", *p);
    return 0;
}

出力は以下

x : 10
p : 10
x : 100
p : 100

ヌルポインタ

どちらかといえばナルじゃね!
ヌルポインタを作るにはポインタにNULLまたは0を格納する。
NULLを使うには特定のヘッダファイルをインクルドすることで使える(stdio.hとか)。
NULLや0はあらゆる型のポインタに格納できる。

int型のポインタpをNULLで初期化して出力するコードが以下

#include <stdio.h>

int main(void){
    int* p = NULL;
    printf("%p\n", p);

}

私の環境では0x0とでた。

使い道は今のところわからない
追記 コメント欄で例を頂いている。

(新しいC言語の教科書 P428)


ポインタで配列を操作する

型* ポインタ = 配列名 // 配列の先頭アドレスで初期化している
ポインタ = 配列名 // ポインタに配列の先頭アドレスを代入

下のコードでは出力は同じものが並ぶはずだ。

#include <stdio.h>

int main(void){
    int bottle[] = {300, 500, 700, 1000, 1500, 0};
    int* p = bottle;
    
    printf("%p\n", bottle);
    printf("%p\n", p);
}
配列の要素を読み書きする

ポインタが指すメモリを読み書きするには間接演算子を使うが、配列を指すポインタの場合は次のように書く。
*(ポインタ+添字)
*(ポインタ+添字) = 式

ところでポインタには整数の加減算ができる。ポインタに整数を加算するとポインタに格納されたアドレスには「整数*型のバイト数」が加算される。
この機能を使うことで配列の各要素に読み書きを加えることができるのだろう。

上記のボトルのコードで配列bottleを指すポインタpと間接演算子を使って0以外の全要素を出力したコードがこれだ。

#include <stdio.h>

int main(void){
    int bottle[] = {300, 500, 700, 1000, 1500, 0};
    int* p = bottle;
    
    printf("%p\n", p);
    for (int i=0; *(p+i); i++)
        printf("%p %d\n", p+i, *(p+i));
}

出力がこれ

0x7ff7b2bf4330
0x7ff7b2bf4330 300
0x7ff7b2bf4334 500
0x7ff7b2bf4338 700
0x7ff7b2bf433c 1000
0x7ff7b2bf4340 1500

for文の条件式に条件らしからぬ文があるが、これも立派な条件文らしい。
*(p+i)は添字iの要素が0以外である限り、繰り返すことを表している。
もちろん(*(p+i)!=0)とかでも良い。

ポインタと添字演算子で配列の要素を読み書きする

ポインタ[添字]
ポインタ[添字]=式

配列に対して使う添字は実はポインタに対しても使える。ややこしい!

#include <stdio.h>

int main(void){
    int x = 10;

    int* p = &x;
    printf("x : %d\n", x);
    printf("p : %d\n", p[0]);    // ここ

    *p += 90;

    printf("x : %d\n", x);
    printf("p : %d\n", p[0]);    // ここ
    return 0;
}
/* 出力は
x : 10
p : 10
x : 100
p : 100 
*/

間接演算子と添字は配列名にもポインタにも使えるそうだ。
(C言語完全入門参照 P476)

ポインタを変化させて配列の要素を読み書きする

ポインタは代入演算子・インクリメント演算子等を使ってアドレスを変化させることができる。この性質を使ってポインタだけで配列の要素にアクセスできる。

以下が例

#include <stdio.h>

int main(void){
    int bottle[] = {300, 500, 700, 1000, 1500, 0};

    for (int* p=bottle; *p; p++){
        printf("%d ", *p);  
    }
}
/* 出力が以下
300 500 700 1000 1500 
*/

これらのように配列の要素を読み書きするには間接演算子を使う方法、添字を使う方法、ポインタ自体を操作する方法があった。場合によって使い分けよう。


有効範囲の外から変数にアクセスする

下記のプログラムは変数の値をswapすることを目的としているがうまくいかない。

#include <stdio.h>

void swap(int, int);

void swap(int x, int y){
    int temp;
    temp = x; x = y; y = temp;
    return;
}

int main(void){
    int a = 2, b = 3, c = 4, d = 5;

    swap(a, b);
    swap(c, d);
    printf("a = %d, b = %d, c = %d, d = %d\n", a, b, c, d);
    return 0;
}

出力

a = 2, b = 3, c = 4, d = 5

うまくいかないのは実引数の式の値だけが仮引数に代入されているだけだからだ。
そこでポインタを実引数と渡してみよう。

#include <stdio.h>

void swap(int*, int*);

void swap(int* x, int* y){
    int temp;
    temp = *x; *x = *y; *y = temp;
    return;
}

int main(void){
    int a = 2, b = 3, c = 4, d = 5;

    swap(&a, &b);
    swap(&c, &d);
    printf("a = %d, b = %d, c = %d, d = %d\n", a, b, c, d);
    return 0;
}

関数swapにアドレスを渡し、関数の中でアドレスにあるメモリを読み書きしている。
出力は以下。しっかり動いた。これがポインタの能力ですな。

a = 3, b = 2, c = 5, d = 4

変数へのポインタさえあればどこからでもその変数を使えると。

C言語のAPIにはポインタを引数として渡すものが多いとのこと。
代表例がscanf("%d", &x)だと。確かに

関数でのポインタの使用例をもう一つ残しておく。
int型の配列から指定された数を見つけるプログラム

#include <stdio.h>

int* find(int [], int);

int* find(int a[], int n){
    int i;
    for (i=0; a[i]!=-1; i++){
        if (a[i]==n)
            break;       
    }
    return &a[i];  
}
int main(void){
    int a[] = {2, 5, 9, 3, 4, 5, 1, 6, -1};
    int* p;

    p = find(a, 4);
    
    if (*p == -1)
        printf("Not Found\n");
    else
        printf("Found %d\n", *p);
    return 0;
}

void型へのポインタ

(新しいC言語の教科書 P425)


長くなってしまった。ここらで閉じる。

今回の内容は富永和人氏の新しいC言語の教科書
松浦健一郎・司ゆき氏のC言語完全入門を参考に書き留めてます。

0
0
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?