C语言

什么是 全局变量 & 宏 & 大程序怎么写,看这一篇就够了

2020-02-13  本文已影响0人  不会编程的程序圆

全局变量

认识 全局变量

例如:

int f(void);

int gAll = 12;

int main(void){
    
    //__func__ 可以打印出当前函数的函数名,下划线一边是两个
    printf("in %s gAll = %d\n", __func__, gAll);
    //全局变量可以直接使用,不需要再声明
    f();
    printf("again in %s gAll = %d\n", __func__, gAll);
    //函数内对全局变量值的改变在 main函数中依然存在
    return 0;
}

int f(void) {
    
    printf("in %s gAll = %d\n", __func__, gAll);
    gAll += 2;
    printf("again in %s gAll = %d\n", __func__, gAll);

    return gAll;
}

输出:

in main gAll = 12
in f gAll = 12
again in f gAll = 14
again in main gAll = 14
全局变量的初始化

注释1:

int gAll = 12;
int g = gAll;//报错

int main(void) {

    return 0;
}

下面这段代码在某些编译器(dev c++)上是可以编译的,但是在 vs 上是不能编译的

const int gAll = 12;
int g = gAll;

int main(void) {

    return 0;
}

但是,这种方式是不推荐的

被隐藏的全局变量
int f(void);

int gAll = 12;

int main(void) {

    printf("in %s gAll = %d\n", __func__, gAll);

    f();

    printf("again in %s gAll = %d\n", __func__, gAll);

    return 0;
}

int f(void) {
    
    int gAll = 2;//仅在这个范围内适用
    printf("in %s gAll = %d\n", __func__, gAll);
    gAll += 2;
    printf("again in %s gAll = %d\n", __func__, gAll);

    return gAll;
}

输出:

in main gAll = 12
in f gAll = 2
again in f gAll = 4
again in main gAll = 12

即使 gAll 在 main 函数中被覆盖,f 函数中的 gAll 也是不会被该改变的

为什么会这样?自己思考一下。

静态本地变量

例:
不用static的情况

int f(void);

int main(void) {

    f();
    f();
    f();

    return 0;
}

int f(void) {

    int All = 1;
    printf("in %s All = %d\n", __func__, All);
    All += 2;
    printf("again in %s All = %d\n", __func__, All);

    return All;
}

输出:

in f All = 1
again in f All = 3
in f All = 1
again in f All = 3
in f All = 1
again in f All = 3

使用static

int f(void);

int main(void) {

    f();
    f();
    f();

    return 0;
}

int f(void) {

    static int All = 1;//只添加 static
    printf("in %s All = %d\n", __func__, All);
    All += 2;
    printf("again in %s All = %d\n", __func__, All);

    return All;
}

输出:

in f All = 1
again in f All = 3
in f All = 3
again in f All = 5
in f All = 5
again in f All = 7

看看地址

int f(void);

int gAll = 12;

int main(void) {

    printf("1 st\n");
    f();
    printf("2 nd\n");
    f();

    return 0;
}

int f(void) {
    
    int a = 0;
    int b = 0;
    static int All = 1;

    printf("&All :  %p\n", &All);
    printf("&gAll:  %p\n", &gAll);
    printf("&a :    %p\n", &a);
    printf("&b :    %p\n", &b);

    return All;
}

输出:

1 st
&All :  00007FF6A9ECC054
&gAll:  00007FF6A9ECC050
&a :    000000E8815CF8B4
&b :    000000E8815CF8D4
2 nd
&All :  00007FF6A9ECC054
&gAll:  00007FF6A9ECC050
&a :    000000E8815CF8B4
&b :    000000E8815CF8D4

全局变量 gAll 与 静态局部变量 All 在内存中相邻

总结

返回指针的函数

请同学们先看一下下面这个程序:

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

int main(void) {

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

    return k;
}

输出:

*p = 12
 k = 24
*p = 24

i 和 k 的内存其实是同一块空间

总结

说了这么多,总结一句话

尽量避免使用 全局变量 和 静态本地变量
???

为什么这里就不深讲了,有兴趣的朋友可以下来自己查查。


编译预处理 与 宏

编译预处理指令

define 关键字

回想我们刚学 double 的时候,是不是计算过圆的面积。当时我们可能是这样写的:

#include<stdio.h>

const double PI = 3.14159;

int main(void) {

    printf("%f\n", 2 * PI * 3.0);
    return 0;
}

现在我们用 宏 就不需要用 const 修饰的全局变量了,我们也说过,全局变量最好不用。

#include<stdio.h>

#define PI 3.14159
//注意:不写分号 不写等于号

int main(void) {

    printf("%f\n", 2 * PI * 3.0);
    return 0;
}

现在,我们打开我们的虚拟机,进入 Linux 系统。


1.创建一个 c文件 2.写一个简单的带宏的 c程序 3.这时后我们成功创建了一个 c文件 4.编译c文件,并保留中间文件

现在多出来了 4 个文件,蓝色的是文件夹,我们不去管它,绿色的是可执行文件,类似 windows 的 .exe 文件
现在我们主要关注这 3 个中间文件

文件详细情况

一个 c文件编译的过程文件变化是这样的:

.c(处理编译预处理指令)-> .i(产生汇编代码)-> .s(汇编生成目标文件) -> .o(链接等) -> a.out

可以看到 .i 文件时很大

我们看看 .i 文件的 结尾部分
对比 .c 文件
我们发现程序中的宏 PI 被换成了它所表示的 数字

这种替换是简单的文本替换,我们再试试其他的替换方式:

替换字符串
FRORMAT 同样被替换了
我们再试试这样,定义宏的时候 不带双引号:
编译器给了 warning
FORMAT并没有被替换
因此可知," "扩起来的字符串 宏 是不会替换的
总结
没有值的宏

这类宏是用来做条件编译的,后面有其他编译预处理指令来检查这个宏是否已经被定义过了。
比如有这个宏执行这部分代码,没有则执行另外一部分

预定义的宏

我们来试着用一下:

int main(void) {

    printf("%s : %d\n", __FILE__, __LINE__);
    printf("%s %s\n", __DATE__, __TIME__);

    return 0;
}

输出:

D:\vscode\练习\12-31\Project1\oj.c : 174
Feb 11 2020 04:12:57

值得注意的是,__LINE__表示的是它自己所在的行数

你们在熟睡,而我还在给你们写教学,关注我/点个赞/转发 不过分吧~

image

带参数的宏

例如:

#define cube(x) ((x) * (x) * (x))

int main(void) {

    printf("%d\n", cube(5));
    return 0;
}

输出:

125

容易犯的错误

一下这两种写法在程序中会不会有问题?

思考一下这个程序会的到你想要的结果吗?

#define ERROR1(x) (x * 57)
#define ERROR2(x) (x) * 57

int main(void) {
    
    printf("%d\n", ERROR(1 + 2));
    printf("%d\n", 300 / ERROR(1));

    return 0;
}

输出:

115
17100

为什么会这样呢?我们不妨来看一下,.i文件内部:

注意运算顺序
定义带参数的宏的原则

所以,上面错误的例子的正确的写法就是:
#define ERROR ( (x) * 57 )

带参数的宏的更多用法

定义宏切记不要加分号

错误示范:

#define PRETTY_PRINT(msg) printf(msg);

int main(void) {

    int n = 0;

    printf("Input an number\n");
    scanf("%d", &n);

    if (n < 10)
        PRETTY_PRINT("less than 10\n");
    else
        PRETTY_PRINT("more than 10\n");

    return 0;
}

VS 会报错 :没有匹配 if 的非法 else,为什么呢?

因为如果你在宏后面加了 ;,你又在 if 内的语句后加了;
这样在.i的阶段,if 后的语句有了两个 ;,即:
PRETTY_PRINT("less than 10\n");;
第二个;表示 一个空语句,这样 else 前面就没有对象可以匹配了

总结

Quiz:
请看下面的代码片段,判断这段程序会输出什么?


#define TOUPPER(c) ('a' <= (c) && (c) <= 'z' ? (c) - 'a' + 'A' : (c))

    int i = 0;
    char s[1000];
    
    strcpy(s, "abcd");
    
    putchar(TOUPPER(s[++i]));

A: B
B: C
C: D
D: E

这道题是需要都脑子的呦!
公众号后台回复:0211 1 查看答案和解析


大程序结构

多个源代码文件

多个源文件.c

引入
回想我们学习的过程,开始是 main()里的代码太长了,我们学习了函数,将其分开
现在如果 一个源文件太长了,我们就可以将其分成几个源文件

怎么让多个源文件联系起来?
在编译器上创建一个项目,将你想操作的 .c 文件放到同一个项目中

头文件 .h

" " 还是 < > ?
#include的误区

为什么不引用 stdlib.h 依然可以使用 malloc ?
这时因为在你调用函数前没有声明函数(引入头文件),编译器回去猜测 参数 和 函数返回类型都为 int
恰好 malloc 的参数 size_tlong int ,返回值是个指针,也可以看作是 16进制的 整型。

为什么?可以参考我的另一篇文章,点击跳转

头文件
不对外公开的 函数&变量

函数&全局变量前加上 static就使得这个 函数/变量 只能在当前文件中被使用

声明

extern

当一个c 文件想调用另一个 c文件中定义的全局变量时
需要在头文件中加上 extern <类型> <变量名> 来声明这个变量

例如:

<1.c>
int gAll = 12;

<2.c>
printf("%d\n", gAll);

<1.h>
extern int gAll;

声明不产生代码

避免重复声明

请看下例:

<1.h>
int a ;

<2.h>
#include"1.h"

<3.h>
#include"1.h" 
//相当于
//int a 

#include"2.h" 
//相当于
//#include"1.h"
//相当于
//int a ;

//可以看到, int a 被重定义的

如何避免上述这种重定义情况?

条件编译和宏
#ifdef _MAIN_H_//先看 这个宏是否定义过,是:继续 不是:跳过这个结构。跳过的意思是编译时将不再向 .i 文件中插入这段代码
#define _MAIN_H_//没有定义,则定义

#endif

这就是我们前面说的预定义的宏的一种使用方法。
应用这种方法我们再看上例

<1.h>
#ifdef _FIRST_H_
#define _FIRST_H_

int a ;

#endif
<2.h>
#include"1.h"

<3.h>
#include"1.h" 
//相当于:
//#ifdef _FIRST_H
//#define _FIRST_H
//int a ;
//#endif
#include"2.h" 
//相当于:
//#include"1.h"
//这时,_FIRST_H 已经被定义,则跳过


以上就是本次的内容,欢迎各位指出我的错误,谢谢!

这篇教程的Github地址:

https://github.com/hairrrrr/C-CrashCourse

Github 大概在 微信公众号更新 1 ~ 2 天后更新,欢迎加入我,让我们一起完成全世界最全的 C 语言教学!

关注我的微信公众号
获取第一时间更新 👇👇👇

image
上一篇 下一篇

猜你喜欢

热点阅读