指针中存的是地址,地址是一个内存单元的编号,通过这个地址我们就可以直接访问存放于地址中的元素了 ,地址由16进制数组成
一个内存单元的大小最好是一个字节
指针是个变量,用来存放内存单元的地址(编号)
存放在指针中的值都被当做地址处理
指针和指针类型
指针类型的大小不随指针类型的变化而变化
它的大小在32位机下是4个字节,64位机下是8个字节
这时问题就来了,如果指针的大小都是4/8个字节,那我们为什么还要区分不同的指针类型呢?
这时因为指针类型决定了指针进行解引用的时候能访问的空间大小
所有的指针变量都能够申请4/8个字节的空间来存储数据,而不同的指针类型就决定了我们解引用时能够访问和操纵的总的空间大小
不同的指针类型决定了我们能够访问的内存空间大小 -- 多少个字节
而不同指针类型访问空间大小的规定与其去掉 * 后的类型大小一致
如 int* p , 我解引用*p只能够访问对应内存空间中的4个字节
char* p -- 1个字节
double* p -- 8个字节
指针在printf输出的时候,用的始 %p
指针+/-整数时
内存单元是一个字节
指针类型决定了:指针走一步走多远(指针的步长)
int* p ; p +1 --> 4 ; p+1则向后偏移了4个字节的空间,这个4就是指针+1后,地址偏移的步长
指针+1,代表我想访问指针指向的变量的下一个相同变量的地址
比如整型指针p,p+1表示我想访问p指向的整型变量的后面的那一个整型变量的地址,而一个整型变量占4个字节,而我们必须跳过p指向的整型变量才能访问它的下一个整型变量,所以我们要跳过四个字节,
设一个整型变量a,int* p = &a, 其中a被放在四个字节的内存空间中存储着,而我们取的地址则是这四个内存空间中的首个空间的地址,若要跨过这四个空间得到下一个整型的地址则需要加4,加4之后就可以找到我们想要访问的空间啦
char* p ; p +1 --> 1 步长为1
double* p ; p+1-->8
数组名中存放的是首元素的地址
内存空间由内存单元组成,而为指针开辟的内存空间是固定的4/8个内存单元(32/64位),如果只想移动一个内存空间中的一个字节 就用char*来存地址 ,4个用int*
指针类型的作用:1.声明指针移动(+1/-1时)的步长 2. 声明指针在解引用时能够访问和操作的的内存空间的大小
野指针 -- 指针指向的位置不可知
1.局部变量如果不初始化的话,默认被初始话为随机值
2.指针变量如果没有被初始化的话,默认被初始化为随机地址,此时,指针指向的位置不可知,指针为野指针。
3.出现野指针的另一种情况: 指针越界访问
一开始我们将指针初始化为指向数组的一个指针
而当我们通过指针访问数组时,如果越出数组范围(即越界时),指针就会变为野指针
越出指针的指向范围时,即越界时,指针会变为野指针
4.指针指向的内存空间释放
在这里a是一个局部变量,局部变量在函数开始时创建并被分配给内存空间存储,而当函数调用完毕之后,局部变量数据自动删除,编译器向系统申请调用来存放a的内存空间归还给系统 -- 这样一个过程称为内存的释放
也就是说,在上面这个代码中的指针变量p指向的是一个已经被释放的内存空间,但又由于这个内存空间已经被归还给了系统,指针想找但又找不到,这时候就会导致野指针的问题。
若想不出现上述这个野指针问题的话,我们必须保证临时变量在调用完后不被销毁,只有这样才能让存储他的内存空间不会被归还释放
而让临时变量不被销毁的方法就是用static将它修饰为一个静态局部变量。
初始化,不越界,指向释放时置NULL,检查是否有效
数组名中存放的是首元素的地址
当我们不知道指针在创建之后该指向谁,该初始化为谁的话,我们可以将指针初始化(指向)空指针 NULL , 操作方式如上图。
NULL 的定义 #define NULL ( ( void* ) 0 ) 这里是0被强制类型转换为了 void* 类型 “无类型”指针
NULL用来给指针初始化,就相当于给数组初始化的0
另外NUL这个空指针对应的内存空间可以被指向,但不能被访问!!!
当我们不想让指针指向我们给定的数据,或者用来存放指针指定数据的内存空间已经被释放,而我们又不想指针消失而是合法的存在的时候,我们只需要将指针指向 NULL 即无,就能实现上面的两个功能。
指针运算
1.指针+-整数
2.指针 - 指针
3. 指针的关系运算
1.指针+-整数
这里也可以是指针++,注意!在+/-的时候一定不要超出指针的访问导致野指针的出现
- 也可以,+3 / +4 等等也可以,无论怎么操作,最重要的就是不要超出指针的可访问范围
指针加减一个整数等于指针的地址 +- 整数*步长
所有的指针类型的大小都是4个字节,但不同指针类型对应的步长却是不同的,而步长就是用来存储对应变量所需的内存空间的大小。
创建一个变量我们需要通过类型知道我们要开辟一个多大的内存空间,这个内存空间就是对应指针类型的步长,而这个变量的地址就是组成这个内存空间的多个内存单元中的首单元的地址
变量内存空间的首单元的地址+变量类型得来的步长就是我们完整的描述一个变量的 地址 所需的两个参数,而这个两个参数一个由我们给定的类型确定,一个由电脑给定。
而指针的作用就是用来存储变量的地址,而存储变量的地址就需要将上面提到的两个参数存储
于是我们就有了和变量类型对应的指针类型 -- 步长,以及&取地址符号-- 存储变量的内存空间中的首单元地址,有了这两个之我们的指针就可以存储地址了,而的创建就是:
int* p = &a (以整型指针为例)
另外步长也是我们在解引用指针时,从对应内存空间的首单元(由首地址知)开始,我们能够访问的空间大小(由步长知)
数组操作符+数组名+[ 常量表达式 ]下标引用操作符就是一个数组的创立,如果我们没有将数组初始化的话,默认数组中的所有元素被初始化为0.
这就是一个数组,它没有“ = { }”,所以它里面的元素没有被初始化,因此都被默认初始化为0.
指针 - 指针 (就是地址 - 地址)
指针减去指针我们得到的是这两个指针之间的内存空间的个数
假设一个内存空间是一个方块
则地址指向的就是这个方块的最左边
注意:指针与指针的相减只能是在两个同类型指针之间发生,否则的话就会导致计算机出现步长判断冲突。
strlen -- 用来求字符串长度的库函数(\n为终止字符不计数并象征着停止)
将字符串存入数组时,\n也会被存入数组中
指针的关系运算
总结一句,指针就和别的数一样,能够正常的比较大小,作逻辑判断
一定不要出现指针访问超出范围的情况,这回导致野指针报错
这两种编译方法都可以实现同一个功能,但是!!!,我们在实际编码中一定要选第一种编码方式,原因是第二种比较方式不在c语言的标准之中
c语言规定了,允许数组元素的指针与数组最后一个元素后面哪一个内存单元的指针进行比较,但不允许其与数组元素第一个元素之前的那个内存空间的地址进行比较。
指针和数组
数组名是首元素地址,但是有两个例外,分别是:
1.&数组名的时候,我们去取出的是用首元素地址最为自己地址名的整个数组的地址名,此时该地址拥有的步长为整个数组的内存空间大小,一旦+1的话,改变的距离就是 1 * 步长即1*整个数组空间的大小。
值得注意的一点是,数组指针其实是一个二级指针,它里面装着的是(数组首元素的地址)的地址
为什么会这样呢?
这是因为在没有指针变量来装地址的时候,我们默认地址的步长就是其内对象的类型的大小,所以 int arr[ 2 ] 中的 arr 的步长就是 4 ,而数组是一个复合对象,其步长等于其内元素步长之和 -- 8
但是在有指针装这些地址之后,这些地址的步长就会被同步为指针类型所决定的步长
此时对p+1和q+1得到的结果是一致的,因为它们的步长都相等,都为对应
arr 表示的是首元素的地址 --步长和元素类型一致, &arr表示的是用首元素地址作为自己的地址的数组的地址 -- 步长和数组内存空间的大小一致
指针+整数n 加的并不是n,而是n*步长,而指针的步长由其指针类型决定,其中数组是比较特殊的,由于它是多个元素的集合,所以数组地址名步长是它包含的所有元素的步长之和
2.sizeof ( arr ) 此时求的不是首元素地址对应的内存空间的大小,而是整个数组所在的内存空间的大小
上面这个输出的结果是40
sizeof注意事项:
1sizeof 的单位是字节,sizeof的输出类型是%zd 指针的输出类型是%p
我们能够通过指针来访问数组,但数组和指针是不同的,数组能够存储一组数据,而指针只能存储一个地址
int * ,char*...等等指针类型开辟的空间都是4个字节
但是!!! int* 对应的类型是int 的内存空间大小,char* -- char
二级指针
int main()
{
int a = 10;
int* p = &a; 这里的p就是一级指针,而int*就是一级指针类型
而指针也是变量,他也需要内存空间存储,而有内存空间就有地址,所以我们就可以
&p;
取出了指针变量p的地址之后我们要将它存到哪里呢?
答案是存到二级指针变量中,于是
int** pp = &p
int** 其实应该这么来看 --> (int*)* 右边那个*是在告诉我们这是一个指针
而括号内的内容则是这个指针指向的对象的类型!(对象被存储在内存空间中),且对象可被更改
pp就是二级指针变量,int** 就是二级指针变量类型
存放一级指针变量的就是二级指针变量,依此类推,还有三级指针变量...等等
与此相对应的还有一级解引用,二级解引用 *p , **p,对应的解引用只能用在对应等级的指针上
以二级解引用为例分析多级解引用逻辑
**p , 首先对二级指针解引用找到一级指针的地址,然后对二级指针指向的一级指针,然后对一级指针解引用找到其指向的存在内存空间中的对象
return 0;
}
指针数组 -- 是一个数组 (指针是数组的修饰词)-- 是存放指针的数组
int* arr[ 10 ] = {&a,&b,&c};
指针数组:存指针的数组
数组指针 -- 是一个指针 -- 指的是数组名的指针,即指向数组中首元素地址的指针
后续补充 -- 已经知道一个地址的对应指针类型,但是我们如果想用与这个地址对应的指针不同的指针来存储这个地址的话,我们需要用到强制类型转换 (转换指针类型)
如上面的arr就是强制类型转换的结果
强制类型转换带来的结果就是地址的步长发生了改变,地址由原对应指针类型给的步长变为强制类型转换后的新的指针类型给的步长