详细解读指针(一)——访问数据的地址

在计算机中,每一个写入的数据都要在内存中存储,拥有一小块空间。每块空间都有一个地址,这个地址一个数字来表示。根据地址我们可以对其进行访问,同时地址也叫做指针。

为了方便数据存储,内存被划分成一个个小的内存单元,每个内存单元是一个字节(8个比特位)

  • 1字节(byte)=8比特位(bit)
  • 1kb=1024byte
  • 1mb=1024kb
  • 1gb=1024mb
  • 1tb=1024gb

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

编址

在这里插入图片描述
在32为(x86)环境下,地址总线有32根(x64环境中64根),每根线都可以由0或1表示,所以地址就可以有2^32种表示方式。
也就是说,x86环境下,地址是由一个32位的二进制序列表示的,所以无论是什么类型数据的地址,它都占32个比特位的空间,也就是4个字节(x64环境中则是64个比特位,8个字节)。

通过地址总线,内存中的数据就可以被传递到CPU中的寄存器里
数据总线可以传输数据
控制总线控制数据是写入还是读取

指针变量


#include<stdio.h>

int main()
{
   int i = 10;
   int* p = &i;
   printf("%p\n", p);
   return 0;
}

结果打印了一个一个8位16进制数字,也就是i的地址

  • int* 是变量p的类型,其中int表示其指向内容的类型是int,*表示它是指针
  • 变量p中存储了i的地址,即p指向i
  • 打印地址时占位符要用%p

解引用操作符*


#include<stdio.h>

int main()
{
   int i = 10;
   int* p = &i;
   *p = 20;
   printf("%d\n", i);
   return 0;
}

打印出来的结果是20

这说明p解引用后的*p可以用来访问该地址指向的内容

(注意:p是地址,*p是地址指向的内容)

指针类型

指针类型也分为char*,int*等等,虽然它们的长度都是4个字节,但是各自也有不同意义


#include<stdio.h>

int main()
{
   int i = 0x12345678;
   int* p = &i;
   char* pc = &i;
   *pc = 0;
   return 0;
}

当代码运行完第一句时,以及把0x12345678的值放到了i里面

在这里插入图片描述

但是当用char*类型的指针去访问i时,只能访问到地址最低的两位

在这里插入图片描述

也就是说,指针变量的类型决定了指针的“步长”,即指针能够访问到空间的大小

图中char类型的指针只能访问到一个字节的空间,而int类型可以访问4个字节

指针的运算

指针 +(-)整数


#include<stdio.h>

int main()
{
   int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
   int* p = arr;//数组名是首个元素的地址
   printf("%p\n", p);
   p++;
   printf("%p\n", p);
   printf("%d\n", *p);
   return 0;
}

在这里插入图片描述

可以看到,地址+1就相当于地址+“步长”,+n就是加上n倍的“步长”

这也就刚好保证了p++能访问到内存中下一个元素

(注意以上结论实现的前提是数组的元素随着下标是从低到高存储的

指针-指针


#include<stdio.h>

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

在这里插入图片描述
指针-指针的绝对值就是中间元素的个数 + 1(如果低地址-高地址就是负数)

注意前提是两个指针必须指向同一块区域

指针比大小


#include<stdio.h>

int main()
{
   int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
   int* p = arr;
   while(p <= &arr[9])
   {
      printf("%d ", *p);
      p++;
   }
   return 0;
}

指针之间可以比较大小,用的时候要注意p++p <= arr[9]是因为数组元素地址随着下标是递增的

const修饰指针

const修饰变量可以使该变量无法被修改,但是利用指针访问依然可以间接修改。

但是如果用const修饰指针,那么就有下面几种情况:const放在*前面或者后面,或者两边都放

  • const放在*前(与放在int的前后无关),那么该指针指向的内容(*p)不能改变,但是指针的指向(p)可以改变

#include<stdio.h>

int main()
{
   int i = 10;
   int j = 20;
   const int* p = &i;//const也可以放在int后面
   *p = 0;//这条代码会报错
   return 0;
}
  • const放在*后,那么该指针的指向(p)不能改变,但是可以通过该指针改变它指向的内容(*p)

#include<stdio.h>

int main()
{
   int i = 10;
   int j = 20;
   int* const p = &i;
   p = &j;//这条代码会报错
   return 0;
}
  • 如果两边都放就都不能改变

野指针

野指针的可能成因

  • 未初始化指针
int* p;
  • 指针指向的内容销毁(空间释放)
int* test()
{
   int n = 0;
   return &n;
}//出函数时&n被保存在寄存器中传出来,但是n已经被销毁了!!!

int main()
{
   int* p = test();
   return 0;
}
  • 指针访问越界
int arr[10] = 0;
int* p = arr[10];

规避野指针

  • 指针初始化(如果提前不知道指针需要指向哪里,可以先置空int* p = NULL
  • 及时置空(NULL)
  • 指针使用前检查有效性

assert断言

在debug版本中,判断括号内的内容,如果为真则不产生任何效果;如果为假程序报错
在release版本中,assert宏被优化掉了,无论是否为真都不产生任何效果

assert断言需要包含头文件<assert.h>

#include<assert.h>

//......
int* p = NULL;
assert(p != NULL)

此时程序会报错
如果需要禁用assert宏,需要在assert头文件前面加上#define NDEBUG这样assert语句就会失效

传值调用和传址调用

传值调用

传值调用是将实际参数的值传给形式参数,二者拥有独立空间,对形参的操作无法影响实参

#include<stdio.h>

void Change(int a, int b)
{
   int change = b;
   b = a;
   a = change;
}
int main()
{
   int x = 3;
   int y = 5;
   Change(x, y);
   printf("%d", x);
   printf("%d", y);
   return 0;
}

代码运行后,发现x和y实际上并没有交换,这就是因为改变形参不会改变实参

传址调用

#include<stdio.h>

void Change(int* a, int* b)
{
   int change = 0;
   change = *b;
   *b = *a;
   *a = *change;
}
int main()
{
   int x = 3;
   int y = 5;
   Change(&x, &y);
   printf("%d", x);
   printf("%d", y);
   return 0;
}

把传入的变量变成地址后,形参和实参共用一个地址,所以是完全一样的,改变形参就是在改变实参

数组和指针

数组名的理解

在一般情况下,数组名就是首元素的地址,但有两种情况不同:

  • 和数组在同一个函数内的sizeof(数组名),但是如果是数组传参后再用sizeof计算大小,此时的数组名就又算作首元素地址了,因为数组传参的本质是传首元素的地址,所以我们可以通过函数来改变数组
#include<stdio.h>

void test(int* ptr)//这里用数组(int ptr[])或者指针接收都可以
{
   printf("%d\n", sizeof(arr));
}
int main()
{
   int arr[10] = {0};
   test(arr);
   return 0;
}

此时打印的结果就是指针长度4而不是数组长度40了

  • &数组名表示整个数组的地址,此时数组名就是整个数组,在数值上等于首元素地址,但是 &数组名+1跨越的长度是整个数组

使用指针访问数组

#include<stdio.h>

int main()
{
   int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
   int sz = sizeof(arr) / sizeof(arr[0]);
   int* p = arr;
   int i = 0;
   for(i = 0; i < sz; i++)
   {
      printf("%d ", *(p + i));
   }
   return 0;
}

这个代码可以完成对该数组的打印

此处的p换成arr,变成*(arr + i)也是可以的

那么就有*(arr + i)=*(i + arr)=arr[i]=i[arr]

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值