弱类型是如何实现的?C语言如何实现弱类型效果?

写惯了PHP,Js,Python这些脚本语言,再写C语言最大的感受就是:强类型真的太不方便了。

比如说要写了个函数,取两个整数的最大值:

int max_int(int n1, int n2)
{
    return (n1 > n2) ? n1 : n2;
}

如果取两个double类型的最大值。于是又写个函数:

int max_double(double n1, double n2)
{
    return (n1 > n2) ? n1 : n2;
}

如果取两个unsigned类型的最大值,难道把同样的代码,再写一遍?

int max_uint(unsigned n1, unsigned n2)
{
    return (n1 > n2) ? n1 : n2;
}

再比如,用C语言开发的栈和队列只能存一种类型,如果要存另一种类型要把同样的代码再写一遍?

C语言如何实现“弱类型”的效果呢?有两种方法可以实现这种效果。

Python和Js的变量和值分离是分离的,变量相当于是一个void *指针,这个指针可以指向任意类型的变量,当变量赋值不同的类型时只需将指针指向不同的变量即可。

#include <stdio.h>

typedef struct pval{
    void *val;
    int type;
} pval;

void myprint(pval val);

int main(){
    pval pval1;
    
    int a = 1;
    char b = 'c';
    float c = 1.2;
    
    pval1.val = &a;
    pval1.type = 1;
    
    myprint(pval1);
    
    pval1.val = &b;
    pval1.type = 2;
    
    myprint(pval1);
    
    pval1.val = &c;
    pval1.type = 3;
    
    myprint(pval1);
}

void myprint(pval val){
    if(val.type == 1){
        printf("%s ", "int");
        printf("%ld\n", *(int *)(val.val));
    } else if(val.type == 2){
        printf("%s ", "char");
        printf("%c\n", *(char *)(val.val));
    } else if(val.type == 3){
        printf("%s ", "float");
        printf("%f\n", *(float *)(val.val));
    }
}

PHP的实现方法是用一个联合体用来存变量的值,再用一个变量来标识变量的类型。

#include <stdio.h>

typedef union uval{
    long a;
    double b;
    char * c;
} uval;

typedef struct pval{
    uval val;
    int phptype;
} pval;

//enum

void var_dump(pval);

int main(){

    pval pval1 = {{.a = 111}, 1};

    var_dump(pval1);// int 111


    pval1.phptype = 2;
    pval1.val.b = 1.21;

    var_dump(pval1);// float 1.210000

    pval1.phptype = 3;
    pval1.val.c = "abc";

    var_dump(pval1);// string abc

}



/**
 * @param val
 * 根据pval变量类型,决定读取联合体的那个类型
 */
void var_dump(pval val){
    if(val.phptype == 1){
        printf("%s ", "int");
        printf("%ld\n", val.val.a);
    } else if(val.phptype == 2){
        printf("%s ", "float");
        printf("%f\n", val.val.b);
    } else if(val.phptype == 3){
        printf("%s ", "string");
        printf("%s\n", val.val.c);
    }
}

容器

把这个问题再扩展一下,假如现在要写一个队列,不仅要存整型、字符、浮点型,还想在这个队列里面存自己定义的结构体类型,比如商户待处理的订单struct order,售票系统排队的用户struct consumer,工厂中排队加工的汽车struct car等等。

现在问题来了,因为结构体是我们自己定义的,所以不可能预先写到代码里,这可咋办?

其实类型这个概念,是我们人为规定的,内存中存储的都是一些1和0,计算机执行的只不过是内存的拷贝,它也不知道这段内存意味着什么,整型,字符型等只不过是我们常用的内存长度,c语言中拷贝一个字符型,和拷贝一个字节长度的内存是一样的。

char a = 'a';
char b = 'b';

//下面两句等效
a = b;
memcpy(&a, &b, sizeof(char));

由此可见,计算机根本不需要知道什么类型,只需要知道内存从哪拷贝到哪及拷贝多长就足够了。基于这点我们就有思路实现“万能”的队列了。

//初始化时,传入要存储类型的长度n
struct queue init_queue(int n)
{
    q.elem = malloc(n);
    q.n = n;
    ......
}

//新增,相当于拷贝n个长度的内存
void insert(struct queue *q, void* e)
{
    memcpy(q->elem+q->n*q->size, e, q->n);
    ......
}

//获取可以直接返回指针
void* queue_get(struct queue *q,int r)
{
    return q->elem + r*q->n;
}

存不同类型的数据结构,只需初始化时传入不同的长度。

//存储订单的队列
struct queue q1 = init_queue(sizeof(struct order));

//存储用户的队列
struct queue q2 = init_queue(sizeof(struct consumer));

//存储汽车的队列
struct queue q3 = init_queue(sizeof(struct car));

通用链表

上述方法用memcpy函数实现,缺乏可读性,这里介绍另外一种方法。假设我们要实现一个双向链表,要求这个链表即可以装int类型,又可以装float类型,甚至可以多种类型混合装。

首先设计一个通用的双向链表结构:

struct list_entry {
    struct list_entry *prev, *next;
};

下面两个数据结构分别是装int型与float型变量的节点:

struct node_int{
    list_entry list;
    int i;
};

struct node_float{
    list_entry list;
    float i;
};

双向链表链接的是这些结构体中的list_entry,如此这般,就相当于将int类型与float类型放到一个双向链表中了:

image

posted @ 2020-06-16 22:32:58
评论加载中...
发表评论