0%

指针详解

文章时效性提示

本文发布于 513 天前,部分信息可能已经改变,请注意甄别。

指针的概念:
指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
指针的大小是固定的4/8字节,取决于32位/64位平台。
指针是类型,指针的类型决定了指针+-整数的步长,指针解引用操作的时候的权限。
指针的运算。

参见:初识指针

字符指针

一般使用:

1
2
3
4
5
6
#include <stdio.h>
int main() {
    char ch = 'w';
    char* pc = &ch;
    return 0;
}
1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main()
{
    char arr[] = "abcdef";
    char* pc = arr;
    printf("%s\n",arr);
    printf("%s\n",pc);
    return 0;
}

打印arr和打印pc的结果是一致的。

1
2
3
4
5
6
7
8
#include <stdio.h>
int main() {
    char* p = "abcdef";//"abcdef"常量字符串,把a的地址赋给了p,即把首地址给了p
    printf("%c\n",*p);//打印的结果是a
    printf("%c\n",*p+2);//打印的结果是c
    printf("%s\n",p);//打印的结果是abcdef,是由于通过a的地址,打印出了这个字符串(%s是打印字符串)
    return 0;
}
1
2
3
4
5
6
7
#include <stdio.h>
int main() {
    char* p = "abcdef";//常量字符串
    *p = 'W';
    printf("%s\n",p);
    return 0;
}

上面的写法是错误的。因为abcdef是常量字符串,不能修改,下面的代码是修正后的。

1
2
3
4
5
6
7
#include <stdio.h>
int main() {
    const char* p = "abcdef";
    //*p = 'W';
    printf("%s\n",p);
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
int main() {
    char arr1[] = "abcdef";
    char arr2[] = "abcdef";
    char* p1 = "abcdef";
    char* p2 = "abcdef";
    if (arr1 == arr2)
    {
        printf("arr1 == arr2");
    }
    else
    {
        printf("arr1 != arr2");
    }
    return 0;
}

打印的结果是arr1 != arr2,因为if (arr1 == arr2)比较的不是数组的内容,而是数组的地址,而两个数组的地址不会相等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
int main() {
    char arr1[] = "abcdef";
    char arr2[] = "abcdef";
    char* p1 = "abcdef";
    char* p2 = "abcdef";
    if (p1 == p2) {
        printf("p1 == p2");
    }
    else
    {
        printf("p1 != p2");
    }
    return 0;
}

打印的结果是p1 == p2,因为指向的都是同一个常量字符串,常量字符串不会被修改,为了节约内存空间,内存中不会存两份,地址是一致的,所以相等。
char* p1const char* p1是一致的。

指针数组

指针数组本质是数组,是用来存放指针的。
int* parr[4]; 整型指针数组,用来存放整型指针的数组。
char* pch[4];字符型指针数组,用来存放字符指针的数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
int main() {
    int a = 10;
    int b = 20;
    int c = 30;
    int d = 40;
    int* arr[4] = {&a,&b,&c,&d};
    int i = 0;
    for (i = 0; i<4; i++) {
        printf("%d ",*(arr[i]));
    }
    return 0;
}

打印的结果是10 20 30 40

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
int main() {
    int arr1[] = {1,2,3,4,5};
    int arr2[] = {2,3,4,5,6};
    int arr3[] = {3,4,5,6,7};
    int* parr[] = {arr1,arr2,arr3};
    int i = 0;
    for (i=0; i<3; i++) {
        int j = 0;
        for (j = 0; j<5; j++) {
            printf("%d ",*(parr[i]+j));
        }
        printf("\n");
    }
    return 0;
}

打印的结果是:
1 2 3 4 5 
2 3 4 5 6 
3 4 5 6 7

数组指针

数组指针本质是指针。
int *p = NULL; p是整型指针 - 指向整型的指针 - 可以存放整型的地址
char * pc = NULL; pc是字符指针 - 指向字符的指针 - 可以存放字符的地址

数组指针 就是指向数组的指针 - 可以存放数组的地址。
arr - 首元素地址
&arr[0] - 首元素地址
&arr - 数组的地址

1
2
3
4
5
6
#include <stdio.h>
int main() {
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int (*p)[10] = &arr;//数组指针 - 数组的地址存起来 - p就是数组指针,首先和*结合
    return 0;
}

上面的p就是数组指针。就是指向数组的指针 - 可以存放数组的地址。

int (*p)[10]p先和星号*结合,在和[]结合。
先结合的就是本质,后结合的就是类型。

数组指针的使用:

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int main() {
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int (*p)[10] = &arr;//数组指针 - 数组的地址存起来 - p就是数组指针,首先和*结合
    int i = 0;
    for (i=0; i<10; i++) {
        printf("%d ",(*p)[i]);
    }
    return 0;
}

一般而言,数组指针最好在二维数组中使用才更方便。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
void print1(int arr[3][5],int x,int y)
{
    int i = 0;
    int j = 0;
    for (i = 0; i<x; i++)
    {
        for (j=0; j<y; j++)
        {
            printf("%d ",arr[i][j]);
        }
        printf("\n");
  }
}

void print2(int (*p)[5],int x,int y)
{
    int i = 0;
    for (i=0; i<x; i++) {
        int j = 0;
        for (j = 0; j < y; j++) {
            printf("%d ",*(*(p+i)+j));//也可以写成(*(p + i))[j]
        }
        printf("\n");
    }
}

int main() {
    int arr[3][5] = { {1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15} };
    print1(arr,3,5);//arr - 数组名 - 首元素地址
    print2(arr,3,5);
    return 0;
}

int arr[5]; arr是一个5个元素的整型数组
int *parr1[10];parr1是一个数组,数组有10个元素,每个元素的类型是int*,parr是指针数组
int (*parr2)[10];parr2是一个指针,该指针指向一个数组,数组有10个元素,每个元素的类型是int,parr2是数组指针
int (*parr3[10])[5];parr3是一个数组,该数组有10个元素,每个元素是一个数组指针,该数组指针指向的数组有5个元素,每个元素是int

判断是指针数组还是数组指针?
——根据[ ]的优先级比*的优先级高的原则判断。

数组参数、指针参数

1
2
3
4
5
6
7
8
#include <stdio.h>
int main()
{
    int arr[10] = {0};
    int* arr2[20] = {0};
    test(arr);
    test2(arr2);
}

void test(int arr[]){...}void test(int arr[10]){...}void test(int *arr){...} 上面的三种传参方法都是可以的。可以写数组名,也可以使用数组的地址。
void test2(int *arr[2])void test2(int **arr)上面的两种写法都可以,可以是一级指针,也可以是二级指针。

二维数组传参

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
void test(int arr[3][5])
{}
void test(int arr[][5])
{}

int main()
{
    int arr[3][5] = {0};
    test(arr);
}

void test(int arr[3][5])void test(int arr[][5])都是可以的,在传参是,二维数组的行可以省略。
但是,void test(int arr[3][])写法是错误的,二维数组的列不能省略。
void test(int arr[][])也是错误的,二维数组不能省略列,也不能都省略。

1
2
3
4
5
6
7
8
9
#include <stdio.h>
void test(int (*arr)[5])
{}

int main()
{
    int arr[3][5] = {0};
    test(arr);
}

总结:二维数组传参,函数形参可以省略第一个[]中的数字。
因为对一个二维数组,可以不知道有多少行,但必须得知道一行有几个元素。

一级指针传参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#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]);
    print(p, sz);
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
void test1(int* p)
{}

int main()
{
int a = 10;
int* p1 = &a;
test1(&a);
test1(p1);
}

如果函数变量是指针形式,比如int *p,可以传递地址,比如&a或者直接传递指针(p1

二级指针传参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
void test(int** ptr)
{
        printf("%d\n",**ptr);
}

int main()
{
    int n = 10;
    int *p = &n;
    int **pp = &p;
    test(pp);
    test(&p);
    return 0;
}

传递的pp和&p都是二级指针,使用二级指针接收(int** ptr

使用int** ptr函数的参数是二级指针可以接收二级指针和一级指针,还可以传指针数组。
指针数组相当于是二级指针

函数指针

函数指针就是指向函数的指针,存放函数地址的第一个指针。
&函数名和函数名都是函数的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
int Add(int x,int y)
{
    int z = 0;
    z = x + y;
    return z;
}

int main()
{
    int a = 20;
    int b = 10;
    printf("%p\n",&Add);
    printf("%p\n",Add);//&函数名和函数名都是函数的地址.
    int (*pa)(int,int) = Add;//函数指针,把函数的地址存到pa里
    printf("%d\n",(*pa)(2,3));//打印的结果是5,证明pa中存的是函数的地址
}
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
void Print(char* str)
{
    printf("%s\n",str);
}

int main()
{
    void (*p)(char*) = Print;
    (*p)("hello");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
int Add(int x,int y)
{
    int z = 0;
    z = x + y;
    return z;
}
int main()
{
    int (*pa)(int,int) = Add;//函数指针,把函数的地址存到pa里
    printf("%d\n",(pa)(2,3));
    printf("%d\n",(*pa)(2,3));//打印的结果是5,证明pa中存的是函数的地址
    printf("%d\n",(**pa)(2,3));
    printf("%d\n",(***pa)(2,3));
}

上面的四种调用方式都是可以的,打印的结果都是5,但不建议使用后面两种。
*pa是对pa的解引用,找到对应的函数。
对于函数,*pa前的*可以省略,对于结果无影响。
后两种虽然结果是正确的,但是这种写法是没有意义的。

函数指针数组

函数指针数组就是把一些函数的地址存在一个数组里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
//先定义加、减、乘、除四个函数
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 (*parr[4])(int,int) = {Add,Sub,Mul,Div};//函数指针数组
    int i = 0;
    for (i = 0; i<4; i++)
    {
        printf("%d\n",parr[i](2,3));//分别进行加、减、乘、除
    }
}

打印的结果是5,-1,6,0

函数指针的用途:转移表(比如计算器)


char* my_strcpy(char* dest,const char* src);
1、写一个函数指针pf,指向my_strcpy。
char* (*pf)(char*,const char*);

2、写一个函数指针数组pfArr,能够存放4个my_strcpy函数的地址。
char* (*pfArr[4])(char*,const char*);


函数指针数组实现计算器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <stdio.h>
void menu()
{
    printf("*************************\n");
    printf("***   1.add   2.sub   ***\n");
    printf("***   3.mul   4.div   ***\n");
    printf("***   5.XOR   0.exit  ***\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 XOR(int x,int y)
{
    return x ^ y;
}

int main()
{
    int x = 0;
    int y = 0;
    int input = 0;
    //pfArr是函数指针数组 - 叫转移表
    int (*pfArr[6])(int,int) = {0,Add,Sub,Mul,Div,XOR};
    do {
        menu();
        printf("请选择:->");
        scanf("%d",&input);
        if (input >= 1 && input <= 5) {
            printf("请输入两个操作数:->");
            scanf("%d%d",&x,&y);
            int ret = pfArr[input](x,y);
            printf("%d\n",ret);
        }
        else if(input == 0){
            printf("退出\n");
        }else
        {
            printf("选择错误\n");
        }
    } while (input);
}

上例中,pfArr是函数指针数组 - 叫转移表


回调函数

回调函数就是一个通过函数指针调用的函数。
如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其他所指向的函数时,我们就说这是回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void print(char *str)
{
    printf("hehe:%s",str);
}

void test(void (*p)(char*))
{
    printf("test\n");
    p("abc");
}

int main()
{
    test(print);
    return 0;
}

使用回调函数实现计算器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <stdio.h>
void menu()
{
    printf("*************************\n");
    printf("***   1.add   2.sub   ***\n");
    printf("***   3.mul   4.div   ***\n");
    printf("***   5.XOR   0.exit  ***\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 XOR(int x,int y)
{
    return x ^ y;
}

void Calc(int (*pf)(int,int))//回调函数
{
    int x = 0;
    int y = 0;
    printf("请输入两个操作数:->");
    scanf("%d%d",&x,&y);
    printf("%d\n",pf(x,y));
}

int main()
{
    int x = 0;
    int y = 0;
    int input = 0;
    do {
        menu();
        printf("请选择:->");
        scanf("%d",&input);
        switch (input) {
            case 1:
                printf("加法:");
                Calc(Add);
                break;
            case 2:
                printf("减法:");
                Calc(Sub);
                break;
            case 3:
                printf("乘法:");
                Calc(Mul);
                break;
            case 4:
                printf("除法:");
                Calc(Div);
                break;
            case 5:
                printf("XOR:");
                Calc(XOR);
                break;
            case 0:
                printf("退出\n");
                break;
            default:
                printf("输入非法\n");
                break;
        }
    } while (input);
}

指向函数指针数组的指针

1
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
    int arr[10] = {0};
    int (*p)[10] = &arr;//取出数组的地址

    int (*pf)(int,int);//函数指针
    int (*pfArr[4])(int,int);//pfArr是一个数组,函数指针的数组
    //pfArr是一个指向[函数指针数组]的指针
    int (*(*PPfArr)[4])(int,int) = &pfArr;
    //ppfArr是一个数组指针,指针指向的数组有4个元素
    //指向数组的每个元素的类型是一个函数指针int(*)(int,int)
    return 0;
}