深入理解指针(1)

目录

1.内存和地址
2.指针变量和地址
3.指针变量类型的意义
4.const修饰指针
5.指针运算
6.野指针
7.assert断言
8指针的使用和传址调用

1.内存和地址

补充:计算机中的单位(从小到大)
bit (比特位)Byte (字节)KB MB GB TB

1Byte=8bit
1KB=1024Byte
1MB=1024KB
以此类推

1.1内存

1.内存划分为一个个的内存单元,每个内存单元的大小是1个字节。

2.每个内存单元都有一个编号,有了这个编号,cpu就可以快速找到一个内存空间。

3.内存单元的编号== 地址 ==指针

2.指针变量和地址

地址打印用%p

2.1取地址操作符&

int main()
{
int a=20;
//变量创建的本质就是在内存中申请空间
//向内存申请四个字节的空间存放20这个数值
//(int)大小为四个字节
//这四个字节都有编号(地址)
//变量的名字仅仅是给程序员看的,编译器不看名字
//编译器是通过地址找内存单元的
&a;//拿到变量a的地址
//拿的是第一个地址
return 0;
}

2.2指针变量

那我们可以不可以 将a的指针存放进另一个变量中呢?答案是可以的,直接上图:
在这里插入图片描述
如上图,我们就成功的把a的指针存进了类型为int*的变量pa中,那么这个变量也叫做指针变量

由此我们也得到了指针变量的概念:顾名思义,用来存放指针的变量。

小总结:
1.*表示pa是指针变量
2.int表示pa指向的变量a的类型是int .
举例 如果我们想创建一个char类型指针变量该怎么写呢?(下图a)

char b=0;
char*a=&b;
int main()
{
int a=20;
int*pa=&a;
*pa=200;//*就是解引用操作符
//(间接访问操作符)
//pa里面存的是a的地址,*pa就相当于
//通过pa中的地址找到a,
//甚至我们可以把*pa看作a,那么上述表达
//式就相当于把200赋给了a。
return 0;
}

2.3指针变量的大小

指针变量的大小取决于地址的大小。

如果是32位机器,有32根地址总线,存放一个地址就需要32个比特位,那一个指针变量大小也就是4个字节。

如果是64位机器,那一个地址就需要64个比特位,那一个指针变量大小也就是8个字节。

x86是32位,x64是64位。

指针变量的大小与数据类型无关。

3.指针变量类型的意义

3.1指针的解引用

答案:指针的类型决定了,对指针解引用的时候有多大权限(一次能操作几个字节)
比如:char的指针解引用只能访问一个字节,而int的解引用就能访问四个字节。

3.2指针 ±整数

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

3.3void*指针

在指针类型中有一种特殊的类型是void类型,可以理解为无具体类型的指针(泛型指针),这种类型的指针可以用来接受任意类型地址,但是也有局限性,void类型的指针不能直接进行指针的±整数和解引用的运算。

4.const修饰指针

4.1const

const修饰的变量常变量,本质上还是变量,只是不能被修改。

int main()
{
int num=100;
num=200;
printf("%d\n",num);
return 0;
}

上述代码中num作为一个变量,它的值是可以改变的,但是如果我们不希望这个变量的值被篡改,那就可以在前面加上const。
在这里插入图片描述
如上图,这样编译器编译的时候就会认为num是一个常量(具有了常属性),无法修改。

但是:我们不能通过对num直接赋值来改变,但是可以通过指针来改。

int main()
{
const int num=100;
int*p=#
*p=200;
return 0;
}

这样num的值确实被修改了,但是我们还是要思考一下,我们用const修饰num就是为了不能被修改,如果通过指针修改就打破了const的限制,这是不合理的,所以应该让p拿到num的地址也不能修改num ,那怎么做呢?

4.2const修饰指针变量

一般来讲const修饰指针变量,可以放在**左边,也可以放在*右边,意义是不一样的。

int main()
{
int a=10;
int*p=&a;//将a的地址放入p
p//指针变量,里面有a的地址
*p//p指向的对象,即a
&p//p变量的地址
return 0;
}

int*p;//没有*修饰constint const *p;//放左边修饰
int *const p;//放右边修饰

放在左边: (const) int const p;
限制的是
p即指针指向的内容(上图的a),也就是说不能通过指针变量来修改它所指的内容。

但是指针变量本身是可以改变的,比如一开始存储的是变量a的地址,我仍然可以对存储的内容进行修改,比如换成b的地址。

**放在右边:**int * const p;
限制的是指针变量本身,指针不能改变它的指向(里面存的地址不能改了

但是可以通过指针变量修改它所指向的内容,(比如里面放的a的地址,我依然可以*p=常数改变a的值)

5.指针运算

指针的运算有三种:
指针±整数
指针
指针

5.1指针±整数

因为数组在内存中是连续存放的,只要知道第一个元素的地址,顺藤摸瓜就能找到后面的所有元素。

我们可以利用指针±来打印数组,如下:
在这里插入图片描述

5.2指针-指针

指针-指针的绝对值是指针和指针之间元素的个数

int main()
{
int fxk[10]={1,2,3,4,5,6,7,8,9,10};
printf("%d\n",&fxk[9]-&fxk[0])
return 0;
}

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

在这里插入图片描述

5.3指针的关系运算

在这里插入图片描述

6.野指针

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

int*p;
*p=20;//p就是野指针。

图6.1
如果把指针比作狗,那野指针就是心中没有装着主人(地址)的野狗。

6.1野指针成因

1.未初始化
如上图6.1,如果不初始化,p存放的值就是随机值,这时候解引用这个值(即地址)会造成非法访问。
2.指针越界访问

int main()
{
int fxk[10]={0};
int*p=&fxk[0];
int i=0;
for(i=0;i<=10;i++)
{
*p=i;//当p超过了数组的范围
//(申请的空间)就是野指针。
p++; 
}

return 0;
}

3.指针指向的空间释放
在这里插入图片描述

6.2如何避免野指针

6.2.1指针初始化

如果明确知道指针指向哪里,就直接赋值,如果不知道,可以赋值为NULL.

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

为什么要这么做呢,你赋 了值指针变量里面起码不是随机值

6.2.2小心指针越界

你访问的值一定要是在申请空间内,不能超出范围访问。

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

7.assert断言

头文件:assert.h
作用:用于在运行中确保程序符合指定条件,如果不符合,就终止运行。
assert后面通常为一个表达式,如果表达式成立则无事发生,如果不成立则终止程序并报错。
如下例:

assert(p!=NULL)

上图中表达式意思为指针不为空,那使用assert的作用就是如果p不为空则无事发生,如果p为空,则报错并终止程序。

如果不需要assert来检查代码了,即确定代码没有错误,也不需要删除assert,只需要在头文件前面加上#define NDEBUG就可以了,它可以说是assert的“开关”,当加上这句话时,整个代码中的assert就全部失效。

#define NDEBUG
#include<assert.h

8.指针的使用和传址调用

8.1strlen的模拟实现

库函数strlen的功能时是求字符串长度,统计的是字符串\0前的个数。

8.2传值调用和传址调用

学习指针是为了解决问题,那么什么问题非指针不可呢?

8.1.1输入a,b的值然后进行交换

在这里插入图片描述图1
这样的代码看似很完美,但实际运行的时候你就会惊喜的发现:你换了寂寞

原因:当实参传递给形参的时候,形参是实参的一份临时拷贝,对形参的修改不会影响实参
所以你在函数中对a,b嘎嘎一顿修改,没有用,对主函数中的a,b不会有任何影响。

在这里插入图片描述图2
如上,通过指针进行修改就可以确保你做的是”有用功“。
图1就是传值调用,图2就是传址调用,

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

如果只是想需要主调函数中的变量值来进行计算,就可以采用传值调用,如果函数内部要修改主函数的中的变量的值,就需要传址调用。

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值