C语言指针

在还没开始学习C语言之前,就听说指针部分是C语言中最难理解的。在学习了指针之后,写下这篇博客来分享一下我关于指针的理解。

C语言指针详解
1. 指针的基本概念
指针变量:存储内存地址的变量。

在这个例子中,我们首先声明了一个整型变量number并赋值为42。接着,我们声明了一个指向整型的指针变量p。然后,我们使用&运算符获取number的地址,并将这个地址赋给p。通过*p,我们可以访问到p指针指向的值,也就是number的值。我们打印了这个值,然后修改了p指向的值,也就是修改了number的值,并再次打印以确认修改。
请注意,解引用一个空指针(即指向NULL的指针)是未定义行为,通常会导致程序崩溃。在实际编程中,你应该始终检查指针是否为空,然后再进行解引用操作。
指针变量在C语言中非常强大,但也非常危险,因为它们允许你直接操作内存。错误的指针使用可能会导致内存泄漏、段错误或其他难以调试的问题。因此,在使用指针时,一定要确保你理解它们的工作原理,并遵循良好的编程实践。

指针声明:使用类型修饰符 *。

在这些例子中,我们声明了各种类型的指针。pInt是一个指向整数的指针,pFloat是指向浮点数的指针,pChar是指向字符的指针。ppInt是一个二级指针,它指向一个整数指针。pFunc和pFuncWithArgs是指向函数的指针,分别表示没有参数和带有两个整数参数的函数。pArray是一个指向含有10个整数的数组的指针。pStruct是指向一个名为MyStruct的结构体的指针。最后,ppFunc是指向函数指针的二级指针。
在声明指针时,类型名(如int、float、char等)后面跟着一个星号(*),表示这是一个指针。如果你需要声明指向指针的指针,就在类型名后面连续放两个星号(**),以此类推。对于函数指针,星号放在括号内部,表示这是一个指向函数的指针。
请注意,声明指针并不意味着指针已经被初始化或分配了内存。在使用指针之前,你通常需要为其分配内存(如通过malloc或calloc),或者将其指向一个已经存在的变量或数组。未初始化的指针可能会导致程序崩溃或未定义行为。

指针赋值:使用 & 运算符获取变量地址。

在这个例子中,我们首先声明了一个整数变量number并为其赋值为42。然后,我们声明了一个指向整数的指针p,并将number的地址赋给了p。通过解引用p(使用*p),我们输出了number的值。
接下来,我们使用malloc函数动态分配了一个能够存储10个整数的内存块,并将返回的地址赋给了dynamicArray指针。我们检查malloc是否成功分配了内存,如果malloc失败,它会返回NULL。然后,我们将dynamicArray的地址赋给了p,并通过循环为这块动态内存中的每个整数赋值。最后,我们遍历这块动态内存并输出每个元素的值。
在完成对动态内存的使用后,我们使用free函数释放了这块内存,并将p设置为NULL,以避免悬挂指针(dangling pointer)的问题。悬挂指针是指向已经释放的内存的指针,使用这样的指针可能会导致程序崩溃或其他未定义行为。
这些示例展示了如何为指针赋值,包括将变量的地址赋给指针,以及动态分配内存并将返回的地址赋给指针。在实际编程中,合理地使用指针赋值是编写高效、安全代码的关键。

指针解引用:使用 * 运算符访问指针指向的值。

在这个例子中,我们首先创建了一个整数变量value并赋值为42。然后我们创建了一个指向整数的指针p,并将value的地址赋给了p。通过使用*p,我们解引用了指针p,获取了它指向的值,即变量value的值,然后将这个值存储在dereferencedValue变量中,并打印出来。
接着,我们通过*p = 100;修改了指针p指向的值,这实际上修改了变量value的值。然后再次解引用指针p以获取修改后的值,并打印出来。
最后,我们尝试解引用一个NULL指针nullPointer。这将导致程序崩溃,因为NULL指针不指向任何有效的内存位置。在实际编程中,你应该始终检查指针是否为NULL,以避免这种错误。
记住,解引用一个NULL指针或者未初始化的指针是未定义行为,通常会导致程序崩溃或其他不可预测的结果。因此,在使用指针解引用之前,确保指针指向了有效的内存地址是非常重要的。

2. 指针运算
指针加减:在地址上加减。

在这个例子中,我们有一个包含5个整数的数组array。我们首先声明了一个指向整数的指针p,并将其初始化为指向数组的第一个元素。然后,我们通过加减操作移动指针,以访问数组中的不同元素。
请注意,当指针指向数组边界之外时,对其进行解引用将导致未定义行为。在上面的代码中,p -= 3; 将指针移动到了数组之前的位置,此时对它进行解引用是危险的。
此外,指针的加减运算也取决于指针所指向的数据类型的大小。对于数组中的整数(通常是4字节),指针加1将跳过4字节的内存。如果你有一个指向double类型的指针(通常是8字节),指针加1将跳过8字节的内存。
因此,指针的加减操作可以用来遍历数组、跳过元素,或者在内存中进行更复杂的操作,但一定要确保不要访问无效的内存地址。

指针比较:比较指针地址。

在这个例子中,我们声明了两个指向整数的指针p1和p2,并将它们分别初始化为指向数组的首元素和第三个元素。然后,我们使用==操作符来比较这两个指针是否指向相同的内存位置,使用<和>操作符来比较它们指向的位置的相对顺序。
在指针比较中,如果两个指针指向相同的内存地址,p1 == p2的结果将为true。如果p1指向的地址在p2指向的地址之前,p1 < p2的结果将为true。如果p1指向的地址在p2指向的地址之后,p1 > p2的结果将为true。
此外,我们还展示了如何使用指针比较来在数组中查找特定的值。在这个例子中,我们遍历数组,使用指针来比较每个元素是否等于要查找的值。如果找到了匹配的值,我们将指针found_ptr设置为指向该元素的指针,并退出循环。最后,我们检查found_ptr是否为非空,以确定是否找到了该值。
注意,在比较指针时,我们通常不关心它们指向的具体内容,而只关心它们指向的内存地址。因此,即使两个指针指向的内容相同,如果它们的地址不同,它们也被认为是不同的指针。

指针关系运算:比较指针指向的地址。

在C语言中,指针关系运算通常用于比较指针所指向的内存地址之间的关系,比如判断一个指针是否位于另一个指针之前或之后,或者两个指针是否指向同一个内存位置。这些关系运算包括小于(<)、大于(>)、小于等于(<=)、大于等于(>=)以及相等(==)和不等于(!=)。

在这个例子中,我们使用了各种关系运算符来比较指向数组元素的指针。由于指针本质上存储的是内存地址,因此这些关系运算符实际上是在比较内存地址的大小。
请注意,只有当两个指针指向同一个数组(或同一个对象)的内存时,关系运算符的结果才是有意义的。如果两个指针指向不同的数组或对象,则使用关系运算符比较它们是未定义行为(undefined behavior),可能会导致不可预测的结果。
在实际编程中,通常应避免比较指向不同数组或对象的指针,除非你确定它们之间的相对位置关系是有意义的。


3. 指针与数组
数组名作为指针:数组名可视为指向首元素的指针。

在上面的代码中,array 是一个整数数组,而 p 是一个指向整数的指针,被初始化为指向 array 的第一个元素。通过 p + 1、p + 2 等,我们可以访问数组中的其他元素,就像使用数组索引一样。
需要注意的是,尽管数组名在大多数情况下可以当作指针使用,但它并不是真正的指针。它不能被重新赋值指向另一个数组或内存位置。数组名是一个常量指针,指向数组第一个元素的地址,并且这个地址在数组的生命周期内是固定的。
此外,sizeof(array) 返回的是整个数组的大小(以字节为单位),而 sizeof(array[0]) 返回的是数组单个元素的大小(以字节为单位)。通过将它们相除,我们可以得到数组的长度(元素个数)。但是,这种方法依赖于数组定义的上下文,如果数组是在函数外部定义的,并且在函数内部传递了数组名作为指针,那么这种方法将不再适用,因为在函数内部,传递的数组名会退化为指向第一个元素的指针,失去了数组长度的信息。

指针与数组元素访问:通过指针访问数组元素。

在上面的代码中,p 是一个指向整数的指针,它被初始化为指向 array 的第一个元素。通过指针 p 和指针算术,我们可以访问和修改数组中的元素。例如,*(p + i) 就是访问数组中索引为 i 的元素。同样地,array[i] 在本质上也是通过指针实现的,因为数组名本身就是一个指向数组第一个元素的常量指针。
此外,我们还展示了如何通过指针修改数组中的元素。在这个例子中,我们将索引为2的元素(即原数组中的第三个元素)的值修改为300。最后,我们遍历并打印出修改后的数组。
需要注意的是,指针算术在这里是很重要的。当我们对指针进行加法运算时,实际上是在增加指针所指向的内存地址。因为指针 p 指向的是 array 的第一个元素,所以 p + 1 会指向数组的第二个元素,以此类推。这就是为什么我们可以通过 *(p + i) 来访问数组中任意位置的元素。

4. 指针与函数
指针作为函数参数:修改指针指向的值。

在上面的代码中,我们定义了一个函数 modifyValue,它接收一个 int 类型的指针作为参数。在 main 函数中,我们声明了一个整数变量 number 并初始化为 10。然后,我们调用 modifyValue 函数,并传递 number 变量的地址(使用 & 运算符获取地址)作为参数。
在 modifyValue 函数内部,我们通过解引用指针 *ptr 来访问和修改指针指向的值。因此,当 modifyValue 函数执行完毕后,number 的值被修改为 20。


指针作为函数返回值:返回指针指向的值或地址。

在这个示例中,createArray 函数负责动态分配一个整数数组,并返回一个指向该数组的指针。在 main 函数中,我们调用 createArray 来创建一个数组,并通过返回的指针来访问和修改数组的元素。
需要注意的是,由于 createArray 函数使用 malloc 动态分配了内存,因此调用者(在这个例子中是 main 函数)需要负责在适当的时候使用 free 函数来释放这块内存,以防止内存泄漏。

5. 指针与动态内存分配
malloc和free:动态分配和释放内存。

在这个示例中,我们首先定义了一个memorySize变量来指定要分配的内存块大小。然后,我们使用malloc函数来分配指定大小的内存,并将返回的指针存储在dynamicMemory变量中。分配的内存被用来存储一系列字符,然后打印出来。
重要的是,在不再需要这块内存时,我们使用free函数来释放它。释放内存后,通常将指针设置为NULL,以防止悬挂指针(dangling pointer)的问题,即指向已释放内存的指针。
运行这段代码将输出分配的内存块的内容,并显示内存已被释放的消息。
请注意,在实际编程中,你需要根据具体情况来管理内存,确保在不再需要内存时释放它,以防止内存泄漏。此外,在复杂的程序中,你可能还需要考虑错误处理、内存分配失败的情况以及多线程环境中的内存管理等问题。

动态数组和字符串:实现可变大小的数组和字符串。

在这个示例中,我们首先定义了一个str指针来指向字符串,以及一个capacity变量来跟踪当前分配的内存大小。然后,我们进入一个无限循环,在这个循环中,我们提示用户输入一个字符串。如果用户输入了"quit",则退出循环;否则,我们将用户输入的字符串追加到当前字符串的末尾。
在追加字符串之前,我们需要检查当前分配的内存是否足够容纳新的字符串。如果不够,我们使用realloc函数来增加内存的大小。这里,我们选择将容量翻倍,但也可以根据需要选择其他增长策略。
最后,我们打印出组合后的字符串,并释放掉所有分配的内存。
注意:这个示例使用了fgets来从标准输入读取字符串,这样可以安全地处理包含空格的字符串,并且不会导致缓冲区溢出。如果直接使用scanf或其他不安全的输入函数,可能会导致程序崩溃或安全漏洞。同时,这个例子也处理了realloc可能失败的情况,并在失败时释放了原有的内存。在实际编程中,你应该总是检查动态内存分配函数的返回值,以确保它们在失败时能够妥善处理。
 

  • 12
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值