目录
地址----学前须知
首先我们需要理解一下地址
当计算机处理数据时,CPU需要从内存中读取和存放数据
内存被分为一个个内存单元,每个内存单元的大小为1字节
同时每个内存单元都有属于它自己的地址,例如:0x1100ff00这样子的16进制数字
所以可以这么理解:
内存单元的编号 = 地址 = 指针
指针变量
初始化和作用
指针通常情况下说的是指针变量,在进行初始化时需要用到取地址符号(&)
指针的作用就是访问内存
模板
指针变量中存放的是对应数据的地址
例如:
int a=10;
int * p = &a;
在上面的代码中,p作为一个int型的指针变量,指向的是a的地址
*的含义是:p是一个指针变量
int的含义是:p指向的是一个int类型的数据
大小
指针的大小取决于编译环境是32位或64位
倘若是32位,一个指针变量的大小就是4个字节,即32bit
若是64位,一个指针变量的大小就是8个字节,即64bit
指针变量还可以通过符号*进行解引用
例如
int a=10;
int * p =&a;
*p=5;//与a=5效果一致
最后一句*p=5就把p指针解引用了,相当于直接对a进行操作
类型
指针类型决定指针进行解引用操作符时访问字节的数目(即决定指针权限)
char*类型可访问1个字节
short*类型可以访问2个字节
int*类型可以访问4个字节的整型
float*类型可以访问4个字节的浮点型
下图中,一个格子代表一个字节
int*型+1时走过4个字节
char*型+1时走过1个字节
指针的类型决定了指针走一步经过的字节大小
当你想访问某一特殊内容时,记得使用适合的指针类型
void* 特殊指针
无具体类型的指针,也称泛型指针
可以接收任意地址的指针,但是无法直接进行指针的+-运算和解引用的运算
一般用于函数参数的部分,用来接收不同类型的指针,可使一个函数处理不同类型的数据
const修饰变量
在C语言中,被const修饰后的变量具有了常属性(不可被修改),为常变量
但在C++中,被const修饰后的变量就成为了常量
例如
const int a=10;
int arr[a];
倘若在C语言环境下就会报错,但是在C++环境下可以正常运行
但只用const修饰变量是可以通过指针进行修改的
就好比一栋宿舍楼,宿管阿姨把大门锁上了,但是有人翻墙进宿舍了,依旧对内部造成了变化
例如
#include<stdio.h>
int main()
{
const int a=10;
int * p =&a;
*p=5;
printf("%d",a);
return 0;
}
可以看到a的值依旧被更改了,这种情况我们可以用const修饰指针变量加以限制
位置
在修饰指针变量时,const可以放在*的左边或右边
放在*右边
当放在*右边时,该指针变量的本身就被限制,不能指向其他变量
例如
int a=1;
int b=2;
int * const p=&a;
p=&b;//报错,因const限制了p
在上面的代码中,p=&b报错,因为p本身已经被const限制,无法指向除a以外的变量
假设a的地址为0x0011ff00,b的地址为0x0011ff04
那么p存放的内容就是0x0011ff00,不能改为0x0011ff04
但此时能通过解引用(*)来修改指针指向的内容
即p不能变,但是*p可以变!!!
例如
int a=1;
int * const p=&a;
*p=10;
printf("%d",a);
可以看到a的值被更改
放在*左边
当const放在*左边时,该指针可以指向其他变量,但不能通过解引用(*)修改其指向的值
int a=1;
int b=2;
int const * p=&a;
p=&b;
程序可以正常运行
但通过解引用(*)进行修改时则会报错
int a=1;
int const * p=&a;
*p=10;//报错
上面程序中*p=10报错
即p能变,但是*p不可以变!!!
同样可以看const右边的内容来区别
const * p时,*p不能变;const p时,p不能变
两种情况相反!!!大家要根据实际应用场景选择合适的修饰位置
*左右都放
也可以在*左右都加上const
此时不论是p还是*p都不能进行更改!!
指针的运算
1.指针 + - 整数
打印数组时可以通过指针加减整数来实现
由于数组的地址在内存中是连续的,我们便可以通过指针加1的方法来依次找到数据对应的地址
PS:数组名就是数组首元素的地址,如:数组arr,则arr = &arr【0】
#include<stdio.h>
int main()
{
int a[5] = { 1,2,3,4,5 };
int* p = &a[0];
int sz = sizeof(a) / sizeof(a[5]);
for (int i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
在上面代码中我们将p指向数组第一个元素的地址,随后依次加上i来指向后面的元素
注意:在打印时若写成*p+i,打印的值就会变成*p的值再加上i
2.指针-指针
指针-指针=两个指针之间的元素个数
指针也可以相加,但通常情况下没有意义
#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;
}
在上面代码中,p1是a【0】的地址,p2是a【4】的地址,两者相减得到之间的元素个数
注意:该计算方式的前提是两个指针指向了同一块空间!
3.指针的关系运算
指针的关系运算就是指针和指针比较大小,也就是地址和地址比较大小
我们可以通过指针的关系运算来打印数组内容
#include<stdio.h>
int main()
{
int a[5] = { 1,2,3,4,5 };
int* p = a;
int sz = sizeof(a) / sizeof(a[0]);
while (p < sz + a)
{
printf("%d ", *p);
p++;
}
return 0;
}
在上面的代码中,我们就是通过“p<sz+a”这个关系式进行循环来打印数组内容
野指针
概念
野指针是有随机指向位置的指针
成因
1.指针未初始化
#include<stdio.h>
int main()
{
int* p;
*p = 1;
return 0;
}
在上面的代码中,由于指针p没有初始化,成为野指针,在此时打印p的地址时会出现随机值(部分编译器会检测到p没有初始化而编译出错)
2.指针越界访问
#include<stdio.h>
int main()
{
int a[5] = { 1,2,3,4,5 };
int* p1=a;
int* p2 = p1 + 5;
return 0;
}
上面的代码中,由于a数组只有5个成员,但p2指向了p1后面第5个不在数组内的地址,p2就成为了野指针
3.指针指向的空间被释放
#include<stdio.h>
int* f()
{
int a = 1;
return &a;
}
int main()
{
int* p = f();
printf("%d", p);
return 0;
}
在上面的代码中f函数向p传递了a的地址,但是这块地址在f函数调用过后被释放,p就成了野指针,此时再通过p访问该地址,就是非法访问
就好比出门旅游向酒店订了一间房,房号为401,一天后退房,此时再想对401访问就是非法的
规避方法
1.指针初始化
若明确知道指向地址,则直接初始化指向地址
若不知道指向地址,就指向NULL空指针(此时无法使用该指针)
2.小心指针越界
使用是注意指针申请内存大小,不要超出界限
3.指针不再使用时,指向空指针
在一段程序中,若一个指针在后面没有用处时,记得把它指向NULL
4.指针使用前检查有效性
使用前查看指针是否无指向地址或指向NULL空指针
5.避免返回局部变量的地址
在程序中不要main函数中的指针不要返回其他局部函数的地址
传值调用和传址调用
传值调用
假设我想要写一个函数交换数字
#include<stdio.h>
void f(int b,int c)
{
int temp = b;
b = c;
c = temp;
}
int main()
{
int a = 1, b = 2;
f(a, b);
printf("%d %d", a,b);
return 0;
}
可以看到a和b的值并没有被交换
原因是因为在运行f函数时,传递的是数值
f函数在运行时,另外创建了两个int型数据b和c来接收a和b的值,即使在函数里交换了两者的值,也不会影响到主函数中a和b的值
在只需要传值来计算时可以采用传值调用
传址调用
同样还是以交换数字的函数举例
#include<stdio.h>
void f(int* x,int* y)
{
int temp = *x;
*x = *y;
*y = temp;
}
int main()
{
int a = 1, b = 2;
f(&a, &b);
printf("%d %d", a,b);
return 0;
}
可以看到同样的函数在使用传址调用后就成功让a和b交换
这是因为在运行函数时,调用的是a和b的地址
在函数中,对传来的指针进行解引用(*)就可以做到直接修改a和b的值
从而做到交换a和b
在需要更改传递的值时,要采用传址调用