三、数组指针、指针数组、函数指针、指针函数

本文详细介绍了C语言中指针与数组、函数的交互使用,包括数组指针与指针数组的区别,以及函数指针和指针函数的定义与应用。文中强调了类型匹配和括号结合律在理解和使用中的重要性,并通过示例代码解释了结构体的指针操作以及链表的创建和显示。
摘要由CSDN通过智能技术生成

对应的视屏链接:鲍松山的个人空间_哔哩哔哩_bilibili


目录

1、指针与数组

2、指针与函数

3、指针操作结构体

4、总结


 

指针除了能指向基本数据类型,还能指向数组、函数、结构体……,对于 C 语言来说,指针几乎无所不能,我往往把指针称为任督二脉之中的一脉,因为打通了指针的理解,毫无夸张的说,C 语言才算是真正的入门,在以后的编程中才几乎不受语言语法的限制。

本节重点讲解指针与数组和函数的结合,它们之间的结合会擦出火花,这些火花将是我们理解复杂程序的基础。

1、指针与数组

指针与数组的结合主要会形成一对左右颠倒的概念,看似一样,实则距离遥远,它们是数组指针和指针数组

  • 数组指针

数组指针,首先它是一个指针,它指向一个数组,它是“指向数组的指针”简称,对于数组指针,强调的是指针的概念,只不过,指针的能力是用来指向数组类型的,并且方括号中的数字是固定的,例如:int (*p)[10],p 就是指向数组的指针,其中 p 指针规定了只能指向整形的数组,且数组大小只能是 10 个整形元素,不能多也不能少,多之少之都会认为其指针的能力与指向的实体不符。

数组指针的意义何在呢?我们考虑如下的程序:

#include<stdio.h> 
int main() 
{ 
    int array[10] = {1,2,3,4,5,6,7,8,9,10}; 
    int *p = array; 
    //int **pp = &array; //应该定义为 int(*pp)[10] = &array; 
    return 0;
} 

对于一个数组,我们可以定义一个相应类型的一级指针,接收其数组名的内容,但是,当我们需要对一个数组进行取地址赋值时,将如何定义指针的类型了?

很多初学者会认为定义一个相应类型的二级指针即可,这是不正确的,(当然,在 C 语言的工程中,即.c 文件中,这个问题是可以编译通过,只不过 C 语言是一种非强类型语言,在 C++中,对类型的检查更加严格,因此编译不过),因为他会认为一级指针接收数组名,那么数组名的地址且不就是二级指针就能接收了?非也,其实,个人认为,指针的操作,往往很多莫名其妙的出错都集中在类型的不匹配,所以,一旦出错,就得认真分析地址与指针的类型是否相应匹配,也只有这样的锻炼,才能把指针真正的融会贯通。那么,我们将如何定义指针的类型,才能接受数组名取地址了?

其实问题不难,只要分析数组足矣,数组名代表整个数组空间,那么,对数组名取地址,即就是在对整个数组取地址,则数组的地址自然要用指向数组的指针才能接收,所以,必须定义指向数组的指针类型,即为数组指针

  • 指针数组

指针数组,首先它是一个数组,数组的元素都是指针,它是“储存指针的数组”的简称。

对于指针数组,强调的是数组的概念,只不过,数组所保存的类型是指针罢了,其地位跟普通的数组没有什么区别,都是数组,只不过是大家保存的类型不同而已,因此,我们美名其曰:保存指针的数组就称其为指针数组, 例如:int *p1[10]

指针数组的意义何在?我们考虑如下程序:

#include<stdio.h> 
void main() 
{ 
    int a = 10; 
    int b = 20; 
    int c = 30; 
    int *array[3] = {&a, &b, &c}; //初值为地址 
} 

有时需要使用数组来保存变量地址的,此时就会用到指针数组类型。

  • 数组指针 VS 指针数组

数组指针int (*p)[3]与指针数组int *p[3]从定义的形式上看,它们之间的差距仅在于是否有小括号,这也告诉我们,其二者差距的本质在于*与[]的结合律的问题,如果没有小括号,变量 p 先与[]结合,形成数组的概念,若有小括号,则变量 p 就与*号先结合成指针的概念,因此,[]的优先级比*高,能够认清这一点是关键。

对于指针数组,强烈建议,在起初定义时可以有意识的让*向类型方向靠近,例如:int* ar[3], 不要写成 int *ar[3],因为指针在 C 语言中太深入人心了,很多人一旦看到*就会既激动又恐惧,激动的是有机会大施拳脚,恐惧的是害怕操作指针,所以在遇到*时失去思考能力,都认为是指针了,其实不然,所以在定义时如果有意识的让*靠近类型,可以提醒自己定义的是一个数组,只不过存储类型为指针而已,当然,如果已经很熟悉了,那么这一切都是可以无视的,甚至是任性的......

2、指针与函数

指针与函数的结合也会形成一对左右颠倒的概念,它们是函数指针和指针函数

  • 函数指针

函数指针,首先它是一个指针,只不过,指针所指向的类型是函数,它是“指向函数的指针”的简称

#include<stdio.h> 
int Max(int a, int b) 
{ 
    return a > b ? a : b; 
} 
int main() 
{ 
    //情形 1
    Max(1,2); 
    
    //情形 2 
    int(*pfun)(int,int); 
    pfun = &Max; 
    (*pfun)(1,2); 
    
    //情形 3 
    pfun = Max; 
    pfun(1,2); 
    
    return 0;
}

一般来说,我们调动函数往往是通过函数名来进行调动,例如情形 1,

由于指针的强悍且几乎无所不能,只要你能表示出具体的类型,一定可以想办法定义出相应的指针类型,因此,通过指针来调动函数就显得很自然了,我们把能够指向函数的指针称为函数指针,例如情形2

把 Max 函数的地址赋给了 pfun 函数指针,在调动时先取值,然后再调动函数,这是一种标准的做法

事实上,由于函数名就是函数的入口地址,本身也充当了地址,因此,我们可以简化程序,例如情形 3,由于指针所指之物为函数,因此它的调动就行如直接运行函数,但是,心里要清楚,情形 3 的做法实际是情形 2 的简写过程

注意,一般指针都有其加 1 的能力,但是,函数指针不允许做这样的运算。即pfun+1 是一个非法的操作

  • 指针函数

指针函数,首先它是一个函数,只不过函数所返回的类型是指针类型,它是“返回指针类型的函数”的简称

我们把返回指针类型的函数称其为指针函数,那就意味着只要返回值为指针,无论是什么类型的指针,都有资格称为指针函数,形如这样的定义就是指针函数 int* fun();

重点在于其返回值一定要是指针类型。

思考:

如何定义一个函数的返回值为指向函数的指针?

  • 函数指针 VS 指针函数

函数指针int(*pfun)(int,int)与指针函数int* fun(int,int)它们本质区别也仅仅在于有无小括号(),这也体现出()结合律高于*,有括号则名字先跟*结合形成指针的概念,只不过指针所指类型为函数,若没有括号则名字先与小括号结合,形成函数的概念,只是函数的返回值为指针类型。

注意,在定义函数指针时,一定要思考所指函数的原型,必须是返回值类型、参数的个数及类型完全一样,这样的指针才能接收其相应的函数地址,否者无法接收

3、指针操作结构体

  • 普通操作

typedef struct Student
{
    char name[10];
    int age;
    float height;
}Student;

int main()
{
    Student s = {"鲍鱼", 30, 170.8};
    printf("name = %s, age = %d, height = %f\n", s.name, s.age, s.height);

    //指针的两种操作方式
    Student *ps = &s;
    printf("name = %s, age = %d, height = %f\n", (*ps).name, (*ps).age, (*ps).height);
    printf("name = %s, age = %d, height = %f\n", ps->name, ps->age, ps->height);
    return 0;
}
  • 结构体传参

typedef struct Student
{
    char name[10];
    int age;
    float height;
}Student;

//结构体传值
void PrintStudent1(struct Student s)
{
    printf("name = %s, age = %d, height = %f\n", s.name, s.age, s.height);
}

//结构体传址
void PrintStudent2(struct Student *ps)
{
    printf("name = %s, age = %d, height = %f\n", ps->name, ps->age, ps->height);
}

int main()
{
    Student s = {"鲍鱼", 30, 170.8};
    PrintStudent1(s);
    PrintStudent2(&s);
    return 0;
}

指针指向结构体时,可以通过指向符->操作其内部成员,简洁方便

当结构体传参时,首选地址传递,效率更高

  • 链表

#define ElemType int

typedef struct ListNode
{
    ElemType data;
    struct ListNode *next;
}ListNode;

typedef ListNode* List;

void ListInit(List *phead)
{
    *phead = NULL;
}

List ListCreate()
{
    ListNode *phead = (ListNode*)malloc(sizeof(ListNode));
    assert(phead != NULL);
    phead->data = 1;
    phead->next = NULL;

    ListNode *p = phead;
    for(int i=2; i<=10; ++i)
    {
        ListNode *s = (ListNode*)malloc(sizeof(ListNode));
        assert(s != NULL);
        s->data = i;
        s->next = NULL;

        p->next = s;
        p = s;
    }
    return phead;
}

void ListShow(List phead)
{
    ListNode *p = phead;
    while(p != NULL)
    {
        printf("%d-->", p->data);
        p = p->next;
    }
    printf("Over.\n");
}

int main()
{
    List mylist;
    ListInit(&mylist);
    mylist = ListCreate();
    ListShow(mylist);
    return 0;
}

4、总结

无论是数组指针、指针数组、函数指针、指针函数,其实我们只需关心后面两个字,因为后面两个字是类型的关键,也是类型的大前提

例如,数组指针,则前提一定是指针类型,再者,他们之间的差距很微妙,只在于括号的有无,其关键在于优先级,咱们心里要有数

针对指针(*p)来说,如果出了小括号,遇到的是[],形如(*p)[],则这是指向数组的指针,若出了小括号,遇到的是(),形如(*p)(),则这是指向函数的指针

能否正确区分这些概念,是我们能否正确解读复杂指针的关键。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值