一、结构体
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);//0
return 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));//8
union 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.c
gcc 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.o
gcc test.o -o app
test.o:test.s
gcc test.s -c -o test.o
test.s:test.i
gcc test.i -S -o test.s
test.i:test.c
gcc 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.o
gcc main.o hqyj1.o hqyj2.o hqyj3.o -o app
main.o:main.c
gcc -I ../include main.c -c -o main.o
hqyj1.o:hqyj1.c
gcc -I ../include hqyj1.c -c -o hqyj1.o
hqyj2.o:hqyj2.c
gcc -I ../include hqyj2.c -c -o hqyj2.o
hqyj3.o:hqyj3.c
gcc -I ../include hqyj3.c -c -o hqyj3.o
clean:
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.o
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 $@
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)