1、定义

  • 指针:是一个变量,其值为另一个变量的地址,即,内存位置的直接地址

    1. int a = 10;
    2. printf("%d\n",a);// 10
    3. // 取址运算符&
    4. printf("%p\n",&a);// 0x7ffeefbff528

    int* pa = &a;

    1. printf("%p\n",pa);// 0x7ffeefbff528
    2. // 取值运算符
    3. printf("%d\n",*pa);// 10

    // 结合 自右向左计算 > printf(“%p\n”,&*pa);// 0x7ffeefbff528

    1. printf("%d\n",*&a);// 10
  • 通过指针赋值

    1. int a = 10;
    2. int* pa = &a;
    3. // 直接改指针指向的变量值

    *pa = 20;

    1. printf("%d \n",a);// 20
  • 不要将整数赋值给指针变量,否则会被当成内存地址处理

    int* pb = 10;

  • 以下两种写法都是正确的,建议使用第一种

    1. // 写法1容易有歧义 &a 和 *p1感觉是一样的值,其实不一样
    2. int a = 3000;
    3. int *p1 = &a;
    4. printf("%p\n",&a);// 0x7ffeefbff5b8
    5. printf("%d\n",*p1);// 此时是3000,不是var的内存地址
    6. // 写法2容易理解
    7. int b = 3000;
    8. int* p2 = &b;
    9. printf("%p\n",&b);// 0x7ffeefbff5b8
    10. printf("%d\n",*p2);// 3000

指针变量的长度

  • 不同类型的指针变量所占用的存储单元长度是相同的,类型也是一样的,都是一个代表内存地址的长的十六进制数

    1. int main(void)
    2. {
    3. int a = 10;
    4. int *pa = &a;
    5. printf("%#p\n",pa);// 0X000000000061FE0C
    6. printf("%d\n",sizeof(pa));// 8
    7. double b = 20.0;
    8. int *pb = &b;
    9. printf("%#p\n",pb);// 0X000000000061FE00
    10. printf("%d\n",sizeof(pb));// 8
    11. return 0;
    12. }

    2、空指针

  • 在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯

  • 赋为 NULL 值的指针被称为空指针

    1. int* p = NULL;
    2. printf("%p\n",p);//0x0
    3. if (!p) {
    4. printf("空指针\n");
    5. }
  • 在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西

  • void *

    • void * 类型表示未确定类型的指针。
    • C、C++ 规定 void * 类型可以通过类型转换强制转换为任何其它类型的指针。

      3、数组与指针

  • 数组变量、数组变量地址、数组第一个数据地址都是数组的内存地址

    1. int arr[3] = {4,5,6};
    2. printf("arr: %p\n", arr );// 000000000061FE14
    3. printf("arr: %p\n", &arr );// 000000000061FE14
    4. printf("arr: %d\n", *arr );// 4
  1. printf("arr[0]: %d\n", arr[0] );// 000000000061FE14
  2. printf("arr[0]: %p\n", &arr[0] );// 4
  3. printf("arr[1]: %d\n", arr[1] );// 000000000061FE18
  4. printf("arr[1]: %p\n", &arr[1] );// 5
  5. printf("arr[2]: %d\n", arr[2] );// 000000000061FE1c
  6. printf("arr[2]: %p\n", &arr[2] );// 6
  • 指针的算术运算
    1. int aa[] = {10,20,30};
    2. // 0x7ffeefbff51c:10
    3. // 0x7ffeefbff520:20
  1. // 0x7ffeefbff524:30
  2. for (int i = 0; i<3; i++) {
  3. printf("%p:",aa+i);// 此数组是int,aa+1就是平移4个字节长度,aa+2,平移8个字节长度
  4. printf("%d\n",*(aa+i));
  5. }
  • &arr+1和arr+1的区别
    1. int arr[3] = {3,4,5};
  1. printf("%p\n",arr);// 000000000061FE14
  2. printf("%p\n",&arr);// 000000000061FE14
  3. printf("%p\n",&arr+1); // 000000000061FE20 和(arr+1)不一样,此处+1是添加整个数组长度3
  4. printf("%p\n",(arr+1)); // 000000000061FE18
  5. printf("%p\n",arr+2);// 000000000061FE1C
  • 指针的比较
    1. int aa[] = {10,20,30};
  1. // 0x7ffeefbff51c:10
  2. // 0x7ffeefbff520:20
  3. // 0x7ffeefbff524:30
  4. for (int* p = aa; p < (aa+3); p++) {
  5. printf("%p:",p);
  6. printf("%d\n",*p);
  7. }

多维数组和指针

  • 第i行第j列
    • ((a+i)+j)
    • *(a[i]+j)
  • 注意a[i]和a+i的区别
    1. **int** a[3][2] = {{1,2},{3,4},{5,6}};
  1. // 0x7ffeefbff510 第一行首地址
  2. printf("%p\n",a);
  3. printf("%p\n",&a);
  4. printf("%p\n",&a[0]);
  5. printf("%p\n",&a[0][0]);
  6. // 0x7ffeefbff518 第二行首地址
  7. printf("%p\n",a[1]);
  8. printf("%p\n",a+1);
  9. printf("%p\n",&a[1]);
  10. // 0x7ffeefbff51c 第二行第二列
  11. printf("%p\n",a[1]+1);
  12. // 二维数组的遍历
  13. **for** (**int** i = 0; i < 3; i++) {
  14. **for** (**int** j = 0; j < 2; j++) {
  15. printf("%d\n",*(a[i]+j));
  16. }
  17. }
  • 多维数组类似一个一维数组,每个元素储存再储存一个一维数组 ```c int a[] = {1,2,3}; int b[] = {4,5,6};

    int* p[2] = {&a,&b};

  1. // 0x7ffeefbff51c
  2. printf("%p\n",p[0]);
  3. printf("%p\n",a);
  4. // 0x7ffeefbff520
  5. printf("%p\n",p[0]+1);
  6. printf("%p\n",a+1);
  7. // 0x7ffeefbff524
  8. printf("%p\n",p[0]+2);
  9. printf("%p\n",a+2);
  10. // 0x7ffeefbff510
  11. printf("%p\n",p[1]);
  12. printf("%p\n",b);
  13. // 0x7ffeefbff514
  14. printf("%p\n",p[1]+1);
  15. printf("%p\n",b+1);
  16. // 0x7ffeefbff518
  17. printf("%p\n",p[1]+2);
  18. printf("%p\n",b+2);
  1. - 待研究
  2. - int (*p)[4] int *p[4]区别
  3. ```c
  4. int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
  5. int (*p)[4];
  6. int i,j;
  7. p = a;
  8. for (i = 0; i < 3; i++) {
  9. for (j = 0; j < 4; j++) {
  10. printf("%d ",*(*(p+i)+j));
  11. }
  12. printf("\n");
  13. }

4、字符数组和字符指针

字符数组

  • 在 C 语言中,字符串实际上是使用 null 字符 ‘\0’ 终止的一维字符数组。因此,一个以 null 结尾的字符串,包含了组成字符串的字符
  • 字符数组的大小比符数多一个

    1. int main ()
    2. {
    3. char greet1[6] = {'H','e','l','l','o','\0'};
    4. char greet2[] = "Hello";
    5. printf("%s\n",greet1);
    6. printf("%s\n",greet2);
    7. printf("%d\n",sizeof(greet1)/sizeof(char));//6
    8. printf("%d\n",sizeof(greet2)/sizeof(char));//6
    9. return 0;
    10. }

    字符指针

    1. char *str = "hello world";
    2. printf("%s\n",str);
    3. printf("%d\n",sizeof(str));//固定是一个内存地址的长度,此处8

    5、指针作为函数参数

    值传递和引用传递

    ```c void swap(int a); void change(int arr[]);

int main () { int a = 1; swap(a); printf(“%d\n”,a);//1

int arr[] = {1,2,3};
change(arr);
printf("%d\n",arr[0]);// 20
return 0;

}

void swap(int a){ a = 20; } void change(int arr[]){ arr[0] = 20; }

<a name="ULF8J"></a>
#### 数组作为函数参数

- 数组作为函数参数时,会被编译成指针变量处理
- 数组作为函数参数时,需要传数组名和数组长度。因为传递的仅仅是数组的第一个元素的地址,无法知道长度
```java
void test1(double arr[], int size){
    for(int i = 0; i < size; i++)
    {
            printf("%f\n",arr[i]);
    }

}

void test2(double *arr, int size){
    for(int i = 0; i < size; i++)
    {
            printf("%f\n",arr[i]);
    }
}

// test1 test2方法是等价的

int main(void)
{

    double arr[3] = {1.0,2.0,3.0};
    test1(arr, 3);
    test2(arr, 3);

    return 0;
}

6、指向指针的指针

  • 例子

image.png

int var = 3000;
// 指向变量的指针
int* ptr = &var;
// 指向指针的指针
int** pptr = &ptr;

// 普通变量var 0x7ffeefbff5b8:3000
printf("Value of var = %d\n", var );// 3000
printf("Value of var = %p\n", &var );// 0x7ffeefbff5b8

// 指针变量ptr 0x7ffeefbff5b0:0x7ffeefbff5b8
printf("Value available at *ptr = %p\n", ptr );// 0x7ffeefbff5b8
printf("Value available at *ptr = %d\n", *ptr);//3000
printf("Value available at *ptr = %p\n", &ptr );//0x7ffeefbff5b0

// 储存指针的指针变量 0x7ffeefbff5a8:0x7ffeefbff5b0
printf("Value available at **pptr = %p\n", pptr);//0x7ffeefbff5b0
printf("Value available at **pptr = %p\n", *pptr);//0x7ffeefbff5b8
printf("Value available at **pptr = %p\n", &pptr);//0x7ffeefbff5a8
printf("Value available at **pptr = %d\n", **pptr);//3000

7、函数指针

  • 函数指针是指向函数的指针变量,函数指针可以像一般函数一样,用于调用函数、传递参数。 ```c

    include

int max(int x, int y){ return x > y ? x : y; }

int main(void){

// p 是函数指针
int (*p)(int, int) = & max; // &可以省略

int a = 10;
int b = 20;
// 与直接调用函数等价,d = max(a, b)
int d = p(a, b);

printf("最大的数字是: %d\n", d);

return 0;

}

<a name="3b485447"></a>
#### 回调函数

- 回调函数:函数指针作为某个函数的参数<br />
- 函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。<br />
- 简单讲:回调函数是由别人的函数执行时调用你实现的函数<br />

- 例子:定义了回调函数 getNextRandomValue,它返回一个随机值,它作为一个函数指针传递给 populate_array 函数。<br />
- populate_array 将调用 10 次回调函数,并将回调函数的返回值赋值给数组。<br />
```c
#include <stdlib.h>
#include <stdio.h>

// 回调函数
void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{
    for (size_t i=0; i<arraySize; i++){
        array[i] = getNextValue();
    }
}

// 获取随机值
int getNextRandomValue(void)
{
    return rand();
}

int main(void)
{
    int myarray[10];
    populate_array(myarray, 10, getNextRandomValue);

    for(int i = 0; i < 10; i++) {
        printf("%d\n", myarray[i]);
    }
    return 0;
}