C语言-指针1(基础,原理,详细)

目录

一,了解内存和地址

二,指针变量

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个比特位。其中每个内存单元都有一个编号,这个编号就是内存的地址,也是指针。

编号=地址=指针

在VS编译器中有两中环境:一种是X64,表示64位的环境,另一种是X86,表示的是32位的环境。
以X86(32位)的环境为例子:在32位的环境中,内存都为32位的二进制,只不过为了方便表示,转化为16进制进行表示。
细节分析:在电脑中有32根地址线,每根地址线有两种状态,分别是有无电信号脉冲(1/0),这32根地址线就可以产生2^32中情况,对应着二进制地址中每一位。于是就可以产生2^32种地址存储内容(与int的可能取值相同),类似在64位环境下,地址有2^64种取值可能。
补充:在栈上开辟空间是由高地址向低地址开辟,在数组上开辟空间是由低地址向高地址处开辟。

二,指针变量

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进制的步骤异曲同工: