内存会划分为一个个内存单元(一个内存单元大小为1字节)每个内存单元都有一个编号即地址也被称为指针,我们可以理解为指针就是地址。通过指针可以找到其所指向的内存单元。
就像我们第一次去一个朋友家时可以通过门牌号找到她的家一样,指针就是所存储数据的“门牌号”
但不同的地方是在C语言中不同类型的数据在内存中所占用的字节数和存储方式是不一样的,所以我们在存取数据时不仅要知道位置信息还要知道该数据的类型信息,下文会提到
如果一个变量专门存放另一个变量的地址(指针),则称其为指针变量
存放指针(地址)的变量就是指针变量
我们平时说的指针其实是指针变量,而指针本质上是地址通过地址可以找到内存单元
类型名 * 指针变量名;
int *p1,*p2;
//int 说明指向的对象类型为int
//* 说明p1和p2是指针变量
//p1和p2是指针变量名
//特别注意一个新手常见错误
//int *p1,p2;此处的p1是指针变量,p2则只是一个普通的整形变量
在介绍之前我们先来学习相关运算符*和&
此处的*与&与之前的又不一样单独记忆即可
&是取地址运算符,假如有一个int型变量a &a代表的就是a的地址
*指针运算符(也称间接访问运算符)假如我们定义了一个指针变量p 。*p代表指针变量p指向对象的值,详见代码
int a=10;
//&a 就是a的地址
int *p=&a;
//p 就是a的地址
//*p 就是a的值
//a==*p
//&a==p
//注意
printf("%d",*&a);//先对a取地址再解引用相当于什么都没做输出a
//但是 &*a 则不对,因为a无法解引用
printf("%p\n", &*p1);//先对p1解引用再取地址,输出的还是a的地址
printf("%p", *&p1);//先对p1取地址得到的是p1的地址,再解引用输出的仍然是a的地址
赋值操作
//1.在定义的时候赋值
int a = 1;
int *p1 = &a;
printf("%d\n", *p1);
printf("%p", p1);//%p输出地址
//2.其他
int a = 1,b=10;
int *p1;
p1 = &a;
printf("%d\n", *p1);
printf("%p", p1);//a的地址
p1=&b;//更改指向
printf("%d\n", *p1);
printf("%p", p1);//b的地址
指针变量是用来存放地址的,其大小取决于一个地址存放是需要多大的空间,32位的机器的地址为32bit位到4byte(字节),指针变量的大小为4个字节,64位的机器的地址为64bit位到8byte(字节),指针变量的大小为8字节
通过代码验证
//%zu是输出size_t型即无符号整形(unsigned int)
printf("%zu", sizeof(char*));
printf("%zu", sizeof(short*));
printf("%zu", sizeof(int*));
printf("%zu", sizeof(double*));
我们若是32位输出全为4,若是64位输出全为8
指针变量的大小并不是所指向对象的数据类型决定的而是取决于所在操作系统位数和编译器类型
指针的类型决定了指针在被解引用时访问几个字节
int* 解引用时访问 4个字节,char* 解引用时访问1个字节,double* 解引用时访问8个字节
指针的类型决定了进行加减操作时往前跳过几个字节也可以说决定了指针的步长
int* 类型+1跳过4个字节,char*+1跳过1个字节,double* +1 跳过8个字节
float* +1也跳过4个字节,但不能与int* 通用
int a=10;
int* p = &a;
printf("%p\n", p);
printf("%p\n", p+1);
printf("%p\n", p+2);
我们可以发现每次往前增加4个字节,验证了我们上述说法。
指针变量-整数=指针变量
指针变量-指针变量=整数 这个整数的绝对值是两个指针变量之间的元素个数,不是所有的指针变量都可以相减,只有指向同一块空间的两个元素才能相减
void swap1(int* p1, int* p2)
{
int t=*p1;
*p1 = *p2;
*p2 = t;
}
void swap2(int p1, int p2)
{
int t=p1;
p1 = p2;
p2 = t;
}
int main()
{
int a = 2, b = 1;
printf("%d %d\n", a, b);
swap1(&a, &b);//要传地址
printf("%d %d\n", a, b);
swap2(a,b);
printf("%d %d\n", a, b);
}
我们发现swap1可以做到互换ab的值,swap2则不可以
因为swap1接收的是a与b的地址,可以直接对a与b进行修改
而swap2 会构建临时变量来接收a与b的值并不会对a与b造成实质性影响
就像你的朋友买了一个玩具你把它借过来玩,玩具发生了损坏,对你的朋友造成了影响。但是如果你选择买一个相同的玩具,损坏了你的朋友不会受到任何影响
在c语言中数组名等价于首元素地址。但有两个例外1.对数组名取地址是获得的是整个数组的地址2.使用sizeof计算数组所占空间大小时也代表整个数组地址
由上可得 int *p=a;与int *p=&a[0];(此处假设a为一个数组)是等价的
由于数组在内存中的存储是连续的
int a[3] = { 1,5,9 };
int* p = a;
printf("%d\n", *p);
printf("%d\n", *(p+1));
printf("%d\n", *(p+2));
printf("%d\n", *a);
printf("%d\n", *(a+1));
printf("%d\n",*(a+2));
由上可得上三个输出和下三个输出的结果是一样的
[] 其实是一个变址运算符。a[1]等价于*(a+1);
所以指针也可使用[]来访问相邻的地址
指向同一数组的两个指针变量相减得到的值是两个元素的距离(即,两个元素间差了几个元素),但是两个指针变量相加没有意义
数组名做函数参数时,如下
void swap3(int a[])
{
int t = a[2];
a[2] = a[0];
a[0] = t;
}
int main()
{
int a[3] = { 1,5,9 };
swap3(a);
for (int i = 0; i < 3; i++)
printf("%d ", a[i]);
printf("\n");
swap3(a);
for (int i = 0; i < 3; i++)
printf("%d ", a[i]);
}
我们发现我们函数好像没用指针来接收那为什么实参发生改变了呢?
在程序编译时其实是将a当做指针变量来处理的
即 void swap(int a[]){} 与void swap(int *a) {}是等价的
我们要实现对一个数组的实际操作形参用一个数组名或者用一个指针变量名都是可以的
造成野指针常见的几种情况
//1.
int *p;//
*p=10;//非法访问
//2.
int a[10]={0};
int *p=a;//p指向数组首地址
for(int i=0;i<11;i++)//越界
{
*p=i;
p++;
}
//3.
int* test()
{
int a=10;
return &a;
}
int main()
{
int *p=test();
return 0;
}
1. 局部变量未初始化,即无明确指向,存放的值为随机值,我们不能对其进行操作
2. p可操作的范围即为数组的范围,数组范围外是随机值。第二种情况是越界访问了
3. 函数中的变量a是局部变量,在函数执行完后会销毁,p指向已经销毁的地址,但不能使用
小寄巧 不知道指针变量初始化为什么可以初始化为NULL即int *p=NULL;
避免野指针的小技巧
1.对指针变量初始化
2.小心指针越界
3. 指针指向空间释放后及时将指针置为空(malloc或new开辟空间释放后)
4. 避免返回局部变量的地址(如上述第三个案例)
5. 指针使用检查有效性
有什么错误欢迎大家在评论区指出,爱你们ε=(´ο`*)))