C语言———指针进阶1

目录

前言:

1、字符指针:

2、指针数组:

3、数组指针:

3.1 数组指针的定义

3.2 &数组名VS数组名

         3.3 数组指针的使用


前言:

    关于指针的进阶,本章重点有:字符指针、数组指针、指针数组、 数组传参和指针传参、 函数指针、函数指针数组、指向函数指针数组的指针、回调函数、指针和数

组面试题的解析,其中内容较多,接下来将分为多篇博客进行阐述。

    指针的主题,我们在初级阶段的《指针》章节已经接触过了,我们知道了指针的概念:

1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。

2. 指针的大小是固定的4/8个字节(32位平台/64位平台),硬件,一般指的是CPU,,支持32虚拟地址空间或者64位虚拟地址空间,则CPU产生的就是32bit或者64bit的

地址,我们使用%p打出来的地址,实际上是虚拟地址,当我们要在真正的物理内存中去访问的时候,实际上是通过物理地址去访问的,此时就需要先把虚拟地址转为物

理地址,然后再去物理内存中访问, 虚拟地址是CP产生的,如果CPU支持32位虚拟地址空间,则产生32bit位的地址,大小就是4byte,如CPU支持64位虚拟地址空间,

则产生64bit位的地址,大小就是8byte,CPU产生虚拟地址,然电信号,地址线上产生的地址就是虚拟地址,然后虚拟地址经过硬件MMU或软件再次转换成物理地址,

物理地址再去物理内存中去访问,,所以,地址的大小就是4byte或8byte,那么存放地址的那块空间,即指针变量的大小就是4/8byte,所以,我们就可以知道了,,指

针或者说地址的大小就是4/8byte。

3. 指针是有类型,指针的类型决定了指针的加减整数的步长,指针解引用操作的时候的权限。

4. 指针的运算。

5.内存会划分为以字节为单位的空间,每个字节都有一个编号(地址或者指针),指针或者地址要被存放起来,需要一个空间,这个空间指的就是指针变量,口头上所

说的指针就是指针变量,本质上指针就是地址,地址就是指针,指针变量的大小是由地址来决定的,地址需要多大空间来存储,指针就需要多大的空间,32位平台就是

32bit即4byte,,64位平台即64bit即8byte,指针的类型也决定了解引用的时候能够访问多少个byte。

这个章节,我们继续探讨指针的高级主题。

1、字符指针:

字符指针的应用一般有两种情况:
一、一种指针类型为字符指针 char* ,字符指针变量指向一个字符,比如:
int main()
{
	char ch = 'w';
	char* p = &ch;
	return 0;
}

此时字符指针变量p指向了ch,而变量ch里面存放的是就是一个字符,所以称,字符指针指向了一个字符。

二、将一个常量字符串放在字符指针变量中,比如:

int main()
{
	char* p = "abcdef";
	*p = 'w';
	return 0;
}

它本质上是存储在内存的只读数据区中,不能改写,只能读取,所以,这样是不行的,不能改写该常量字符串的内容,只能读取其内容。

 

加上const就更能表达出当把常量字符串放在字符指针变量中时,其内容是不可以改写其内容的,所以下面再进行改变的时候,,会直接出错,在很多编译器下,如果不

写const,系统会报警告,所以,当我们把常量字符串放在字符指针变量中的时候,最好是把const加上,更加安全一些,现在如果把一个常量字符串放在一个字符指针里

面,我们知道,不管什么类型的指针,,只要是指针变量,它所占的空间大小就是4byte或者8byte,现在的字符串"hello bit";里面一共有10个字符,包括空格和\0,,

一个字符占一个字节,,共占10byte,但是我们的指针变量最多才存储8byte,所以这个字符串是存不进去的,,对于这种,,如果一个字符指针放了了一个常量字符

串,,那么实际上就是放了这个字符串的首元素的地址,,所以这里ps里面就是放了常量字符串首元素h的地址,所以对ps进行解引用的时候,,因为这是字符char类型

的指针,对char指针解引用的时候就会只访问一个字节,所以打印出来就应该是一个字符h,其中由于指针变量ps里面存放的是字符串首元素的地址,而首元素是一个字

符,所以,指针变量ps的类型是字符指针。

int main()
{
	//本质上是把"hello bit"这个字符串的首字符的地址存储在字符指针变量ps中
	char* ps = "hello bit";
	printf("%c\n", *ps); //h
	return 0;
}
而对于一个字符数组来说:
int main()
{
	char arr[] = "hello bit";
	return 0;
}

这里是把这个字符串整体全部放在了字符数组arr里面,前面的只是把字符串首元素得地址放在了字符指针变量ps里面;

对于''hello bit" 来说,,它是一个字符串,如果想要存储字符串的话,,应该是把他存储在一个字符数组里面,即: char arr[20]="hello bit",,这才是正确的形式。

但是两者的访问形式是一样的,因为数组名就代表首元素的地址。

对于下面两行代码而言,字符串都是常量字符串,只是如果把常量字符串放在字符指针中去的时候,就不能改变该常量字符串的内容,只能读取它的内容,而如果把常

量字符串放在字符数组里面的时候,是可以改变其内容的,只是如果把常量字符串放在字符指针变量中的时候,会被系统存储在单独的一个内存区域,即只读数据区

内,,但是如果拿着常量字符串去初始化字符数组的时候,该常量字符串不会被系统存储起来。

int main()
{
	char* ps = "hello bit";
	char arr[] = "hello bit";
	printf("%s\n", ps);
	printf("%s\n", arr);
	return 0;
}

当想要打印字符串的时候,,只需要知道该字符串首元素的地址就可以把整个字符串打印出来,因为,字符串首元素的地址就是我这个字符串整体的一个地址,,现在

我知道了字符串整体的地址,然后用%s去打印,就可以把整个字符串都打印出来,,因为对于字符(包括一个字符还包括字符串)来说,他有两种打印方式,即%s

和%c,,我如果想打印一个字符,就用%c,再需要一个首元素的地址就可以了,如果想打印字符串,要用%s和这个字符串的地址就可以了,,因为字符串整体的地址

和字符串首元素的地址是一样的,,所以只需要一个字符串首元素的地址就可以了。而对于其他类型的数组比如:整形数组和字符数组来说,,,我打印的时候只能

用%d和%c,,所以我知道数组首元素的地址,只能把首元素打印出来,如果想打印整个数组里面的元素,就需要让地址++,然后再用%d和%c通过循环去打印才行,

和字符串的打印是有很大差距的,注意不要混淆;

比如:

打印字符串用%s,打印字符用%c,,当我们把一个字符串放到一个字符指针变量里面的时候,实际上就是把这个字符串的首元素的地址放在了指针变量里面,,,所以

当打印字符串的时候,,直接用%s打印ps,,因为我们把字符串首元素的地址放在了ps里面,字符串的地址和字符串首元素的地址是一样的,,就和整个数组的地址和

数组首元素的地址所指的位置是一样的概念相同,,我们直接打印ps,就是知道了字符串或字符串首元素的地址,,就可以把字符串打印出来,而对于arr来说,,数组

名就代表了字符串首元素的地址也是整个数组的地址,并且是%s进行打印,所以我么直接打印arr就可以把整个字符串打印出来;

int main()
{
	char* ps = "hello bit";
	char arr[] = "hello bit";
	*ps = 'w';
	//arr[0]='w';
	return 0;
}

对与ps后面的字符串是一个常量字符串,即它里面的字符内容是不可以进行更改的,这里我们*ps=' w '; 意思就是想把字符串里面的首元素h变为w,这是不可以的,因为

他是一个常量字符串,不可以修改它的内容。但是对于: char arr[ ] ="hello bit";来说,我现在要 arr[0]='w'; 就是想把数组首元素换为字符w,这是可以的,因为我们第一

步只是对这个数字进行的初始化,后面仍可以进行改变该数组的元素。

int main()
{
	char* str3 = "hello bit.";//常量字符串
	char* str4 = "hello bit.";
	return 0;
}

所以对于这里的两个字符串,,都是常量字符串,,两者都不可以进行更改,两者一模一样,对于这样的常量字符串他的数据永远不会被修改,那么在内存中就没必要

存多份,由于是固定的,所以在内存中,只存一份就可以了,,所以在内存里面,二者只有一份,在这里只是把这一份字符串调用了两次,所以,这里str3和str4调用的

就是内存中同一个常量字符串,因为是同一个常量字符串,所以,str3和str4里面存放的是同一个常量字符串首元素的地址,所以说 str3==str4;

因此对于第二个if语句来说, 满足条件,所以两者相同,而对于第一个if语句来看,,因为str1和str2是两个数组,,两个数组的创建一定是在不同的空间进行创建的,,

又因为数组名代表数组首元素的地址,所以str1代表数字str1首元素的地址,即str1中字符h的地址,,str2代表数字str2首元素的地址,即str2中字符h的地址,, 但是因

为两个数组所在内存的空间是不同的,,所以,这两个字符h的地址是不同的,所以不满足第一个if语句,则打印不相同;

关于连续定义多个指针时容易出错的地方:
#define是定义符号,完全替换、

2、指针数组:

所谓指针数组 即指存放指针的数组。
对于一个数组来说,去掉数组名,剩下的部分就是数组的类型,去掉数组名和[n]数组元素的个数,
剩下的部分就是数组里面每个元素的类型。

 

对于指针数组来说,他就是存放指针的数组,,指针又是地址,所以可以成为存放地址的数组,,顾名思义,,指针数组里面存放的就是地址;所以,&a,&b,&c就是

a,b,c的地址,,恰好a,b,c又是整型,所以,可以存进去,对于下面的*(arr[i])而言,,,我这里的arr[i]是通过下标来访问数组arr的元素,而数组arr里面每个元

素都是一个地址,所以说,arr[i]就是指的地址,对地址进行解引用就可以找到该地址对应的那个变量,,,也可以这样看,,平常我们都是对指针进行解引用,,对指

针进行解引用就是找到该指针所指的那个变量,现在,,地址又是指针,所以对地址进行解引用就和对指针进行解引用的操作的作用是一样的,就是找到该地址对应的

那个变量。

int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int b[5] = { 2, 3, 4, 5, 6 };
	int c[5] = { 3, 4, 5, 6, 7 };
	//这样写也是可以的,,因为数组名就是代表数组首元素的地址,,所以,a,b,c就是数组名,,代表a,b,c这三个数组首元素1,2,3的地址;
	int* arr[3] = { a, b, c };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *(arr[i] + j));
		}
		printf("\n");
	}
	return 0;
}

我们之前对arr数组传参的时候,,知道arr是数组名,,代表数组首元素的地址,,如果我形参用指针变量str接收,,即相当于把数组首元素的地址放在了指针变量str里

面,,指针变量str里面存放的就是我们的数组首元素的地址,,如果str++,就会找到数组第二个元素的地址,,这里的arr[i]+j和我们之间的str+1道理是一样的,,因

为,,arr[i]就是知道a,b,c以i=0为例,,即arr[0],所以就是指的a,a又是数组名,,他就是指a这个数组首元素1的地址,,arr[0]指的就是数组a的首元素1的地

址,,现在arr[0]+j,,j从0到4,,所以arr[0]+j分别指的就是数组a里1.2.3.4.5的地址;

这样就可以把三个数组里面所有的元素进行打印出来了;

printf("%d ", *(arr[i] + j));

其中,

这里可以看成是模拟出了一个二维数组,但是只是模拟出来的并不是实际的,,数组在内存中都是连续存放的;但是在这里我们三个数组是完全独立的,只不过是前面

的 int* 把他们三个统一起来了,让我们感觉像一个二维数组,但实际上并不是二维数组;

int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组

如果是:

int arr[5]={1,2,3,4,5};
int* p=arr;
p++;

即,指针变量++,就要先去看该指针变量的类型,已知,指针变量p的类型是int*,,所以,p+1就跳过一个int整型,即,跳过4byte。如果是arr++,即,地址++,就要

去看该地址对应的变量是什么类型的,,arr是数组名,代表数组首元素的地址,又因为是int整型数组,所以数组里面每个元素的类型都是int整型,所以,数组首元素类

型也是int整型,所以说,该地址对应的变量的类型是int整型,所以arr+1就会跳过一个int整型, 跳过4byte。对于所有的初始化,均可用0来进行初始化,所以这里的指

针数组仍可以用0来进行初始化,当对指针数组使用0来初始化的时候,系统就会默认把0转为空指针NULL来对指针数组进行初始化。

3、数组指针:

3.1 数组指针的定义

指针数组和数组指针不是一个概念,数组指针是指针,是指向数组的指针。

整形指针: int * pint; 能够指向整形数据的指针。

浮点型指针: float * pf; 能够指向浮点型数据的指针。

数组指针应该是:能够指向数组的指针。

int *p1[10]   —— 由于[ ]的优先级高于*,所以,p1和[ ]优先进行结合形成数组,该数组有10个元素,每个元素的类型是int* 类型,所以这是一个指针数组,即p1是一个指

针数组。

int (*p2)[10] —— ()的优先级是最高的,所以先进行()内的操作,即*和p2先结合,通过*可知,p2是一个指针变量,往后看发现有[ ],所以该指针指向了一个数组,

该数组有10个元素,每个元素的类型为int整型, 所以这是一个数组指针,即p2是一个数组指针,这里要注意:[ ]的优先级要高于*号的,所以必须加上()来保证p先和*

结合。

3.2 &数组名VS数组名

我们平常见到的数组名就是代表数组首元素的地址,但是有两个特例,即,单独把数组名放到sizeof中和单独的在数组名前面加上取地址符号&,,即:

sizeof(数组名)—— 如果sizeof里面单独放了一个数组名,那么该数组名表示整个数组的,计算的是整个数组所占空间的大小;

&数组名 —— 如果取地址后面单独放了一个数组名,该数组名表示整个数组的,取出的是整个数组的地址;

除此之外,所有的数组名,都是数组首元素的地址;

通过上图可知,arr就是数组名,是数组首元素的地址,&(arr[0]),,这也是数组首元素的地址,&arr,其中,单独的在数组名arr前面加了个取地址符号&,则此时的数组

名arr代表的是整个数组,取出的是整个数组的地址,由图可知,三者的结果是一样的,,这就说明了,数组首元素的地址和整个数组的地址仅在大小,即所指的位置是

相同的,值是一样的,但是不同的是,两者的类型是不相同的,因为:

因为,数组首元素的类型是int类型,,如果要把数组首元素的地址要放在指针变量中的话,该指针变量的类型就是int*类型,,即放在一个整型指针变量中去,而,整个

数组的地址要放在一个指针变量中去的话,要放在一个数组指针变量里面去,因为,整个数组的类型是去掉数组名剩下的部分,即,整个数组的类型是:int [10]类型,

如果要把整个数组的地址放在指针变量中的话,该指针的类型就是int(*)[10],,即放在一个数组指针中去,所以,由于存放数组首元素地址的整型指针变量的类型是

int*类型,所以该指针变量+1就相当于是该数组首元素的地址+1,就会跳过一个int整型,即4byte,,由于存放整个数组地址的数组指针变量的类型是int(*)[10],,所

以,该指针变量+1就相当于是该整个数组的地址+1,就会跳过一个数组,即跳过10个整型,即40byte,,或者也可以直接从地址出发来看,,因为数组首元素的类型是

int类型,所以,数组首元素的地址+1就会跳过一个int整型,即4byte,所以,arr+1和&(arr[0]+1)就会跳过一个int整型,4byte,,其次就是,整个数组的地址+1,,

整个数组的类型就是int [10],,所以,整个数组的地址+1就会跳过整个一个数组,即跳过10个int整型,即40byte。数组的地址存放起来,就要放在一个数组指针中去,

对于int *p而言,,指针变量p的类型就是int*,,而对于,int(*p)[10] 而言,指针变量p的类型就是:int(*)[10]。

3.3 数组指针的使用

一般不使用于 一 维数组,既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。

由图可知,现在把整型变量a的地址取出来放在整形指针变量p中,然后对p进行解引用就可以的得

到了整型变量a,,再有:

由图可知,现在把整个数组的地址取出来放在数组指针变量p中,然后对p进行解引用就可以拿到整个数组,但是对于字符数组和整型数组,是不可以直接把数组里面的

元素一下打印出来的,,仿照上题来看,*p=arr,,又因为arr是数组名,数组名在这里没有特例,代表的是数组首元素的地址,即:*((*p)+i )=== *(arr+i),,

这样就可以对数组中的内容进行访问了,但是如果这样使用的话,反而会更加麻烦,所以,对于数组指针的使用,一般不会这样来使用的数组指针的使用一般是用在

二维数组数组名传参时:二维数组在数组传参时,数组名在没有特例的情况下代表的是数组首元素的地址,而对于二维数组来说,数组首元素就是指的二维数组的第一

行元素,二维数组中数组名也表示是数组首元素的地址,


#include <stdio.h>
//数组接收
void print_arr1(int arr[3][5], int row, int col) 
{
	int i = 0;
	for (i = 0; i<row; i++)
	{
		int j = 0;
		for (j = 0; j<col; j++)
		{
			printf("%d ", arr[i][j]);
		}
			printf("\n");
	}
}
//指针接收
void print_arr2(int(*p)[5], int row, int col)
{
	int i = 0;
	for (i = 0; i<row; i++)
	{
		int j = 0;
		for (j = 0; j<col; j++)
		{
			//printf("%d ", p[i][j]);
			printf("%d ", *(*(p + i) + j));
			//*(p+i)相当于拿到了每一行,也相当于拿到了每一行的数组名,数组名在这里不是两个特例,所以就相当于拿到了每一行数组首元素的地址。
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	//打印数组内容
	print_arr1(arr, 3, 5);//二维数组数组名传参
	//数组名arr,表示首元素的地址
	//但是二维数组的首元素是二维数组的第一行
	//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
	//可以数组指针来接收
	print_arr2(arr, 3, 5);

	//int arr[10] = { 0 };
	//int i = 0;
	arr[i] === *(arr+i) === p[i] === *(p+i)
	//int *p = arr;
	//*(p + i);

	return 0;
}

解释代码的意思: int (*parr3[10])[5]
首先()的优先级最高,先进行()内的操作,又因为[ ]的优先级高于*,所以要先进行parr3和[ ]的结合,,从而形成一个数组,去掉数组名parr3和数组元素的个数
[10],,剩下的就是该数组每个元素的类型,从而可知,每个元素的类型是 int(*)[5],该类型是一种数组指针,该类型的指针指向了一个数组,这个数组有5个元素怒,每
个元素的类型是int类型。
此篇博客到此为止,剩下的部分会陆续更新出来,谢谢大家点赞收藏。
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

脱缰的野驴、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值