对指针的进阶认识

本文详细探讨了C++中的指针,从字符指针、指针数组、数组指针到数组参数和指针参数的使用,深入讲解了数组指针的定义、数组名与地址的关系以及二维数组的传参方式。此外,文章还介绍了函数指针、函数指针数组以及指向函数指针数组的指针,阐述了回调函数的概念及其应用。通过对各种指针类型的分析,揭示了C++中指针的强大功能和灵活运用。
摘要由CSDN通过智能技术生成

目录

1. 字符指针

2. 指针数组

3. 数组指针

3.1 数组指针的定义

3.2 &数组名VS数组名

3.3 数组指针的使用

4. 数组参数、指针参数

4.1 一维数组传参

4.2 二维数组传参

4.3 一级指针传参

4.4 二级指针传参

5. 函数指针

6. 函数指针数组

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

8. 回调函数



前言回顾:

对指针的初阶认识:对指针的初级认识_Bug梨哥的博客-CSDN博客

总结对指针的初阶认识:

内存会划分为字节为单位的空间每个字节都有一个编号(地址/指针);指针/地址要被存储起来,需要一个空间,这个空间就是指针变量。

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

打印在屏幕上的地址是虚拟地址,不是真实的物理地址,虚拟地址去内存真正的物理地址访问的时候,会把虚拟地址再转换成物理地址,这个物理地址才会在内存中找相应的数据。
虚拟地址是由CPU产生的,如果CPU支持的是32位虚拟地址空间就会产生32bit位的地址,是一个虚拟地址(就是打印在屏幕上的一串数字,如:0x0012ff48);如果CPU支持的是64位虚拟地址空间就会产生64bit位的地址,是虚拟地址。
虚拟地址是通过地址线(物理电线)传递,即地址线上产生电信号也是虚拟地址,再通过中间的硬件和软件的转换就会生成物理地址,物理地址才会去内存中拿数据。
去内存访问中访问数据需要的是物理地址,物理地址是由虚拟地址产生的,虚拟地址是由CPU产生的,CPU如果支持的是32或64虚拟地址空间,则产生的就是32bit位或64bit位地址,其大小是4字节或8字节,所以存储地址的指针变量大小也是4个字节或8个字节。

1. 字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char* ;
一般使用:(放字符的变量,放字符串首字符的地址)

#include <stdio.h>
int main()
{
	char ch = 'w';
	char* p = &ch;

	char* p = "abcdef";
	return 0;
}

 

#include <stdio.h>
int main()
{
	char* p = "abcdef";
	*p = 'w';
	return 0;
}

此时代码会发生崩溃。

p里存的是a的地址,指向a。若把a的空间内容改成'w',是不可以该的。

因为abcdef是字符串常量,是存储在内存中的只读数据区,不能写,不能被修改。

(常量字符串本来就不能该)

更改为更严谨的代码:

const char* p = "abcdef";
*p = 'w';

语法限制。(想改就该不了)

不加const修饰限制编译器会报警告。

#include <stdio.h>
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcdef";

	/*const char* str1[] = "abcdef";
	const char* str2[] = "abcdef";*///错误

	const char* str1 = "abcdef";
	const char* str2 = "abcdef";

	if (arr1 == arr2)
	{
		printf("arr1 = arr2\n");
	}
	else
	{
		printf("arr1 != arr2\n");
	}

	if (str1 == str2)
	{
		printf("str1 =str2\n");
	}
	else
	{
		printf("str1 != str2\n");
	}
	return 0;
}
//arr1 != arr2
//str1 = str2

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

注意:如果指针指向的是常量字符串的时候需要用const修饰。

连续定义多个指针时:

int * pa, * pb, * pc;

若是:

int* pa, pb;

错误的——pa的类型是int*;pb的类型是int

若是:

#include <stdio.h>
typedef int* pint;
int main()
{
	pint pa, pb;
	return 0;
}

   正确的——pa的类型是int*,pb的类型是int*,都是指针变量。

若是:

#include <stdio.h>
#define PINT int*
int main()
{
	PINT pa, pb;
	return 0;
}

错误的,此时PINT是完全替换的,编译器会把PINT pa, pb——=——int* pa, pb,所以

这里:pa的类型是int*,pb的类型是int。

typedef是重新产生一个新的类型,是独立的,和int等一样,而#define是替换,这两者不一样。

2. 指针数组

指针数组是一个存放指针的数组。

int* arr1[10]; //整形指针的数组,int*是一种指针类型
char* arr2[4]; //一级字符指针的数组,char*是一种指针类型
char** arr3[5];//二级字符指针的数组,每个元素的类型是char**,数组中存放的是二级指针

#include <stdio.h>
int main()
{
	char* arr[] = { "abcdef","quer","zhangsan" };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s\n", arr[i]);
	}
	return 0;
}
//abcdef
//quer
//zhangsan

#include <stdio.h>
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };

	int* arr[] = { arr1,arr2,arr3 };
	//访问arr中的元素
	int i = 0;
	for (i = 0; i < 3; i++)//arr中有3个元素
	{
		//arr[i];//这里i=1时是1的地址,i=2时是2的地址,i=3时是3的地址——每个数组的首元素地址
		//若要访问所有元素:
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);//访问的是arr[i]数组下标为j的元素		}
		printf("\n");
	}
	return 0;
}
//1 2 3 4 5
//2 3 4 5 6
//3 4 5 6 7
//这里模拟实现了一个二维数组

二级指针的初始化:

int** arr2[4] = {0};

NULL就是0,在C语言中是:#define NULL ((void *)0),强制转换类型为void *,是一种指针类型,本质上是0。在C++中就直接是0。

指针数组的使用:

可以把相同类型的元素的数据的地址放到数组中,通过数组的元素存的地址可以找到它指向的空间。

3. 数组指针

3.1 数组指针的定义

数组指针是指针。
因为:

字符指针:char * pc; pc是指向字符的指针(变量);
整形指针: int * pint; pint是指向整形的指针(变量);
浮点型指针: float * pf; pf是指向浮点型的指针(变量);
那数组指针应该是:指向数组的指针。
下面代码哪个是数组指针?

int* p1[10];
int(*p2)[10];

p1——p1和[ ]先结合,说明p1是一个数组,有10个元素,每个元素的类型是int*,说明p1是存放指针的数组,所以是指针数组。

p2——p2先和*结合,*说明p2是一个指针,指向一个方括号[ ],说明指向对象是一个数组,该数组有10个元素,每个元素的类型是int。所以p2就是数组指针。

解释:

int (*p)[10];
p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
注意:[ ]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

3.2 &数组名VS数组名

对于数组:

int arr[10];

arr 和 &arr 分别是?
因为arr是数组名,数组名表示数组首元素的地址。
&arr数组名是?

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

	printf("%p\n", &arr);//0096FE54
	return 0;
}

对比:

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

	printf("%p\n", &(arr[0]));
	printf("%p\n", &(arr[0])+1);

	printf("%p\n", &arr);
	printf("%p\n", &arr+1);
	return 0;
}
//007CFE54
//007CFE58
//007CFE54
//007CFE58
//007CFE54
//007CFE7C

得出:数组名是数组首元素的地址。

007CFE54和007CFE7C相差28(十六进制的28)

28转换为十进制是10,所以&arr与&arr+1之间相差40;

数组的地址存放起来会放在数组指针中。所以定义指针数组:int (*p)[10] = &arr(初始化)
该指针数组p的类型就是:int * [10],指向的数组有10个元素,每个元素内容是int,所以该指针+1就会跳过原来所指向的对象,跳了40字节即一个数组。
综上:
arr是数组名,是数组首元素地址;
&arr是数组的地址。

&arr得到的是数组的地址,既然是数组的地址,就需要放到数组指针变量中。

3.3 数组指针的使用

数组指针指向的是数组,数组指针中存放的应该是数组的地址。

数组的地址不可能用二级指针存放。

#include <stdio.h>
int main()
{
	char arr[5];
	char (*pa)[5] = &arr;
//pa就是一个指向字符数组的指针,星号的圆括号不能省略。

	int* parr[6];
	int* (*pp)[6] = &parr;
	return 0;
}

数组指针有什么用?

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}//1 2 3 4 5 6 7 8 9 10
#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr;
//此时*p就是数组名,即是首元素的地址,所以*p是1的地址
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *((*p) + i));
	}
	return 0;
}//1 2 3 4 5 6 7 8 9 10

 一维数组很少使用数组指针,它一般用在二维数组中。

二维数组传参和接收,若用数组接收:

#include <stdio.h>
void Print(int a[3][5], int r, int c)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < c; j++)
		{
			printf("%d ", a[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	Print(arr, 3, 5);
	return 0;
}
//1 2 3 4 5
//2 3 4 5 6
//3 4 5 6 7

若换一种方式:指针接收

#include <stdio.h>
//int (*p)[5]是数组指针
void Print(int(*p)[5], int r, int c)
//这里p是一个指针,指向第一行,指向5个指针的整形。
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; j++)
		{
			//*(p+i)相当于拿到了二维数组的第i行,也相当于第i行的数组名
			//数组名表示首元素的地址,其实也是第1行第一个元素的地址
			printf("%d ", *(*(p + i) + j));
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	Print(arr, 3, 5);
	return 0;
}
//1 2 3 4 5
//2 3 4 5 6
//3 4 5 6 7

注意:二维数组传参的时候数组名也表示数组首元素的地址,但二维数组的首元素是第一行。所以二维数组的数组名是第一行的地址。
(所以二维数组在传参时相当于是一维数组,每个元素是一行为单位的多个整形)

 arr[i] = *(p+i);
因为int* p = arr
所以p和arr是一回事,所以arr[i] = p[i];   arr[i] = *(p+i) = *(arr+i)
所以:
arr[i] = *(p+i) = *(arr+i) = p[i];
这几个写法是
*(p+i)就相当于p[i]
所以*(*(p+i)+j)是*(p[i]+j),就是p[i][j],还是数组形式访问

#include <stdio.h>
int main()
{
	int arr[10];
	int* p = arr;
	int i = 0;
	arr[i] == p[i] == *(p + i) == *(arr + i);
	*(p + i);
	return 0;
}

所以:

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值