Skip to content

Latest commit

 

History

History
424 lines (300 loc) · 10.2 KB

自定义结构和类型.md

File metadata and controls

424 lines (300 loc) · 10.2 KB

自定义结构和类型

C语言允许使用关键字typedef来为各种类型设置别名,但问题在于其语法相当混乱,用法大约是这么几种:

  • 为原生类型赋予别名

    typedef int count;

    int类型就有了别名count

  • 为函数取别名

    typedef int (*intfunc)(int);

    原型为int (*)(int)的函数指针就有了别名intfunc,这通常用于指定函数的回调函数参数的类型.

    用的时候就是

    intfunc intmap(x);
  • 为数组取别名

    typedef int intarr[5];

    表示将int类型的长度为10的数组取别名为intarr 用的时候

    intarr a = {1,2,3,4,5};
    
  • 为函数数组取别名

    typedef int (*intfunc_arr[10])(int);

    表示将存放int (*)(int)类型函数指针的数组取别名为intfunc_arr

  • 为结构体取别名

    typedef struct {
        int x;
    } x_struct;

    表示这个结构体别名叫x_struct.

C中的特殊结构

C中提供了3种特殊的结构,用于描述复杂的结构化数据,分别是

  • 结构体
  • 联合体
  • 枚举

结构体

结构体可以理解为一组有意义的特殊成员变量的组合,比如对于小朋友,有性别,姓名,年龄身高,体重这几个维度,每个小朋友都会有这些属性.我们就可以将小朋友定义为一个结构体

结构体的定义方式为:

struct 结构标签{
类型 成员变量名;
类型 成员变量名;
类型 成员变量名;
};

注意不要落了封号

我们来声明一个Child结构体:

struct Child{
    const char *name;
    const char *sex;
    int age;
    float weight;
    float height; 
};

ps:在结构体中字符串使用const char *来保存你不想修改的字符串,也就是字符串字面值,这种方式只保存了字符串的指针,只有把字符串定义成字符数组如char name[20]这样才能将字符串保存在结构体中,但一般我们不这么用,原因在于C语言中字符串常量本来就存放在代码段,没有必要到处保存

结构体变量的初始化

结构体是一种自定义类型,它和一般的类型如int呀char呀有相同的性质,不同之处是他是值是复合的,要非初始化上面的结构体结构体,只要这样:

struct Child Tom = {"Tom","m",8,48.5,1.5};

也可以声明变量后再赋值

struct Child Sam;
Sam.name = "Sam";
Sam.sex = "m";
Sam.age = 7;
Sam.weight = 45.7;
Sam.height = 1.57;

结构体变量的特性

结构体变量作为函数参数传递的时候需要注意是传值的,这和一般的类型是一样的. 如果我们的函数参数是结构体的指针变量(传引用),那么和一般的指针变量一样,需要注意.

访问结构体变量中的字段

对于一个结构体变量,比如上文的Sam,访问方式就和第二种初始化方式一样,使用.符号即可,特殊的地方在于如果是如下这种情况:

struct Child *Tom = {"Tom","m",8,48.5,1.5};

那么就比较尴尬了,需要记住*Tom.name访问的其实是*(Tom.name),正确的写法应该是(*Tom).name.为了避免用错括号的情况,c语言中在结构体变量的指针要访问其内部字段时使用->代替.

Tom->name

自定义类型操作

我们可以使用typedef关键字将结构体或者枚举,或者其他任何的类型取个别名,这样可以更加方便使用

typedef 原类型 类型别名;

基本上这也是结构体的正常使用方式,毕竟每次申明都要带上struct关键字实在是太罗嗦了

例子:

#include <stdio.h>
#include <string.h>
typedef struct{
    char name[10];
    char sex[2];
    int age;
    float weight;
    float height;
} Child;

int main(void) {
    Child Tom = {"Tom","m",8,48.5,1.5};
    Child Sam;
    strcpy(Sam.name,"Sam");
    strcpy(Sam.sex,"m");
    Sam.age = 7;
    Sam.weight = 45.7;
    Sam.height = 1.57;
    printf("%s 身高%fm\n",Tom.name,Tom.height);
    printf("%s 体重%fkg\n",Sam.name,Sam.weight);
    return 0;
}
Tom 身高1.500000m
Sam 体重45.700001kg

为什么要有结构体

设想下有这样一个场景:

我们有若干个函数用于处理水族馆中的各种水产

/* 打印目录项 */
void catalog(const char *name, const char *species, int age){
    printf("%s 是一种的 %s.这只%s已经%i岁了\n",
        name,species,name,age);
    
}

/* 打印贴在水缸上的标签 */
void label(const char *name,const char *species, int age){
    printf("名字: %s\n种类: %s\n%i 岁.\n",
        name,species,age);
}

int main(){
    catalog("皮皮虾","节肢动物",2);
    label("皮皮虾","节肢动物",2);
    return 0;
}

我们用来描述水产的参数现在有3个,这个数量还在可控范围内,但如果再多几个,那这个函数将变得非常不可读,同时,如果这些描述一旦有变化,修改这些函数将成为一件非常棘手的事儿,

结构体最大的作用就在这里,使用结构体将这些参数归类到一起,这样就不需要一次传入大量的参数了

#include <stdio.h>
#include <wchar.h>
#include <locale.h>
/* 定义 */
typedef struct {
    const wchar_t *name;
    const wchar_t *species;
    int age;
} Aquatic;


/* 打印目录项 */
void catalog(Aquatic *aquatic){
    wprintf(L"%ls 是一种的 %ls.这只%ls已经%i岁了\n",
        aquatic->name,aquatic->species,aquatic->name,aquatic->age);
    
}

/* 打印贴在水缸上的标签 */
void label(Aquatic *aquatic){
    wprintf(L"名字: %ls\n种类: %ls\n%i 岁.\n",
       aquatic->name,aquatic->species,aquatic->age);
}

int main(){
    setlocale(LC_ALL, "zh_CN.UTF-8");   
    Aquatic mantis_shrimp = {L"皮皮虾",L"节肢动物",2};
    catalog(&mantis_shrimp);
    label(&mantis_shrimp);
    return 0;
}
皮皮虾 是一种的 节肢动物.这只皮皮虾已经2岁了
名字: 皮皮虾
种类: 节肢动物
2 岁.

typedef是结构体的好朋友

上面介绍了如何用typedef为结构体取别名,但我们看别人代码一般都不这么用,下面介绍几种常见的用法和其利弊:

  • 结构体标签与别名一致

    typedef struct T{
        int x;
    } *T;

    或者

    struct T{
        int x;
    };
    typedef struct T *T;

    这种方式如果要使用结构体的指针就直接使用T,如果使用结构体本身可以使用struct T,这样的好处是相当简洁,但不好的地方在Tstruct T很容易混淆.

  • 为结构体和其指针分别取不同的别名

    typedef struct{
        int x;
        } T,*T_p;

    这种方式用结构体指针就使用T_p,使用结构体本身则可以使用T这种方式的好处是结构体本身和指针都分开用不同的名字,不好的地方在结构体本身因为没有struct关键字所以当代码很长的时候很容易语义上混淆.

  • 更加推荐的做法,

    struct T{
        int x;
    };
    typedef struct T *T_p;

    这种方式最老实,但也最清晰的,结构体指针就使用T_p,使用结构体本身则可以使用struct T.

共用体

在C语言中,还有另外一种和结构体非常类似的语法,叫做共用体(Union),它的定义格式为:

union 共用体名{
    成员列表
};

结构体和共用体的区别在于:

  • 结构体的各个成员会占用不同的内存,互相之间没有影响;
  • 共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员.

Union的作用就在于当成员间

结构体占用的内存大于等于所有成员占用的内存的总和(成员之间可能会存在缝隙),共用体占用的内存等于最长的成员占用的内存.共用体使用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员赋值,就会把原来成员的值覆盖掉.

#include <stdio.h>
union data{
    int n;
    char ch;
    short m;
};
int main(){
    union data a;
    printf("%ld, %ld\n", sizeof(a), sizeof(union data) );
    a.n = 0x40;
    printf("%X, %c, %hX\n", a.n, a.ch, a.m);
    a.ch = '9';
    printf("%X, %c, %hX\n", a.n, a.ch, a.m);
    a.m = 0x2059;
    printf("%X, %c, %hX\n", a.n, a.ch, a.m);
    a.n = 0x3E25AD54;
    printf("%X, %c, %hX\n", a.n, a.ch, a.m);
    return 0;
}
4, 4
40, @, 40
39, 9, 39
2059, Y, 2059
3E25AD54, T, AD54

枚举

枚举在python中并没有直接的实现,但C++中它是默认的一种自定义类型,它的声明关键字是enum

声明枚举类型:

enum 枚举类型名 {枚举值1,枚举值2...} 

定义枚举变量:

枚举类型名 变量名 = 枚举值;

需要注意的是枚举类型变量只能赋值为枚举类型声明时的枚举值,其他都会报错,而枚举值实际上是对应声明时位置对应的整形数.

看个例子:

#include <stdio.h>

enum Weekday {MON,TUE,WED,THU,FRI,SAT,SUN};

int main(void) {
    enum Weekday firstday = TUE;
    printf("first day is %d\n",firstday);
    return 0;
}
first day is 1

枚举值转枚举字面量

一个常见的用处是通过枚举值或者int型的数转成对应的字面量字符串.这个实现需要通过宏中#操作.#表示字符串化操作符,它把其后的串变成用双引号包围的串

可以参考下面的代码.

#include <stdio.h>
 
#define ENUM_CHIP_TYPE_CASE(x)   case x: return(#x);
 
enum cvmx_chip_types_enum {
    CVMX_CHIP_TYPE_NULL,
    CVMX_CHIP_TYPE_DEPRECATED,
    CVMX_CHIP_TYPE_OCTEON_SAMPLE,
    CVMX_CHIP_TYPE_MAX
};
 
 
static inline const char *cvmx_chip_type_to_string(enum cvmx_chip_types_enum type)
{
    switch (type)
    {
        ENUM_CHIP_TYPE_CASE(CVMX_CHIP_TYPE_NULL)
        ENUM_CHIP_TYPE_CASE(CVMX_CHIP_TYPE_DEPRECATED)
        ENUM_CHIP_TYPE_CASE(CVMX_CHIP_TYPE_OCTEON_SAMPLE)
        ENUM_CHIP_TYPE_CASE(CVMX_CHIP_TYPE_MAX)
    }
    return "Unsupported Chip";
 
}
int main()
{
    enum cvmx_chip_types_enum a;
    
    printf("the result:%s \n",cvmx_chip_type_to_string(CVMX_CHIP_TYPE_OCTEON_SAMPLE));
    printf("the result:%s \n",cvmx_chip_type_to_string(2));
    return 0;
}
the result:CVMX_CHIP_TYPE_OCTEON_SAMPLE 
the result:CVMX_CHIP_TYPE_OCTEON_SAMPLE