c语言十六:指针进阶篇

指针进阶篇

一 指针和数组元素的关系

1.1 数组元素的指针

  1. 定义一个指针变量保存数组第0个元素的地址(数组首元素地址)
    在这里插入图片描述
  2. 用指针遍历数组
#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));
	}
}
  1. 用指针变量给数组元素赋值
#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 数组的[]和*()的关系

  1. 早期的c语言数组操作没有[]操作,都是用指向数组元素的变量,变量用*号指针解引用来操作数组元素
  2. 之后为了方便定义了[]符号,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. 指向同一数组的两个元素的指针变量相减,返回的是相差元素的个数
  2. 指向同一数组的两个元素的指针变量可以比较大小
  3. 指向同一数组的两个元素的指针变量可以相互赋值
  4. 指向同一数组的两个元素的指针变量尽量不要相加
  5. []里面在不越界的前提下可以为负值
    在这里插入图片描述

1.5 *p++ 和(*P)++和 * (p++)

在这里插入图片描述

在这里插入图片描述

1.6 总结

  1. []是*()的缩写
  2. 数组名arr代表数组首元素的地址,加1跳过一个元素
  3. &arr代表数组的地址,加1跳过整个数组
    在这里插入图片描述
  4. 数组名arr是一个符号常量,不能被赋值

二 指针数组

  1. 指针数组的本质是数组,只是数组的每个元素是指针
  2. 指针数组的定义
    定义一个指针数组,数组中每个元素是int型指针变量
    a. 先写个有三个元素的数组 arr[3]
    b. 再写个指针*p
    c. 由于是int型的指针,所以写个 int a
    d. 从上往下依次替换得到 int *arr[3]
    在这里插入图片描述
  3. 定义并初始化一个字符指针数组
char *arr[3]={"hehehe", "hahaha", "heiheihei"};

在这里插入图片描述
打印第一行字符串hahaha

printf("%s\n",arr[1]);

打印第一行字符串的第2个字符

printf("%c\n",*(arr[1]+2));

三 数组指针

在这里插入图片描述

  1. 数组指针的本质是一个指针变量,只是该变量保存的是数组的首地址

  2. 数组指针的定义
    a. 先写一个指针 *p
    b. 指针指向的是一个数组,所以再写一个数组 int arr[3]
    c. 从上往下替换: int * p[3]
    d. 由于[]的优先级大于 * ,所以加括号: int (*p)[3]

  3. 数组变量arr代表数组首元素的地址,他的宽度是一个元素; &数组变量$arr代表数组的首地址,他的宽度是整个数组

  4. 如何用数组指针来操作数组中的某个元素

int arr[5]={10,20,30,40,50};
int (*p)[5];
p=&arr;
//上面等式两个同时加*得到*p=*&arr
//上面等式后侧将*&抵消得到*p=arr
  1. 总结
    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=&num;
	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. 指针作为函数参数的目的就是要实现在函数内部修改外部变量的值;
  2. 要在函数内部修改外部变量的值,需要将外部变量的地址作为实参传递给函数;
  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=&num;
	}
	test(&p);
	printf("%d\n",*p);

}
[root@ansible9 ~]# ./a.out 
100

九 一维数组名作为函数的实参

9.1 总结

  1. 如果函数内部想要操作(读,写)外部数组的元素,需要将外部数组的数组名作为实参传递给函数
  2. 在定义和初始化函数时,将一维数组作为函数的形参时,会被优化成一级指针变量
  3. 在定义和初始化函数时,将一维数组作为函数的形参时,可以写成以下三种形式
    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 总结

  1. 如果函数内部想要操作(读,写)外部数组的元素,需要将外部数组的数组名作为实参传递给函数
  2. 在定义和初始化函数时,将二维数组作为函数的形参时,会被优化成数组指针
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

十一. 多维数组名作为函数的实参

  1. 如果函数内部想要操作(读,写)外部数组的元素,需要将外部数组的数组名作为实参传递给函数
  2. 在定义和初始化函数时,将多维数组作为函数的形参时,会被优化成如下:
一维数组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 &num;
	}
	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 要调用函数直接使用函数名就可以了,没必要再定义函数指针来调用函数了,使用函数指针的真正目的是要封装一个多功能的函数

  1. 用一个总函数封装多个函数功能的前提是,这多个函数的返回值类型要一致,形参数量和类型要一样
    在这里插入图片描述
  2. 定义一个函数实现上述三个函数的功能
[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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值