C语言:数组指针

指针

变量指针与指针变量

指针变量做函数参数

指针变量做函数参数往往传递的是变量的首地址,借助于指针变量间接访问是可以修改实参变量数据的。

指针有一个作用,通过形参修改实参,我们将这样的参数称之为输出型参数

案例:

  • 需求:有a,b两个变量,要求交换后输出,使用函数处理,用指针变量做函数的参数

  • 方式1:交换指向(指针指向改变,指向对象的数据不变)

    #include <stdio.h>
    /**
    * 方式1:交换指向
    */
    void swap(int *p_a, int *p_b)
    {
        int *p_t;
        // 交换
        p_t = p_a;
        p_a = p_b;
        p_b = p_t;
        printf("交换后:%d,%d\n",*p_a, *p_b); // 交换后:4,3
    }
    int main(int argc,char *argv[])
    {
        int a = 3, b = 4;
        printf("交换前:%d,%d\n", a, b);// 交换前:3,4
        swap(&a, &b); // 传参的过程可以理解:int *p_a = &a, int *p_b = &b;
        return 0;
    }

  • 方式2:交换数据(指针指向不变,指向对象的数据改变)

    #include <stdio.h>
    /**
    * 方式2:交换数据
    */
    void swap(int *p_a, int *p_b)
    {
        int temp;
        // 交换
        temp = *p_a;
        *p_a = *p_b; // 将p_b指向对象的值赋给p_a指向的对象
        *p_b = temp; // p_b:访问指针变量的空间,*p_b:访问指针指向对象的空间
        printf("交换后:%d,%d\n",*p_a, *p_b); // 交换后:4,3
    }
    int main(int argc,char *argv[])
    {
        int a = 3, b = 4;
        printf("交换前:%d,%d\n", a, b);// 交换前:3,4
        swap(&a, &b); // 传参的过程可以理解:int *p_a = &a, int *p_b = &b;
        return 0;
    }
    指针变量指向数组元素(重难点)
    数组元素的指针
  • 数组的指针就是数组中第一个元素的地址,也就是数组的首地址。

  • 数组元素的指针是指数组的首地址。因此,同样可以用指针变量来指向数组或者数组元素。

  • 在C语言中,由于数组名代表数组的首地址,因此数组名实际上也是指针。访问数组名就是访问数组首地址。

  • #include <stdio.h>
    int main(int argc,char *argv[])
    {
        // 创建一个数组
        int arr[] = {11,22,33};
        int *p1 = &arr[0]; // 指针变量指向数组arr第一个元素,指针的范围就是数
        组元素
            int *p2 = arr; // 等价于上面写法,数组名默认就是一个指向首元素地
        址的指针,推荐
            printf("%p,%p,%p\n", p1, p2, arr); //
        0x7ffe33135e2c,0x7ffe33135e2c,0x7ffe33135e2c
            return 0;
    }

    注意:虽然我们定义了一个指针变量接收了数组地址,但不能理解为指针变量指向了数组,而应该理解为指向来了数组的元素(默认为第1个元素)。

    指针的运算

    指针运算:前提是指针变量必须要指向数组的某个元素。(指针运算只能在同一数组内进行,并且只能是元素之间的偏移)

    序号指针运算偏移量说明
    1自增:p++++pp+=1sizeof(type)指向下一个元素的首地址(需边界检测,防止越界)
    2自减:p----pp-=1sizeof(type)指向上一个元素的首地址(需边界检测,防止越界)
    3加 n 个数:p+nn * sizeof(type)指向后面 n 个元素的首地址(需边界检测,防止越界)
    4减 n 个数:p-nn * sizeof(type)指向前面 n 个元素的首地址 (需边界检测,防止越界)
    5指针相减:p1-p2|(p1 - p2)| / typep1,p2之间相差几个元素
    6指针比较:p1 < p2逻辑值:真(1),假(0)前面的指针小于后面的指针

    注意:

  • 上面表格中的type,是指针指向数组的元素的类型

  • sizeof不支持运算,举例:

    #include <stdio.h>
    int main(int argc,char *argv[])
    {
        int a = 10;
        printf("sizeof(a)=%lu,sizeof(int)=%lu,sizeof(++a)=%lu\n",
               sizeof(a), sizeof(int), sizeof(++a)); //
        sizeof(a)=4,sizeof(int)=4,sizeof(a++)=4
            return 0;
    }

    说明

    ① 如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的 下一个元素,p-1指向同一数组中的上一个元素。即p+1或p-1也表示地址。但要注意的是,虽然指针变量p中存放的是地址,但p+1并不表示该地址加1,而表示在原地址的基础上加了该数据类型所占的字节数d(d = sizeof(数据类型)) 。

    ② 如果p原来指向a[0],执行++p后p的值改变了,在p的原值基础上加d,这样p就指向数组的下一个元素a[1]。d是数组元素占的字节数。

    ③ 如果p的初值为&a[0]则p+i 和a+i 就是数组元素a[i]的地址,或者说,它们指向a数组的第 i 个元素 。

    (p+i) 或(a+i)是p+i或a+i所指向的数组元素,即a[i]。

    ⑤ 如果指针变量p1和p2都指向同一数组,如执行p2-p1,结果是两个地址之差除以数

    组元素的长度d。

  • 案例

    #include <stdio.h>
    #include <math.h>
    int main(int argc,char *argv[])
    {
        // 创建一个用来实现指针运算的数组
        int arr[] = {11,22,33,44,55};
        int *p1 = arr + 4; // 55 等价于 arr[4]
        int *p2 = arr + 1; // 22 等价于 arr[1]
        size_t size = fabs(p2 - p1); // 3 = fabs(22对应的地址 - 55对应的地址) / int的字节数
        printf("*p1=%d,*p2=%d,size=%lu,&arr[1]+2=%d\n", *p1, *p2, size, *(&arr[1]+2));
        return 0;
    }

  • 运行结果:

 

案例

  • 需求;通过下标法和指针法遍历数组

  • 代码

#include <stdio.h>
/**
* 下标法遍历数组
*/
void arr1(int arr[], int len) // 数组作为函数参数,传递的是数组的首地址(数组被降级为指针)
{
    for (register int i = 0; i < len; i++) printf("%-4d", arr[i]);
    printf("\n");
}

/**
* 指针法遍历数组
*/
void arr2(int arr[], int len)
{
    // 创建一个指针变量,接收数组,此时实际上接收到的是数组中第一个元素的地址
        int *p = arr;
    for (register int i = 0; i < len; i++) printf("%-4d",/* *(arr+i) 等价于*/ *(p+i));
    printf("\n");
}

/**
* 指针法遍历数组
*/
void arr3(int arr[], int len)
{
    int *p = arr;
    for (register int i = 0; i < len; i++)
    {
        printf("%-4d", *p);
        p++;
    }
    printf("\n");
}

/**
* 指针法遍历数组
*/
void arr4(int arr[], int len)
{
    int *p = arr;
    for(; p < arr + len; p++) // 判断的时候不能写作 p + len,因为p在变化,而arr没有变化
    {
        printf("%-4d", *p);
    }
    printf("\n");
}
int main(int argc,char *argv[])
{
    int arr[] = {11,22,33,44,55};
    int len = sizeof(arr) / sizeof(arr[0]);
    arr1(arr, len);
    arr2(arr, len);
    arr3(arr, len);
    arr4(arr, len);
    return 0;
}

 

案例

  • 需求:推导以下代码执行过程

  • 代码

int arr2()
{
    // 创建一个普通数组
    int arr[] = {11,22,33,44,55,66,77,88};

    int *p = arr;
    printf("%d\n", *p); // 11

    p++; // 指针偏移 1 * sizeof(int) 指针移动到22这个位置
    printf("%d\n", *p); // 22

    int x = *p++; // 第1步:解引用p的值赋值给x,x = 22;第2步:p++,指针移动到33这个位置
    printf("%d,%d\n", x, *p);// 22,33

    int y = *(++p); // 第1步:++p,指针偏移到44这个位置;第2步:对44这个地址解引用,得到44
    printf("%d,%d\n", y, *p);// 44,44

    (*p)++;      // 第1步:对p解引用得到44;第2步:对44这个值+1,得到45
    printf("%d\n",*p); // 45
}

p++ 与 (p)++ 区别

1. *p++(指针自增)
  • 行为:先解引用 p,再将指针 p 自增(偏移一个元素)。

  • 代码示例

     int arr[] = {11,22,33}, *p = arr;  
     int x = *p++; // x=11(解引用取11),p随后指向22(指针偏移)  

2. (*p)++(数值自增)
  • 行为:先解引用 p,再将解引用得到的数据值自增。

  • 代码示例

     int arr[] = {11,22,33}, *p = arr;  
     int x = (*p)++; // x=11(解引用取11),*p的值随后变为12(数据自增)  

通过指针引用数组元素

引用一个数组元素,可以用:

①下标法:如``

②指针法:如*(arr + 1) 或者 (p+i)。其中arr是数组名,p是指向数组元素的指针变量,其初始值:p = arr

指针法遍历数组的 3 种写法比较:

  • 第①种写法和第②种写法执行效率相同。系统是将arr[i]转换为*(arr+i)处理的,即先计算出地址,因此比较费时。

  • 第③种方法比第①②种方法快。用指针变量直接指向数组元素,不必每次都重新计算地址。(p++) 能大大提高执行效率。

  • 用第①种写法比较直观,而用地址法或者指针变量的方法难以很快判断出当前处理的元素。

使用指针变量指向数组元素时(上面第③种写法),注意以下前两点: ① *(p--) 相当于arr[i--],先*p,再p--*(p++) 相当于arr[i++],先*p,再p++; ② *(--p) 相当于arr[--i],先--p,再**(++p) 相当于arr[++i],先++p,再*; ③ *p++*p,再p++(*p)++*p,再*p++

具体关系参照下面表格:

操作类型指针表达式数组下标等价执行顺序指针移动方向是否改变指针地址
前置自减 + 取值*(--p)arr[--i]1. 指针前移 2. 取新地址的值向前(←)
前置自加 + 取值*(++p)arr[++i]1. 指针后移 2. 取新地址的值向后(→)
后置自减 + 取值*(p--)arr[i--]1. 取原地址的值 2. 指针前移向前(←)
后置自加 + 取值*(p++)arr[i++]1. 取原地址的值 2. 指针后移向后(→)
后置自减(简写)*p--arr[i--]1. 取原地址的值 2. 指针前移向前(←)
后置自加(简写)*p++arr[i++]1. 取原地址的值 2. 指针后移向后(→)
取值后自减(*p)--arr[i]--1. 取原地址的值 2. -1不移动×
取值后自加(*p)++arr[i]++1. 取原地址的值 2. +1不移动×

数组名作函数参数

①形参和实参都是数组名

 //arr 数组 形参
 void fun(int arr[], int len){...}
 ​
 void main()
 {
     int arr[] = {11,22,33};
     int len = sizeof(arr) / sizeof(arr[0]);
     //arr 数组 实参
     fun(arr, len);
 }

②实参用数组名,形参用指针变量

 //arr 指针 形参
 void fun(int *arr, int len){...}
 ​
 void main()
 {
     int arr[] = {11,22,33};
     int len = sizeof(arr) / sizeof(arr[0]);
     //arr 数组 实参
     fun(arr, len);
 }

③形参和实参都用指针变量

 //arr 指针 形参
 void fun(int *arr, int len){...}
 ​
 void main()
 {
     int arr[] = {11,22,33};
     int len = sizeof(arr) / sizeof(arr[0]);
     //arr 指针 实参
     int *p = arr;
     fun(p, len);
 }

④实参用指针,形参用数组名

 //arr 数组 形参
 void fun(int arr[], int len){...}
 ​
 void main()
 {
     int arr[] = {11,22,33};
     int len = sizeof(arr) / sizeof(arr[0]);
     //arr 指针 实参
     int *p = arr;
     fun(p, len);
 }

案例

  • 需求:将数组a中的n个整数按相反顺序存放(数组反转)

  • 分析

  • 代码

#include <stdio.h>
/**
* 数组的反转:下标法
*/
void inv1(int arr[], int len)
{
    // 反转思路:第0个和最后一个交换,第1个和倒数第二个交换...
    // 定义循环变量和临时变量
    register int i = 0, temp;
    // 遍历数组
    for (; i < len/2; i++)
    {
        temp = arr[i];
        arr[i] = arr[len-1-i];
        arr[len-1-i] = temp;
    }
}
/**
* 数组的反转:指针法
*/
void inv2(int *p, int len)
{
    // 反转思路:第0个和最后一个交换,第1个和倒数第二个交换...
    // 定义循环变量和临时变量
    int *i = p, *j = p + len - 1, temp;
    // 遍历数组
    for (; i < j; i++, j--)
    {
        temp = *i;
        *i = *j;
        *j = temp;
    }
}
/**
* 遍历数组
*/
void list(const int *arr, int len) // const int *arr = arr;
{
    const int *p = arr; // 添加const之后,指针指向对象的值不变,指针指向可
    以改变
        for (; p < arr + len; p++) printf("%-4d", *p); printf("\n");
}
int main(int argc,char *argv[])
{
    int arr[] = {11,12,13,14,15};
    int len = sizeof(arr) / sizeof(arr[0]);
    list(arr, len);
    inv1(arr, len);
    list(arr, len);
    inv2(arr, len);
    list(arr, len);
    return 0;
}

 

数组指针与指针数组

数组指针

定义

概念:数组指针是指向数组的指针(指针变量),本质上是指针

指针变量指向数组元素和数组指针的区别

 

特点:

①先有数组,再有指针

②它指向的是一个完整的数组

一维数组指针

语法

 数据类型 (*指针变量名)[容量]

案例

 #include <stdio.h>
 int main(int argc,char *argv[])
 {
     // 一维数组指针
     int arr[] = {100,200,300};
     int len = sizeof(arr) / sizeof(arr[0]);
     // 定义一个数组指针(一维数组指针)
     int (*p)[len] = &arr; // arr默认指向数组元素,&arr指向整个数组,需要注意的的是,它们表示的范围不同,地址相同
     // p++:此时不能p++,否则会越界
     printf("&arr=%p,arr=%p,&arr[0]=%p\n", &arr, arr, &arr[0]); // arr等价于 &arr[0]
     // 如何访问数组指针
     printf("%d\n", (*p)[2]); // 300
     // 遍历数组指针
     for (int i = 0; i < len; i++) printf("%-6d", (*p)[i]);
     printf("\n");
     return 0;
 }

二维数组指针

语法

 数据类型(*指针变量名)[行容量][列容量];

案例

  • 写法1:二维数组指针指向二维数组(不推荐)

 #include <stdio.h>
 int main(int argc,char *argv[])
 {
     // 创建一个二维数组
     int arr[][3] = {10,20,30,100,200,300,1000,2000,3000};
     // 定义一个二维数组指针指向二维数组
     int (*p)[][3] = &arr;
     // 遍历数组
     for (int i = 0; i < 3; i++)
     {
         for (int j = 0; j < 3; j++)
         {
             printf("%-6d", (*p)[i][j]);
         }
     }
     printf("\n");
     return 0;
 }
  • 写法2:

 #include <stdio.h>
 int main(int argc,char *argv[])
 {
     // 创建一个二维数组
     int arr[][3] = {10,20,30,100,200,300,1000,2000,3000};
     // 定义一个一维数组指针指向二维数组,相当于指针指向的是二维数组的行[行容量]
     int (*p)[3] = arr; // 等价于 &arr[0] (*p):指向数组的行 int arr[] = {100, 200, 300}; int *p = arr; 解引用p 得到第一个元素
         // 遍历数组
         for (int i = 0; i < 3; i++)
         {
             for (int j = 0; j < 3; j++)
             {
                 printf("%-6d", p[i][j]);
                 // printf("%-6d", *(*(p+i)+j));
                 // printf("%-6d", (*(p+i))[j]);
                 // printf("%-6d", *(p[i]+j));
             }
         }
     printf("\n");
     return 0;
 }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值