1、typedef和#define的区别
在用作数据类型替换时的区别:
#include <stdio.h>
#include <unistd.h>
typedef char * A; //typedef需要;
#define B char *
int main(int argc, char *argv[])
{
A a,b;
B c,d;
printf("a_size=%ld\n",sizeof(a));
printf("b_size=%ld\n",sizeof(b));
printf("c_size=%ld\n",sizeof(c));
printf("d_size=%ld\n",sizeof(d));
return 0;
}
运行结果:
在代码中我使用了两种方法去替换char *,在主函数中分别用这两种方式替换后的名字去定义char*类型的指针,我是64位操作系统,因此指针的大小为8,但是由结果可以看出d并不是一个指针;这里的区别就是typedef会把它当成一个整体的数据类型,但是#define只会替换,所以只有第一个字母c才定义为了指针。
区别总结:
- 处理阶段:
typedef
是编译时指令。#define
是预处理指令。
- 类型检查:
typedef
定义的别名可以进行类型检查。#define
只是简单的文本替换,不进行类型检查。
- 用途:
typedef
主要用于定义类型别名。#define
可以用于定义常量、函数宏或进行简单的文本替换。
- 作用域:
typedef
的作用域与变量和函数的作用域类似,可以定义在文件级或块级。#define
的作用域通常是文件级的,除非使用#undef
显式取消定义。
2、结构体
任意数据类型(已知数据类型和构造类型)的成员集合,在空间地址上连续存储
结构体的本质:是一个数据类型
结构体的一般形式:
struct 结构体名{
数据类型 成员1;
数据类型 成员2;
数据类型 成员3:;
......
};-------分号不能省略
结构体的大小:
1、计算机会给每个成员都开辟对应空间地址,64位默认以8byte对齐,但是如果最大成员的数据类型小于8byte,会以最大成员数据类型大小方式对齐,32位默认以4byte对齐,但是如果最大成员的数据类型小于4byte,会以最大成员数据类型大小方式对齐,最终结构体的大小一定为最大成员数据类型大小的倍数。
结构体指针:
指针的间接调用:
1、(*指针变量名).成员名 -----指针的用法
2、指针变量名->成员名 -----常用
#include <stdio.h>
#include <unistd.h>
typedef struct demo{
char name[32];
int age;
char sex[32];
}D,*p_D;
int main(int argc, char *argv[])
{
D a = {"xiaoming",120,"man"};
D b = {"xiaohong",18,"woman"};
p_D q = &a;
struct demo *x = &b;
printf("a.name=%s\n",a.name);
printf("(*q).name=%s\n",a.name);
printf("q->name=%s\n",a.name);
printf("b.sex=%s\n",b.sex);
printf("x->name=%s\n",x->sex);
return 0;
}
解析:在上面代码中结构体指针有两种定义方法,一种是在定义结构体类型的时候,我的结构体重命名为D,结构体指针为*p_D;另一种就是直接用结构体类型来定义一个指针,struct demo *x=&b;
3、定义一个时间结构体,输入一个日期,输出该日期为该年的第几天
#include <stdio.h>
typedef struct time_day
{
int year;
int month;
int day;
}T;
int main(int argc, char *argv[])
{
T num;
int i, sum=0;
printf("请输入一个日期(2000.1.1):");
scanf("%d.%d.%d",&num.year,&num.month,&num.day);
int y[12]={31,28,31,30,31,30,31,31,30,31,30,31};
for(i=0;i<num.month-1;i++)
{
sum += y[i];
}
sum +=num.day;
if(num.month>2 && num.year%4==0 && num.year%100!=0 || num.year%400==0)
{
sum +=1;
}
printf("这一天是%d年的第%d天\n",num.year,sum);
return 0;
}
解析:首先判断一个日期是该年的第几天我们需要做的事有:这个日期是第几个月,前面有几个月,要加多少天,还有就是该年是不是闰年,如果是闰年2月要多一天;除了闰年2月以外每个月的天数是固定的,因此我使用一个数组来存储每个月的天数;既然是用结构体那肯定要定义一个结构体,结构体里面放的是年月日,从键盘获取时间,得到之后判断月份为几月,因为数组元素是从0开始的,所以在循环求前几个月的天数和的时候循环条件就是输入的月份减去一,故循环条件为 i=0;i<num.month-1;i++;之后我们只需要判断年份是否为闰年(能被4整除但不能被100整除或能被400整除),是闰年且月份大于2月,就在总天数加上一天即可。运行结果如下:
4、定义一个结构体,包括学生的学号,姓名,性别和成绩四个部分,然后定义如下五个学生,根据他们的成绩从高到低进行排序
#ifndef _TEXT4_H
#define _TEXT4_H
#include <stdio.h>
typedef struct student
{
int id;
char name[32];
char sex[32];
int score;
}STU;
void my_score(STU *s);
#endif
#include "text4.h"
void my_score(STU *s)
{
int i,j;
STU temp;
for(i=0;i<4;i++)
{
for(j=0;j<4-i;j++)
{
if(s[j].score<s[j+1].score)
{
temp = s[j];
s[j] = s[j+1];
s[j+1] = temp;
}
}
}
puts("学号 姓名 性别 成绩");
for(i=0;i<5;i++)
{
printf("%d %s %s %d\n",s[i].id,s[i].name,s[i].sex,s[i].score);
}
}
#include "text4.h"
int main(int argc, char *argv[])
{
STU a={2014123,"张三","男",67};
STU b={2014456,"李四","女",92};
STU c={2014789,"王五","男",74};
STU d={2014999,"赵六","男",83};
STU e={2014888,"小七","女",99};
STU arr[5]={a,b,c,d,e};
my_score(arr);
return 0;
}
解析:这里也是对结构体的应用,首先是定义一个学生信息的结构体,然后是通过学生的成绩对他们进行排序,这里排序不是只排分数,而是对结构体进行排序,所以就需要用到结构体指针,除此之外需要操作每一个结构体就需要用结构体数组将结构体放到数组中,将这个数组当做参数传入到排序函数中进行排序;排完序直接在功能函数中输出即可。代码运行结果如下:
5、共用体(联合体)(union)
用法和结构体类似,但是没有初始化
共用体只会给最大成员开辟空间地址,所有成员都使用这一片空间地址,所以后面的成员赋值会覆盖前面成员赋值(前面成员一定会等于最后一次成员的赋值)
也要遵守结构体的大小字节对齐规则
优点:
-
内存效率:
共用体的最大优点在于其内存效率。由于共用体的所有成员共享同一块内存区域,因此它只占用其最大成员所需的内存空间。这对于内存资源有限的系统(如嵌入式系统)来说是一个巨大的优势,因为它允许程序员在不影响功能的前提下最大限度地减少内存占用。 -
类型灵活性:
共用体提供了在相同内存位置存储不同数据类型的能力。这意味着程序员可以根据需要在运行时动态地选择存储哪种类型的数据。这种灵活性在某些应用场景中非常有用,比如处理多态数据或在不同数据类型之间进行快速转换。 -
简化代码:
在某些情况下,使用共用体可以简化代码结构。例如,当处理具有多种可能表示的数据时(如错误码、状态码或特定于协议的字段),可以使用共用体来避免编写大量的条件语句(如if-else或switch-case)。通过直接访问共用体的成员,程序员可以更直接地处理数据,从而提高代码的可读性和可维护性。 -
硬件接口:
在嵌入式系统和低级编程中,共用体常用于与硬件接口进行交互。硬件寄存器通常映射到特定的内存地址,并且这些寄存器可能具有不同的数据类型(如整数、位字段或浮点数)。通过定义一个包含这些不同数据类型的共用体,程序员可以方便地访问和操作这些寄存器,而无需担心类型转换或内存对齐的问题。 -
数据封装:
虽然共用体本身不是一种封装机制,但它们可以用于在数据结构中封装不同类型的数据。这有助于将相关但不同类型的数据组合在一起,从而简化数据处理和传递的过程。
6、枚举型(enum)
1、里面的元素都是常量,都是连续自加一次
2、里面不赋初值就从0开始自加,赋了初值就从赋得初值开始自加,如果在中间的元素赋值,前面的从0开始自加,后面的从初值开始自加
3、不能在枚举型外面赋值
4、不能赋值浮点型的数据
5、大小为4byte,里面的元素都用同一片空间地址,但不会覆盖,只会自加1
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
enum y{a,b,c=10,d,e,f};
printf("a=%d\n",a);
printf("c=%d\n",c);
printf("f=%d\n",f);
return 0;
}
解析:从代码中可以看出,不赋初值就从0开始自加,a=0,b=1;从c赋了初值,c之后变量的值就为d=11,e=12,f=13。