11- C程序的组成结构 (C语言)

news2024/12/23 22:39:23

一、C程序的基本组成结构

  • 1、源文件: 后缀为.c 的文件
  • 2、头文件:后缀为.h的文件

注意:

  1. 源文件 功能:实现程序功能
  2. 头文件 功能:函数的声明、全局变量的声明、宏定义、类型的声明
  3. 一个由C语言所组成的项目中 只允许有一个main函数

二、头文件

2.1 头文件的作用

1、 定义

#define PI 3.14

2、结构体 定义

typedef struct student
{
    char name[32];
    int age;
}Stu;

3、全局变量 的声明

extern int cnt;  //注意这里是声明,而不是定义
int sum;  //这里也是声明

4、函数的声明

int func(int x, char y);
int func2(int, int); //函数声明时,形参只需要指定数据类型即可

示例代码:
头文件:main.h

//宏定义
#define PI 3.14
//结构体定义
typedef struct student
{
    char name[32];
    int age;
}Stu;

//全局变量的声明
extern int cnt;    //注意这里是声明,而不是定义
int sum;           //这里也是声明 编译时编译器会自动添加extern

//函数的声明
int func(int x, char y);
int func2(int, int);    //函数声明时,形参只需要指定数据类型即可

源文件:main.c

#include <stdio.h>
#include "main.h" //包含自己实现的头文件
//定义全局变量
int cnt;
int sum;
//定义函数
int func(int x, char y)
{
    return 0;
}
//定义函数
int func2(int x, int y)
{
    return 0;
}
int main()
{
    return 0;
}

2.2 头文件 的编译原理

1、编译器在编译的时候首先进行“预处理”,#include “main.h”  是预处理代码编译器在预处理的时候会将.c 中所包含的头文件中的内容拷贝到.c 文件中
2、为什么 #include <stdio.h>  用 <>, 而#include “main.h”用“”呢?
首先编译器在编译的时候,如果为 ” ” ,编译器会先检索(查找)本工程目录下””内对应的文件,如果该文件存在,继续检索“系统的头文件目录(标准C安装的目录)”,如果是<>,编译器只会去 系统的头文件目录 下检索<>内对应的文件。
疑问:是不是讲#include stdio.h 写成 #include "stdio.h"编译的时候也能找到stdio.h?是的!!
但是这样会增加编译的时间!!!

2.3 头文件中包含其他的头文件

头文件1:a.h

#define PI 3.14
//结构体定义
typedef struct student
{
    char name[32];
    int age;
}Stu;

头文件2:b.h

#include "a.h" //包含头文件a.h
#define max(a, b) (a)>(b)?(a):(b)

2.4 多个头文件的包含问题

1、在上一节中,头文件b.h中包含了头文件a.h, 假如在b.h中我们也定义一个结构体Stu,然后再main.c中包含头文件b.h,将会发生什么情况呢?
头文件1:a.h

#define PI 3.14
//结构体定义
typedef struct student
{
    char name[32];
    int age;
}Stu;

头文件2:b.h

#include "a.h" //包含头文件a.h
#define max(a, b) (a)>(b)?(a):(b)
//结构体定义
typedef struct student
{
    char name[32];
    int age;
}Stu;

源文件:main.c

#include <stdio.h>
#include "b.h"
int main()
{
    return 0;
}

对main.c 进行编译后报错:

报错显示 struct student这个结构体被重复定义了?这是为什么呢?
因为在编译main.c时,因为main.c中包含了头文件b.h, 就会将b.h中的内容进行展开,而b.h中包含了a.h,所以在b.h中还会将a.h中的内容展开,这样b.h中的内容就变成了:

#define PI 3.14
//结构体定义
typedef struct student
{
    char name[32];
    int age;
}Stu;

#define max(a, b) (a)>(b)?(a):(b)
#define max(a, b) (a)>(b)?(a):(b)
//结构体定义
typedef struct student
{
    char name[32];
    int age;
}Stu;

显然结构体struct student 被重复定义了。
2、如果b.h中没有包含头文件a.h,但是在main.c中同时包含了a.h 和 b.h 也会出现这种编译报错

#include "a.h"
#include "b.h"

3、解决方法如下:

#ifndef A_H
#define A_H
//头文件中的内容
#endif

注意:A_H只是一个标识而已,一般使用头文件名字的大写形式,这样所有的头文件中该标识名不会冲突
原理:在头文件中使用这种方法可以避免头文件被重复包含造成的编译报错(大部分时候会报错:类型冲突)
为什么可以做到呢?编译器在编译(预处理阶段)的时候,假如第一次包含该头文件,定义“标识”并且将该头文件中的内容拷贝至源文件中,第二次再包含该头文件的的时候,因为”标识”被定义了该头文件中的内容不会再次被拷贝。

三、多个源文件

1、在实际开发过程中有如下需求:
假如A.c 文件文件中实现了函数void func();
如果需要在B.c中调用A.c中的函数func,需要在B.c中加入一条函数的声明语句: extern void func(); 当然我们也经常将函数的声明语句放到头文件中
A.c :

void func()
{
    printf("hello world\n");
}

A.h:

#ifndef A_H
#define A_H
extern void func();
#endif

B.c:

#include "A.h"
extern void func(); //如果包含了A.h 该声明可以省略
void func2()
{
    func(); //调用A.c 中的函数func
}

练习:
分别用4个.c 文件实现四个函数

void info_save(STU *stus, int *num);  //信息录入
void info_check(STU *stus, int num)   //信息的查询
void info_modify(STU *stus, int num)  //信息的修改
void sort(STU *stus, int num)

// 将stus所指向的空间中的结构体按照学生的分数进行升序排序
定义一个头文件,在头文件中定义一个结构体

四、static 的使用

4.1 static修饰局部变量

1、局部变量的作用域:在定义的{ }内
2、局部变量的声明周期:随{ }的结束而结束
3、static修饰的局部变量:

  • 作用域:在定义的{ }内
  • 声明周期:随进程(程序)的结束而结束
int func()
{
    int cnt1 = 0;
    static int cnt2 = 0;
    cnt1++;
    cnt2++;
    printf("cnt1: %d, cnt2: %d\n", cnt1, cnt2);
}

int main()
{
    int i;
    for (i = 0; i < 4; i++)
        func();
    return 0;
}

运行结果:每次调用函数func,cnt1的值都是1,cnt2的值分别为1,2,3,4
原理:

  • 普通局部变量在函数定义时被分配在函数栈上,函数栈在函数调用结束后被系统自动回收,所以下一次调用函数时,普通局部变量会被重新定义和赋值!!
  • 被static修饰的局部变量定义在静态存储区中,不会随函数的调用结束被系统回收
  • 没有初始化的局部变量会被自动初始化为0

4.2 static修饰全局变量

1、普通全局变量的作用域:整个进程(程序),例如在A.c 中定义的全局变量int cnt; 在B.c 进行声明 extern int cnt ;就可以被访问到了。
2、如果全局变量 不想被其他文件访问到(只能在定义的文件内被访问到),可以使用static修饰该全局变量。
A.c:

static int cnt = 10;

B.c:

extern int cnt;
void func()
{
    cnt++; //错误
}

2、思考:什么时候全局变量需要使用static修饰不被其他源文件访问到呢?

4.3 static修饰函数

1、普通函数的作用域:整个进程(程序),例如在A.c 中定义的函数 void func(int , int );在B.c 进行声明 extern void func(int , int ) ;就可以被访问到了。
2、如果A.c 中的函数func 不想被其他文件访问到(只能在定义的文件内被访问到),可以使用static 修饰该函数。
A.c:

static void func(int x, int y)
{
    printf("A.c: func\n");
}

B.c:

extern void func(int , int );
void func2()
{
    func(0, 1); //错误的
}

原理:
因为A.c中的func函数被static修饰了,该函数的作用域就只在A.c文件内了,所以其他文件不能访问到A.c中的func函数
其他文件中可以定义函数函数名也可以叫做func,例如在B.c中定义函数 void func(int , char);那么在B.c中调用的函数就是B.c文件中的,而不是A.c中。
思考:什么时候需要将函数使用static修饰呢?

4.4 static使用总结

1、改变作用域
static修饰的全局变量和函数,将其作用域由原来的整个进程变成所在的文件中。
2、改变生命周期
static修饰的局部变量,将其生命周期由原来的随{ }的结束而结束变成随进程的结束而结束。

五、const的使用

1、被const修饰的对象是只读的
2、const常见笔试题
(a)const int * p; 该语句表示指向整形常量 的指针,它指向的值不能修改
(b)int const * p; 该语句与b的含义相同,表示指向整形常量 的指针,它指向的值不能修改
(c)int * const p; 该语句表示指向整形的常量指针 ,它不能在指向别的变量,但指向(变量)的
值可以修改

(d)const int *const p; 该语句表示指向整形常量 的常量指针 。它既不能再指向别的常量,指向
的值也不能修改

(e)int const *const p; 该语句与d的含义相同,表示指向整形常量 的常量指针 。它既不能再指向别的常量,指向的值也不能修改

六、const和define的区别

  1. 就起作用的阶段而言: #define是在编译的预处理阶段起作用,而const是在 编译、运行的时候起作用。
  2. 就起作用的方式而言: #define只是简单的文本替换,没有类型检查。而const有对应的数据类型,是要进行判断的,可以避免一些低级的错误(针对C++)。
  3. 就存储方式而言:#define只是进行展开,有多少地方使用,就替换多少次,它定义的宏常量在内存中有若干个备份;const定义的只读变量在程序运行过程中只有一份备份
  4. 从代码调试的方便程度而言: const常量可以进行调试的,define是不能进行调试的,因为在预编译阶段就已经替换掉了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/681423.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

离散数学大作业任务书

目 录 实际的练习题目、系统的总功能和各子模块的功能………………………………………………………………………………1 1.1题目及问题描述………………………………………………………………1 1.2功能概述………………………………………………………………………1 1.3技…

02 | 日志系统:一条SQL更新语句是如何执行的?

以下内容出自《MySQL 实战 45 讲》 02 | 日志系统&#xff1a;一条SQL更新语句是如何执行的&#xff1f; 查询语句的那套流程&#xff0c;更新语句也会走一遍。 更新流程中和查询不一样的是&#xff0c;更新流程中涉及了两个重要的日志模块。redo log (重做日志) 和 binglog&a…

如何编写用于Neo-Hookean材料的Abaqus VUMAT Fortran子例程

引言 大家好&#xff0c;我是一个热爱编程、研究有限元分析的普通程序员。我非常感谢你们能够抽出宝贵的时间来阅读我的文章&#xff0c;你们的支持是我前行的动力。今天&#xff0c;我们将讨论一个非常专业的话题&#xff0c;即如何编写用于Neo-Hookean材料的Abaqus VUMAT Fo…

Unreal 5 实现UI制作

这一篇讲解一下unreal engine里面的内置ui插件UMG&#xff0c;虚幻示意图形界面设计器&#xff08;Unreal Motion Graphics UI Designer&#xff09;(UMG) 是虚幻引擎内置的一套ui制作工具&#xff0c;通过它我们能够实现平面ui&#xff0c;场景hud内容 实现背景图片填充整个…

【MySQL数据管理】:插入、修改、删除操作

前言 ✨欢迎来到小K的MySQL专栏&#xff0c;本节将为大家带来MySQL数据插入、修改、删除的讲解✨ 目录 前言一、插入数据二、修改数据三、删除数据四、总结 一、插入数据 使用INSERT INTO语句来向表中插入数据 ✨语法&#xff1a; 给指定字段添加数据 INSERT INTO 表名 (字段…

ctfshow web入门 php特性web98-102

1.web98 get会被post方式覆盖&#xff0c;传入的参数需要等于flag&#xff0c;才能读取到flag值,如果直接传http_flagflag,返回的结果会是一个空数组&#xff0c;因为get变量被覆盖了&#xff0c;而post没有传参 payload: get 11 post HTTP_FLAGflag 2.web99 array_push在数组…

机器视觉初步8:特征提取专题

文章目录 1.角点检测2.纹理特征提取3.特征描述符匹配3.1 Harris角点描述符3.2 SIFT&#xff08;尺度不变特征变换&#xff09;描述符3.3 SURF&#xff08;加速稳健特征&#xff09;描述符 4.基于深度学习的特征提取 在机器视觉中&#xff0c;特征提取是从目标图像中提取有用的视…

C语言:打印菱形(输入菱形上半部分行数)

题目&#xff1a; 用C语言在屏幕上输入以下图案&#xff1a; 思路&#xff1a; 总体思路&#xff1a; &#xff08;一&#xff09;. 输入菱形上半部分行数 -- scanf()函数 &#xff08;二&#xff09;. 使用 for循环 进行 菱形上半部分三角形 的打印&#xff0c; 菱形上半部分…

基于5G网络的视频远程操控应用实践——低延迟视频技术及应用

本次分享将分为三个部分&#xff1a;第一部分介绍低延迟视频所涉及到的关键技术&#xff0c;包括低延迟视频编解码、视频传输、视频处理低延时框架、视频采集和显示&#xff1b;第二部分重点介绍5G环境下低延迟视频对抗弱网提出的要求&#xff0c;包括&#xff1a;弱网状态的探…

Vulcanexus-一体化ROS2工具集

Vulcanexus机器人操作系统ROS2一站式工具集&#xff08;GalacticHumble&#xff09;2022 Humble Hierro v2 x86_64 arm64 Ubuntu Jammy (22.04) Vulcanexus是ROS 2的一站式工具集&#xff0c;用于构建机器人应用程序。它固定了DDS中间件&#xff0c;使用了Fast DDS&#xff…

word文件未保存 如何恢复

问题 word文件未保存 如何恢复 详细问题 笔者关闭已编辑完成的word文件&#xff0c;误触不保存&#xff0c;再次打开文件恢复至编辑前的状态&#xff0c;如何恢复至编辑完成后的状态 解决方案 文件 → \rightarrow →打开 → \rightarrow →恢复未保存的文件 或 1、打开 …

Spring关于@Configuration配置处理流程解析

Configuration配置处理流程解析 AnnotationConfigApplicationContext基于注解配置ApplicationContext启动刷新流程Spring关于Configuration解析处理流程那些年被忽略问题 AnnotationConfigApplicationContext基于注解配置 Spring通过上下文应用AnnotationConfigApplicationCon…

10- c语言复合数据类型 (C语言)

一 结构体 1.1 引入 1、在自然界中 任何一个物体&#xff0c;都有多个属性&#xff0c;如果用计算机语言来描述的话&#xff0c;一个属性也许可以用某一个基本数据类型来表示&#xff0c;但是当有多个属性的时候&#xff0c;一个基本数据类型就不能表示了。例如&#xff1a;学…

ML算法——线代预备知识随笔【机器学习】

文章目录 数学预备知识3、线性代数3.1、矩阵奇异值分解&#xff08;SVD&#xff09;3.2、广义逆矩阵&#xff08;Moore-Penrose &#xff09;3.3、数据白化&#xff08;Data Whitening&#xff09;3.4、向量导数 4、其它 数学预备知识 3、线性代数 3.1、矩阵奇异值分解&#…

最小化暗数据风险的 5 个步骤

超过一半的公司数据存储库包含哪些内容&#xff0c;但大多数人甚至不知道自己拥有什么&#xff1f;这是暗数据&#xff0c;是公司在不知不觉中收集的信息&#xff0c;它们不是日常业务交互的组成部分&#xff0c;因此通常位于后台。 虽然这些数据对于大多数公司来说似乎是不必…

C语言——数据的输入输出

数据的输入输出 前言&#xff1a;一、格式输入输出函数1.格式输出函数printf&#xff08;&#xff09;2.格式输入函数scanf&#xff08;&#xff09; 二、字符输入输出函数1.字符输出函数putchar&#xff08;&#xff09;2.字符输入函数getchar&#xff08;&#xff09; 三、字…

JUC并发工具类--阻塞队列BlockingQueue

JUC并发工具类--阻塞队列BlockingQueue 队列队列&#xff08;Queue接口&#xff09;提供的方法 阻塞队列阻塞队列&#xff08;BlockingQueue接口&#xff09;提供的方法应用场景JUC包下的阻塞队列如何选择适合的阻塞队列选择策略线程池对于阻塞队列的选择 队列 是限定在一端进…

Servlet技术实现服务端,Android平台作为客户端,实现一个个人店铺

背景&#xff1a; 使用Servlet技术实现服务端&#xff0c;使用Android平台作为客户端&#xff0c;实现一个个人店铺&#xff0c;店铺商品不限。功能要求如下&#xff1a; 1. 提供登录、注册功能&#xff1b;&#xff08;10分&#xff09; 2. 首页面包括“商品列表”子页面、“…

ATA-8000系列射频功率放大器——在生物医学中的应用

ATA-8000系列射频功率放大器——在生物医学研究中的应用 ATA-8000系列是一款射频功率放大器。其P1dB输出功率500W&#xff0c;饱和输出功率最大1000W。增益数控可调&#xff0c;一键保存设置&#xff0c;提供了方便简洁的操作选择&#xff0c;可与主流的信号发生器配套使用&…

VulnHub靶机渗透:SKYTOWER: 1

SKYTOWER: 1 靶机环境介绍nmap扫描端口扫描服务扫描漏洞扫描总结 80端口目录爆破 3128端口获取立足点获取立足点2提权总结 靶机环境介绍 https://www.vulnhub.com/entry/skytower-1,96/ 靶机IP&#xff1a;192.168.56.101 kali IP&#xff1a;192.168.56.102 nmap扫描 端口扫…