C语言总结第七弹(深入理解指针上)

壹 内存和地址

1.1 内存

计算机中常见的单位(补充):

一个比特位可以存储一个二进制的位1或者0

概念类比:

所以我们可以理解为:内存单元的编号 = 地址 = 指针

1.2 如何理解编址

计算机中的编址,并不是把每个字节的地址记录下来,而是通过硬件设计完成的。

总结:

① 内存被划分为一个个单元,一个内存的大小是一个字节。

② 每个内存单元都给一个编号,这个编号就是地址,C语言中把地址又称为“指针”。

贰 指针变量和地址

2.1 取地址操作符(&)

#include<stdio.h>
int main()
{
    int a = 10;
    return 0;
}

上示代码的表意是创建一个变量a,并赋值为10;深层是在内存上申请4个字节的空间,存放10

int * pa = &a;

pa 是指针变量—— 用来存放地址——地址又被称为指针,指针变量是用来存放指针的

指针——指针变量

地址——变量:存放地址

口语中所说指针一般是指针变量

2.2 解引用操作符(*)

*pa = 20;  // * 是解引用操作符,*pa 等价于 a

有时直接操作不够方便,就把 a 的地址信息交给指针 pa ,用 *pa 来改变 a 的地址,看起来貌似 &(取地址) 和 *(解引用操作符) 是一对

二者关系:一来一去,拿到地址可以解引用

2.3 指针变量的大小

1. 指针变量是专门用来存放地址的,那么指针变量的大小是多少呢?

答:取决于一个地址的存放需要多大空间。

举例:32位机器上:地址线是32根,地址的二进制序列就是 32bit 位,要把这个地址存起来,需要4个字节的空间,也就是 32bit 位的空间。

所以,

32位机器上指针变量的大小都是4个字节,64位同理(8个字节)

指针变量的大小和类型是无关的,只要是指针变量,在相同的平台下,大小都是相同的。

叁 指针变量类型的意义

结论:指针的类型决定了对指针解引用的时候有多大的权限(即:一次能操作几个字节)

比如:char* 的指针解引用就只能访问一个字节,而 int* 的指针解引用一次就能访问四个字节,两个字节是 short* ;八个是 double* 

3.1 指针 +/- 整数

结论:指针的类型决定了指针向前或者向后走一步有多大(距离)

%p 是专门用来打印地址的。

3.2 void* 指针

void* 指针可以理解为无具体类型的指针(或者叫泛指针),这种类型的指针可以用来接收任意类型地址,但是也有局限性

void* 类型的指针不能直接进行指针的 +/- 整数解引用的运算,即无法直接进行指针运算

那么 void* 类型的指针到底有什么用呢?

一般 void* 的指针是使用在函数参数的部分,用来接收不同类型数据的地址,这样的设计可以实现泛型编程的效果,使得一个函数可以处理多种类型的数据。

肆 const 修饰指针

const 意为常属性(即不变的属性)

#include<stdio.h>
int main()
{
    int a = 10;
    a = 20;

    return 0;
}

 

上示代码的 a 值可以随意更改,但倘若如下代码使用了 const :

#include<stdio.h>
int main()
{
    const int a = 10;
    a = 20;
    
    return 0;
}

 

 编译器就会报错,所以 const 类似保护 a 的警察,a 的本质上还是自由的(还是变量),const 仅仅是在语法上做了限制,所以我们习惯上把 a 叫做常变量。

不太好的写法:

该行为其实很危险,所以我们可以写成如下形式:

这样编译器就会报错了。

const 修饰指针的时候,const 可以放在 * 的左边,也可以放在 * 的右边。

4.1 const 放在*左边

实际上,

const int* p = &a;

int const* p = &a;

效果是一样的,只要 const 放在 * 的左边

 const int* p; 限制的是 *p,意思是:不能通过 p 来修改 p 指向空间的内容

const 放在*的左边,限制的是 *p,意思是不能通过指针变量 p 来修改p指向空间的内容,写成 *p = 20; 是错误的,但是 p 是不受限制的,写成 p = &b;是可以的。

4.2 const 放在*右边

int* const p; 限制的是p,

const 放在*的右边,限制的是变量 p ,也就是 p 变量不能被修改了,没办法再指向其它变量了,p = &b;是错误的,但是相应的 *p 就不受限制,还是可以通过 p 来修改 p 所指向对象的内容,*p = 20;是可以的

4.3 三个概念

  • p里面存放的是地址(a的地址)
  • p是变量,有自己的地址
  • *p 是 p 指向的空间

伍 指针运算 

指针的基本运算有三种:

  • 指针 +/- 整数
  • 指针 - 指针
  • 指针的关系运算(例如比较大小)

5.1  指针 +/- 整数

详见上文 3.1指针 +/- 整数

5.2 指针 - 指针

其实就是地址减地址(看的是元素个数,而不是字节个数)

| 指针 - 指针 |(绝对值)得到的是指针和指针之间的元素个数

指针 - 指针运算的前提条件是:两个指针指向同一块空间

举例:

如上代码编译会 warning 说明了如下问题:

  1. 两个数组在内存中到底是什么关系,以及二者之间有没有空隙
  2. 当这两个指针相减的时候,二者之间的元素个数,到底按 char 算还是按 int 算是不确定的。

 类比:指针?地址?——日期

5.2 指针的关系运算

其实就是指针比较大小(地址比较大小)

注:一般在使用数组时,不会把数组大小固定不变,所以一般都会计算:

int sz = sizeof(arr) / sizeof(arr[0]);

注:arr 是数组名,数组名其实是数组首元素的地址

所以 &arr[0] 等价于 arr

陆 野指针

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

6.1 成因

6.1.1 指针未初始化

局部变量如果不初始化,变量的值是随机的

全局变量如果不初始化(相当于静态变量),变量的值默认是0

(因为全局变量和静态变量都是放在静态区的)

6.1.2 指针越界访问

6.1.3 指针指向的空间释放

6.2 如何规避野指针

6.2.1 指针初始化

①:int a = 10;

int* p = &a;  //这里明确知道 p 应该指向 a ,所以拿 a 的地址初始化

②:可能一开始不知道给指针初始化谁的地址,直接赋一个NULL

int* pa = NULL;

NULL 是C语言中定义的一个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写该地址会报错。

#ifdef _cplusplus
    #define NULL 0
#else
    #define    NULL ((void*)0) //强制类型转换0为一个空指针类型(地址值是0)
#enddif    

6.2.2 小心指针越界

6.2.3 指针变量不再使用时,及时置NULL,指针使用之前检查有效性

6.2.4 避免返回局部变量的地址

......等等 

柒  assert 断言

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

assert( p != NULL );

assert() 宏接受一个表达式作为参数,如果该表达式为真(即返回值非零),assert() 不会产生任何作用,程序继续运行。

如果该表达式为假(即返回值为零),assert()就会报错,在标准错误流 stderr 中写入一条错误信息,显示没有通过的表达式,以及包含这个表达式的文件名和行号

7.1 使用 assert 的好处:

① 它不仅能自动标识文件和出问题的行号

② 还有一种无需更改代码就能开启或关闭 assert() 的机制。(如果已经确定程序没有问题,不需再做断言,就在 #include<assert.h> 语句的前面,定义一个宏 NDEBUG)

#define NDEBUG

#include<assert.h>

根据需要,自由选择是否注释(关闭)或取消注释(启用)assert() 语句。

7.2 缺点

因为引用了额外的检查,增加了程序的运行时间。

一般我们在 Debug 中使用,在 Release 版本中选择禁用 assert() 就行,在VS这样的集成开发环境中,在 Release 版本中直接就优化掉了,这样在 Debug 版本中写有利于程序员排查问题,在Release 版本不影响用户使用时程序的效率

捌 指针的使用和传址调用

函数的调用:

 8.1 strlen 的模拟实现

#include<string.h>

8.2 传值调用和传址调用

传值(即没有指针),传址必然会用到指针

传值调用函数时,函数的实参传给形参时,形参是实参的一份临时拷贝!

形参有自己独立的空间,对形参的修改不会影响实参!

总结:

传址调用,可以让函数和主调函数之间建立真正的联系,在函数内部可以修改主调函数中的变量;

所以在未来函数中只是需要主调函数中的变量值来实现计算,就可以采用传值调用。

如果函数内部要修改主调函数中的变量的值,就需要传址调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雁澈星月

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值