【超详细指针系列】指针超详细讲解------从入门到应用-----一步一步将你带入深挖指针【1】

本文讲带你认识指针,了解指针,如有不对,请及时指出

准备好了吗,来,一起进入指针的世界吧!!!!!!

目录

入门篇

第一讲:内存和地址

1.1内存

1.2.究竟该如何理解编址

二、 指针变量和地址

2.1 取地址操作符(&)

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

2.2.1指针变量       

2.2.2 指针类型的拆分

2.2.3 解引用操作符

2.2.4指针变量的大小

三、指针类型的意义

2.2 指针的解引⽤(指针类型的权重)

3.2指针+-整数  

3.3 void* 指针

四、const修饰指针变量

五、指针运算

1.指针+-整数

2.指针-指针

3.元素的关系运算

六.野指针

6.1 野指针成因

1. 指针未初始化

2.指针越界访问

3.指针的空间释放

6.2 如何规避野指针

6.2.1空指针NULL

6.2.2指针越界

七.assert断言

 八.传值调用和传址调用

8.1传值调用:

8.2传址调用 


入门篇

第一讲:内存和地址

1.1内存

单讲内存和地址太枯燥,来举个例子吧:

一天小玉突然想起了多年的挚友小雪,想联络联络感情,所以想要去小雪家,所以打了电话:

小玉:”你?还好吗?“

小雪:“你是??”

小玉:“嗯?不至于我的声音都听不出来吧”

小雪:“哈哈哈,原来是你,怎么了??”

小玉:“好久没见,我可以找你去玩吗?"

小雪:"哈哈哈哈,好呀,好久没见你了,对了,你不知道我家地址吧”

小雪:“我发你”

小玉:”嗯嗯,多时不见,期于君遇“

(对话结束)


!!其实在这里这段对话中,已经显示出了内存的本质,听我详细道来:

!!小雪给出的地址面向的对象----楼层,其实就是就是内存内存嘛,其实就是存东西的地方


可问题来了,小玉到达了小区,看到了小雪的单元楼(内存),而接下来问题就出现了,那间房子是哪,所以小玉问了小雪房间号。。。。。

对!!!,这么大个单元楼,怎么多房间,要怎么分辨小雪的房间哪?

所以我们给这里的每一个内存单元(房间)编制了房间号

一楼:101、102、103、104........

二楼:201、202、203、204........

三楼:.......................

这里的房间号其实就是地址!!!!

说回正题:

内存相比大家都不陌生,在你买电脑的时候总会了解到电脑是多少g内存的,如4G/8G/16G/32G而这些到底是怎么划分的哪????


其实,内存不是单独的一个大整体,而是一个大的空间被划分为一个一个的小空间,我们把它叫做内存单元

每个内存单元都是1个字节。1个字节里面8个比特位,每个比特位用2进制表示,所以一个字节可以表示2^8个情况,每个内存单元都有编号,而这些编号,我们也叫做地址

通俗一点来讲

内存单元就是一个宿舍,8个比特位就是8人间,内存就是整个宿舍楼

而每个宿舍的编号==地址

在c语言中我们给地址起了一个新的名字:指针

所以


宿舍编号==地址==指针


1.2.究竟该如何理解编址

cpu是怎么准确访问内存单元的哪??其实跟人找房间一样

在这里值得注意的一点是,内存和cpu的交互需要用到很多跟数据线,每种数据线都执掌不同的功能。

这里我们就关注3种线:地址总线、数据总线、控制总线

地址总线分别用有无脉冲表示1,0

1根线有2种含义,2根线有4种,3根线有8种....

依次类推,地址线有32根,所以可以表示2^32种含义,每一种含义都表示一个地址

而在传输中,地址信息被下达给内存,在内存上,就可以找到 该地址对应的数据,将数据在通过数据总线传⼊ CPU内寄存器


二、 指针变量和地址

2.1 取地址操作符(&)

如果说创建变量是向内存申请空间,但是每个空间都有着属于自己的编号

那么取地址操作符就是把这些编号拿出来,在储存到一个新的内存单元中。

但是问题来了:如int向内存申请4个字节,那么取地址操作符难道要每个字节的地址都拿出来吗

不妨做个程序来探究一下

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	int m = 4;
	printf("%p", &m);
    return 0;
}

结果如下所示 

        

打印的地址是最小的地址(首元素地址)

同时我们也可以发现,内存是连续存放的!!!! 


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

2.2.1指针变量       

当我们用去地址操作符取出地址时如:0x006FFD69,那么我们将这个东西存在哪里哪?

答案是指针变量!!!!

例子

#include<stdio.h>

int main()

{

int a=0;

int*p=&a;//讲取出的a的地址存放到指针变量p中

return 0;

}

2.2.2 指针类型的拆分

举个例子:

1.int a=10;

int *p=&a;

 我们已经知道,p的类型是int*类型

‘*’是指p的类型是指针

int的意思是指p所指向的对象为整型(指向的是整型(int)的对象

2.2.3 解引用操作符

对的,解引用操作符也是我们的老朋友‘*’号,废话不多说,我们看它怎么用

int a=10;

int *p=&a;//指针变量存放a的地址

*p=10;//解引用操作将p所指向的对象的值改为10

2.2.4指针变量的大小

先说结论

指针变量的大小只有两个值:4和8;


!在32位的平台上运作时,指针变量的大小为4。

!在64位的平台上运作时,指针变量的大小为8。


为什么捏???

先直观的感受一下指针变量的大小的运作结果。

	printf("%d\n", sizeof(char*));
	printf("%d\n", sizeof(int*));
	printf("%d\n", sizeof(double*));
	printf("%d\n", sizeof(short*));
	printf("%d\n", sizeof(float*));

 我们看一下32位(x86)的平台下运行的结果

结果显示:在x86的平台下运行的结果,无论什么类型都是4个字节。


再看一下64位的(x64)的平台下运行的结果 

 结果显示:都是8个字节

为什么会产生这样的结果哪???

简单来说:32位机器有32根地址总线,将电信号转换为数字信号时,32个二进制产生的序列,我们可以看作位1个地址的产生,那么一个地址是由32个bite位储存的,32bit==4个字节,所以

32位下的指针变量就是4个字节

64位也相同..........................................


注意指针变量的大小和类型是⽆关的,只要指针类型的变量,在相同的平台下,大小都是相同的。!!!


三、指针类型的意义

2.2 指针的解引⽤(指针类型的权重)

体验一下两个代码的不同

代码一:

#include<stdio.h>
int main()
{
int a = 0x11223344;
int *p=&a;
*p=0;
return 0;

}

代码二:

#include<stdio.h>
int main()
{
int a = 0x11223344;
char * p = (char *)&a;
*p=0;
return 0;

}

我们打开调试界面的内存

代码一:

解引用归赋值0前:

解引用归赋值0后:

代码二:

解引用归赋值0前:

解引用归赋值0后:

所以感受到了嘛

不同的指针类型的解引用掌握的是访问内存的权重。

我们可以理解为:

int类型为4个字节

代码一访问指针所指向的对象时,一次性访问整型变量内存的个4个字节

代码二:

代码二访问指针所指向的对象时,因为权重的问题,一次性访问整型变量的内存的1个字节

3.2指针+-整数  

int main()

    int a = 10;
    char* p = (char*)&a;
    int* pi = &a;

printf("%p\n",&a);
printf("%p\n",&a+1);
printf("%p\n",p );
printf("%p\n",p+1 );
printf("%p\n",pi );
printf("%p\n",pi+1 );

   return 0;

}

在这里,我们要分清p,pi,&a分别代表的是内存地址的哪里,取的是整个数组,还是数组里的一个元素的地址。

&a:我们可以发现:

&a和&a+1中间的地址差了4个字节,又因为&取地址取的是最小的地址,所以我们可以发现


&取的是整个数组。


将我们把int类型换成double时

之间差了8个字节

进一步佐证了&取的是整个数组


pi:pi指针变量由于权重的关系,+1时,也是只加了一个字节,到达的是该变量的第二字节的元素地址。


p:由于p指针变量取的是a的地址,在变量操作中,所以在无论如何+和-整数,操作始终保持一致。


3.3 void* 指针

在指针类型中有⼀种特殊的类型是 void* 类型的,可以理解为⽆具体类型的指针(或者叫泛型指 针),这种类型的指针可以⽤来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进 ⾏指针的+-整数和解引⽤的运算。


四、const修饰指针变量

const修饰指针变量有三种

1.const int *p=&a

2.int*const p=&a

3.int const* const p=&a

一一来进行讲解

我们用一段测试代码来进行演示:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void test1()//用来测试*左边
{
	int n1 = 10;
	int n2 = 11;
	int const* p = &n1;
	*p = 9;
	p = &n2;

}
void test2()//用来测试*右边
{
	int n1 = 10;
	int n2 = 11;
	int * const p = &n1;
	*p = 9;
	p = &n2;

}
void test3()//用来测试int左边
{
	int n1 = 10;
	int n2 = 11;
	const int * p = &n1;
	*p = 9;
	p = &n2;

}
void test4()//用来测试两边
{
	int n1 = 10;
	int n2 = 11;
	int const * const p = &n1;
	*p = 9;
	p = &n2;


}
int main()
{
test1();
test2();
test3();
test4();
return 0;
}

1.1.const int *p=&a

2.int*const p=&a

3.3.int const* const p=&a

综上所述:

1.当const在*左边时,修饰的是*p

2.当const在*右边是,修饰的是p

3.当*左右两边都有时,修饰的时*p和p



五、指针运算

常见类型有种

1.指针+-整数

2.指针+-指针

3.指针的关系运算

1.指针+-整数

上面有一个变量的指针加减,那么在数组中又如何运算?

我们可以设计一下代码

#include<stdio.h>
int main()
{
	/*1.指针+-整数*/
	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\n", *(p + i));//让p+i(整数)看看运行结果是否将arr数组全部打印)
	}
return 0;
}

           

                   结果1                                                                     结果二(i改为5时)

解释:

当指针p被赋值为元素首地址时,+-的操作,也就在数组之内,遍历数组。


2.指针-指针


先说结论,指针减指针的结果返回的是两个指针间的元素个数


#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
int my_strlen(char* s)
{
    char* p = s;
    while (*p!='\0')
    {
        p++;
    }
    return p - s;
}
//
int main()

{

    char arr[] = "abcd";
    char* s = &arr[0];
    printf("%d\n", my_strlen(s));

    return 0;
}

结果

这里为什么返回的时4哪??

解答: 

p的运算结果是遇到\0就停止,所以 p最后的落子是在\0而s是元素的首地址。


因为地址是来连续存放的!!!!

两个地址相减返回的是两个之间的字节数,也是元素个数


3.元素的关系运算

其实关系运算的本质就一个

p<arr+sz(一个整数)

这里怎么运算

arr【数组名】是首元素地址,加上sz,就是sz的地址本质就是(0+1==1)


六.野指针

6.1 野指针成因

1. 指针未初始化

#include

int main()

{

int *p;//局部变量指针未初始化,默认为随机值

*p = 20; 

return 0;

}

2.指针越界访问

include int main() { int arr[10] = {0};

 int *p = &arr[0];

int i = 0;

for(i=0; i<=11; i++)

{

*(p++) = i;//当指针指向的范围超出数组arr的范围时,p就是野指针

}

return 0; }

这表明指针访问数组时越界了,指针也成了野指针

3.指针的空间释放

#include

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让其待命

NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址 会报错。

讲空指针NULL转到反汇编就可以看到NULL的值为0,当然这个地址也是无法使用的,这也是重置指针的一个手段


在指针不被使用时,或使用完时,一定一定要及时将指针赋值为空指针,这样才会导致出现指针乱指的问题,防止指针成为野指针!!!!!


6.2.2指针越界

指针越界问题是一个典型问题,在使用指针时一定要算好边界,不然会造成程序崩溃,导致报错


七.assert断言

assert断言可以理解为一个强制性判断(可以操作)

但是assert断言的操作的优点在于:如果该处出现错误,运行时会直接指出在哪里出的错,这对程序员真的真的很友好,可以省去大部分找error的时间。

头文件:#inlclude<assert.h>

是否实现:assert:#define NDEBUG(有的这条预处理指令,assert不进行实现操作)

功能实现:assert(判断式)

实际编写:assert(p!=NULL)


 八.传值调用和传址调用

两段代码,进行对比

8.1传值调用:

代码一:

#define _CRT_SECURE_NO_WARNINGS 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和&x,&b和&y并不相同,但局部变量x和a,y和b的值是相同的,这代表了什么

在自定义函数内,只是x和y完成了交换,而a和b传给函数的只是数值而已,在运行函数后,a和b并没发生交换。

这也说明了:形参是实参的一份临时拷贝,而形参无法代替实参进行操作!!!!


8.2传址调用 

那怎么才能交换哪???

我们不妨用下指针,指针是什么,指针是地址呀,我们把地址传给函数,让指针所指向的对象发生交换不就完了

比较抽象哈,我们举个例子:还是小玉和小雪,哈哈哈哈哈,

为了促进两者之间的感情,小玉也给了小雪地址,让小雪也找小玉去玩。

但是有一天,小雪突发奇想,我们交换一下礼物吧,但是交换礼物的方式不如这样吧:

我顺着你家地址,去你家把我的礼物放进去,然后我挑一件你为我准备的礼物放回我家

(这就是传址交换

然后你去我家,把你的礼物放进去,挑一件我为你准备的礼物放回你家。

小雪也是乐子人,所以一拍即合,所以开始实施了。

代码二:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

void Swap2(int* px, int* py)
{
	int tmp = 0; //临时变量
	tmp = *px;//px接受a的地址
	*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);
	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

  • 33
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值