1、定义
指针:是一个变量,其值为另一个变量的地址,即,内存位置的直接地址
int a = 10;printf("%d\n",a);// 10// 取址运算符&printf("%p\n",&a);// 0x7ffeefbff528
int* pa = &a;
printf("%p\n",pa);// 0x7ffeefbff528// 取值运算符printf("%d\n",*pa);// 10
// 结合 自右向左计算 > printf(“%p\n”,&*pa);// 0x7ffeefbff528
printf("%d\n",*&a);// 10
通过指针赋值
int a = 10;int* pa = &a;// 直接改指针指向的变量值
*pa = 20;
printf("%d \n",a);// 20
不要将整数赋值给指针变量,否则会被当成内存地址处理
int* pb = 10;
以下两种写法都是正确的,建议使用第一种
// 写法1容易有歧义 &a 和 *p1感觉是一样的值,其实不一样int a = 3000;int *p1 = &a;printf("%p\n",&a);// 0x7ffeefbff5b8printf("%d\n",*p1);// 此时是3000,不是var的内存地址// 写法2容易理解int b = 3000;int* p2 = &b;printf("%p\n",&b);// 0x7ffeefbff5b8printf("%d\n",*p2);// 3000
指针变量的长度
不同类型的指针变量所占用的存储单元长度是相同的,类型也是一样的,都是一个代表内存地址的长的十六进制数
int main(void){int a = 10;int *pa = &a;printf("%#p\n",pa);// 0X000000000061FE0Cprintf("%d\n",sizeof(pa));// 8double b = 20.0;int *pb = &b;printf("%#p\n",pb);// 0X000000000061FE00printf("%d\n",sizeof(pb));// 8return 0;}
2、空指针
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯
赋为 NULL 值的指针被称为空指针
int* p = NULL;printf("%p\n",p);//0x0if (!p) {printf("空指针\n");}
在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。然而,内存地址 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西
void *
数组变量、数组变量地址、数组第一个数据地址都是数组的内存地址
int arr[3] = {4,5,6};printf("arr: %p\n", arr );// 000000000061FE14printf("arr: %p\n", &arr );// 000000000061FE14printf("arr: %d\n", *arr );// 4
printf("arr[0]: %d\n", arr[0] );// 000000000061FE14printf("arr[0]: %p\n", &arr[0] );// 4printf("arr[1]: %d\n", arr[1] );// 000000000061FE18printf("arr[1]: %p\n", &arr[1] );// 5printf("arr[2]: %d\n", arr[2] );// 000000000061FE1cprintf("arr[2]: %p\n", &arr[2] );// 6
- 指针的算术运算
int aa[] = {10,20,30};// 0x7ffeefbff51c:10// 0x7ffeefbff520:20
// 0x7ffeefbff524:30for (int i = 0; i<3; i++) {printf("%p:",aa+i);// 此数组是int,aa+1就是平移4个字节长度,aa+2,平移8个字节长度printf("%d\n",*(aa+i));}
- &arr+1和arr+1的区别
int arr[3] = {3,4,5};
printf("%p\n",arr);// 000000000061FE14printf("%p\n",&arr);// 000000000061FE14printf("%p\n",&arr+1); // 000000000061FE20 和(arr+1)不一样,此处+1是添加整个数组长度3printf("%p\n",(arr+1)); // 000000000061FE18printf("%p\n",arr+2);// 000000000061FE1C
- 指针的比较
int aa[] = {10,20,30};
// 0x7ffeefbff51c:10// 0x7ffeefbff520:20// 0x7ffeefbff524:30for (int* p = aa; p < (aa+3); p++) {printf("%p:",p);printf("%d\n",*p);}
多维数组和指针
- 第i行第j列
- ((a+i)+j)
- *(a[i]+j)
- 注意a[i]和a+i的区别
**int** a[3][2] = {{1,2},{3,4},{5,6}};
// 0x7ffeefbff510 第一行首地址printf("%p\n",a);printf("%p\n",&a);printf("%p\n",&a[0]);printf("%p\n",&a[0][0]);// 0x7ffeefbff518 第二行首地址printf("%p\n",a[1]);printf("%p\n",a+1);printf("%p\n",&a[1]);// 0x7ffeefbff51c 第二行第二列printf("%p\n",a[1]+1);// 二维数组的遍历**for** (**int** i = 0; i < 3; i++) {**for** (**int** j = 0; j < 2; j++) {printf("%d\n",*(a[i]+j));}}
多维数组类似一个一维数组,每个元素储存再储存一个一维数组 ```c int a[] = {1,2,3}; int b[] = {4,5,6};
int* p[2] = {&a,&b};
// 0x7ffeefbff51cprintf("%p\n",p[0]);printf("%p\n",a);// 0x7ffeefbff520printf("%p\n",p[0]+1);printf("%p\n",a+1);// 0x7ffeefbff524printf("%p\n",p[0]+2);printf("%p\n",a+2);// 0x7ffeefbff510printf("%p\n",p[1]);printf("%p\n",b);// 0x7ffeefbff514printf("%p\n",p[1]+1);printf("%p\n",b+1);// 0x7ffeefbff518printf("%p\n",p[1]+2);printf("%p\n",b+2);
- 待研究- int (*p)[4] 和 int *p[4]区别```cint a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};int (*p)[4];int i,j;p = a;for (i = 0; i < 3; i++) {for (j = 0; j < 4; j++) {printf("%d ",*(*(p+i)+j));}printf("\n");}
4、字符数组和字符指针
字符数组
- 在 C 语言中,字符串实际上是使用 null 字符 ‘\0’ 终止的一维字符数组。因此,一个以 null 结尾的字符串,包含了组成字符串的字符
字符数组的大小比符数多一个
int main (){char greet1[6] = {'H','e','l','l','o','\0'};char greet2[] = "Hello";printf("%s\n",greet1);printf("%s\n",greet2);printf("%d\n",sizeof(greet1)/sizeof(char));//6printf("%d\n",sizeof(greet2)/sizeof(char));//6return 0;}
字符指针
char *str = "hello world";printf("%s\n",str);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、指向指针的指针
- 例子

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、函数指针
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;
}
