【C语言】指针(1)

1.内存和地址

电脑内存有8GB、16、32GB等等,说明了电脑储存数据的大小,那么问题来了,这么大的空间如何精确地找到每一个数据所在的位置呢?😊😊😊😊😊
生活中有地址,其实数据在内存中的储存也有地址,也就是耳熟能详的指针,让我们一起来了解指针吧

请添加图片描述

内存编址

内存编址就是给每个地址编号,不过并不是记录下来,而是通过硬件设计来完成,这里就要介绍地址总线
在这里插入图片描述
一根线有正负两种信号,32位(32根)就是232个,64位就是264个,这样地址信息可以下达到内存,而内存作出反应,将对应空间数据传给CPU寄存器

2.指针变量和地址

地址本身就是数据,也需要指针变量存起来

2.1&取地址操作符

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

内存一个字节代表一个空间
整型4个字节,因此
在这里插入图片描述
&a取出地址较小的地址,即0x006FFD70

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

int* pa=&a;

*指明pa为指针变量,int为指针变量的类型

int a=0;
int* pa=&a;
*pa=2;//*pa指到达指针指向的地方,并可以修改对应的数值
pa=&b;//只使用pa表示修改指针变量所储存的地址

指针大小

32位,即4个字节
64位,即8个字节

3.指针变量类型的意义

3.1指针解引用

首先指针变量类型决定指针解引用的权限大小

	int n = 0x11223344;
	int* pi = &n;
	*pi = 0;

在这里插入图片描述
在这里插入图片描述
我们清楚的看到,int 类型指针可以访问4个字节空间并修改为0
在这里插入图片描述
而char类型的指针只能修改一个字节空间

3.2指针±整数

上代码

	int a = 1;
	int* pi = &a;
	char* po = &a;
	printf("pi     = %p\n", pi);
	printf("pi + 1 = %p\n", pi+1);
	printf("po     = %p\n", po);
	printf("po + 1 = %p\n", po+1);

在这里插入图片描述
我们清晰的看到指针变量类型决定了±整数能前进或后退一步能有多大距离

4.const修饰指针

4.1const修饰变量

上代码

在这里插入图片描述
通过const修饰的变量无法再次通过变量修改
但可以使用指针变量修改
在这里插入图片描述

4.2const修饰指针

同样const也可以修饰指针
但有两种情况

     int const n = 12;
	 const int* pi = &n;
	 *pi = 2;
	 printf("%d\n", *pi);

在这里插入图片描述
consr在*前面表示指针变量指向的地址数据无法更改,但变量本身可以修改,且指针储存的地址也可以修改

     int const n = 12;
	  int* const pi = &n;
	 pi = 2;

在这里插入图片描述
const位于*后面则指针变量储存的地址不可更改

5.指针运算

5.1指针±整数

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

在这里插入图片描述
通过知道数组的首元素地址,指针不断加一就能访问每个元素

5.2指针±指针

指针-指针,指二者之间相同类型元素的个数
以数组为例

	int arr[8] = { 1,2,3,4,5,6,7,8 };
	//int sz = sizeof(arr) / sizeof(arr[0]);
	//int i = 0;
	int* pi =&arr[0];
	int* po = &arr[7];
	printf("%d\n", po - pi);

在这里插入图片描述
为什么是7而不是8呢?
在这里插入图片描述

5.3指针的关系运算

指针之间也可以比较大小

	int arr[8] = { 1,2,3,4,5,6,7,8 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	int* pi =arr;
	while (pi < arr + sz)
	{
		printf("%d ", *pi);
		pi++;
	}

在这里插入图片描述

6.野指针

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

6.1成因

1.指针未初始化

int *p;
*p=20

在这里插入图片描述

2.指针越界访问

	int arr[10] = { 0 };
	int* p = arr;
	int i = 0;
	for (i = 1;i < 12;i++)
	{
		*p = i;
		printf("%d ", *p);
		p++;
	}

在这里插入图片描述

3.指针指向的空间释放

int* test()
{
int a=0;
return &a;
}
int main()
{
  int* p=test();
  *p=20;
  reurn 0;
  }

a为临时变量,函数调用后就会销毁,此时*就指向了不确定位置

6.2如何规避

6.2.1指针初始化

如果知道指针指向哪里,就初始化地址,否则置NULL。
NULL1是C语言定义的一个标识符常量,值为0,0也是地址,只是无法使用,读写时会报错

int num=10;
int* po=&num;
int *Pi=NULL;

6.2.2小心指针越界

使用指针就养成检查的习惯

6.2.3指针使用之前检查有效性,使用后及时置NULL

int arr[10]={0};
int i=0;
int*p=arr;
for(i=0;i<10;i++)
{
*p=i;
p++;
}
*p=NULL;//使用后即使置空

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

7.assert断言

在assert.h中定义了宏assert(表达式),符合表达式条件就返回非0,程序正常运行,如果不符合,返回0,程序报错,停止运行

assert(*p!=NULL);

assert()不仅可以自动标识文件和指出出问题的行号,还有一种无需更改就能开启或关闭assert的机制
如果程序运行已经没有问题,那我们可以在#include<assert.h>前加上#define NDEBUG
然后重新编译程序,编译器会自动禁用所有assert语句
防止assert语句增加程序陨星的时间
一般我们可以在debug版本中使用,在release版本中禁用

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

如果我们要实现两个整型的交换,我们很有可能做出以下代码

void swap(int a, int b)
{
	int i = 0;
	i = a;
	a = b;
	b = i;
	return;
}

int main()
{
	int a = 12;
	int b = 32;
	printf("a=%d b=%d\n", a, b);
	printf("交换后:\n");
	swap(a, b);

	printf("a=%d b=%d\n", a, b);

	return 0;
}

在这里插入图片描述
还记得我们说过,函数的形参是实参的一份拷贝
在这里插入图片描述
形参xy的确交换了,但不会影响实参,也就是传值调用
这就是典型只能用指针解决的问题
来看看代码

void swap(int* x, int* y)
{
	int i = 0;
	i =* x;
	*x =* y;
	*y = i;
	return;
}

int main()
{
	int a = 12;
	int b = 32;
	int* pi = &a;
	int* po = &b;
	printf("a=%d b=%d\n", a, b);
	printf("交换后:\n");
	swap(pi,po );

	printf("a=%d b=%d\n", a, b);

	return 0;
}

在这里插入图片描述
这就是传址调用

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值