C语言之自定义类型

结构体

C语言允许用户自己建立由不同类型数据或相同类型的数据组成的组合型的数据结构,称为结构体。

- 结构体创建

Struct 结构体类型名
{
成员1的数据类型名成员1名称;
成员2的数据类型名成员2名称;
.
.
成员n的数据类型名成员n名称;
}结构体变量表={成员初始化};
例如:
Struct student
{
Long num;
Char name;
Char sex;
Float score;
};

- 结构体的声明
声明一个结构体类型的一般形式为:
struct结构体类型名{成员表列};
结构体类型名用来作结构体类型的标志。声明结构体类型的位置一般在文件的开头,在所有函数(包括main函数)之前,以便本文件中所有的函数都能利用它来定义变量。当然也可以在函数中声明结构体类型。

- 结构体类型变量的定义

可以采取以下3种方法定义结构体类型的变量。

  • 先声明结构体类型再定义变量名
    例:
    struct Student//声明一个结构体类型Student
    .{
    .int num;//包括一个整型变量num
    .char name[20];//包括一个字符数组name,可以容纳20个字符
    .char sex;//包括一个字符变量sex
    .int age;//包括一个整型变量age
    .float score;//包括一个单精度型变量
    .char addr[30];//包括一个字符数组addr,可以容纳30个字符
    .};//最后有一个分号
    struct Student studentl,student2;
  • 在声明类型的同时定义变量
    例:
    struct Student//声明结构体类型Student
    .{
    .int num;
    .char name[20];
    .char sex;
    .int age;
    .float score;
    .char addr[30];
    .}student1,student2;//定义两个结构体类型Student的变量student1,student2
  • 直接定义结构体类型变量
    struct//注意没有结构体类型名
    {
    成员表列
    }变量名表列;
    提倡先定义类型后定义变量的第(1)种方法

    - 结构体变量的初始化
    和其他类型变量一样,对结构体变量可以在定义时指定初始值。如:
    .struct Student
    .{
    .int num;
    .char name[20];
    .char sex;
    .int age;
    .float score;
    .char addr[30];
    .}student1={10001,”Zhang Xin”,’M’,19,90.5,”Shanghai”};
    也可以采取声明类型与定义变量分开的形式,在定义变量时进行初始化:
    Student student2={10002,”Wang Li”,”F”,20,98,”Beijing”};//Student是已声明的结构体类型

    - 结构体成员的访问

  • 结构体成员是通过结构体变量名和成员名进行访问的,不能把他作为一个整体进行访问。其访问格式如下:
    结构体变量名.成员名
    其中运算符.是成员访问运算符,其运算级别是最高的,和圆括号运算符()、下标运算符[]是同一级别的。如果结构体成员是指针变量,则访问格式为:
    *stu1.name
    如果某个结构体变量的成员数据类型又是一个结构体,则访问方式为:
    外层结构体变量名.外层成员名.内层成员名

  • 当一个指针变量用来指向一个结构变量时,称为结构体指针变量。结构体指针变量中的值是所指向的结构体变量的首地址。
    结构体指针变量:
    Struct 结构名*结构体指针变量名
    Struct student *pstu;
    必须先赋值后使用,把结构体变量的首地址赋给指针变量,不能把结构名或结构体变量名赋给指针变量。
    利用结构体指针变量访问成员如下:
    ( * 结构体指针变量).成员名
    或者:
    结构体指针变量->成员名

    - 结构体数组
    结构体数组指的是数组中的每个元素都为同一结构体类型的数据。结构体数组与一般数组的定义相似,其格式为:
    struct 结构体名 数组名[元素个数];
    如: struct student s[3]; 定义了以一个struct student 类型的数组s,数组中有3个元素。
    与一般数组一样,结构体数组可以初始化,对每个元素的初始化,按照结构体变量初始化方式进行。如:
    struct student s[3]=
    {21, “zhangsan”, ’ T’, 1978, 10, 23,
    35, “lisi”, ’ F’, 1980, 2,3,
    36, “wangwu”, ’ T’,1978, 4, 5};
    对于结构数组中各元素的引用与一般数组相同,通过下标的方式来引用每个元素,而每个元素的成员通过分量运算符“.”实现。如:s[1].name 、s[2].birthday.year等。

    - 结构体内存对齐

为什么系统要对于结构体数据进行内存对齐呢?

1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
内存对齐原则:

原则1:数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offse,即偏移量t为0的地方

原则2:以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。

原则3:结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

原则4:结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部最大元素大小的整数倍地址开始存储。
例1

struct s1{
    short a1;
    short a2;
    short a3;
};
int main()
{
    printf("%d\n", sizeof(struct s1));
    system("pause");
    return 0;
}


struct s2{
    long a1;
    short a2;
};
int main()
{
    printf("%d\n", sizeof(struct s2));
    system("pause");
    return 0;
}

sizeof(struct s1) = 6; 这个很好理解,三个short都为2。
sizeof(struct s2) = 8; long为4,short为2,整个为8,因为规则2。
例2

struct A{
    int a;
    char b;
    short c;
};
struct B{
    char b;
    int a;
    short c;
};
int main()
{
    printf("%d\n", sizeof(struct A));
    printf("%d\n", sizeof(struct B));
    system("pause");
    return 0;
}

sizeof(struct A) = 8;sizeof(struct B) = 12。
深究一下,为什么是这样,我们可以看看内存里的布局情况。
a b c
A的内存布局:1111, 1*, 11
b a c
B的内存布局:1***, 1111, 11**
其中星号*表示填充的字节。A中,b后面为何要补充一个字节?因为c为short,其起始位置要为2的倍数,就是原则2。c的后面没有补充,因为b和c正好占用4个字节,整个A占用空间为4的倍数,也就是最大成员int类型的倍数,所以不用补充。B中,b是char为1,b后面补充了3个字节,因为a是int为4,根据原则2,起始位置要为4的倍数,所以b后面要补充3个字节。c后面补充两个字节,根据原则3,整个B占用空间要为4的倍数,c后面不补充,整个B的空间为10,不符,所以要补充2个字节。

例3:

struct A{
    int a;
    double b;
    float c;
};
struct B{
    char e[2];
    int f;
    double g;
    short h;
    struct A i;
};
int main()
{
    printf("%d\n", sizeof(struct A));
    printf("%d\n", sizeof(struct B));
    system("pause");
    return 0;
}

sizeof(struct A) = 24; 这个比较好理解,int为4,double为8,float为4,总长为8的倍数,补齐,所以整个A为24。
sizeof(struct B) = 48; 看看B的内存布局。
e f g h i
B的内存布局:11* , 1111, 11111111, 11 * * * * , 1111 * * , 11111111, 1111 * * *
i其实就是A的内存布局。i的起始位置要为24的倍数,所以h后面要补齐。


位段

C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”( bit field) 。利用位段能够用较少的位数存储数据。
位段的定义格式为:
例如:
struct A
{
int a:2;
int b:5;
int c:10;
int d:30;
};
其中后面数字表示变量所占比特位。
位段的内存分配:

1.位段的成员只能为int,unsigned int,signed int,char类型。
2.位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的。
3.位段涉及很多不确定因素,位段是不跨平台的,注意可移植的程序应该避免使用位段。


枚举

枚举:就是一一列举。
定义枚举类型:
enum 枚举类型名
{枚举常量表};
常量表中的每个常量都是一个标识符。

  1. 枚举常量不是变量,不能在程序中用赋值语句对它赋值。但可以在定义枚举类型时对它赋初值。
  2. 定义枚举类型时系统自动给每个枚举常量分配一个表示序号的整数值,默认第一个值为0,以后每个标识符的值依次比前面的增加1,。可以通过赋初值来改变默认值,从赋值以后的标识符开始忍然依次加1。
  3. 枚举常量是一个标识符,不是字符常量也不是字符串常量,使用时不要加单引号或双引号。

联合

联合体几个成员共享同一个位置开始的存储区,这些成员并不同时存在,而是在不同时刻拥有不同的成员,在同一时刻仅拥有其中一个成员。因此,联合体变量的存储空间只要保证能存放其中任何一个成员就可以了。
联合体成员的数据类型可以是任何数据类型,联合体在定义、变量说明和使用形式上均与结构体相似,两者本质上的不同仅在于使用内存的方式上。
Union 联合体名
{
成员1数据类型成员1名;
成员2数据类型成员2名;
.
.
.
成员n数据类型成员n名;
}联合体变量表={成员初始化};

共用体变量的定义方式与结构体变量的定义方式相似,也有3种方式:

  1. 类型定义与变量定义分开。如:
    union data d1, d2;
  2. 在定义类型的同时定义变量。如:
    union data
    {
    int i;
    char ch;
    float f;
    }x, y;

  3. 直接定义共用体类型的变量,不给出共用体名。如
    union
    {
    int i;
    char ch;
    float f;
    }x, y;

联合体与结构体的区别
(1)从存储结构上来看,结构变量所占的内存长度是每个成员所占的内存长度
之和。每个成员分别占有自己独立的内存单元。而联合变量所占的内存长度等于最长的成员的长度。
(2)在初始化时,结构体初始化与一维数组类似,而联合只能对第一个成员初
始化。
(3)从成员的地址看,结构体各成员的地址互不相同,只有第一个成员的地址
与结构变量地址相同(类型不同),联合体所有成员的地址相同,都等于联合变量的地址。
(4)从使用上看,结构体中所有成员可以分别赋值、同时存在、互不影响。而
联合体中通过给某成员赋值,该成员覆盖其他成员,使联合体中始终只能正确保存一个成员的值。


结构体小试:
实现一个通讯录;
通讯录可以用来存储1000个人的信息,每个人的信息包括:
姓名、性别、年龄、电话、住址
提供方法:
1. 添加联系人信息
2. 删除指定联系人信息
3. 查找指定联系人信息
4. 修改指定联系人信息
5. 显示所有联系人信息
6. 清空所有联系人
7. 以名字排序所有联系人

要存放1000个人的信息,就要定义一个结构体,存放姓名,性别,年龄,电话,住址等信息。另外,还要定义一个结构体来存放通讯录的大小以及通讯录的当前存放的大小。通过结构体指针就可以访问各个功能函数。
可以定义三个函数来分别实现不同的功能。”Contact.h”部分来实现结构体的定义以及增删查改等功能函数的定义。”Contact.c”部分分别实现每个函数的功能。”test.c”部分即为测试部分,包括主函数。

Contact.h

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

enum po  
{  
    EXIT,  
    ADD,  
    DELE,  
    SER,  
    MODIFY,  
    DISPLAY,  
    CLEAR,  
    SORT  
};  


#define NAME_MAX 100      //姓名的最大长度  
#define SEX_MAX 5         //性别的最大长度  
#define TELE_MAX 12       //电话的最大长度  
#define ADDR_MAX 25       //地址的最大长度  
#define MAX 1000          //最多存放的变量内容  

typedef struct Perfo  
{  
    char name[NAME_MAX];  
    char sex[SEX_MAX];  
    int age;  
    char tele[TELE_MAX];  
    char addr[ADDR_MAX];  
}Perfo;  


typedef struct Contact       //定义此结构体来保存变量的大小  
{  
    Perfo data[MAX];  
    int size;  
}Con,*Pcon;  

void init(Pcon pcon);        //结构体初始化  
void _add(Pcon pcon);        //插入  
void _ser(Pcon pcon);         //查找  
void _dele(Pcon pcon);       //删除  
void _modify(Pcon pcon);     //修改  
void _display(Pcon pcon);    //显示  
void _clear(Pcon pcon);      //清除  
void _sort(Pcon pcon);       //排序  

Contact.c

#include"Contact.h"  

void init(Pcon pcon)  
{  
    memset(pcon->data,0,MAX*sizeof(Perfo));  
    pcon->size = 0;  
}  


void _add(Pcon pcon)             //插入  
{  
    if(pcon->size >= NAME_MAX)  
    {  
        printf("电话本已满,请删除部分后再插入\n");  
        return ;  
    }  
    else  
    {  
        printf("请输入信息:姓名 性别 年龄 电话 住址\n");  
        scanf("%s",pcon->data[pcon->size].name);  
        scanf("%s",pcon->data[pcon->size].sex);  
        scanf("%d",&pcon->data[pcon->size].age);  
        scanf("%s",pcon->data[pcon->size].tele);  
        scanf("%s", pcon->data[pcon->size].addr);  
        pcon->size++;  
    }  
    printf("插入成功!\n");  
}  


int find(Pcon pcon,char *name)    
{  
    int i = 0;  
    for(i = 0;i<pcon->size;i++)  
    {  
        if(strcmp(pcon->data[i].name,name) == 0)  
            return i;  
    }  
    return -1;  
}  


void _ser(Pcon pcon)                     //查找联系人  
{  
    int ret = 0;  
    char name[NAME_MAX] = {0};  
    printf("请输入要查找的人的名字:");  
    scanf("%s",name);  
    ret = find(pcon,name);  
    if(ret == -1)  
    {  
        printf("不存在此人\n");  
        return ;  
    }  
    else  
    {  
        printf("%9s\t%5s\t%7d\t%12s\t%15s\n",  
                pcon->data[ret].name,  
                pcon->data[ret].sex,  
                pcon->data[ret].age,  
                pcon->data[ret].tele,  
                pcon->data[ret].addr);  
    }  
}  


void _display(Pcon pcon)        //显示  
{  
    int n = 0;  
    printf("姓名           性别           年龄           电话           住址\n");  
    if(pcon->size <= 0)  
    {  
        printf("电话本空!\n");  
    }  
    else  
    {  
        for(n = 0;n<pcon->size;n++)  
        {  
            printf("%9s\t%5s\t%7d\t%12s\t%15s\n",  
                    pcon->data[n].name,  
                    pcon->data[n].sex,  
                    pcon->data[n].age,  
                    pcon->data[n].tele,  
                    pcon->data[n].addr);  
        }  
    }  
}  


void _dele(Pcon pcon)       //删除  
{  
    char name[NAME_MAX];  
    int ret = 0;  
    int i = 0;  
    printf("请输入要删除的人的姓名:");  
    scanf("%s",name);  
    ret = find(pcon,name);  
    if(ret == -1)  
        printf("不存在此人!\n");  
    else  
    {  
        for(i = ret;i<pcon->size;i++)  
        {  
            pcon->data[i] = pcon->data[i+1];  
        }  
        pcon->size--;  
        printf("删除成功!\n");  
    }  
}  


void _modify(Pcon pcon)        //修改  
{  
    int ret = 0;  
    char name[NAME_MAX];  
    printf("请输入要修改的人的姓名:");  
    scanf("%s",name);  
    ret = find(pcon,name);  
    if(ret == -1)  
        printf("不存在此人\n");  
    else  
    {  
        printf("请输入名字:>");  
        scanf("%s",pcon->data[ret].name);  
        printf("请输入年龄:>");  
        scanf("%d",&(pcon->data[ret].age));  
        printf("请输入性别:>");  
        scanf("%s",pcon->data[ret].sex);  
        printf("请输入电话:>");  
        scanf("%s",pcon->data[ret].tele);  
        printf("请输入地址:>");  
        scanf("%s",pcon->data[ret].addr);  
    }  
}  


void _clear(Pcon pcon)       //清空  
{  
    pcon->size = 0;  
    printf("电话本已空!\n");  
}  


void _sort(Pcon pcon)      // 排序  
{  
    int i = 0;  
    int j = 0;  
    for(i = 0;i<pcon->size-1;i++)  
    {  
        for(j = 0;j<pcon->size-1-i;j++)  
        {  
            Perfo temp = {0};  
            if(strcmp(pcon->data[j].name,pcon->data[j+1].name)>0)  
            {  
                temp = pcon->data[j];  
                pcon->data[j] = pcon->data[j+1];  
                pcon->data[j+1] = temp;  
            }  
        }  
    }  
}  

test.c

#include "Contact.h"  

void menu()  
{  
    printf("--------- 0.exit ---------\n");  
    printf("--------- 1.add ----------\n");  
    printf("--------- 2.dele ---------\n");  
    printf("--------- 3.ser ----------\n");  
    printf("--------- 4.modify -------\n");  
    printf("--------- 5.display ------\n");  
    printf("--------- 6.clear --------\n");  
    printf("--------- 7.sort ---------\n");  
}  


int main()  
{  
    int input = 1;  
    Con con;  
    init(&con);   
    while(input)  
    {  
        menu();  
        printf("请选择要操作的数字:");  
        scanf("%d",&input);  
        printf("\n");  
        {  
            switch(input)  
            {  
            case EXIT:  
                break;  
            case ADD:  
                _add(&con);  
                break;  
            case DELE:  
                _dele(&con);  
                break;  
            case SER:  
                _ser(&con);  
                break;  
            case MODIFY:  
                _modify(&con);  
                break;  
            case DISPLAY:  
                _display(&con);  
                break;  
            case CLEAR:  
                _clear(&con);  
                break;  
            case SORT:  
                _sort(&con);  
                break;  
            default:  
                printf("无效选择!");  
                break;  
            }  
        }  
    }  
    system("pause");  
    return 0;  
}  
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值