Linux--C语言之构造类型

文章目录

一、数据类型分类

(一)基本类型

1. 整数型

  • 短整型:short (2个字节)
  • 整型(默认):int (4个字节)
  • 长整型:long (8个字节)
  • 长长整型:long long

2. 浮点型

  • 单精度:float (4个字节)
  • 双精度:double (8个字节)

3. 字符型

  • char (1个字节)

(二)指针类型

  1. 数据类型*:int*,char*float*
  2. void*:任意数据类型的指针

(三)空类型

  1. void:没有返回值或没有形参(不能定义变量

(四)自定义类型/构造类型

  1. 结构体类型:struct
  2. 共用体类型(联合体):union
  3. 枚举类型:enum

注意:整数型和字符型分有符号signed和无符号unsigned,默认的是有符号的,有符号可以省略关键字。

二、结构体类型

(一)结构体类型的定义

1. 定义:

自定义数据类型的一种,关键字struct,结构体类型的变量可以存储多个不同数据类型的数据。

2. 定义格式:

struct 结构体名
{
    数据类型1 成员名称1;
    数据类型2 成员名称2;
    ... 
};

注意:结构体中定义的变量,我们称之为成员变量。

3. 格式说明:

  • 结构体名:合法的标识符,建议单词的首字母大写
  • 数据类型n:C语言支持的所有类型
  • 成员名称:合法的标识符,就是变量的命名标准
  • 数据类型n 成员名称n:类似于定义变量,定义了结构体中的成员

注意:

  • 结构体在定义的时候,成员不能赋值

  • 举例:

    struct Cat
    {
        int age = 5; // 错误,结构体在定义的时候,成员不能赋值
        double height; // 正确
    };
    

4. 常见的定义格式:

  • 方式1:常规定义(命名结构体,只定义类型) – 推荐

    struct Student
    {
        int num; // 学号
        char name[20]; // 姓名
        char sex; // 性别
        int age; // 年龄
        char address[100]; // 家庭住址
    };
    
  • 方式2:定义匿名结构体(常用于作为其他结构体成员使用)

    struct Dog
    {
        char *name; // 姓名
        int age; // 年龄
        struct  // 此时这个结构体就是匿名
        {
            int year; // 年
            int month; // 月
            int day; // 日
        } birthday;
    };
    
  • 注意:定义匿名结构体的同时必须定义结构体变量,否则编译报错,结构体可以作为另一个结构体的成员。

  • 总结:

    • 结构体可以定义在局部位置,也可以定义在全局位置;
    • 全局位置的结构体名和局部位置的结构体名可以相同,就近原则(和普通变量的定义同理)

5. 结构体类型的使用:

利用结构体类型定义变量,定义数组;结构体类型的使用与基本数据类型的使用类似。

(二)结构体变量的定义

1. 三种形式定义结构体变量

结构体变量也称为结构体的实例

  • 第1种:先定义结构体,然后使用struct 结构体名 变量名

    // 先定义结构体(先定义结构体这个数据类型)
    struct A
    {
        int a;
        char b;
    };
    
    // 定义结构体变量
    struct A x;
    struct A y;
    
  • 第2种:在定义结构体的同时,定义结构体变量;

    // 在定义结构体的同时定义结构体变量
    struct A
    {
        int a;
        char b;
    } x,y;
    

    此时定义了一个结构体A,x和y是这个结构体类型的变量。

  • 第3种:在定义匿名结构体的同时,定义结构体变量(不推荐);

    struct 
    {
        int a;
        char b;
    } x,y;
    

    此时定义了一个没有名字的结构体(称为匿名结构体);y,x是这个匿名结构体类型的变量

2. 匿名结构体:弊大于利(尽量少用)

  • 优点:少写一个结构体名称

  • 缺点:只能使用一次,定义的结构体类型的同时就必须定义变量

  • 应用场景:

    • 当结构体的类型只需要使用一次,并且定义类型的同时定义变量;
    • 作为其他结构体成员使用。

3. 定义结构体的同时,定义结构体变量初始化

struct Cat
{
    int age;
    char color[20];
}cat;
  • 结构体成员部分初始化时,大括号不能省略;
  • 如果赋值没有歧义,编译和运行就没有问题;
  • 结构体成员,没有默认值,是不确定的数。

案例:

/**
 * 结构体变量的定义
 */
#include <stdio.h>

// 先定义结构体,再定义结构体变量
void fun1()
{
    // 先定义结构体
    struct A
   	{
        int a;
        char b;
   	};
    // 再定义结构体变量
    struct A x;
    struct A y;
}

// 定义结构体的同时定义结构体变量
void fun2()
{
    struct A
   	{
        int a;
        char b;
   	} x,y;
    struct A z;
}

// 定义匿名结构体的同时定义结构体变量
void fun3()
{
    struct
   	{
        int a;
        char b;
   	} x,y;
    
    struct
   	{
        int a;
        char b;
   	} z;
}

int main()
{
    fun1();
    fun2();
    fun3();
    
    return 0;
}

(三)结构体变量的使用

1. 结构体变量访问结构体成员

  • 格式

    结构体变量名.成员名;
    
    • 可以通过访问给成员赋值(存数据)

    • 可以通过访问获取成员的值(取数据)

  • 结构体变量为初始化,结构体的成员值随机(不确定)

2. 结构体变量在定义时,可以初始化

  • 建议用大括号表明数据的范围
  • 结构体成员初始化时,可以部分初始化,部分初始化时一定要带大括号标明数据的范围

案例:

/**
 * 结构体变量的初始化
 */

#include <stdio.h>

/* 全局的结构体(数据类型) */
struct Dog
{
	char *name; // 姓名
	int age; // 年龄
	char sex; // M:公,W:母
};

/* 先定义,再初始化 */
void fun1()
{
	// 定义结构体变量
	struct Dog dog;

	// 给结构体变量赋值
	dog.name = "旺财";
	dog.sex = 10;
	
	// 访问结构体变量的成员
	printf("%s,%d,%c\n",dog.name,dog.sex,dog.sex); 
}

/* 定义的同时初始化 */
void fun2()
{
	// 定义结构体变量并初始化
	struct Dog dog = {"大黄",16,'M'};

	// 访问结构体变量的成员
    printf("%s,%d,%c\n",dog.name,dog.sex,dog.sex);
}

int main()
{
	fun1();
	fun2();
	
	return 0;
}

(四)结构体数组

结构体数组的定义

1. 说明时候需要结构体数组?
  • 比如:我们需要管理一个学生对象,只需要定义一个struct Student zhangsan;
  • 假如:我们需要管理多个学生对象,此时就需要一个结构体的数组struct Student students[64]
2. 三种形式定义结构体数组
  • 第1种:先定义结构体类型,然后定义结构体变量,再将变量存储到结构体数组中。

    // 定义一个学生类型的结构体
    struct Student
    {
        int id;
        char *name;
        int age;
        float scores[3]; // 三门课程成绩
    };
    
    // 定义结构体对象
    struct Student zhangsan = {1,"张三",23,{67.5,89.0,90.0}};
    struct Student lisi = {2,"李四",21,{77.0,80.0,85.0}};
    
    // 定义结构化数组
    struct Student students[3] = {zhangsan,lisi};
    
  • 第2种:定义结构体类型,然后定义结构体数组并初始化

    // 定义一个学生类型的结构体
    struct Student
    {
        int id;
        char *name;
        int age;
        float scores[3]; // 三门课程成绩
    };
    
    // 定义结构体数组并初始化
    struct Student students[3] = {
        {1,"张三",23,{67.5,89.0,90.0}},  // 这里赋值的顺序需要跟成员在结构体中的顺序一致
        {2,"李四",21,{77.0,80.0,85.0}}
    };
    
  • 第3种:定义结构体类型同时定义结构体数组并初始化

    // 定义一个学生类型的结构体
    struct Student
    {
        int id;
        char *name;
        int age;
        float scores[3]; // 三门课程成绩
    } students[3] = {
        {1,"张三",23,{67.5,89.0,90.0}},  // 这里赋值的顺序需要跟成员在结构体中的顺序一致
        {2,"李四",21,{77.0,80.0,85.0}}
    };
    
  • 第4种:定义结构体类型同时定义结构体数组,然后通过索引给结构体成员赋值

    // 定义一个学生类型的结构体
    struct Student
    {
        int id;
        char *name;
        int age;
        float scores[3]; // 三门课程成绩
    } students[3];
    
    students[0].id = 1;
    students[0].name = "张三";
    students[0].age = 12;
    students[0].scores[0] = 98;
    

    小贴士:

    结构体数组名访问结构体成员:

    格式:结构体数组名 --> 成员名

案例:

需求:对候选人得票的统计程序。设有3个候选人,每次输入一个得票的候选人的名字,要求最后输出 各人得票结果。

/**
* 结构体数组案例:对候选人得票的统计程序。设有3个候选人,每次输入一个得票的候选人的名字,要求最后输出各人
得票结果。
*/

#include <stdio.h>
#include <string.h>
/**
* 定义一个候选人结构体(对象)
*/
struct Person
{
    char name[20]; // 姓名
    int count; // 票数
};

// 定义候选人数组,并初始化
struct Person persons[3] = {
	{"张三",0},
	{"李湘",0},
	{"王五",0}
};

void main()
{
    int i,j;
    char leader_name[20]; // 用来接收被投票的候选人姓名
    
    printf("请输入您要投票的候选人姓名(10名):\n");
    // 使用一个循环,完成10次投票
    for(i = 0; i < 10; i++)
    {
        scanf("%s",leader_name);
        
        // 给被投票的候选人+1票
        for(j = 0; j < 3; j++)
        {
            // 判断两个字符串相等
            if(strcmp((leader_name,persons[j].name)) == 0)
            {
                persons[j].count++;
            }
        }
    }
    
    printf("\n投票结果:\n");
    
	for(i = 0; i < 3; i++)
	{
		printf("%5s:%d\n",persons[i].name,persons[i].count);
    }
}

(五)结构体指针

1. 定义:

结构体类型的指针变量指向结构体变量或者数组的起始地址。

2. 语法:

struct 结构体名 *指针变量列表;

3. 举例:

struct Dog
{
    char name[20];
    int age;
};

struct Dog dog;
struct Dog *p = &dog;

(六)结构体成员访问

1. 结构体数组访问结构体成员

  • 格式:结构体数组名 -> 成员名;

2. 结构体成员访问符

  • .:左侧是结构体变量(结构体对象/实例),也可以叫做结构体对象访问成员符;右侧是结构体成员。
  • ->:左侧是一个指针,也可以叫结构体指针访问成员符;右侧是结构体成员。

3. 访问结构体成员有两种类型,三种方式:

  • 类型1:通过结构体对象访问成员

    struct Stu
    {
        int id;
        char name[20];
    } Stu;
    
    // 访问成员
    stu.name;
    
  • 类型2:通过结构体指针访问成员

    • 指针引用访问成员

      struct Stu
      {
          int id;
          char name[20];
      } Stu;
      
      struct Stu *p = &stu;
      
      // 指针引用访问成员
      p -> name;
      
    • 指针解引用间接访问成员

      struct Stu
      {
          int id;
          char name[20];
      } Stu;
      
      struct Stu *p = &stu;
      
      // 指针解引用间接访问成员
      (*p).name;
      
  • 结构体数组元素的访问

    struct Stu
    {
        int id;
        char name[20];
        float scores[3];
    } stus[2] = {
        {1,"张三",{89,88,86},
        {2,"李四",{75,66,88},
        {3,"王五",{70,99,90}
    };
         
    // 取数据 --- 下标法
    printf("%s,%2f\n",stus[1].name,stus[1].scores[2]); // 李四,88
         
    // 结构体成员引用符号:-> 指针法
    printf("%s,%2f\n",stus -> name,stus -> scores[2]);  // 张三,86   
    printf("%s,%2f\n",(stus + 1) -> name,(stus + 1) -> scores[2]);  // 李四,88 
    printf("%s,%2f\n",(*(stus + 1)).name,(*(stus + 1)).scores[2]);  // 李四,88 
    

小贴士:

结构体是自定义数据类型,它是数据类型,用法类似于基本类型的int;

结构体数组它是存放结构体对象的数组,类似于int数组存放int数据;

基本类型数组怎么用,结构体数组就怎么用—>可以遍历,可以作为形式参数,也可以做指针等。

4. 结构体类型的使用案例

  • 代码:

    #include <stdio.h>
    
    // 定义结构体
    struct Cat
    {
        char *name; // 姓名
        int age; // 年龄
        char color[20]; // 颜色
    };
    
    // 1.结构体类型作为形式参数
    void test1(struct Cat c);
    
    // 2.结构体类型作为形式参数,结构体类型作为返回值类型
    struct Cat test2(struct Cat c);
    
    // 3.结构体数组作为形式参数
    void test3(struct Cat cats[],int len);
    
    // 4.结构体数组作为形式参数,结构体指针作为返回值类型
    struct Cat *test4(struct Cat cats[],int len);
    
  • 测试:

    int main()
    {
        // 定义结构体对象
        struct Cat cat = {"小黑",8,"baise"};
        // 结构体对象作为实际参数
        test1(cat);
        
         // 定义结构体类型对象
        struct Cat cat1 = {"小白",8,"heise"};  
        // 调用函数并接受返回值
         struct Cat c1 = test2(cat1);
        
        // 通过返回值访问结构体对象成员
        printf("%s==%d==%s\n",c1.name,c1.age,c1.color);
        
        // 定义结构体数组
        struct Cat cats[3] = {
            {"汤姆",16,"蓝色"},
            {"杰瑞",18,"褐色"},
            {"斯派克",19,"灰色"}
        };
        
        // 结构体数组名作为实际参数
        test3(cats,3);
        
        // 定义结构体数组并初始化
        struct Cat cats1[3] = {
            {"汤姆",16,"蓝色"},
            {"杰瑞",18,"褐色"},
            {"斯派克",19,"灰色"}
        }; 
        
        // 调用函数
        struct Cat *p = test4(cats1,3);
        struct Cat *w;
        
        // 通过指针运算遍历数组
        for(w = p;w < p + 3; w++)
        {
            // 通过结构体指针访问符访问结构体成员
            printf("%s----%d----%s\n",w -> name,w -> age,w -> color);
        }
    }
    

(七)结构体类型求大小

1. 规则:

字节对齐(数据在内存中存储在其类型大小的整数倍上)

  • 首先保证结构体中的成员存储在自身的对齐边界(类型大小的整数倍);
  • 在满足1的条件下,最终大小要满足最大成员所占存储单元的整数倍;

2. 为什么要使用字节对齐:

节省内存,提高访问效率。

3. 在GNU标准中,可以在定义结构体时,指定对齐规则:

__attribute__((packed)); 结构体所占内存大小是所有成员所占内存大小之和
__attribute__((aligned(n))); 设置结构体占n个字节,如果n比默认值小,n不起作用;n必须是2的次方

4. 柔性数组:

struct st
{
    ...
    char a[0];
};

柔性数组不占有结构体的大小。

案例:

/**
* 求结构体数据类型的大小
*/

#include <stdio.h>

// 定义测试结构体
struct TEST1
{
	char a;// 1
	int b; // 4
};

struct TEST1_1
{
	char a;// 1
	int b;// 4
}__attribute__((packed));// 取消字节对齐,取消之后,结构体数据类型大小就等于其所有成员的数据类型之和

struct TEST1_2
{
	char a __attribute__((aligned(2)));
	int b;
};

struct TEST2
{
	char a;// 1
	short c; // 2
	int b; // 4
};

struct TEST3
{
	int num;// 4
	char name[10];// 10
	char sex;// 1
	int age;// 4
	double score;// 8
};

struct TEST4
{
	int num;// 4
	short name[5];// 10
	char sex;// 1
	int age;// 4
	int scores[2];// 8
};

int main()
{
	// 创建结构体变量
	struct TEST1 test1;
	struct TEST2 test2;
	struct TEST3 test3;
	struct TEST4 test4;
	struct TEST1_1 test1_1;
	struct TEST1_2 test1_2;
    
	// 计算大小
	printf("%lu\n",sizeof(test1));
	printf("%lu\n",sizeof(test2));
	printf("%lu\n",sizeof(test3));
	printf("%lu\n",sizeof(test4));
	printf("%lu\n",sizeof(test1_1));
	printf("%lu\n",sizeof(test1_2));
}   

推导过程:

在这里插入图片描述

快速计算结构体大小网页链接:

https://blog.csdn.net/weixin_72357342/article/details/131135555

https://blog.csdn.net/x2528238270/article/details/120798606

三、共用体/联合体类型

(一)定义

使几个不同的变量占用同一段内存的结构。共用体按定义中需要存储空间最大的成员来分配存储单元,其他成员也是用该空间,它们的首地址是相同的。

1. 定义格式

union 共用体名称
{
    数据类型 变量名;
    数据类型 变量名;
    ...
};

2. 共用体的定义和结构体类型相似:

  • 可以有名字,也可以匿名;

  • 共用体在定义时也可以定义共用体变量;

  • 共用体在定义时也可以初始化成员;

  • 共用体也可以作为形参和返回值类型使用;

  • 共用体也可以定义共用体数组;

    也就是说,结构体的语法,共用体都支持。

注意:

  • 共用体弊大于利,尽量少用,一般很少用;
  • 共用体变量在某一时刻只能存一个数据,并且也只能取出一个数;
  • 共用体和结构体都是自定义数据类型,用法类似于基本数据类型;
    • 共用体可以是共用体的成员,也可以是结构体的成员。
    • 结构体可以是结构体成员,也可以是共用体成员。

案例:

/**
* 共用体
*/
#include <stdio.h>

// 定义共用体
union S
{
	char a;
	float b;
	int c;
};

// 共用体作为共用体的成员
union F
{
	char a;
	union S s;
};

// 共用体作为结构体的成员
struct G
{
	int a;
	union S s;
};

// 定义一个结构体
struct H
{
	int a;
	char b;
};

// 结构体作为结构体成员
struct I
{
	int a;
	int b;
	struct H h;
};

// 共用体作为结构体成员
struct J
{
	int a;
	char b;
	union S s;
};

void test1()
{
	// 定义共用体类型
	union Stu
	{
        int num;
        char sex;
        double score;
	};
    
	// 定义匿名共用体:匿名共用体一般作为结构体成员或者其他共用体成员
    union
    {
        int a;
        char c;
    } c;
    
	printf("%lu,%lu\n",sizeof(union Stu),sizeof(c));
}

void test2()
{
    union C
    {
    	int a;
    	char b;
    };

    // 定义变量
    union C c;

    // 存数据
    c.a = 10;
    c.b = 'A';
    
    printf("%d---%d\n",c.a,c.b);// 取数据
    
    c.a += 5;
    printf("%d---%d\n",c.a,c.b);// 取数据

    union E
    {
    	char *f;
    	long a;
        int b;
    } e = {"hello world!"};
    
    printf("%s,%p---%ld,%p---%d\n",e.f,&(e.f),e.a,&(e.a),e.b);
}

int main()
{
	test1();
	test2();
}

四、枚举类型

(一)定义

  1. 我们一般情况下,定义常量使用宏定义(#define 宏名称 值),宏定义非常适合没有关联关系的常量;但是有时候我们可能需要对一组拥有关联关系的量进行定义,比如“周一周二”、“1月12月”等,那么使用宏定义,就不是很清晰,这个时候就需要使用到枚举。
  2. 枚举的存在就是将多个拥有关联关系的常量组合到一起,提高代码的可读性。

(二) 说明

  1. 枚举类型定义了一组常量,我们在开发中直接使用这些常量(常用)。
  2. 枚举类型也可以类似于结构体一样定义变量等操作(不常用)。
  3. 枚举常量有默认值,从0开始依次加1;我们可以在定义时指定它的值,如果个别没有赋值,可以根据赋值依次加1推导。

(三)特点

  1. 定义了一组常量,类似于定义了多个自定义常量(宏定义)
  2. 提高了代码的可读性(避免了魔术数字)

(四)定义语法

  1. 定义枚举类型名以后就可以定义该枚举类型的变量

    enum  枚举类型名  变量表;
    
  2. 在定义枚举类型的同时定义该枚举类型的变量

     enum  枚举类型名{ 枚举元素列表 }变量表;
    
  3. 直接定义枚举类型变量

    enum  { 枚举元素列表 }变量表;
    

案例

/**
* 枚举类型
*/
#include <stdio.h>

// 常量-宏定义
// 常量的命名:大写英文字母+下滑线,举例:MAX_VALUE
#define PI 3.1415926
void test1()
{
    // 定义枚举类型
    enum Week
    {
    	SUN=10,MON,TUE,WED,THU,FRI,SAT
    };
    
    printf("%d,%d,%d\n",SUN,WED,SAT);
    
    // 定义枚举类型的变量(先定义变量,后赋值)
    enum Week w;
    
    // 初始化
    w = MON;
    printf("%d\n",w);
    
    // 定义枚举类型的变量同时赋值(定义变量的同时赋值)
    enum Week w1 = THU;
    printf("%d\n",w1);
    
    enum H
    {
    	A,B,C
    } x,y;
    
   	x = B;
    y = C;
    
    printf("x=%d,y=%d\n",x,y);// 1,2
}

void test2()
{
    // 定义枚举
    enum CaiQuan
    {
   	 	SHI_TOU,JIAN_DAO,BU
    };
    
    printf("请输入0~2之间的整数:\n[0-石头,1-剪刀,2-布]\n");
    
    int number;
    scanf("%d",&number);
    
    switch(number) // switch和enum是天生的搭档
    {
        case SHI_TOU:
            printf("石头\n");
            break;
        case JIAN_DAO:
            printf("剪刀\n");
            break;
        case BU:
            printf("布\n");
            break;
    }
}

int main()
{
	test1();
	test2();
}

五、类型重命名:typedef

(一)说明

给类型重命名,不会影响到类型本身

(二)作用

给已有的类型起别名

(三)格式

typedef 已有类型名 新别名;

(四)使用

// 定义结构体
struct Student
{
    int id;
    char *name;
    char sex;
    int age;
};

// 类型重命名
typedef struct Student Stu;

// 定义变量
struct Stu stu = {1,"张三",'M',21};

// 定义结构体的同时类型重命名
typedef struct PersonInfo
{
    int a;
    double b;
} Per;

// 定义变量
struct Per per = {2,5};

(五)应用场景

  1. 数据类型复杂(结构体,共用体,枚举,结构体指针)时使用
  2. 为了跨平台兼容性,例如:
    • size_t:类型重命名后的数据类型,typedef unsigned long size_t;
    • unit_16:类型重命名后数据类型

案例:

//类型重命名
#include <stdio.h>

struct Student
{
    int age;
    char* name;
    double score;
    int arr[3];
};

    typedef struct Student Stu_t;
    typedef Stu_t* pStu_t;

void test1()
{
    Stu_t s1 = {23, "zhangsan", 23.33, {11, 22, 33}};
    printf("%d, %s, %f, %d\n", s1.age, s1.name, s1.score, s1.arr[0]);
    
    //Stu_t *p = &s1;
    Stu_t* p;
    p = &s1;
    
    pStu_t p2;
    p2 = p;
    
    printf("%d, %s, %f, %d\n", p2->age, p2->name, p2->score, p2->arr[0]);
}

int main()
{
    test1();
    
    return 0;
}
  • 16
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值