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

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

入门篇

第一讲:内存和地址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个字节,那么取地址操作符难道要每个字节的地址都拿出来吗

不妨做个程序来探究一下

代码语言:javascript复制#define _CRT_SECURE_NO_WARNINGS 1

#include

int main()

{

int m = 4;

printf("%p", &m);

return 0;

}结果如下所示

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

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

2.2、指针变量和解引用操作符(*)2.2.1指针变量 当我们用去地址操作符取出地址时如:0x006FFD69,那么我们将这个东西存在哪里哪?

答案是指针变量!!!!

例子

#include

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。

为什么捏???

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

代码语言:javascript复制 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 指针的解引⽤(指针类型的权重)体验一下两个代码的不同

代码一:

代码语言:javascript复制#include

int main()

{

int a = 0x11223344;

int *p=&a;

*p=0;

return 0;

}代码二:

代码语言:javascript复制#include

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

一一来进行讲解

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

代码语言:javascript复制#define _CRT_SECURE_NO_WARNINGS 1

#include

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.指针+-整数上面有一个变量的指针加减,那么在数组中又如何运算?

我们可以设计一下代码

代码语言:javascript复制#include

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

#include

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,就是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:#define NDEBUG(有的这条预处理指令,assert不进行实现操作)

功能实现:assert(判断式)

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

八.传值调用和传址调用两段代码,进行对比

8.1传值调用:代码一:

代码语言:javascript复制#define _CRT_SECURE_NO_WARNINGS 1

#include

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传址调用 那怎么才能交换哪???

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

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

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

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

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

(这就是传址交换)

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

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

代码二:

代码语言:javascript复制#define _CRT_SECURE_NO_WARNINGS 1

#include

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;

}

Copyright © 2088 网游活动先锋站 All Rights Reserved.
友情链接