C语言指针

指针

在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]

  1. *(*(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]

✅ 正确访问数组元素的方式。

  1. *(*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)可能指向:
      • 栈上的其他变量(如果存在)。
      • 未分配的内存区域。
      • 程序禁止访问的地址。
  1. printf("%d\n", *p2);
    • 解引用 p2 会读取该随机地址处的数据,导致:
      • 段错误(Segmentation Fault):如果地址无效。
      • 数据污染:如果地址恰好指向其他变量。

为什么 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=&num;
     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] }

❌ 不会退化的情况

以下情况数组 不会 退化为指针:

  1. 使用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 是数组,未退化

  1. arr参与计算的时候,会退化为第一个元素的指针
  2. 特殊情况:
  3. sizeof运算的时候,不会退化,arr还是整体,
  4. &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; // 正确

简单理解:不管是常量指针、指针常量还是常量指针常量,本质上都是一个赋值受到限制的指针变量。

总结对比

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值