指针
在C语言中,指针是一种特殊的变量,它存储了另一个变量的内存地址。指针在C语言中扮演着非常重要的角色,它们用于直接访问内存、动态内存分配、数组操作、函数参数传递以及实现数据结构等。
在指针运算中,*(解引用)和 ++(后置递增)的优先级和计算顺序非常重要,直接影响代码的行为。下面我们彻底理清这个问题:
*p++ 的执行顺序
*p++ 的实际行为分两步(先使用 p,再递增 p):
1、先计算
p++ 的值(返回 p 的当前值,但副作用是 p 会递增)。
2、然后解引用
(* 作用于 p++ 的返回值,即 p 的原始值)。
当使用 * 解引用指针时,CPU 会执行以下操作:
1.读取指针变量存储的地址
(例如 ptr 存储 0x7ffd...)。
2.根据地址找到内存中的数据
(例如 0x7ffd... 处存储 100)。
3.返回该地址处的数据
(100)。
p[i]场景在指针面前就是地址,但是在数组面前就是访问元素的方法因为定义了指针返回类型 int*名字[ ]类型此时p[i] *(p+i)一定是地址。而int 类型 普通类型 int *p 定义的可以用p[i]遍历指针,也可以用*(p+i)=arr[i]
- *(*(p + i)) 解析
p 是一个 指针数组(int *p[len]),存储的是 arr 各个元素的地址。
p + i 是指针数组的第 i 个元素的地址(因为 p 是 int ** 类型)。
*(p + i) 等价于 p[i],即第 i 个指针(指向 arr[i])。
*(*(p + i)) 等价于 *p[i],即 arr[i]。
示例
如果 p 存储的是 &arr[0], &arr[1], &arr[2], ...:
((p + 0)) → *p[0] → arr[0]
((p + 1)) → *p[1] → arr[1]
((p + 2)) → *p[2] → arr[2]
✅ 正确访问数组元素的方式。
- *(*p + i) 解析
p 仍然是一个指针数组(int *p[len])。
*p 等价于 p[0](即第一个指针,指向 arr[0])。
*p + i 是 p[0] + i,即 &arr[0] + i(指针算术运算,相当于 &arr[i])。
*(*p + i) 等价于 arr[i]。
示例
如果 p[0] 指向 arr[0]:
*(*p + 0) → arr[0]
*(*p + 1) → arr[1]
*(*p + 2) → arr[2]
✅ 看起来也能访问数组元素,但存在严重问题!因为首地址永远不变,实现指针的指向地址交换一般不会采用这种写法
#include <stdio.h>
int main(int argc, char const *argv[])
{
int a = 10;
float b = 20.0f;
char c = 'a';
int *p1 = &a;
float *p2 = &b;
char *p3 = &c;
/*
指针本身是有大小的,但它的大小与指向的数据类型无关,
而是由系统的寻址能力决定(即系统是 32 位还是 64 位)。64大小是8
p1 是指针指向的变量的地址(即 &a)。
&p1 是指针自己的地址。
打印指针指向的变量的地址时,直接打印 p1 即可,无需再取地址。
*/
printf("%zu\n", sizeof(p1));
printf("%p\n", p1);
printf("%p\n", &p1);
printf("%zu\n", sizeof(p2));
printf("%zu\n", sizeof(p3));
}
运行结果
#include<stdio.h>
#include<string.h>
void method();
int method1(int a,int b);
int main(int argc, char const *argv[])
{
// int a=10;
// int *p=&a;
int* 指针 指针指向的类型
*指针名 意思解引用
// printf("%d\n",*p);
// *p=20;
// printf("%d\n",*p);
// int arr[]={1,2,3,4,5};
// int len=sizeof(arr)/sizeof(arr[0]);
// p=arr;//这个时候arr数组会退化为第一个元素
// for (int i = 0; i < len; i++)
// {
// printf("%d\n",p[i]);//p[i]因为退化为第一个元素此时相等于偏移量就是*p(i+0),*p(i+1)
// }
// char *arrstring;
// char string[100]="abcdefg";
// int len1 = strlen(string);
// printf("%d\n",len1);
// arrstring=string;//退化为第一个指针加*相当于元素类型所以这里咱们应该不加* *arrstring=string;
// for (int i = 0; i <len1; i++)
// {
// printf("%c\n",arrstring[i]);
// }
//函数指针
// void (*p)()=method;
// int (*p2)(int,int)=method1;
// p();
// int a= p2(5,4);
// printf("%d\n",a);
//二维数组遍历
// int arr[][3]={1,2,3,4,5,6,7,8,9};
// int (*p)[3]=arr;//这里定义的数组指针,二维数组用数据类型(*p)[列大小]=数组元素首地址
// int row=sizeof(arr)/sizeof(arr[0]);
// int col=sizeof(arr[0])/sizeof(arr[0][0]);
// for (int i = 0; i < row; i++)
// {
// for (int j = 0; j < col; j++)
// {
// printf("%d ",arr[i][j]);
// }
// printf("\n");
// }
}
// void method(){
// printf("我是一个没有参数的函数指针\n");
// }
// int method1(int a,int b){
// return a+b;
// }
基础指针
#include <stdio.h>
void arr_point(int arr[], int len);
int main(int argc, char const *argv[])
{
int arr[] = {10, 24, 56, 78};
int len = sizeof(arr) / sizeof(arr[0]);
arr_point(arr, len);
return 0;
}
void arr_point(int arr[], int len)
{
int *p = arr;
for (int i = 0; i < len; i++)
// a[i]==*(a+i)
{ // 数组arr[下标]本身就是地址计算,C语言标准指针只是封装了相当于解引用*(arr+i)
// printf("%p\n", arr + i); // arr[0]等价于地址计算等价于arr+0*sizeof(int) arr+0*4 相当于解引用*(arr+i)
// arr[1]等价于地址计算等价于arr+1*sizeof(int) arr+1*4
// printf("%d\n", p[i]);
// printf("%d\n",*(p++));
//printf("%d\n", *p++);
printf("%d\n",*p+1);
// p[0]等价于地址计算等价于p+0*sizeof(int) p+0*4=p 偏移0个单位
// p[1]等价于地址计算等价于p+1*sizeof(int) p+1*4=p+4偏移4个字节1个单位
}
}
C语言中的指针是一种特殊的变量,它存储的是另一个变量的内存地址。通过指针,程序可以直接访问和操作内存,这使得指针成为C语言中非常强大和灵活的特性。以下是一些关于C语言指针的基本知识点:
1. 声明指针:
int *ptr; // 声明一个指向整型数据的指针
2. 指针初始化: 指针在使用前需要被初始化,否则它可能指向任意内存地址,这会导致未定义的行为。
int var = 10;
int *ptr = &var; // ptr 现在指向 var 的地址
3. 解引用指针: 使用星号(*)操作符可以访问指针指向的值。
int value = *ptr; // value 现在存储 ptr 指向的值,即 var 的值 10
4. 指针运算: 指针可以进行加法和减法运算,但这些运算通常用于数组。
int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr;
ptr++; // ptr 现在指向 arr[1] 的地址
int value = *ptr; // value 为 2
5. 指针与数组: 数组名在大多数表达式中会被解释为指向数组第一个元素的指针。
int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr; // 等同于 int *ptr = &arr[0];
6. 指针与函数: 指针可以作为函数的参数,允许函数直接修改传入的变量。
void increment(int *num) {
*num = *num + 1;
}
int main() {
int value = 5;
increment(&value);
printf("%d", value); // 输出 6
return 0;
}
7. 指针类型转换: 不同类型的指针可以相互转换,但通常需要显式转换。
char *cptr = "Hello";
int *iptr = (int *)cptr; // 将 char 指针转换为 int 指针
8. 指针与动态内存分配: 使用 malloc 和 free 可以在堆上动态分配和释放内存。
int *ptr = (int *)malloc(sizeof(int) * 10);
if (ptr != NULL) {
// 使用内存
}
free(ptr);
9. 空指针: 空指针(NULL pointer)是一个指针常量,其值为0,表示没有指向任何对象。
int *ptr = NULL;
10. 指针数组与数组指针: 指针数组是存储指针的数组,而数组指针是指向数组的指针。
int arr[3] = {10, 20, 30};
int *arr_ptr[3] = {&arr[0], &arr[1], &arr[2]}; // 指针数组
int (*arr_ptr2)[3] = &arr; // 数组指针
指针是C语言的核心概念之一,掌握指针对于深入理解C语言和进行系统编程至关重要。
总结来说, & 用于获取变量的地址,取地址,取指针的作用,也就是地址运算符,而 * 用于访问指针指向的值解指针。
数据类型* 变量名
1、数据类型要跟指向变量的类型保持一致
2、* 表示标记
3、变量名就是自己起的名字
printf("%p",b)如果是单独的b字母表示一个指针的名字
指针变量占用的大小,跟数据类型无关,跟编译器有关
32位:4字节
64位:8字节
给指针变量赋值的时候,不能把一个数值赋值给指针变量
如下错误:
int a=10;
int*p=500;
作用1:操作其他函数中的变量
注意:函数中变量的生命周期跟函数相关,函数结束了,变量也会消失此时在其他函数中,就无法通过指针使用了
#include <stdio.h>
int*method();
int main()
{
int*p=method();
for (int i = 0; i < 5; i++)
{
printf("拖点时间\n");
}
}
int *method(){
int a=10;
return &a;
}
如果不想函数中的变量被回收,可以在变量前面加static关键字
#include <stdio.h>
int*method();
int main()
{
int*p=method();
for (int i = 0; i < 5; i++)
{
printf("拖点时间\n");
}
printf("%d",*p);//不能打印的,因为method函数结束后,那么该函数里面所有的变量也会随着消失
}
int *method(){
static int a=10;//加上static此时变量一直保存到程序结束
return &a;
}
作用2:函数返回多个值
作用3:函数的结果和计算状态分开
作用4:方便的操作数组和函数
案例
1、修改变量中的值
#include <stdio.h>
int main()
{
int a=20;
int *p=&a;
*p=200;
printf("%d\n",*p);
printf("%d\n",a);
return 0;
}
2、交互变量中记录的值
#include <stdio.h>
void swap(int *p1,int *p2);
int main()
{
//作用一:交换两个变量的值
int a=10;
int b=20;
printf("交换之前的值为%d %d\n",a,b);
swap(&a,&b);
printf("交换之前的值为%d %d",a,b);
}
void swap(int *p1,int *p2){
int temp;
temp=*p1;
*p1=*p2;
*p2=temp;
}
3、练习:定义一个函数,求数组的最大值和最小值,并进行返回
#include <stdio.h>
int arrswap(int arr[],int len,int *max,int*min);
int main()
{
int arr[]={10,23,34,45,78,2};
int len =sizeof(arr)/sizeof(int);
int max=arr[0];
int min=arr[0];
arrswap(arr,len,&max,&min);
printf("这个数组最大值是%d",max);
printf("这个数组最小值是%d",min);
}
int arrswap(int arr[],int len,int *max,int*min){
*max=arr[0];
*min=arr[0];
for(int i=1;i<len;i++){
if(arr[i]>*max){
*max=arr[i];
}
}
for(int i=0;i<len;i++){
if(arr[i]<*min){
*min=arr[i];
}
}
}
运行结果
运行结果
4、函数的结果和计算状态分开练习:定义一个函数,将两数相除,获取他们的余数
#include <stdio.h>
int getRemainder(int num1,int num2,int*res);
int main()
{
int a=10;
int b=3;
int res=0;
int flag=getRemainder(a,b,&res);
printf("%d\n",flag);
if (!flag)
{
printf("获取的余数为%d",res);
}
}
//返回值:表示计算机的状态,0正常,1不正常
int getRemainder(int num1,int num2,int*res){
if (num2==0)
{
return 1;
}
*res=num1%num2;
return 0;
}
运行结果
指针高级(进阶)
指针的计算
加法:指针往后面移动了N步P+1
减法:指针往前面移动了N步P-1
步长跟数据类型有关
1.指针 +1或者指针 -1是什么意思?把指针中记录的内存地址,往后或者往前移动一个步长
2.什么是步长?跟什么有关?
跟数据类型有关
Windows64位操作系统:
char:移动一个字节
short:移动两个字节
int:移动四个字节
long:移动四个字节
long long:移动八个字节
#include <stdio.h>
int getRemainder(int num1,int num2,int*res);
int main()
{
int a=10;
int b=10;
int*p=&a;
printf("%d\n",p-1);
printf("%d\n",p);
printf("%d\n",p+1);
printf("%d\n",p+2);
}
运行结果图
指针有意义的操作和无意义的操作
指针运算有意义的操作:
指针跟整数进行加、减操作(每次移动一个步长)指针跟指针进行减操作(间隔步长)
指针运算无意义的操作:
指针跟整数进行乘除操作指针跟指针进行加、乘、除操作(野指针)
案例
#include <stdio.h>
int main()
{
//前提条件:保证内存空间是连续的
int arr[]={10,23,45,17,8,9};
//获取0索引的内存指针
int *p=&arr[0];
//通过内存地址(指针p)获取数据
printf("%d\n",*p);
printf("%d\n",*(p+1));
printf("%d\n",*(p+2));
printf("%d\n",*(p+3));
}
运行结果
野指针和悬空指针
野指针:指针指向的空间未分配或者无效
int main(int argc, char *argv[])
{
// 野指针:指针指向的空间没有分配
int a = 10;
int *p1 = &a; // p1 指向变量 a,是有效指针
printf("%p\n", p1); // 输出 p1 的地址(例如:0x7ffe53a2d1ac)
printf("%d\n", *p1); // 输出 a 的值(10)
// p2 就是一个野指针
int *p2 = p1 + 10; // p2 指向 p1 之后的第10个 int 位置
printf("%p\n", p2); // 输出 p2 的地址(例如:0x7ffe53a2d1f4)
printf("%d\n", *p2); // 解引用野指针(危险!)
}
p1 + 10 = 0x7ffe53a2d1ac + (10 × 4)
= 0x7ffe53a2d1ac + 0x28 // 40 十进制 = 0x28 十六进制 int比特位是32
= 0x7ffe53a2d1f4
int *p2 = p1 + 10;
-
- p1 指向变量 a 的地址(例如 0x7ffe53a2d1ac)。
- p1 + 10 会将指针偏移 10 个 int 的大小(假设 int 为 4 字节,则偏移 10 * 4 = 40 字节)。
- 偏移后的地址(例如 0x7ffe53a2d1f4)可能指向:
- 栈上的其他变量(如果存在)。
- 未分配的内存区域。
- 程序禁止访问的地址。
- printf("%d\n", *p2);
-
- 解引用 p2 会读取该随机地址处的数据,导致:
- 段错误(Segmentation Fault):如果地址无效。
- 数据污染:如果地址恰好指向其他变量。
- 解引用 p2 会读取该随机地址处的数据,导致:
为什么 p2 是野指针?
- 野指针定义:指针指向未分配或无效的内存地址。
- 关键问题:p1 + 10 的计算没有确保目标地址是合法的。
- 变量 a 仅占用 4 字节(假设 int 为 4 字节),p1 + 10 超出了 a 的内存范围。
- 栈上的相邻内存可能属于其他变量或程序状态,修改这些内存会导致未定义行为。
悬空指针:指针指向的空间已分配,但是被释放了
/p3是一个悬空指针
int *p3=method();
printf("%p\n",p3);
printf("%d\n",*p3);
}
//悬空指针:指针指向的空间已分配,但是被释放了
int*method(){
int num=10;
int *p=#
return p;
}
调用方法会开辟一个栈空间,在这个方法内定义的变量会存放在这个栈空间,当方法执行结束,栈空间就是消息,变量也就随着消失,添加static后,该变量就不存在栈空间,而是存在堆空间
void 类型的指针(相当于Java中泛型)
void *p 不表示任何类型
特点:无法获取数据,无法计算,但是可以接收任意地址
不同类型的指针之间,是不能互相赋值的
void类型的指针打破上面的观念void没有任何类型,好处可以接受任意类型指针记录的内存地址
#include <stdio.h>
int*method();
int main()
{
// void 类型的指针
//1、定义两个变量
int a=10;
short b=20;
//2、定义两个指针
int *p1=&a;
int *p2=&b;
printf("%d\n",*p1);
printf("%d\n",*p2);
short *p3=&a;//因为指针类型跟原地址类型不一样
printf("%d\n",*p3);
}
会出现警告如下图
int main()
{
// void 类型的指针
//1、定义两个变量
int a = 10;
short b = 20;
//2、定义两个指针
int *p1 = &a;
int *p2 = &b;
printf("%d\n", *p1);
printf("%d\n", *p2);
short *p3 = &a; //因为指针类型跟原地址类型不一样
printf("%d\n", *p3);
// 现在这行在 main() 函数内部
void* p4 = p1;
void* p5 =p2;
}
缺点:void类型的指针,无法获取变量里面到数据,也不能进行加、减的计算
优点:相当于Java里面的泛型void类型可以接受任何数据类型
#include <stdio.h>
void * swap(void*a,void*b,int len);
int main()
{
int a=4; //现在函数引用了void类型可以是long类型 short类型,longlong类型
int b=5;
printf("交换两个数之前%d,%d\n",a,b);
swap(&a,&b,4);
printf("%d,%d",a,b);
}
//在C语言中一个字节用char单位表示
void * swap(void*a,void*b,int len){
char *p1=a;
char *p2=b;
char temp=0;
for (int i = 0; i < len; i++)
{
temp=*p1;
*p1 = *p2;
*p2=temp;
p1++;
p2++;
}
}
二级指针和多级指针
每个指针都有自己的内存地址
指针数据类型:跟指向空间中,数据的类型是保持一致的
作用:二级指针可以操作一级指针记录的地址
#include <stdio.h>
int main()
{
//定义一个一级指针;
int a=10;
int b=30;
int*p1=&a;
int **p2=&p1;
//利用二级指针修改一级指针里面记录的地址
*p2=&b;
printf("%p\n",&a);
printf("%p\n",&b);
printf("%p\n",p1);
}
/*
p2 的值是 &p1(p1 的地址)。
*p2
表示访问 p2 指向的数据,即 p1 的值(&a)。
**p2
表示访问 *p2 指向的数据,即 a 的值 10。
*/
int main()
{
//定义一个一级指针;
int a=10;
int b=30;
int*p1=&a;
int **p2=&p1;
//利用一:二级指针修改一级指针里面记录的地址
//作用二:利用二级指针获取变量中记录的数据
*p2=&b;
printf("%p\n",&a);
printf("%p\n",&b);
printf("%p\n",p1);
printf("%d",**p2);
}
数组和指针
p[i]场景在指针面前就是地址,但是在数组面前就是访问元素的方法因为定义了指针 返回类型int*名字[ ]类型此时p[i]一定是地址。而int 类型 普通类型 int *p 定义的可以用p[i]遍历指针,也可以用
*(p+i)=arr[i]
数组名=数组首元素地址=指针=数组第一个元素地址
概念:指向数组的指针,叫做数组指针(指向整个数组的指针,而不是退化为数组的数组首元素地址)
作用:方便的操作数组中的各种数据。
重点:p[i] 相当于 *(p + i),它会解引用指针
p 向前移动 i 个 int 大小的位置,并取得那个位置上的值。
#include <stdio.h>
int main()
{
//数组本身就是地址
int arr[]={10,20,30,40,50};
int len=sizeof(arr)/sizeof(int);
int *p=arr;//这里等同于int*p=arr[0]
printf("%d\n",*p);
for (int i = 0; i < len; i++)
{
printf("%d\n",p[i]);
}
}
数组指针(指针退化)
1. 数组作为函数参数传递时
当数组作为参数传递给函数时,它会退化为指向首元素的指针:
void func(int arr[]) { // 实际等价于 int *arr
printf("%zu\n", sizeof(arr)); // 输出指针大小(如8),而非数组大小
}
int main() {
int a[5] = {1, 2, 3, 4, 5};
func(a); // a 退化为 &a[0]
return 0;
}
- 关键点
函数内无法通过 sizeof 获取数组长度。
2. 数组名出现在表达式中
当数组名用于表达式(如算术运算、赋值等),它会退化为指针:
int arr[3] = {10, 20, 30}; int *p = arr; // arr 退化为 &arr[0] printf("%d\n", *(p + 1)); // 输出 20
3. 数组与指针算术运算
数组参与 +、- 等运算时,退化为指针:
int arr[5] = {0}; int *p = arr + 2; // arr 退化为指针,指向 arr[2]
4. 数组作为 printf 等函数的参数
char str[] = "Hello"; printf("%s\n", str); // str 退化为 &str[0]
5. 数组初始化指针时
int arr[3] = {1, 2, 3}; int *p = arr; // arr 退化为 &arr[0]
6. 数组作为返回值时(实际返回的是指针)
C 语言不允许直接返回数组,但可以返回指针:
int* getArray() { static int arr[3] = {1, 2, 3}; return arr; // arr 退化为 &arr[0] }
❌ 不会退化的情况
以下情况数组 不会 退化为指针:
- 使用sizeof 运算符时
int arr[5]; printf("%zu\n", sizeof(arr)); // 输出 20(假设int为4字节),此时arr仍是数组
2、使用 & 取地址时
int arr[5]; int (*p)[5] = &arr; // &arr 的类型是 int(*)[5](指向整个数组的指针)
3、作为字符串初始化的字符数组
char str[] = "Hello"; // 这里 str 是数组,未退化
- arr参与计算的时候,会退化为第一个元素的指针
- 特殊情况:
- sizeof运算的时候,不会退化,arr还是整体,
- &arr获取地址的时候,不会退化。例如:int*p=&arr,这时候arr就是整体
此时arr就是4个字节(int)
此时&arr就是20个字节(int)
二维数组
概念:把多个小数组,放到一个大的数组当中
二维数组的声明规则
- 必须指定列数
:因为编译器需要知道每个"行"占多少内存。
- 可以省略行数
:编译器会根据初始化数据自动计算。
数据类型 arr[m][n]=
{
{1,2,3,4,5.....},
{1,2,3,4,5.....},
{1,2,3,4,5.....},
{1,2,3,4,5.....},
}
#include <stdio.h>
int main()
{
int arr[]={10,20,30,40,50};
int arr1[]={60,70,80,90,100};
int len1=sizeof(arr)/sizeof(int);
int len2=sizeof(arr1)/sizeof(int);
int * combine_arry[]={arr,arr1};
int lensum[]={len1,len2};
printf("%d",lensum[0]);//相当于{10,20,30,40,50}
int len3=sizeof(lensum)/sizeof(int);
for (int i = 0; i < len3; i++)
{ //i表示二维数组的索引0 1
for (int j = 0; j <lensum[i]; j++)
{
printf("%d\n",combine_arry[i][j]);
}
}
}
利用指针遍历二维数组
#include <stdio.h>
int main()
{
int arr[3][5] = {
{1,2,3,4,5},
{7,8,9,10,11},
{2,4,6,7,8}
};
//二维数组里面存的是一维数组int[5]
//int (*p)本来就是一个指针,地址是指针,指针是地址,数组就是地址所以相当于定义了一个二级指针
// 定义一个指向包含5个int的数组的指针
int (*p)[5] = arr;
for (int i = 0; i < 3; i++) // 遍历行
{
for (int j = 0; j < 5; j++) // 遍历列(注意变量名改为j)
{
printf("%d ", p[i][j]); // 正确访问方式
// 或者等价写法:printf("%d ", *(*(p + i) + j));
}
printf("\n"); // 每行结束后换行
}
return 0;
}
#include <stdio.h>
int main()
{
int arr1[5]={1,2,3,4,5};
int arr2[5]={11,22,33,44,55};
int arr3[5]={111,222,333,444,555};
int *arr[3]={arr1,arr2,arr3};
int **p=arr;
for (int i = 0; i <3; i++)
{
for (int j = 0; j <5; j++)
{
printf("%d ",arr[i][j]);
}
}
}
指针数组和数组指针
数组指针
只要涉及到了二维数组都可以用p[i][j]此时就是获取的元素,不需要解引用
p[i]场景在指针面前就是地址,但是在数组面前就是访问元素的方法因为定义了指针返回类型 int*名字[ ]类型此时p[i]一定是地址。而int 类型 普通类型 int *p 定义的可以用p[i]遍历指针,不能用*(p+i)因为p是个整个数组可以用*p+i *p退化为第一个指针
数组指针(Pointer to an Array)是一种 指向整个数组的指针,而不是指向数组的第一个元素。它的核心特点是能直接操作整个数组(如多维数组的行),而普通指针只能操作单个元素。
数组指针存储的是整个数组的起始地址
概念:数组指针是指向数组的指针(指针变量),本质上还是指针。
作用:方便的操作数组中的各种数组
举例:int* p=arr; 步长为int(4字节)
举例:int(*p)[5]=&arr; 步长为int乘5(20字节)
//二维数组遍历
int arr[][3]={1,2,3,4,5,6,7,8,9};
int (*p)[3]=arr;//这里定义的数组指针,二维数组用数据类型(*p)[列大小]=数组首地址
int row=sizeof(arr)/sizeof(arr[0]);
int col=sizeof(arr[0])/sizeof(arr[0][0]);
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("%d ",p[i][j]);
}
printf("\n");
}
#include<stdio.h>
int main(int argc, char const *argv[])
{
//定义一个数组指针
int arr[]={32,45,67,12};
int (*p)[]=&arr;
int main(int argc, char const *argv[])
{
// 定义一个数组指针,本质是指针,指向数组
int arr[] = {10, 20, 30, 40};
int len = sizeof(arr) / sizeof(arr[0]);
int (*p)[] = &arr;
for (int i = 0; i < len; i++)
{
printf("%d \n", *(*p + i));
printf("%d \n", (*p)[i]);
printf("%d \n", (*p+0)[i]);
}
return 0;
}
#include <stdio.h>
int main(int argc, char const *argv[])
{
int arr[] = {10, 20, 30, 40};
int len = sizeof(arr) / sizeof(arr[0]);
int (*p)[len] = &arr;
for (int i = 0; i < len; i++)
{
*p=arr
printf("%d ", *(*p + i)); p指向这个数组的地址,然后可以用*p[i]*p 得到数组 arr,并退化为 int*(指向 arr[0])*p + i 等价于 arr + i,即 &arr[i](指向第 i 个元素)
//*p 得到数组 arr,并退化为 int*(指向 arr[0])。*p + i 等价于 arr + i,即 &arr[i](指向第 i 个元素)
//printf("%d ", *(*(p + i)));因为p代表的是整个数组这个会越界
printf("%d ",(*p)[i])
printf("%d ",*(p+0)[i])
}
return 0;
}
相当于(*p)就是整个arr *p 等价于 arr(即数组本身)
二维数组指针
写法1:二维数组指针指向二维数组【不推荐】
#include <stdio.h>
int main(int argc, char const *argv[])
{
//二维数组指针
int arr[][4]={10,30,45,60,40,56,78,23};
int len=sizeof(arr)/sizeof(arr[0]);
int (*p)[][len]=&arr;
for (int i = 0; i < 2; i++)
{
for ( int j = 0; j < len; j++)
{
printf("%d ",(*p)[i][j]);
}
}
}
写法2:一维数组指针指向二维数组【推荐】
#include <stdio.h>
int main(int argc, char const *argv[])
{
//二维数组指针
int arr[][4]={10,30,45,60,40,56,78,23};
int row=sizeof(arr)/sizeof(arr[0]);
//一维数组指针指向二维数组
int col=sizeof(arr[0])/sizeof(arr[0][0]);
// 等价于 &arr[0] (*p):指向数组的行 int arr[] = {100, 200, 300}; int *p = arr; 解引用p 得到第一个元素
int (*p)[col]=arr;//此时访问的就是二维数组的第一行&arr[0]把二维数组指针看成一维数组指针
for (int i = 0; i < row; i++)
{
for (int j = 0; j <col ; j++)
{
printf("%d \n",*(*(p+i)+j));//p[i][j]
}
}
}
案例
需求:有若干个学生,每个学生有4门成绩,要求在用户输入学号(int id)后,能输出该学生的全部成绩(float scores[4]),用指针函数实现。
#include <stdio.h>
// 需求:有若干个学生,每个学生有4门成绩,要求在用户输入学号(int id)后,能输出该学生的全部成绩(float scores[4]),用指针函数实现。
float *printf_score(float (*arr)[3], int id);
int main(int argc, char *argv[])
{
float arr[][3] = {
{90, 89, 70},
{78, 87, 99},
{87, 89, 67},
};
int num;
printf("请输入学生的学号0-2\n");
scanf("%d", &num);
float *p;
p = printf_score(arr, num);
int len = sizeof(p) / sizeof(p[0]);
printf("学生学号%d的各科成绩为:",num);
for (int i = 0; i <=len; i++)
{
printf("%.1f\n", p[i]);
}
}
// flaot*(arr)[]=sorce
float *printf_score(float (*arr)[3], int id)
{
float *t;
t = *(arr) + id;
return t;
}
运行结果
指针数组
只要涉及到了二维数组都可以用p[i][j]此时就是获取的元素,不需要解引用
p[i]场景在指针面前就是地址,但是在数组面前就是访问元素的方法因为定义了指针 返回类型int*名字[ ]类型此时p[i]一定是地址。而int 类型 普通类型 int *p 定义的可以用p[i]遍历指针,也可以用*(p+i)
概念:存放数组的指针
指针数组(Pointer Array)是一种 存储指针(内存地址)的数组,即数组的每个元素都是一个指针。它主要用于管理多个地址,常见于字符串处理、动态内存分配和多维数据操作。
作用;用来存放指针的地址
举例:int*p【5】,这个数组里面存放着int类型的指针
int arr1【5】={1,2,3,4,5}
int arr2【5】={1,2,3,4,5,67,8}
int*arr3[2]={arr1,arr2}
需求:将数组a中的n个整数按相反顺序存放(数组反转)
#include <stdio.h>
void arr_revser(int arr[], int len);
void arr_revser_point(int *p, int len);
void arr_revser_point_positon(int *p, int len);
int main(int argc, char const *argv[])
{
int arr[] = {10, 2, 45, 60};
int len = sizeof(arr) / sizeof(arr[0]);
// arr_revser(arr,len);//通过数组下标访问
// arr_revser_point(arr, len);//通过指针指向变量的值访问
arr_revser_point_positon(arr, len);//通过改变指针指向的方向访问指针数组
return 0;
}
// 将一个数组反转下标法
void arr_revser(int arr[], int len)
{
int temp;
for (int i = 0; i < len / 2; i++)
{
temp = arr[i];
arr[i] = arr[len - 1 - i];
arr[len - 1 - i] = temp;
}
for (int i = 0; i < len; i++)
{
printf("%d ", arr[i]);
}
}
// 改变指针指向数据的值
void arr_revser_point(int *p, int len)
{
int *first = p; // 指向第一数组元素地址
int *rear = p + len - 1; // 指向最后一个数组元素地址
while (first < rear)
{
int temp = *first;
*first = *rear;
*rear = temp;
*first++; // 等价于先自增先前移动*(first+1)
*rear--; // 等价于先自减先后移动 *(rear-1)
}
for (int i = 0; i < len; i++)
{
printf("%d ", *(p + i)); // 也可以写成p[i]
}
}
// 改变指针指向方向
void arr_revser_point_positon(int *p, int len)
{
int *arr[len];
for (int i = 0; i < len; i++)
{
arr[i] = &(*(p + i)); // 给新的指针数组&arr[下标]本身就是地址*****指针数组
}
for (int i = 0; i < len/2; i++)
{
int *temp;
temp=arr[i];
arr[i]=arr[len-1-i];
arr[len-1-i]=temp;
}
printf("反转之后的 ");
for (int i = 0; i < len; i++)
{
printf("%d ",*arr[i]);//等价于*(*(arr+i))
}
printf("\n");
printf("反转之前的 ");
for (int i = 0; i < len; i++)
{
printf("%d ",p[i]);
}
}
运行结果
#include <stdio.h>
int main(int argc, char const *argv[])
{
// 定义一个指针数组里面存放数组
int arr[] = {23, 44, 12, 78};
int arr1[] = {2, 3, 4, 5};
int arr_len = sizeof(arr) / sizeof(arr[0]);
int arr1_len = sizeof(arr1) / sizeof(arr1[0]);
int *p[] = {arr, arr1};
int p_len = sizeof(p) / sizeof(p[0]);
int sub_len[] = {arr_len, arr1_len}; // 存储每个子数组的长度
for (int i = 0; i < p_len; i++)
{
for (int j = 0; j < sub_len[i]; j++)
{
printf("%d ", p[i][j]); // 或 *(p[i] + j) *(*(p+i)+j)
}
printf("\n");
}
}
#include <stdio.h>
int main(int argc, char const *argv[])
{
//定义一个指针数组,存放指针本质是数组
int a=10,b=20,c=30,d=23;
int *arr[]={&a,&b,&c,&d};
int len=sizeof(arr)/sizeof(arr[0]);
for (int i = 0; i < len; i++)
{
printf("%d \n",*arr[i]);
printf("%d \n",*(*arr+i));
printf("%d \n",*(*(arr+i)));
}
return 0;
}
键盘录入整数
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 5 // 定义最大输入数量
int main() {
int nums[MAX_SIZE]; // 存储输入的整数
int *p[MAX_SIZE]; // 指针数组,存储每个整数的地址
printf("请输入%d个整数:\n", MAX_SIZE);
// 1. 键盘输入整数并存入数组
for (int i = 0; i < MAX_SIZE; i++) {
scanf("%d", &nums[i]);
p[i] = &nums[i]; // 将地址存入指针数组
}
// 2. 遍历指针数组输出存储的值
printf("您输入的整数是:\n");
for (int j = 0; j < MAX_SIZE; j++) {
printf("%d ", *p[j]); // 通过指针访问值
}
return 0;
}
函数和指针
格式:返回值类型(*指针名)(形参列表)//这个是函数指针
格式:返回值类型*P(形参列表) //这个是指针函数
函数指针
定义函数指针本质上是指针,是一个指向函数的指针。函数都有一个入口地址,所谓指向函数的指针,就是指向函数的入口地址。(这里的函数名就代表入口地址)
#include <stdio.h>
void method1();
int method2(int a,int b);
int main()
{
void (*p1)()=method1;//定义了一个无参数函数指针,存储的是函数代码片段
int (*p2)(int,int)=method2;//定义了一个有参数的函数指针
p1();//调用无参数的函数指针
int num=p2(10,20);//调用有参数的函数指针
printf("%d",num);
}
void method1(){
printf("我是函数指针\n");
}
int method2(int a,int b){
return a+b;
}
#include <stdio.h>
int add(int num1,int num2);
int subtract(int num1,int num2);
int mutiply(int num1,int num2);
int divide(int num1,int num2);
int main()
{
//定义一个数组去装函数的指针
//函数指针数组
int (*p[4])(int,int)={add,subtract,mutiply,divide};
printf("请录入俩个数字参与计算\n");
int num1;
int num2;
scanf("%d %d",&num1,&num2);
int choose;
printf("请录入一个数字表示要进行的计算");
scanf("%d",&choose);
int res=(*p[choose-1])(num1,num2);
printf("%d\n",res);
}
int add(int num1,int num2){
return num1+num2;
}
int subtract(int num1,int num2){
return num1-num2;
}
int mutiply(int num1,int num2){
return num1*num2;
}
int divide(int num1,int num2){
return num1/num2;
}
细节:只有形参完全相同而且返回值也要一样的函数,才能放到同一
指针函数
定义:本质上是上函数,这个函数的返回值类型是指针,这个函数称之为指针函数。(返回值是指针的函数叫做指针函数)
#include <stdio.h>
#include <stdlib.h>
// 指针函数:返回int*类型指针
int* add(int num1, int num2) {
// 动态分配内存存储结果
int* result = (int*)malloc(sizeof(int));
if (result == NULL) {
printf("内存分配失败!\n");
exit(1); // 异常退出
}
*result = num1 + num2; // 存储计算结果
return result; // 返回指针
}
int main() {
int a = 10, b = 20;
// 调用指针函数,获取返回的指针
int* sum_ptr = add(a, b);
printf("%d + %d = %d\n", a, b, *sum_ptr);
// 释放动态分配的内存
free(sum_ptr);
return 0;
}
回调函数
定义回调函数就是一个通过函数指针调用的函数。如果你把函数的指针作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时。我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。简单来说,就是使用函数指针作为函数的形参,这种函数就被称作回调函数。
#include<stdio.h>
int callback_1(int a)
{
printf("hello, this is callback_1:a=%d\n", a);
return a;
}
/**
* 回调函数2
*/
int callback_2(int b)
{
printf("hello, this is callback_2:b=%d\n", b);
return b;
}
/**
* 回调函数3
*/
int callback_3(int c)
{
printf("hello, this is callback_3:c=%d\n", c);
return c;
}
/**
* 实现回调函数(形参是函数指针的函数)
*/
int handle(int x, int (*callback)(int))
{
printf("日志:开始执行任务!\n");
int res = callback(x);
printf("日志:执行结果:%d\n",res);
printf("日志:结束执行任务!\n");
}
int main(int argc,char *argv[])
{
handle(100,callback_1);
handle(200,callback_2);
handle(300,callback_3);
return 0;
}
运行结果
常量指针与指针常量
常量指针
本质:指向常量数据的指针
语法:
const 数据类型 *变量名;
const 数据类型* 变量名;
const int *p; // p是常量指针
指向对象的数据不可改变( int a = 10; const int *p = &a; *p = 20; ,非法)
指针本身的指向可以改变( int a = 10, b = 20; const int *p = &a; p = &b; ,合法)
#include <stdio.h>
int main()
{
int a = 10; // 变量
const int *p = &a; // 常量指针
// *p = 100; // 错误,指针指向的数据不可改变
printf("%d\n", *p);// 10
int b = 20; // 变量
p = &b; // 正确,指针指向可以改变
printf("%d\n", *p);// 20
}
指针常量
本质:指针本身是常量,指向固定地址
语法:
数据类型* const 变量名; 数据类型 *const 变量名;
特性:
指向对象的数据可以改变( int a = 10; int* const p = &a; *p = 20; ,合法)
指针本身的指向不可改变( int a = 10, b = 20; int* const p = &a; p = &b; ,非法)
注意:
定义时必须初始化:
int a = 10;
int* const p = &a; // 正确
#include <stdio.h>
int main()
{
int a = 10; // 变量
int* const p = &a; // 指针常量
*p = 100; // 正确,指针指向的数据可以改变
printf("%d\n", *p);// 100
int b = 20; // 变量
// p = &b; // 错误,指针指向不可改变
printf("%d\n", *p);// 100
}
常量指针常量
本质:指针指向和指向对象的数据都不可改变
语法:
const 数据类型* const 变量名;
const 数据类型 *const 变量名;
举例:
const int* const p; // p是常量指针常量
特性:
指向对象的数据不可改变( int a = 10; int* const p = &a; *p = 20; ,非
法)
指针本身的指向不可改变( int a = 10, b = 20; int* const p = &a; p = &b; ,非法)
注意:
定义时需要初始化:
int a = 10; const int *const p = &a; // 正确
简单理解:不管是常量指针、指针常量还是常量指针常量,本质上都是一个赋值受到限制的指针变量。