绪论
书接上回,经过操作符章你应该对编程中的运算有了一个更深入的了解,了解到了他底层的运算(二进制运算),以及各种操作符的具体用法。而这章的指针是我们在C语言学习中很重要的一个部分此处为初阶指针后期会更新进阶般,希望你可以通过我这篇文章,对指针有个更好的认识。
所以安全带系好,发车啦(建议电脑观看)。
附:红色,部分为重点部分;蓝颜色为需要记忆的部分(不是死记硬背哈,多敲);黑色加粗或者其余颜色为次重点;黑色为描述需要
思维导图:
要XMind思维导图的话可以私信哈
目录:
1.指针的定义
1.1指针是什么
知识点:
1.1.1指针:
内存最小单元的编号,即地址(指针和指针变量不一样,我们常说的指针一般指指针变量)
细节(注意点):
1.1.2内存:
我们在使用计算机的过程中每使用一个程序都会占用我们的内存空间,而指针就是某个内存的地址编号,如今内存通常在计算机中以4G、16G的形式存在,当我们打开我们的任务管理器就能看到自己当前内存等的使用情况。
例:整形变量a在内存中占用的内存使用情况如下
附:&a取的是首元素地址(低地址),底下是低地址,高处是高地址
通过上面所画的图,在每个内存单元的大小时一个字节,其对应的地址编号占四个字节(在32位机器上时)
1.2指针变量(地址的变量)
知识点:
1.2.1指针变量(一种存放指针(地址)的变量):
当我们先要去使用指针时,我们就要创建一个指针变量如:int * p = &a(此处p变量存放的是a的地址);同过此处指针变量可以去访问a变量的地址。此处的p就是指针变量。因为p指针已经找到了a的内存所在的地址,指针变量就可以通过解应用操作符,去改变a变量。(就好比你已经知道了一个地址,你就可以通过这个地址找到所对应的地方)
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() { int a = 0; int* p = &a; printf("%p\n", p);//p指针指向a变量的地址 printf("%p\n", &a); printf("%d\n", *p);//通过*解引用找到a,并且可以改变a printf("%d\n", a); *p = 20; printf("%d\n", a); return 0; }
细节:
通过上面的知识点可以概括成,指针变量就是用来存放某个地址的变量。而对于地址来说,每个地址都有对应编号,而这个编号又是怎么产生的呢?
地址编号的产生:
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或者0); 那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
...
11111111 11111111 11111111 11111111
这里就有2的32次方个地址。 每个地址标识一个字节,那我们就可以给 (2^32Byte == 2^32/1024KB == 2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB) 4G的空闲进行编址。 同样的方法,那64位机器,如果给64根地址线,那能编址 2 ^ 64 byte == .... = 16G
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以 一个指针变量的大小就应该是4个字节。(32位平台上指针变量大小4byte)
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。(64位平台上指针变量大小是8byte),所以指针大小要看在什么机器上
x84:32位机器 ; X64 :64位机器
2.指针和指针类型
2.1指针类型
知识点:
同变量一样,指针也有不同的指针类型int *、 char * 、 long *、.........
但我们通过上面的指针大小又发现他们不同类型间的大小和正常的变量类型大小不一样是相同的,这样不同的类型岂不是没什么区别?
其实,指针变量类型的大小虽然确实是相同的,但是他们的意义不同,一般来说,int * 的指针变量,存放的地址是int类型的变量地址。一般来说,char * 的指针变量,存放的地址是char类型的变量地址, .....
不同的指针类型将会决定其指针在前进or后退时的前进or后退的内存单元大小,如int * 类型的指针,当他往前走时是每次走4个byte,同理char * 类型 每次往前走1个byte
也就是步长
char * :
int * :
.....
2.2指针的解应用
知识点:
指针通过 * 解应用操作符来找到所指定的地址空间,并且可以通过这个地址去改变该空间内的值
注:指针的类型决定了指针解应用的权限,即一次性改变的值(通过上面所截的图就是此处)
int main() { int a = 0x11223344; char* p = (char*)&a; for(int i = 0; i < 4 ; i++) *p++ = 0;//同过后++让指针往前走 return 0; }
3.野指针
3.1野指针的来源
知识点:
3.1.1野指针:
是一种我们在日常写代码时容易犯的错,就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。
细节:
3.1.2野指针的危害:
如果指针随意的去指向一个你自己都不知道的地方,假如你将这个指针改变了也就是将一个你都不知道的位置进行改变,假如在一个大型的程序中这样可能会将你自己所设的值改变,而自己却很难发现原因。
3.1.3常见野指针的成因:
- 指针未初始化 从函数栈帧的创建和销毁你可以知道局部的变量在为初始化前都是一个随机值,同理指针变量也是如此,当你没有去初始化时,他所指向的空间地址也是一个随机的。此处指针未初始化,就会让其指向一个未知的地址空间
- 指针越界访问 同数组越界类似,当一个指针去访问一个数组时可能会在向前走时,走过了数组的范围指向了数组外的未知地址空间。 此处指针在不断往后走时因为i<=12所以将会使指针指到数组外的未知地址
- 指针指向的空间已经释放 当指针指向函数内的变量时,其函数用完后就会把空间还给内存,即:假如你在主函数内去使用函数内所指定的变量就会导致指向的空间地址已经不是该变量的地址
此处可以看到当打印*p指向的空间时,虽然第一次打印出正确的10 但是当第二次空间彻底释放后将会打印出随机值。
3.2如何避免野指针
知识点:
1. 指针初始化
2. 小心指针越界
3. 指针指向空间释放即时将指针置为NULL、避免返回局部变量的地址
4. 指针使用之前检查有效性
如:指针不能将NULL改变,NULL的地址时不能进行访问的这是系统的一个区域我们用户无法进行改变,我们正常写代码的区域是用户区域;就好比我们的操作系统的内存的内核区,他是一个专门给系统使用的。
所以我们在用指针改变某值时要确定这个值的地址不是NULL
if ( p != NULL)
4.指针的运算
4.1指针 + - 整数
知识点:
指针的加减整数其可以大概的看成指针的前进和后退。
4.2指针-指针
知识点:
在这两个指针指向同一块空间(一般是数组)的前提下,当这两个指针向减后得到的绝对值是两指针之间的元素个数的(绝对值是因为当有小的指针减掉大的指针)
4.3指针的关系运算
知识点:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
当p处所指的内容为一个随机值时就可以表示其已经越界了但图1时符合标准规定的,而图二并不符合标准。
5.指针和数组的关系
知识点:
- 数组名为首元素的地址(两种情况除外,数组章写过)
- 在创建指针变量时,可以直接初始化成数组名(不用加&取地址操作符)
细节:
arr[i] 其实就可以看成 *(arr+i) 。因为arr为首元素的地址,而指针指向的变量也是指向其首元素地址,所以当指针指向数组时可以表示成,arr[i] == *(p+i) == *(arr+i),指针通过前进指向下一个空间(注意类型的不同情况)。
附:加法具有交换律,所以还可以写成*(i+p) / *(i+arr)== i [arr] , [ ] 是操作符而i与arr只是操作数所以可以交换。
所以p+i其实就是arr【i】的地址,再加上*找到其空间(所以在改变其数组内容时可以直接通过指针进行改变,注指针甚至可以在其他函数内对主函数的数组进行改变)
6.二级指针及多级指针
知识点:
指针变量也是变量,所以他就会有相对应的地址。即我们在二级指针中所存的地址其实就是(一级)指针的地址。
附: 多级指针的用法类似如三级指针,其用法就是***p2 就可以找到a
7.指针数组
知识点:
虽然叫着指针,指针数组任然是一个数组。他就是数组的一种类型如int arr[ ] , 字符数组一样。其表现形式是int* arr[ ] .... ; 在整形指针中他存放的是一个个整形,而在指针数组中的整形指针数组他存放的是一个个整形指针
所以当我们用arr[i]找到其存放的内容,在加上* --> *(arr[0]) == *(&a) == a
本章完。预知后事如何,暂听下回分说。
持续更新大量C语言细致内容,三连关注哈