当前位置: 首页 > news >正文

C语言-指针初级(指针定义、指针的作用、指针的计算、野指针、悬空指针、void类型指针)

本章概述思维导图:

C语言指针

指针是C语言中最强大但也最容易混淆的特性之一。它提供了直接操作内存地址的能力,使得C语言具有高效性和灵活性。下面我将详细介绍C语言指针的各个方面。

指针定义

指针的本质:指针是一个变量,其值为另一个变量的内存地址;可以理解为:(普通变量:存储数据值的变量)、(指针变量:存储内存地址的变量);

指针变量定义格式:

指针查询数据和修改数据格式:

指针注意事项:

1.数据类型 :要跟指向变量的类型保持一致;

2.定义中的' * ':这是标记,不是解引用的含义;

3.变量名和指针名都是自己起的名字;

4.指针指向变量名时,指向的是内存地址,并不是数据,所以要用取地址符&;

5.定义中的‘ * ’是标记,查询数据和存储数据中的‘ * ’是解引用,二者并不一样;

代码示例:

#include <stdio.h>
int main()
{int num1=100;int *p=&num1;printf("num1=%d\n",num1);printf("指针指向的内存地址上的值为:%d\n",*p);return 0;
}//代码运行结果:
num1=100
指针指向的内存地址上的值为:100

代码讲解:首先定义一个整型变量num1并赋值为100,然后声明一个整型指针p并用&运算符将num1的地址赋给它(此时p存储的是num1的内存地址),接着通过printf直接输出num1的值,再通过解引用指针*p(即访问p指向的内存地址中的值)输出相同结果,最终证明指针p确实指向了num1的内存空间。

指针使用细节:

1.指针变量的名字不能重复定义;

2.指针变量的数据类型要跟指向变量的类型保持一致;

3.指针变量占用的大小,跟数据类型无关,跟操作系统有关;(32位操作系统:占4个字节,64位操作系统占8个字节);

4.给指针变量赋值时,不能把一个数值赋值给指针变量;

指针的作用

指针的作用1:操作其它函数中的变量

指针的一个重要作用是在不同函数之间直接操作变量,避免全局变量的使用,同时提高代码的灵活性和效率。

代码示例(利用指针在不同函数交换变量):

#include <stdio.h>
void huan(int *p1,int *p2);//函数封装:交换变量
int main()
{int num1=100;int num2=1000;printf("初始值为:num1=%d\tnum2=%d\n",num1,num2);huan(&num1,&num2);printf("交换后值为:num1=%d\tnum2=%d\n",num1,num2);return 0;
}
void huan(int *p1,int *p2)//函数封装:交换变量
{int temp=0;temp=*p1;*p1=*p2;*p2=temp;
}//代码运行结果:
初始值为:num1=100	num2=1000
交换后值为:num1=1000	num2=100

代码讲解:在 main 函数中定义两个变量 num1 和 num2,调用 huan 函数时传递它们的地址(&num1 和 &num2),huan 函数通过指针参数(int *p1 和 int *p2)接收地址,并通过解引用(*p1 和 *p2)直接修改原始变量的值,最终完成交换。

关键点在于传递地址而非值,使得函数能操作外部变量,避免了值传递的副本限制,体现了指针在间接访问和修改数据时的核心作用。


指针的作用2:函数返回多个值

在C语言中,函数通常只能通过返回值传递一个结果,但通过指针参数,可以绕过这一限制,实现返回多个值的效果。

代码示例(在数组中找出最大值和最小值):

#include <stdio.h>
void out_max_min(int arr[],int len,int *p1,int *p2);//函数封装:找最大值和最小值
int main()
{int arr[]={88,54,69,75,25};int len=sizeof(arr)/sizeof(int);for(int i=0;i<len;i++){printf("%d\t",arr[i]);}putchar('\n');int max=0,min=0;out_max_min(arr,len,&max,&min);printf("数据中最大值为%d\t最小值为:%d\n",max,min);}
void out_max_min(int arr[],int len,int *p1,int *p2)//函数封装:找最大值和最小值
{*p1=arr[0];*p2=arr[0];for(int i=0;i<len;i++){if(*p1<arr[i])//找最大值{*p1=arr[i];}if(*p2>arr[i])//找最小值{*p2=arr[i];}}
}//代码运行结果:
88	54	69	75	25	
数据中最大值为88	最小值为:25

代码讲解:在 main() 函数中定义数组并计算长度,打印数组后调用 out_max_min() 函数,传入数组、长度以及 max 和 min 的地址;该函数先将指针指向的值初始化为数组首元素(避免初始化为 0 导致全负数数组出错),然后遍历数组,通过指针更新最大值和最小值,最终返回结果到 main() 函数并打印。


指针的作用3:函数的结果和计算状态分开

在C语言中,指针的核心价值之一是实现“计算逻辑”与“结果存储”的分离,使函数能够专注于核心任务(如数据处理),而将结果的去向交由调用者控制。

代码示例(定义函数,将两数相除,获取他们的余数。返回值为这段算数是否有意义):

#include <stdio.h>
int yu(int *p1,int *p2,int *p3);
int main()
{int num1,num2;int num3=0;printf("输入两个数智能获取余数\n");scanf("%d%d",&num1,&num2);int flag=yu(&num1,&num2,&num3);if(flag){printf("式子无意义");}else{printf("余数为:%d",num3);}return 0;
}
int yu(int *p1,int *p2,int *p3)//函数封装:将两数相处,获取余数。返回值为0这个式子有意义,返回值为1,这个式子无意义
{if(*p2 == 0){return 1;}*p3=*p1%*p2;return 0;
}//代码运行结果:
输入两个数智能获取余数
85
7
余数为:1

代码讲解:主函数中用户输入两个整数后,将它们的地址连同存储余数的变量地址一起传给yu()函数,函数内通过指针参数直接读取输入值并检查除数是否为零,若为零则返回错误标志1,否则计算余数并通过指针将结果写入主函数的变量中,最后主函数根据返回值判断输出余数或错误提示,这种设计既利用指针高效传递了计算结果,又通过返回值清晰区分了正常和异常情况。


指针作用4:方便操作数组和函数传参

指针操作数组的核心优势在于它能直接通过内存地址高效访问和修改元素,避免了数组下标计算的间接开销,同时支持动态内存分配(如用malloc创建可变大小的数组),且在函数间传递数组时只需传递首地址指针而非整个数组,极大节省了内存和时间开销,尤其适合处理大规模数据或多维数组等复杂结构,但需注意指针的边界控制以防止越界访问。

代码示例(在函数中使用指针对数组进行遍历):

#include <stdio.h>
void arr_bianli(int *p,int *p1);//函数封装:利用指针对数组遍历
int main()
{int arr1[]={1,2,3,4,5,6,7,8,9};int len=sizeof(arr1)/sizeof(int);arr_bianli(arr1,&len);return 0;
}
void arr_bianli(int *p,int *p1)//函数封装:利用指针对数组遍历
{for(int i=0;i<*p1;i++){printf("arr[%d]=%d\t",i,*(p+i));}putchar('\n');
}//代码运行结果:
arr[0]=1	arr[1]=2	arr[2]=3	arr[3]=4	arr[4]=5	arr[5]=6	arr[6]=7	arr[7]=8	arr[8]=9	

代码讲解:主函数中定义数组 arr1 并计算其长度 len,调用 arr_bianli() 时传递数组首地址(arr1)和长度指针(&len),函数内通过指针解引用获取长度(*p1)控制循环次数,并利用指针算术(*(p+i))访问每个元素并打印其下标和值,这种方式避免了数组拷贝,直接操作内存地址,既高效又清晰地展示了指针与数组的紧密关联。


指针的计算

指针的计算是C语言中操作内存地址的核心机制,它通过基于数据类型大小的地址偏移实现高效的数据访问和内存操作。

指针的定义:

指针中数据类型的作用:获取字节数据的个数;

步长:指针移动一次的字节个数

char:1个字节;        short:2个字节        int:4个字节        longlong:8个字节;

指针加法:指针往后移动了n步,总字节=n*sizeof(数据类型)=N个字节;

指针减法:指针往前移动了n步,总字节=n*sizeof(数据类型)=N个字节;

代码示例:

#include <stdio.h>
int main()
{//int类型指针步长int arr1[]={1,22,33,44};int *p1=arr1;printf("int类型指针移动2次多少字节:%d字节\narr1[2]=%d\n",2*sizeof(int),*(p1+2));//char类型指针步长char arr2[]="abcd";char *p2=arr2;printf("char类型指针移动2次多少字节:%d字节\narr2[2]=%c\n",2*sizeof(char),*(p2+2));return 0;
}//代码运行结果:
int类型指针移动2次多少字节:8字节
arr1[2]=33
char类型指针移动2次多少字节:2字节
arr2[2]=c

代码讲解:指针的加减运算会根据其指向的数据类型自动计算地址偏移量:当对指针进行 +n 或 -n 操作时,实际移动的字节数等于 n × sizeof(数据类型)(例如 int* 每次移动4字节,char* 每次移动1字节),因此 *(p + i) 能直接访问数组第i个元素(如 int* p 指向数组时,p+2 会跳过8字节访问 arr[2]),这种机制使得指针运算与数组索引天然对应,但需注意避免越界访问。

指针步长和指针变量占用大小的区别

指针步长和指针变量占用大小是两个完全不同的概念,前者决定指针移动时的地址偏移量,后者反映指针变量本身在内存中占用的空间。

指针步长:由指针指向的数据类型决定,计算公式为:步长 = sizeof(数据类型);

指针变量占用大小:指针变量本身在内存中占用的字节数。由操作系统和编译器决定(与指针类型无关),通常为:32位系统:4字节、64位系统:8字节;

C语言野指针

野指针(Wild Pointer)是指未初始化或已释放的指针变量,其指向的内存地址不可预测或无效。访问野指针会导致未定义行为(如程序崩溃、数据损坏)。

野指针的常见成因

1.未初始化的指针

代码示例:

#include <stdio.h>
int main()
{int *p;*p=10;printf("%d\n",*p);return 0;
}//代码运行结果:
段错误 (核心已转储)

代码讲解:未初始化,p的值是随机地址(野指针),危险!可能访问非法内存;


2. 指针越界访问

代码示例:

#include <stdio.h>
int main()
{int arr[3]={1,2,3};int *p=arr;p+=5;printf("%d\n",*p);return 0;
}//代码运行结果:
-912009440

C语言野指针总结

野指针的本质:指向不可控或无效内存的指针。

防范手段:

1. 初始化指针为 NULL

2. 释放内存后立即置空。

3. 确保指针操作在合法范围内。

C语言悬空指针

悬空指针是指指向已被释放或回收的内存地址的指针。此时指针仍保存着旧的内存地址,但该地址已无效,访问它会导致未定义行为(如程序崩溃或数据损坏)。

悬空指针的常见成因

1.内存被释放后未置空

代码示例:

#include <stdio.h>
#include <stdlib.h>
int main()
{int *p=malloc(sizeof(int));*p=10;free(p);*p=20;printf("%d\n",*p);return 0;
}//代码运行结果:
段错误

代码讲解:free已经释放内存了,此时p仍指向原地址,成为悬空指针,危险!访问已释放的内存


2.局部变量指针超出作用域

代码示例:

#include <stdio.h>
int *num(void);//函数封装:返回数据
int main()
{int *p=*num();printf("%d",*p);return 0;
}
int *num(void)//函数封装:返回数据
{int num1=10;return &num1;
}//代码运行结果:
段错误 (核心已转储)

代码讲解:函数num返回局部变量num1的地址,可是num1在函数结束后被销毁释放了。因此成为悬空指针,在主函数中*p是属于未定义行为;

C语言悬空指针总结

悬空指针的本质:指针与内存生命周期不同步。

防范手段:

1.释放内存后立即置空(p = NULL)。

2.不返回局部变量的地址。

3.使用动态内存分配(malloc)替代局部变量。


void类型的指针

void*(无类型指针)是一种通用指针类型,可以指向任意数据类型(如 intfloat、结构体等),但不能直接解引用(即不能通过 * 访问值),必须先转换为具体类型的指针。

代码示例:

#include <stdio.h>
int main()
{int num1=10;void *p=&num1;int *p1=(int*)p;printf("%d\n",*p1);return 0;
}//代码运行结果:
10

代码讲解:先定义一个整型变量 num1 并赋值为 10,再用 void* p 存储其地址(此时 p 仅保存地址信息,不关联具体类型),接着通过显式类型转换 (int*)p 将 void* 还原为 int* 指针 p1,最后通过 p1 解引用并打印 num1 的值(10),体现了 void* 作为通用指针需转换后才能操作数据的特性,同时强调了类型转换必须与实际数据类型匹配,否则会导致未定义行为。


制作不易!喜欢的小伙伴给个小赞赞!喜欢我的小伙伴点个关注!有不懂的地方和需要的资源随时问我哟!

http://www.dtcms.com/a/312239.html

相关文章:

  • Spring框架深度学习实战
  • ⭐CVPR2025 单目视频深度估计新框架 Seurat
  • 嵌入式系统的中断控制器(NVIC)
  • rosdep的作用以及rosdep install时的常用参数
  • 质数时间(二分查找)
  • ​​​​​​​第二十一天(CDN绕过)
  • EPICS aSub记录示例2
  • [学习笔记-AI基础篇]02_深度基础
  • Kotlin协程极简教程:5分钟学完关键知识点
  • 工业场景工服识别准确率↑32%:陌讯多模态融合算法实战解析
  • OpenVLA复现
  • 23th Day| 39.组合总和,40.组合总和II,131.分割回文串
  • Linux—进程状态
  • 深入 Go 底层原理(九):context 包的设计哲学与实现
  • 智能手表:电源检查
  • Java多线程详解(2)
  • 一、灵巧手捉取几何原理——空间五指平衡捉取
  • GraphRag安装过程中的报错:系统找不到指定的文件(Could not install packages due to an OSError)
  • AI赋能测试:技术变革与应用展望
  • C++const成员
  • [网安工具] Web 漏洞扫描工具 —— AWVS · 使用手册
  • 机器学习【五】decision_making tree
  • Linux重定向和缓冲区
  • Piriority_queue
  • 三、摩擦刚体——捉取质量函数
  • ARP协议是什么?ARP欺骗是如何实现的?我们该如何预防ARP欺骗?
  • 前端与后端部署大冒险:Java、Go、C++三剑客
  • Codeforces Round 1039 (Div. 2) A-C
  • nodejs读写文件
  • 数据类型Symbol