Linux-Level1-day11: 大型程序编译makefile;复合类型之结构体(核心);联合体union

本文主要是介绍Linux-Level1-day11: 大型程序编译makefile;复合类型之结构体(核心);联合体union,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

回顾:

1.指针数组

每个元素都是一个地址

语法:数据类型 *数组名[元素个数] = {地址列表}; int *a[2]={&c,&d};

2.字符指针数组

每个元素都是一个字符串的首地址

3.预处理指令(替换作用)

1.#include

  1. #define :它可以提高代码可移植性

常量宏与 宏函数:代码执行效率高,函数相比效率低

编译器预定义的宏:FILE,_FUNCTION,LINE,DATE,TIME__ 用于将来软件调试或者打印日志

-D选项指定一个自己的宏,注意字符串需要用\"转义

3.条件预处理指令:#if/#ifdef/#ifndef/#else/#elif/endif-----条件判断编译器是否编译

4.大型程序文件分类:三大类

头文件:变量声明,函数声明,自定义数据类型声明

源文件:变量定义,函数定义。源文件包含自己对应的头文件

主文件:包含main函数,调用其他源文件的变量和函数,所以前提还需包含源文件对应的头文件

公共头文件:头文件中如果有相同的内容,摘出来单独放到一个公共的头文件。其他头文件包含公共的头文件即可

注意:static定义的变量与函数只能在本文件使用


5.大型程序的编译靠:Makefile

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

gcc -o main main.c a.c b.c ....

这么简化程序的编译呢?

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

5.2.Makefile功能:能够制定编译规则,将来让gcc编译器根据这个规则来编译程序,

Makefile本质就是一个文本文件,此文件给make命令使用,

将来make命令会根据Makefile里面的编译规则让gcc编译程序

5.3.Makefile语法格式:

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

(TAB键)编译命令1

(TAB键)编译命令2

...

(TAB键)编译命令N

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

注意:Makefile注释用#

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

 #指定规则:一步到位法
​helloworld:helloworld.cgcc -o helloworld helloworld.c或者#指定规则1:分步法helloworld:helloworld.ogcc -o helloworld helloworld.o
​#指定规则2:helloworld.o:helloworld.cgcc -c -o helloworld.o helloword.c

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

mkdir -p /home/tarena/stdc/day11/Makefile1/

cd /home/tarena/stdc/day11/Makefile1/

vim helloworld.c

vim Makefile

make //编译程序命令

./helloworld

img编辑

img编辑

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

vim helloworld.c //修改源文件

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

make //又重新编译

说明make命令可以帮你检查这个文件是不是最新编译过的。

案例:将昨天的多文件代码拷贝并且利用Makefile编译

mkdir -p /home/tarena/stdc/day11/Makefile2/

cd /home/tarena/stdc/day11/Makefile2/

vim Makfile

make

./main

img编辑

img编辑

5.4.Makefile工作原理了解

当执行make命令时,make命令首先在当前目录下找Makefile,一旦找到Makfile

文件,打开此文件并且找到所有的编译规则,通过这些编译规则确定了最终的目标是

helloworld和源文件helloworld.c,然后make命令首先在当前目录下找是否存在

目标文件helloworld,如果helloworld存在,然后检查helloworld和helloworld.c

的时间戳哪个更新,如果helloworld的时间戳比helloworld.c新,说明源文件没有

改过,无需编译,提示文件最新,如果helloworld的时间戳比helloworld.c要旧

说明helloworld.c修改过,根据编译规则的命令重新编译

如果一开始没有找到helloworld,程序整个重新编译

5.5.Makefile小技巧---灵魂

 %.o:%.c(TAB键)gcc -c -o $ @ $<说明:%.o:目标文件.o%.c:源文件.c$@:目标文件$<:源文件

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

img编辑

6.复合类型之结构体(核心)

6.1)明确:目前C程序分配内存的方法两种:定义变量和定义数组

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

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

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

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

int age; //年龄

char *name ;//名字

int id; //学号

float score; //学分

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

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

对应的关键字:struct

结构体也是一种数据类型,它是程序员自行定义的一种数据类型

类比成一个int类型

结构体分配的内存是连续的,一个成员挨着一个成员

6.3)结构体声明定义的使用方法:

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

1.语法:struct  {
结构体成员; //又称结构体字段}结构体变量名;
​
2.例如:描述学生信息//定义一个学生信息的结构体变量student1
​
struct  {int age; //描述学生的年龄int id; //描述学生的学号float score; //描述学生的学分char name[30]; //描述学生的姓名}student1;
​//再定义一个学生信息的结构体变量student2struct  {int age; //描述学生的年龄int id; //描述学生的学号float score; //描述学生的学分char name[30]; //描述学生的姓名}student2;

缺陷:每次定义一个结构体变量,结构体成员都要重新写一遍,很烦躁!

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;
​

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  student{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;

6.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.特点:不用按照顺序,不用全部成员初始化

6.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->agestrcpy(p->name, "葛鹏");   p->weight = 821;p->age = 17;  
​
​
/*struct1.c先声明后定义结构体玩法*/
#include <stdio.h>
#include <string.h>
​
struct student {char name[30]; //姓名int id; //学号int age; //年龄float score; //学分
};
int main(void)
{//定义并且初始化结构体变量:描述关羽同学的学生信息struct student student1 = {"关羽", 666, 18, 65.5};//不取别名定义变量方法printf("%s, %d, %d, %g\n", student1.name, student1.id,student1.age, student1.score);student1.age++;student1.score = 80;printf("%s, %d, %d, %g\n",student1.name, student1.id,student1.age, student1.score);
​//通过指针变量访问struct student *p = &student1; printf("%s, %d, %d, %g\n",p->name, p->id, p->age, p->score);p->age++;p->score = 95.5;strcpy(p->name, "张飞");printf("%s, %d, %d, %g\n",p->name, p->id, p->age, p->score);
​return 0;
}
​
/*struct2.c 采用别名方式*/
#include <stdio.h>
#include <string.h>
​
/*对声明的结构体取别名*/
typedef struct student {char name[30]; //姓名int id; //学号int age; //年龄float score; //分数
}stu_t;
​
int main(void)
{stu_t student1;//取别名定义变量方式stu_t student1 = {.score = 65.5,.id = 555,.name = "关羽"};printf("%s, %d, %d, %g\n", student1.name, student1.id, student1.age, student1.score);strcpy(student1.name, "刘备");student1.score = 85.5;printf("%s, %d, %d, %g\n", student1.name, student1.id, student1.age, student1.score);stu_t *p = &student1; printf("%s, %d, %d, %g\n",p->name, p->id, p->age, p->score);strcpy(p->name, "张飞");p->score = 15.5;printf("%s, %d, %d, %g\n",p->name, p->id, p->age, p->score);return 0;
}
​
/*struct3.c采用别名方式2*/
#include <stdio.h>
#include <string.h>
​
/*声明结构体*/
struct student {char name[30]; //姓名int id; //学号int age; //年龄float score; //分数
};
​
/*取别名*/
typedef struct student stu_t;
​
int main(void)
{//定义初始化结构体变量:描述学生信息stu_t student1 = {.score = 65.5,.id = 555,.name = "关羽"};printf("%s, %d, %d, %g\n", student1.name, student1.id, student1.age, student1.score);//修改strcpy(student1.name, "刘备");student1.score = 85.5;printf("%s, %d, %d, %g\n", student1.name, student1.id, student1.age, student1.score);stu_t *p = &student1; //p指向student1printf("%s, %d, %d, %g\n",p->name, p->id, p->age, p->score);strcpy(p->name, "张飞");p->score = 15.5;printf("%s, %d, %d, %g\n",p->name, p->id, p->age, p->score);return 0;
}
​

6.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;  

6.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; //学生的出生日期
​};

/*struct5.c结构体嵌套*/
#include <stdio.h>
​
//声明描述学生出生日期的结构体
typedef struct birthday {int year;//年int month; //月int date; //日
}birthday_t;
​
//声明描述学生信息的结构体
typedef struct student {char name[30]; //名字int age;//年龄birthday_t birth; //出生日期
}stu_t;
​
int main(void)
{//定义初始化结构体变量//stu_t student1 = {"关羽", 18, {2001, 2, 10}}; //传统初始化stu_t student1 = {  //标记初始化.name = "关羽",.age = 18,.birth = {.year = 2001,.month = 2,.date = 10}};
​stu_t *p = &student1; //p指向student1printf("%s, %d, %d:%d:%d\n", student1.name, student1.age,student1.birth.year, student1.birth.month,student1.birth.date);p->birth.year = 2000;p->birth.month = 3;printf("%s, %d, %d:%d:%d\n", p->name, p->age,p->birth.year, p->birth.month,p->birth.date);return 0;
}
​
​
/*struct6.c结构体嵌套*/
#include <stdio.h>
​
//声明描述学生出生日期的结构体
typedef struct birthday {int year;//年int month; //月int date; //日
}birthday_t;
//声明描述学生信息的结构体
typedef struct student {char name[30]; //名字int age;//年龄birthday_t *pbirth; //出生日期
}stu_t;
​
int main(void)
{//定义初始化描述出生日期的结构体变量birthday_t stu_birth = {2001, 2, 10};
​//定义初始化结构体变量描述学生的信息//stu_t student1 = {"关羽", 18, &stu_birth}; //传统初始化stu_t student1 = {  //标记初始化.name = "关羽",.age = 18,.pbirth = &stu_birth //让pbirth指向stu_birth};stu_t *p = &student1; //p指向student1printf("%s, %d, %d:%d:%d\n", student1.name, student1.age,student1.pbirth->year, student1.pbirth->month,student1.pbirth->date);p->pbirth->year = 2000;p->pbirth->month = 3;printf("%s, %d, %d:%d:%d\n", p->name, p->age,p->pbirth->year, p->pbirth->month,p->pbirth->date);return 0;
}
​
​

6.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++;}
/*struct7.c函数的形参是结构体变量*/
#include <stdio.h>
//声明描述学生信息的结构体
typedef struct student {char name[30];int age;
}stu_t;
​
//定义打印函数
void show(stu_t st)
{printf("%s %d\n", st.name, st.age);
}
/*定义grow函数*/
void grow(stu_t st)
{st.age++;
}
int main(void)
{//定义初始化结构体变量stu_t  student1 = {"关羽", 18};show(student1); grow(student1); //岁数没有加1show(student1);return 0;
}
​
/*struct8.c函数的形参是结构体变量*/
#include <stdio.h>
//声明描述学生信息的结构体
typedef struct student {char name[30];int age;
}stu_t;
//定义打印函数
void show(const stu_t *pst)
{printf("%s %d\n", pst->name, pst->age);//strcpy(pstr->name, "刘备"); //不允许改名
}
/*定义grow函数*/
void grow(stu_t *pst)
{pst->age++;
}
int main(void)
{//定义初始化结构体变量stu_t  student1 = {"关羽", 18};show(&student1); //打印grow(&student1); //岁数加1show(&student1);//打印return 0;
}
​

6.9)结构体内存对齐问题(笔试题必考)

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

例如:

struct A {

char buf[2];

int val;

};

结果:sizeof(struct A) = 8

内存分布图:结构体对齐.png

结构体内存对齐最小单位2,4,8....

img

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

7.联合体:很少用

7.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 那么c,b就不要访问了,因为数据随机

union A a = {.c = 8} //指定给c赋值 那么a,b就不要访问了,因为数据随机

img编辑

img编辑

7.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=0x12345678;if (c.b==78)printf("小端\nn");elseprintf("大端\n");return 1;
​
}

指针验证是什么架构

img编辑

img编辑

X86架构

这篇关于Linux-Level1-day11: 大型程序编译makefile;复合类型之结构体(核心);联合体union的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/373686

相关文章

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法

《ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法》本文介绍了Elasticsearch的基本概念,包括文档和字段、索引和映射,还详细描述了如何通过Docker... 目录1、ElasticSearch概念2、ElasticSearch、Kibana和IK分词器部署

Linux流媒体服务器部署流程

《Linux流媒体服务器部署流程》文章详细介绍了流媒体服务器的部署步骤,包括更新系统、安装依赖组件、编译安装Nginx和RTMP模块、配置Nginx和FFmpeg,以及测试流媒体服务器的搭建... 目录流媒体服务器部署部署安装1.更新系统2.安装依赖组件3.解压4.编译安装(添加RTMP和openssl模块

linux下多个硬盘划分到同一挂载点问题

《linux下多个硬盘划分到同一挂载点问题》在Linux系统中,将多个硬盘划分到同一挂载点需要通过逻辑卷管理(LVM)来实现,首先,需要将物理存储设备(如硬盘分区)创建为物理卷,然后,将这些物理卷组成... 目录linux下多个硬盘划分到同一挂载点需要明确的几个概念硬盘插上默认的是非lvm总结Linux下多

Python如何计算两个不同类型列表的相似度

《Python如何计算两个不同类型列表的相似度》在编程中,经常需要比较两个列表的相似度,尤其是当这两个列表包含不同类型的元素时,下面小编就来讲讲如何使用Python计算两个不同类型列表的相似度吧... 目录摘要引言数字类型相似度欧几里得距离曼哈顿距离字符串类型相似度Levenshtein距离Jaccard相

Python中顺序结构和循环结构示例代码

《Python中顺序结构和循环结构示例代码》:本文主要介绍Python中的条件语句和循环语句,条件语句用于根据条件执行不同的代码块,循环语句用于重复执行一段代码,文章还详细说明了range函数的使... 目录一、条件语句(1)条件语句的定义(2)条件语句的语法(a)单分支 if(b)双分支 if-else(

在不同系统间迁移Python程序的方法与教程

《在不同系统间迁移Python程序的方法与教程》本文介绍了几种将Windows上编写的Python程序迁移到Linux服务器上的方法,包括使用虚拟环境和依赖冻结、容器化技术(如Docker)、使用An... 目录使用虚拟环境和依赖冻结1. 创建虚拟环境2. 冻结依赖使用容器化技术(如 docker)1. 创

Go语言中三种容器类型的数据结构详解

《Go语言中三种容器类型的数据结构详解》在Go语言中,有三种主要的容器类型用于存储和操作集合数据:本文主要介绍三者的使用与区别,感兴趣的小伙伴可以跟随小编一起学习一下... 目录基本概念1. 数组(Array)2. 切片(Slice)3. 映射(Map)对比总结注意事项基本概念在 Go 语言中,有三种主要

linux进程D状态的解决思路分享

《linux进程D状态的解决思路分享》在Linux系统中,进程在内核模式下等待I/O完成时会进入不间断睡眠状态(D状态),这种状态下,进程无法通过普通方式被杀死,本文通过实验模拟了这种状态,并分析了如... 目录1. 问题描述2. 问题分析3. 实验模拟3.1 使用losetup创建一个卷作为pv的磁盘3.