C语言:(第十章)结构体

10.1:定义

*为什么需要结构体?

        为了表示一些复杂的事物,而普通的基本类型无法满足实际要求

*什么叫结构体?

        把一些基本类型数据组合在一起形成的一个新的复合数据类型

*如何定义一个结构体?

/*
时间:2022年4月6日10:01:43
目的:如何定义一个结构体,结构体变量的赋值和初始化
*/
#include<stdio.h>
struct Student
{
     int age;
     float score;
     char sex;
};
​
int main (void)
{
struct Student st = { 80,66.6,'F' };  //初始化:定义的同时赋初值
struct Student st2;
    st2.age = 10;
    st2.score = 88;
    st2.sex = 'F';
​
    printf("%d  %f  %c\n",st.age, st.score, st.sex);
    printf("%d  %f  %c\n", st2.age, st2.score, st2.sex);
    return 0;
}
/*
在vs2019中的输出结果
~~~~~~~~~~~~~~~~~~~~~
80  66.599998  F
10  88.000000  F
~~~~~~~~~~~~~~~~~~~~~
总结:。。。
*/
/*
时间:2022年4月6日16:10:44
目的1:通过函数完成对结构体变量的输入和输出
示例:
    发送地址还是发送内容
      指针的优点之一:
         1快速的传递数据
         2耗用内存小
         3执行速度快
*/
#include<stdio.h>
#include<string.h>
​
//定义结构体类型struct student
 struct Student
{
    int age;
    char sex;
    char name[100];
};   //分号不可省略
​
//声明输入输出函数
void  InputStudent (struct Student *);
void  OutputStudent (struct Student *);
​
//main函数,程序入口
int main(void)
{
    struct Student st;
    printf("字节 = %d\n",sizeof(st));
    //想要修改学生结构体变量st的值
    //必须通过传递st地址去修改
    InputStudent(&st);  //pstu只占4个字节
    //输出学生信息既可以传递结构体变量
    //也可以传递结构体变量地址
    OutputStudent(&st);  //可以发送st的地址,也可以直接发送st的内容,但为了减少内存的耗费,也为了提高执行度,推荐发送地址
    return 0;
}
//输出学生结构体成员信息的函数
void OutputStudent(struct Student *pst )
    {
        printf("age = %d  sex = %c  name = %s\n",pst->age,pst->sex,pst->name );
    }
//输入学生结构体成员信息的函数
void  InputStudent(struct Student * pstu)
    {
        (*pstu).age = 10;
        strcpy_s(pstu->name, "张三");   //strcpy:字符串拷贝
        pstu->sex = 'F';
    }
​
/*
//本函数无法修改主函数26行st的值,所以本函数是错误的
void  InputStudent(struct Student stu)
{
    stu.age = 10;
    strcpy(stu.name, "张三");  //不能写成stu.name = "张三";strcpy:string copy
    stu.sex = 'F';
}
*/
/*
在vs2019中的输出结果
~~~~~~~~~~~~~~~~~~~~~~~
字节 = 108
age = 10  sex = F  name = 张三
~~~~~~~~~~~~~~~~~~~~~~~
总结:注意大小写(第30,35行)
*/

*三种方式,推荐使用第一种

​ //第一种方式

        struct Student

        ​ {

                ​ int age;

                ​ float score;

                ​ char sex;

        ​ };

​ //第二种方式

        struct Student2

        {

                ​ int age;

                ​ float score;

                ​ char sex;

        } st2;

​ //第三种方式

        struct

        {

​                 int age;

​                 float score;

                ​ char sex;

        } st3;

*怎么使用结构体变量?

        1:赋值和初始化

        (1)定义的同时可以整体赋初值

        (2)如果定义完之后,则只能单个的赋初值

        2:如何取出结构体变量中的每一个成员

        (1)结构体变量名.成员名

        (2)指针变量->成员名【在计算机内部会被转化成(*指针变量名).成员名来执行】

                1>pst->age在计算机内部会被转化成(*pst).age,这是->的含义,也是一种硬性规定

                2>所以pst->age等价于(*pst).age也等价于st.age

                3>我们之所以知道pst->age等价于st.age,是因为pst->age被转化成了(*pst).age来执行

                4>pst->age的含义:pst所指向的那个结构体变量中的age这个成员

        3:结构体变量的运算

                结构体变量不能相加减,相乘除, 但其可以相互赋值

        例子:
struct Student
{
    int age;
    char sex;
    char name[100];
};
struct Student st1,st2;
st1+st2  st1*st2 st1/st2;  //都是错的
St1  = st2 或者 st2 = st1  //都是对的

        4:结构体变量和结构体指针变量作为函数参数传递的问题

                推荐使用结构体指针变量作为函数参数来传递

                举例:动态构造存放学生信息的结构体数组

                动态构造一个数组,存放学生的信息,然后按分数排序输出

10.2:typedef

typedef的4种常见用法:

        一、给已定义的变量类型起个别名

        二、定义函数指针类型

        三、定义数组指针类型

        四、定义数组类型

        总结一句话:

                “加不加typedef,类型是一样的“,这句话可以这样理解: 没加typedef之前如果是个数组,那么加typedef之后就是数组类型; 没加typedef之前如果是个函数指针,那么加typedef之后就是函数指针类型; 没加typedef之前如果是个指针数组,那么加typedef之后就是指针数组类型;

typedef char TA[5];  //定义数组类型
typedef char *TB[5];  //定义指针数组类型,定义的变量为含5个char*指针元素的数组(指针数组类型)
typedef char *(TC[5]);  //指针数组类型,因为[]的结合优先级最高,所以加不加()没啥区别,TC等价于TB
typedef char (*TD)[5];  //数组指针类型

        10.2.1:给已定义的变量类型起个别名

① typedef unsigned char uint8_t;       //uint8_t就是unsigned char的别名,这是最基础的用法
​
②
struct __person
{
    char name[20];
    uint8_t age;
    uint8_t height;
}
typedef __person person_t;
//以上两段代码也可合并为一段,如下:
typedef struct __person
{
    char name[20];
    uint8_t age;
    uint8_t height;
}person_t;
//作用是给struct  __person起了个别名person_t,这种这种用法也很基础

        10.2.2:定义函数指针类型

我们首先来看一下如何定义函数指针变量,然后再看如何定义函数指针类型

1、定义函数指针变量

        ① int (*pFunc)(char *frame, int len);

                定义了一个函数指针变量pFunc,它可以指向这样的函数:返回值为int,形参为char*、int

        ② int (pFunc[5])(int len);

                定义了5个函数指针变量:pFunc[0]、pFunc[1]···,它们都可以指向这样的函数:返回值为int*,形参为int

2、定义函数指针类型

定义函数指针类型,必须使用typedef,方法就是,在“定义函数指针变量”加上typedef。

        typedef int (*pFunc_t)(char *frame, int len); //定义了一个类型pFunc_t

        举例:

typedef  int (*pFunc_t)(char *frame, int len);//定义了一个类型pFunc_t
int read_voltage(char *data, int len)
{
    int voltage = 0;
    ···//其他功能代码
```
return voltage;
```
}
int main(void)
{
    pFunc_t   pHandler = read_voltage;//使用类型pFunc_t来定义函数指针变量
    ···//其他功能代码
}

        10.2.3:定义数组指针类型

这个问题还是分两步,先看如何定义数组指针变量,再看如何定义数组指针类型

1、定义数组指针变量

        ① int(*pArr)[5]; //定义了一个数组指针变量pArr,pArr可以指向一个int [5]的一维数组

        ② char(*pArr)[4][5]; //定义了一个数组指针变量pArr,pArr可以指向一个char[4][5]的二维数组

举例:

int(*pArr)[5];//pArr是一个指向含5个int元素的一维数组的指针变量
int a[5] = {1,2,3,4,5};
int b[6] = {1,2,3,4,5,6};
pArr = &a;//完全合法,无警告
pArr = a;//发生编译警告,赋值时类型不匹配:a的类型为int(*),而pArr的类型为int(*)[5]
pArr = &a[0];//发生编译警告,赋值时类型不匹配:a的类型为int(*),而pArr的类型为int(*)[5]
pArr = &b;//发生编译警告,赋值时类型不匹配:&b的类型为int(*)[6],而pArr的类型为int(*)[5]
pArr = (int(*)[5])&b;//类型强制转换为int(*)[5],完全合法,无警告

        上面这个例子中,使用类型转换时,代码的样式略显复杂,试想,我们如果强转为一个结构体数组的指针,那这个强转的括号里的内容得多长!这就直接影响了代码的可读性,因此,强转后的类型应该定义出来。

2、定义数组指针类型

如同上面定义函数指针类型的方法,直接在前面加typedef即可,例如

        typedef int (*pArr_t)[5];//定义了一个指针类型pArr_t,该类型的指针可以指向含5个int元素的数组

typedef int(*pArr_t)[5];//定义一个指针类型,该类型的指针可以指向含5个int元素的一维数组
​
int main(void)
{
    int a[5] = {1,2,3,4,5};
    int b[6] = {1,2,3,4,5,6};
    pArr_t pA;//定义数组指针变量pA
    pA= &a;//完全合法,无警告    
    pA= (pArr_t)&b;//类型强制转换为pArr_t,完全合法,无警告
}

        10.2.4:定义数组类型

如果我们想声明一个含5个int元素的一维数组,一般会这么写:int a[5];

如果我们想声明多个含5个int元素的一维数组,一般会这么写:int a1[5], a2[5], a3[5]···,或者 aN

可见,对于定义多个一维数组,写起来略显复杂,这时,我们就应该把数组定义为一个类型,例如:

typedef int arr_t[5];//定义了一个数组类型arr_t,该类型的变量是个数组
​
int main(void)
{
    arr_t d;        //d是个数组,这一行等价于:  int d[5];
    arr_t b1, b2, b3;//b1, b2, b3都是数组
```
d[0] = 1;
d[1] = 2;
d[4] = 134;
d[5] = 253;//编译警告:下标越界
```
}
typedef struct _jmp_buf
{ 
    int _jb[_JBLEN + 1]; 
} jmp_buf[1];
上面这一段定义来自C库函数 <setjmp.h>,为了理解这个定义,我们把它分解为下面这个样子:

typedef struct _jmp_buf
{ 
    int _jb[_JBLEN + 1]; 
} _jmp_buf_t;
​
typedef _jmp_buf_t jmp_buf[1];
//jmp_buf是一个含一个元素的数组类型,数组的元素为_jmp_buf_t类型,也即struct  _jmp_buf类型。
这种定义有什么作用呢?看个例子:

jmp_buf buf;//这一行等价于:struct _jmp_buf buf[1];
buf->_jb[5] = 34;//这一行等价于: (&buf[0])->_jb[5] = 34;
handle(buf);//这一行等价于:handle(&buf[0])

​         本例中,buf变量其实是个含一元素的数组,由于数组名本身就是第一个一级成员的指针,而该数组的成员又是个结构体,因此,我们可以直接用数组名指向结构体的成员__jb。这种技巧有什么好处:例子中,

        1、定义buf变量,就相当于定义了一个结构体变量,而且buf就是这个结构体实体的指针

        2、想把结构体的地址传给某个函数时,不必写取地址符了,buf本身就是地址。总结起来就一句话,jmp_buf这种类型,在定义变量实体的同时,也获得了该变量的地址。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值