当前位置: 代码迷 >> 综合 >> C语言.结构体、联合体(共同体)、枚举类型、typedef、define 、头文件、预处理、 条件编译
  详细解决方案

C语言.结构体、联合体(共同体)、枚举类型、typedef、define 、头文件、预处理、 条件编译

热度:25   发布时间:2023-12-29 11:48:43.0

一. 结构体

  1. 结构体的概念 -> 新类型
    定义一个整型变量: int A;
    定义多个整型变量: int A[100]; --> 数组解决同时定义多个相同类型的变量

    同时定义多个不同类型变量时? 如何解决? --> 结构体 --> 内部存储不同类型的基本数据类型

结构体: 同时有char short int long float double int* char* int()[3] int ()(int,int),数组,结构体,联合体 --> 不能有函数

—> 结构体: 就是由多个不同变量组成的复杂的新的数据类型。

  1. 结构体这种新类型是如何定义的呢?

模型:
struct 结构体名字{
/* 结构体的组成变量 */
}; --> 后面有一个分号,不然就会报错!
编译出错: error: expected ‘;’, identifier or ‘(’ before ‘int’

例子:
struct mydata{ struct mydata --> 新的数据类型 好像我们的int一样。
char name[20];
int age;
};

  1. 结构体变量如何定义?

    struct mydata --> 新的数据类型

    定义变量公式: 数据类型 变量名;
    定义整型变量 int a;

    定义结构体变量: struct mydata b;

  2. 结构体指针如何定义?

    定义结构体变量: struct mydata b;

    1)先写一个 *
    2)在*后面写一个指针变量名p p
    3)确定指向数据是什么 struct mydata b;
    4)把指向的数据的变量名去掉 struct mydata
    5)把剩余的部分加在
    p前面 struct mydata *p;

    结果: 结构体指针: struct mydata *p;
    数据类型: struct mydata *
    变量名: p

  3. 结构体变量与指针是如何访问成员?

1)设计结构体的成员

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

2)结构体的变量是使用 “.” 访问结构体的成员

struct mydata gec;
strcpy(gec.name,“helloworld”);
gec.age = 100;

printf(“gec.name = %s\n”,gec.name); //helloworld
printf(“gec.age = %d\n”,gec.age);//100

3)结构体的指针是使用 “->” 访问结构体的成员

strcpy(p->name,“apple”);
p->age = 20;

printf(“p->name = %s\n”,p->name);//hello
printf(“p->age = %d\n”,p->age);//20

  1. 结构体变量赋值方式

    1. 先定义一个变量,再使用"."访问成员进行赋值

struct mydata gec;
strcpy(gec.name,“helloworld”);
gec.age = 100;

2.  在定义的同时进行初始化	

struct mydata aaa = {“helloworld”,10}; --> 等价于 “helloworld” 拷贝对应成员name的空间 10 -> age的空间中
–> 注意对应的位置与类型必须与结构体定义时变量类型一致
struct mydata bbb = {“hello”}; --> 后面没有赋值的变量都为0 指针都为NULL

3.  使用另外一个结构体变量给某个结构体变量整体赋值

struct mydata gec;
struct mydata aaa = {“helloworld”,10};
gec = aaa;

  1. 计算结构体在内存空间中大小

计算结构体空间的大小原则
在默认对齐方式下,结构体成员的内存分配满足下面三个条件

结构体第一个成员的地址和结构体的首地址相同
结构体每个成员地址相对于结构体首地址的偏移量(offset)是该成员大小的整数倍,如果不是则编译器会在成员之间添加填充字节(internal adding)。
结构体总的大小要是其成员中最大size的整数倍,如果不是编译器会在其末尾添加填充字节(trailing padding)。

struct mydata{
char a; //偏移0 自己
char b; //偏移1 为char的整数倍(x1) 无需填充
short c; //偏移2 为short(x1) 无需填充
int d; //偏移4 为int的整数倍 无需填充
//此处偏移8
//结束了 看看条件2 结构体总的大小要是其成员中最大size的整数倍 最大为int ,刚好8是int的整数倍,结尾无需填充!
};
sizeof(struct mydata) = 8 //

把顺序换一下看一看

struct mydata{
char a; //偏移0 自己
short b; //偏移1 自己是short 2 非整数倍 需要先填充1个位置 使自己的偏移变2 再填short
//以上已占内存4
char c; //偏移4 为chaar的整数倍 无需填充
int d; //偏移5 不是int的整数倍 需要先填3个位置 使其变为8(int 的整数倍) 再放自己进去
///此处偏移12
//结束了 看看条件2 结构体总的大小要是其成员中最大size的整数倍 最大为int ,刚好8是int的整数倍,结尾无需填充!
};
sizeof(struct mydata) = 12 //

再看看另外一道需要结尾填充的题目
struct mydata(){
char a;
double b; //偏移1 但是需要8的整数倍 ! 再char后面填7 再放double !
// //结束了 看看条件2 结构体总的大小要是其成员中最大size的整数倍 最大为double ,刚好8是double的整数倍,结尾无需填充!
}
sizeof(struct mydata) = 16;

struct mydata(){
double a;
char b; //偏移8 为char 的整数倍 无需填充!
// //结束了 看看条件2 结构体总的大小要是其成员中最大size的整数倍 最大为double 但是此时结构体只有(8+1)=9 ,所以要在结尾
填充,使结构体为8的整数倍,需要填7个!!!,
}
sizeof(struct mydata) = 16;

思考
struct mydata(){
int a;
double b;
int c;
} sizeof = 24~

练习: 做一个通讯录,里面存放三个同学 姓名,年龄,电话号码 --> 结构体数组

	1. 先分别给三个同学注册	2. 输出三个同学的信息

#include <stdio.h>

struct mydata{
char name[20];
int age;
char tel[20];
};

int main(int argc,char *argv[])
{
int i;//0~2
struct mydata A[3];
for(i=0;i<3;i++)
{
printf(“pls input %d’s name”,i+1);
scanf("%s",A[i].name);
printf(“pls input %d’s age”,i+1);
scanf("%d",&(A[i].age));
printf(“pls input %d’s tel”,i+1);
scanf("%s",A[i].tel);
}

printf("==========================================\n");
for(i=0;i<3;i++)
{printf("%s   %d    %s\n",A[i].name,A[i].age,A[i].tel);
}return 0;

}

二. 联合体

  1. 概念: 解决结构体中占用空间比较大情况

例子:
struct mydata{
int a;
char b;
short c;
long d;
float e;
double f;
}; —> 占用了24个字节,空间非常大 --> 如果定义为联合体,则可以大大节省空间

union mydata{
int a;//4
char b;//1
short c;//2
long d;/4
float e;//4
double f;//8
};

联合体计算大小的方法: 看看成员中占用空间最大的变量类型占用的空间字节数是多少
1)如果结果是4的倍数,那么大小就是最大的变量类型占用的空间字节数。
2)如果结果不是4的倍数,剩余位置补0,补完0之后加上0的位置占用的字节就是总字节。

  1. 联合体使用方法

例子:
union mydata{
int age;
char name;
};

结论1: 在同一个时刻只能使用一个成员

union mydata A = {10,‘x’}; --> 同时使用两个成员,只使用了age变量,没有使用name变量 'x’越界访问别的内存空间!
编译警告:warning: excess elements in union initializer
warning: (near initialization for ‘A’)

结论2:所有成员的起始地址都是一致的

union mydata A;
A.age = 65;
printf(“A.age = %d\n”,A.age);//65
printf(“A.age = %c\n”,A.age);//A
printf(“A.name = %c\n”,A.name);//A

union mydata A;
A.name = ‘A’;
printf(“A.age = %d\n”,A.age);//随机值
printf(“A.age = %c\n”,A.age);//A
printf(“A.name = %c\n”,A.name);//A

结论3: 结构体变量与联合体变量都是可以作为函数的参数!

三. 枚举类型

概念: 枚举类型其实就是int类型常量,意义就是给常量一个身份。
枚举类型使用场景:
1)switch(枚举类型)
2)函数返回值return 枚举类型

如何定义枚举类型?
enum mydata{
a, //没有赋值的话,默认从0开始赋值给第一个
b, //后面还没有赋值,值就等于前面的变量的值+1
c,
};
–> 其实这里就相当于定义了三个int类型的变量

实例:
enum fun_return_state{
ok = 0,
failed = -1,
error = -2,
};

return ok;
return failed; --> 使得返回值更加有意义!

四. typedef --> type define

  1. 概念意义: 给某种数据类型取新的名字 int --> 为int数据类型取别名aaa

  2. 使用场景: 一般typedef很少给基本数据类型取别名,一般都是为复杂的数据类型取别名,例如结构体

  3. 例子

没有取别名的时候: 定义变量非常麻烦
struct data{
int a;
char b;
};

struct data gec;
gec.a = 10;
gec.b = ‘h’;

//取了别名,看起来简便

struct data{
int a;
char b;
};

int main()
{
//1. 确定该结构体的类型是struct data
//2. 使用typedef来取别名 typedef 第1步的类型 新别名
typedef struct data mydata; //在后面的代码中: mydata 等价于 struct data

mydata gec;  //等价于   struct data gec;
gec.a = 10;
gec.b = 'h';

}

  1. 在给结构体取新的别名,一般写在定义同时直接取别名

struct data{
int a;
char b;
}gec; --> gec是一个全局结构体变量

gec a;  //错误

typedef struct data{
int a;
char b;
}gec; --> gec就是代表struct data这个类型

gec a; //正确,定义了一个结构体类型变量

五. 预处理阶段处理的内容有哪些? --> #开头的都是属于预处理阶段的内容

  1. 预处理阶段内容应该写在头文件中
    gcc test.c -o test.i -E

注意几点:

  1. 如果头文件中有函数声明,那么包含头文件的位置一定在函数调用之前

  2. 预处理的语句都是以"#"开头

  3. 不可以在同一行出现多个预处理命令,否则只执行第一个命令
    如果在同一行出现多个预处理指令,编译警告:
    warning: extra tokens at end of #include directive

  4. 包含头文件问题 < > " "

例子:
=head.h==
#ifndef HEAD_H //如果没有定义过_HEAD_H_这个宏
#define HEAD_H //防止该头文件的内容被重复声明

/* 系统头文件 */
#include <stdio.h>

#endif

=test.c==
#include “head.h”

int main()
{
printf(“helloworld!\n”);

return 0;

}

编译通过,“xxxx.h” 系统会去系统头文件路径(大部分都在/usr/include)和当前目录下寻找xxxx.h,如果都找不到,就会提示xxxx.h找不到

""包含头文件时,范围会广泛一点   --> 预处理效率低

=test.c==
#include <head.h>

int main()
{
printf(“helloworld!\n”);

return 0;

}

编译出错:  fatal error: head.h: No such file or directory<xxxx.h>  系统只会去系统头文件路径寻找xxxx.h,如果找不到,就会提示xxxx.h找不到
解决方法: 使用 "-I" 选项指定头文件的路径

一般地: #include <系统头文件>
#include “自定义头文件”

  1. 宏定义替换 --> 替换也是发生在预处理

#define OK 0
#define ERROR -1 //无参宏
#define XYZ(a,b) a*b //带参宏

if(a == OK)
printf(“hello!\n”);
a = XYZ(10,20); --> XYZ(10+10,20)结果:210 --> 因为宏定义只会替换,不会管运算符号关系!

	--> 该代码在预处理阶段就会变成

if(a == 10)
printf(“hello!\n”);
a = 10*20

注意:

  1. 宏定义在预处理阶段发生,占用预处理时间,而不会占用运行时间。

  2. 宏定义既可以的是大写字母,也可以是小写字母

  3. 条件编译

#if 1
printf(“appletree!\n”); --> 这部分代码进行编译
#endif

#if 0
printf(“appletree!\n”); --> 这部分代码不进行编译
#endif

六. 多个.c文件进行拆分

没有拆分时候:

#include <stdio.h>

void fun()
{
printf(“nihao!\n”);
}

int main()
{
printf(“helloworld!\n”);
fun();
}

拆分之后:
==============head.h --> 预处理内容的头文件

#ifndef HEAD_H
#define HEAD_H

#include <stdio.h>

#endif

==============main.c --> 包含main函数的.c文件
#include “head.h”

int main()
{
printf(“helloworld!\n”);
fun();
}

=============fun.c --> 不含main函数在内的.c文件
#include “head.h”

void fun()
{
printf(“nihao!\n”);
}

==================================================

编译命令: gcc main.c fun.c -o main
  相关解决方案