C言語の高度なポインタ

主要な指針のレビュー

1. ポインタは、メモリ空間を一意に識別するアドレスを格納するために使用される変数です。
2. ポインタのサイズは 4/8 バイト固定です (32 ビット プラットフォーム/64 ビット プラットフォーム)。
3. ポインタには型があります。ポインタの型によって、ポインタの +- 整数ステップ サイズと、ポインタ逆参照操作中の権限が決まります。
4. ポインタ操作。

次に、ブロガーはポインターの高度なトピックを続けます。

1.文字ポインタ

ポインタの種類の中には、文字ポインタchar* ;

int main()
{
    char ch = 'c';
    char *pc = &ch;
    *pc = 'c';
    return 0;
}

別の使用方法は次のとおりです。
 

int main()
{
    const char* pstr = "hello bit.";//这里是把一个字符串放到pstr指针变量里了吗?
    printf("%s\n", pstr);
    return 0;
}

代码 const char* pstr = "こんにちは。";

特に、文字列 hello bit が文字ポインタ pstr に配置されていると考えるのは簡単ですが、本質的には、文字列 hello friends. の最初の文字のアドレスが pstr に配置されるということです。

上記のコードは、定数文字列の最初の文字 h のアドレスをポインタ変数 pstr に格納することを意味します。
面接では次のような質問があります。
 

#include <stdio.h>
int main()
{
    char str1[] = "hello friend.";
    char str2[] = "hello friend.";
    const char *str3 = "hello friend.";
    const char *str4 = "hello friend.";
    if(str1 ==str2)
    printf("str1 and str2 are same\n");
    else
    printf("str1 and str2 are not same\n");
    if(str3 ==str4)
    printf("str3 and str4 are same\n");
    else
    printf("str3 and str4 are not same\n");
    return 0;
}

最終結果は次のとおりです。

ここで、str3 と str4 は同じ定数文字列を指します。 C/C++ は定数文字列を別のメモリ領域に保存します。複数のポインタが同じ文字列を指す場合、実際には同じメモリを指します。ただし、同じ定数文字列を使用して異なる配列を初期化すると、異なるメモリ ブロックが開かれます。したがって、str1 と str2 は異なり、str3 と str4 は異なります。

2. ポインタ配列

次のポインターの配列は何を意味しますか?

int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组

ポインタ配列は配列です
文字配列 - 文字を格納する配列
整数配列 = 整数を格納する配列
ポインター配列 - ポインターを格納する配列。配列に格納される要素はすべてポインター型です。

3. 配列ポインタ

3.1 配列ポインタの定義

配列ポインタはポインタですか?それとも配列でしょうか?
答えは「ポインタ」です。
私たちはすでによく知っています:
整数ポインター: int * pint; 整数データを指すことができるポインター。
浮動小数点ポインタ: float * pf; 浮動小数点データを指すことができるポインタ。
配列ポインタは、配列を指すことができるポインタである必要があります。
次のコードのうち、配列ポインタはどれですか?

int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?

答え:

int (*p)[10];
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个
指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

3.2 &配列名 VS 配列名

次の配列の場合:

int arr[10];

arr &arr とはそれぞれ何ですか?
arr は配列名であり、配列名は配列の最初の要素のアドレスを表すことがわかります。
それでは、&arr 配列の名前は何でしょうか?
コードの一部を見てみましょう:
 

#include <stdio.h>
int main()
{
    int arr[10] = {0};
    printf("%p\n", arr);
    printf("%p\n", &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 と arr の値は同じですが、その意味は異なるはずであることがわかりました。
実際: &arr は配列のアドレスを表します。配列の最初の要素のアドレス。 (よく考えてください)
この例では、 &arr の型は次のとおりです: < a i= 8>int(*)[10] は、配列ポインタ型のアドレスに array + 1 を加えたもので、配列全体のサイズをスキップします。したがって & arr+1 &arr の差は 40 です。

3.3 配列ポインタの使用

配列ポインタを使用するにはどうすればよいですか?
配列ポインタは配列を指すため、配列ポインタには配列のアドレスを格納する必要があります。
コードを見てください:

#include <stdio.h>
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
    //但是我们一般很少这样写代码
    return 0;
}

配列ポインタの使用:
 

#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
    int i = 0;
    for(i=0; i<row; i++)
    {
        for(j=0; j<col; j++)
        {
        printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}
void print_arr2(int (*arr)[5], int row, int col)
{
    int i = 0;
    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};
    print_arr1(arr, 3, 5);
    //数组名arr,表示首元素的地址
    //但是二维数组的首元素是二维数组的第一行
    //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
    //可以数组指针来接收
    print_arr2(arr, 3, 5);
    return 0;
}

ポインター配列と配列ポインターについて学習した後、それらを一緒に確認し、次のコードが何を意味するかを見てみましょう。

int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];

arr が整数配列であることに疑いの余地はありません

裏面を見てみましょう。Parr1 は配列です。配列には 10 個の要素があります。各要素の型は int * です。

* が最初に parr2 と結合されるため、parr2 は配列ポインタです。ポインタは配列を指します。指す配列には 10 個の要素があり、すべて Int 型です。

parr3 は配列ポインタを格納する配列です。格納された配列ポインタが指す配列には 5 つの要素があり、各要素の型は int です。 parr3 は最初に [10] と結合されるため、parr3[10] を取り除き、残りの int(*)[5] がその型になります。

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

 コードを書く際に関数に「配列」や「ポインタ」を渡すことは避けられませんが、関数の引数はどのように設計すればよいのでしょうか?

4.1 1次元配列パラメータの転送

#include <stdio.h>
void test(int arr[])//ok?  数组传参,形参部分写成数组是可以的,这里传入首地址,所以大小可以省略
{}
void test(int arr[10])//ok?  如上,所以是OK的
{}
void test(int *arr)//ok?   数组传参的本质是,传递了数组的首元素地址,所以可以是指针类型 
{}
void test2(int *arr[20])//ok?  test2是个整型指针,所以形参也可用整型指针
{}
void test2(int **arr)//ok?    test2的每个类型都是int *,用int **没毛病,二级指针接收一级指针
{}
int main()
{
    int arr[10] = {0};
    int *arr2[20] = {0};
    test(arr);
    test2(arr2);
}

 4.2 2次元配列パラメータの転送

void test(int arr[3][5])//ok?二维数组用二维数组为形参,没问题
{}
void test(int arr[][])//ok? 二维数组的行可省略,列不能省略
{}
void test(int arr[][5])//ok? 这是个二维数组,行省略,列没省略,所以OK
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//ok?二维数组的数组名实际上传的是第一行的地址,不能用整型指针
{}
void test(int* arr[5])//ok? 如上,不OK
{}
void test(int (*arr)[5])//ok?形参是个数组指针,存放数组,没问题
{}
void test(int **arr)//ok?二级指针接收的是一级指针,不OK
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}

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

#include <stdio.h>
void print(int *p, int sz)
{
    int i = 0;
    for(i=0; i<sz; i++)
    {
    printf("%d\n", *(p+i));
    }
}
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9};
    int *p = arr;
    int sz = sizeof(arr)/sizeof(arr[0]);
    //一级指针p,传给函数
    print(p, sz);
    return 0;
}

考察:
関数のパラメータ部分が第 1 レベルのポインタである場合、関数はどのようなパラメータを受け取ることができるでしょうか?
例:
 

void test1(int *p)
{}
//test1函数能接收什么参数?
void test2(char* p)
{}
//test2函数能接收什么参数?

test1 関数は、次のような整数アドレスを受け取ることができます。

int a = 10;
test(&a);

次のようにすることもできます。

int a = 10;
int * ptr = &a;
test(ptr);

次のようにすることもできます。

int arr[5];
text(arr);

概要: 整数 1 次元配列の配列名、整数ポインター、および整数変数のアドレスを渡すことができます。

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

#include <stdio.h>
void test(int** ptr)
{
    printf("num = %d\n", **ptr);
}
int main()
{
    int n = 10;
    int*p = &n;
    int **pp = &p;
    test(pp);
    test(&p);
    return 0;
}

考察: 関数のパラメータが 2 次ポインタである場合、関数はどのようなパラメータを受け取ることができるでしょうか?

void test(char **p)
{ }
int main()
{
    char c = 'b';
    char*pc = &c;
    char**ppc = &pc;
    char* arr[10];
    test(&pc); pc是一级指针,&pc是二级指针,所以OK
    test(ppc);  二级指针传二级指针,没问题
    test(arr);//Ok? OK,数组名表首元素地址,传递的是指针数组的首元素地址,是一级指针
    return 0;
}

概要: 第 2 レベルのポインターはパラメーターであり、第 1 レベルのポインター、第 2 レベルのポインター、およびポインター配列を受け取ることができます。

5. 関数ポインタ

 まずコードの一部を見てください。

#include <stdio.h>
void test()
{
    printf("hehe\n");
}
int main()
{
    printf("%p\n", test);
    printf("%p\n", &test);
    return 0;
}

は 2 つのアドレスを出力します。これらは test 関数のアドレスです。

では、関数のアドレスを保存したい場合、どうやって保存すればよいでしょうか?

以下のコードを見てみましょう。

void test()
{
    printf("hehe\n");
}
//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();

まず、ストレージ アドレスを指定するには、pfun1 または pfun2 がポインタである必要があります。では、どちらがポインタでしょうか?
答えは次のとおりです。
pfun1 は保存できます。 pfun1 は最初に * と結合されており、pfun1 がポインターであることを示します。ポインターは関数を指します。指す関数にはパラメーターがなく、戻り値の型は void です。

6. 関数ポインタの配列

配列は、同じ型のデータを保存する記憶域です。ポインタ配列についてはすでに学習しました。
例:

int *arr[10];
//数组的每个元素是int*

関数のアドレスは配列に格納する必要があります。この配列は関数ポインタ配列と呼ばれます。関数ポインタの配列を定義するにはどうすればよいですか?
?

int (*parr1[10])();
int *parr2[10]();
int (*)() parr3[10];

答えは次のとおりです: parr1
まず、parr1 が [] と結合されて、parr1 が配列であることが示されます。配列の内容は何ですか?
は、int (*)() 型の関数ポインタです。

関数ポインタ配列の使用: テーブル転送
例: (電卓)
 

#include <stdio.h>
int add(int a, int b)
{
    return a + b;
}
    int sub(int a, int b)
{
    return a - b;
}
    int mul(int a, int b)
{
    return a*b;
}
    int div(int a, int b)
{
    return a / b;
}
int main()
{
    int x, y;
    int input = 1;
    int ret = 0;
    do
    {
        printf( "*************************\n" );
        printf( " 1:add 2:sub \n" );
        printf( " 3:mul 4:div \n" );
        printf( "*************************\n" );
        printf( "请选择:" );
        scanf( "%d", &input);
        switch (input)
        {
        case 1:
            printf( "输入操作数:" );
            scanf( "%d %d", &x, &y);
            ret = add(x, y);
            printf( "ret = %d\n", ret);
            break;
        case 2:
            printf( "输入操作数:" );
            scanf( "%d %d", &x, &y);
            ret = sub(x, y);
            printf( "ret = %d\n", ret);
            break;
        case 3:
            printf( "输入操作数:" );
            scanf( "%d %d", &x, &y);
            ret = mul(x, y);
            printf( "ret = %d\n", ret);
            break;
        case 4:
            printf( "输入操作数:" );
            scanf( "%d %d", &x, &y);
            ret = div(x, y);
            printf( "ret = %d\n", ret);
            break;
        case 0:
            printf("退出程序\n");
            breark;
        default:
            printf( "选择错误\n" );
            break;
        }
    } while (input);
    return 0;
}

関数ポインターの配列を使用した実装:
 

#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a*b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
    int x, y;
    int input = 1;
    int ret = 0;
    int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
    while (input)
    {
        printf( "*************************\n" );
        printf( " 1:add 2:sub \n" );
        printf( " 3:mul 4:div \n" );
        printf( "*************************\n" );
        printf( "请选择:" );
        scanf( "%d", &input);
        if ((input <= 4 && input >= 1))
        {
            printf( "输入操作数:" );
            scanf( "%d %d", &x, &y);
            ret = (*p[input])(x, y);
        }
        else
            printf( "输入有误\n" );
        printf( "ret = %d\n", ret);
    }
    return 0;
}

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

関数ポインタの配列へのポインタはポインタです
ポインタは配列を指し、配列の要素は関数ポインタです。
どうやって定義するの?
 

void test(const char* str)
{
    printf("%s\n", str);
}
int main()
{
    //函数指针pfun
    void (*pfun)(const char*) = test;
    //函数指针的数组pfunArr
    void (*pfunArr[5])(const char* str);
    pfunArr[0] = test;
    //指向函数指针数组pfunArr的指针ppfunArr
    void (*(*ppfunArr)[5])(const char*) = &pfunArr;
    return 0;
}

8. コールバック関数

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

#include <stdio.h>
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void * p1, const void * p2)
{
    return (*( int *)p1 - *(int *) p2);
}
int main()
{
    int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
    int i = 0;
    qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
    for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
    {
        printf( "%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

コールバック関数を使用して qsort の実装をシミュレートします (バブリング メソッドを使用)

注: void* ポインターを使用して void* の役割を説明するのはこれが初めてです。

#include <stdio.h>
int 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 *base, int count , int size, int(*cmp )(void *, void *))
{
    int i = 0;
    int j = 0;
    for (i = 0; i< count - 1; i++)
    {
        for (j = 0; j<count-i-1; j++)
        {
            if (cmp ((char *) base + j*size , (char *)base + (j + 1)*size) > 0)
            {
                _swap(( char *)base + j*size, (char *)base + (j + 1)*size, size);
            }
        }
    }
}
int main()
{
    int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
    //char *arr[] = {"aaaa","dddd","cccc","bbbb"};
    int i = 0;
    bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
    for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
    {
        printf( "%d ", arr[i]);
    }
    printf("\n");
    return 0;
}


 

おすすめ

転載: blog.csdn.net/A1546553960/article/details/133532453