📝个人主页🌹:Eternity._
⏩收录专栏⏪:C语言 “ 登神长阶 ”
🤡往期回顾🤡:C语言操作符
🌹🌹期待您的关注 🌹🌹
❀指针
前言:在编程的广阔天地里,C语言以其接近硬件的特性和强大的灵活性,始终占据着举足轻重的地位。而在这片由代码构建的领域中,指针无疑是那把开启C语言深层奥秘的钥匙。它不仅让程序员能够直接访问和操作内存,更是构建复杂数据结构、实现高效算法、进行底层开发不可或缺的工具
对于许多初学者而言,指针或许是一个令人望而生畏的概念。那些看似晦涩难懂的语法规则和抽象的概念,常常让人望而却步。然而,正如攀登高峰的过程虽然艰难,但一旦站在山顶,便能领略到前所未有的壮丽景色。学习C语言指针,也是一场从迷茫到清晰、从畏惧到掌握的旅程
在这篇文章中,我们将一起踏上这段旅程,从指针的基础概念讲起,逐步深入到内存管理、数组操作、函数参数传递等核心话题。我们将通过生动的示例、清晰的解释和实用的技巧,帮助你克服对指针的恐惧,逐步建立起对指针的深刻理解和灵活运用能力
让我们一起,用指针这把钥匙,打开C语言世界的大门,探索其中的无限可能和精彩世界!
📒1. 指针和指针类型
🌄指针的概念
指针理解的2个要点:
- 指针是内存中一个最小单元的编号,也就是地址
- 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址
指针变量
我们可以通过
&(取地址操作符)
取出变量的内存其实地址,把地址可以存放到一个变量中,这个变量就是指针变量
代码示例 (C语言):
int main()
{
int a = 10; // 在内存中开辟一块空间
int* p = &a; // 这里我们对变量a,取出它的地址,可以使用&操作符。
// a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量
// 中,p就是一个之指针变量
return 0;
}
指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)
对于32位的机器,假设有32根地址线
那么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的空间进行编址
- 在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节
- 那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址
总结:
- 指针变量是用来存放地址的,地址是唯一标示一个内存单元的
- 指针的大小在32位平台是4个字节,在64位平台是8个字节
🏞️指针 + - 整数
代码示例 (C语言):
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;
}
指针的类型决定了指针向前或者向后走一步有多大(距离)
⛰️指针的解引用
代码示例 (C语言):
int main()
{
int n = 0x11223344;
char* pc = (char*)&n;
int* pi = &n;
*pc = 0; //重点在调试的过程中观察内存的变化。
*pi = 0; //重点在调试的过程中观察内存的变化。
return 0;
}
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)
比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节
📙2. 野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
🎈野指针成因
指针未初始化
int main()
{
int *p; // 局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
指针越界访问
int main()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i <= 11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
指针指向的空间释放
(动态内存开辟的时候会提及… …)
🎩如何规避野指针
- 指针初始化
- 小心指针越界
- 指针指向空间释放,及时置NULL
- 避免返回局部变量的地址
- 指针使用之前检查有效性
int main()
{
int* p = NULL;
//....
int a = 10;
p = &a;
if (p != NULL)
{
*p = 20;
}
return 0;
}
📕3. 指针运算
🍁指针 - 指针
指针 - 指针表示两个指针相隔的距离
int main()
{
int a = 10;
int* p1 = &a;
int* p2 = p1;
p1++;
printf("%d", p1 - p2);
return 0;
}
🍂指针的关系运算
指针之间不能直接使用 <、>、<=、>= 进行比较,除非这些指针:
- 指向同一个数组的元素
- 指向同一个结构体(或联合体)的不同成员
对于指向不同数组或不同对象的指针,进行关系运算的结果是未定义的
标准规定:
- 允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较
📚4. 指针和数组
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;
}
结论:数组名表示的是数组首元素的地址
// 因此我们这么写也是可以的
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr;//p存放的是数组首元素的地址
我们可以通过数组名来访问数组元素
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,0 };
int* p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr) / sizeof(arr[0]);
for (int 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的地址
我们也就可以直接通过指针来访问数组
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("%d ", *(p + i));
}
return 0;
}
📜5. 二级指针
二级指针储存指针变量的变量
关于二级指针:
- *ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , ppa 其实访问的就是 pa
int b = 20;
*ppa = &b;//等价于 pa = &b;
- **ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a
**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;
📝6. 指针数组
指针数组储存指针的数组
int arr1[5];
char arr2[6];
int* arr3[5];
arr3是一个数组,有五个元素,每个元素是一个整形指针
📖7. 总结
在探索C语言指针的旅途中,我们不难发现,指针不仅是C语言强大功能的核心,也是理解底层内存管理和高级编程技巧的关键。通过深入学习和实践,我们逐渐揭开了指针神秘的面纱,掌握了它们如何像桥梁一样连接起变量、数组、结构体乃至更复杂的数据结构
在本文的结尾,我想强调的是,掌握C语言指针并非一蹴而就的过程,它需要时间的积累、实践的锤炼以及对错误和困惑的不懈探索。每一次对指针的深入理解,都是对C语言编程能力的一次飞跃,也是对自己逻辑思维和问题解决能力的一次提升
同时,我们也要认识到,指针虽然强大,但使用时必须格外小心。错误的指针操作可能导致内存泄露、野指针、段错误等一系列问题,严重影响程序的稳定性和安全性。因此,在享受指针带来的便利和效率的同时,我们也必须时刻铭记其潜在的风险,并养成良好的编程习惯,如初始化指针、检查空指针、合理使用指针类型等