数组指针和指针数组,函数指针和指针函数,常量指针和指针常量,常量引用

一、数组指针和指针数组

1.数组指针(行指针)

首先要知道数组指针是指向数组的指针。所以数组指针本质是个指针,只不过指向一个数组而已。格式为:T (*ptr)[]。
注意:"[]" 优先级高于 " * " ,本质得是指针,所以" * "得优先与ptr在一起,所以必须用()。
在继续讲述数组指针知识之前先来讲讲普通一维数组和普通指针。
(1)一维数组和普通指针

int main()
{
     
	int* p;
	int arr[5] = {
      1,2,3,4,5 };
	p = arr;
	printf("%d	%d	%d\n", p[0], (int)p, (int)&arr[0]);
	
	printf("%d	%d	%d\n", p[1], (int)p, (int)&arr[1]);

	return 0;
}

数组指针和指针数组,函数指针和指针函数,常量指针和指针常量,常量引用_第1张图片
arr两个元素之间的地址相差为4个字节(因为元素是int类型,一个int占4个字节)。
对于p[1]就相当于 * (p+1)
对于arr[1]就相当于 *(arr+1)
即将指针向后偏移一个元素单元(在这里即是4个字节)。然后对指针向后偏移过后的地址解引用。注意:这里指针偏移,指针没有改变指向,而是用表达式p+1代表偏移后的地址。
类似,如果我们想要得到arr[2],arr[3]的值的话,可以用一下语句:

arr[2];
arr[3];
p[2];
p[3];
*(arr+2);//arr[2]的本质
*(arr+3);//arr[3]的本质
*(p+2);//p[2]的本质
*(p+3);//p[3]的本质

上面语句都能正常使用。

再来说说p+1偏移多少字节量的问题。
p+1偏移多少字节量和指针p类型有关,如果p是int*类型,那么p+1就向代表后偏移4个字节量的位置,如果p是char类型,那么p+1就代表向后偏移1个字节量的位置。如下代码:

int main()
{
     
	char* p;
	int arr[5] = {
      1,2,3,4,5 };
	p = (char*)arr;
	printf("%d	%d\n", p[0], (int)(p + 0));
	printf("%d	%d\n", p[1], (int)(p + 1));
	printf("%d	%d\n", p[2], (int)(p + 2));
	printf("%d	%d\n", p[3], (int)(p + 3));
	printf("%d	%d\n", p[4], (int)(p + 4));

	return 0;
}

数组指针和指针数组,函数指针和指针函数,常量指针和指针常量,常量引用_第2张图片
发现这个时候p+n代表的是p向后偏移n*sizeof(char)的字节量。arr[0]地址是20183592,arr[1]地址是20183596。arr[0]这个元素占有四个字节大小的空间,只不过数据放在了第一个字节。(这个跟大小端也有点关系,小端是低地址存放低字节,大端是高地址存放低字节,平时我们用的电脑操作系统这些都是小端,网络字节序列是大端,换句话说如果是大端的话,那么1存放在(p+3)里面,也就是存放在四个字节的后一个字节空间)。
普通数组和指针的关系说了,那么再回来说说数组指针。

(2)数组指针和二维数组
下面讲述数组指针将拿int (*ptr)[]举例。
int(*ptr)[5];
这个语句是定义一个指针,这个指针指向一个int类型的整形数组,注意是指向整个int类型的整形数组,换句话说p+1的时候,p要跨过5个整形数据的长度。

int main()
{
     
	int(*ptr)[5];
	int arr[4][5] = {
      1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 };
	ptr = arr;
	printf("%d\n", (*ptr)[0]);
	printf("%d\n", (*ptr)[1]);
	printf("%d\n", (*ptr)[2]);
	return 0;
}

运行结果:
在这里插入图片描述

ptr在代码中的使用也发现了,其实ptr就是一个二级指针,只不过int(* ptr)[5]表明了这个二级指针第一次解引用后的一级指针代表多少空间。也就是 *ptr代表的是int[5]整个空间,即 *ptr是一个指向整个五个int类型空间的指针。相当于 int *p;int arr[5];p = arr; (这里的等价于 *ptr),所以 *ptr可以像普通一维数组和一级指针那样访问数组。*ptr就是普通的一级指针,可以和(1)中的那样访问数组元素。比如(1)中的p[2]和 * ( p+2)同样的效果,所以( * ptr)[2]和 * ( *ptr+2)是一样的效果,即用 *ptr代替本就是一级指针的p而已。
如下代码及运行结果:

int main()
{
     
	int(*ptr)[5];
	int arr[4][5] = {
      1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 };
	ptr = arr;
	printf("%d\n", *(*ptr + 0));//等价于(*ptr)[0]
	printf("%d\n", *(*ptr + 1));//等价于(*ptr)[1]
	printf("%d\n", *(*ptr + 2));//等价于(*ptr)[2]

	printf("%d\n", *(*ptr + 8));//等价于(*ptr)[8]

	return 0;
}

数组指针和指针数组,函数指针和指针函数,常量指针和指针常量,常量引用_第3张图片
注意*ptr和普通一级指针操一样,没什么区别。int(ptr)[5]中的[5]对ptr解依次引用后的一级指针ptr没有限制。[5]主要是对二级指针ptr起作用的。
上面都是对ptr解一次引用后的操作,其实解一次引用后,和普通一级指针操作一样。下面看二级指针ptr的操作。
代码:

int main()
{
     
	int(*ptr)[5];
	int arr[4][5] = {
      1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 };
	ptr = arr;
	printf("%d	%d\n", ptr[0][0],arr[0][0]);
	printf("%d	%d\n", ptr[0][1],arr[0][1]);
	printf("%d	%d\n", ptr[1][0],arr[1][0]);
	printf("%d	%d\n", ptr[1][1],arr[1][1]);

	return 0;
}

数组指针和指针数组,函数指针和指针函数,常量指针和指针常量,常量引用_第4张图片
二级指针如果我们用[][]形式访问元素,那么和二位数组是一样。而且arr+1我们知道是跳到下一行数组。那么ptr+1也是跳到下一行数组,ptr和arr区别就是,ptr没有指定有多少行,指定了一行有几个即[5]指定了一行有五个,其实[5]的作用就在ptr这里,ptr+n就是向后偏移了n * sizeof(int*5)个字节。

int main()
{
     
	int(*ptr)[5];
	int arr[4][5] = {
      1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 };
	ptr = arr;

	printf("%d	%d\n", *(ptr + 1)[0], *(arr + 1)[0]);//arr[1][0]
	printf("%d	%d\n", *(ptr + 1)[1], *(arr + 1)[1]);//arr[2][0]

	printf("%d	%d\n", (*(ptr + 1))[0], (*(arr + 1))[0]);//arr[1][0]
	printf("%d	%d\n", (*(ptr + 1))[1], (*(arr + 1))[1]);//arr[1][1]

	printf("%d	%d\n", **(ptr + 1), **(arr + 1));//arr[1][0]
	printf("%d	%d\n", *(*(ptr + 1) + 1), *(*(arr + 1) + 1));//arr[1][1]

	return 0;
}

运行结果:
数组指针和指针数组,函数指针和指针函数,常量指针和指针常量,常量引用_第5张图片
数组指针和指针数组,函数指针和指针函数,常量指针和指针常量,常量引用_第6张图片
(注意:实际上二维数组的的空间是一直连续的,这个图只是为了突出二维数组的形态)。

再来看下面的访问方式:

int main()
{
     
	int(*ptr)[5];
	int a[2][5] = {
      1,2,3,4,5,6,7,8,9,10 };

	ptr = a;
	printf("%d\n", *(ptr[0]+1));//==*(*(ptr+0)+1)==ptr[0][1]
	printf("%d\n", *(ptr[1] + 1));//==*(*(ptr+1)+1)==ptr[1][1]

	printf("%d\n", *(ptr + 1)[2]);//**(ptr+1+2)==*ptr[3]超出范围
	printf("%d\n", (*(ptr + 1))[2]);//ptr[1][2]
	return 0;
}

运行结果:
数组指针和指针数组,函数指针和指针函数,常量指针和指针常量,常量引用_第7张图片

上面都可以看作是对二级指针的操作。ptr和二级指针的区别就在于[5]上面的5。所以遇到数组指针把数组指针就看成多了个[n]信息的二级指针就好了。
T *ptr[n]意思是ptr+1偏移n个sizeof(T)字节。就比二级指针多了这一个信息。而ptr只能用 int ( * )[n]类型赋值。
比如
int(*ptr) [5]
int arr[4][4];
ptr = arr;//error

int(*ptr) [5]
int arr[4][5];
ptr = arr;//OK

int(*ptr) [5]
int arr[5];
ptr = &arr;//OK
也就是说用不论是让ptr指向一维数组(ptr是二级指针,所以一维数组首地址得&取地址)还是二维数组,必须满足一行元素是5个,即列数和数组指针的[n]中的n值相对应。

2.指针数组

首先要知道指针数组是存放指针的数组。所以指针数组本质是个数组,只不过存放的是指针而已。格式为:T * arr[]。
注意:" [] " 优先级高于 " * "所以不需要(),当然如果T* (arr[])这样写更清晰,只是没必要。
下面讲述指针数组将拿int* arr[]举例。
指针数组比数组指针理解起来简单多了。因为本质就是一个数组,里面存的是指针变量。

int main()
{
     
	cout << "***************************************" << endl;
	int* p = new int(10);
	int* a[3] = {
      p,p,p};
	cout << (int)p << endl;
	cout << (int)a[0] <<"   " << (int)a[1] <<"   " << (int)a[2] << endl;
	cout << *a[0] << endl;
	cout << *a[1] << endl;
	cout << *a[2] << endl;
	 
	cout << "***************************************" << endl;
	*p = 20;
	cout << *a[0] << endl;
	cout << *a[1] << endl;
	cout << *a[2] << endl;

	cout << "***************************************" << endl;

	*a[0] = 50;
	cout << *a[0] << endl;
	cout << *a[1] << endl;
	cout << *a[2] << endl;
	cout << "***************************************" << endl;

	cout << (int)p << endl;
	cout << (int)a[0] <<"   " << (int)a[1] <<"   " << (int)a[2] << endl;
	return 0;
}

(注意:本代码没有考虑内存释放问题)
运行结果:
数组指针和指针数组,函数指针和指针函数,常量指针和指针常量,常量引用_第8张图片
发现a元素存的地址值和p存的地址值一样。说明我们用p指针初始化指针数组a的时候,是字节拷贝的方式(可以看成是类和对象问题里的浅拷贝),把p里面存放的地址值一个一个字节的拷贝到指针数组的元素。

int main()
{
     
	int* arr[3];
	cout << arr << endl;
	cout << arr[0] << "   " << arr[1] << "   " << arr[2] << endl;

	//arr[0] = (int*)10;//error
	//*arr[0] = 10;//error
	arr[0] = new int(10);
	arr[1] = new int(20);
	int* p = new int(30);
	arr[2] = p;
	cout << *arr[0] << "   " << *arr[1] << "   " << *arr[2] << endl;
	cout << arr[0] << "   " << arr[1] << "   " << arr[2] << endl;
	return 0;
}

数组指针和指针数组,函数指针和指针函数,常量指针和指针常量,常量引用_第9张图片
(1)注意arr[0]、arr[1]、arr[2]最开始保存的内存地址。
打印出来发现是CCCCCCC,搜了一下,说VS编译器(微软的做法)开辟栈帧时,会使用CCCCCCC填充栈帧空间。也就是arr[0]、arr[1]、arr[2]都是在栈帧上的,换句话说,指针数组存放在栈帧上,编译器给指针数组开辟栈帧了,本来这些空间都会存放一个堆地址,堆空间里放的时是int类型的数据(仅对我们的代码而言),但是由于一开始我们没有初始化指针数组,所以里面元素值都是CCCCCCC。
(2)注意以下写法
arr[0] = (int*)40;
*arr[0] = 40;
这两个写法是不可取的。
arr[0] = (int *)40相当于把40直接强转为int *类型,40是右值。不可能随便给个值40,然后把40强转为地址值40,那这还乱了套了,我们只能把已有的变量的地址值强转为不同类型。
一开始arr[0]存放的地址值是CCCCC,这根本不是地址值,是开辟栈帧时自动填充的值,*arr[0] = 40相当于把这个栈空间里的值CCCCCCC当成地址值,解引用,然后把值放进去,这样肯定不行。

二、函数指针和指针函数

1.函数指针

首先要知道函数指针是指向函数的指针。所以函数指针本质是一个指针。要求*肯定要与函数名相结合,可以和数组指针指针函数对比一下,思路一样的。
先简单熟悉两个函数指针类型

void (*F)(int, int);
//函数指针,函数原型为void fun(int,int)这里函数名随便起的,跟函数名没关系

void* (*F)(int, int);
//函数原型是void* fun(int,int)函数名随便起的,函数指针只认函数返回类型和形参

上面两种函数指针的使用方法和调用方法演示:
代码:

void fun1(int a, int b)
{
     
	cout << "this is void fun1(int,int)" << endl;
	cout << "a = " << a << "   " << "b = " << b << endl;
	cout << "***********************************************" << endl;
}

void* fun2(int a)
{
     
	cout << "this is void* fun2(int)" << endl;
	cout << "a = " << a << endl;
	cout << "***********************************************" << endl;
	return NULL;
}

int main()
{
     
	void (*F1)(int, int);
	void* (*F2)(int);
	F1 = &fun1;
	F2 = fun2;
	//加不加&都行,不加的话,编译器会自动将函数名转化为函数指针,加的话就是我们显示的转化
	F1(0,0); 
	(*F1)(1, 1);
	F2(0);
	(*F2)(1);

	return 0;
}

数组指针和指针数组,函数指针和指针函数,常量指针和指针常量,常量引用_第10张图片
给函数指针赋值的两种方法:
(1)F1 = fun;
(2)F1 = &fun;
注意,只需要用函数名就可以给函数指针赋值,如果是重载函数,那么函数指针知道是哪个函数名转换为函数指针给它赋的值。(因为函数指针会根据定义的时候的形参列表自己选)。
对函数名取不取地址无关紧要,因为不取地址的话,编译器会自动将函数名转换为地址格式赋值给函数指针。加上取地址符就是我们显示转化了。
函数指针调用函数的两种方式(以上面代码中的F1为例):
(1)F1(“实参”,“实参”);
(2)(*F1)(“实参”,“实参”);
F1里面放的是函数地址,第一种方式,编译器会自己访问函数指针空间的函数地址值找到函数,对其调用。
第二种方式就是我们直接对函数指针解引用,直接调用函数,省去了编译器的隐式转换。

讲完这两种基本的函数指针和上面的指针数组,下面给出一些代码,区分一下数组指针,指针数组,函数指针。

void (*F)(int);
//函数指针,指向的函数原型为void fun(int)

void* (*F)(int,int);
//函数指针,指向的函数原型为void* fun(int,int)

void (*(*F)(int,int))(int);
//函数指针,指向的函数原型是void(*F)(int) fun(int,int)
//即函数参数类型为(int,int)函数返回类型是一个函数指针
//返回类型为void(*F)(int),这是一个指向函数原型为void fun(int)的函数指针

void* (*(*F)(int))(int,int);
//函数指针,指向的函数原型为void* (*F)(int,int) fun(int)
//这个函数的返回类型为void* (*F)(int,int)函数指针
//函数指针void* (*F)(int,int)指向的函数原型为void* fun(int,int)

int* ptr[10];
//指针数组,存放类型为int*类型元素的指针数组

int*(*ptr)[10];
//数组指针,指针指向的数组为指针数组,指针数组列数为10,且数组存放的元素类型int*类型


int* (*F[10])(int);
//函数指针数组,一维数组,可以存放10个元素,存放的元素类型为函数指针,函数指针指向的函数原型为int* fun(int)

(已经有点快要胡言乱语了…)
如果能捋清楚,可以挑战一下自己:写一个函数指针数组,函数指针指向的函数返回类型又是一个函数指针。(形参随意,最后一个函数指针指向的函数返回类型为void*)
可以先写一个普通函数指针:void (*F)(int);
在上面基础上写一个指向的函数的返回类型为函数指针的函数指针:void * ( *(*F)(int))(int,int);
在上面基础上完成数组:
void * ( * (*F[10])(int))(int,int);

2.指针函数

首先要知道指针函数是返回类型为指针的函数。所以指针函数本质是一个函数。
int *fun(int,int);
这是一个指针函数。()优先级高于 *,所以这是一个函数,这个很好认,平常写函数写这么多,返回值类型为指针的函数肯定能看出来。

三、常量指针和指针常量

1.常量指针

首先要知道常量指针是指向常量的指针。所以常量指针本质是一个指针。意思是指针指向的是常量,常量内容不能被修改。所以也就是指针指向的东西的内容不能改变。但是指针的指向允许被修改。
格式为T const * p。

2.指针常量

首先要知道指针常量是指针本身是常量。换句话说,不能修改指针的内容(即指针存储的地址),也就是不能修改指针的指向。但是指针指向的东西的内容允许被修改。
格式为:T * const p或者const T * const p。
总的来说,指针常量就是指针本身是常量,不能修改指针的指向。

四、常量引用

常量引用
常量引用即引用常量的引用,所以不能修改引用的东西的内容。
格式为const T&
此外我们都知道,引用是不能修改的,即一开始是谁的别名就一直是谁的别名,没办法修改。这在引用那一篇博客里谈过。即引用底层用指针实现:
int a = 10;
int& b = a;
即在底层实现为:int * const b = &a;

大总结: 总的来说,数组指针、函数指针、常量指针、常量引用代表的意思分别是:
①指向数组的指针。
②指向函数的指针。
③指向常量的指针。
④引用常量的引用。
指针数组、指针函数、指针常量代表的意思分别是:
①存放指针的数组。
②返回类型为指针的函数。
③指针本身为常量的指针。

你可能感兴趣的:(C++,c语言,c++)