深度理解指针

发布于:2023-01-04 ⋅ 阅读:(324) ⋅ 点赞:(0)

目录

引言

1. 字符指针

2. 数组指针和指针数组

2.1数指针和指针数组的区别与概念组

2.2 &数组名VS数组名

2.3函数指针的应用

3. 数组传参和指针传参

5. 函数指针

6. 函数指针数组

7. 指向函数指针数组的指针

8. 回调函数


引言

在之前的学习了中我们解到了指针的一些特点,在这篇文章中我们将深度刨析指针的特性

1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的 4/8 个字节( 32 位平台 /64 位平台)。
3. 指针是有类型,指针的类型决定了指针的 +- 整数的步长,指针解引用操作的时候的权限。
4. 指针可以进行运算。

1. 字符指针

在指针的类型中我们知道char*为字符指针,这里我们举个例子说明一下

int main()
{
    char ch = 'w';
    char *pc = &ch;
    *pc = 'x';
    printf("%c",*pc);
    return 0; 
}

字符指针不仅仅可以指向一个指针,它还可以指向一个字符串,本质其实是字符指针指向了字符串的第一个字符的地址。

我们可以写一段代码来观察一下

int main()
{
	const char* pstr = "hello bit.";
	printf("%s\n", pstr);
	return 0;
}

仔细思考一下,这里是把一个字符串放到pstr指针变量里了吗?

不是,上面代码的意思是把一个常量字符串的首字符 h 的地址存放到指针变量 pstr 中,切记字符指针存储的是字符串首字母的地址

为了加深大家的理解我们可以分析一下下面的图片

 

这里 str3 str4 指向的是一个同一个常量字符串。 C/C++ 会把常量字符串存储到单独的一个内存区域,当 几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和 str2 不同, str3 str4 不同。

2. 数组指针和指针数组

2.1数指针和指针数组的区别与概念组

为了更好的让大家区分开这两个概念,我们将会把这两个内容放在一起讲解

首先,理解一下数组指针和指针数组这两个名词:

数组指针:可以理解为数组的指针,其本质是一个指针————指向数组的指针。

指针数组:可以理解为指针的数组,其本质是一个数组————装着指针的数组。

然后我们需要了解一个优先级的顺序:()>[]>*

char * arr [ 4 ]
我们可以知道[]的优先级大于*,则数组名arr先与[]结合,则他的本质就是一个数组,数组中的每一个元素的类型为char*;
char (*arr)[4]
我们可以知道()的优先级大于[],则数组名arr先与()中的*结合,其本质是一个指针,一个指向数组首元素地址的指针;

2.2 &数组名VS数组名

我们知道数组指针指向的是数组首元素的地址,那么是否取地址数组名与数组名所代表的含义相同呢,我们通过以下的代码来进行一个验证。

#include <stdio.h>
int main()
{
     int arr[10] = { 0 };
     printf("arr = %p\n", arr);
     printf("&arr= %p\n", &arr);
     printf("arr+1 = %p\n", arr+1);
     printf("&arr+1= %p\n", &arr+1);
     return 0; 
}

所得结果如下图所示

根据上面的代码我们发现,其实&arr arr,虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。
&arr+1跳过的是整个数组的大小,而arr+1跳过的是一个元素的大小

2.3函数指针的应用

void print_arr1(int arr[3][5], int row, int col) {
	int i = 0; int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

void print_arr2(int(*arr)[5], int row, int col) {
	int i = 0;
	for (i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
	print_arr1(arr, 3, 5);
	printf("-----------------------\n");
	print_arr2(arr, 3, 5);
	return 0;
}

数组名arr,表示首元素的地址,但是二维数组的首元素是二维数组的第一行,所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址,可以数组指针来接收

3. 数组传参和指针传参

3.1一维数组传参

#include <stdio.h>
void test(int arr[])//ok?正确,传过来的是一个一维数组用一个一维数组来接受
{}
void test(int arr[10])//ok?正确,传过来的是一个一维数组用一个一维数组来接受
{}
void test(int *arr)//ok?正确,传过来是一个一维数组,用一个一级指针来接受其首元素地址
{}
void test2(int *arr[20])//ok?正确,传过来是一个指针数组,用一个指针数组来接受,没问题
{}
void test2(int **arr)//ok?正确,传过来是一个指针数组,其首元素是一个一级指针,用一个二级指针来接受一级指针的地址没问题
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}

3.2二维数组传参

void test(int arr[3][5])//ok?正确
{}
void test(int arr[][])//ok?不可以,二维数组传参可以省略第一个[]中的值,但是第二个[]中的值不可省略
{}
void test(int arr[][5])//ok?正确
{}
void test(int *arr)//ok?错误 二维数组数组名是数组第一行的地址,而一个整型指针是存不下一个一位数组地址的
{}
void test(int* arr[5])//ok?类型错误
{}
void test(int (*arr)[5])//ok?正确
{}
void test(int **arr)//ok?错误
{}

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

总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
这样才方便运算。

3.3一级指针传参

#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]);
 //一级指针p,传给函数
 print(p, sz);
 return 0; }

3.4二级指针传参

#include <stdio.h>
void test(int** ptr) {
 printf("num = %d\n", **ptr); 
}
int main()
{
 int n = 10;
 int*p = &n;
 int **pp = &p;
 test(pp);
 test(&p);
 return 0; }

小结:

以上便是深度解析指针的上半篇,指针在我们的编程中是至关重要的知识点,在之后的学习中用的的地方将会越来越多,所以一定要理解透彻

,下篇将持续更新


网站公告

今日签到

点亮在社区的每一天
去签到