在C中指针的高级技巧

对于初学者来说,指针是比较头疼的东西,但是,如果你想深入了解底层的一些东西,指针你又是避不开的。既然逃避不了,干嘛不加入呢?一起继续探索指针中更高级更好玩的技巧吧!

1 回顾

指针也指内存地址,指针变量是用来存放内存地址的变量,在32位的操作系统中,它的大小为4个字节,在64位的操作系统中则是8个字节,依次类推…

c语言中常见的指针变量定义

/**********************part one**********************/
//声明一个存储整型变量地址的指针
int *p; 

//使用方式
int c;
p = &c;

/**********************part two**********************/
//声明一个存储 指向整型变量的指针 的地址的指针 即指向指针的指针
int **pp; 
//使用方式
int *p_1;
pp = &p_1;


/**********************part three**********************/
int *p[]; //声明一个数组,该数组存储的内容为整型变量地址即整型指针

//使用方式
int d;
p[0] = &d; 

 /**********************part four**********************/
int (*p)[4]; //声明一个指针变量,指向的是数组 

//使用方式
int a[4];
p = &a;

2 声明推断

从回顾中,我们简单回顾了一些常见的指针用法,下面继续看看一些有意思的声明以及理解这个声明到底是什么意思。

注意: 用于声明变量的表达式和普通的表达式在求值时所使用的规则相同。

示例一

//一个整型变量
int i;
//一个指向整型的指针
int *i_p;

针对这个*i_P,可以理解成被声明为了一个整数,但它用*表示了这个表达式,所以可以理解成这是一个指针变量,它指向的地址存储的是整数

示例二

//声明一个函数原型
int f();
//声明一个返回整型指针的函数原型
int *f();

对于表达式*f(),优先级最高的是(),所以声明时最先与f结合的是(),所以这是一个函数,既然知道了它是一个函数,那么剩下的部分就应该的是它的返回值类型了,所以可以理解成这是一个回整型指针的函数原型

示例三

//1.
int (*f)();
//2.
int *(*f)();

如果能够理解示例一与二的推断方式,那么针对这个示例应该是很容易理解。

表达式(*f)()中由于括号优先级,可以看到f先与*结合,所以f是一个指针,接下来的是与第二个括号结合,说明这个指针指向的是一个函数,这个函数的返回值为整型。所以整体理解就是这是一个指向/返回整型的函数的/首地址的指针,即函数指针

表达式*(*f)()这个与(*f)()声明多了左边的*,也就是相当于函数的返回类型是整型指针,故可以理解为这是一个指向/返回整型指针的函数的/首地址的指针

示例四

//以下先省略数组长度
//1.
int * f[];

//错误示例
//2.
int f[]();
//3.
int f()[];

下面序号1、2、3分别代码中的三个声明:

  1. 表达式*f[]中优先级最高的是[],所以这个是一个数组,数组元素存储的是整型指针。
  2. 表达式f[]()中的f先与[]结合,所以它是一个数组,然后它的是元素类型是函数,这是错的!,对于一个函数来说,不同的函数长度是不一样的,然而对于数组来说,要求是一样的,所以这个声明无法成立。
  3. f()[]中的f先与()结合,所以它是一个函数,那么它的返回值就是数组,然而函数中不能够返回数组,所以这个声明也是错的。

示例五

int *(*f[])();

这个声明表达式阅读起来确实有点困难!但是依然可以用上面的拆解方式进行解读。*(*f[])()是由*,(*f[]),()组成,显然(*f[])优先级更高,所以根据这个括号的内容知道这个f一定是一个数组,然后它应该存储的是xx类型的指针,要看是什么类型的指针,还得看外面,噢是()啊,那就是函数,所以它存储的内容是函数的指针。那么这个函数的返回值是什么呢?噢,左边是一个*,所以它是返回了一个整型的指针。

分析这个表达式实在是头大的话,建议反复阅读这几个示例!

注意,如果函数有参数的话,声明涉及到函数原型时最好注明参数类型,如:

int (*f)(int,float);
int *(*g[])(int,float);

3 函数指针

函数指针上面举例子的时候,已经被举例过了,它就是指向函数的指针变量。在c中,函数也会有存储空间的,也有对应的入口地址,函数名一般就是指向这个入口的地址。

函数指针的初始化与使用

int f(int);
int (*pf)(int) = &f; //这个&是可选的,因为函数名被使用时总是由编译器把它转换为函数指针。

int ans;
ans = f(25); //函数名调用
ans = (*pf)(25); //间接访问得到一个函数名然后调用
ans = pf(25); //与第一个一样,间接访问非必须

3.1 函数指针的应用场景一: 回调函数

说起回调函数,其实在很多地方能够见到这个东西,比如点击按钮时,执行什么;发起一个http异步请求,处理响应结果时等等。

回调函数往往时这样: 把一个函数作为参数传递给其他函数,后者将会”回调“该函数。

示例代码

#include
#include
#include 
#define DataType char

struct Node{
    DataType value;
    struct Node * next;
};
typedef struct Node* LinkNode;

typedef struct Node* LinkList;



LinkList creat_linklist();
void add_element(LinkList list,DataType value);

/************************************key point*****************************************************/
int search_value(LinkList list,void const* value,int (*compare)(void const * ,void const *));
int compare_self_impl(void const * op1,void const * op2);
int compare_self_impl_char(void const * op1,void const * op2);
int main()
{

    //创建一个空链表
    LinkList list=creat_linklist();
    add_element(list,'a');
    add_element(list,'b');
    add_element(list,'d');
    char c='a';
    int result = search_value(list,&c,compare_self_impl_char);

    printf("%d", result);



    return 0;
}


LinkList creat_linklist()
{
    LinkList list = (LinkList) malloc(sizeof(struct Node));
    list->next=NULL;
    return list;
}


void add_element(LinkList list,DataType value)
{
    if(!list)
        exit(ERROR_INVALID_PARAMETER);
    LinkNode new_node = (LinkNode) malloc(sizeof(struct Node));
    new_node->next=list->next;
    new_node->value=value;
    list->next=new_node;
}


/************************************key point*****************************************************/

int search_value(LinkList list,void const * value,int (*compare)(void const * ,void const *))
{
    int result = 0;
    if(!list)
        exit(ERROR_INVALID_PARAMETER);
    LinkNode p = list->next;
    while (p)
    {
        if(compare(&p->value,value) == 1)
        {
            result = 1;
            break;
        }
        p=p->next;
    }

    return result;
}


//实现自定义的比较函数 数字
int compare_self_impl(void const * op1,void const * op2)
{
    return *((int *)op1) == *((int *)op2) ? 1:0;
}



//实现自定义的比较函数 字符
int compare_self_impl_char(void const * op1,void const * op2)
{
    return *((char *)op1) == *((char *)op2) ? 1:0;
}

search_value函数用于查找某个值是否在链表中,它的内部必然涉及两个元素进行比较。如果我们的链表只针对某个特定的元素类型,那么它的实现就比较简单,直接内部进行取值比较就行了。但是如果想要更为通用,那么这个比较就不能在函数内部实现了(当然,你直接实现也可以,但是你得在内部进行判别类型)。这时候函数指针的用处就体现出来了,只需声明一个函数指针参数,其他元素类型只需重写这个比较函数,并提供函数地址,由search_value函数在内部调用即可。

search_value函数的原型如下:

int search_value(LinkList list,void const * value,int (*compare)(void const * ,void const *));

不知道你是否注意到其中的int (*compare)(void const * ,void const *)参数类型?由于声明这个函数原型的时候,是不知道具体参与比较的元素的类型的,所以为了匹配所有元素,故将类型定为void *。到时候声明一个符合该函数指针要求的函数时,则需在内部进行强制类型转换即可,这时肯定能知道所需要比较的参数类型了。

3.2 函数指针的应用场景二:转移表

假设,我们想要实现一个简单的加减乘除计算器,常见的实现方式:

    int select_no,op1,op2,result;
    switch (select_no) {
        case ADD:
        {
            result= op1 + op2; 
            break;
        }

        case SUB:
        {
            result= op1 - op2;
            break;
        }
        case MUL:
        {
            result= op1 * op2;
            break;
        }

        case DIV:
        {
            result= op1 / op2; //这里实现有点小瑕疵,但也很常见的
            break;
        }
    }

如果我们想再增加更多的功能模块的话,这个switch语句将会更长。如果想要达到良好的程序设计,建议把具体操作与选择操作分开,即加减乘除操作的实现与选择分开,所以推荐使用函数实现,以便于后期扩展。

对于switch,需要知道的是: 表示操作符的分支必须是整数。所以我们可以想到,如果是从0开始的连续的整数,可以用数组来映射的,这就是转移表。所谓数组,就是存储函数指针的数组。

示例代码

#include

double self_add(double a,double b){ return a+b;}
double self_sub(double a,double b ){return a-b;}
double self_mul(double a,double b ){return a*b;}
double self_div(double a,double b ){return a/b;}//这里实现有点小瑕疵

//定义一个函数指针数组,然后初始化
double (*oper_func[])(double ,double )={
        &self_add, &self_sub, &self_mul, &self_div
};
enum OP{ADD=0,SUB,MUL,DIV}; //定义 枚举

int main()
{
    
    double a=oper_func[ADD](3,1);
    printf("%lf",a);
    
    return 0;
}

这里需要主要注意的是:

既然用到了数组,就有可能发生越界行为,就可能会造成不可预料的结果,所以建议在使用需要保证在合理的范围内。

你可能感兴趣的:(学习笔记,c语言)