Lecture 8 程序结构

博主不定期更新【保研/推免、C/C++、5G移动通信、Linux、生活随笔】系列文章,喜欢的朋友【点赞+关注】支持一下吧!


Lecture 8 程序结构

1. 全局变量

1.1 全局变量

全局变量:定义在函数之外的变量全局的生存期和作用域

  • 它们与任何函数都无关
  • 在任何函数内部都可以使用它们

全局变量初始化:

  • 没有初始化的全局变量会得到0值(而本地变量不会)

    • 指针会得到NULL值
  • 只能用编译时刻已知的值来初始化全局变量

  • 它们的初始化发生在main函数之前

全局变量的隐藏:

  • 如果函数内部存在与全局变量同名的变量,则全局变量被隐藏

小常识:

printf("in %s all=%d\n", _func_, all); //此语句中,_func_表示的是函数的名字,用%s输出

1.2 静态本地变量

静态本地变量:能在函数结束后继续保有原值的本地变量

  • 在本地变量定义时加上static修饰符就成为静态本地变量
  • 当函数离开时,静态本地变量会继续存在并保持其值
  • 静态本地变量的**初始化只会在第一次进入这个函数时做**,以后进入函数时会保持上次离开时的值

静态本地变量与全局变量:

  • 静态本地变量实际上是特殊的全局变量
  • 它们位于相同的内存区域
  • 静态本地变量具有全局的生存期,函数内的局部作用域
    • static在这里的意思是局部作用域(本地可访问)

1.3 后记

返回指针的函数:

  • 返回本地变量的地址是危险的(因为函数调用结束后,该地址可能会分配给其他变量)例如:
#include <stdio.h>

int* f(void);
void g(void);

int main()
{
	int *p = f();
	printf("*p=%d\n", *p);
	g(); 
	printf("*p=%d\n", *p);
	
	return 0;
}

int* f(void)
{
	int i=12;
	return &i;
}

void g(void)
{
	int k=24;
	printf("k=%d\n", k);
}

运行结果:
[Warning] function returns address of local variable
*p=12
k=24
*p=24
  • 返回全局变量或静态本地变量的地址是安全的
  • 返回在函数内malloc的内存是安全的,但是容易造成问题
  • 最好的做法是返回传入的指针

tips:

  • 不要使用全局变量在函数间传递参数和结果
  • 尽量避免使用全局变量
  • 使用全局变量和静态本地变量的函数是线程不安全的

2. 编译预处理和宏

编译预处理指令

  • #开头的是编译预处理指令
  • 它们不是C语言的成分,但是C语言程序离不开它们
  • #define用来定义一个宏

2.1 宏定义

#define <名字> <值>

  • 注意没有结尾的分号,因为不是C的语句
  • 名字必须是一个单词,值可以是各种东西
  • 在C语言的编译器开始编译之前,编译预处理程序(cpp)会把程序中的名字换成值(完全的文本替换)
  • gcc中,使用–save-temps可以保存编译过程中的临时文件

  • 如果一个宏的值中有其他的宏的名字,也是会被替换的
  • 如果一个宏的值超过一行,除最后一行外其他行末都需要加\
  • 宏的值后面出现的注释不会被当做宏的值的一部分

没有值的宏

  • #define _DEBUG
  • 这类宏是用于条件编译的,后面有其他的编译预处理指令来检查这个宏是否已经被定义过了

预定义的宏

  • _LINE_ //当前所在的行号 整数
  • _FILE_ //文件名 字符串
  • _DATE_ //编译时的日期 字符串
  • _TIME_ //编译时的时间 字符串
  • _STDC_ //如果今前编泽器符合ISO标准,那么该宏的值为1,否则未定义

2.2 带参数的宏

像函数的宏

  • #define cube(x) ((x)*(x)*(x))
  • 宏可以带参数

错误定义的宏

  • #define RADTODEGZ(X) (x*57.29578)//弧度转换成角度
  • #define RADTODEGZ(X) (x)*57.29578

带参数的宏的原则

  • 一切都要括号(整个值要括号,参数出现的每个地方都要括号
  • 正确定义#define RADTODEGZ(X) ((x)*57.29578)

可以带多个参数

  • #define MIN(a,b) ((a)>(b)?(b):(a))
  • 也可以组合(嵌套)使用其他宏

带参数的宏

  • 在大型程序的代码中使用非常普遍
  • 可以非常复杂,如“产生”函数(在#和##这两个运算符的帮助下)
  • 存在中西方文化差异(中方用得少,西方用得多)
  • 部分宏会被inline函数替代

3. 大程序结构

3.1 多个源代码文件

多个.c文件

  • main()里的代码太长了适合分成几个函数
  • 一个源代码文件太长了适合分成几个文件
  • 两个独立的源代码文件不能编译形成可执行的程序

项目

  • 在Dev C++中新建一个项目,然后把几个源代码文件加入进去
  • 对于项目,Dev C++的编译会把一个项目中的所有源代码文件都编译后,链接起来
  • 有的IDE(例如Visual Studio)有分开的编译(compile)和构建(build)两个按钮,前者是对单个源代码文件编译,后者是对整个项目做链接

编译单元

  • 一个.c文件是一个编译单元
  • 编译器每次编译只处理一个编译单元

3.2 头文件

函数原型放到一个头文件(以.h结尾)中,在需要调用这个函数的源代码文件(.c文件)中#include这个头文件,就能让编译器在编译的时候知道函数的原型

#include

  • #include是一个编译预处理指令,和宏一样,在编译之前就处理了
  • 它把那个文件的全部文本内容原封不动地插入到它所在的地方
  • 所以也不是一定要在.c文件的最前面#include

""还是<>

  • #include有两种形式来指出要插入的文件

    • ""要求编译器首先在当前目录(.c文件所在的目录)寻找这个文件,如果没有,到编译器指定的目录去找
    • <>让编译器只在指定的目录去找
    • 自己的头文件用"",标准库中的头文件用<>
  • 编译器自己知道自己的标准库的头文件在哪里

  • 环境变量和编译器命令行参数也可以指定寻找头文件的目录

#include的误区

  • #include不是用来引入库的,而是用来插入文本内容的(原封不动)
  • stdio.h里只有printf的原型,printf的代码在另外的地方,某个.lib(Windows)或.a(Unix)中
  • 现在的C语言编译器默认会引入所有的标准库
  • #include <stdio.h>只是为了让编译器知道printf函数的原型,保证你调用时给出的参数值是正确的类型

头文件tips

  • 在使用和定义这个函数的地方都应该#include这个头文件
  • 一般的做法是任何.c都有对应的同名的.h(除了main.c),把所有的对外公开的函数的原型和全局变量的声明都放进去

不对外公开的函数

  • 函数前面加上static就使得它成为只能在所在的编译单元中被使用的函数
  • 全局变量前面加上static就使得它成为只能在所在的编译单元中被使用的全局变量

3.3 声明

全局变量的声明

  • int i;是变量的定义
  • extern int i;是变量的声明
  • 函数的定义是定义,函数的原型是声明
  • 变量声明时不能初始化

声明和定义

  • 声明是(使编译器)不产生代码的东西

    • 函数原型
    • 变量声明
    • 结构声明
    • 宏声明
    • 枚举声明
    • 类型声明
    • inline函数
  • 定义是(使编译器)产生代码的东西

    • 函数
    • 全局变量

头文件规则

  • 只有声明可以被放在头文件中(是规则而非法律)
  • 否则会造成一个项目中多个编译单元里有重名的实体
  • 某些编译器允许几个编译单元中存在同名的函数,或者用weak修饰符来强调这种存在

重复声明

  • 同一个编译单元里,同名的结构不能被重复声明
  • 如果你的头文件里有结构的声明,很难这个头文件不会在一个编译单元里被#include多次(当项目很复杂,有多个.c、.h文件相互#include时)
  • 所以需要==“标准头文件结构”==
#ifndef _LIST_H_ //条件编译
#define _LIST_H_
//如果没有定义这个宏,就定义这个宏
#include "node.h"

typedef struct _list {
    Node* head;
    Node* tail;
} List;
    
#endif

运用条件编译和宏,保证这个头文件在一个编译单元中只会被#include一次;
#pragma once也能起到相同的作用(如VS中),但是不是所有编译器都支持。
头文件结构”==

```c
#ifndef _LIST_H_ //条件编译
#define _LIST_H_
//如果没有定义这个宏,就定义这个宏
#include "node.h"

typedef struct _list {
    Node* head;
    Node* tail;
} List;
    
#endif

运用条件编译和宏,保证这个头文件在一个编译单元中只会被#include一次;
#pragma once也能起到相同的作用(如VS中),但是不是所有编译器都支持。
发布了62 篇原创文章 · 获赞 109 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_43871127/article/details/104472853