一. typedef
typedef 是用来给类型起别名的。
int a; 变量
int *a; 一级指针
int **a; 二级指针
int a[5]; 一维数组
int a[3][4]; 二维数组
int *a[5]; 指针数组
int (*a)[5]; 数组指针
int *a(int); 指针函数
int (*a)(int); 函数指针
上面将定义变量的语句中的变量名a去掉,剩下的就是类型名。
在定义变量的语句前加上 typedef ,原来的变量名就成 了新的类型名
typedef int a;
typedef int *a;
typedef int **a;
typedef int a[5];
typedef int a[3][4];
typedef int *a[5];
ypedef int (*a)[5];
typdef int *a(int);
ypedef int (*a)(int);
typedef 和 #define 的区别
执行阶段和错误检查:
typedef在编译阶段完成处理,有类型检查功能,编译器会检查定义的别名是否符合相应类型
的语法
#define在预处理阶段完成,只会完成简单替换,不会检查类型及语法问题。预处理时会直接替换,编译时才会因类型及语法错误而报错。
语法细节
typedef是一个存储类的关键字,句末必须加分号。
#define句末不需要分号,如果加也会被替换到源程序
对指针的操作
typedef当用于定义指针类型的别名时,更符合类型定义的逻辑,是真正的给类型起别名。
#include <stdio.h> typedef int *pint1; #define pint2 int * int main(int argc, const char *argv[]) { int a = 10; int b = 20; // 下面的用法 p1 和 p2 都是 int * 类型的指针变量 pint1 p1 = &a, p2 = &b; // 下面的用法 q1 是 int * 类型的指针变量 而 q2 只是一个 int 类型的变量 // 因为 预处理后 : int *q1 = &a, q2 = &b; pint2 q1 = &a, q2 = &b; return 0; }
二.枚举--enum
2.1定义枚举的格式
枚举一个基本类型,枚举就是数据的有限罗列,枚举是用来防止“魔鬼数字”。
格式:
enum 枚举类型名{ 成员1, 成员2, 成员n };
1.enum和枚举类型名一起构成类型的名字,定义枚举变量: enum 枚举类型名 枚举变量名
2.枚举类型成员之间用逗号分割
3.枚举成员1如果没有初值,默认值为0.后面成员一次递增1.如果某个成员被赋了初始值,后面成员在这个值基础上依次递增1.
4.枚举类型的大小取决于枚举成员的最大值,一般情况下都是4字节
5.枚举成员定义之后就是一个常量
6.当局部变量和枚举成员名冲突时,局部变量优先
#include <stdio.h>
// 定义了一个枚举类型
enum Color{
red,
green,
blue,
pink = 100,
black,
yellow,
white
};
int main(int argc, const char *argv[])
{
// 使用枚举类型定义变量
enum Color c1
/ 枚举成员可以直接用来给枚举变量赋值
c1 = red;
printf("c1 = %d\n", c1); // 0
enum Color c2 = black; // 枚举变量初始化的写法
printf("c2 = %d\n", c2); // 101
// 枚举变量之间也可以直接相互赋值
c1 = c2;
printf("c1 = %d\n", c1); // 101
}
2.2定义枚举变量的方式
方式1:先定义类型 再定义变量--常用
enum Color{
red
bule
};
enum Coler c1,c2;
方式2:定义类型的同时定义变量
enum Color{
red
bule
}c1,c2;
c1 = red;
c2 = bule;
enum Coler c3;
c3 = red;
方式3:省略枚举类型名的定义变量的方式
enum {
red
bule
}c1,c2;
c1 = red;
c2 = bule;
//这种方式在定义之后,无法再定义其他枚举变量。
2.3枚举和typedef结合
方式1:
typedef enum Color{
red
bule
}color_t;
color_t c1 = red;
color_t c2= blue;
enum Color c2 = red;
方式2:这种用法就只能使用别名来定义变量了
typedef enum {
red
bule
}color_t;
color_t c1 = red;
color_t c2= blue;
2.结构体 -- struct、
结构体是一个构造类型,结构体中既可以存放一组不同数据类型的数据,也可以用来存放一组相同数据类型的数据,一般使用结构体时,都用来存放一组不同数据类型的数据。
2.1定义结构体的类型的格式
struct 结构体类型名{
数据类型1 成员1;
数据类型2 成员2;
....
数据类型n 成员n;
};
注意事项:
1.定义结构体类型和使用结构体类型定义变量的方式和枚举很想,但是又有不同。
2.结构体的成员之间用分号分割。(枚举成员用逗号分隔)
3.结构体的成员是变量。(枚举成员是常量)
4.结构体变量之间可以相互赋值。
5结构体成员在内存上是“连续的”(涉及到内存对齐的问题)
2.2定义结构体变量和结构体变量访问成员
定义结构体变量
struct 结构体类型名 结构体变量名
结构体变量访问成员
变量名.成员名
#include<stdio.h>
#include<string.h>
//定义结构体类型
struct Student{
int id;
char name[32];
int score;
char gender;
//需要的成员依次罗列
};
int main(int argc, const char *argv[])
{
//使用结构体类型定义变量
struct Student s1;
s1.id = 1001;
strcpy(s1.name, "zhangsan");
s1.score = 98;
s1.gender = 'M';
printf("%d %s %d %c\n", s1.id, s1.name, s1.score, s1.gender); // 1001 zhangsan 98 M
//每个结构体变量独占用内存空间 有自己独立的成员 相互不影响
struct Student s2;
s2.id = 1002;
printf("s2.id = %d\n", s2.id); // 1002
printf("s1.id = %d\n", s1.id); // 1001
//结构体变量,可以相互赋值
s2 = s1;
printf("%d %s %d %c\n", s2.id, s2.name, s2.score, s2.gender); // 1001 zhangsan 98
// 结构体变量如果不初始化 里面也是也是随机值
struct Student s3;
printf("%d %s %d %c\n", s3.id, s3.name, s3.score, s3.gender); // 随机值
//结构体变量使用memset清0
memset(&s3, 0 , sizeof(struct Student));
printf("%d %s %d %c\n", s3.id, s3.name, s3.score, s3.gender); // 0 0
return 0;
}
2.3定义结构体指针和结构体指针访问成员
定义结构体指针
struct 结构体类型名 *结构体指针名
结构体指针的作用:决定操作空间的大小、决定以哪种结构访问指向的内存。
结构体指针访问成员
指针名->成员名
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
typedef struct Student{
int id;
char name[32];
int score;
char gender;
}stu_t; //typedef起别名
struct XXX{
int score;
char name[32];
int id;
char gender;
};
int main(int argc, const char *argv[])
{
stu_t s1; //定义结构体变量
struct Student *p1; //定义结构体指针
p1 = &s1; //结构体指针指向结构体变量
p1->id = 1001;
strcpy(p1->name, "zhangsan");
p1->score = 98;
p1->gender = 'M';
//x1和&s1的类型不匹配,通过x1访问s1时,是按x1的结构来访问,会有问题
#if 0
struct XXX *x1 = &s1;
x1->id = 1002;
printf("s1.id = %d\n", s1.id); // 1001
printf("s1.score = %d\n", s1.score); // 1002
#endif
//结构体指针指向堆区分配的空间
stu_t *p2 = (stu_t *)malloc(sizeof(stu_t));
if(NULL == p2){
return -1;
}
p2->id = 1003;
strcpy(p2->name, "lisi");
p2->score = 100;
p2->gender = 'W';
printf("%d %s %d %c\n", p2->id, p2->name, p2->score, p2->gender); // 1003 lisi 100 W
free(p2);
p2 = NULL;
// 结构体指针指向在堆区分配的空间 里面也是随机值
stu_t *p3 = (stu_t *)malloc(sizeof(stu_t));
if(NULL == p3){
return -1;
}
printf("%d %s %d %c\n", p3->id, p3->name, p3->score, p3->gender);
// 通过结构体指针将指向的空间清0 要注意下面的用法
memset(p3, 0, sizeof(stu_t)); // 正确的用法
// memset(&p3, 0, sizeof(struct Student)); // 错误的 把指针自身的空间清0了 还越界访问
//memset(p3, 0, sizeof(p3)); // 错误的用法 sizeof(p3) 固定为一个指针的大小
return 0;
}
2.4结构体变量的初始化和赋值
//方式1:
struct Student{
int id;
char name[16];
char gender;
int score;
};
struct Student s1;
si.id = 1001;
strcpy(s1.name, "zhangsan");
s1.gender = 'M;
si.score = 98;
//方式2:
struct Student{
int id;
char name[16];
char gender;
int score;
};
struct Student s1 = {1001, "zhangsan", 'M', 98}; //初始化
//方式3:
struct Student{
int id;
char name[16];
char gender;
int score;
}s1; //定义类型的同时并定义变量
si.id = 1001;
strcpy(s1.name, "zhangsan");
s1.gender = 'M;
si.score = 98;
//方式4:
struct Student{
int id;
char name[16];
char gender;
int score;
}s1 = {1001, "zhangsan", 'M', 98};
//方式5:
struct Student{
int id;
char name[16];
char gender;
int score;
}
struct Student s1 = {.id = 1001, .score = 98}; //不完全初始化
//方式6:
struct Student{
int id;
char name[32];
char gender;
int score;
};
struct Student s1;
s1 = (struct Student){1001, "zhangsan", 'M', 98};
2.5结构体数组
格式:struct 结构体类型名 结构体数组名[下标];
对结构体数组取出一个元素后 ,和操作单个结构体变量是相同的
#include<stdio.h>
#include<string.h>
struct Student{
int id;
int name[32];
};
int main(int argc, const char *argv[])
{
struct Student s[2];
//对数组中元素的操作,和操作单个结构体变量相同
s[0].id = 1001;
strcpy(s[0].name, "zhangsan");
printf("%d %s\n",s[0].id, s[0].name); //1001 zhangsan
s[1].id = 1002;
strcpy(s[1].name, "lisi");
printf("%d %s\n",s[1].id ,s[1].name); //1002 lisi
return 0;
}
lin
2.6结构体数组的初始化和赋值
//方式1:
struct Student{
int id;
char name[32];
char gender;
int score;
};
struct Student s[2];
s[0].id = 1001;
strcpy(s[0].name, "zhangsan");
s[0].gender = 'M';
s[0].score = 98;
s[1].id = 1002;
strcpy(s[1].name, "lisi");
s[1].gender = 'W';
s[1].score = 100;
//方式2:
struct Student{
int id;
char name[32];
char gender;
int score;
};
struct Student s[2] = {
{1001, "zhangsan". 'M', 98},
{1002, "lisi", 'W', 100}
}
//方式3:
struct Student{
int id;
char name[32];
char gender;
int score;
};
struct Student s[5] = {
[0] = {1001, "zhangsan", 'M', 98},
[3] = {1002, "lisi", 'W', 100}
};
//方式4:
struct Student{
int id;
char name[32];
char gender;
int score;
};
struct Student s[5] = {
[0] = {
.id = 1001,
.name = "zhangsan"
},
[3] = {
.gender = 'W',
.score = 100
}
};
2.7C语言的结构体不允许定义函数
C语言的结构体不允许定义函数,可以定义函数指针。
#include<stdio.h>
#include<string.h>
int func(int x, int y){
return x+y;
}
struct Test{
int a;
char b[10];
int (*p)(int, int);
#if 0
int func2(int x, int y){ //c语言的结构体无法定义函数
return x+y;
}
#endif
};
int main(int argc, const char *argv[])
{
struct Test t = {10, "hello", func};
printf("%d\n", t.p(10, 20)); //30
return 0;
}
2.8 *结构体对齐*
2.8.1 32位系统对齐规则
1.如果结构体成员都小于4字节,则按最大的成员对齐。
2.如果结构体成员大于或等于4字节,则按4字节对齐。
3.注意:char 和 short连续存储的问题。
4.成员距离首地址的偏移量必须是成员类型大小的整数倍。
如下方例子,一个格代表一个字节
struct Test{
char a;
char b;
char c;
};// 3
struct Test{
char a;
char b;
short c; };// 4
struct Test{
short a;
char b; };// 4
struct Test{
char a;
short b; };// 4
struct Test{
char a;
int b; };// 8
struct Test{
char a;
short b;
char c;
int d; };// 12
struct Test{
char a;
long long b; };//12 ----32位系统 有成员超过4字节 也是按4字节对齐
2.8.2 64位系统对齐规则
对齐规则:在32位系统的基础上,按最大成员对齐。
同时也遵循成员距离首地址的偏移量必须是成员类型大小的整数倍原则
long double 32位系统中12字节 64位系统中6字节
struct Test{
long double a;
char b; }; // 32
注意事项
结构体中包含其他结构体变量时的对齐规则
struct A{
short x;
short y;
short z;
}; // 6struct B{
struct A a;
char b;
int c;
}; // 12//因为 结构体A是按2字节对齐的,大小是6字节,
// 当把结构体A放在结构体B中时,由于结构体B是按4字节对齐的
// 所以结构体B为了满足自身的对齐,需要额外分配2个字节(下图中绿色的部分)
// 这2个字节相当于结构体B分配的所以 结构体B的成员可以占用
struct C{
int x;
short y;
}; // 8struct D{
struct C a;
char b;
int c;
}; // 16//因为结构体C自身就是按照4字节对齐的,大小是8字节
// 当把结构体C放在结构体D中时,结构体D并没有为了满足自身的对齐
// 而额外给结构体C分配空间 (下图绿色的部分)是结构体C为了满足自身对齐而浪费的
// 结构体D的成员不能占用这两个字节
2.9 结构体位域
结构体位域是用来压缩结构体的,可以通过 冒号 指定结构体的成员占用多少个bit位。
#include<stdio.h>
#include <stdio.h>
struct LED{
unsigned char led0:1; // 通过冒号的方式指定成员只占用1个bit位
unsigned char led1:1;
unsigned char led2:1;
unsigned char led3:1;
unsigned char led4:1;
unsigned char led5:1;
unsigned char led6:1;
unsigned char led7:1;
};
int main(int argc, const char *argv[])
{
//使用位运算
unsigned char led;
led |= (1<<2); // 将2号灯点亮(只操作2号灯不影响其他灯)
led &= ~(1<<4);// 将4号灯熄灭(只操作4号灯不影响其他灯)
//使用结构体位域
struct LED my_led;
my_led.led2 = 1; // 将2号灯点亮(只操作2号灯不影响其他灯)
my_led.led4 = 0; // 将2号灯熄灭(只操作4号灯不影响其他灯)
return 0;
}
3. 共用体(联合体)--union
格式:
union 共用体名{
数据类型1 成员1;
数据类型2 成员2;
...
数据类型n 成员n;
};
1.定义共用体类型和使用共用体类型定义变量与结构体一样。
2.共用体所有成员共用一块内存空间。
3.共用体的所有成员的首地址是一样的。
4.共用体的大小是成员的最大类型。
使用共用体写一个简单的C程序 判断使用的主机是大端存储还是小端存储。
#include<stdio.h> //定义共用体类型 union Test{ int a; char b; }; int main(int argc, const char *argv[]) { union Test t; t.a = 0x12345678; if(0x78 == t.b){ printf("小端存储\n"); }else if(0x12 == t.b){ printf("大端存储\n"); } return 0; }