【深入理解指针1】——浅尝辄止!


大家好!今天小编给大家带来了有关指针的一些内容,
尤其是对小白很合适哦,不妨看看吧!

在这里插入图片描述

1.内存和地址

  1. 含义:1个内存单元大小是一个字节,即8个比特位。

  2. 理解:每个内存单元相当于一个学生宿舍,即一个字节空间。每个宿舍有8个床位=每个字节空间有8个比特位=八人间,每个人是一个比特位。

  3. 联系:计算机使用的是二进制语言,每个内存单元有自己的编号,内存编号=地址=指针

  4. 如何理解编址?
    CPU访问内存空间 需要知道其地址 故要给内存进行编址
    ==》查寝 由于寝室很多 需要设计房间号

  5. 总结:内存会被分成一个个的内存单元,每个内存单元的大小是一个字节,每个内存单元都会给一个编号=地址=指针


2.指针变量和地址

2.1指针变量

int main()
{
int a=20;
int *p=&a;//int*是指针指向的类型,p是指针变量
return 0;
}

理解:存放地址的变量–>指针变量,指针变量是用来存放地址的,也默认为放在指针变量里的都是地址。

2.2解引用*

*p//解引用操作,间接访问操作 *p等价于a


3.指针变量的大小

  1. 说明:指针变量需要多大空间是取决于存放的是什么?存放的是地址,地址存放需要多大空间,指针变量的大小就是多大。
  2. 总结:
    a.x86(32位):地址是32个0/1的二进制序列,存储起来需要32个bit位,即4个字节,指针变量大小就4个字节。
int main()
{
	int a = 20;
	int* pa = &a;
	printf("%zd\n", sizeof(pa));//4
	printf("%zd\n", sizeof(int*));//4
	printf("%p\n", pa);//00EFFE4C
	return 0;
}

b.x64(64位):地址是64个0/1的二进制序列,存储起来需要64个bit位,即8个字节,指针变量大小就8个字节。

int main()
{
	int a = 20;
	int* pa = &a;
	printf("%zd\n", sizeof(pa));//8
	printf("%zd\n", sizeof(int*));//8
	printf("%p\n", pa);//00000097B9AFF954
	return 0;
}

4.指针类型

int main()
{
	int a = 0x11223344;
	int* p = &a;
	*p = 0;
	return 0;
}

4.1结论:

指针类型决定了指针进行解引用操作的时候访问多大的空间。
int的指针解引用访问4个字节,char的指针访问1个字节。

4.2指针±整数
在这里插入图片描述

在这里插入图片描述

可以看见:char类型的指针+1跳过了1个字节,int类型的指针+1跳过了4个字节。

结论指针类型决定了指针的步长,就是向前或向后走一步有多大距离。

type*p:
p+i是跳过了i个type类型的数据,相当于跳过了i*sizeof(type)个字节
int*p:
p+2是跳过了2个int*类型的数据,相当于跳过了2*sizeof(int)==8个字节

4.3void*指针

含义:无具体类型的指针,可以用来接受任意类型地址。
局限性:void* 类型的指针不能直接进⾏指针的±整数和解引⽤的运算。
在这里插入图片描述
如上,将int类型的变量地址赋值给char类型的指针变量,编译器警告:类型不兼容,使用void指针则不会有这种情况发生在这里插入图片描述

4.4那么 void* 类型的指针到底有什么⽤呢?
⼀般 void* 类型的指针是使⽤在函数参数的部分,⽤来接收不同类型数据的地址,这样的设计可以实现泛型编程的效果。使得⼀个函数来处理多种类型的数据,在后续我们会讲解。


5.const修饰指针

5.1const修饰变量
在这里插入图片描述

总结:n本质是变量,只不过被const修饰后,不能被修改了,在语法上加了限制。那么如何间接修改n呢?绕过n,使用n的地址,就能修改啦!
理解:老默,我想吃鱼了—高启强借刀杀人

5.2const修饰指针变量

在这里插入图片描述
说明:
p是指针变量,里边存放了其他变量的地址,*p是指向的对象,p也有自己的地址&p。

两种情况

1. const放在*的左边:const int p=&n;
2. const放在
的右边:int * const p=&n;

int n = 10;
int m = 100;

const放在*的左边

	const int* p = &n;
	*p = 20;//err
	p = &m;//ok

const放在*的右边

	const int* p = &n;
	*p = 20;//err
	p = &m;//ok

总结

1. const放在的左边,修饰的是指向的内容,指向对象的内容不能变,但指针变量本身的内容可以变。
限制的是
p,不能通过p来改变p指向对象的内容,但p本身可以变,p可以指向其他对象。

2. const放在*的右边,修饰的是指针变量本身,指针变量的内容不能变,但是指针指向对象的内容可以变。
限制的是p,不能修改p本身的值,但p指向的内容可以通过p来改变。


6.指针运算

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

  • 指针±整数
  • 指针-指针
  • 指针的关系运算

6.1指针±整数
数组在内存中是连续存放的,随下标增大,地址增大。
只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有元素。

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

6.2指针-指针
前提:两个指针指向了同一块空间
说明:得到的是指针之间元素的个数

strlen求字符串长度时,统计的是\0之前的字符个数;
数组名表示首元素地址,两种特例除外。

strlen模拟实现
#include <stdio.h>
int my_strlen(char *s)
{
char *p = s;
while(*p != '\0' )
p++;
return p-s;//指针-指针:求字符串的长度
}
int main()
{
printf("%d\n", my_strlen("abc"));
return 0;
}

6.3指针的关系运算
指针的大小比较

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;
}

7.野指针

7.1野指针成因

1.指针未初始化

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

2.指针越界访问

#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.指针指向的空间释放

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

如何规避
a.指针初始化

int main()
{
	int* p;//不初始化,相当于野狗没栓绳
	int* p = NULL;//相当于把野狗拴在树上,NULL,标识符常量,0
	return 0;
}

b.小心指针越界:不要超过申请的内存空间
c.指针不再使用及时置NULL,指针使用前检查有效性
d.避免返回局部变量的地址


8.assert断言

8.1前提:使用前需引用头文件<assert.h>
8.2含义:assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值⾮零), assert() 不会产⽣任何作⽤,程序继续运⾏。如果该表达式为假(返回值为零), assert() 就会报错。
8.3使用:在这里插入图片描述
理解:验证变量 p 是否等于 NULL 。如果确实不等于 NULL ,程序继续运⾏,否则就会终⽌运⾏,并且给出报错信息提⽰。

总结:真,继续;假,停止并报错。


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

9.1strlen模拟实现

函数原型:size_t strlen ( const char * str );
size_t无符号整型 用%zd来打印

int my_strlen(char* str)//str接受一个字符串的起始地址
{
	int count = 0;
	assert(str);
	//while (*str != '\0')
	while(*str)
	{
		count++;
		str++;//一个个向后遍历访问
	}
	return count;
}
int main()
{
	char str[] = "abcdef";
	int len = my_strlen(str);
	printf("%d\n", len);//6
	return 0;
}

9.2传值调用和传址调用
9.2.1例如:写⼀个函数,交换两个整型变量的值
可能的写法:

#include <stdio.h>
void Swap1(int x, int y)
{
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("交换前:a=%d b=%d\n", a, b);
Swap1(a, b);
printf("交换后:a=%d b=%d\n", a, b);
return 0;

但是运行后发现,a和b的值并没有交换,为什么呢?
原来,Swap1函数在使⽤的时候,是把变量本⾝直接传递给了函数,这种调⽤函数的⽅式我们之前在函数的时候就知道了,这种叫传值调⽤

结论:

  1. 实参传递给形参,形参有自己的独立空间
  2. 形参时实参的一份临时拷贝
  3. 对形参的修改,不会影响实参

9.2.2怎么办呢?
可以使用指针,将main函数中a,b的地址传递给Swap函数,就可以在Swap函数中通过地址间接访问main函数中的a和b了。

#include <stdio.h>
void Swap2(int*px, int*py)//通过指针间接访问a和b
{
int tmp = 0;//创建一个空杯子,实现交换两杯牛奶
tmp = *px;
*px = *py;
*py = tmp;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);//
printf("交换前:a=%d b=%d\n", a, b);
Swap2(&a, &b);//传地址给Swap函数
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}

说明:这里调用Swap2函数的时候是把地址传递给了函数,这种函数调用方式叫:传址调用

总结:传址调⽤,可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量;所以未来函数中只是需要主调函数中的变量值来实现计算,就可以采⽤传值调⽤。如果函数内部要修改主调函数中的变量的值,就需要传址调⽤。


结语:好啦,以上就是小摆子今天带来的有关于指针的一些内容,水平有限,请大家多多包涵和支持,一键三连哦!
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值