#全局变量和局部变量
##1.变量作用域基本概念
– 变量作用域:变量的可用范围
– 按照作用域的不同,变量可以分为:局部变量和全局变量
##2.局部变量
– 局部变量:
+定义在函数内部的变量以及函数的形参称为局部变量
+作用域:从定义哪一行开始直到与其所在的代码块结束
+生命周期:从程序运行到定义哪一行开始分配存储空间到程序离开该变量所在的作用域
– 特点:
+1、相同作用域内不可以定义同名变量
+2、不同作用范围可以定义同名变量,内部作用域的变量会覆盖外部作用域的变量
##3.全局变量
– 全局变量:
+定义在函数外边的变量称为全局变量
+作用域范围:从定义哪行开始直到文件结尾
+生命周期:程序一启动就会分配存储空间,直到程序结束
+存储位置:静态存储区
– 特点:
+多个同名的全局变量指向同一块存储空间
—
##4.练习
“`
int a = 10;
int b, c = 20;
int sum(int v1, int v2)
{
returnv1 + v2;
}
void test()
{
int num;
num = 0;
a++;
num++;
printf(“a = %d, num = %d\n”, a, num);
}
int main(int argc, const char * argv[])
{
int value = 20;
test();
test();
test();
test();
return 0;
}
# 预处理指令基本概念
##1.预处理指令的概念
– C语言在对源程序进行编译之前,会先对一些特殊的预处理指令作解释(比如之前使用的#include文件包含指令),产生一个新的源程序(这个过程称为编译预处理),之后再进行通常的编译
– 为了区分预处理指令和一般的C语句,所有预处理指令都以符号“#”开头,并且结尾不用分号
– 预处理指令可以出现在程序的任何位置,它的作用范围是从它出现的位置到文件尾。习惯上我们尽可能将预处理指令写在源程序开头,这种情况下,它的作用范围就是整个源程序文件
– C语言提供了多种预处理功能,“`如宏定义、文件包含、条件编译等“`。合理地使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。本章介绍常用的几种预处理功能
# 宏定义
##1.宏的概念及定义方法
– 被定义为“宏”的标识符称为“宏名”。在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”。
– 宏定义是由源程序中的宏定义命令完成的。宏代换是由预处理程序自动完成的。在C语言中,“`“宏”分为有参数和无参数两种“`。本节讨论无参数宏。
##2.不带参数的宏定义
– 格式:
#define 标识符 字符串
“`
+其中的“#”表示这是一条预处理命令。凡是以“#”开头的均为预处理命令。“define”为宏定义命令。“标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。
“`
#include <stdio.h>
// 源程序中所有的宏名PI在编译预处理的时候都会被3.14所代替
#define PI 3.14
// 根据圆的半径计radius算周长
float girth(float radius) {
return 2 * PI *radius;
}
int main ()
{
float g = girth(2);
printf(“周长为:%f”, g);
return 0;
}
“`
– 注意点:
+1) 宏名一般用大写字母,以便与变量名区别开来,但用小写也没有语法错误
+2)对程序中用双引号扩起来的字符串内的字符,不进行宏的替换操作
“`
#define R 10
intmain ()
{
char *s = “Radio”; // 在第1行定义了一个叫R的宏,但是第4行中”Radio”里面的’R’并不会被替换成10
return 0;
}
“`
+ 3)在编译预处理用字符串替换宏名时,不作语法检查,只是简单的字符串替换。只有在编译的时候才对已经展开宏名的源程序进行语法检查
“`
#define I 100
intmain ()
{
int i[3] = I;
return 0;
}
“`
+4) 宏名的有效范围是从定义位置到文件结束。如果需要终止宏定义的作用域,可以用#undef命令
“`
#define PI 3.14
int main ()
{
printf(“%f”, PI);
return 0;
}
#undef PI
void test()
{
printf(“%f”, PI); // 不能使用
}
“`
+5) 定义一个宏时可以引用已经定义的宏名
“`
#define R 3.0
#define PI 3.14
#define L 2*PI*R
#define S PI*R*R
“`
+6) 可用宏定义表示数据类型,使书写方便。
“`
#define String char *
int main(int argc, const char * argv[])
{
String str = “This is a string!”;
return 0;
}
“`
##2.带参数的宏定义
– C语言允许宏带有参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。“`对带参数的宏,在调用中,不仅要宏展开,而且要用实参去代换形参。“`
– 格式:
“`
#define 宏名(形参表) 字符串
“`
– 示例
“`
// 第1行中定义了一个带有2个参数的宏average,
#define average(a, b) (a+b)/2
int main ()
{
// 第4行其实会被替换成:inta = (10 + 4)/2;,
int a = average(10, 4);
// 输出结果为:7是不是感觉这个宏有点像函数呢?
printf(“平均值:%d”, a);
return 0;
}
“`
– 注意点:
+1)宏名和参数列表之间不能有空格,否则空格后面的所有字符串都作为替换的字符串.
“`
#define average (a, b) (a+b)/2
intmain ()
{
int a = average(10, 4);
return 0;
}
注意第1行的宏定义,宏名average跟(a, b)之间是有空格的,于是,第5行就变成了这样:
int a = (a, b) (a+b)/2(10, 4);
这个肯定是编译不通过的
“`
+2)带参数的宏在展开时,只作简单的字符和参数的替换,不进行任何计算操作。所以在定义宏时,一般用一个小括号括住字符串的参数。
“`
#include <stdio.h>
// 下面定义一个宏D(a),作用是返回a的2倍数值:
#define D(a) 2*a
// 如果定义宏的时候不用小括号括住参数
intmain ()
{
// 将被替换成int b =2*3+4;,输出结果10,如果定义宏的时候用小括号括住参数,把上面的第3行改成:#define D(a) 2*(a),注意右边的a是有括号的,第7行将被替换成int b = 2*(3+4);,输出结果14
int b = D(3+4);
printf(“%d”, b);
return 0;
}
+计算结果最好也用括号括起来
#include <stdio.h>
// 下面定义一个宏P(a),作用是返回a的平方
#define Pow(a) (a) * (a) // 如果不用小括号括住计算结果
int main(int argc, const char *argv[]) {
// 代码被替换为:int b = (10) *(10) / (2) * (2);
// 简化之后:int b = 10 * (10/ 2) * 2;,最后变量b为:100
int b = Pow(10) / Pow(2);
printf(“%d”, b);
return 0;
}
“`
#include <stdio.h>
// 计算结果用括号括起来
#define Pow(a) ( (a) * (a) )
int main(int argc, const char *argv[]) {
// 代码被替换为:int b = ( (10)* (10) ) / ( (2) * (2) );
// 简化之后:int b = (10 *10) / (2 *2);,最后输出结果:25
int b = Pow(10) / Pow(2);
printf(“%d”, b);
return 0;
}
# 宏定义与函数以typedef区别
##1.与函数的区别
– 从整个使用过程可以发现,带参数的宏定义,在源程序中出现的形式与函数很像。但是两者是有本质区别的:
+1> 宏定义不涉及存储空间的分配、参数类型匹配、参数传递、返回值问题
+2> 函数调用在程序运行时执行,而宏替换只在编译预处理阶段进行。所以带参数的宏比函数具有更高的执行效率
—
##2.typedef和#define的区别
– 用宏定义表示数据类型和用typedef定义数据说明符的区别。
+宏定义只是简单的字符串替换,“`是在预处理完成的“`
+typedef是在编译时处理的,它不是作简单的代换,“`而是对类型说明符重新命名。“`被命名的标识符具有类型定义说明的功能
“`
typedef char *String;
int main(int argc, const char * argv[])
{
String str = “This is a string!”;
return 0;
}
#define String char *
int main(int argc, const char * argv[])
{
String str = “This is a string!”;
return 0;
}
“`
typedef char *String1; // 给char *起了个别名String1
#define String2 char * // 定义了宏String2
int main(int argc, const char * argv[]) {
/*
只有str1、str2、str3才是指向char类型的指针变量
由于String1就是char *,所以上面的两行代码等于:
char *str1;
char *str2;
*/
String1 str1, str2;
/*
宏定义只是简单替换, 所以相当于
char *str3, str4;
*号只对最近的一个有效, 所以相当于
char *str3;
char str4;
*/
String2 str3, str4;
return 0;
}
# 条件编译基本概念
##1.条件编译基本概念
– 在很多情况下,我们希望程序的其中一部分代码只有在满足一定条件时才进行编译,否则不参与编译(只有参与编译的代码最终才能被执行),这就是条件编译。
– 为什么要使用条件编译
+1)按不同的条件去编译不同的程序部分,因而产生不同的目标代码文件。有利于程序的移植和调试。
+2)条件编译当然也可以用条件语句来实现。但是用条件语句将会对整个源程序进行编译,生成的目标代码程序很长,而采用条件编译,则根据条件只编译其中的程序段1或程序段2,生成的目标程序较短。
—
##2.#if-#else 条件编译指令
– 第一种格式:
“`
#if 常量表达式
..code1…
#else
..code2…
#endif
“`
#define SCORE 67
#if SCORE > 90
printf(“优秀\n”);
#else
printf(“不及格\n”);
#endif
“`
+它的功能是,如常量表达式的值为真(非0),则对code1 进行编译,否则对code2进行编译。因此可以使程序在不同条件下,完成不同的功能。
+注意:“`条件编译后面的条件表达式中不能识别变量,它里面只能识别常量和宏定义“`
– 第二种格式:
“`
#if 条件1
…code1…
#elif 条件2
…code2…
#else
…code3…
#endif
“`
“`
#define SCORE 67
#if SCORE > 90
printf(“优秀\n”);
#elif SCORE > 60
printf(“良好\n”);
#else
printf(“不及格\n”);
#endif
“`
+1>如果条件1成立,那么编译器就会把#if 与 #elif之间的code1代码编译进去(注意:是编译进去,不是执行,很平时用的if-else是不一样的)
+2> 如果条件1不成立、条件2成立,那么编译器就会把#elif 与 #else之间的code2代码编译进去
+3> 如果条件1、2都不成立,那么编译器就会把#else 与 #endif之间的code3编译进去
+4> 注意,条件编译结束后,要在最后面加一个#endif,不然后果很严重(自己思考一下后果)
+5> #if 和 #elif后面的条件一般是判断宏定义而不是判断变量,因为条件编译是在编译之前做的判断,宏定义也是编译之前定义的,而变量是在运行时才产生的、才有使用的意义
##3.#ifdef 条件编译指令
– 格式:
“`
#ifdef 标识符
程序段1
#else
程序段2
#endif
“`
+它的功能是,如果标识符已被#define命令定义过则对程序段1进行编译;否则对程序段2进行编译。如果没有程序段2(它为空),本格式中的#else可以没有,即可以写为:
#ifdef 标识符 程序段
#endif
“`
##4.ifndef 条件编译指令
– 格式:
#ifndef 标识符 程序段1
#else 程序段2
#endif
+与第二种形式的区别是将“ifdef”改为“ifndef”。它的功能是,如果标识符未被#define命令定义过则对程序段1进行编译,否则对程序段2进行编译。这与第二种形式的功能正相反。
##5.使用条件编译指令调试bug
– 应用:可变参数宏,更方便地打印调试信息
“`
#define DEBUG1 1
#if DEBUG1 == 0 //format是格式控制,##表示可以没有参数,__VA_ARGS__表示变量 #defineLog(format,…) printf(format,## __VA_ARGS__)
#else
#define Log(format,…)
#endif
void test(){
Log(“xxxxx”);
}
int main(int argc, const char * argv[]) {
Log(“%d\n”,10);
return 0;
}
“`
+ 这里,‘…’指可变参数。这类宏在被调用时,##表示成零个或多个参数,包括里面的逗号,一直到到右括弧结束为止。当被调用时,在宏体(macro body)中,那些符号序列集合将代替里面的__VA_ARGS__标识符。
# static和extern关键字-对函数的作用
##本小节知识点:
##1.static 与 extern对函数的作用
– 内部函数:只能在本文件中访问的函数
– 外部函数:可以在本文件中以及其他的文件中访问的函数
– 默认情况下所有的函数都是外部函数
– static 作用
+声明一个内部函数
“`
static int sum(int num1,int num2);
“`
+定义一个内部函数
“`
static int sum(int num1,int num2)
{
return num1 + num2;
}
““
– extern作用
+声明一个外部函数
“`
extern int sum(int num1,int num2);
“`
+定义一个外部函数
“`
extern int sum(int num1,int num2)
{
return num1 + num2;
}
# static和extern关键字-对变量的作用
##1.static 与 extern对局部变量的作用
– static对局部变量的作用
+延长局部变量的生命周期,从程序启动到程序退出,但是它并没有改变变量的作用域
+定义变量的代码在整个程序运行期间仅仅会执行一次
– extern用在函数内部
+不是定义局部变量,它用在函数内部是声明一个全局变量
##2.static 全局变量的作用
– 全局变量分类:
+内部变量:只能在本文件中访问的变量
+外部变量:可以在其他文件中访问的变量,默认所有全局变量都是外部变量
– static对全局变量的作用
+声明一个内部变量
“`static int a;“`
+定义一个内部变量
“`static int a = 10;“`
– 由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以 避免在其它源文件中引起错误。
##3.extern对全局变量的作用
– extern作用
+完整声明一个外部变量
“`extern int a;“`
+完整定义一个外部变量
“`extern int a = 10;“`
– 如果声明的时候没有写extern那系统会自动定义这个变量,并将其初始化为0
– 如果声明的时候写extern了,那系统不会自动定义这个变量。
#全局变量和局部变量
##1.变量作用域基本概念
– 变量作用域:变量的可用范围
– 按照作用域的不同,变量可以分为:局部变量和全局变量
—
##2.局部变量
– 局部变量:
+定义在函数内部的变量以及函数的形参称为局部变量
+作用域:从定义哪一行开始直到与其所在的代码块结束
+生命周期:从程序运行到定义哪一行开始分配存储空间到程序离开该变量所在的作用域
– 特点:
+1、相同作用域内不可以定义同名变量
+2、不同作用范围可以定义同名变量,内部作用域的变量会覆盖外部作用域的变量
—
##3.全局变量
– 全局变量:
+定义在函数外边的变量称为全局变量
+作用域范围:从定义哪行开始直到文件结尾
+生命周期:程序一启动就会分配存储空间,直到程序结束
+存储位置:静态存储区
– 特点:
+多个同名的全局变量指向同一块存储空间
—
##4.练习
“`
int a = 10;
int b, c = 20;
int sum(int v1, int v2)
{
return v1 + v2;
}
void test()
{
int num;
num = 0;
a++;
num++;
printf(“a = %d, num = %d\n”, a, num);
}
int main(int argc, const char * argv[])
{
int value = 20;
test();
test();
test();
test();
return 0;
}