C语言指针详解

1、指针相关概念

1.1 内存与地址

内存: 我们的学校宿舍,宿舍划分为很多个房间单元,每个学生的宿舍都有一个房间编号,如1栋101,如果想要找到某一个学生,只需要知道这个编号就能找到这个学生。
类比到计算机中,计算机CPU在处理数据时,这些数据是在内存中存储的,内存也是被划分为一个个的内存单元,每个单元是一个字节,一个字节有8个比特位,一个比特位可以存储一个二进制的0或者1,和宿舍房间一样,内存中每一个单元也有编号,这个编号就是地址。C语言中给这个编号取了个名字叫指针。
地址: CPU访问内存中的某个字节空间,必须知道这个字节空间在内存的什么位置,而因为内存中字节很多,所以需要给内存进行编址(就如同宿舍很多,需要给宿舍编号⼀样)。计算机中的编址,并不是把每个字节的地址记录下来,而是通过硬件设计完成的。
⾸先,必须理解,计算机内是有很多的硬件单元,而硬件单元是要互相协同⼯作的。所谓的协同,至少相互之间要能够进行数据传递。但是硬件与硬件之间是互相独⽴的,那么如何通信呢?答案很简单,用"线"连起来。而CPU和内存之间也是有大量的数据交互的,所以,两者必须也用线连起来。
其中有一种线叫地址总线。
32位机器有32根地址总线,每根线只有两态,表⽰0,1【电脉冲有无】,那么⼀根线,就能表示2种含义,2根线就能表示4种含义,依次类推。32根地址线,就能表表示2^32种含义,每⼀种含义都代表一个地址。地址信息被下达给内存,在内存上,就可以找到该地址对应的数据,将数据在通过数据总线传入CPU内寄存器。

1.2 指针变量和地址

1.2.1 取地址符号&
理解了内存的概念,我们可以知道,创建变量其实就是在内存中开辟了一块空间,将变量的值存放在这个空间,如果想得到这个空间的地址,可以使用&符号。例如

int main()
{
	int a=10;
	&a;
	return 0;
}

&a可以取出a的地址,那这个地址怎么保存呢?答案是可以保存到指针变量中
1.2.2 指针变量
如何定义指针变量?拿上面代码举例:

int main()
{
	int a=10;
	int *p=&a;
	char ch='a';
	char*pc=&ch;
	return 0;
}

p指的是变量名,这个*说明了p这个变量是一个指针变量,int则说明这个指针变量指向的是int类型(存放int类型变量的地址)。&ch表示取出ch的地址,存放在pc中,pc这个指针变量指向的是char类型。

1.3 指针变量的类型

1.3.1 指针变量的类型
在定义指针变量的时候,如果把变量名字去掉,得到的就是指针变量的类型
例如

int i = 10;
int *pi = &i;//类型为int*

char ch = 'a';
char* pch = &ch;//类型为char*

double d = 3.14;
double*pd = &d;//类型为double*

1.3.2 指针变量的大小
前面的内容我们了解到,32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后
是1或者0,那我们把32根地址线产⽣的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4个字节才能存储;同理64位机器需要8个字节才能存储。

#include<stdio.h>
int main()
{
	printf("%zd\n",sizeof(int*));
	printf("%zd\n",sizeof(char*));
	printf("%zd\n",sizeof(double*));
	printf("%zd\n",sizeof(short*));
	return 0;
}

32位平台运行结果:
在这里插入图片描述
64位平台运行结果:
在这里插入图片描述

总结:
32位平台:指针变量大小是4个字节
64位平台:指针变量大小是8个字节

1.3.2 指针变量类型的意义
在理解指针变量类型的意义之前,我们先了解解引用操作和指针加减整数运算。

解引用操作符 *

先看一段代码:

int main()
{
	int a=10;
	int *p=&a;
	*p=20;
	printf("%d\n",a);
	return 0;
}

输出结果
在这里插入图片描述
可以理解为:通过解引用操作,访问变量a的内存,将内存中的10改成20。

指针加减整数

int main()
{
	int n = 10;
	char* pc = (char*)&n;
	int* pi = &n;

	printf("&n   = %p\n", &n);
	printf("pc   = %p\n", pc);
	printf("pc+1 = %p\n", pc + 1);
	printf("pi   = %p\n", pi);
	printf("pi+1 = %p\n", pi + 1);
	return 0;
}

在这里插入图片描述

可以看出char * 类型的指针+1跳过了一个字节,int * 类型的指针+1跳过了4个字节。
至此我们就可以总结指针变量类型的意义了:

指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)、指针向前(+1)向后(-1)能走多远的距离
⽐如: char* 的指针解引⽤就只能访问⼀个字节,+1能跳过一个字节的距离;而int* 的指针的解引用就能访问四个字节,+1能跳过4个字节

1.4 const修饰指针变量

变量的值是可以被修改的,如果不想让变量的值被修改,可以加上const修饰。如

const int a =10;

被const 修饰后,变量a的值就不能被修改了。但是我们仍然通过指针来修改它,例如

int main()
{
	const int a = 10;
	int* p = &a;
	*p = 20;
	printf("%d\n", a);
	return 0;
}

在这里插入图片描述
为了不让种情况发生,我们可以用const修饰指针。
const修饰指针有两种情况

1、const在 * 的左边,修饰指针指向的内容,不能通过指针解引用来修改指针指向的内容
2、const在 * 的右边

例如:
const在* 左边

int main()
{
	int a = 10;
	int b = 20;
	const int* p = &a;
	int const* p = &a;//这种写法和const int* p是一样的
	// *p = 100;这种写法错误,不能通过解引用修改a的值
	p = &b;//可以改变p的指向
	return 0;
}

const在 *的右边

int main()
{

	int a = 10;
	int b = 20;
	int * const p = &a;
	*p = 100;//可以通过指针解引用改变指针指向的内容
	//p = &b;不能改变指针的指向
	return 0;
}

1.5 void*

int类型变量的地址只能由int类型的指针接收,如果用char类型的指针接收,会报一个警告:从int到char的类型不兼容,想要不报警告,可以改用void*接收。
在这里插入图片描述

在这里插入图片描述

void * 是一种特殊的指针,它可以保存任意类型的地址,相当于一个垃圾桶,什么类型的地址都可以接收。

int main()
{
	int i = 10;
	char ch = 'a';
	double d = 3.14;
	void* pi = &i;
	void* pch = &ch;
	void* pd = &d;
	return 0;
}

1.6 指针运算

1.6.1 指针加减整数
指针加减整数表示指针向前或向后走了多少步(与指针的类型有关),int类型的指针+1(-1)表示往后(往前)跳过了4个字节,而char类型跳过的是1个字节。例如

int main()
{
	int a = 10;
	char b = 'w';
	double c = 1.1;
	int* pa = &a;
	char* pb = &b;
	double* pc = &c;
	printf("pa=   %p\n", pa);
	printf("pa+1= %p\n", pa + 1);
	printf("pb=   %p\n", pb);
	printf("pb+1= %p\n", pb + 1);
	printf("pc=   %p\n", pc);
	printf("pc+1= %p\n", pc + 1);
	return 0;
}

在这里插入图片描述

1.6.2 指针减指针
指针减指针的值的绝对值表示:两个指针之间的元素个数。

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

输出结果:
在这里插入图片描述
在这里插入图片描述

1.7 野指针

野指针就是指针指向的是未知的地址、随机的地址、不允许访问的地址。
野指针产生的原因有:

  • 1、指针未初始化
int main()
{
	int *p;
	return 0;
}
  • 2、指针越界访问
int main()
{
	int arr[3]=0;
	for(int i=0;i<4;i++)
	{
		arr[i]=i;
	}
	return 0;
}
  • 3、指针指向的空间已经被释放
int* test()
{
 	int n = 100;
	return &n;
}
int main()
{
 	int*p = test();
 	printf("%d\n", *p);
 	return 0;
}

避免野指针出现,我们可以:

1、指针变量定义的同时初始化指针,如果不知道指针的指向可以赋值为NULL(空指针)
2、避免指针越界访问
3、指针不再使用时,及时置空

1.8 二级指针

由前面的知识我们知道,每一个变量都是有地址的,指针变量也是一样。指针变量的地址存在哪里?
答案是二级指针。二级指针如何定义?请看以下代码:

int main()
{
	int a=10;
	int *p=&a;
	int**p=&p;
	return 0;
}

int**p的解读:
p左边的这个*,表示p是一个指针变量
int * int右边这个*,表示指针变量指向的类型是int*
在这里插入图片描述
假设a的地址是0xff44,p变量存放的就是a的地址,假设p的地址是0xff0048,pp变量存放的就是p的地址。

2、指针与数组

2.1 数组名的含义

数组名就是首元素地址,但是有两个例外

1、sizeof(数组名):数组名单独放在sizeof内部,此时数组名表示整个数组
2、&数组名:取地址数组名,此时取出的是整个数组的地址

也就是说,除了以上两种情况,其他情况数组名都表示首元素地址。
对于二维数组,二维数组的首元素是第一行的内容。

2.2 使用指针访问数组

指针访问一维数组

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < sz; i++)
	{
		//printf("%d ", *(p + i));
		printf("%d ", p[i]);
	}
	return 0;
}

p[i]等价于*(p+i)

2.3 一维数组传参的本质

由前面的知识我们知道,数组名就是首元素地址,所以一维数组传参本质上传的是地址。

void Fun(int arr[])
{
	int ret = sizeof(arr) / sizeof(arr[0]);
	//地址就是指针,指针变量的大小取决于机器是多少位系统
	//32位系统:sizeof(arr)=4
	//64位系统:sizeof(arr)=8
	//sizeof(arr[0])=4
	printf("%d\n", ret);
}
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	Fun(arr);
	return 0;
}

在这里插入图片描述
在这里插入图片描述

2.4二维数组传参

数组名表示首元素地址,而二维数组的首元素是第一行的地址,所以二维数组传参传递的是二维数组第一行的地址
在这里插入图片描述

2.5指针数组

指针数组,是数组,数组里的每一个元素的类型是指针。
定义方式如下:

int main()
{
	int arr1[5];//整型数组,是存放int类型的数组,存放了5个整型元素
	char arr2[5];//字符数组,是存放char类型的数组,存放了5个字符型元素
	int* arr3[5];//整型指针数组,是存放int*类型的数组,存放了5个int*类型的元素
	char* arr4[5];//字符指针数组,是存放char*类型的数组,存放了5个char*类型的元素
	return 0;
}

2.6 数组指针

数组指针,是指针,指针指向的是数组,具体的定义方式如下

int main()
{
	int* a;//整型指针,指针指向类型的是int
	char* ch;//字符指针,指针指向类型的是char
	int(*pa)[5];//数组指针,指针指向的是一个数组,这个数组里有5个元素,数组中每一个元素的类型是int
	char(*pch)[5];//数组指针,指针指向的是一个数组,这个数组里有5个元素,数组中每一个元素的类型是char
	int* (*ppa)[5];//数组指针,指针指向的是一个数组,这个数组里有5个元素,数组中每一个元素的类型是int*
	char* (*ppch)[5];//数组指针,指针指向的是一个数组,这个数组里有5个元素,每一个元素的类型是char*
	return 0;
}

3、指针与函数

3.1 函数指针

在学习函数指针之前,我们得知道函数也是有地址的。例如

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	printf("%p\n", Add);
	printf("%p\n", &Add);
	return 0;
}

输出结果:
在这里插入图片描述
从输出结果我们还可以知道,函数名就是地址。
而函数指针就是存放函数地址的变量,还是拿上面Add函数举例来理解函数指针的定义

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int(*p)(int, int) = Add;
	return 0;
}
  • (*p)的 *表示p是一个指针变量
  • 右边的圆括号()表示指针指向的是函数,圆括号里面的int,int 是指向的这个函数的参数,参数名可以不写,也可以加上如(int x ,int y)
  • (*p)左边的int表示函数的返回值
  • p的初始值是Add函数的地址

所以函数指针的定义语法可以是

返回值(*指针名)(参数1类型,参数2类型,.....参数n类型)
例如:
void (*p)(char,int)=Fun;
表示p指针指向的是Fun函数,Fun函数的返回值是void
第一个参数是类型char,第二个参数类型是int

3.2函数指针数组

假如需要存放多个函数的地址,我们可以把这些函数的地址存放在一个数组中,这个数组就是函数指针数组。数组中每一个元素都是指针,这些指针分别指向不同的函数,定义的方式基于函数指针,看下面的例子

int Fun1(int x, int y)
{
	return x + y;
}
int Fun2(int x, int y)
{
	return x - y;
}
int Fun3(int x, int y)
{
	return x * y;
}
int Fun4(int x, int y)
{
	return x / y;
}

int main()
{

	int(*p)(int, int) = Fun1;
	int(*parr[4])(int, int) = { Fun1,Fun2,Fun3,Fun4 };
	return 0;
}

代码解读:

int(*p)(int, int) = Fun1;
这个p是指向Fun1的指针,把名字去掉,剩下的部分就是类型:
int(*)(int, int),这是函数指针的类型
在定义数组的时候,我们是按照:类型+数组名字[常量]的方式定义的,
例如int arr[4]
那么定义函数指针数组我们也可以这样理解:
int(*)(int, int)parr[4]
但是上面的这个代码是错误的,因为语法规定parr[4]要和*待在一起
所以正确的写法是int(*parr[4])(int, int)
int(*)(int, int)parr[4]这种形式有助于我们理解,但是语法是错误的

4、与指针相关的笔试题

4.1 sizeof与strlen

在解析笔试题之前,我们先复习一下sizeof和strlen区别

4.1.1 sizeof

sizeof是计算变量所占内存空间大小的操作符,它不会在乎括号内的操作数是什么数据,它只管计算内存大小,如果操作数是表达式,它也只是计算这个表达式结果的内存大小,不会改变变量的值。
例如:

int main()
{
	int a = 10;
	printf("%zd\n", sizeof(a));
	printf("%zd\n", sizeof(a++));
	printf("%zd\n", sizeof(10));
	printf("%zd\n", sizeof(char));
	printf("%d\n", a);
	return 0;
}

输出结果:
在这里插入图片描述
即使第二个printf中sizeof的操作数是一个表达式a++,但是它并不会改变a的值。

4.1.2 strlen

strlen是一个库函数,它的功能是计算字符串中字符的个数,它遇到字符’\0’ 才会停止。它的原型是

size_t strlen(const char* str);

strlen只关心’\0’,它不在乎是否越界。

4.2 数组和指针笔试题

4.2.1 一维数组

int main()
{
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof(a + 0));
	printf("%d\n", sizeof(*a));
	printf("%d\n", sizeof(a + 1));
	printf("%d\n", sizeof(a[1]));
	printf("%d\n", sizeof(&a));
	printf("%d\n", sizeof(*&a));
	printf("%d\n", sizeof(&a + 1));
	printf("%d\n", sizeof(&a[0]));
	printf("%d\n", sizeof(&a[0] + 1));
	return 0;
}

代码解读:

sizeof(a);//数组名单独放在sizeof内部,sizeof计算的是整个数组的大小->4*4=16
sizeof(a + 0);//数组名是首元素地址,a+0还是首元素地址,是地址,也就是指针,指针变量的大小就是4(32位)或者8(64位)
sizeof(*a));//数组名是首元素地址,解引用拿到了数组第一个元素,是一个int类型,大小是4
sizeof(a + 1);//数组名是首元素地址,a+1表示的是第二个元素的地址,是地址,大小就是4(32位)或者8(64位)
sizeof(a[1]);//a[1]就是数组的第二个元素,是一个整型变量,大小是4
sizeof(&a);//&a取出的是整个数组的地址,整个数组的地址也是地址,大小就是4(32位)或者8(64位)
sizeof(*&a);//取地址再解引用,作用抵消了,相当于sizeof(a)大小是16
sizeof(&a + 1);//&a取出的是整个数组的地址,但也是地址,大小是4(32位)或者8(64位)
sizeof(&a[0]);//取出第一个元素的地址,也是地址,大小是4(32位)或者8(64位)
sizeof(&a[0] + 1);//取出第一个元素的地址再+1,+1也是地址,大小是4(32位)或者8(64位)

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

4.2.2 字符数组

代码1:

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%zd\n", sizeof(arr));
	printf("%zd\n", sizeof(arr + 0));
	printf("%zd\n", sizeof(*arr));
	printf("%zd\n", sizeof(arr[1]));
	printf("%zd\n", sizeof(&arr));
	printf("%zd\n", sizeof(&arr + 1));
	printf("%zd\n", sizeof(&arr[0] + 1));
	return 0;
}

代码解读:

sizeof(arr);//数组名单独放在sizeof内部,sizeof计算的是整个数组的大小6*1=6
sizeof(arr + 0);//数组名是首元素地址, a + 0还是首元素地址, 是地址, 也就是指针, 指针变量的大小就是4(32位)或者8(64位)
sizeof(*arr);//数组名是首元素地址,解引用拿到了数组第一个元素,大小是1
sizeof(arr[1]);//arr[1]表示数组第二个元素,也就是字符b,大小是1
sizeof(&arr);//取出整个数组的地址,大小是4(32位)或者8(64位)
sizeof(&arr + 1);//取出整个数组的地址,+1跳过整个数组,但也是地址,大小是4(32位)或者8(64位)
sizeof(&arr[0] + 1);//取出第一个元素的地址再+1,+1也是地址,大小是4(32位)或者8(64位)

运行结果:
32位:
在这里插入图片描述
64位:
在这里插入图片描述
代码2

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", strlen(arr));
	printf("%d\n", strlen(arr + 0));
	printf("%d\n", strlen(*arr));
	printf("%d\n", strlen(arr[1]));
	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));
	return 0;
}

代码解读

char arr[] = { 'a','b','c','d','e','f' };
//该字符串中\0的位置未知,是随机的
strlen(arr));//数组名是首元素地址,从第一个元素开始计数,但是\0位置未知,所以是随机值
strlen(arr + 0));//随机值
strlen(*arr));//数组名是首元素地址,解引用拿到字符a,但是strlen的参数是char*类型,所以程序会把a的ASCII码当做一个地址,造成非法访问,所以这是错误代码,程序会崩溃
strlen(arr[1]));//arr[1]拿到的是字符b,与*(arr)一样,也是错误的
strlen(&arr));//随机值
strlen(&arr + 1));//随机值
strlen(&arr[0] + 1));//随机值

运行结果
错误代码除外
在这里插入图片描述
代码3

int main()
{
	char arr[] = "abcdef";
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr + 0));
	printf("%d\n", sizeof(*arr));
	printf("%d\n", sizeof(arr[1]));
	printf("%d\n", sizeof(&arr));
	printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0] + 1));
	return 0;
}

代码解读

char arr[] = "abcdef";
//字符串会有一个\0,所一个有7个元素
sizeof(arr));//数组名单独放在sizeof内部,计算的是整个数组的大小,包含\0,结果是7
sizeof(arr + 0);//数组名是首元素地址,是地址,大小是4或8
sizeof(*arr);//数组名是首元素地址,解引用拿到第一个元素a,大小是1
sizeof(arr[1]);//arr[1]是第二个元素,大小是1
sizeof(&arr);//取出整个数组的地址,是地址,大小是4或8
sizeof(&arr + 1);//取出整个数组的地址再+1,也是地址,大小是4或8
sizeof(&arr[0] + 1);//取出第一个元素的地址然后+1,得到的是第二个元素的地址,是地址,大小是4或8

运行结果
32位
在这里插入图片描述
64位
在这里插入图片描述
代码4

int main()
{
	char arr[] = "abcdef";
	printf("%d\n", strlen(arr));
	printf("%d\n", strlen(arr + 0));
	printf("%d\n", strlen(*arr));
	printf("%d\n", strlen(arr[1]));
	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));
	return 0;
}

代码解读:

strlen(arr);//数组名是首元素地址,从第一个元素开始计数,遇到\0结束,结果是6
strlen(arr + 0);//arr+0还是首元素地址从,第一个元素开始计数,结果是6
strlen(*arr);//错误代码:数组名是首元素地址,解引用拿到第一个元素a,把a的ASCII码97当做地址,这个地址是未知的,造成非法访问,程序崩溃
strlen(arr[1]);//错误代码,把b的ASCII码,当做地址,原因与(*arr)一样
strlen(&arr);//&arr,取出整个数组的地址,整个数组的地址和第一个元素的地址是一样的,所以也是从第一个元素的地址开始计数,结果是6
strlen(&arr + 1);//&arr,取出整个数组的地址,+1跳过了整个数组,指向的那块空间往后是随机的,\0的位置未知,所以是随机值
strlen(&arr[0] + 1);//&arr[0]取出第一个元素的地址,+1表示的是第二个元素的地址,从第二个元素开始计数,结果是5

输出结果:
在这里插入图片描述
代码5

int main()
{
	char* p = "abcdef";
	printf("%d\n", sizeof(p));
	printf("%d\n", sizeof(p + 1));
	printf("%d\n", sizeof(*p));
	printf("%d\n", sizeof(p[0]));
	printf("%d\n", sizeof(&p));
	printf("%d\n", sizeof(&p + 1));
	printf("%d\n", sizeof(&p[0] + 1));
	return 0;
}

代码解读:

sizeof(p);//p是一个指针变量,大小是4或8
sizeof(p + 1);//也是指针变量,大小是4或8
sizeof(*p);//解引用得到字符a,大小是1
sizeof(p[0]);//p[0]表示数组第一个元素a,大小是1
sizeof(&p);//取出p的地址,是地址,大小是4或8
sizeof(&p + 1);//取出p的地址,地址+1也是地址,大小是4或8
sizeof(&p[0] + 1);//取出第一个元素的地址,+1就是第二个元素的地址,是地址,大小是4或8

运行结果:
32位:
在这里插入图片描述
64位:
在这里插入图片描述
代码6:

int main()
{
	char* p = "abcdef";
	printf("%d\n", strlen(p));
	printf("%d\n", strlen(p + 1));
	printf("%d\n", strlen(*p));
	printf("%d\n", strlen(p[0]));
	printf("%d\n", strlen(&p));
	printf("%d\n", strlen(&p + 1));
	printf("%d\n", strlen(&p[0] + 1));
	return 0;
}

代码解读

strlen(p);//a开始计数,结果是6
strlen(p + 1);//p+1指向的是b,从b开始计数,结果是5
strlen(*p);//解引用得到a,把a的asci码当地址,错误
strlen(p[0]);//p[0]等价于*(p+0),拿到的是字符a,同样是把a的ASCII当地址,错误
strlen(&p);//从p的地址开始计数,这个地址和字符串没有任何关系,这个地址往后\0的位置也是未知的,所以是随机值
strlen(&p + 1);//从p+1的地址开始计数,随机值
strlen(&p[0] + 1);//等价于取出第一个元素的地址再+1,得到的是第二个元素b的地址,所以从b开始计数,结果是5

4.2.3 二维数组

int main()
{
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof(a[0][0]));
	printf("%d\n", sizeof(a[0]));
	printf("%d\n", sizeof(a[0] + 1));
	printf("%d\n", sizeof(*(a[0] + 1)));
	printf("%d\n", sizeof(a + 1));
	printf("%d\n", sizeof(*(a + 1)));
	printf("%d\n", sizeof(&a[0] + 1));
	printf("%d\n", sizeof(*(&a[0] + 1)));
	printf("%d\n", sizeof(*a));
	printf("%d\n", sizeof(a[3]));
	return 0;
}

代码解读

sizeof(a);//数组名单独放在sizeof内部,计算的是整个数组,12*4=48
sizeof(a[0][0]);//a[0][0]就是数组第一个元素,int类型,大小是4
sizeof(a[0]);//a[0]是第一行这个一维数组的数组名,数组名单独放在sizeof内部,计算的是第一行这个数组的大小,第一行有4个元素,所以是4*4=16
sizeof(a[0] + 1);//a[0]是第一行这个数组的数组名,数组名是首地址,+1就是第二行这个一维数组的地址,但也是地址大小是4或8
sizeof(*(a[0] + 1));//a[0] + 1第二行的数组名,数组名是首元素地址,解引用得到第二行第一个元素,大小是4
sizeof(a + 1);//数组名是首元素地址,+1跳过整个数组,但也是地址,大小是4或8
sizeof(*(a + 1));//数组名是首元素地址首元素,而二维数组的首元素是第一行这个一维数组,+1就是第二行的地址,解引用得到第二行的所有元素,所以计算的是第二行全部的元素,结果是16
sizeof(&a[0] + 1);//取出第一行的地址,+1得到第二行的地址,是地址,结果是4或8
sizeof(*(&a[0] + 1));//第二行的地址解引用,得到第二行的内容,结果是4*4=16
sizeof(*a);//数组名是首元素地址,二维数组的首元素是第一行,解引用得到第一行,所以计算的是第一行这个一维数组大小,16
sizeof(a[3]);//a[3]表示的是第4行这个一维数组的数组名,虽然这个数组没有第4行,但是sizeof在乎,结果是16

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

4.3指针运算笔试题

题1

int main()
{
 int a[5] = { 1, 2, 3, 4, 5 };
 int *ptr = (int *)(&a + 1);
 printf( "%d,%d", *(a + 1), *(ptr - 1));
 return 0;
}

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

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

题解:
在这里插入图片描述
运行结果
在这里插入图片描述
完…

  • 23
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值