指针
引言
当谈及C语言中最强大、最具有挑战性的概念时,指针通常会排在前列。指针是一种特殊的变量类型,它存储了内存地址而不是直接存储值。在C语言中,指针为程序员提供了对内存直接访问和操作的能力,这使得C语言在系统编程、嵌入式开发以及性能关键型应用中非常受欢迎。
指针的使用能够实现动态内存分配、数据结构的实现以及函数参数的传递,同时也是许多高级数据结构和算法的基础。然而,指针的灵活性也带来了一些挑战,如悬挂指针、野指针等问题,这些问题可能导致程序运行时的不确定行为甚至是严重的安全漏洞。
本篇文章将深入探讨C语言中指针的概念、各种用法以及常见问题,并提供实例和技巧来帮助大家更好地理解和利用指针。通过学习和掌握指针,你将能够编写更高效、更灵活的C语言程序,并且能够更好地理解计算机内存管理的工作原理。
1.初始指针
在part1我将为大家简单的介绍一下指针的概念以及基本用法和常见问题。
1.1 指针变量
在讲解指针变量之前,先给大家简单举个例子引入一下内存和地址的概念。
例子
假设有一个宿舍楼(学校用来安置学生的),楼里有100个房间,每个房间都有自己的门牌号,来方便我们快速地找到自己的房间。计算机也是的同理,CPU处理数据的时候,需要的数据的存储和读取都是在内存中(计算机用来存放数据的)完成的。那么内存的空间是如何高效的管理呢?
其实,内存中划分为一个个的内存单元,每个内存单元的大小取1个字节(8个bit位)。其中,每个内存单元,相当于一个学生宿舍,一个字节空间里面能放8个bit位,就好比8人间的宿舍,每个人就是一个比特位。
生活中,我们把门牌号也叫做地址,在计算机中我们把内存单元的编号也称为地址。地址在内存中都是连续存放的。 ——这个是非常重要的,也是贯穿始终的。 C语言中给地址起的新名字叫做:指针。
现在我们已经对内存、指针、地址有一个形象的了解了。接下来我们要学习两个操作符方便以后对 指针变量(用来存放地址的变量) 的使用。
取地址操作符和解引用操作符
1.取地址操作符 &
我们可以通过取地址操作符 &取出变量的内存起始地址,然后把地址存放到一个变量当中,这个变量就是指针变量。
示例
#include<stdio.h>
int main()
{
int a = 10; //在内存中开辟一块空间
int* p = &a; //这里我们使用&操作符取出了变量a的地址
return 0;
}
注
a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量中,p就是一个指针变量。
这里的p左边写的是int *,*是在说明p是指针变量,而前面的int是在说明p指向的是整形类型的对象。

此图片的意思是:a中存放的是数字10,他的地址是0x0012ff40,int * p = &a;这条语句的作用是,取出a的地址放入到指针变量p中存储。所以p变量中存放的是0x0012ff40。因此也就证明指针变量是指向a的。
2.解引用操作符*
我们可以通过解引用操作符*对指针变量进行解引用,换言之就是 *操作符通过指针变量中存放的地址,找到其指向的空间。
示例
#include<stdio.h>
int main()
{
int a = 100; //在内存中开辟一块空间
int* p = &a; //这里我们使用&操作符取出了变量a的地址
*p = 0;
return 0;
}
注
*p = 0;
这行代码的意思是,通过对p指针变量的解引用,找到p指向的空间——这个空间对应的就是a的空间。因此我们就可以理解为*p其实就是a变量了;所以*p = 0; ,就是把a改成0;
指针变量的大小
要想知道指针变量的大小,我们就得先明白其大小的由来。
简单的来讲,CPU和内存之间有大量的数据交换,因此我们需要一组线来链接——地址总线。我们可以简单理解,32位机器有32根地址总线,每根线只有两态,表示0,1【电脉冲有无】,那么一根线,就能表示2种含义,2根线就能表示4种含义,依次类推。32根地址线,就能表示2^32种含义,每⼀种含义都代表⼀个地址。地址信息被下达给内存,在内存上,就可以找到该地址对应的数据,将数据在通过数据总线传入CPU内寄存器。
根据前边所讲,我们可以推断出,32位机器有32根地址总线,每根地址线出来的电信号转换成数字信号后是1或者0,那我们把32根地址线产⽣的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4个字节才能存储。如果指针变量是用来存放地址的,那么指针变量的大小就得是4个字节的空间才可以。同理64位机器,假设有64根地址线,⼀个地址就是64个⼆进制位组成的⼆进制序列,存储起来就需要8个字节的空间,指针变量的大小就是8个字节。
总结
这时我们可能有个疑问,指针指向的内容不同会不会导致指针变量的大小不同呢?
答案是:不会!
指针变量的大小与类型无关,只要是指针类型的变量,其大小只取决于是32位机器(4个字节)还是64位机器(8个字节)。
1.2 指针类型的意义
大家有没有思考过一个问题,指针变量就是用来存放地址的,为什么还要有类型呢?希望我接下来的讲解,能让你心里有一个答案。
指针解引用的权限有多大
示例

首先int a = 0x11223344;申请了一段内存空间来存放a变量,并赋给a变量一个十六进制的值0x11223344,然后char * pc = &a;通过取地址操作符将a的地址取出放入到了char类型的指针pc中,最后*pc = 0;对指针变量pc进行解引用,找到pc指向的a空间,并给a赋值为0;
重点在于,右面的内存监视窗口我们会发现整形变量a一共四个字节,而11223344只有44变成了00。这就说明char类型(大小为1字节)的指针变量解引用后只改变了int类型(大小为4个字节)的一个字节。——由此我们可以得出指针的类型决定了指针解引用的时候有多大的权限(一次能操作几个字节)。
指针的步长有多大
示例

这段代码中,将整形数组的地址赋给了两个指针变量,我们可以看到int *类型的指针变量+1跳过了4个字节,char *类型的指针变量+1跳过1个字节。由此可以得出 指针的类型决定了指针向前或者向后走一步有多大(距离)。
void*指针
在指针类型中有⼀种特殊的类型是void*类型的,可以理解为无具体类型的指针(或者叫泛型指针),这种类型的指针可以用来接受任意类型地址。 但是也有局限性, void* 类型的指针不能直接进行指针的±整数和解引用的运算。
示例
#include<stdio.h>
int main()
{
int a = 10;
int* pa = &a;
char* pc = &a;
return 0;
}

在上面的代码中,将⼀个int类型的变量的地址赋值给⼀个char*类型的指针变量。编译器给出了⼀个警告,是因为类型不兼容。而是用void*类型就不会有这样的问题。
使用 void*类型的指针接收地址
int main()
{
int a = 10;
void* pa = &a;
void* pc = &a;
*pa = 10;
*pc = 0;
return 0;
}

这里我们可以看到,void*类型的指针可以接受不同类型的地址,但是无法直接进行指针运算。
总结
⼀般 void* 类型的指针是使用在函数参数的部分,用来接收不同类型数据的地址,这样的设计可以实现泛型编程的效果。使得一个函数来处理多种类型的数据。
1.3 const修饰指针——使其成为常变量不可被修改
const修饰变量可以使其成为常变量不可被修改。
在指针变量中,const摆放的位置不同,对应的作用也不同。
int main()
{
int a = 10;
int b = 20;
int const* p = &a;
//p = &b; 成功
//*p = 100; 错误

本文深入探讨C语言中指针的概念、用法及常见问题。介绍了指针变量、指针类型的意义、指针运算等基础知识,分析了野指针的成因与规避方法。还阐述了指针与数组的关系,包括数组名的理解、一维数组传参本质等,最后给出了数组和指针的笔试题解析。
最低0.47元/天 解锁文章
1165

被折叠的 条评论
为什么被折叠?



