【C语言进阶】——指针(一) (字符指针,数组指针,指针数组) !! (下)

简介: 【C语言进阶】——指针(一) (字符指针,数组指针,指针数组) !! (下)

3、数组指针

1.数组指针的定义

数组指针是指针还是数组 ?
答案是∶指针

我们已经熟悉︰
整形指针 : int p* ; 能够指向整形数据的指针
浮点型指针 : float pf* ; 能够指向浮点型数据的指针
数组指针应该是︰能够指向数组的指针

数组指针和指针数组要区分开来。

整型指针 —> 指向整型的指针

int a = 10;
int* pa = &a;

字符指针 —> 指向字符的指针

char ch = 'w';
char* pc = &ch;

数组指针—> 指向数组的指针----->eg : int (*p)[10]=NAll ;

#include<stdio.h>
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    //int* p = arr;//数组名是首元素地址
    //数组指针 存放数组的指针变量
    int(*p)[10] = &arr;//(*p)代表p是指针变量
    //该指针指向了一个数组,数组10个元素,每个元素的类型是int
    //如果不用括号将*p括起来,写成int* p[10],这是指针数组
    return 0;
}

首先,我们要知道[]的优先级是比 * 要高的,对于形式1,p1会先与[ ]结合,在与 * 结合,所以形式1是指针数组,()的优先级又比[]高,所以p2会先于 * 结合,在与[ ]结合,所以形式2是数组指针。

到这里,相信你对数组指针有了一定了解,哪么怎么建立数组指针呢

例如:给定char arr[5],请写出用来表达char arr[5]的数组指针**

先分析char* arr[5],很明显,这是一个指针数组,数组名是arr,有五个元素,数组的类型是char*,我们已知数组指针 - 指向数组的指针 - 存放数组的地址,所以应该对数组取地址,即&arr,如何应该定义一个有五个元素的指针存放数组的地址,即(p)[5],指针类型为char,所以数组指针是char* (*p)[5] = &arr

【C语言进阶】——指针(一)  (字符指针,数组指针,指针数组)   !!_指针数组_15

不成熟的说指针的指针相当于二级指针,肯定有两个 “ * ”


所以我们现在会写数组指针了。

来试一试!

例如:

  • 写出char* arr[10]的数组指针

char*( *pa)[10] = &arr

  • 写出int* par[2]的数组指针

int*( *pa)[2] = &par


2. & 数组名 和 数组名

【C语言进阶】——指针(一)  (字符指针,数组指针,指针数组)   !!_数组指针_16

我们看到打印的结果都是一样的,那么数组名arr和数组的地址 & arr是一样的吗?

从地址值来看,两者是一样的,但是两者的含义和使用是不同的:

【C语言进阶】——指针(一)  (字符指针,数组指针,指针数组)   !!_传参_17

int* p1; p1+1 表示跳过一个int类型的长度,也就是4个字节
char* p2; p2+1表示跳过一个char类型的长度,也就是1个字节
int(p3)[10]; p3+1表示跳过一个具有10个整型长度的数组,也就是410=40个字节

【C语言进阶】——指针(一)  (字符指针,数组指针,指针数组)   !!_指针_18


3.数组指针的使用

① 对一维数组的使用

我们先看这个例子:

【C语言进阶】——指针(一)  (字符指针,数组指针,指针数组)   !!_指针数组_19

通过数组指针解引用找到数组,再用方括号[ ],去找到数组中的每个元素。
这种并非数组指针的常用方式,因为用起来很“别扭”。
这种方式不如首元素地址 + i 流畅:

【C语言进阶】——指针(一)  (字符指针,数组指针,指针数组)   !!_指针数组_20


数组指针的使用,一般常见于二维数组及其以上

当我们在谈首元素的时候,一维数组的首元素就是第一个元素,二维数组的首元素要先将二维数组看作一维数组(该数组中每一个元素都是一个一维数组),那二维数组的首元素就是第一个一维数组。那么二维数组的首元素地址就是第一个一维数组的地址!(不是第一个一维数组中第一个元素的地址,虽然值相同,但含义和使用不同)

② 对二维数组的使用

#include<stdio.h>
//常见的方式
void print_arr1(int arr[3][5], int x, int y)
{
    int i = 0;
    int j = 0;
    for (i = 0; i < x; i++)
    {
        for (j = 0; j < y; j++)
        {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}

//数组指针方式
void print_arr2(int(*p)[5], int x, int y)
{
    int i = 0;
    int j = 0;
    for (i = 0; i < x; i++)
    {
        for (j = 0; j < y; j++)
        {
            printf("%d ", (*(p + i))[j]);
            printf("%d ", *(*p + i)+j);
            printf("%d ", *(p[i]+j);
            printf("%d ", p[i][j]);
            //四个都等价
        }
        printf("\n");
    }
}

int main()
{
    int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
    //打印这个二维数组
    print_arr1(arr,3,5);//数组名,行,列也要传参
    
    print_arr2(arr, 3, 5); //数组名-首元素地址
    return 0;
}

结果展示:

【C语言进阶】——指针(一)  (字符指针,数组指针,指针数组)   !!_字符指针_21

图解:

【C语言进阶】——指针(一)  (字符指针,数组指针,指针数组)   !!_数组指针_22


注意:对一个存放数组地址的指针进行解引用操作,找到的是这个数组,也就是这个数组的数组名,数组名这时候又表示数组首元素地址!

*( p + i ):相当于拿到了一行 相当于这一行的数组名 ( *p + i )[ j ] <===> *(*(p + i ) + j )

【C语言进阶】——指针(一)  (字符指针,数组指针,指针数组)   !!_数组指针_23


为了更好的理解这一点,我们来看这个例子:

#include<stdio.h>
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int* p = arr;
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d ", *(p + i));
        //方式一:通过指针找到与首元素偏移i个元素的地址,
        //再对齐解引用操作,找到这个元素
        printf("%d ", *(arr + i));
        //方式二:既然可以将arr赋值给p,说明arr与p等价
        //那么就可以直接用arr替代p进行相应的解引用操作
        printf("%d ", arr[i]);
        //方式三:通过数组名+[下标]访问数组元素
        //即arr+[下标i]访问下标为i的元素,也就是第i+1个元素
        printf("%d ", p[i]);
        //方式四:既然arr与p等价,
        //那么也可以直接用p+[下标]的方式访问数组的元素

        //上述四种方式实际结果完全相同,实际上也可以互相转换使用
    }
    return 0;
}

【C语言进阶】——指针(一)  (字符指针,数组指针,指针数组)   !!_数组指针_24

恍然大悟


**总结:**我们对一个数组指针变量进行解引用操作,比如int(*p)[10],得到的是一个数组,或者说是这个数组的数组名,而数组名又可以表示该数组首元素的地址。如果要找到该数组中的每一个元素,就需要对这个数组元素的地址进行解引用操作。
简单点来说就是,对一个数组指针类型进行解引用操作,得到的还是地址,对这个地址在进行相应的解引用操作,才能得到数组中的具体的元素。

【C语言进阶】——指针(一)  (字符指针,数组指针,指针数组)   !!_字符指针_25


巩固练习

下面这些代码的含义是什么?

int arr[5];
int* parr1[10];
int(*parr2)[10];
int(*parr3[10])[5];

解析:

int arr[5];
//arr是一个数组,数组有5个元素,每个元素类型是int
//arr类型是  int [5]  --- 去掉变量名,剩下的就是变量的类型

int* parr1[10];
//parr1是一个数组,数组有10个元素,每个元素类型是int*
//parr1是指针数组,类型是 int* [10]

int(*parr2)[10];
//parr2是一个指针,指针指向一个数组,数组有10个元素,每个元素的类型是int
//parr2是数组指针,类型是 int(*)[10]

int(*parr3[10])[5];
//parr3是一个数组,数组有10个元素,每个元素都是一个指 针
//指针指向一个数组,数组有5个元素,每个元素类型是int
//parr3是一个指向数组指针的数组,本质上还是数组
//parr3类型是 int(*[10])[5]

【C语言进阶】——指针(一)  (字符指针,数组指针,指针数组)   !!_指针_26


int( parr3 [10])[ 5 ]; 拿掉数组名后,剩下 int()[5]就是这个数组的类型

【C语言进阶】——指针(一)  (字符指针,数组指针,指针数组)   !!_传参_27

问题1:parr2 = &parr1;//能否将数组parr1的地址放到parr2中呢?

答:不能,因为类型不匹配,parr2指向的类型应该是 int[10] parr1 是 int* [10];


4、数组参数、指针参数

1.一维数组传参

#include <stdio.h>
void test(int arr[])//ok? 
{}
void test(int arr[10]) //ok?
{}
void test(int* arr)//ok?
{}
void test2(int* arr[20])//ok?
{}
void test2(int** arr) // ok ? 
{}
int main()
{
    int arr[10] = { 0 };
    int* arr2[20] = { 0 };
    test(arr);
    test2(arr2);
}

答案:以上五种传参方式均ok

**注意:**一维数组传参可以传数组形式,也可以传指针形式,传数组形式的时候数组元素的个数可以不写,也可以写,传指针的时候要注意指针的类型,也就是指针指向什么类型的元素,

比如说指针指向int类型元素,那么指针的类型就是 int*


2.二维数组传参

void test(int arr[3][5])//ok ?
{}
//可以
void test(int arr[][])//ok ?
{}
//不可以,行可以省略,列不可以,第一个[ ]内容可以不写,第二个[ ]要写
void test(int arr[][5])//ok ?
{}
//可以
void test(int* arr)// ok ? 
{}
//不可以
void test(int* arr[5])//ok ?
{}
//不可以
void test(int(*arr)[5])//ok ?
{}
//可以
void test(int** arr)// ok ? 
{}
//不可以
int main()
{
    int arr[3][5] = { 0 };
    test(arr);
}

总结 : 二维数组传参,函数形参的设计只能省略第一个[ ]的数字。
因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
这样才方便运算。
二维数组传参也能写成指针的形式,指针的类型应该是数组指针。


3.一级指针传参

【C语言进阶】——指针(一)  (字符指针,数组指针,指针数组)   !!_字符指针_28

思考1:这里的指针传参可以用数组去接收吗?

【C语言进阶】——指针(一)  (字符指针,数组指针,指针数组)   !!_指针_29

经过实验我们可以看到这样做是没问题的,指针传参可以用数组去接收!


思考2:当一个函数的参数部分为一级指针的时候,函数能接收什么参数 ?

例如:int * p

void test1(int* p)
{
}
int main()
{
    int a = 10;
    int* pa = &a;
    int arr[10] = { 0 };
    test1(&a);
    test1(pa);
    test1(arr);
    return 0;
}

再例如:char* p

void test2(char* p)
{
}
int main()
{
    char a = 'W';
    char* pa = &a;
    char arr[10] = { 0 };
    test2(&a);
    test2(pa);
    test2(arr);
    return 0;
}

注意:
int* p1;
int* p2;
靠近int 或者靠近变量p实际的意义和效果没区别,是一样的。 一般我们习惯用int p2这种写法,这样可以明确表示指针变量p2的类型。*


4.二级指针传参

void test(int** p)
{
}
int main()
{
    int* p1;
    int** ptr;
    int* arr[5];
    test(&p1);//一级指针取地址
    test(ptr);//二级指针
    test2(arr);//一级指针数组的首元素
    return 0;
}

【C语言进阶】——指针(一)  (字符指针,数组指针,指针数组)   !!_数组指针_30


目录
相关文章
|
1月前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
86 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
1月前
|
存储 算法 C语言
【C语言】字符常量详解
字符常量是C语言中处理字符数据的重要工具。通过单引号括起一个字符,我们可以方便地使用字符常量进行字符判断、字符运算和字符串处理等操作。理解字符常量的表示方法、使用场景和ASCII码对应关系,对于编写高效的C语言程序至关重要。
155 11
|
1月前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
56 9
|
1月前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
45 7
|
1月前
|
存储 C语言 开发者
【C语言】格式化输出占位符及其标志字符详解(基于ISO/IEC 9899:2024)
在C语言中,格式化输出通过 `printf` 函数等格式化输出函数来实现。格式说明符(占位符)定义了数据的输出方式,标准ISO/IEC 9899:2024(C23)对这些格式说明符进行了详细规定。本文将详细讲解格式说明符的组成部分,包括标志字符、宽度、精度、长度修饰符和类型字符,并适当增加表格说明。
44 6
|
1月前
|
传感器 算法 安全
【C语言】两个数组比较详解
比较两个数组在C语言中有多种实现方法,选择合适的方法取决于具体的应用场景和性能要求。从逐元素比较到使用`memcmp`函数,再到指针优化,每种方法都有其优点和适用范围。在嵌入式系统中,考虑性能和资源限制尤为重要。通过合理选择和优化,可以有效提高程序的运行效率和可靠性。
98 6
|
2月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
71 5
|
2月前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
2月前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
136 3
|
2月前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。