C语言指针初阶

指针是什么?

       我们先来讨论一下地址,生活中我们有收货地址、学校地址和自己家的地址,这些地方都有对应的编号也就是——地址,那么换位思考一下,计算机是不是也有地址的概念,我们程序运行需要占用内存空间,每一个变量也有自己对应的地址编码,如果我们能知道这些地址的话,是不是也就找到了这个变量。因此指针其实就是用来存放地址的,指针的原名叫做指针变量,准确来说,指针是存放地址的变量。
       既然指针是用来存放地址的变量,那地址的大小也就是指针的大小了,在计算机里,32位机器下,地址为32个比特位,也就是4个字节64位机器下,地址为64个比特位,也就是8个字节。因此指针的大小是根据对应的机器来指定的。

指针的操作符

       有两个操作符是使用指针常用的,一个是取地址操作符&,还有一个是解引用操作符 *
       这两个操作符顾名思义,&就是取变量的地址,* 就是通过指针来找到对应的变量来进行操作的。
简单看一下使用:
C语言指针初阶_第1张图片

指针的类型

       和变量常量一样,指针也有自己的数据类型,例如 int*、char*、float*、double* 等等。使用这些数据类型也很简单,就是你取得变量是int 类型的,那指针类型就是int*,同理可得,如果是取float的变量地址就用float*
       我们来拆分一下这个数据类型,以int* 为例,首先int 说明这个指针存放的是int 类型的数据的地址,* 这个星号说明这是一个指针变量。,以此类推…
       既然指针的大小是一样的,那这些数据类型为什么会不一样呢?存在即合理,首先从变量入手,变量不同的数据类型也就决定了这个变量的大小,指针存在的意义不只是用来存放这个变量的地址,而是要通过这个地址来找到对应的变量,对这个变量进行解引用操作,注意解引用操作是需要对内存空间进行操作的,如果每个指针没有别的区别,怎么来知道要解引用多大的字节空间,要知道对内存进行访问的,是最害怕越界访问的,如果超出这个变量的范围进行解引用操作,就很有可能会修改了别的变量,引发雪崩式的反应,所以这些int*、float*、double* 这些不是用来摆设的,而是规定这个指针能访问多大的空间大小,如果大了就是越界访问,如果小了,就可能无法得知完整的变量,连完整的变量都不得而知,那你使用指针就没有意义了。
C语言指针初阶_第2张图片
刚开始:
C语言指针初阶_第3张图片
解引用后:
C语言指针初阶_第4张图片
C语言指针初阶_第5张图片
由于使用char* 接收int类型的变量,所以指针只能改变char(一个字节)的大小,无法改变整个int 类型的数据。

指针运算

       首先指针的运算有两种:①指针 + - 整数 ②指针 - 指针       为什么没有指针+指针呢?那地址加地址有什么意义吗?

指针 + - 整数

       这个很简单,指针对应的类型就是个基本单位,加几就是往高处走几个基本单位,减几就是往低处走几个基本单位。以数组为例:

C语言指针初阶_第6张图片

pa加几就会往后访问对应的数组:

C语言指针初阶_第7张图片

指针 - 指针

       指针减指针得到的是元素个数。以下面模拟strlen 为例:
C语言指针初阶_第8张图片

野指针

       野指针就是指针指向的位置是不可知的(随机的、不确定的、没有明确限制的)

野指针的成因

       ①指针未初始化:
C语言指针初阶_第9张图片

没有给 p 指定对应的变量地址,如果去解引用的话,会对内存进行非法访问,这是很危险的,也是不允许的。

       ②指针越界访问:

C语言指针初阶_第10张图片

指针越界访问数组时,这个指针就是野指针了,指针访问到了数组以外的空间。

       ③指针指向的空间释放:
C语言指针初阶_第11张图片

test函数在调用之后就会将空间释放,还给了操作系统,如果用指针来接收已经释放了变量 a 的话,就是野指针,也是越界访问了,因为 a 已经不存在了。

如何避免野指针

       ①指针初始化:

int* p = NULL;

       ②小心指针越界
       ③当指针变量不再使用的时候,就要及时置空
       ④避免放回局部变量的地址,例如上面指针成因③

const 修饰指针

       const 是会让某个值不能修改,我这里直接放结论:

const 如果放在* 的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变,但是指针变量本身是可以修改的。
C语言指针初阶_第12张图片

const 如果放在* 的右边,修饰的是指针变量本身,保证指针变量自己不能改变,但是指针指向的内容可以改变。
C语言指针初阶_第13张图片

如果想两个都不能更改的话,就是这样的:

int a = 10;
int const* const p = &a;

这里有一个好记的方法:const 在谁前面谁就不能修改。

assert 断言

       assert.h头文件定义了宏assert(),用于在运行时确保程序符合指定条件,如果不符合就会终止程序运行,这个宏常常被称为“断言”。

assert(p != NULL)

       上面的代码在程序运行到这一行语句的时候,就会验证 p 是否等于NULL,如果不为NULL时,程序就会终止运行,并且报出错误信息提示,会告诉你具体的报错的位置,如果没有错误则会继续运行。
       当不需要assert的时候,我们只需要在#include 前面加上#define NDEBUG,就不需要一句句的删除或注释掉。

传值调用和传址调用

       传值调用就是将数值传入到函数内部,相当于拷贝一份临时变量。

void test(int n)
{

}

int main()
{
int a = 10;
test(a);

return 0;
}

传址调用就是将变量的地址传入到函数内,函数就可以通过指针来改变相应的变量或者使用这个变量。

void test(int* p)
{

}

int main()
{
int a = 10;
int* p =&a;
test( p );

return 0;
}

你可能感兴趣的:(初始c语言,c语言)