Bootstrap

【C语言】自定义数据类型

类型命名

类型命名关键字typedef

  • C语言中可以对类型赋予新名字
  • 语法:typedef Type New TypeName;
  • 注意:typedef并没有创建新类型,只是创建了类型别名

深入typedef应用

  • typedef可在函数中定义“局部类型名
  • typedef常用于简化类型名(如:unsigned long long)
  • typedef定义类型名,能够以统一的方式创建变量(Type var;)
#include<stdio.h>
typedef unsigned char byte;//将unsigned char赋予一个新名字byte,则byte现在是一个完整的原子类型
void func(){
    typedef byte uint8;//将byte赋予一个新名字uint8
    uint8 var=200;
    byte b=var;//本质为相同类型变量之间的初始化
    printf("sizeof(uint8)=%d\n",sizeof(uint8));
    printf("var=%d\n",var);
    printf("b=%d\n",b);
}
int main(){
    //uint8 var=1;//ERROR
    byte b=128;
    fun();
    printf("sizeof(byte)=%d\n",sizeof(byte));
    printf("b=%d\n",b);
    return 0;
}
//sizeof(uint8) = 1
//var = 200
//b = 200
//sizeof(byte) = 1
//b = 128(!函数的传值调用不会改变形参的值)

最后变量输出的值参考函数

注意:本代码中的byte和uint8为同一个自定义类型,所以他们之间可以相互赋值。

再来看一段代码

#include<stdio.h>
typedef float(FArr5)[5];//定义数组类型名
typedef int(IFuncII)(int,int);//定义函数类型名

typedef *FArr5 PFArr5;//定义数组指针类型名
typedef *IFuncII PIFuncII;//定义函数指针类型名

float g_arr[5]={0.1,0.2,0.3};
int add(int a,int b){
    return a+b;
}
int main(){
    *FArr5 pa=&g_arr;//float(*)[5]
    *IFuncII pf=add;//int(*)(int,int)
    PFArr5 npa=pa;
    PIFuncII npf=pf;
    
    int i=0;
    for(i=0;i<5;i++){//遍历数组地址
        printf("%f\n",(*pa)[i]);
        printf("%f\n",(*npa)[i]);
    }
    printf("%d\n",pf(2,3));
    printf("%\n",npf(2,3);
    return 0;
}
//0.100000
//0.100000
//0.200000
//0.200000
//0.300000
//0.300000
//0.000000
//0.000000
//0.000000
//0.000000
//5
//5

注意函数指针的用法,可以通过typedef使得函数指针的定义简化。

数组

数组也是自定义类型,参见文章数组

结构体

定义

  • struct是C语言中的自定义关键字
  • struct能够定义不同数据类型变量的集合类型

结构体有两种声明方式:一是普通声明;二是特殊声明(也叫不完全声明)

普通声明:是中规中矩的声明,也是用的比较多的一种

struct name tag{//结构体类型标签
 meber_list;//存放成员列表
}variable_list;//存放变量列表

特殊声明(输出即是匿名结构体类型):省略掉后面的tag,这也意味着匿名结构体类型声明后只能用一次,就算是两个一模一样的结构体类型,其中一个存放指针变量并指向另一个结构体变量,再将这个指针存放某个东西,也会造成非法访问,所以说匿名结构体类型再实际编写代码中会用的很少。

struct name{
 成员列表
};

注意:struct结构体类型可先前置声明,再具体定义。前置声明只能用于指针定义

自引用

  1. 指针

    错误示范:

    struct Node{
        int data;
        struct Node next;
    };
    

    这样写代码好像没问题,看着和递归有点像

    但是不能在结构体中直接把其中一个成员写成与本结构体相同的类型,这样只会造成死循环使程序崩溃,正确的代码应该如下:

    struct Node{
        int data;
        *struct Node next;
    };
    

    应该将结构体struct Node类型改成*struct Node类型,这就类似于数据结构中的链表,都是通过指针指向的位置来寻找的。

  2. typedef

    那如果是用typedef来定义结构体类型进而来实现自引用应该怎么办呢?

    错误示范二:

    typedef struct{
        int data;
        *Node next;
    }Node;
    

    这样的代码也是不正确的,虽然注意到了应该用指针来进行访问,但是因为是用typedef来定义结构体的,而代码是在整个结构体的尾声才声明成Node的,这样的话中间部分(即结构体内部)的Node是未定义的。正确的修改应该如下:

    typedef struct Node{
        int data;
        *struct Node next;
    }Node;
    

    把整个结构体类型定义成struct Node,结构体内部就可以使用这个结构体类型指针来调用,最后再声明成Node(也就是将结构体类型变为Node,以方便下面使用时的调用等)。

结构体变量

定义和初始化

类型完整定义后才能进行变量定义

两种定义:

  • 声明类型的同时定义变量,如p1
  • 在外面定义结构体变量,如p2

这两种定义的都是全局变量,而如果是在一个函数中定义的结构体变量,那么这个结构体变量就是局部的。

struct Point{
    int x;
    int y;
}p1;
    
struct Point p2;

在外面定义变量时,还有两种情况:

  1. struct可省略,所以第二种可以写成Point p2;,每次创建变量必须给出完整结构体定义
  2. 如果用typedef赋予了新名字pp,可以写成pp p3;

两种初始化方式:

  • 定义结构体变量的同时初始化

    struct point{
        int x;
        int y;
    };
    struct Point p={1,2};
    
  • 结构体嵌套初始化

    #include<stdio.h>
    struct Date{
        int year;
        int month;
        int day;
    };
    struct Student{
        char s_name[20];
        struct Date birthday;//嵌套声明的第一个struct
        float score;
    };
    /*也可以这样写:
    struct Student{
    	char s_name[20];
    	struct Date{
    	int year;
    	int month;
    	int day;
    	}birthday;
    	float score;
    };*/
    int main(){//main函数在结构体声明之后
        struct Student stu={"Yangyang",2003,4,7,99.9};
    	printf("%s\t%d.%d.%d\t%.lf",stu.s_name,stu.birthday.year,stu.birthday.month,stu.birthday.day,stu,score);
        return 0;
    }
    

访问结构体变量元素

结构体变量name.成员name

如果其成员本身又是一种结构体类型,那么可以通过若干个成员运算符,一级一级的找到最低一级成员再进行对其操作:结构体变量name.成员.子成员......最低一级子成员,例如上述代码块的第二个输出。

注意:在C语言中不存在结构体类型的强制转换

整体与分开

可以将一个结构体变量作为一个整体赋值给另一相同类型的结构体变量,可以达到整体赋值的效果;这个成员变量的值将全部整体赋值给另外一个变量;不能将一个结构体变量作为一个整体进行输入和输出;在输入输出结构体数据时,必须分别指明结构体变量的各成员。

结构体和指针

可定义struct结构体类型的指针,并指向对应类型的变量

结构体指针变量

struct Student{
    *char s_id;
    *char s_name;
    *char s_sex;
    *char s_age;
};

结构体变量和指针

结构体类型指针访问成员的获取赋值形式:

  1. 获取:**(*p).**成员名
  2. 赋值:**p->**成员名
#include<stdio.h>
struct Inventory{
    char description[20];
    int quantity;
};
int main(){
    struct Inventory sta={"iphone",20};
    struct *Inventory stp=&sta;
    printf("%s %d\n",stp->description,stp->quantity);//先给结构体类型的指针赋值
    printf("%s %d\n",(*stp).description,(*stp).quantity);//将赋值后的指针输出
    return 0;
}

结构体和函数

#include<stdio.h>
struct School{//struct类型定义
    char s_name[20];
    int s_age;
};
void Print_a(struct School sx){//定义struct变量的同时,定义函数a
    printf("%s %d\n",sx.s_name,sx.s_age);
}
void Printf_b(struct *School sp){//定义函数a,给struct指针赋值
    printf("%s %d\n",sp->s_name,sp->age);
}
int main(){
    struct School sc={"xi'an",100};//struct赋值
    Printf_a(sc);//输出赋值后的struct
    Printf_b(&sc);//输出struct类型的指针
    return 0;
}
//xi'an 100
//xi'an 100

结构体和数组

结构体数组,是指数组中的每一个元素都是一个结构体类型。

在实际应用中,C语言结构体数组常被用来表示有相同数据结构的群体,比如一个班的学生,一个公司的员工等。

#include<stdio.h>
struct Student{//struct类型声明
    char s_name[20];
    int age;
    float score;
};
int main(){
  struct Student cla[]={//struct变量定义同时赋值
      {"李华",18149.5}{"李磊",16,130};
      {"韩梅梅",16,141.5};
  };
  for(int i=0;i<3;i++){//循环输出结构体数组
      printf("%s\t%d\t%f\n",cla[i].s_name,cla[i].age,cla[i].score);
  }
    return 0;
}
//李华    18      149.500000
//李雷    16      130.000000
//韩梅梅  16      141.500000

计算大小

结构体遵从内存对齐规则

#include<stdio.h>
struct node{
    char cha;
    char chb;
    int ia;
};
struct student{
    char a;
    int i;
    char b;
};
int main(){
    struct node sd={'a','b',2};
    struct student std={'d','e',6};
    printf("%d",sizeof(struct node));
    printf("%d",sizeof(struct student));
    return 0;
}
//8
//12

共用体

成员共享一块存储空间

定义

union 共用体名{
    成员列表
}

定义和用法类比于结构体

内存分配

共用体内存分配符合两项原则:

  1. 共用体的内存必须大于或等于其他成员变量中最大数据类型(包括基本数据类型和数组)的大小
  2. 共用体的内存必须是最基本数据类型的整数倍,如果不是,则填充字节

eg1.成员变量都是基本数据类型的共用体

union data{
    int m;
    float x;
    char c;
}a;

共用体a的内存大小是最大数据类型所占的字节数,即int和float的大小,所以a的内存大小为4字节.

eg2.成员变量包含数组类型的共用体

union{
    int m;
    float x;
    char c;
    char str[5];
}b;

共用体b的最大数据类型为字符数组,但是他的大小是5字节,不满足原则2,必须是最大基本数据类型的整数倍,所以填充3字节,共8字节。

共用体变量

初始化

在共用体变量的定义的同时,只能对其中一个成员的类型值进行初始化,这与他的内存分配也是响应的。

共用体变量初始化的格式如下:

union 共用体类型 共用体变量={其中一个成员的类型值}//必须要用大括号括起来

引用

完成共用体变量的初始化后,就可以引用共用体中的成员,共用体变量的引用与结构体类似,有直接引用间接引用两种。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>

struct Person{//定义一个struct
    char name[20];
    char role[20];
    union{//定义一个包含数组类型的union,作为struct的子成员
        char classroon[20];
        char office[20];
    }dept;
}person[3];

int main(){
    for(int i=0;i<3;i++){//循环输入struct变量person
        printf("please input your information:NO.%d\n",i+1);
        printf("Name");
        scanf("%s",&person[i].name);
        getchar();//字符输入函数,没有参数,从输入缓冲区里面读取一个字符(一次只能读取一个字符,包括空格,回车和Tab)
        printf("Role:");
        scanf("%s",&person[i].role);
        getchar();
        if(strcmp(person[i],role,"student")==0){
            printf("Classroom:");
            getchar();
            scanf("%s",&person[i].dept.classroom);
        }
    	else if(strcmp(person[i].role,"teacher")==0){
        printf("Office:");
        getchar();
        scanf("%s",&person[i].dept.Office);
    	}
    	getchar();
	}
    //循环输出上面输入的信息
    for(int i=0;i<3;i++){
        printf("please input your information:No.%3d\n",i+1);
        printf("\tName:%6s",person[i].name);
        printf("\tClassroom:%s",person[i].dept.role);
        if(strcmp(person[i].role,"teacher")==0)
    }
}

联合体

位段

在C语言中,位段是一种以位为单位来定义的结构体(或联合体)中的成员变量所占空间的自定义数据类型。这种数据结构允许开发者在一个结构体中以位为单位指定其成员所占的内存长度,从而实现数据的紧凑存储,用较少的位数存储数据,达到节省空间的目的。位段的声明和结构体类似,但位段的成员必须是int、unsigned int、signed int或char类型,并且成员名后有一个冒号和一个数字,用于指定该成员所占的位数。

主要用于描述那些无法用单一内置数据类型表示的复杂元素。

枚举类型

在程序中,可能需要为某些整数定义一个别名,我们可以利用预处理指令**#define**来完成这项工作,您的代码可能是:

#define NON 1
#define TUE 2
#define WED 3
#define THU 4
#define FRI 5
#define SAT 6
#define SUN 7

在此,我们定义一种新的数据类型,希望它能完成同样的工作,这种新的数据类型叫枚举型。

定义

enum 枚举类型名{
    成员列表
};

eg:

enum DAY{
    NON =1,TUE,WED,THU,FRI,SAT,SUN
};
  1. 枚举型是一组有限的值的有序集合,集合中的元素(枚举成员)是一些命名的整型常量,元素之间用逗号,隔开。
  2. DAY是一个标识符,可以看成这个集合的名字,是一个可选项,即是可有可无的项。
  3. 第一个枚举成员的默认值为整型的0,后续枚举成员的值在前一个成员上加1.
  4. 可以人为设定枚举成员的值,从而自定义某个范围内的整数
  5. 枚举型是预处理指令#define的替代
  6. 类型定义以分号 ; 结束。

声明

使用枚举类型对变量进行声明

三种方法

方法一:枚举类型的定义和变量的声明分开

enum DAY{//类型定义,创建枚举类
    MON=1,TUE,WED,THU,FRI,SAT,SUN//定义枚举常量
};
enum DAY yesterday;//变量的声明
enum DAY today;
enum DAY tomorrow;//变量tomorrow的类型为枚举型enum DAY
enum DAY good_day,bad_day;//变量good_day和bad_day的类型均为枚举型enum DAY

方法二:类型定义与变量声明同时进行

enum DAY{MON=1,Tue,Wed,Thu,Fri Sat,Sun}days;//变量days的类型为枚举enum week
enum BOOLEAN{false,true}end_flag,match_flag;//定义枚举类型并声明了两个枚举型变量
enum{//跟第一个定义不同的是,此处的标号DAY省略,这是允许的
    saturday,
    sunday=0,
    monday,
    tuesday,
    wednesday,
    thursday,
    friday
}workday;//变量workday的类型为枚举类型enum DAY

方法三:用typedef关键字将枚举类型定义成别名,并利用该别名进行变量声明:

typedef enum workday//enum workday中的workday可以省略
{
    saturday,
    sunday=0,
    monday,
    tuesday,
    wednesday,
    thursday,
    friday
}workday;//此处的workday为枚举型enum workday的别名
workday today,tomorrow;//变量today和tomorrow的类型为枚举型workday,也即enum workday

两种错误声明

同一个程序中不能定义同名的枚举类型,不同的枚举类型中也不能存在同名的命名常量

错误声明一:存在同名的枚举类型(变量同名)

typedef enum{
    wednesday,
    thursday,
    friday
}workday;

typedef enum WEEK{
    saturday,
    sunday=0,
    monday,
}workday;

错误声明二:存在同名的枚举成员

typedef enum{
    wednesday,
    thurday,
    friday
}workday_1;

typedef enum WEEK{
    wednesday,
    sunday=0,
    monday,
}workday_2;

初始化和赋值

  • 两种方法:

    1. 在定义枚举变量的同时,可以对其进行初始化

      enum Dir{UP,DOWN,LEFT,RIGHT};
      enum Dir dir1=LEFT;
      
    2. 先定义后赋值

      enum Dir{UP,DOWN,LEFT,RIGHT};
      enum Dir dir2;
      dir2=DOWN;
      
  • 对枚举型的变量赋整数值时,需要进行类型转换

    枚举变量赋值的步骤如下:

    1. 创建枚举类,定义枚举常量
    2. 使用枚举常量赋值给变量,或作为方法的参数和返回值
    3. 枚举常量可以拥有属性和方法。一旦为枚举常量添加了属性和方法,可以在程序中使用他们
    #include<stdio.h>
    enum DAY{MON=1,TUE,WED,THU,FRI,SAT,SUN};//类型定义
    int main(){
        enum DAY yesterday,today,tomorrow;//变量声明
        yesterday=TUE;
        today=(enum DAY)(yesterday + 1);//类型转换
        tomorrow=(enum DAY)30;//类型转换
        //tomorrow = 30;//错误
        printf("%d %d %d \n",yesterday,today,tomorrow);//输出:2 3 30
    }
    

    为什么要进行类型转换?

    在上面的例子中,如果赋整数值的时候不进行类型转换,会出现报错:

    在这里插入图片描述

  • 使用枚举变量

    #include<stdio.h>
    enum//定义一个枚举型
    { 
        BELL       =   '\a' ,//警告提示音
        BACKSPACE  =   '\b' ,//退格删除符
        HTAB       =   '\t' ,//水平制表符
        RETURN     =   '\r' ,//回车
        NEWLINE    =   '\n' ,//换行符
        VTAB       =   '\v' ,//垂直制表符
        SPACE      =   ' '//空格
    };
    enum  BOOLEAN { FALSE = 0 , TRUE } match_flag;
    int main()
    {
         int  index = 0 ;//index下标
         int  count_of_letter= 0 ;//字符的数量
         int  count_of_space = 0 ;//空格的数量
    
         char str[] = "I'm Ely efod" ;//定义一个字符串
    
        match_flag  =  FALSE;//标记为0
    
         for (; str[index]  != '\0' ; index ++ )//遍历整个字符串
             if ( SPACE  !=  str[index] )//字符不为空格时,letter+1
                count_of_letter ++ ;
             else//字符为空格时,space+1
            {
                match_flag  =  ( enum  BOOLEAN)  1 ;
                count_of_space ++ ;
            }
        
        printf( "%s %d times %c" , match_flag  ?   "match"  :  "not match" , count_of_space, NEWLINE);//三目运算符
        printf( "count of letters: %d %c%c " , count_of_letter, NEWLINE, RETURN);
    }
    //输出:match 2 times
    //count of letters: 10
    

与sizeof运算符

#include<stdio.h>
enum escapes
{ 
    BELL       =   '\a' ,//警告提示音 
    BACKSPACE  =   '\b' ,//退格删除符 
    HTAB       =   '\t' ,//水平制表符 
    RETURN     =   '\r' ,//回车 
    NEWLINE    =   '\n' ,//换行符 
    VTAB       =   '\v' ,//垂直制表符 
    SPACE      =   ' '
};
enum BOOLEAN{FALSE = 0,TRUE }match_flag;
int main(){
    printf("%d bytes \n",sizeof(enum escapes));//4 bytes
    printf("%d bytes \n",sizeof(escapes));//4 bytes
    printf("%d bytes \n",sizeof(enum BOOLEAN));//4 bytes
    printf("%d bytes \n",sizeof(BOOLEAN));//4 bytes
    printf("%d bytes \n",sizeof(match_flag));//4 bytes
    printf("%d bytes \n",sizeof(SPACE));//4 bytes
    printf("%d bytes \n",sizeof(NEWLINE));//4 bytes
    printf("%d bytes \n",sizeof(FALSE));//4 bytes
    printf("%d bytes \n",sizeof(0));//4 bytes
}
;