本文讲带你认识指针,了解指针,如有不对,请及时指出
准备好了吗,来,一起进入指针的世界吧!!!!!!
目录
入门篇
第一讲:内存和地址
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;
}