指针详解(看完直接起飞) 第一部分,分5个部分。(无杂乱图纯享版)

目录

1.内存和地址

1.1首先关于内存和地址,你可以先构思一个公寓楼,里面有很多房间,如果这个公寓很不友好,没把门牌号整上,这天你要去找你的朋友,这样是不是就很麻烦也没效率。

 1.2.怎么理解编址

2.指针变量和地址

2.1取地址操作符(&)

 2.2指针变量和解引用操作符(*)

2.2.1 指针变量

2.2.2如何拆解指针类型

2.2.3 解引用操作符(*)

2.3 指针变量大小

3.指针变量类型的意义

3.1指针的解引用

 3.2指针+ -整数

4.const修饰指针

4.1const修饰变量

4.2const修饰指针变量

5.指针运算

5.1 指针 +- 整数

5.2 指针 - 指针

5.3 指针的关系运算 

6.野指针

6.1野指针的成因

1.指针未初始化。

2.指针越界访问。

3.指针指向的空间已被释放。

 6.2如何规避野指针

6.2.1指针的初始化

6.2.2小心指针越界。 

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

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

7.assert断言

assert的好处:

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

8.1传址调用


1.内存和地址

1.1首先关于内存和地址,你可以先构思一个公寓楼,里面有很多房间,如果这个公寓很不友好,没把门牌号整上,这天你要去找你的朋友,这样是不是就很麻烦也没效率。

2.那如果我们把门牌号搞上那是不是就方便多了,很快就能找到你的朋友。相同的计算机中的 内存就是很像这样一个公寓楼,CPU在处理数据时,它要的数据也是从内存中找到的,将数据处理好后,又将数据送回他的地址房间中。

3.其实内存中也是划分好一个个内存单元的,每个内存单元就是1 个 字节(byte)。

4.下表是内存换算单位呢。

1b yte = 8b it
1 KB = 1024b yte
1 MB = 1024 KB
1 GB = 1024 MB
1 TB = 1024 GB
1 PB = 1024 TB

5.每个内存单元中,就是一个字节(byte)中,想象成是一个宿舍还是个八人间,其中的 8 个 比特(bit)。然后每个内存单元字节是有编号的,就是有门牌号的意思,有了门牌号,cpu就能很快找到想找的内存空间 。

6.门牌号就是 内存单元编号 = 地址 = 指针。指针就是地址!!!

 1.2.怎么理解编址

1.在32位系统中 内存和cpu中有一条地址总线是相连接的,地址总线中有32根小线(64位就有64根),每根线只能表示两个形态 1 或者 0.这样32根线就可以表示2 ^32种含义,地址的信息就被下达给了内存,在内存中就可以找到对应的数据,再将数据通过数据总线传输到cpu中

2.指针变量和地址

2.1取地址操作符(&)

在c语言中创建变量就是向内存申请了空间。如果我们创建了一个 int a = 0变量,要怎么找到他的地址并且利用他呢,就要用到&符号,这个符号可以取出变量a的地址,因为a的类型是int 所以占4个字节,前面的0x十六进制数字就是a的地址,房间号。但打印出来的地址是006FFD70,因为只要有了第一个字节的地址,就可以顺藤摸瓜找到后面的字节的地址。

 2.2指针变量和解引用操作符(*)

2.2.1 指针变量

顾名思义指针变量,就是变量的类型是指针(地址),可以把一个地址储存在一个变量中,这样就方便之后的使用。(因为指针的英文是pointer,所以这里使用p当指针变量)

记住存在指针的值都是地址。

int a = 0;
int* p = &a

2.2.2如何拆解指针类型

 首先int a = 0中,int为变量类型,a为变量名称,=0是将0赋予a。

那同理在 int* p 中 int*为变量类型(所以指针类型的书写就是int*),那p 就是变量的名称,=&a就是把a 变量的地址赋予 p ,p里存着 a 变量所在的地址!!

相同的如果想要存的不是 int 类型的变量,那同理也可以 存 char类型的变量,只要把int*改为char*即可。

2.2.3 解引用操作符(*)

没错解引用操作符也是* 和乘法一样。

那它存在的意义是什么呢,在生活中我们使用地址找到房间,要用钥匙开门去里面取东西或存放。

c语言中也是同理,一个指针中存放了一个地址,那这个地址里的东西我们要怎么取出来并且更改呢?

这里就要用到解引用操作符(*)

int a = 100;
int* p = &a;
*p = 0;
printf("%d ",a);

像上述代码,*p 就代表着 有人照着 p 中给的 地址,找到了 a房间,并且还把里面的100块抢了,所以现在a 的值就变为了0。*p 就是 一把钥匙,可以进入房间拿东西存东西都可以。

2.3 指针变量大小

指针变量的大小是固定的不论你要的类型是char* int* long*.....这些类型在32位系统下都是4个字节

在64位中都是8个字节。这是为什么呢?

还记得刚开始我们说的地址总线吗?因为32位系统中可以提出32根线,32个bit位(比特),就需要4个字节来存,因为1个字节等于8个比特位。

相同的平台下,大小都是相同的。

3.指针变量类型的意义

既然指针变量的大小与类型无关,那只要是指针变量,同一平台下,大小都一样,为什么还要各种各样的指针变量呢?那肯定是有意义的!!

3.1指针的解引用

 观察下列两个代码。一个是int* 一个则强制转换为了 char*。0x的意思是16进制数字。

最后输出我们可以看到,第一个n的4个字节全部改为了0,而第二个强转为char*的n 只有一个字节被改为0

指针的类型决定了,对指针解引用时有多大的权限(一次能操作多少个字节)。如下char*只能修改一个字节,而int*就是4个字节。

//代码1
#include <stdio.h>
int main()
{
 int n = 0x11223344;
 int *pi = &n; 
 *pi = 0; 
 return 0;
}
//代码2
#include <stdio.h>
int main()
{
 int n = 0x11223344;
 char *pc = (char *)&n;
 *pc = 0;
 return 0;
}

 3.2指针+ -整数

先看如下代码,调试观察地址变化。 

 #include <stdio.h>
 int main()
 {
     int n = 10;
     char *pc = (char*)&n;
     int *pi = &n;
 
     printf("%p\n", &n);
     printf("%p\n", pc);
     printf("%p\n", pc+1);
     printf("%p\n", pi);
     printf("%p\n", pi+1);
     return 0;
 }

 结果如下

 可以看出char* 跳过一个字节,而int* 变量+1跳过了4个字节。

所以指针的类型决定了指针+- 时所走过的一步是多大

4.const修饰指针

4.1const修饰变量

变量是可以修改的,如果把变量的地址交给一个指针变量,那通过指针也是可以将变量修改的。

但如果利用const就给变量上个锁,让他不能更改。

 #include <stdio.h>
 int main()
 {
     int m = 0;
     m = 20;//m是可以修改的
     const int n = 0;
     n = 20;//n是不能被修改的
     return 0;
}

 上述变量中n 是不能直接修改的,因为const给 n 变量上了一把锁,钥匙还不见了。导致n不能改

但是如果我们绕过 n 变量的大门,而是从窗户溜进去也是可以的,但怎么溜进去呢?,我们直接跳过n,利用n的地址(窗户),就可以修改n了,但这样做其实是在打破语法规则。

#include <stdio.h>
int main()
{
    const int n = 0;
    printf("n = %d\n", n);
    int*p = &n;
    *p = 20;
    printf("n = %d\n", n);
    return 0;
}

 

 这里看到n的值还是被修改了,这样打破了const的语法规则,既然const就是为了将 n 保护起来才给他上了锁, 所以应该让 n 的窗户(地址),也被保护起来才行。const 病态的爱。那我们要怎么做呢?来帮助这个病态const。

4.2const修饰指针变量

#include <stdio.h>
//代码1
void test1()
{
     int n = 10;
     int m = 20;
     int *p = &n;
     *p = 20;//ok?
     p = &m; //ok?
}
void test2()
{
 //代码2
     int n = 10;
     int m = 20;
     const int* p = &n;
     *p = 20;//ok?
     p = &m; //ok?
}
void test3()
{
     int n = 10;
     int m = 20;
     int *const p = &n;
     *p = 20; //ok?
      p = &m; //ok?
}
void test4()
{
      int n = 10;
      int m = 20;
      int const * const p = &n;
      *p = 20; //ok?
       p = &m; //ok?
}
int main()
{
 //测试⽆const修饰的情况
    test1();
 //测试const放在*的左边情况
    test2();
 //测试const放在*的右边情况
    test3();
 //测试*的左右两边都有const
    test4();
    return 0;
}

 用4个test来测试const放在指针不同位置的时候,对指针的修饰效果。

第一个没有const当然没有任何影响的。

第二个在* 的左边加了const,发现*p (解引用p)是错误的。这就说明了当 const在 * 左边时,const 给 n 上了一个锁,并且把窗子也强化了,所以n 就不能被更改了。

第三个 在* 右边 加了const,发现p = &m,出现了错误,这就说明了,这次不是给 n 上锁了,反而是给 p 这个指针变量上了锁。给他保护了起来,那 p 里存放的地址就不能更改了。

那就说明了 只有 p 里的内容不能更改,但是 n 是没有保护起来的,n 的值还是可以更改的。

第四个在两边都加上了const,那就是 n 和 p 都被上锁,都被 保护了起来,这样两个变量的内容都不能被更改。

总结:如果const 在 *左边,那说明他上的锁是在 p 指针 指向 地址里的内容。那个变量的内容不能更改。

如果 const 在 *右边,那就是p 指针被 上锁了,p 里 存放的内容是不能更改的

5.指针运算

指针基本运算一共有三种,分别是:

指针+- 整数
指针-指针
指针的关系运算

5.1 指针 +- 整数

我们知道数组是在内存中连续存放的,只要知道了第一个元素的地址,就可以顺水推舟的找到剩下的元素。

这里搞了一个数组1 - 10,然后用指针变量p 指向了数组的首元素的地址(&arr[ 0 ])

当然数组的首元素地址也可以直接用数组名。

然后求出数组的里有多少个元素 sz 。

for循环遍历0-i 。输出 *(p + i)的意思是 p是一个指针对吧,那他指向的是个地址,先在给那个地址加个i

记住指针变量的 +- 是根据你定义的指针类型的 int*的话跳过4个字节,char*是一个字节。

所以 p + i 就等于 arr[ i ]。在将其解引用(*)就可以输出数组内容。

#include <stdio.h>
 //指针+- 整数
 int main()
 {
     int arr[10] = {1,2,3,4,5,6,7,8,9,10};
     int *p = &arr[0];
     int i = 0;
      int sz = sizeof(arr)/sizeof(arr[0]);
      for(i=0; i<sz; i++)
  {
      printf("%d ", *(p+i));//p+i 这⾥就是指针+整数
  }
      return 0;
 }

5.2 指针 - 指针

 #include <stdio.h>
int my_strlen(char* s)
{
    char* p = s;
    while (*p != '\0')
        p++;
    return p - s;
}
int main()
{
    char arr[4] = "abc";
    printf("%d\n", my_strlen(arr));
    return 0;
}

 可以自行去调试器中查看结果,结果就是3,所以 指针减指针 算出来的就是 两个指针间(地址间)元素个数。

5.3 指针的关系运算 

//指针的关系运算
#include <stdio.h>
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9,10};
 int *p = &arr[0];
 int i = 0;
 int sz = sizeof(arr)/sizeof(arr[0]);
 while(p<arr+sz) //指针的⼤⼩⽐较
 {
 printf("%d ", *p);
 p++;
 }
 return 0;
}

 指针的关系运算,可以想成就是地址间的比较,因为地址是一个16进制数字,所以就像是两个数字的关系运算。

6.野指针

概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
注意野指针如果指向一个病毒的地址,你解引用时可能就把一个病毒给解引用出来。所以要注意。

6.1野指针的成因

1.指针未初始化。

即你直接赋予指针,但没给他任何地址(房间号)给他找,那他只能随便找一个房间住着

#include <stdio.h>
int main()
{ 
    int *p;//局部变量指针未初始化,默认为随机值
    *p = 20;
    return 0;
}

2.指针越界访问。

就比如一个数组,给员工开了10个房间,p 是老板,他根据房间找了 每个员工,可这一层还有许多房间,p 老板忍不住,打开了其中一间,结果里面有一伙罪犯,老板直接gg。所以这就是野指针,很危险。

#include <stdio.h>
int main()
{
    int arr[10] = {0};
    int *p = &arr[0];
    int i = 0;
    for(i=0; i<=11; i++)
 {
 //当指针指向的范围超出数组arr的范围时,p就是野指针
     *(p++) = i;
 }
     return 0;
}

3.指针指向的空间已被释放。

这里的代码的意思是,*p 指向的是 一个 函数的 返回值,但返回的这个 n 的地址,其实已经没有了,因为n 是一个局部变量,利用完后就没了。所以地址里存的是什么都不知道。

就像 你还是那个p 老板,你刚刚复活了,然后你的员工已经把房间退了,你还去找你的员工,结果进去 又是一伙罪犯分赃,你又被 暗杀了。

这也是野指针。

#include <stdio.h>
int* test()
{
 int n = 100;
 return &n;
}
int main()
{
 int*p = test();
 printf("%d\n", *p);
 return 0;
}

 6.2如何规避野指针

6.2.1指针的初始化

如果明确了指针要指向哪,那就直接赋予指针那的地址,如果不知道就给他赋值一个NULL。

NULL的值是0,0也是地址但这个地址是无法使用的。所以就像你把野指针这只野狗,拴在了一棵树上这样他就不能乱跑了。

#include <stdio.h>
int main()
{
    int num = 10;
    int*p1 = &num;
    int*p2 = NULL;
 
    return 0;
}

6.2.2小心指针越界。 

⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是 越界访问。

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

当指针变量指向一块区域时,我们可以通过指针访问这片区域,后期不用这个指针,把它拴在NULL时就行,因为有一个规矩,只要是NULL指针就被拴住了,就不去访问了。同时使用指针之前也可以看看它是否被NULL拴着。

不过就算野狗被拴着我们也得绕开走。

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

和上面的第三个指针类似。

7.assert断言

首先要用assert 要有头文件#include<assert.h>。assert(),用于在运行时确保程序符合指定的条件,如果不符合,就停止运行。所以称为断言。

这样就可以用assert 来判断 指针变量是否指向 NULL,如果指向那就终止程序。

assert的好处:

它不仅能⾃动标识⽂件和 出问题的⾏号,还有⼀种⽆需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问 题,不需要再做断⾔,就在 #include <assert.h> 语句的前⾯,定义⼀个宏 NDEBUG
#define NDEBUG
#include <assert.h>

 assert 一般只在 debug 调试下使用,在release版本中等于没有。不会影响用户使用程序的效率。

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

8.1传址调用

学习指针当然是因为有必须使用指针才能解决的问题。

就像写一个函数交换两个数的值。

这个时候如果就必须用到指针才能完成地址内容的交换。

可以看看我的这篇文章

还有这篇关于strlen函数的内容

有很多问题只能用指针解决,所以指针必不可少

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值