一.
程序进行编译时,并不为形式参数分配存储空间。只有在函数被调用时,形式参数才临时的占有存储空间,其过程如下:
1.调用开始,系统为形参开辟一个临时存储区,形参与实参各占有一个独立的存储空间。
2.然后将各实参之值传递给形参,这时形参就得到了实参的值。这种虚实结合方式称为“值结合”
3.函数返回时,临时存储区也被撤销。
要特别注意的是:函数中对形参变量的操作不会影响到调用函数中的实参变量,即形参值不能传回给实参。
二.
任何一个运行中的程序,在内存中都被分成代码区和数据区两大部分,而数据区又被分为静态存储区、自动存储区和动态分配区三部分。
自动存储区是按照栈结构组织的存储区。除了特别声明的之外,局部变量通常被存放在栈区。这些变量在进入所在的块时被创建,所在的块结束时被撤销。当所在的块结束之后,各变量的值不再保留。另外,变量必须由程序员显式的初始化,否则初始值是不确定的。
静态存储区是在程序编译时就分配的存储区。分配在静态存储区中的变量在程序开始执行时被创建并自动初始化(数值变量初始化为0),当程序结束时才被撤销。所以通常称静态变量的生存期是永久的。全局变量就是被分配在静态存储区的。
三.
Register的作用是声明寄存器存储自动变量。这类变量具有局部变量的所有特点。当把一个变量制定为寄存器存储类型时,系统就将它存放在一个寄存器中。通常把使用频率较高的变量(如循环次数较多的循环变量)定义为register类型。
如func(register int a, register int b)
四.
/***file1.c ***/
#include<stdio.h>
int x, y;
char ch;
int main(void)
{
x= 12;
y=24;
f1();
printf("%c", ch);
return 0;
}
/***file1.c ***/
/***file2.c ***/
extern int x, y;
extern char ch;
f1()
{
printf("%d, %d\n", x, y);
ch = 'a';
}
/***file2.c ***/
在file2.c文件中并没有定义x,y,ch,而是通过extern声明x,y,ch是外部变量,因此在file1.c中所定义的变量在file2.c可以引用。所以结果为 12,24, a
五.
在升序设计中,如果要处理一个复杂程序,包含若干文件,而且各文件都要用到一些共用的变量,可以再一个文件中定义所有全局变量,而在其他有关文件中使用extern来声明这些变量即可。
六.
#include<文件夹>
在文件名两侧的是尖括号。按照这种格式定义时,预处理程序只按照系统所规定的标准方式(从编译系统所在的子目录中)检索文件目录。
#include"文件夹"
在文件夹名两侧的是双撇号。按照这种格式,预处理程序首先在当前源文件目录中检索该指定文件;如果没有找到,则按系统所指定的标准方式进行检索。
所以,通常要包含系统所定义的库函数的声明时,应当使用格式1;要包含自定义函数的声明时,应当使用格式2。
七.
使用宏时需要注意的几个问题:
1.在#define命令中,宏名字与字符串(宏体)之间用一个或多个空格相分隔。
2.宏名字不能用引号括起来,如#define "YES" …… printf(""YES"");将不进行宏定义
3.宏名字中不能含有空格。例如想用"A NAME"定义"SMISS",而写成#define A NAME SMISS.则实际所进行的宏定义是A为宏名字,宏体是"NAME SMISS".
4.C语言程序设计人员一般都习惯用大写字母定义宏名字。
5.不能进行宏名字的重定义
6.定义一个宏名字之后,就可以使用这个宏名字了,包括在其后的宏定义中使用。例如:
#define PI 3.1415926,之后我们可以嵌套使用,即#define ABC 2 * PI。
第二种形式:
#define PI 3.1415926
#define ABC return (2 * PI);(最后的分号是return语句的一部分)
在调用ABC时,就不需要加分号了
7.不能进行的宏替换
不可替换作为用户标识符中的成分。例如,在计算圆周长的程序中,不可用“R”替换“XIRCUM”中的“R”
不能替换字符串常量中的成分。如#define ABC 2 * PI,printf("ABC\n");则输出的是ABC,而不是ABC定义的字符串,不可用宏名字替换格式串中的同名字符串。
8.一行中写不完的宏定义,应在前一行结尾用一个续行符“\”且在下一行开始不适用空格。例如#define ALPHABET ABCDEFGHIJKLMN\
OPQRSTUVWXYZ
9.宏定义可以写在源程序中的任何地方,但一定要写在程序中引用该宏之前,通常写在一个文件之首。
八.
用命令#undef可以撤销已定义的宏。如:
#define OK 1
……
#undef OK
在#undef之后的范围,OK不再代表1
九.带参数的宏定义
带参数的宏在形式上很像参数。它也带有形参,调用时也进行实参与形参的结合
带参数的宏定义的格式为
#define 标识符(形参表) 宏体
注意:这时的宏体是一个表达式,它也带有形参,调用时也进行实参与形参的结合
对于一个求平方的宏定义如下:
1.#define SQUARE(x) x*x
2.#define SQUARE(x) (x*x)
3.#define SQUARE(x) (x)*(x)
4.#define SQUARE(x) ((x)*(x))
到底哪个正确呢?我们可以测试一下:
用a=SQUARE(n+1)测试
1.a=n+1*n+1,错误
2.a=(n+1*n+1),错误
3.a=(n+1)*(n+1),正确
4.a=((n+1)*(n+1)),正确
用a=16/SQUARE(2)测试
3.a=16/(2)*(2)=16,显然错误
4.A=16/((2)*(2))=4,结果正确
所以,在有参数的宏定义时,还是把宏体及其形参都用圆括号括起来最正确。
十.
宏定义中有双引号括起来的字符串常量中,含有形参,则在做宏替换时实参不能替换此双引号中的形参。如:
#define ADD(m) printf(“m=%d\n”,m)
用ADD(x+y);语句调用,结果为printf(“m=%d\n”,x+y);
如要解决此问题则可在形参前加一“#”号,变为如下形式:
#define ADD(m) printf(#m“=%d\n”,m)
则调用ADD(x+y);语句后,结果就会变为printf(“x+y=%d\n”,x+y);
十一.
如宏定义包含双“##”号,则宏替换时将双“##”号去掉,并将其前后字符串合在一起。例如:
#define S(a,b) a##b
当调用S(define ,5);语句时,宏展开为define5。
十二.宏与函数都可作为程序模块应用于模块化程序设计中,但其各有特色。
(1)时空效率不同
宏定义时要用宏体替换宏名,往往使程序体积膨胀,加大了系统的存储开销。但是它不像函数调用那样要进行参数传递、保存现场、返回等操作,所以时间效率比函数高。所以,通常对于简短的表达式以及调用频繁、要求快速响应的场合(如实时系统中),采用宏比采用函数更合适。
(2)宏虽然可以带有参数,但宏定义过程不像函数那样要进行参数值的计算、传递及结果返回等操作:宏定义只是简单的字符替换,并不进行计算。因而一些过程是不能用宏来代替函数的,如递归调用。
下面举例来说明不适合使用宏的情况:
#include<stdio.h>
#define square(n) ((n)*(n))
int main(void)
{
int i = 1;
while(i <= 5)
printf("%d\n", square(i++));
return 0;
}
程序的输出结果分别为1,9,25
当i=1时,被替换,执行输出语句输出1,接着i经过2次自增,变为3,输出9,接着i又经过2次自增,变为5,输出25,又经过2次自增,变为7,程序结束。
可以看到,使用带参数的宏,引入了i++的副作用,而函数则不会出现这样的问题。因为在函数中,i++作为实参只出现一次,而在宏定义后i++会出现两次。