C语言指针初阶详解
作者介绍:友友们好我是沐曦希,可以叫我小沐💕
作者主页:沐曦希的个人博客主页.🎉
作者的gitee:https://gitee.com/muxi-c-language.
零基础学习C语言系列:
🎈 https://blog.csdn.net/m0_68931081/category_11742786.html.
🎉小沐和友友们一样喜欢编辑,天天敲代码🤭,沉迷学习,日渐消瘦。很荣幸能向大家分享我的所学,和大家一起进步,成为合格的卷王。✨如果文章有错误,欢迎在评论区✏️指正。那么开始今天的学习吧!😘
文章目录
- 🎉指针
- 🎉指针和指针类型
-
- 🎆指针+-整数
- 🎆指针的解引用
- 🎉野指针
-
- ✨野指针成因
-
- 🚗指针未初始化
- 💫指针越界访问
- 💥指针指向的空间释放
- ✨如何预防野指针
- 🎉指针运算
-
- 🎊指针+-整数
- 🎊指针-指针
- 🎊指针的关系运算
- 🎉指针和数组
- 🎉二级指针
- 🎉指针数组
- 💫写在最后
🎉指针
指针是什么?
指针就是内存地址,指针变量是用来存放内存地址的变量,在同一CPU构架下,不同类型的指针变量所占用的存储单元长度是相同的,而存放数据的变量因数据的类型不同,所占用的存储空间长度也不同。有了指针以后,不仅可以对数据本身,也可以对存储数据的变量地址进行操作。
指针的两个要点:
- 指针是内存中一个最小单元的编号,也就是地址
- 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
总而言之:指针就是地址,口语中说的指针通常指的是指针变量。
也可以理解为内存:
指针变量
我们可以通过&(取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个变量就是指针变量
#includeint main(){int a = 10;//在内存中开辟一块空间int* p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量中,p就是一个之指针变量。printf("&p=%p\n", &a);printf("p=%p\n", p);printf("*p=%d\n", *p);}
由此可见:指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
那这里的问题是:
- 一个小的单元到底是多大?(1个字节)
- 如何编址?
经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电 平(低电压)就是(1或者0);
那么32根地址线产生的地址就会是:
00000000000000000000000000000000
00000000000000000000000000000001
…
1111111111111111111111111111111111111
这里就有2的32次方个地址。
每个地址标识一个字节,那我们就可以给 (2^32Byte == 2^32/1024KB ==
232/1024/1024MB==232/1024/1024/1024GB == 4GB)== 4G的空闲进行编址。
那么64位的机器是8G的空闲进行编址。
所以:
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
那么在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
总结: 指针是用来存放地址的,地址是唯一标示一块地址空间的。
指针的大小在32位平台(即x86)是4个字节,在64位平台(即x64)是8个字节。
例如:
#includeint main(){int* p = NULL;printf("%d\n", sizeof(p));return 0;}
在x86平台下:
在x64平台下:
🎉指针和指针类型
变量有不同类型,指针变量也是变量,故指针变量也有不同的类型。
对于:
int num=10;p=#
要将&num(num的地址)保存到p中,我们知道p就是一个指针变量,那它的类型是怎样的呢?
我们给指针变量相应的类型。
char *pc = NULL;//NULL是空指针。数值是零int *pi = NULL;short *ps = NULL;long *pl = NULL;float *pf = NULL;double *pd = NULL;
这里可以看到,指针的定义方式是: type + * 。
其实:
char* 类型的指针是为了存放 char 类型变量的地址。
short* 类型的指针是为了存放 short 类型变量的地址。
int*类型的指针是为了存放 int 类型变量的地址。
…
但是指针变量不管是什么类型都是4或者8字节,为什么又分为那么多的类型呢?指针类型的意义是什么?
🎆指针±整数
#include int main(){int n = 10;char* pc = (char*)&n;int* pi = &n;printf("%p\n", &n);printf("%p\n", pc);printf("%p\n", pc + 1);printf("%p\n", pi);printf("%p\n", pi + 1);return 0;}
指针的类型决定了指针向前或者向后走一步有多大(距离)。
🎆指针的解引用
#include int main(){int n = 0x11223344;char* pc = (char*)&n;int* pi = &n;*pc = 0; *pi = 0; return 0;}
执行pc=0;后:
执行pi=0;后:
总结:
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
比如: char* 的指针解引用就只能访问一个字节,而 int*的指针的解引用就能访问四个字节。
🎉野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
✨野指针成因
🚗指针未初始化
#include int main(){ int *p;//局部变量指针未初始化,默认为随机值 *p = 20; return 0;}
💫指针越界访问
#include int main(){ int arr[10] = {0}; int *p = arr; int i = 0; for(i=0; i<=11; i++) { //当指针指向的范围超出数组arr的范围时,p就是野指针 *(p++) = i; } return 0;}
💥指针指向的空间释放
#includeint test(){int a = 10;return &a;}int main(){int* p = test();printf("%p\n", p);return 0;}
此时即使程序能跑,但是有警告。
✨如何预防野指针
- 指针初始化
- 小心指针越界
- 指针指向空间释放即使置NULL
- 避免返回局部变量的地址
- 指针使用之前检查有效性
#include int main(){ int *p = NULL; //.... int a = 10; p = &a; if(p != NULL) { *p = 20; } return 0;}
🎉指针运算
指针运算包括:
1.指针± 整数
2.指针-指针
3.指针的关系运算
🎊指针±整数
#includeint main(){int a = 5;float arr[5] = { '\0' };float* p = NULL;//指针+-整数;指针的关系运算for (p = &arr[0]; p < &arr[5];p++){*p = a;//*p++=a;}int i = 0;for (i = 0; i < 5; i++){printf("%d ", arr[i]);}return 0;}
#includeint main(){int arr[10] = { 0 };int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);int* p = arr;for (i = 0; i < sz; i++){*p++ = 1;printf("%d ", arr[i]);}return 0;}
🎊指针-指针
#includeint main(){int arr[10] = { 0 };int* p1 = &arr[9];int* p2 = arr;printf("%d\n", &arr[9] - arr);printf("%d\n", p1 - p2);printf("%d\n", arr - &arr[9]);printf("%d\n", p2 - p1);return 0;}
因为指针-指针的绝对值得到的是指针和指针之间的元素个数。
但是不是所以的指针都可以相减的,指向同一空间的指针才能相减(有意义)。
例如:
#includeint main(){int arr[10] = { 0 };char ch[10] = { '\0' };printf("%d\n", &arr[0] - &ch[0]);return 0;}
结果是随机值。
也可以通过指针-指针来求一个字符串的长度。
#includeint my_strlen(char* str){char* start = str;while (*str != '\0'){str++;}return (str - start);}int main(){int len = my_strlen("abcdef");printf("%d\n", len);return 0;}
🎊指针的关系运算
指针的关系运算就像比较大小。
#define N_VALUES 5for(vp = &values[N_VALUES]; vp > &values[0];){ *--vp = 0;}
代码可以写成:
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--){ *vp = 0;}
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较。
🎉指针和数组
举一个例子:
#include int main(){ int arr[10] = {1,2,3,4,5,6,7,8,9,0}; printf("%p\n", arr); printf("%p\n", &arr[0]); return 0;}
可见数组名和数组首元素的地址是一样的。
结论:数组名表示的是数组首元素的地址。(2种情况除外,数组章节讲解了)
既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个就成为可能。
#include int main(){ int arr[] = { 1,2,3,4,5,6,7,8,9,0 }; int* p = arr; //指针存放数组首元素的地址 int sz = sizeof(arr) / sizeof(arr[0]); int i = 0; for (i = 0; i < sz; i++) { printf("&arr[%d] = %p p+%d = %p\n", i, &arr[i], i, p + i); } return 0;}
所以 p+i 其实计算的是数组 arr 下标为i的地址。
那我们就可以直接通过指针来访问数组。
#includeint main(){ int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; int* p = arr; //指针存放数组首元素的地址 int sz = sizeof(arr) / sizeof(arr[0]); int i = 0; for (i = 0; i < sz; i++) { printf("%d ", *(p + i)); } return 0;}
🎉二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
这就是 二级指针 。
#includeint main(){int a = 10;int* p = &a;//*表示p是指针变量,int表示指针变量所指向的对象的类型int** pp = &p;//int*是说明pp指向的对象是int*类型的,*说明pp是指针//二级指针是用来存放一级指针变量的地址printf("&a=%p\n", &a);printf("p=%p\n", p);printf("&p=%p\n", &p);printf("pp=%p\n", pp);return 0;}
对于二级指针的运算有:
*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa .
int b = 20;*ppa = &b;//等价于 pa = &b;
**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a .
**ppa = 30;//等价于*pa = 30;//等价于a = 30;
🎉指针数组
存放指针的数组就是指针数组。
指针数组是指针还是数组?
答案:是数组。是存放指针的数组。
数组我们已经知道整形数组,字符数组。
int arr1[3]={0};char ch2[3]={'\0'};
那指针数组是怎样的?
int* arr3[5];
arr3是一个数组,有五个元素,每个元素是一个整形指针。
#includeint main(){int a = 10;int b = 20;int c = 30;int arr[10] = { 0 };int* pa = &a;int* pb = &b;int* pc = &c;int* parr[10] = { &a,&b,&c };//parr是存放指针的数组//指针的数组int i = 0;for (i = 0; i < 10; i++){printf("%p\n", parr[i]);}return 0;}
#includeint main(){int arr1[4] = { 1,2,3,4 };int arr2[4] = { 2,3,4,5 };int arr3[4] = { 3,4,5,6 };int* parr[3] = { arr1,arr2,arr3 };int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 4; j++){printf("%d ", parr[i][j]);}printf("\n");}return 0;}
💫写在最后
那么今天的学习就到这里了。友友们觉得不错的可以给个关注,点赞或者收藏哦!😘感谢各位友友们的支持。
你的❤️点赞是我创作的动力的源泉
你的✨收藏是我奋斗的方向
你的🙌关注是对我最大的支持
你的✏️评论是我前进的明灯
创作不易,希望大佬你支持一下小沐吧😘
下一期见了!