C语言初阶——指针

指针的定义——指针是什么?

首先我们明确内存空间是如何管理的?当我们进行存储时,系统会将内存切割成内存单元,也就是1 byte(字节)。

1.指针是内存中一个最小的单元,也就是地址。

2.在平常口语中,我们常讲的指针通常是指针变量,是用来存放内存地址的变量。

指针变量:就是用来存放地址的变量(存放在指针中的值都被当成地址处理)。

我们定义指针的基础格式:

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

	return 0;
}

经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。

对于32位的机器来讲:我们假设其有32个地址线,其产生的地址位1或0,那么就相当于其地址值有32位,也就是有2的32次方种情况,每个地址有一个字节,就相当于2^32/1024/1024/1024=4G的内存空间。那么对于64位的机器来讲,用来存储的内存空间更大。

在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以
一个指针变量的大小就应该是4个字节。
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地
址。

在普通的编译器上会有X86,X64的版本,X86是32位的环境,X64是64位的环境。

指针的类型

int* a = NULL;
char* b = NULL;
short* c = NULL;
long* d = NULL;
float * e = NULL;
double* f = NULL;

char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。

当我们知道指针的类型的时候,那么我们想它的意义在哪里呢?

指针+-整数

我们想当指针加减整数的时候会发生什么呢?接下来我们编辑一下代码来看一下:

int a = 10;
int* pa = &a;
char* pb = (char*) & a;//a是int类型的值,我们定义一个char类型的有可能编译器不会保存,但是我们还是进行强制类型转换一下,以免报错。

printf("%p\n", &a);
printf("%p\n", pb);
printf("%p\n", pb+1);
printf("%p\n", pa);
printf("%p\n", pa+1);
0000003832D3FC44
0000003832D3FC44
0000003832D3FC45
0000003832D3FC44
0000003832D3FC48

我们来看,当我们直接取a的地址值和直接打印指针变量pa和pb是,输出的结果是一样的,都是00000003832D3FC44,当我们用char类型,也就是pb+1时,可以看到地址变成00000003832D3FC45,也就意味着内存地址增加了一个字节,当我们用int类型时,也就是pa+1地址变成00000003832D3FC48,也就意味着增加了四个字节。那么我们就明白,指针类型就意味着决定指针向前或者向后走一步有多大(距离)。

指针的解引用

int n = 0x11223344;
char* pc = (char*)&n;
int* pi = &n;
*pc = 0;//重点在调试的过程中观察内存的变化。
*pi = 0;//重点在调试的过程中观察内存的变化。

指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。

野指针

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

有以下情况:

1.未初始化:

int* p;//局部变量指针未初始化,默认为随机值
*p = 20;

2.指针的越界访问

int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i <= 11; i++)
{
	*(p++) = i;//当指针指向的范围超出数组arr的数组范围,那么p指针就是野指针。
}

3.指针指向的空间释放

如何规避野指针

1.指针要初始化,如何不知道放什么,那么就放NULL。2.小心指针越界,看清楚指针所指向的范围。3.指针指向空间释放,要及时NULL。4.避免返回局部变量的地址值。5.指针使用前要检查有效期。

指针运算

指针+-整数、指针-指针、指针的关系运算

指针+-整数

#define N_VALUES 5
	float values[N_VALUES];
	float* vp;
	//指针+-整数;指针的关系运算
	for (vp = &values[0]; vp < &values[N_VALUES];)
	{
		*vp++ = 0;
	}

指针-指针

int my_strlen(char* s)
{
	char* p = s;
	while (*p != '\0')
		p++;
	return p - s;
}

指针的关系运算

for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}

上述可以改下成下面代码

for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}

实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。

标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与
指向第一个元素之前的那个内存位置的指针进行比较。

指针与数组

int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
000000173198F928
000000173198F928

通过上面的案例,我们就可以得出数组的地址和数组首个元素的地址相同。

那么我们得出结论:数组名表示的是首元素的地址(两种情况除外:sizeof(数组名)、&数组名)

int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
int* p = arr;

那么我们定义一个指针直接存放数组首元素的地址。

既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个就成为可能。

int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < sz; i++)
{
	printf("&arr[%d]=%p  <=======> p+%d=%p\n",i,&arr[i],i,p+i);
}
&arr[0]=000000A0B370FBD8  <=======> p+0=000000A0B370FBD8
&arr[1]=000000A0B370FBDC  <=======> p+1=000000A0B370FBDC
&arr[2]=000000A0B370FBE0  <=======> p+2=000000A0B370FBE0
&arr[3]=000000A0B370FBE4  <=======> p+3=000000A0B370FBE4
&arr[4]=000000A0B370FBE8  <=======> p+4=000000A0B370FBE8
&arr[5]=000000A0B370FBEC  <=======> p+5=000000A0B370FBEC
&arr[6]=000000A0B370FBF0  <=======> p+6=000000A0B370FBF0
&arr[7]=000000A0B370FBF4  <=======> p+7=000000A0B370FBF4
&arr[8]=000000A0B370FBF8  <=======> p+8=000000A0B370FBF8
&arr[9]=000000A0B370FBFC  <=======> p+9=000000A0B370FBFC

所以 p+i 其实计算的是数组 arr 下标为i的地址。

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

那我们就可以直接通过指针来访问数组。

二级指针

指针变量虽然是存放变量的地址,但是其也是变量,也是会产生地址,那么我们就由此来产生出二级指针,用来存放一级指针变量的地址。

int a = 10;
int* pa = &a;
int** pi = &pa;

printf("%p\n",&a);
printf("%p\n",pa);
printf("%p\n",pi);
00000043984FF5D4
00000043984FF5D4
00000043984FF5F8

这样我们就可以清楚的看到,我们一级指针存储的就是我们设置变量a的地址,我们的二级指针存放的就是我们一级指针的地址。

我们对二级指针的运算:

*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa .

int b = 20;
*ppa = &b;//等价于 pa = &b;
**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;

**ppa 通过*ppa来找到pa,然后我们对pa进行解引用操作:*pa 那找到的就是a。

数组指针

指针数组顾名思义到底是指针还是数组呢,答案是数组。只不过是用来存放指针的数组。

int arr1[4] = { 1,2,3,4 };
int arr2[4] = { 5,6,7,8 };
int arr3[4] = { 2,5,8,7 };

int* arrp[] = { arr1,arr2,arr3 };

for (int i = 0; i < 3; i++)
{
	for (int j = 0; j < 4; j++)
	{
		printf("%d ",*(arrp[i] + j));
	}
	printf("\n");
}
1 2 3 4
5 6 7 8
2 5 8 7

我们还可以直接定义一个二维数组的方式进行打印:

int arr[3][4] = { 1,2,3,4,5,6,7,8,2,5,8,7 };
for (int i = 0; i < 3; i++)
{
	for (int j = 0; j < 4; j++)
	{
		printf("%d ",arr[i][j]);
	}
	printf("\n");
}
1 2 3 4
5 6 7 8
2 5 8 7

我们这里看到打印的结果相同,但是上面的代码是将三个数组进行打印,而下面的代码则是进行二维数组打印,打印结果虽然相同,但是本质不同。

如有错误,还望指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值