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

相关文章

windows和Linux使用命令行计算文件的MD5值

《windows和Linux使用命令行计算文件的MD5值》在Windows和Linux系统中,您可以使用命令行(终端或命令提示符)来计算文件的MD5值,文章介绍了在Windows和Linux/macO... 目录在Windows上:在linux或MACOS上:总结在Windows上:可以使用certuti

Linux之systemV共享内存方式

《Linux之systemV共享内存方式》:本文主要介绍Linux之systemV共享内存方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、工作原理二、系统调用接口1、申请共享内存(一)key的获取(二)共享内存的申请2、将共享内存段连接到进程地址空间3、将

快速修复一个Panic的Linux内核的技巧

《快速修复一个Panic的Linux内核的技巧》Linux系统中运行了不当的mkinitcpio操作导致内核文件不能正常工作,重启的时候,内核启动中止于Panic状态,该怎么解决这个问题呢?下面我们就... 感谢China编程(www.chinasem.cn)网友 鸢一雨音 的投稿写这篇文章是有原因的。为了配置完

将Java程序打包成EXE文件的实现方式

《将Java程序打包成EXE文件的实现方式》:本文主要介绍将Java程序打包成EXE文件的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录如何将Java程序编程打包成EXE文件1.准备Java程序2.生成JAR包3.选择并安装打包工具4.配置Launch4

Linux命令之firewalld的用法

《Linux命令之firewalld的用法》:本文主要介绍Linux命令之firewalld的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux命令之firewalld1、程序包2、启动firewalld3、配置文件4、firewalld规则定义的九大

Java程序进程起来了但是不打印日志的原因分析

《Java程序进程起来了但是不打印日志的原因分析》:本文主要介绍Java程序进程起来了但是不打印日志的原因分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java程序进程起来了但是不打印日志的原因1、日志配置问题2、日志文件权限问题3、日志文件路径问题4、程序

Linux之计划任务和调度命令at/cron详解

《Linux之计划任务和调度命令at/cron详解》:本文主要介绍Linux之计划任务和调度命令at/cron的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux计划任务和调度命令at/cron一、计划任务二、命令{at}介绍三、命令语法及功能 :at

Linux下如何使用C++获取硬件信息

《Linux下如何使用C++获取硬件信息》这篇文章主要为大家详细介绍了如何使用C++实现获取CPU,主板,磁盘,BIOS信息等硬件信息,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录方法获取CPU信息:读取"/proc/cpuinfo"文件获取磁盘信息:读取"/proc/diskstats"文

Linux内核参数配置与验证详细指南

《Linux内核参数配置与验证详细指南》在Linux系统运维和性能优化中,内核参数(sysctl)的配置至关重要,本文主要来聊聊如何配置与验证这些Linux内核参数,希望对大家有一定的帮助... 目录1. 引言2. 内核参数的作用3. 如何设置内核参数3.1 临时设置(重启失效)3.2 永久设置(重启仍生效

Python列表去重的4种核心方法与实战指南详解

《Python列表去重的4种核心方法与实战指南详解》在Python开发中,处理列表数据时经常需要去除重复元素,本文将详细介绍4种最实用的列表去重方法,有需要的小伙伴可以根据自己的需要进行选择... 目录方法1:集合(set)去重法(最快速)方法2:顺序遍历法(保持顺序)方法3:副本删除法(原地修改)方法4: