数据类型符 函数名(形式参数名声明列表)
{
函数体
}
注意:
函数是独立的功能模块, 在函数定义时, 函数体内绝对不能有其他函数的定义, 但可以有其他函数的调用.
函数调用作为运算数进而构成表达式,c = add(add(a,b),b);这类函数调用涉及的函数是具有返回值的函数,函数返回值的数据类型一般属于基本数据类型, 但绝不能是void类型
函数声明不同于函数定义, 函数声明只是在编译时告知系统被调函数在运行中将要调用声明中的函数, 确保对被调函数返回值的处理. 函数声明没有函数体
当一个函数被多个主调函数调用时, 各主调函数都要对同一个被调函数进行多次函数声明(这里指函数仅在内部声明).
函数定义是明确函数功能, 在同一个程序中, 不能有同名函数的多个函数定义, 即在源文件中, 函数定义必须唯一, 但函数声明可以不唯一.
调用函数声明可以在主调函数内, 也可以在主调函数外, 这两种形式的声明其被调函数的有效范围不同.
主调函数内声明的被调函数只限于该主调函数内有效, 而且这些主调函数内无须再对该被调函数进行声明
为了保证程序的可读性, 将所有的被调函数在函数外声明, 并放在头文件处, 其有效范围就是整个文件的所有主调函数.
在函数定义时指明函数参数和函数返回
函数调用时, 主调函数把数据传递给被调函数, 被调函数处理完数据后, 把处理结果返回给主调函数
在函数调用中, 紧跟函数名后的括号内所带的参数。实参可以是常量、变量、表达式。
float x, y, z;
x=2;
y=3;
z=add(x,y);//x,y为实际参数
在定义函数时, 紧跟函数名后的括号内的参数, 表明在函数调用时, 所需特定数据类型的形参和形参个数。
void multi(int a, int b)//a,b为形式参数
{
int c;
a=a+1;
b=b+1;
printf(“a=%d, b=%d\n”, a, b);
c=a*b;
printf(“%d*%d=%d\n”, a, b, c);
}
结构体变量、共用体变量、枚举变量与基本类型变量一样, 可作为函数参数, 并具有与形参和实参相同的结合方式.
数组也可以作为函数参数, 数组作为函数参数的形式有两种,数组元素作为函数参数和数组名作为函数参数.
return 语句结束被调函数的执行, 返回主调函数。在结束被调函数时, 可以将返回值给主调函数。
int max(int x, int y)
{
if(x>y) return x; //x>y, 则返回x, 后面的语句不执行
else return y;
}
int max(float x, float y)
{
float z;
z=x>y?x:y;
return z; //z定义时为浮点型, 但返回int型
}
若函数中没有return语句, 则当调用该函数后, 得到一个已指明返回值类型的随机值, 并不是不返回值.
int max(float x, float y)
{
float z;
z=x>y?x:y;
}
在定义函数时, 指明函数返回值的数据类型为空类型, 该调用函数就不能得到任何值.
void print_value(float w)
{
printf(“%5.3f\n”, w);
return;
}
max(float x, float y)
{
return(x>y?x:y); //结果为浮点型, 但返回整型
}
static 数据类型符 函数名(形参列表)
{
函数体
}
用static声明
函数只能被本文件的其他函数调用, 而不能被其他文件的函数调用, 即内部函数的最大有效范围也不能超过本文件中的函数调用.
extern 数据类型符 函数名(形参列表) //extern可以省略
{
函数体
}
函数不仅可以被本文件的其他函数调用, 而且也可以被其他文件中的函数调用, 即外部函数的有效范围可以是所有文件的函数调用.
文件包含虽然可以连接不同文件, 但是只能对源程序文件进行链接(如上,只对file1进行连接)
文件包含后只生成一个目标文件, 并没有生成被包含文件的目标文件
文件包含预处理后只有一个源程序文件, 因此所有文件中的函数都是内部函数, 没有外部函数. 即内部函数与外部函数只是对后续各文件独立编译后链接而言的.
无论有多少个文件进行连接,最多只能有一个主函数
在C语言中,每个独立的源文件(.c文件)都可以包含一个主函数(main函数)。在连接(linking)多个源文件时,确保只有一个主函数的方法是通过使用编译和链接两个步骤,并在链接阶段指定一个入口点。
具体来说,将每个源文件编译为单独的目标文件(.o文件),然后将这些目标文件链接在一起生成最终的可执行文件。在链接阶段,可以通过指定一个入口点来明确指定程序的起始位置,只有这个入口点所在的源文件中的主函数将成为可执行文件的主函数。
以下是一个示例:
1.假设有两个源文件file1.c
和file2.c
,它们都包含了主函数main
file1.c:
#include
int main()
{
printf("This is the main function in file1.c\n");
return 0;
}
file2.c:
#include
int main()
{
printf("This is the main function in file2.c\n");
return 0;
}
2.在命令行中,使用编译器(如gcc)将每个源文件编译为目标文件:
gcc -c file1.c -o file1.o
gcc -c file2.c -o file2.o
3.在链接阶段,使用编译器指定入口点:
gcc file1.o file2.o -o myprogram
在这个例子中,我们通过使用gcc
编译器将file1.c
和file2.c
分别编译为file1.o
和file2.o
两个目标文件。然后,在链接阶段,通过指定file1.o
作为入口点来决定哪个主函数将成为最终可执行文件的主函数。
在这种情况下,生成的可执行文件myprogram
将使用file1.c
中的main
函数作为主函数,而file2.c
中的main
函数将被忽略。
任何函数都不能访问不在有效范围内的变量。
形参是内部变量。
在不同有效范围内的内部变量可以同名,因为对应不同的存储单元,所以这些变量之间并没有关联
当同名内部变量有效范围出现完全覆盖时,被覆盖的内部变量访问优先权高于覆盖内部变量。
在同一个有效区域内的变量只能唯一,不能有同名的变量。
全局变量在程序的全部执行过程中都占用存储单元,而不是仅在需要时才开辟单元。
使用全局变量过多,会降低程序的清晰性,人们往往难以清楚地判断出每个瞬时各个外部变量的值。在各个函数执行时都可能改变外部变量的值,程序容易出错。因此,要限制使用全局变量。
外部变量影响了函数的独立性,破坏了程序的模块化,同时函数要受外部变量当前值的影响,因此必须跟踪外部变量的变化,这样就降低了程序的可读性。
# include
int b=100;
void fun1( )
{
printf("内部变量1 b=%d \n", b);
b*=10; //此时的b是外部的b,改变其值
}
int a=10;
void main()
{
printf("内部变量2 a=%d \n", a);
fun1( );
printf("外层变量2 b=%d \n", b);
}
//内部变量2: a=10
//内部变量1: b=100
//外部变量2: b=1000
#include
int b=100; //空间作用域一直从这到最后(全局变量)
void fun2()
{
int b=1;//空间作用域在此函数内,被上面的全局变量覆盖,所以优先使用被覆盖的局部变量
printf("内部变量1 b=%d \n", b);//这里的b是局部变量
b*=10; //改变的也是局部变量
} //函数调用完后,局部变量就被销毁,不在栈中。
int a=10; //空间作用域为从这到后面
void main()
{
int a=100; //局部变量
printf("内部变量2 a=%d \n", a);//打印局部变量(就近原则)
fun2(); //跳转到上面函数定义
printf("外层变量2 b=%d \n", b);//打印栈中还有的b
}
/*
外部变量2: a=100
外部变量1: b=1
外部变量2: b=100
*/
如果不容易理解,那么可以采用汇编的方法来理解,函数的调用要开辟自己的栈帧,开辟栈帧的时候要为非静态局部变量开辟空间,函数执行完成后有销毁非静态局部变量,因此函数每次访问(取值或者赋值)时,都会在栈帧中向上(高地址)寻找变量,如果在本函数中有,那么就访问最近的这个,有点像就近原则,但是一旦这个函数(或者时复合语句)结束后,本函数内所有的非静态局部变量就被销毁了。
栈(Stack)是一种采用“先进后出”方式进行访问的一块存储区, 用于嵌套过程调用。从高地址向低地址增长。
在函数中定义的变量,在函数调用开始时分配动态存储空间,函数结束时释放这些空间。在程序执行过程中,这种分配和释放是动态的。
int f(int a)
{
auto int b,c=3; //auto可以省略
…
}
#include
void fun()
{ int a = 10;
static int b = 10;
a++; b++;
printf("a=%d b=%d\n", a,b);
}
void main()
{ int i;
for(i=1; i<= 5; i++)
{ printf("No:%d ", i);
fun();
}
} /*
No:1 a=11 b=11
No:1 a=11 b=12
No:1 a=11 b=13
No:1 a=11 b=14
No:1 a=11 b=15
*/
而自动变量(即动态局部变量)属于动态存储类别,占动态存储区空间而不占静态存储区空间,函数调用结束后即释放
对静态局部变量是在编译时赋初值的,即只赋初值一次,在程序运行时它已有初值。以后每次调用函数时不再重新赋初值而只是保留上次函数调用结束时的值。
而对自动变量赋初值,不是在编译时进行的,而是在函数调用时进行,每调用一次函数重新给一次初值,相当于执行一次赋值语句。
如在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值0(对数值型变量)或空字符(对字符变量)。
而对自动变量来说,如果不赋初值则它的值是一个不确定的值。这是由于每次函数调用结束后存储单元已释放,下次调用时又重新另分配存储单元,而所分配的单元中的值是不可知的。
虽然静态局部变量在函数调用结束后仍然存在,但其他函数是不能引用它的。因为它是局部变量,只能被本函数引用,而不能被其他函数引用。
用静态存储要多占内存(长期占用不释放,而不能像动态存储那样一个存储单元可供多个变量使用,节约内存),而且降低了程序的可读性,当调用次数多时往往弄不清静态局部变量的当前值是什么。因此,若非必要,不要多用静态局部变量。
CPU运行时,需要的操作数大部分来自寄存器
如需要从(向)存储器中取(存) 数据时,先访问cache,如在,取自cache
所以:如果把数据直接存储到寄存器中, 则数据没有在内存与寄存器之间交互传输, 大大提高了计算机的执行效率, 因此把使用频率高的变量设置为寄存器储存方式为宜.
对一个变量的属性可以从两个方面分析,一是变量的作用域,一是变量的生存期。
《运算数》<<移动次数
移位复合赋值运算:
《~》《运算数》
char a=~9;
printf("%d\n",a);
《运算数1》&《运算数2》
char a=9,c=5,c;
c=a&b;
C的结果为1
《运算数1》|《运算数2》
《运算数1》^《运算数2》
所谓位域,就是把把一个字节按照二进制位,划分成不同区段,每个区段包含连续的若干二进制位。
《struct》《位域类型名》
{
《数据类型符1》《位域名1》《:位域长度》
……
}
struct bs
{
int a:8;
int b:2;
int c:6;
}
注意:
《位域变量名》《.》《位域名》
#include
int main()
{
struct bs
{
unsigned int a:1;//最大存1
unsigned int b:3;//最大存111(7)
unsigned int c:4;//最大存1111(15)
} bit,bits[2];
int i;
bit.a=1;
bit.b=5;//101
bit.c=12;//1100
bits[0]=bit;
bits[1]=bits[0];
printf("%u,%u,%u\n",bit.a,bit.b,bit.c);
for(i=0;i<2;i++)
{
printf("%u,%u,%u\n",bits[i].a,bits[i].b,bits[i].c);
}