嵌入式-C语言-9-Makefile/结构体/联合体

一、Makefile

1.1.问:如果项目产品代码有1万源文件.c,编译极其的繁琐

             gcc -o main main.c a.c b.c .... 一个万.c

             这么简化程序的编译呢?

      答:必须只能利用Makefile来实现

1.2.Makefile功能:能够制定编译规则,将来让gcc编译器根据这个规则来编译程序,Makefile本质就是一个文本文件,此文件给make命令使用,将来make命令会根据Makefile里面的编译规则让gcc编译程序。

1.3.Makefile语法格式:

      目标:依赖1  依赖2 依赖3 ....依赖N

      (TAB键)编译命令1

      (TAB键)编译命令2

      ...

      (TAB键)编译命令N

      (TAB键) 还可以是其他命令:ls/cp/cd等

      注意:Makefile注释用#

      例如:目标是把helloworld.c编译生成helloworld

      vim Makfile 添加

      #指定规则:一步到位

      helloworld:helloworld.c

gcc -o helloworld helloworld.c 

      #或者

      #指定规则1:分步

      helloworld:helloworld.o

gcc -o helloworld helloworld.o

      #指定规则2:

      helloworld.o:helloworld.c

gcc -c -o helloworld.o helloword.c

      案例:利用Makefile编译helloworld.c文件

      vim helloworld.c

      vim Makefile

      make   //编译程序

      ./helloworld

      make //编译提示helloworld是最新的

     vim helloworld.c //修改源文件

     ls -lh //查看helloworld.c和helloworld的时间戳

     make //又重新编译

先检查有没有helloworld文件,如果有,就检查helloworld.c文件的时间戳是不是比他新,如果helloworld.c文件比helloworld文件新,就重新编译,反之不更新,如果没有helloworld文件,就按编译规则来。

1.4.Makefile工作原理

当执行make命令时,make命令首先在当前目录下找Makefile,一旦找到Makfile文件,打开此文件并且找到所有的编译规则,通过这些编译规则确定了最终的目标是helloworld和源文件helloworld.c,然后make命令首先在当前目录下找是否存在目标文件helloworld,如果helloworld存在,然后检查helloworld和helloworld.c的时间戳哪个更新,如果helloworld的时间戳比helloworld.c新,说明源文件没有改过,无需编译,提示文件最新,如果helloworld的时间戳比helloworld.c要旧,说明helloworld.c修改过,根据编译规则的命令重新编译,如果一开始没有找到helloworld,程序整个重新编译

1.5.Makefile小技巧

      %.o:%.c

      (TAB键)gcc  -c  -o  $@  $<

      说明:

      %.o:目标文件.o

      %.c:源文件.c

      $@:目标文件

      $<:源文件

      作用是将当前目录下所有的.c文件单独编译生成对应的.o目标文件

二、复合类型之结构体

2.1.目前C程序分配内存的方法两种:定义变量和定义数组

定义变量的缺陷:不能大量定义,所以诞生数组

定义数组的缺陷:数据类型是相同的,所以诞生结构体

问:什么场合需要定义大量变量和变量的数据类型不相同呢?

答:比如让计算机记录或者描述一个学生的信息

学生的信息如下:

int age; //年龄

char *name ;//名字

int id; //学号

float score; //学分

显然变量的数据类型不一致,数组无法做到,采用结构体.

2.2.结构体特点:能够包含大量的变量并且对变量的数据类型无要求       

对应的关键字:struct

结构体也是一种数据类型,它是程序员自行定义的一种数据类型,类比成一个int类型,结构体分配的内存是连续的,一个成员挨着一个成员

2.3.结构体声明定义的使用方法:

a)方法1:直接定义结构体变量(很少用)

1.语法:struct  {

结构体成员; //又称结构体字段

   }结构体变量名;

2.例如:描述学生信息 

  //定义一个学生信息的结构体变量student1
   struct  {
int age; //描述学生的年龄
int id; //描述学生的学号
float score; //描述学生的学分
char name[30]; //描述学生的姓名
   }student1;
   //再定义一个学生信息的结构体变量student2
   struct  {
int age; //描述学生的年龄
int id; //描述学生的学号
float score; //描述学生的学分
char name[30]; //描述学生的姓名
   }student2;

3.缺陷:每次定义一个结构体变量,结构体成员都要重新写一遍,很繁琐

b)方法2:先声明结构体数据类型, 然后用这种结构体数据类型定义结构体变量(常用,掌握)

1.声明结构体数据类型的语法:

    struct  结构体名 {

结构体成员;

    };

    注意:不会分配内存     大型程序,结构体声明放到头文件来写

2.用结构体数据类型定义结构体变量的语法:

   struct 结构体名  结构体变量名;

   注意:会分配内存        大型程序,结构体定义放到源文件中来写

3.例如:

   //1.声明描述学生信息的结构体数据类型

   struct student {
int age; //描述学生的年龄
int id; //描述学生的学号
float score; //描述学生的学分
char name[30]; //描述学生的姓名
   };

  //2.定义两个学生信息的结构体变量  

struct student student1;
struct student studnet2;
//或者
struct student student1, student2;

4.缺陷:每次定义结构体变量,struct 结构体名每次都要书写,很烦躁!

c)方法3:先用typedef关键字给一个声明的结构体数据类型取别名(外号),然后用别名定义结构体变量(实际开发最常用)

1)务必掌握typedef关键字

   功能:给数据类型取别名(外号)

   语法:typedef  原数据类型   别名;

   例如:对于基本数据类型取别名(实际开发代码)

typedef  char  s8;   //s=signed:有符号,8:8位
typedef unsigned char u8; //u=unsiged
typedef short s16;
typedef unsigned short u16;
typedef int s32;
typedef unsigned int u32;
typedef long long s64;
typedef unsigned long long u64;
typedef float f32;
typedef double f64;
//使用:
int a 写成 s32 a;
unsigned char b 写成 u8 b

2.用typedef对声明的结构体取别名

   注意:规定:别名后面加_t,对于大型程序写头文件(不成文规定)

   形式1:

   语法:typedef struct {

    结构体成员;

   }别名_t;    

   例如:

typedef struct  {
int age; //描述学生的年龄
int id; //描述学生的学号
float score; //描述学生的学分
char name[30]; //描述学生的姓名
   }stu_t;

   形式2:

   语法:typedef struct 结构体名{

    结构体成员;

   }别名_t;    

   例如:  

typedef struct  studnet{
int age; //描述学生的年龄
int id; //描述学生的学号
float score; //描述学生的学分
char name[30]; //描述学生的姓名
   }stu_t;

   形式3:

   struct  studnet{
int age; //描述学生的年龄
int id; //描述学生的学号
float score; //描述学生的学分
char name[30]; //描述学生的姓名
int weight; //学生的体重
   };
   //取别名
   typedef struct student stu_t;

3.不管使用哪种typedef对结构体数据类型取别名,定义结构体变量都一样

    定义结构体变量语法:别名  结构体变量名;

    例如:定义两个学生信息的结构体变量

    stu_t student1;    

    stu_t student2;

    或者:

    stu_t     student1, student2;

     

2.4.结构体变量的初始化方式,两种方式:

a)传统初始化方式:

1.语法:struct 结构体名/别名  结构体变量名 = {初始化的值};

2.例如: 

struct student student1 = {18, 666, 100, "哥", 128};
//或者
stu_t  student1 = {18, 666, 100, "哥"};

3.缺陷:定义初始化的时候需要按照顺序全部初始化,因为有些场合可以不用按照顺序,关键是可以不用全部初始化

b)标记初始化方式:  

1.语法:struct 结构体名/别名  结构体变量名 = {

.某个成员名 = 初始化值,

.某个成员名 = 初始化值,

... };

2.例如:

struct student student1 = {
.name   = "哥",
.weight = 128,
.age = 18,
 };
//或者
stu_t  student1 = {
.name   = "哥",
.weight = 128,
.age = 18,
};

3.特点:不用按照顺序,不用全部成员初始化

2.5.结构体变量成员的访问:两种形式

a)通过"."运算符来访问结构体变量的成员

   语法:结构体变量名.成员名; //将来可以访问这个成员的内存区域

   例如:

stu_t  student1 = {
.name   = "哥",
.weight = 128,
.age = 18,
 };
//读查看
printf("%s %d %d\n",
student1.name, student1.weight, student1.age);
//写修改
strcpy(student1.name, "弟");    
student1.weight = 821;
student1.age = 17;  

b)通过"->"运算符来访问结构体指针变量的成员

语法:结构体指针变量名->成员名; //将来可以访问这个成员的内存区域

例如:

stu_t  student1 = {
.name   = "哥",
.weight = 128,
.age = 18,
    };
stu_t *p = &student1; //定义一个结构体指针变量p指向student1结构体变量
//读查看
printf("%s %d %d\n",
p->name, p->weight, p->age
  strcpy(p->name, "弟");    
  p->weight = 821;
  p->age = 17;  

2.6.结构体变量之间可以直接赋值

      例如:

      stu_t  student1  = {18, 666, 100, "哥", 128};

      stu_t  student2 = student1;

      或者

      stu_t  student1  = {18, 666, 100, "哥", 128};

      stu_t *p = &student1; //p指向student1

      stu_t student2 = *p;      

2.7.结构体嵌套:结构体成员还是一个结构体

      例如:

      //声明描述学生出生日期的结构体

typedef struct birthday {
int year;  //年
int month; //月
int date; //日
}birthday_t;
//声明描述学生信息的结构体
typedef struct student {
char name[30]; //姓名
int age; //年龄
//struct birthday birth; //学生的出生日期
birthday_t  birth; //学生的出生日期
     };

2.8.函数的形参是结构体,两种形式

a)直接传递结构体变量本身,形参是实参的一份拷贝,结构体有多大就需要拷贝多大,函数通过形参是不能修改结构体实参,只是对形参做了改变

b)直接传递结构体变量的地址,函数通过形参可以直接修改结构体实参,代码执行效率高,如果是指针只需拷贝4字节

c)公式,规矩:如果函数要访问结构体,将来要传递结构体指针,不要传递结构体变量,如果函数对结构体成员不进行修改,形参用const修饰

  void show(const stu_t *pst)
  {
printf("%s\n", pst->name);
//不让修改:strcpy(pst->name, "蛋");
  }
  void grow(stu_t *pst)
  {
pst->age++;
  }

2.9.结构体内存对齐问题

a)gcc对结构体成员编译时,默认按4字节对齐

例如:

struct A {

char buf[2];

int val;

};

结果:sizeof(struct A) = 8

内存分布图:

b)演示代码:

/*结构体内存对齐*/
#include <stdio.h>
//声明结构体数据类型A
struct A {
    char buf[2]; //4
    int val; //4
};
//声明结构体数据类型B
struct B {
    char c; //4
    short s[2]; //4
    int i; //4
};
#pragma pack(1) //让gcc强制从这个地方开始后面代码按照1字节对齐方式编译
    //声明结构体类型C
    struct C {
        char c; //1
        short s[2]; //4
        int i; //4
};
#pragma pack() //让gcc到这里在恢复成默认4字节对齐
//声明结构体类型D
struct D {
    int i; //4
    char c; //4
};
//声明结构体类型E
struct E {
    double d;  //8
    char c; //4
};
int main(void)
{
    printf("sizeof(struct A) = %d\n", sizeof(struct A)); //8
    printf("sizeof(struct B) = %d\n", sizeof(struct B)); //12
    printf("sizeof(struct C) = %d\n", sizeof(struct C)); //9
    printf("sizeof(struct D) = %d\n", sizeof(struct D)); //8
    printf("sizeof(struct E) = %d\n", sizeof(struct E)); //12
    return 0;
}

三、联合体

3.1.特点:

a)它和结构体使用语法一模一样,只是将关键字struct换成union

b)联合体中所有成员是共用一块内存,优点节省内存

c)联合体占用的内存按成员中占内存最大的来算例如:

union A {
char a;
short b;
int c;
};
sizeof(union A) = 4;

d)初始化问题

   union A  a = {8}; //默认给第一个成员a,a = 8

   union A  a = {.c = 8} //强制给c赋值

3.2.经典笔试题

      现象:

      1.X86架构的CPU为小端模式:数据的低位在内存的低地址,数据的高位在内存的高地址处

         例如:

         int a = 0x12345678;

         内存条

         低地址 高地址

         0-------1-----2-------3------4--------------------------------->

           0x78    0x56  0x34   0x12

     2.POWERPC架构的CPU为大端模式

数据的低位在内存的高地址,数据的高位在内存的低地址处

         例如:

         int a = 0x12345678;

         内存条

         低地址 高地址

         0-------1-----2-------3------4--------------------------------->

           0x12    0x34  0x56   0x78

     要求:编写一个程序求当前处理器是X86架构还是POWERPC架构

思路:采用union或者指针

提示:

 union A {

char a;

int b;

};

参考代码:

#include <stdio.h>
//声明一个联合体
typedef union w
{
  int a;  //4 字节
  char b; //1 字节
} c_t;
int main(void)
{
  //定义联合体变量
  c_t c.a=1;
  if (c.b==1)
   printf("小端\nn");
  else
   printf("大端\n");
  return 1;
}