一、结构体
1.1 结构体变量的初始化和赋值的方式
struct Student{int id;char name[32];int score;
}
struct Student s;
s.id = 1001;
strcpy(s.name,"zhangsan");
s.score = 98;
struct Student{int id;char name[32];int score;
}s;
s.id = 1001;
strcpy(s.name,"zhangsan");
s.score = 98;
struct Student{int id;char name[32];int score;
};
struct Struct s = {1001,"zhangsan",98};
struct Student{int id;char name[32];int score;
}s = {1001,"zhangsan",98};
struct Student{int id;char name[32];int score;
};
struct Student s={.id = 1001,.score = 98;
};
struct Student{int id;char name[32];int score;
};
struct Student s;
s = (struct Student){1001,"zhangsan",98};
1.2 结构体数组的初始化和赋值的方式
struct Student{int id;char name[32];int score;
};struct Student s[2];
s[1].id = 1001;
strcpy(s[1].name,"zhangsan");
s[1].score = 100;s[2].id = 1002;
strcpy(s[2].name,"lisi");
s[1].score = 101;
struct Student{int id;char name[32];int score;
};
struct Student s[2]={{1001,"zhangsan",98},{1002,"lisi",99}
};
struct Student{int id;char name[32];int score;
};
struct Student s[5] = {[0] = {1001,"zhangsan",98},[3] = {1002,"lisi",100}
};
struct Student{int id;char name[32];int score;
};
struct Struct s[5] = {[0] = {.id = 1001;.score = 99},[3] = {.name = "wangwu"}
};
1.3 C语言的结构体中不允许定义函数
C++的结构体中允许定义函数,但是C语言中不允许,C语言的结构体中可以有函数指针
#include <stdio.h>int my_add(int x,int y){return x+y;
}typedef struct Test{int id;char name[32];int score;int (*p)(int ,int );//定义函数指针
}Test_t;int main(int argc, const char *argv[])
{Test_t t;t.p = my_add;//让函数指针指向函数printf("%d\n",t.p(20,30));//通过函数指针调用函数return 0;
}
结果:
50
1.4 结构体对齐
----------------------------------32位系统------------------------------------------------
64位系统想将程序按32位编译,需要加编译选项 -m32
对齐规则:
1.如果结构体成员都是小于4字节,则按最大成员对齐
2.如果结构体有成员大于等于4字节,都按4字节对齐
3.要特别注意 char 和 short 连续存储问题:
每个成员距离首地址的偏移量必须是成员类型大小的整数倍!!
struct Test{char a;char b;char c;
};
共占3字节
struct Test{short b;char a;
};
共占4字节
struct Test{short b;char a;char c;
};
共占4字节
struct Test{char c;short b;char a;
};
共占6字节
struct Test{char c;int a;char b;
};
共占12字节
struct Test{char a;int c;
};
共占8字节
struct Test{char c;long long a;
};
共占12位
struct Test{char a;char b;short c;int d;
};
共占8字节
struct Test{char a;short c;char b;int d;
};
共占12字节
---------------------------------------64位系统--------------------------------------------
64位系统的对齐规则基本和32位系统一样,
只不过不是4位对齐,而是按照最大成员进行对齐
且每个成员距离首地址的偏移量也必须是成员类型大小的整数倍!!
struct Test{char a;long double b;
};
共占32字节
struct Test{char a;int b;char c;short d;long double e;
};
共占32字节
结构体中嵌套结构体时的对齐规则:
struct A{short x;short y;short z;
};
struct B{struct A m;char n;int k;
};
结构体A自身是2字节对齐,当把A放在B中的时候,由于B是4字节对齐的
相当于B为了满足自身的对齐,需要额外分给A两个字节(下图左紫色部分)
这两个字节是属于B的,所以B的成员可以占用
struct C{int x;short y;
};
struct D{struct C m;char n;int k;
};
结构体C自身就是4字节对齐,当把C放在D中的时候,D并没有为了满足自身的对齐,
而额外给C分配空间,因为C满足自身对齐时本来就浪费了两个字节(上图右紫色部分)
这两个字节时属于C的,所以D成员不可以占用
1.5 结构体位域
压缩结构体的一种手段,嵌入式底层开发时经常用到
#include <stdio.h>struct LED{unsigned char led0:1;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[])
{printf("%ld\n",sizeof(struct LED));//8字节struct LED my_led;//点亮3号灯my_led.led3 = 1;//熄灭4号灯my_led.led4 = 0;//压缩之后成员的存储范围也随之变小了my_led.led5 = 2;//超范围了printf("%d\n",my_led.led5);//0return 0;
}
结果:
1
0
二、共用体(联合体)--union
1.定义共用体类型的格式以及使用共用体定义变量的格式,都和结构体一模一样,只不过把关键字struct 换成了union
2.共用体中的所有成员时共用同一块内存空间
3.共用体的所有成员首地址相同
4.共用体的大小取决于共用体中成员的最大类型
union 共用体类型名{数据类型1 成员1;数据类型2 成员2;...数据类型n 成员n
};
因为共用体中所有成员都是用同一块内存空间
修改一个成员 其他成员的值也会受影响
所以,使用共用体时,我们不会同时使用多个成员
#include <stdio.h>union Test{char a;short b;int c;long long d;
};int main(int argc, const char *argv[])
{printf("%ld\n",sizeof(union Test));//8union Test t;t.a = 100;printf("t.a = %d\n",t.a);t.b = 300;printf("t.b = %d\n",t.b);printf("t.a = %d\n",t.a);//成员首地址都是一样的printf("%p\n",&t.a);printf("%p\n",&t.b);printf("%p\n",&t.c);printf("%p\n",&t.d);return 0;
}
结果:
8
t.a = 100
t.b = 300
t.a = 44
0x7ffcdcbc8800
0x7ffcdcbc8800
0x7ffcdcbc8800
0x7ffcdcbc8800
#include <stdio.h>union Test{char a;int d;
};int main(int argc, const char *argv[])
{union Test t;t.d = 0x12345678;if(t.a == 0x78){printf("小端\n");}else if(t.a == 0x12){printf("大端\n");}return 0;
}
结果:
小端
三、Makefile
3.1 什么是Makefile?
Makefile就是一个文件,他能体现出我们是否具有大型项目开发的能力
Makefile文件中存放的是整个项目编译的规则
它可以根据文件的时间戳来决定哪些文件需要重新编译(更新时间戳)
3.2 什么时Make?
make是一个可执行文件 是/usr/bin/make,是用来解析Makefile文件的,如果系统没有make,可以自己安装 sudo apt-get install make
3.3 Makefile 的语法
3.3.1 标签式语法
标签: #标签必须顶满格写指令 #指令必须以 tab 键开头
标签1:指令1
标签2:指令2
...
标签n:指令n
执行时,直接使用make 命令即可
make 会在当前路径下,去自动寻找。名为Makefile 的文件来解析和执行
make 默认执行时 执行第一个标签下的内容
也可以使用 make 标签名 方式来指定执行哪个标签下的内容
如果文件名不叫Makefile或者makefile ,也可以使用 make -f 文件名 方式来指定解析哪些文件
3.3.2 目标:依赖 式语法
目标:依赖 #目标必须顶满格写指令 #指令必须以tab键开头
app:02test.cgcc 02test.c -o app
例如:
test.c
编译步骤:预处理 编译 汇编 链接
预处理: 展开头文件 删除注释 替换宏定义
gcc -E test.c -o test.i
编译:词法分析 语法分析 查错
gcc -S test.i -o test.s
汇编:将汇编文件生成二进制的文件
gcc -c test.s -o test.o
链接:链接多个目标文件和库文件 生成最终的可执行文件
gcc test.o -o app
以上流程为例,写Makefile来看Makefile的执行方式
app:test.ogcc test.o -o app
test.o:test.sgcc test.s -c -o test.o
test.s:test.igcc test.i -S -o test.s
test.i:test.cgcc test.c -E -o test.i
make在解析Makefile时,会进行自动推导
app-->依赖-->test.o-->依赖-->test.s-->依赖-->test.i-->依赖-->test.c
c i s o
所以最终执行的顺序:
gcc -E test.c -o test.i
gcc -S test.i -o test.s
gcc -c test.s -o test.o
gcc test.o -o app
Makefile的目标又分为 目标和伪目标
目标:最终执行完后会生成目标文件的 如app
伪目标:最终执行完后不会生成目标文件的 如clean
------------------------------------------------------------------------------------
模拟大型项目,使用Makefile编写规则来编译项目
---------------(例子在最后)--------------------
Makefile中使用变量的方式和shell类似
VALUE = xxx
引用变量的值
$(VALUE) 或者 ${变量名}
变量的赋值方式:
这种赋值方式会将变量在整个文件中所有的赋值位置都找到
最后一次赋值的结果取出 赋给其他变量
V1=hello
V2=$(V1)
V1=world
all:@echo $(V2) //world
//@表示取消命令的回显
V1=hello
V2:=$(V1)
V1=world
all:@echo $(V2) //hello
V1=hello
V2=world
V1+=$(V2)
all:@echo $(V1) //hello world
如果前面已经给变量赋值过了,本次赋值不再生效
V1?=hello
V1?=world //赋值不生效
all:@echo $(V1) //hello
3.5 Makefile中的特殊变量
$@ 目标文件
$^ 所有依赖文件
$< 第一个依赖文件
CC=gcc
OBJ=main.o hqyj1.o hqyj2.o hqyj3.o
TARGET=app
INCLUDE=-I ../include
FLAG=-c -o$(TARGET):$(OBJ)$(CC) $^ -o $@
main.o:main.c$(CC) $(INCLUDE) $< $(FLAG) $@
hqyj1.o:hqyj1.c$(CC) $(INCLUDE) $< $(FLAG) $@
hqyj2.o:hqyj2.c$(CC) $(INCLUDE) $< $(FLAG) $@
hqyj3.o:hqyj3.c$(CC) $(INCLUDE) $< $(FLAG) $@clean:rm $(OBJ) $(TARGET)
3.6 Makefile 中的通配符
* Makefile中执行shell命令时使用的通配符 ( 通配任意长度的任意字符 )
LIST=$(shell ls *.c)
all:@echo $(LIST)
% Makefile中的通配符 ( 通配任意长度的任意字符 )
3.7 可以在Makefile包含其他的.mk文件
-include 要包含的文件名
-include ../config.mk
表示将上一级路径下的config.mk文件中的内容包含到当前Makefile文件中
---------------------------Makefile例子-------------------------------------
app:main.o hqyj1.o hqyj2.o hqyj3.ogcc main.o hqyj1.o hqyj2.o hqyj3.o -o app
main.o:main.cgcc -I ../include main.c -c -o main.o
hqyj1.o:hqyj1.cgcc -I ../include hqyj1.c -c -o hqyj1.o
hqyj2.o:hqyj2.cgcc -I ../include hqyj2.c -c -o hqyj2.o
hqyj3.o:hqyj3.cgcc -I ../include hqyj3.c -c -o hqyj3.oclean:rm main.o hqyj1.o hqyj2.o hqyj3.o app
CC=gcc
OBJ=main.o hqyj1.o hqyj2.o hqyj3.o
TARGET=app
INCLUDE=-I ../include
FLAG=-c -o$(TARGET):$(OBJ)$(CC) $(OBJ) -o $(TARGET)
main.o:main.c$(CC) $(INCLUDE) main.c $(FLAG) main.o
hqyj1.o:hqyj1.c$(CC) $(INCLUDE) hqyj1.c $(FLAG) hqyj1.o
hqyj2.o:hqyj2.c$(CC) $(INCLUDE) hqyj2.c $(FLAG) hqyj2.o
hqyj3.o:hqyj3.c$(CC) $(INCLUDE) hqyj3.c $(FLAG) hqyj3.oclean:rm $(OBJ) $(TARGET)
CC=gcc
OBJ=main.o hqyj1.o hqyj2.o hqyj3.o
TARGET=app
INCLUDE=-I ../include
FLAG=-c -o$(TARGET):$(OBJ)$(CC) $^ -o $@
main.o:main.c$(CC) $(INCLUDE) $< $(FLAG) $@
hqyj1.o:hqyj1.c$(CC) $(INCLUDE) $< $(FLAG) $@
hqyj2.o:hqyj2.c$(CC) $(INCLUDE) $< $(FLAG) $@
hqyj3.o:hqyj3.c$(CC) $(INCLUDE) $< $(FLAG) $@clean:rm $(OBJ) $(TARGET)
CC=gcc
OBJ=main.o hqyj1.o hqyj2.o hqyj3.o
TARGET=app
INCLUDE=-I ../include
FLAG=-c -o$(TARGET):$(OBJ)$(CC) $^ -o $@
%.o:%.c$(CC) $(INCLUDE) $< $(FLAG) $@clean:rm $(OBJ) $(TARGET)
-include ../config.mk
OBJ=main.o hqyj1.o hqyj2.o hqyj3.o
TARGET=app$(TARGET):$(OBJ)$(CC) $^ -o $@
%.o:%.c$(CC) $(INCLUDE) $< $(FLAG) $@clean:rm $(OBJ) $(TARGET)