标准c语言8

结构:
什么是结构:

是一种由程序员设计的复合数据类型,它由若干个其它类型的成员组成,用于统一描述事物的各项属性。

使用各类型的变量也可以描述事物的各项属性(如:通讯录项目),但使用麻烦且容易出错,没有使用结构方便,安全性高、统一性高,同时结构也是面向对象编程的基础。

基础C语言编程思想:面过过程

如何设计结构:
struct 结构名 // 结构名一般首字母大写
{
    成员类型 成员名;
    ...
};
​
​
//  设计联系人结构
struct Contact
{
    int id; 
    char name[20];
    char sex;
    char tel[12];
};
//  注意:一般结构的设计放置在函数外,这样所有函数都可以使用该结构类型
//  注意:结构设计完成后,仅仅只是设计出一种数据类型而已,必须通过该类型定义结构变量、结构数组、或者分配堆内存才能正常使用
如何使用结构:
// 在C语言中,使用结构类型时 struct 关键字不能省略
struct 结构名 结构变量名;
​
//  定义结构数组
struct 结构名 结构数组名[数量];
​
//  定义在堆内存
//  一般结构变量字节数较大,建议定义在堆内存中
struct 结构名* 结构指针变量 = malloc(sizeof(struct 结构名)*数量);
​
//  结构指针传参 提高传参效率
//  不希望被修改 所以const保护
void func(const struct 结构名* con)
{
​
}
​
初始化结构变量:
// 顺序初始化,数据要与成员的顺序一一对应。
struct 结构名 结构变量名 = {v1,v2,v3,...}; 
struct 结构名 结构数组[n] = {
    {v1,v2,v3,...},
    {v1,v2,v3,...},
    ...
};
​
// 指定成员初始化 
//  初始化顺序无所谓 没有初始化的成员会默认为0
struct 结构名 结构变量名 = {
    .成员名1 = 初始化数据1,
    .成员名2 = 初始化数据2,
    ...
};
​
struct 结构名 结构数组[n] = {
    {.成员名1 = 初始化数据1,.成员名2 = 初始化数据2,...},
    {.成员名1 = 初始化数据1,.成员名2 = 初始化数据2,...},
    {.成员名1 = 初始化数据1,.成员名2 = 初始化数据2,...},
    ...
};
访问成员:
// 结构变量.成员名;
// 结构指针->成员名;
​
​
#include <stdio.h>                                                                                                         
#include <string.h>
#include <stdlib.h>
​
//  设计联系人结构
struct Contact
{
    int id;
    char name[20];
    char sex;
    char tel[12];
};
​
//  结构指针传参 提高传参效率
//  不希望被修改 所以const保护
void show_con(const struct Contact* con)
{
    printf("%d %s %c %s\n",
            con->id,con->name,con->sex,con->tel);
}
​
int main(int argc,const char* argv[])
{
    char str[] = "hehe";
    //  定义结构变量
    struct Contact con = {.sex = 'm',.name = "uuu"};
    
    show_con(&con);
​
    //  访问结构变量的成员
    con.id = 1001;
    strcpy(con.name,"hehe");
    con.sex = 'w';
    strcpy(con.tel,"1234123123");
​
    //printf("%d %s %c %s\n",con.id,con.name,con.sex,con.tel);
    show_con(&con);
​
    //  定义结构数组
    struct Contact arr[10];
​
    //  定义在堆内存
    struct Contact* p = malloc(sizeof(struct Contact)*10);
    if(NULL == p)
    {
        perror("malloc");
        return 0 ;
    }
}
使用typedef重定义简短的类型名:

在C语言中,struct 结构名 才是完整的数据类型名,但使用时比较麻烦,可以使用typedef给结构重定义简短的类型名。

// 结构设计完成后重定义
typedef struct 结构名 结构类型名;
typedef struct Contacts Contacts;
​
// 设计结构时重定义
typedef struct 结构名
{
    成员类型 成员名;
    ...
}结构名;
​
typedef struct Contact
{
    ...
}Contact;
​
Contact con;
练习1:

设计一个教师结构体(姓名,工龄,工号,科目,...),定义教师结构变量,使用scanf从终端输入各成员的值,然后使用printf显示结构变量。

typedef struct Teacher
{
    char name[20];
    char age;
    char sex;
    char id[8];
    char subject[10];
}Teacher;
​
int main(int argc,const char* argv[])
{
    /*
    char str[10] = {};
    scanf("%s",str);
    printf("str:%s\n",str);
    */
​
    Teacher tch = {};
    printf("请输入教师信息:");
    scanf("%s %hhd %c %s %s",
        tch.name,&tch.age,&tch.sex,tch.id,tch.subject);
​
    printf("%s %hhd %c %s %s\n",
        tch.name,tch.age,tch.sex,tch.id,tch.subject);
} 

如何计算结构的总字节数:

1、结构变量的总字节数 >= 所有成员的字节数之和

2、结构成员的顺序会影响结构的总字节数

3、了解结构总字节数的计算规则,可以通过合理安排结构的成员顺序,从而达到节约内存的目的

4、计算机为了提高结构成员的访问速度,会在成员之间以及结构内存的末尾填充一些空闲内存,称为内存对齐、内存补齐行为

5、在笔试题中内存对齐、补齐考量较大。

内存对齐:

假定从0字节排列结构的第一个成员,之后所有成员的起始字节数,必须是成员本身字节数的整数倍,如果不是则填充一些空闲字节,直到是为止。

struct Data
{
    char c; //  0
            //  1 2 3 空闲字节
    int i;  //  4 5 6 7
    double d; // 8 9 10 11 12 13 14 15
}Data;
内存补齐:

结构的总字节数必须是它最大成员字节数的整数倍,如果不是则在结构的末尾填充一些空闲字节。

struct Data
{
    char c; //  0
            //  1 2 3 空闲字节
    int i;  //  4 5 6 7
    double d; // 8 9 10 11 12 13 14 15
    char c1; // 16
             // 总字节为17,不能被4整除,所以会在末尾填充三个空闲字节
             // 17 18 19 因此Date的总字节数是20
}Data;
注意:

在32位系统下,内存对齐、内存补齐字节数是有上限的,超过上限按4字节计算。

// 在Linux32位系统下,超过4字节按4字节计算。
#include <stdio.h>
// 在Windows32位系统下,超过8字节按4字节计算。
typedef struct Data
{
    char ch;            // 0
                        // 1 2 3 
    long double num;    // 4 ~ 15
    short sh;           // 16 17
                        // 18 19                                               
}Data;
​
int main(int argc,const char* argv[])
{
    printf("%d\n",sizeof(Data));    // 结果是20字节
}
#include <stdio.h>
// 在Windows32位系统下,未超过8字节,按成员的实际字节对齐补齐
typedef struct Data
{
    char ch;    // 0
                // 1 2 3 4 5 6 7 
    double num; // 8 ~ 15
    short sh;   // 16 17
                // 18 19 20 21 22 23                                       
}Data;
​
int main(int argc,const char* argv[])
{
    printf("%d\n",sizeof(Data)); // 结果是24字节
}
#include <stdio.h>
// Windows64位系统和Linux64位系统,都按成员的字节数计算内存对齐、内存补齐
typedef struct Data
{
    char ch;    // 0
                // 1 2 3 4 5 6 7 8 9 11 12 13 14 15
    long double num;    // 16 ~ 31
    short sh;   // 32 33
                // 34 35 36 37 38 39 40 41 42 42 44 45 46 47                                               
}Data;
​
int main(int argc,const char* argv[])
{
    printf("%d\n",sizeof(Data));
}
注意:如果结构成员是数组类型,那么计算对齐补齐时,应当选择该数组的成员类型字节数计算
struct Data
{
    char c;             // 0
    char str[20];       // 1~20
};
//  Data的总字节数是21 而不是24
long类型的字节数:

Linux32系统 4字节

Linux32系统 8字节

Windows32系统 4字节

Windows64系统 4字节

注意:一般结构变量、结构数组所占用的连续内存可能较大,所以建议存储在堆内存

结构成员的位域:

早期由于计算机内存资源比较匮乏,一种节约内存的方式。

// 设计结构时重定义
typedef struct 结构名
{
    成员类型 成员名:n; // 设置该成员只使用n个二进制位
    ...
}结构名;

联合:union

也是一种由程序员设计的复合数据类型,使用语法与结构一模一样,与结构不同的是,结构中的成员各自拥有独立的内存,而联合中的所有成员共用一块内存(也叫共用体),所以只要有一个成员的值发生变化,其它成员的也会跟着一起变化。

union 联合名
{
    成员类型 成员名;
    ...
};

联合的总字节数:(考点)

由于联合的所有成员共用一块内存,所有成员是天然对齐的,不需要考虑内存对齐,但要考虑内存补齐。

情况1:所有联合的成员都是基本类型,则联合的总字节数就是最大成员的字节数。

union D
{
    char c;
    int i;
    double d;   
};

情况2:如果联合的成员有数组类型,则联合的总字节数应该是最大成员的整数倍。

union D
{
    char ch[5];
    int n;
 };//总字节是8,在末尾内存补齐了3个空白字节
​

使用联合的意义:

1、使用少量的内存对应若干个标识符,只要不同时使用联合的成员,就不会冲突,能大大节约内存,在早期计算机内存比较小时,该技术使用较多,现在随着计算机内存越来越大已经基本不再使用。

2、联合可以对一块内存进行不同格式的解释,所以在网络通信时还存在着少量的使用(使用网络协议中已经设计好的联合体)。UDP\TCP了解

大端系统和小端系统:

假定有一个int类型变量,它的4字节的内存地址分别是:

int num;
​
0xbf9f3828  // 低位地址
0xbf9f3829
0xbf9f382a
0xbf9f382b  // 高位地址

假定有一个十六进制的整数:0xa1b2c3d4

0xa1    //高位数据
0xb2
0xc3
0xd4    //低位数据
大端系统:

低位数据存储高位地址,或者说是高位数据存储在低位地址,一般大型的服务器、网络设备采用的是大端系统,所以大端格式也叫网络字节序。

int num = 0xa1b2c3d4;
​
0xbf9f3828  存储的是0xa1
0xbf9f3829  存储的是0xb2
0xbf9f382a  存储的是0xc3
0xbf9f382b  存储的是0xd4
小端系统:

低位数据存储在低位地址,或者说高位数据存储在高位地址,一般的个人计算机采用的是小端系统。

int num = 0xa1b2c3d4; 
​
0xbf9f3828  存储的是0xd4
0xbf9f3829  存储的是0xc3
0xbf9f382a  存储的是0xb2
0xbf9f382b  存储的是0xa1
注意:

数据存储的是大端格式还是小端格式是由计算机的CPU决定的。

练习:常考笔试题

实现判断当前系统是大端还是小端功能。

1、可以使用联合解决

2、直接使用指针解决

int main(int argc,const char* argv[])
{
    union Data d;
    d.i = 0xa1b2c314;
    if(0x14 == d.ch)
    {
        printf("小端\n");
    }
    else
    {
        printf("大端\n");
    }
​
    int num = 0x10203040;
    char* p = (char*)&num;
    if(0x40 == *p)
    {
        printf("小\n");
    }
    else                                                                                                         
    {
        printf("大\n");
    }
}

枚举:

是一种值受限的整数类型,由程序员设置它的值的范围,并且还可以给这些值取一个名字。

设计枚举:
typedef enum 枚举名
{
    标识符名=枚举值1,
    枚举值2,
    枚举值3,
    ...
}枚举名;
​
注意:使用标识符作为枚举值
    
// enum与struct、union一样,使用typedef重定义省略enum关键字
定义枚举变量:

enum 枚举名 枚举变量;

1、理论上枚举变量只能使用枚举值赋值,这样可以提高代码的可读性和安全性。

2、C语言编译器为了提高编译速度,不会检查枚举变量的赋值,全靠程序员的自觉(枚举变量就是int类型变量)。

3、C++编译器类型检查比较严格,所以使用C++编译器编译C代码时,枚举变量只能由枚举值赋值、比较。

枚举值:

1、第一个枚举值的默认值是0,之后的枚举值逐渐递增+1。

#include <stdio.h>
​
enum DirectionKey
{
    Up,
    Down,
    Right,
    Left
};
​
int main(int argc,const char* argv[])
{
    enum DirectionKey key;
    key = Up;
    printf("%d\n",key); // 0
    key = Down;
    printf("%d\n",key); // 1
    key = Right;
    printf("%d\n",key); // 2
    key = Left;  
    printf("%d\n",key); // 3
}

2、可以使用=设置枚举值,没有进行设置的枚举值是上一个枚举值递增+1。

#include <stdio.h>
​
enum DirectionKey
{
    Up = 123,
    Down,
    Right = 456,                  
    Left
};
​
int main(int argc,const char* argv[])
{
    enum DirectionKey key;
    key = Up;
    printf("%d\n",key); // 123
    key = Down;
    printf("%d\n",key); // 124
    key = Right;
    printf("%d\n",key); // 456
    key = Left;
    printf("%d\n",key); // 457
}

3、枚举值可以单独使用,这种用法可以给没有意义的字面值数据取一个有意义的名字,这样可以提高代码的可读取性,也可以定义匿名的枚举,只使用枚举值。

#include <stdio.h>
​
enum
{
    Up = 123,
    Down,
    Right = 456,
    Left
};
​
int main(int argc,const char* argv[])
{
    printf("%d %d %d %d\n",Up,Down,Right,Left);       
}

4、枚举值是常量,所以可以与switch语句配合使用,枚举值可以写在case的后面。

#include <stdio.h>
#include <getch.h>
​
enum
{
    Up = 183,
    Down,
    Right,
    Left
};
​
int main(int argc,const char* argv[])
{
    for(;;)
    {
        switch(getch())
        {
            case Up: puts("上"); break;
            case Down: puts("下"); break;
            case Right: puts("右"); break;
            case Left: puts("左"); break;    
        }
    }
}

5、枚举值的作用域是全局的(尽量名字取的复杂一些),所以它不能与全局变量、函数、结构、联合重名。

#include <stdio.h>       
#include <getch.h>
​
int num;
enum 
{
    Up = 183,
    Down,
    Right,
    Left,
    num,
    main
};
​
int main(int argc,const char* argv[])
{
    
}
注意:

枚举是一种锦上添花的技术,使用它能让代码的可读性、安全性更高,但直接使用字面值数据也不影响代码的编写和运行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值