hello,欢迎打开博主的文章,接下来让博主带着大家一起学习初阶指针,冲冲冲!
文章目录
前言
为什么存在指针?
在计算机科学中,指针是编程语言中利用地址它的值将直接指向存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元。因此,将地址形象化的称为“指针”。
在高级语言中,指针有效地取代了在低级语言,如汇编语言与机器码,直接使用通用暂存器的地方,但它可能只适用于合法地址之中。指针参考了存储器中某个地址,通过被称为反参考指针的动作,可以取出在那个地址中存储的值。
1.指针
1.1指针变量
我们说指针就是是内存中一个最小单元的编号,也就是地址,指针通常是指指针变量,而这个变量是用来存储内存地址的变量。
1.2怎样去定义指针
我们可以通过“&”取出变量的内存其实地址,把地址可以存放到我们说的指针变量。如&a表示变量a的地址,&b表示变量b的地址。变量本身必须预先说明。
*:指针运算符(或称“间接访问” 运算符)。
在c语言标准中,指针的定义方式是: type(数据类型) + * 。
不同类型的type,对应的意义有什么不同呢,其实:
char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int* 类型的指针是为了存放 int 类型变量的地址。
设有指向整型变量的指针变量p,如要把整型变量a 的地址赋予p可以有以下方式:
#include <stdio.h>
int main()
{
int a;
int *p=&a; //a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量
return 0; //中,p就是一个之指针变量。
}
#include <stdio.h>
int main()
{
int a;
int *p=NULL;
p=&a;
return 0;
}
注:不允许把一个数赋予指针变量,故下面的赋值是错误的: int p;p=1000; 被赋值的指针变量前不能再加“”说明符,如写为 *p=&a; 也是错误的
1.3指针的大小
我们知道指针也是一种数据类型,既然是数据类型,那么他的大小是多少呢?我们来利用sizeof函数(返回一个数据类型或者对象所占的内存字节数)计算一下指针的大小,接下来看代码:
#include <stdio.h>
int main()
{
int a=2;
char b = 's';
int* p = &a;
char* pp = &b;
printf("p大小为:%d\n", sizeof(p));
printf("pp大小为:%d\n", sizeof(pp));
return 0;
}
我们看两张图:
我们发现,当我们将处理器由64位改成32位以后,指针的大小也由8变成了4,这是为什么呢?
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电
平(低电压)就是(1或者0);
那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
…
11111111 11111111 11111111 111111111111
每个地址标识一个字节,那我们就可以给 (2^32byte = 4GB)的空间进行编址。
同样的方法,那64位机器,如果给64根地址线呢?
这里我们就大概清楚:
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以
一个指针变量的大小就应该是4个字节。
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地
址。
总结一下:
指针的大小在32位平台是4个字节,在64位平台是8个字节。
2.指针运算
既然是数据类型,那他是否可以进行运算呢?答案是肯定的,接下来我们看看指针的运算
2.1指针的解引用
既然我们可以顺着指针找到一片地址,那我们指针存在的意义也就应该有去访问这片内存存在的权利,我们看如下代码:
#include <stdio.h>
int main()
{
int n = 0x11223344;
int *p = &n;
*p = 0; //重点在调试的过程中观察内存的变化。
return 0;
我们通过调试来看&n在内存中的存储变化情况:
我们可以清楚的看到通过对指针p的调用修改了n的值,这种调用我们就称之为解引用
同时要注意:
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
2.2指针±整数
话不多说,上代码:
#include <stdio.h>
int main()
{
int n = 10;
char* pc = (char*)&n; //将int型内容强行转换为char类型
int* pi = &n;
printf("%p\n", &n); //%p就是printf中用来打印地址的
printf("%p\n", pc);
printf("%p\n", pc + 1);
printf("%p\n", pi);
printf("%p\n", pi + 1);
return 0;
}
结果代码如下图:
接下来我们一起解读这段代码:
第一句“&n”打印的是n的地址012FF828
第二句打印pc指向的地址,我们知道他指向n,所以打印的地址和"&n"相同
第三句打印pc+1,我们发现pc+1打印的地址是pc的地址跳过一个字节,而他的数据类型为char*,好,我们先保留想法,继续下一个
第四句打印pi指向的地址,我们知道他也指向n,所以打印的地址也和"&n"相同
第五句打印pi+1,我们发现pi+1打印的地址是pi的地址跳过四个字节,而他的数据类型为int*
char*+1跳过一个字节,int*+1跳过四个字节,那是不是指针+1就跳过type个字节呢,+2就是跳过2*type个字节呢?
答案是正确的,也就是说指针的类型决定了指针向前或者向后走一步有多大(距离)。
而在指针-整数的过程中,我们要注意这个问题:
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--) {
*vp = 0; }
这个代码可以在大部分编译器上正常运行,但它不符合我们的c语言标准,标准如下:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与
指向第一个元素之前的那个内存位置的指针进行比较。
2.3指针-指针
这种运算比较少见,但是我们也是必须要了解的,他的运算要求比较严格:必须要指向同一个数组!
看代码:
#include<stdio.h>
int main()
{
int a[5] = { 1,2,3,4,5 };
int* p1 = &a[0];
int* p2 = &a[4];
printf("%d", p2 - p1);
return 0;
}
看结果:
我们可以发现他运行的结果其实是两个指针之间的元素数目。
2.4指针的关系运算
*ptr++;
对于这个语句,我们要清楚的是++优先级高于*,所以ptr++==(ptr++);
3.二级指针
3.1什么是二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
这就是 二级指针的由来 。
#include<stdio.h>
int main()
{
int a = 30;
int* p = &a;
int** pp = &p;
**pp = 0;//等价于*p= 30; 等价于a = 30;
printf("%d", a);
return 0;
}
4.野指针
4.1什么是野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。
4.2野指针常见成因
4.2.1.0 NULL
NULL是在计算中具有保留的值,用于指示指针不引用有效对象。指针还没有明确指向时通常使用空指针来表示,例如未知长度列表的结尾或未执行某些操作。
int* p=NULL;
空指针与未初始化的指针不同:
4.2.1指针未初始化
#include <stdio.h>
int main()
{
int *p;
*p = 20;
return 0;
说明://局部变量指针未初始化,随机指向内存中的一块地址
4.2.2 指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
*(p++) = i;
}
return 0; }
说明: 当指针指向的范围超出数组arr的范围时,p指向一块未知的区域,他也是野指针
4.2.3指针指向的空间释放
使用动态开辟空间时,没有释放指针(有兴趣的关注博主,博主会在后面的动态开辟空间中具体谈到释放指针问题)
4.3如何避免野指针
- 指针初始化
- 小心指针越界
- 指针指向空间释放,及时置NULL
- 避免返回局部变量的地址
- 指针使用之前检查有效性
总结
今天关于指针你get到了吗?关于指针,还有诸多内容,本篇文章只是介绍指针的一部分基础内容。后续还有诸多指针的应用,例如指针数组,数组指针,函数指针等等,我们会在进阶篇详细介绍,真正走进指针。欢迎关注博主,与博主第一时间学习更多c语言的内容。