内存与地址
学习指针,我们首先要了解指针,指针是用来访问内存的。举个例子,就像过节走亲访友,首先我们要知道地址,才可以去拜访。那么指针的作用就是地址,用于以后可以轻松的访问到内存。也可以这么理解:内存的单元编号==地址==指针。
指针变量与地址
&,在C语言中,名为取地址的操作符,例如:
int main()
{
int a=0;
printf("%p",&a);
return 0;
}
这样便可以得到a的地址。
得到的地址p也是一个变量,其中存放的是a的地址。那我们该如何定义这种变量呢?
指针类型
定义指针首先要了解指针的类型,有int型,有char型,还有float型......
其次我们还要知道一个解引用的操作符(*)
//整型
int a=20;
int*p=&a;
指针变量的大小
有地址总线决定,我们知道,计算机有32位也有64位,所以指针的大小也有两种,四个字节和八个字节,其中32位有32个比特位,也就是4个字节,64位则是8个字节。而且指针的大小与前面说的指针的类型不同,指针的大小只和平台有关,与指针的类型无关。
指针类型的意义
指针的类型决定了对指针解引用时的权限大小(一次可以操作几个字符)
例如;char*的指针只能访问一个字节,int*的指针可以访问四个字节。
指针+-整数
上面我们说了char*与int*的区别,所以他们+-一个整数的结果也是不一样的。
char*类型的指针+1跳过一个字节,而int*类型的指针+1跳过4个字节。
所以结论:指针类型决定了指针向前或者向后走一步的距离大小。
void*指针
上面介绍的指针在定义时我们知道其类型,而如果我们并不知道的话,便可以用void*指针,
void*指针可以理解为无具体类型的指针(泛型指针)可以接受任何类型的地址,但是这类指针并不可以直接进行+-整数的操作,也不可以进行解引用的操作。所以我们一般使用在函数参数的部分,用来接收不同类型数据的地址,实现泛型编程的效果。
const修饰指针
const修饰变量
通过使用const修饰,可以使一个变量具有常属性,但本质上还是变量(在c++中const修饰后变量会变成常量)具有常属性之后,这个变量变得不可被修改,但是通过指针我们依旧可以改变存在一个变量里面的值,如:
可以看出n的值改变了,这是不符合const限制的,所以是不合理的,那么如何让指针也无法改变const定义后的变量的值呢?
const修饰指针变量
如果const放在*左边修饰的是指针内容,指针内容就不会发生改变。但是指针变量本身可以改变。
运行后我们可以发现,n所指的值还是10,即所存的数值没有改变。但是p的指向改变了。
int main()
{
int n=10;
int m=20;
int const * p=&n;
*p=20;
p=&m
}
相反的,如果放在*右边的话,p中所存放的值会发生改变,而p的指向并不会发生改变,*p不变。
如果*前后都有const限制的话,那么两个值都不会发生改变。
指针运算
分为三种:指针+-整数;指针-指针;指针的关系运算(指针比较大小)。
注意:指针-指针的运算方式的两个指针指向同一块空间。
野指针
概念:就是指针指向的位置是不可知的(随机的,不正确的,没有限制的)
产生野指针的原因
1.指针未初始化
int main()
{
int * p;//局部变量未初始化,默认为随机值
*p=20;
return 0;
}
2.指针的越界访问
如出现在数组当中,超出数组的大小。
3.指针指向的空间释放
不能返回局部变量的地址。
assert断言
assert()括号内只要是表达式都可以,如果表达式的结果为假,assert()就会报错,使程序的执行中止。可以用来判断一个指针是否是一个空指针。
#include<assert.h>
是他的头文件。
传值调用和传址调用
传址调用,可以让函数和主调函数(不是主函数,是自己创建的函数)之间建立真正的联系,,在函数内部可以修改主调函数中的变量;所以未来函数中只是需要主调函数的变量值来实现计算,就可以采用传值调用(人话:只用不改变)如果函数内部要修改主调函数中变量的值,就需要传址调用。
结语:关于指针的学习并没有结束,后续还会有更多关于指针的介绍。