C语言——指针

一、指针的基本概念和内存与地址

1.1指针的基本概念

指针是C语言中用于存储内存地址的变量。我们可以将指针理解为内存地址的别名,通过指针,我们可以间接访问和操作内存中的数据。指针的引入大大提高了程序的灵活性和效率。

1.2指针的内存与地址

内存是计算机中存储数据的地方,类似于生活中的宿舍楼,每个内存单元相当于一个房间。为了方便找到特定的内存单元,我们给每个内存单元分配了一个唯一的编号,这个编号就叫做地址。在计算机中,地址是CPU访问内存的基础,类似于生活中的房间号,它告诉CPU数据存放在内存的哪个位置。

二、运算符&和*

2.1取地址操作符(&)

取地址操作符(&)用于获取变量的内存地址。当我们对一个变量使用取地址操作符时,它会返回该变量在内存中的地址。这个地址可以被存储在一个指针变量中,以便后续通过这个地址来访问或修改该变量的值。

在这个例子中,&x 就是取地址操作符,它返回变量 x 的内存地址。这个地址被赋值给指针变量 ptr

2.2解引用操作符(*)

解引用操作符(*)用于通过指针访问指针所指向的内存地址中的值。当我们对一个指针变量使用解引用操作符时,它会返回该指针所指向的变量的值。

在这个例子中,*ptr 就是解引用操作符,它返回指针 ptr 所指向的值,即变量 x 的值。这个值被赋值给变量 y

三、const修饰指针

const 关键字在C语言中用于定义常量,即不可变的变量。当 const 用于修饰指针时,它可以有几种不同的含义,取决于 const 的位置。以下是关于 const 修饰指针的详细解释:

1.指向常量的指针
当 const 出现在指针类型之前时,它表示这个指针指向的内容是不可变的。也就是说,你不能通过这个指针去修改它所指向的数据。例如:

这里,ptr 是一个指向 const int 的指针。你可以改变 ptr 指向哪个整数(即可以改变指针的值),但是你不能通过 ptr 来修改它所指向的整数的值。

2.常量指针
当 const 出现在指针变量名之前时,它表示这个指针本身是不可变的。也就是说,一旦这个指针被初始化指向某个地址,你就不能再让它指向其他地址了。例如:

这里,是一个常量指针,它指向一个 。你不能改变 的值(即不能让它指向另一个地址),但是你可以通过 来修改它所指向的整数的值。

3.指向常量的常量指针
你也可以同时让指针本身和它指向的内容都不可变。这通过在指针类型前和指针变量名前都加上 const 来实现。例如:

这里,ptr是一个指向 const int的常量指针。你既不能改变 ptr的值(即不能让它指向另一个地址),也不能通过 ptr来修改它所指向的整数的值。

四、指针的算术运算

1.加法:当你对指针进行加法运算时,实际上是在增加指针所指向的内存地址。增加的量是所指向数据类型的大小。例如,如果ptr是一个指向int的指针,那么ptr + 1将指向ptr后一个int的位置。

2.减法:减法运算与加法相反,它将减少指针所指向的内存地址。

3.自增和自减:自增(++)和自减(--)运算符分别将指针向前或向后移动一个所指向数据类型的大小。

五、数组和指针的关系

5.1 使用指针访问数组

在C语言中,数组名实际上是一个指向数组首元素的指针。因此,我们可以使用指针来遍历和操作数组。

5.2 指针数组

指针数组是一个数组,其元素是指针。它的声明形式为类型 *数组名[数组大小]。指针数组常用于存储多个指针,例如存储多个字符串的地址。

在上面的代码中,strings是一个指针数组,它存储了四个字符串的地址。我们通过索引来访问这些地址,并使用printf函数打印出对应的字符串。

六、指针变量

6.1 指针变量的基本概念

指针变量是一种特殊的变量,它的值是一个内存地址,这个地址指向内存中的某个位置。我们可以通过指针变量来间接访问和修改该内存位置上的数据。指针变量的类型决定了它所指向的数据类型。

指针变量的声明通常使用星号(*)来表示,星号前面的类型指定了指针所指向的数据类型,星号后面的名称则是指针变量的名称。例如,以下是一个指向整数的指针变量的声明:

在上面的代码中,ptr 是一个指向整数的指针变量。它的值可以是一个整数变量的地址,通过该地址,我们可以访问和修改该整数变量的值。

6.2.1 数组指针变量

数组指针变量,通常称为指向数组的指针,它本身是一个指针,但指向的是一个数组的首地址。换句话说,它存储的是一个数组首元素的地址。数组指针变量在多维数组和动态数组的处理中特别有用。数组指针变量的声明方式如下:

其中,N 是数组中元素的个数。注意,这里的括号是必须的,它们用来指明 *ptr 是一个整体,表示一个指向数组的指针,而不是一个指向整数的指针数组。

6.2.2 数组指针变量的用法

数组指针变量常用于处理多维数组,因为它们能够让我们在函数中传递和操作整个数组。下面是一个简单的示例:

在上面的代码中,printArray 函数接收一个指向包含3个整数的数组的指针 arr 和一个整数 rows,表示二维数组的行数。在 main 函数中,我们创建了一个2x3的二维数组 matrix,并将其首地址传递给 printArray 函数。

6.3 函数指针变量

函数指针变量,顾名思义,是一个指向函数的指针。通过函数指针,我们可以在运行时动态地调用不同的函数。这在实现回调函数、函数表、插件系统等高级功能时非常有用。函数指针变量的声明方式取决于它所指向的函数的签名(即函数的返回类型和参数列表)。下面是一个指向没有参数且返回类型为 int 的函数的指针的声明:

如果函数有参数,我们需要在声明中指定参数的类型。例如,一个指向接收两个整数参数并返回整数的函数的指针可以这样声明:

6.3.1 函数指针变量的用法

函数指针变量可以用于存储函数的地址,并在需要时通过该地址调用函数。下面是一个简单的示例:

在上面的代码中,operate 函数接收两个整数和一个函数指针作为参数。根据传递的函数指针,operate 函数可以动态地调用 add 或 subtract 函数。在 main 函数中,我们分别将 add 和 subtract 函数的地址传递给 operate 函数,并打印出结果。

七、野指针

7.1 野指针的概念

野指针,指的是那些已经被释放(例如,通过free()delete)或者从未被初始化(即指向一个未知的内存地址)的指针。这些指针变量本身仍然存在于程序中,但它们所指向的内存已经不再可用或者从未存在过。因此,当程序试图通过野指针访问或修改内存时,就会发生不可预测的行为,如程序崩溃、数据损坏等。

7.2 野指针的产生原因

7.2.1 指针未初始化

在声明指针变量后,如果没有及时将其初始化为一个有效的内存地址,那么这个指针就是一个野指针。例如:

7.2.2 内存释放后继续使用

当使用free()delete释放了指针所指向的内存后,如果没有将指针置为NULL,那么这个指针就会成为一个野指针。因为这块内存可能已经被操作系统分配给其他部分使用,继续使用原来的指针访问该内存会导致不可预测的行为。例如:

7.2.3 指针越界访问

当指针访问数组或动态分配的内存时,如果超出了其合法范围(即越界),就可能会访问到不属于该指针的内存区域,从而导致不可预测的行为。虽然这本身不直接产生野指针,但越界访问可能导致程序访问到已经被释放的内存或未知的内存地址,间接产生野指针的问题。

7.3 如何避免野指针

7.3.1 初始化指针

在声明指针变量后,应立即将其初始化为NULL或一个有效的内存地址。这样可以确保在使用指针之前,它总是指向一个已知的内存地址。例如:

7.3.2 及时释放内存并置空指针

当使用完指针所指向的内存后,应立即使用free()delete释放内存,并将指针置为NULL。这样可以防止后续错误地使用已经释放的内存。例如:

7.3.3 检查指针的有效性

在使用指针之前,应检查它是否指向了一个有效的内存地址。这可以通过判断指针是否等于NULL来实现。例如:

7.3.4 避免指针运算错误

在使用指针进行运算时(如数组索引、指针偏移等),应确保运算结果不会超出合法范围。可以使用断言或其他机制来检查指针运算的有效性。例如:

  • 22
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值