目录
一,了解内存和地址
二,指针变量
1,指针变量的表示和大小
1,指针变量的创建和使用
2,指针变量的大小:
2,解引用操作符(*)
3,不同指针变量的意义
1,指针解引用
2,指针加减整数
4,特殊指针:void*
5,const修饰指针
1,const修饰的常变量
2,const修饰的指针变量
三,指针运算
1,指针+-整数
2,指针-指针
3,指针的关系运算
四,野指针
1,指针未初始化
2,指针越界访问
3,指针指向的空间释放
4,规避野指针
五,assert断言
六,指针的使用和传址调用
1,模拟实现strlen函数
2,传值调用和传址调用
补充:进制转化
1,任意进制转化为十进制
2,十进制转化为2进制
3,二进制转化为 8进制和16进制
一,了解内存和地址
再引出指针前,我们要先了解一下计算机的内存和地址。
在计算机中内存有8GB/16GB/32GB等等,这些内存被分为一个个的内存单元。(一个内存的大小为:1字节),一个字节就是8个比特位。其中每个内存单元都有一个编号,这个编号就是内存的地址,也是指针。
编号=地址=指针
二,指针变量
1,指针变量的表示和大小
1,指针变量的创建和使用
int a = 10;
int* p = &a; //p为指针变量
补充:若a为char类型,则p前面的类型为char*,其他同理
分析://&为取地址操作符 &a就是a在内存中分配的地址,传给了指针变量p.注意:由于使用 int创建了4个字节的空间,而每个字节都用对应的内存编码,这里&a表示四个地址中最小的地址。
//int*为p的类型 *表示p为一个指针变量,int表示p所存地址的对象为一个整型。
2,指针变量的大小:
指针的大小取决于地址的大小
32位平台下地址是32个bit位 (即4个字节 )
64位平台下 地址是64个bit位(即8个字节)
2,解引用操作符(*)
int a = 10;
int* p = &a;
*p =5;
printf("%d\n",a);
执行结果:5
分析:说明*p就是a变量。*p表示p指向的对象。
3,不同指针变量的意义
1,指针解引用
x86(32位)环境下:
在计算机的存储当中有大小端存储模式:
小端:低位字节存放低地址,高位字节存放高地址
大端:高位字节存放低地址,低位字节存放高地址
而VS下的不论是x86还是x64环境都使用的是小端存储模式
11位于高位字节,地址就比较高
由上图可知:
*ps将n的内容全部转化为0,而*pi只将最后一位转化为0,本质上是ps是int*类型的指针变量可以访问4个字节,而pi是char*类型的指针变量,只可以访问1个字节。
2,指针加减整数
由上图可知:由于int类型占4个字节,int*类型+1后直接跳过了4个字节,而char只占1个字节char*类型+1只跳过了 1个字节。
这就是不同指针变量类型带来的差异。指针的类型决定了走一步的大小。
4,特殊指针:void*
void*指针为无具体类型的指针(或者叫做泛用指针),这种地址可以用来接收任意类型的地址。但是也有局限性:void*指针不能进行解引用和指针加减整数。
原因:void没有指定类型,导致在解引用时,无法确定访问的字节数,同理:在进行加减整数时,无法确定跳过的字节数。
作用:void*指针一般使用在函数参数部分,用来接收不同类型数据的地址。
5,const修饰指针
1,const修饰的常变量
上述操作只锁住了a变量,但是没有锁住a变量的地址,要想让a的值彻底不能修改,需要即使让p拿到了地址,也不能修改a的值。如何才能实现此操作呢?
由此我们引入const修饰的指针变量
2,const修饰的指针变量
3种形式:
1,int* const p=&a;
2,int const *p = &a;
3,int const * const p = &a;
注意:p的三个内容
1,p表示a的地址 2,*p表示a的值 3,p还有自己的地址&p
这三种形式表示不同的含义:
总结:在const后面的内容不可改变。
三,指针运算
指针运算有三种分别是:
1,指针+-整数
2,指针-指针
3,指针的关系运算
1,指针+-整数
int a = 10;
int* p = &a;
(type)*p:跳过的sizeof(type)取决于*p前的类型;
p+1:p+1*sizeof(type)
p+n:p+n*sizeof(type)
指针+-整数的运用:
打印数组中的每一个元素:
2,指针-指针
指针+整数= 指针
故:指针-指针=整数
整数为两个指针之间的元素个数,可以为正,也可以为负。
展示:
2,利用指针-指针得到字符串的长度
注意:没有指针+指针的形式:这是一种没有意义的形式
3,指针的关系运算
指针的关系运算就是:比较指针的大小即比较地址的大小
运用:
当p的地址小于arr+sz时,就一直执行。
四,野指针
野指针就是指针指向的位置是不可知的(随机的,不确定的,没有明确限制的)
造成野指针的三种情况:
1,指针未初始化
2,指针越界访问
3,指针指向的空间释放
4,规避野指针
1,指针初始化:如果知道指针明确指向哪里就赋值,如果不知道指针指向哪里就给指针初始化为NULL。
NULL是C语言规定的一个标识符常量,值为0,也就是说地址为零,不可以进行使用。
2,小心数组越界
不能超出数组的访问范围
3,避免返回局部变量的地址
五,assert断言
语法:assert(表达式 )
若表达式的执行结果为1,则继续往下执行,若表达式的 执行结果为0,则终止报错,并显示错误信息:断言失败:表达式,以及在哪个文件下哪一行。
注意:assert断言使用时必须包含头文件#include <assert.h>
使用assert的好处:使用assert不仅可以在出错误时显示出错误的信息,还可以随时对assert断言进行开启和关闭。如果不需要断言时,在#include <assert.h>的前面输入#define NDBUG
运用:
1,判断是否为空指针
2,表达式判断
assert断言失效,如果想要重新开启assert对#define NDEBUG进行注释或删除即可。
assert的唯一缺点是由于引入了assert,会增加程序的运行时间。
注意:assert断言只能在Debug中使用,不能在Release中使用,在Release中会直接失效。
六,指针的使用和传址调用
1,模拟实现strlen函数
分析:strlen的返回值类型是:size_t,表示无符号的整型。(使用int也可以)
通过使用const和assert断言使得代码的健壮性得到了提高。
2,传值调用和传址调用
利用函数实现对两个整数的交换
1,传值调用:
分析:函数执行前后a,b的值并未交换,原因:a和b向函数传入的只是两个数值,x和y接收的也仅仅是两个数。x和y在函数内进行了交换。出函数时销毁(无意义)。a和b并未和x,y建立真正的联系。这种情况称为传值调用。
2,传址调用:
分析:通过传地址的形式,使得a,b和x,y建立了真正的联系。通过对地址内值的修改,实现了交换。
总结:实参传给形参时,形参时实参的一份临时拷贝,对形参的修改不会改变实参。
补充:进制转化
1,任意进制转化为十进制
公式:每一位的数字*该进制^(位数-1次幂) 对此求和
例如:十六进制:ee 转化为10进制数:
15*16^(2-1)+15*16^(1-1)=255
2,十进制转化为2进制
例如:125
3,二进制转化为 8进制和16进制
由于8进制的每一位的范围为0~7,而二进制三位000到111的范围刚好是0~7,于是一个八进制为和三个二进制位进行匹配。
步骤:将二进制从后往前每三个一组进行划分,将每一组的数字按照十进制转化并填写到8进制内
转化为16进制的原理和步骤与上述转化为8进制的步骤异曲同工: