指针进阶篇
一 指针和数组元素的关系
1.1 数组元素的指针
- 定义一个指针变量保存数组第0个元素的地址(数组首元素地址)
- 用指针遍历数组
#include <stdio.h>
int main(int arg, char *argv[])
{
int arr[5]={10,20,30,40,0};
int *p=&arr[0];
int i;
for(i=0;i<5;i++)
{
printf("%d\n", *(p+i));
}
}
- 用指针变量给数组元素赋值
#include <stdio.h>
int main(int arg, char *argv[])
{
int arr[5]={0};
int *p=&arr[0];
int i;
for(i=0;i<5;i++)
{
scanf("%d", p+i);
}
}
1.2 数组的[]和*()的关系
- 早期的c语言数组操作没有[]操作,都是用指向数组元素的变量,变量用*号指针解引用来操作数组元素
- 之后为了方便定义了[]符号,x[y]会被解析为*(x+y) ,即+号左边的值放在[]的左边;+号右边的值放在[]里面
int arr[5]={10,20,30,40,0};
int *p=&arr[0];
// arr[2]等价于*(arr+2)等价于*(2+arr)等价于2[arr]
// arr[2]等价于*(arr+2)等价于*(p+2)等价于p[2]等价于*(2+p)等价于2[p]
//arr[2] p[2] 2[arr] 2[p] *(arr+2) *(p+2) *(2+arr) *(2+p) 都是等价的
[root@ansible9 ~]# cat test.c
#include <stdio.h>
int main(int arg, char *argv[])
{
int arr[5]={10,20,30,40,0};
int *p=arr;
printf("arr[2]=%d\n",arr[2]);
printf("p[2]=%d\n",p[2]);
printf("2[arr]=%d\n",2[arr]);
printf("2[p]=%d\n",2[p]);
printf("*(arr+2)=%d\n",*(arr+2));
printf("*(p+2)=%d\n",*(p+2));
printf("*(2+arr)=%d\n",*(2+arr));
printf("*(2+p)=%d\n",*(2+p));
}
[root@ansible9 ~]# gcc -Wall test.c
[root@ansible9 ~]# ./a.out
arr[2]=30
p[2]=30
2[arr]=30
2[p]=30
*(arr+2)=30
*(p+2)=30
*(2+arr)=30
*(2+p)=30
1.3 用[]和*()的关系和*&可以抵消这两个理论来证明数组名arr代表的是数组首元素的地址&arr[0]
&arr[0] 转换为*()
&*(arr+0) &和*抵消
arr+0
arr
1.4 指向同一数组的两个元素的指针变量间的关系
- 指向同一数组的两个元素的指针变量相减,返回的是相差元素的个数
- 指向同一数组的两个元素的指针变量可以比较大小
- 指向同一数组的两个元素的指针变量可以相互赋值
- 指向同一数组的两个元素的指针变量尽量不要相加
- []里面在不越界的前提下可以为负值
1.5 *p++ 和(*P)++和 * (p++)
1.6 总结
- []是*()的缩写
- 数组名arr代表数组首元素的地址,加1跳过一个元素
- &arr代表数组的地址,加1跳过整个数组
- 数组名arr是一个符号常量,不能被赋值
二 指针数组
- 指针数组的本质是数组,只是数组的每个元素是指针
- 指针数组的定义
定义一个指针数组,数组中每个元素是int型指针变量
a. 先写个有三个元素的数组 arr[3]
b. 再写个指针*p
c. 由于是int型的指针,所以写个 int a
d. 从上往下依次替换得到 int *arr[3]
- 定义并初始化一个字符指针数组
char *arr[3]={"hehehe", "hahaha", "heiheihei"};
打印第一行字符串hahaha
printf("%s\n",arr[1]);
打印第一行字符串的第2个字符
printf("%c\n",*(arr[1]+2));
三 数组指针
-
数组指针的本质是一个指针变量,只是该变量保存的是数组的首地址
-
数组指针的定义
a. 先写一个指针 *p
b. 指针指向的是一个数组,所以再写一个数组 int arr[3]
c. 从上往下替换: int * p[3]
d. 由于[]的优先级大于 * ,所以加括号: int (*p)[3] -
数组变量arr代表数组首元素的地址,他的宽度是一个元素; &数组变量$arr代表数组的首地址,他的宽度是整个数组
-
如何用数组指针来操作数组中的某个元素
int arr[5]={10,20,30,40,50};
int (*p)[5];
p=&arr;
//上面等式两个同时加*得到*p=*&arr
//上面等式后侧将*&抵消得到*p=arr
- 总结
a. 对数组指针取 * 得到数组首元素的地址;
#include <stdio.h>
int main(int arg, char *argv[])
{
int arr[5]={10,20,30,40,50};
int (*p)[5];
p=&arr;
// 因为上面等式两边同时加个*号后为 *p=*&arr ,右侧的*&抵消后,等式变为*p=arr
printf("%d\n", *(*p+2)); // 打印出来的是30
}
b. 对数组首地址取 * 得到数组首元素的地址;
&arr是数组首地址,前面取 * 后 * &arr, * 和&抵消就是arr,arr是数组首元素地址
四 二维数组分析
int arr[3][5];
1. arr: 二维数组名代表二维数组的首行地址,+1跳过一行;
2. &arr: 二维数组名前加&代表二维数组的首地址,+1跳过整个二维数组;
3. arr[0]: 二维数组名后加[0]代表二维数组首行的首元素地址,+1跳过一个元素; arr[0]等价于*(arr+0), 等价于*arr
4. *arr: 对二维数组的行地址取 *, 变成当前行的第0列的列地址;
#include <stdio.h>
int main(int arg, char *argv[])
{
int arr[3][5]={0};
printf("二维数组名代表首行的地址:%u\n",arr);
printf("二维数组名代表首行的地址,+1跳过一行:%u\n",arr+1);
printf("二维数组名前加&代表二维数组的地址:%u\n",&arr);
printf("二维数组名前加&代表二维数组的地址,+1跳过整个二维数组:%u\n",&arr+1);
printf("二维数组名加[0]代表首行的首元素的地址:%u\n",arr[0]);
printf("二维数组名加[0]代表首行的首元素的地址,+1跳过一个元素:%u\n",arr[0]+1);
printf("对二维数组的行地址取*,将变为当前行的第0列的列地址:%u\n",*arr);
printf("对二维数组的行地址取*,将变为当前行的第0列的列地址, +1跳过一个元素:%u\n",*arr+1);
}
结果:
[root@ansible9 ~]# ./a.out
二维数组名代表首行的地址:182722480
二维数组名代表首行的地址,+1跳过一行:182722500
二维数组名前加&代表二维数组的地址:182722480
二维数组名前加&代表二维数组的地址,+1跳过整个二维数组:182722540
二维数组名加[0]代表首行的首元素的地址:182722480
二维数组名加[0]代表首行的首元素的地址,+1跳过一个元素:182722484
对二维数组的行地址取*,将变为当前行的第0列的列地址:182722480
对二维数组的行地址取*,将变为当前行的第0列的列地址, +1跳过一个元素:182722484
五 数组指针与二维数组的关系
#include <stdio.h>
int main(int arg, char *argv[])
{
int arr[3][5]={{1,2,3},{4,5,6},{7,8,9}};
int (*p)[5];
p=arr; //p和arr都是指向二维数组第0行的指针
printf("*(p+1)指向的是第1行第0列的地址: %p\n",*(p+1)); //*(p+1)指向的是第1行第0列的地址
printf("*(arr+1)指向的是第1行第0列的地址: %p\n",*(arr+1)); //*(arr+1)指向的是第1行第0列的地址
printf("*(p+1)+2指向的是第1行第2列的地址: %p\n",*(p+1)+2); //*(p+1)+2指向的是第1行第2列的地址
printf("*(arr+1)+2指向的是第1行第2列的地址: %p\n",*(arr+1)+2); //*(arr+1)+2指向的是第1行第2列的地址
printf("*(*(p+1)+2)是取第1行第2列的元素的值: %d\n",*(*(p+1)+2)); //*(*(p+1)+2)是取第1行第2列元素的值
printf("*(*(arr+1)+2)是取第1行第2列的元素的值: %d\n",*(*(arr+1)+2)); //*(*(arr+1)+2)是取第1行第2列元素的值
}
六 任何维度的数组在物理存储上都是一维的
所以操作时,可以用一个普通指针来操作任意维的数组
#include <stdio.h>
int main(int arg, char *argv[])
{
int arr[3][5]={{1,2,3},{4,5,6},{7,8,9}};
int i=0;
int *p=&arr[0][0]; // 定义一个指向普通int型数据的指针,指向多维数组的第一个元素,然后通过逐个加1的方式移动指针,去遍历其他元素
int n=0;
n=(int)sizeof(arr) / (int)sizeof(arr[0][0]);
for(i=0;i<n;i++)
{
printf("%d\n",*(p+i));
printf("%d\n",p[i]);
}
}
七 多级指针
[root@ansible9 ~]# cat test.c
#include <stdio.h>
int main(int arg, char *argv[])
{
int num=10;
int *p1=#
int **p2=&p1;
int ***p3=&p2;
printf("普通变量num的地址是:%p\n", &num);
printf("一级指针变量中存放的内容是:%p\n", p1);
printf("一级指针变量的地址是:%p\n", &p1);
printf("二级指针变量中存放的内容是:%p\n", p2);
printf("二级指针变量的地址是:%p\n", &p2);
printf("三级指针变量中存放的内容是:%p\n", p3);
printf("三级指针变量的地址是:%p\n", &p3);
}
[root@ansible9 ~]# ./a.out
普通变量num的地址是:0x7ffdf802a7bc
一级指针变量中存放的内容是:0x7ffdf802a7bc
一级指针变量的地址是:0x7ffdf802a7b0
二级指针变量中存放的内容是:0x7ffdf802a7b0
二级指针变量的地址是:0x7ffdf802a7a8
三级指针变量中存放的内容是:0x7ffdf802a7a8
三级指针变量的地址是:0x7ffdf802a7a0
几级指针变量前加几个 * 号后得到的就是最初的普通变量的值
***p3 是num
**p2 是num
*p1 是num
八 指针作为函数的参数
8.1 如果想在函数内部 修改 外部的变量的值,将外部变量本身传递给函数是不行的
8.2 如果想在函数内部 修改 外部的变量的值,就要将外部变量的地址传递给函数
8.3 总结
- 指针作为函数参数的目的就是要实现在函数内部修改外部变量的值;
- 要在函数内部修改外部变量的值,需要将外部变量的地址作为实参传递给函数;
- 外部变量为普通变量时,函数的形参为1级指针变量;
外部变量为1级指针变量时,函数的形参为2级指针变量;
外部变量为n级指针变量时,函数的形参为n+1级指针变量
实例:在函数内部将外部指针变量的指向更改
[root@ansible9 ~]# cat test.c
#include <stdio.h>
int main(int arg, char *argv[])
{
int *p=NULL;
void test(int **a)
{
static int num = 100;
*a=#
}
test(&p);
printf("%d\n",*p);
}
[root@ansible9 ~]# ./a.out
100
九 一维数组名作为函数的实参
9.1 总结
- 如果函数内部想要操作(读,写)外部数组的元素,需要将外部数组的数组名作为实参传递给函数
- 在定义和初始化函数时,将一维数组作为函数的形参时,会被优化成一级指针变量
- 在定义和初始化函数时,将一维数组作为函数的形参时,可以写成以下三种形式
int arr[5], int arr[], int *arr
void write_arr(int a[5], int n)
{
语句;
}
void write_arr(int a[], int n)
{
语句;
}
void write_arr(int *p, int n)
{
语句;
}
9.2 在定义和初始化函数时,如果形参是一维数组,将被优化成一级指针变量
9.3 如果函数内部想要操作(读,写)外部数组的元素,需要将外部数组的数组名作为实参传递给函数
[root@ansible9 ~]# cat test.c
#include <stdio.h>
int main(int arg, char *argv[])
{
int arr[5]={0};
void write_arr(int a[], int n)
{
int i=0;
printf("请给数组赋%d值:",n);
for(i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
}
void read_arr(int a[2], int n)
{
int i=0;
printf("数组值为:\n");
for(i=0;i<n;i++)
{
printf("%d\n",a[i]);
}
}
write_arr(arr,5);
read_arr(arr,5);
}
[root@ansible9 ~]# ./a.out
请给数组赋5值:10 20 30 40 50 60 70
数组值为:
10
20
30
40
50
十. 二维数组名作为函数的实参
10.1 总结
- 如果函数内部想要操作(读,写)外部数组的元素,需要将外部数组的数组名作为实参传递给函数
- 在定义和初始化函数时,将二维数组作为函数的形参时,会被优化成数组指针
int arr[3][4] 被优化成 int (*arr)[5]
10.2 在定义和初始化函数时,将二维数组作为函数的形参时,会被优化成数组指针
10.3 如果函数内部想要操作(读,写)外部数组的元素,需要将外部数组的数组名作为实参传递给函数
[root@ansible9 ~]# cat test.c
#include <stdio.h>
int main(int arg, char *argv[])
{
void write_arr(int a[3][5], int x, int y)
{
printf("输入%d个数组元素:\n",x*y);
int i=0;
int j=0;
for(i=0;i<x;i++)
{
for(j=0;j<y;j++)
{
scanf("%d",&a[i][j]);
}
}
}
void read_arr(int a[3][5], int x, int y)
{
printf("数组元素:\n");
int i=0;
int j=0;
for(i=0;i<x;i++)
{
for(j=0;j<y;j++)
{
printf("%d\n",a[i][j]);
}
}
}
int arr[3][5]={0};
int row=(int)sizeof(arr) / (int)sizeof(arr[0]);
int col=(int)sizeof(arr[0]) / (int)sizeof(arr[0][0]);
printf("%d %d",row,col);
write_arr(arr,row,col);
read_arr(arr,row,col);
}
[root@ansible9 ~]# ./a.out
3 5输入15个数组元素:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
数组元素:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
十一. 多维数组名作为函数的实参
- 如果函数内部想要操作(读,写)外部数组的元素,需要将外部数组的数组名作为实参传递给函数
- 在定义和初始化函数时,将多维数组作为函数的形参时,会被优化成如下:
一维数组int arr[3] 优化为 一级指针变量 int *p
二维数组int arr[3][4] 优化为 一维数组指针int (*arr)[4]
三维数组int arr[3][4][5] 优化为 二维数组指针int (*arr)[4][5]
n维数组int arr[3][4][5]...[n] 优化为 n-1维数组指针int (*arr)[4][5]...[n]
十二 指针作为函数的返回值
int* test(void); //由于()的优先级比 * 大,所以test是一个函数,他的返回值是int*
12.1 想在函数内部修改外部指针变量的指向,不必指针变量的地址作为实参传给函数,这种方法要使用多级指针麻烦;可以将指针作为函数返回值来赋给外部指针变量
[root@ansible9 ~]# cat test.c
#include <stdio.h>
int main(int arg, char *argv[])
{
int *p=NULL;
int * get_address(void)
{
static int num=100;
return #
}
p=get_address();
printf("%d\n",*p);
}
[root@ansible9 ~]# ./a.out
100
十三 函数名代表的是函数的入口地址
13.1 定义一个指向函数的函数指针变量
1. 先写一个指针变量 *p
2. 想让变量指向那个函数就把这个函数定义的第一行照抄 int my_func(int a,int b)
3. 从上往下整体替换 int *p(int a,int b)
4. 由于()的优先级比 * 高,所以要加一个括号 int (*p)(int a,int b)
5. 可以把函数的变量名去掉 int (*p)(int,int)
13.2 使用实例
[root@ansible9 ~]# cat test.c
#include <stdio.h>
int my_add(int a, int b)
{
return a+b;
}
int main(int arg, char *argv[])
{
printf("函数my_add的地址:%p\n",my_add); //函数名代表函数的地址
int (*p)(int,int); //定义一个指针变量来指向函数my_add
p=my_add; //将函数地址赋给变量p
printf("变量p中存储的地址:%p\n",*p);
//调用函数可以用三种方式
//方式一 用函数名调用
printf("%d\n",my_add(2,3));
//方式二 用执行函数的指针调用
printf("%d\n",p(20,30));
//方法三直接用地址调用
printf("%d\n",((int(*)(int,int))(0x400596))(200,300));
}
[root@ansible9 ~]# ./a.out
函数my_add的地址:0x400596
变量p中存储的地址:0x400596
5
50
500
13.3 要调用函数直接使用函数名就可以了,没必要再定义函数指针来调用函数了,使用函数指针的真正目的是要封装一个多功能的函数
- 用一个总函数封装多个函数功能的前提是,这多个函数的返回值类型要一致,形参数量和类型要一样
- 定义一个函数实现上述三个函数的功能
[root@ansible9 ~]# cat test.c
#include <stdio.h>
int my_add(int a, int b)
{
return a+b;
}
int my_sub(int a, int b)
{
return a-b;
}
int my_mul(int a, int b)
{
return a*b;
}
// 定义一个函数来实现上面三个函数的功能
int my_cale(int a, int b, int (*p)(int, int))
{
return p(a,b);
}
int main(int arg, char *argv[])
{
printf("%d\n", my_cale(1,2,my_add));
printf("%d\n", my_cale(1,2,my_sub));
printf("%d\n", my_cale(1,2,my_mul));
}
[root@ansible9 ~]# ./a.out
3
-1
2