C言語 - ポインタの詳しい解説(上級編)


はじめに: C 言語の基礎を
学習し た後は、ポインタについての予備的な理解が必要ですが、次の高度なコンテンツを読むことで、C 言語のポインタをより早く習得できるようになります。 ポインタの概念:

  1. ポインタはアドレスを格納するための変数であり、アドレスはメモリ空間に一つずつ対応します。
  2. ポインタのサイズは 4/8 バイトに固定されています (32 ビット プラットフォーム/64 ビット プラットフォーム)
  3. ポインタには型があり、ポインタの型によってポインタ±整数のステップ サイズと、ポインタ逆参照操作の権限が決まります。
  4. ポインタ変数の型は、それが指す変数の型と一致している必要があります。
  5. ポインタ変数はアドレスのみを格納できるため、整数変数と混同することはできません

1. 文字ポインタ

文字ポインタ: char*

const char* p="hello word";上記のコードは、定数文字列の最初の文字h
のアドレスをポインタ変数 p に入れることを意味します。

ここに画像の説明を挿入

2. 配列ポインタ

Integer ポインタ - 整数変数へのポインタ Char
ポインタ - 文字変数へのポインタ 配列への
ポインタ - 配列へのポインタ

2.1 配列ポインタの定義

int (*p) [10];
説明: p は最初に * と結合され、p がポインター変数であり、ポインター変数が 10 個の整数の配列を指すことを示します。したがって、p は配列を指すポインターであり、配列ポインターとも呼ばれます。

2.2 & 配列名 VS 配列名

& 配列名と配列名の違いは何ですか?
arr は配列の名前であり、配列名は配列の最初の要素のアドレスを示します
。arr は配列のアドレスであり、配列の最初の要素のアドレスではありません
。 例 1:

#include <stdio.h>
int main()
{
    
    
    int arr[10] = {
    
     0 };
    printf("%p", arr);
    printf("%p", &arr);
    return 0;
}

ここに画像の説明を挿入
アドレスは同じでも意味が異なります
例2:

#include <stdio.h>
int main()
{
    
    
    int arr[10] = {
    
     0 };
    printf("arr=%p\n", arr);
    printf("&arr=%p\n", &arr);
    printf("arr+1=%p\n", arr+1);
    printf("&arr+1=%p\n", &arr+1);
    return 0;
}

ここに画像の説明を挿入
出力された結果から、&arr は配列のアドレスであり、配列の最初の要素のアドレスではないことがわかります。
配列のアドレス +1 は配列全体のサイズをスキップするため、&arr+1 は &arr に対して 40 バイトをスキップします。
配列の最初の要素のアドレス +1 は 1 要素のサイズをスキップするため、arr+1 は arr の 4 バイトをスキップすることと同じです。

2.3 配列ポインタの使用

配列ポインタは配列を指し、配列のアドレスは配列ポインタに格納されます

#include <stdio.h>
void arr1(int arr[][5], int row, int col)
{
    
    
    int i, j;
    for (i = 0; i < row; i++)
        for (j = 0; j < col; j++)
            printf("%d ", arr[i][j]);
    printf("\n");
}
void arr2(int(*arr)[5], int row, int col)
{
    
    
    int i, j;
    for (i = 0; i < row; i++)
        for (j = 0; j < col; j++)
            printf("%d ", arr[i][j]);
    printf("\n");
}
int main()
{
    
    
    int arr[3][5] = {
    
     1,2,3,4,5,6,7,8,9,10 };
    arr1(arr, 3, 5);
    //数组名arr表示数组首元素地址
    //二维数组的首元素地址是二维数组的第一行
    //这里传递的arr,就是第一行的地址,就是一维数组的地址
    //可以用数组指针来接收
    arr2(arr, 3, 5);
    return 0;
}

印刷結果
ここに画像の説明を挿入

3. ポインタの配列

整数配列 - 整数の配列
文字配列 - 文字データのアドレス
ポインタ配列 - ポインタの配列

int* arr1[10];//整数ポインタの配列
char* arr2[4];//主文字ポインタの配列
char** arr3[5];//副文字ポインタの配列

4. 配列パラメータとポインタパラメータ

機能パラメータの設定

4.1 1次元配列パラメータの受け渡し

#include <stdio.h>
void test(int* p)//对的
{
    
    }
void test(int arr[10])//对的
{
    
    }
void test(int arr[])//对的
 {
    
    }
void test1(int** arr1)//对的
{
    
    }
void test1(int* arr1[20])//对的
{
    
    }
int main()
{
    
    
    int arr[10] = {
    
     0 };
        int *arr1[20] = {
    
     0 };
        test(arr);
        test1(arr1);
    return 0;
}

4.2 2次元配列パラメータの受け渡し

#include <stdio.h>
void test(int arr[][5])//形参部分,行可以省略,但是列不可以省略
{
    
    }//对的
void test(int  arr[3][5])
{
    
    }//对的
void test(int (*arr)[5])//数组指针
{
    
    }//对的
void test(int *arr[5])//存放指针的数组
{
    
    }//错的
void test(int arr[][])
{
    
    }//错的
void test(int **arr)
{
    
    }//错的
void test(int *arr)
{
    
    }//错的
int main()
{
    
    
    int arr[3][5] = {
    
    0};
    test(arr);//首元素的地址,一行的地址
    return 0;
}

概要: 仮パラメータと実パラメータの型は同じである必要があります。2
次元配列でパラメータを渡す場合、関数パラメータの設計では最初の [] 番号のみを省略できます。行が何行あるかを知る必要はありませんが、連続する要素が何個あるかは知っておく必要があります。

4.3 第 1 レベルのポインタパラメータの受け渡し

#include <stdio.h>
void test(int* p, int sz)
{
    
    
    int i = 0;
    for (i = 0; i < sz; i++)
        printf("%d ", *(p + i));
}
int main()
{
    
    
    int arr[10] = {
    
     1,2,3,4,5,6,7,8,9,10 };
    int* p = arr;
    int sz = sizeof(arr) / sizeof(arr[0]);
    test(p, sz);//一级指针p,传给函数
    return 0;
}
void test(char* p)
{
    
    }
char ch=9’;
char* ptr=&ch;
char arr[]="abcdef";
参数为一级指针的时候接收什么参数
test(&ch);
test(ptr);
test(arr);

4.4 第 2 レベルのポインタパラメータの受け渡し

void test(char** p)
{
    
    }

char n=‘p’;
char* p1=&n;
char** p2=&p1;
char* arr[5];
参数为二级指针的时候接收什么参数
test(&p1);
test(p2);
test(arr);

5、関数ポインタ

  • プログラム内で関数が定義されている場合、システムはコンパイル時に関数コード用の記憶領域のセクションを開きます。
  • この記憶空間の先頭アドレスを関数のアドレスと呼び、関数名はこのアドレスを示します
  • アドレスなので、それを格納するポインタ変数を定義することができ、このポインタ変数を関数ポインタ変数、略して関数ポインタと呼びます。

関数ポインタ定義の形式:
データ型指定子 (*関数名) ([仮パラメータ リスト]);
注:仮パラメータ リストは省略できます。

#include <stdio.h>
void test()
{
    
    
    printf("hehe");
}
int main()
{
    
    
    printf("p\n", test);//函数名是函数的首地址
    printf("p\n", &test);//&函数名也是函数的地址
    return 0;
}

int (*pf) (int, int) = &test;
pf は関数ポインタ変数です
2 つの興味深いコードを共有します

コード 1:
(*(char(*)())0)()
0 は関数ポインタ型にキャストされます。
アドレス 0 にあるこの関数は呼ばれます。
コード 2:
void(*sig(int, void(*)(int)) ) (int)
sig は関数宣言です。
sig 関数には 2 つのパラメータがあります。最初のパラメータの型は int で、2 番目のパラメータの型は void (*) (int) 関数ポインタの型です。 関数ポインタが指す関数は int 型です
。 、戻り値の型が void の場合
、コード 2 を簡略化できます:
typedef void ( * pf ) (int); pf sig(int,pf);

6. 関数ポインタの配列

関数のアドレスを配列に格納します。これは関数ポインターの配列と呼ばれます
。形式:

int(*pf[10])();
pf は配列であり、配列の内容は int(*)() 型の関数ポインタです。
関数ポインタの配列の目的:転送テーブル

例:

#include <stdio.h>
void menu()
{
    
    
    printf("*******************\n");
    printf("**1.加法   2.减法**\n");
    printf("**3.乘法   4.除法**\n");
    printf("*******************\n");
}
int add(int x, int y)//加法
{
    
    
    return x + y;
}
int sub(int x, int y)//减法
{
    
    
    return x - y;
}
int mul(int x, int y)//乘法
{
    
    
    return x * y;
}
int div(int x, int y)//除法
{
    
    
    return x / y;
}
int main()
{
    
    
    int input;
    int ret = 0;
    int a,  b;
    int (*py[5])(int a, int b) = {
    
     0,add,sub,mul,div };//定义一个函数指针数组,5个元素,每个元素存放一个函数
    do
    {
    
    
        menu();//菜单
        scanf("%d", &input);
        if (input >= 1 && input <= 4)//根据取值范围,进入函数
        {
    
    
            printf("请输入操作数:\n");
            scanf("%d %d", &a, &b);
            ret = (*py[input])(a, b);//调用函数指针数组
            printf("%d\n", ret);
        }
        else if (input == 0)
            ;
        else
            printf("输出错误,请重新输入:\n");
    } while (input);//非零的值就一直循环
}

7. 関数ポインタの配列へのポインタ

関数ポインタの配列へのポインタはポインタでありポインタは要素が関数ポインタである配列を指します。

int(*pf)(int,int); //関数ポインタ
int(*p[5])(int,int); //関数ポインタ配列
int(*(*pff)[5])(int,int) = &p ; //関数ポインタ配列のアドレス p は
関数ポインタ配列へのポインタです

#include <stdio.h>
void test(const char* str)
{
    
    
    printf("%s\n", str);
}
int main()
{
    
    
//函数指针pf
    void(*pf)(const char*) = test;//pf是函数指针变量
    //函数指针数组pff
    void (*pff[10])(const char* str);//pff是存放函数指针的数组
    pff[0] = test;//函数名是函数的地址,&函数名也是函数的地址
    //指向函数指针数组pff的指针pfff
    void (*(*pfff)[5])(const char*)=&pff;//pfff是指向函数指针数组的指针
    return 0;
}

8. コールバック関数

コールバック関数は、関数ポインターを通じて呼び出される関数です。関数ポインター (アドレス) をパラメーターとして別の関数に渡す場合、このポインターがそれが指す関数を呼び出すために使用されるとき、これをコールバック関数と呼びます。コールバック関数は、関数の実装者によって直接呼び出されるのではなく、特定のイベントまたは条件が発生したときに別の当事者によって呼び出され、イベントまたは条件に応答するために使用されます。
例:
コールバック関数を使用して、qsort (バブリングの方法) の実装をシミュレートします。

#include <stdio.h>
//回调函数
int cmp(const void* p1, const void* p2)
{
    
    
    return *(int*)p1-*(int*)p2;
}
void _swap(void* p1, void* p2,  int size)
{
    
    
    int i = 0;
    for (i = 0; i < size; i++)
    {
    
    
        char tmp = *((char*)p1 + i);
        *((char*)p1+i)= *((char*)p2 + i);
        *((char*)p2 + i) =tmp;
    }
}

void bubble(void* ba,int count,int si,int(*cmp)(void*, void*))//函数指针调用的函数,函数指针作为参数传递给另外的函数
{
    
    
    int i = 0, j = 0;
    for (i = 0; i < count - 1; i++)
    {
    
    
        for (j = 0; j < count - 1; j++)
        {
    
    
            if (cmp((char*)ba + j * si, (char*)ba + (j + 1) * si) > 0)
                _swap((char*)ba + j * si, (char*)ba + (j + 1) * si, si);
        }
    }
}
int main()
{
    
    
    int arr[] = {
    
     11,2,32,4,5,61,7,81,9 };
    int i = 0;
    bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), cmp);
    for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
    {
    
    
        printf("%d ",arr[i]);
    }
    printf("\n");
return 0;
}

おすすめ

転載: blog.csdn.net/plj521/article/details/131748490