C语言 超详细 零基础入门 共用体(union) typedef的使用

C语言 超详细 零基础入门 结构体数组 结构体指针 数据结构-CSDN博客

6、共用体类型(union)

6.1 共用体概述

有时需要一种数据结构,不同的场合表示不同的数据类型。比如,如果只用一种数据结构表示学生的"成绩",这种结构就需要有时是整数(80、90),有时是字符('A'、'B'),又有时是浮点数(80.5、60.5)。

**C 语言提供了共用体类型(Union 结构),用来自定义可以灵活变更的数据结构。**它内部可以包含各种属性,但同一时间只能有一个属性,因为所有属性都保存在同一个内存地址,后面写入的属性会覆盖前面的属性。这样做的最大好处是节省内存空间

"共用体"与"结构体"的定义形式相似,但它们的含义是不同的。

  • 结构体变量所占内存长度是各成员占的内存长度之和;每个成员分别占有其自己的内存单元。
  • 共用体变量所占的内存长度等于最长的成员的长度;几个成员共用一个内存区。

6.2 声明共用体

格式:

union 共用体类型名称{
    数据类型 成员名1;
    数据类型 成员名2;
    ...
    数据类型 成员名n;
};

举例:

union Data {
    short m;
    float x;
    char c;
};

上例中, union 命令定义了一个包含三个属性的数据类型 Data。虽然包含三个属性,但是同一时间只能取到一个属性。最后赋值的属性,就是可以取到值的那个属性。

6.3 声明共用体变量

方式1:先定义共用体类型,再定义共用体变量

union Data {
    short m;
    float x;
    char c;
};

//声明共用体变量
union Data a, b;

方式2:定义共用体类型的同时定义共用体变量

union Data {
    short m;
    float x;
    char c;
} a, b;

方式3:直接定义共用体类型变量

union {
    short m;
    float x;
    char c;
} a, b;

以共用体变量a为例,它由3个成员组成,分别是m、x和c,编译时,系统会按照最长的成员为它分配内存,由于成员x的长度最长,它占4个字节,所以共用体变量a的内存空间也为4个字节。

union {
    short m;
    float x;
    char c;
} a, b;

int main(){
    printf("%d\n",sizeof(a));  //4
    return 0;
}

6.4 调用共同体变量的成员

正确的方式

方式1:

union Data a;
a.c = 4;

方式2:声明共同体变量的同时,给任一成员赋值

union Data a = {.c = 4};

方式3:声明共同体变量的同时,给首成员赋值

union Data a = {8};

注意,方式3不指定成员名,所以只能为第一个成员进行赋值。

错误的方式

union Data a = {1,1.5,'a'};  //错误的

后续操作

执行完上面的代码以后, 另外取其它属性取不到值。

int main() {
    union Data a = {.c = 4};

    printf("c is %i\n", a.c); // c is 4
    printf("x is %f\n", a.x); // 未定义,x is 0.000000
}

如果要让 a.x属性可以取到值,就要先为它赋值。

int main() {
    union Data a = {.c = 4};

    printf("c is %i\n", a.c); // c is 4
    printf("x is %f\n", a.x); // 未定义,x is 0.000000

    a.x = 0.5;
    printf("x is %f\n", a.x); // x is 0.500000
    printf("c is %i\n", a.c); // c is 0
}

一旦为其他属性赋值,原先可以取到值的 a.c 属性就不再有效了。除了这一点,Union 结构的其他用法与 Struct 结构,基本上是一致的。

6.5 ->操作符

Union 结构也支持指针运算符 -> 。

union evaluation { //评价
    int score;
    float grade;
    char level;
};

int main() {
    union evaluation e;
    e.score = 85;

    union evaluation *p;
    p = &e;
    printf("%d\n", p->score); // 85

    return 0;
}

上例中, p 是 e 的指针,那么 p->score等同于 e.score。

了解:Union指针与它的属性有关,当前哪个属性能够取到值,它的指针就是对应的数据类型。

union foo {
    int a;
    float b;
} x;

int main() {
    x.a = 12;
    int *p1 = (int *) &x;
    printf("%d\n", x.a);  // 12
    printf("%d\n", *p1); // 12

    x.b = 3.141592;
    float *p2 = (float *) &x;
    printf("%f\n", x.b);  // 3.141592
    printf("%f\n", *p2); // 3.141592

    return 0;
}

上例中, &x 是 foo 结构的指针,它的数据类型完全由当前赋值的属性决定。

6.6 补充说明

  • 不能对共用体变量名赋值,也不能企图引用变量名来得到一个值。只能引用共用体变量中的成员。

  • C99允许同类型的共用体变量互相赋值。

    a.i //引用共用体变量中的整型变量i
    a.ch //引用共用体变量中的字符变量ch
    a.f //引用共用体变量中的实型变量f

    printf("%d",a); //错误的

    printf("%d",a.i); //正确的

  • C99允许用共用体变量作为函数参数。

    b = a; //a和b是同类型的共用体变量,合法

  • 共用体类型可以出现在结构体类型定义中,也可以定义共用体数组。反之,结构体也可以出现在共用体类型定义中,数组也可以作为共用体的成员。

6.7 练习

现有一张关于学生信息和教师信息的表格。

学生信息包括姓名、编号、性别、职业、分数;

教师的信息包括姓名、编号、性别、职业、教学科目。

请看下面的表格,请使用共用体编程完成。

|----------|--------|------------|----------------|------------------|
| name | id | gender | profession | score/course |
| 孙悟空 | 12345 | 男(m) | 学生(s) | 89.5 |
| 菩提祖师 | 1001 | 男(m) | 老师(t) | math |
| 孙尚香 | 54321 | 女(f) | 学生(s) | 92.0 |
| 诸葛亮 | 34567 | 男(m) | 老师(t) | english |

#define TOTAL 2 //人员总数

struct Person {
    char name[20];
    int id;
    char gender; //性别  m->男  f->女
    char profession;//职业 s->学生  t->老师
    union {
        float score;
        char course[20];
    } sc; //sc 是一个共用体变量
};

int main() {
    int i;
    struct Person persons[TOTAL];  //定义一个结构体数组

    //输入人员信息
    for (i = 0; i < TOTAL; i++) {
        printf("Input info: ");
        scanf("%s %d %c %c", persons[i].name, &(persons[i].id), &(persons[i].gender), &(persons[i].profession));
        if (persons[i].profession == 's') { //如果是学生
            printf("请输入学生成绩:");
            scanf("%f", &persons[i].sc.score);
        } else { //如果是老师
            printf("请输入老师课程:");
            scanf("%s", persons[i].sc.course);
        }
    }

    //输出人员信息
    printf("\nName\tid\tgender\tProfession\tScore/Course\n");
    for (i = 0; i < TOTAL; i++) {
        if (persons[i].profession == 's') { //如果是学生
            printf("%s\t%d\t%c\t%c\t\t%f\n", persons[i].name,
                   persons[i].id, persons[i].gender, persons[i].profession, persons[i].sc.score);
        } else { //如果是老师
            printf("%s\t%d\t%c\t%c\t\t%s\n", persons[i].name,
                   persons[i].id, persons[i].gender, persons[i].profession, persons[i].sc.course);
        }

    }
    return 0;
}

7、typedef 的使用(熟悉)

7.1 为什么使用typedef

C语言允许为一个数据类型起一个新的别名,就像给人起"绰号"一样。

起别名的目的不是为了提高程序运行效率,而是为了编码方便。例如,有一个结构体的名字是 student,定义一个结构体变量stu1,代码如下:

struct student stu1;

struct 看起来就是多余的,但不写又会报错。如果为 struct student起了一个别名 Student,书写起来就简单了:

Student stu1;

这种写法更加简练,意义也非常明确,不管是在标准头文件中还是以后的编程实践中,都会大量使用这种别名。

7.2 使用格式

用typedef声明数组类型、指针类型,结构体类型、共用体类型等,使得编程更加方便。

1、为某个基本类型起别名

typedef 命令用来为某个类型起别名

typedef 类型名 别名;

习惯上,常把用typedef声明的类型名的第1个字母用大写表示,以便与系统提供的标准类型标识符相区别。

举例:

typedef int Integer;  //用Integer作为int类型别名,作用与int相同
Integer a, b;
a = 1;
b = 2;

Integer a, b;等同于int a, b;

举例:

typedef unsigned char Byte;  //为类型 unsign char 起别名 Byte
Byte c = 'z';

注意:使用 typedef 可以为基本类型一次起多个别名。

typedef int chocolate, doughnut, mushroom; //一次性为 int 类型起了三个别名

2、为结构体、共用体起别名

为 struct、union等命令定义的复杂数据结构创建别名,从而便于引用。

struct treenode {
  // ...
};

typedef struct treenode* Tree;  //Tree 为 struct treenode* 的别名

typedef 也可以与 struct 定义数据类型的命令写在一起。

typedef struct animal {
  char* name;
  int legs;
  int speed;
} Animal;

上例中,自定义数据类型时,同时使用 typedef 命令,为 struct animal 起了一个别名 Animal 。

这种情况下,C 语言允许省略 struct 命令后面的类型名。

typedef struct {
  char* name;
  int legs;
  int speed;
} Animal;

上例相当于为一个匿名的数据类型起了别名 Animal 。进而:

//使用typedef之前
struct animal dog;

//使用typedef之后
Animal dog;

再举例:typedef 命令可以为 union 数据类型起别名。

typedef union {
  short count;
  float weight;
  float volume;
} quantity;

上例中, union 命令定义了一个包含三个属性的数据类型, typedef 命令为它起别名为quantity 。

3、为指针起别名

typedef 可以为指针起别名。

typedef int* intptr;
int a = 10;
intptr x = &a;

上例中, intptr 是 int* 的别名。不过,使用的时候要小心,这样不容易看出来,变量 x 是一个指针类型。

再举例:

typedef char* String;

char * str1 = "hello"; //之前的写法
String str2 = "hello"; //现在的写法

为字符指针起别名为 String,以后使用 String声明变量时,就可以轻易辨别该变量是字符串。

4、为数组类型起别名

typedef 也可以用来为数组类型起别名。

//举例1
typedef int five_ints[5];
five_ints x = {11, 22, 33, 44, 55};  

//举例2
typedef int Num[100]; //声明Num为整型数组类型名
Num a; //定义a为整型数组名,它有100个元素

上例中, five_ints 是一个数组类型。我们把原有的int [5]看做是数组的类型。

举例:指针数组

typedef int (*PTR_TO_ARR)[4];

表示 PTR_TO_ARR 是类型int * [4]的别名,它是一个二维数组指针类型。接着可以使用 PTR_TO_ARR 定义二维数组指针:

PTR_TO_ARR p1, p2;

5、为函数起别名

typedef 为函数起别名的写法如下

typedef signed char (*fp)(void);

类型别名 fp 是一个指针,代表函数 signed char (*)(void) 。

再举例:

typedef int (*PTR_TO_FUNC)(int, int);
PTR_TO_FUNC pfunc;

7.3 举 例

#include <stdio.h>

int add(int a, int b) {
    int sum = a + b;
    return sum;
}

char str[3][30] = {"尚硅谷教育", "www.atguigu.com", "010-56253825"};


typedef int(*PTR_FUNC)(int,int);

typedef char (*PTR_ARR)[30];

int main() {

    //使用函数指针
    int (*add_ptr)(int,int);
    add_ptr = &add;
    int sum = (*add_ptr)(10,20);
    printf("sum = %d\n",sum);

    //使用数组指针
    char (*arr_ptr)[30]; //arr_ptr是一个指针,指向30个元素构成的char型数组
    arr_ptr = str; //将str[0]的首地址赋给ptr_arr指针
    for(int i = 0;i < 3;i++){
        printf("str[%d]=%s\n",i,*(arr_ptr + i));
    }

    //使用typedef之后:
    //调用函数
    PTR_FUNC ptr_add = &add;
    int sum1 = (*ptr_add)(10,20);
    printf("sum1 = %d\n",sum1);

    //调用数组
    PTR_ARR ptr_arr = str;
    for(int i = 0;i < 3;i++){
        printf("str[%d]=%s\n",i,*(ptr_arr + i));
    }

    return 0;
}

7.4 小 结

(1) typedef的方法实际上是为特定的类型指定了一个同义字(synonyms)。

(2) 用typedef只是对已经存在的类型指定一个新的类型名,而没有创造新的类型。

(3) typedef与#define是不同的。#define是在预编译时处理的,它只能作简单的字符串替换,而typedef是在编译阶段处理的,且并非简单的字符串替换。

(4) 当不同源文件中用到同一类型数据(尤其是像数组、指针、结构体、共用体等类型数据)时,常用typedef 声明这些同一的数据类型。

技巧:可以把所有的typedef名称声明单独放在一个头文件中,然后在需要用到它们的文件中用#include指令把它们包含到文件中。这样编程者就不需要在各文件中自己定义typedef名称了。

(5) 使用typedef名称有利于程序的通用与移植。有时程序会依赖于硬件特性,用typedef类型就便于移植。

某一个值在不同计算机上的类型,可能是不一样的。

int i = 100000;

上面代码在32位整数的计算机没有问题,但是在16位整数的计算机就会出错。C 语言的解决办法,就是提供了类型别名,在不同计算机上会解释成不同类型,比如 int32_t 。

int32_t i = 100000;

上例将变量 i 声明成 int32_t 类型,保证它在不同计算机上都是32位宽度,移植代码时就不会出错。

这一类的类型别名都是用 typedef 定义的。下面是类似的例子。

typedef long int ptrdiff_t;
typedef unsigned long int size_t;
typedef int wchar_t;

这些整数类型别名都放在头文件 stdint.h ,不同架构的计算机只需修改这个头文件即可,而无需修改代码。

7.5 应用场景

场景1:

在考研中, typedef主要用在结构体的定义过程中,如二叉树结点的结构体定义,其他地方几乎不用。新定义的结构体若没有名字,则用typedef 给它起个名字是有必要的。

场景2:

对于已有的数据类型,如int、float等已经有了简洁的名字,还有必要给它起个新名字吗?有必要,但不是在考研数据结构中。举个例子:

在一个大工程中,对于其中的一个变量,在整个工程中都已经用int 型定义过了,但是工程如果要求修改,将所有int 型换成long型,如果事先给int型起个新名字为ElemType,则在整个工程中凡是类似于int x;的语句都写成ElemType x; ,此时只需将typedef int ElemType这一句中的int 换成long 即可实现全局的数据类型替换,这就是typedef 的意义所在。(上述这些对考研答卷的实际意义并不大。)

typedef int ElemType;
ElemType i1, i2, i3;

上例中,变量 i1 、 i2 、 i3 的类型都是 int。如果以后需要为它们改类型,只需要修改typedef 语句即可。

typedef long ElemType;

上面命令将变量 i1 、 i2 、 i3 的类型都改为 long。

非常感谢您阅读到这里,创作不易!如果这篇文章对您有帮助,希望能留下您的点赞 👍 关注 💖 收藏 💕 评论 💬 感谢支持!!!

听说 三连能够给人 带来好运!更有可能年入百w,进入大厂,上岸

[ 本文作者 ]   软工菜鸡
[ 博客链接 ]   https://blog.csdn.net/m0_67184231
[ 版权声明 ]   如果您在非 CSDN 网站内看到这一行,
说明该死的侵权网络爬虫可能在本人还没有完整发布的时候就抓走了我的文章,
可能导致内容不完整,请去上述的原文链接查看原文。
相关推荐
逊嘘2 分钟前
【Java语言】抽象类与接口
java·开发语言·jvm
van叶~5 分钟前
算法妙妙屋-------1.递归的深邃回响:二叉树的奇妙剪枝
c++·算法
Half-up5 分钟前
C语言心型代码解析
c语言·开发语言
简简单单做算法6 分钟前
基于Retinex算法的图像去雾matlab仿真
算法·matlab·图像去雾·retinex
morris13110 分钟前
【SpringBoot】Xss的常见攻击方式与防御手段
java·spring boot·xss·csp
云卓SKYDROID20 分钟前
除草机器人算法以及技术详解!
算法·机器人·科普·高科技·云卓科技·算法技术
Source.Liu27 分钟前
【用Rust写CAD】第二章 第四节 函数
开发语言·rust
monkey_meng27 分钟前
【Rust中的迭代器】
开发语言·后端·rust
余衫马30 分钟前
Rust-Trait 特征编程
开发语言·后端·rust
monkey_meng33 分钟前
【Rust中多线程同步机制】
开发语言·redis·后端·rust