函数可以把大的计算任务分解成若干个较小的任务,程序设计人员可以基于函数进一步构造程序,二部需要重新编写一些代码。
一个设计得当的函数可以把程序中不需要了解的具体操作细节隐藏起来,从而使整个程序结构更加清晰,并降低修改程序的难度。
函数的定义形式:
返回值类型 函数名(参数声明表)
{
声明和语句
}
函数定义中的个构成部分都可以省略。
最简单的函数如下: dummy() {}
该函数不执行任何操作也不返回任何值。
如果函数定义中省略了返回值类型,则默认为 int 类型。
程序可以看成是变量定义和函数定义的集合。
函数之间的通信可以通过参数、函数返回值以及外部变量进行。
函数在源文件中出现的次序可以是任意的。
只要保证每一个函数不被分离到多个文件中,源程序就可以分成多个文件。
被调用的函数通过return语句向调用者返回值,return语句的后面可以跟任何表达式:
return 表达式;
在必要时,表达式将被转换为函数的返回值类型。
表达式两边通常加一对圆括号,此处括号可选。
通过函数 atof(s) 来说明函数返回非整形值的方法。
该函数把字符串 s 转换为相应的双精度浮点数。
atof函数的返回值类型不是 int ,因此必须声明返回值的类型。
在调用函数中显示声明 atof ,例:
#include
#define MAXLINE 100
/* 简单计算器程序 */
main()
{
double sum, atof(char []);
char line[MAXLINE];
int getline(char line[], int max);
sum = 0;
while (getline(line, MAXLINE) > 0)
printf("\t%g\n", sum += atof(line));
return 0;
}
其中,声明语句
double sum, atof(char []);
表明 sum 是一个 double 类型的变量,atof函数带有一个char [] 类型的参数,且返回一个double类型的值。
函数 atof 的声明与定义必须一致。
如atof与调用它的主函数main在同一个源文件中,且类型不一致,编译器检测到该错误
如atof是单独编译的,不匹配错误无法被检测.
如果没有函数原型,函数将在第一次出现的表达式中被隐式声
如先前没声明过的一个名字出现在某个表达式中,且其后紧跟一个左圆括号
上下文认为该名字是一个函数名字,返回值被假定为int,上下文不对其参数作任何假设
如果没有函数原型,则函数将在第一个出现的表达式中被隐式声明。
/* atoi函数: 利用atof函数把字符串s 转换为整型 */
int atoi(char s[])
{
double atof(char s[]);
return (int) atof(s);
}
return(表达式);
表达式的值在返回之前将被转换为函数的类型。
因为函数 atoi 的返回值是int 类型,索引return语句中的atof函数的double类型值将被自动转换为int类型值。
定义在函数外,可以在许多函数中使用。
外部变量可以在全局范围内访问。
任何函数都可以通过名字访问一个外部变量。
外部变量是永久存在的,它们的值在一次函数调用到下一次函数调用之间保持不变。
外部变量与函数具有下列性质:
通过同一个名字引用的所有外部变量(即是这种引用来自单独编译的不同函数)实际上都是引用同一个对象(标准中吧把这一性质称为外部链接)。
外部变量的用途还表现在:
它们与内部变量相比具有更大的作用域和更长的生长周期。
名字的作用域指程序中可使用该名字的部分
外部变量或函数的作用域从声明它的地方开始,到其所在的(待编译)文件的末尾结束.
如要在外部变量定义前使用该变量,或外部变量的定义与变量的使用不在同一个源文件中,则必须在相应的变量声明中强制性地使用关键字extern.
变量声明用于说明变量的属性,变量定义除此之外还将引起存储器的分配.
外部变量初始化只能出现在定义中.
定义只有一个,
声明可以有多个
数组定义时需要指明容量,声明时可以不必
声明一般用extern在前面修饰。
文件1的定义的外部变量
在文件2使用时,文件2中使用前需要先声明
在文件1定义部分前使用时,也需要在使用前有声明。
前向声明,后向声明【针对c++,指的是对类类型,前向声明不包含类内部细节,后向则包括。一个用于可以使用部分类型下,一个用于必须使用完整类型下。】
外部变量较多时,可以将若干外部变量声明集中到一个独立.h文件。
尽可能把共享的部分集中在一起,这样只需要一个副本,改进程序时也容易保证程序的正确性。
把这些公共的部分放到头文件 calc.c中,在需要使用该头文件时通过 #include 指令将它包含进来。
外部变量/函数+static。限制外部变量/函数只能在指定的单个源文件内使用。不可跨文件使用。
局部静态变量只被初始化一次,生存期持续到程序结束.
register声明的形式:
// 建议编译器将变量放在寄存器
// register只可用于自动变量,形参
register int x;
register char c;
声明为寄存器类型的变量不可获取变量地址
C语言并不是Pascal等语言意义上的程序块结构式语言,它不允许在函数中定义函数。
但是,在函数中可以以程序块结构的形式定义变量。
不显式初始化下,外部和静态变量将被初始化为0,自动和寄存器变量的初值则未定义.
对外部和静态变量,初始化必须是常量表达式。初始化一次。
对局部和寄存器变量,其初始化表达式不要求是常量表达式。
数组列表初始化【可自动推导数组容量,初始化值个数可比容量少,不可多。少时,剩余元素初始化为0】
字符数组可用字符串来初始化。
C语言中的函数可以递归调用,即函数可以直接或间接调用自身。
函数递归调用自身时,每次调用都会得到一个与以前的自动变量集合不同的新的自动变量集合。
标准库中提供了一个qsort函数,它可用于对任何类型的对象进行排序。
文件包含
文件包含指令(即 #include指令)使得处理大量的#define 指令以及声明更加方便。
在源文件中,任何形式如
#include "文件名"
或
#include <文件名>
的行都将被替换为由文件名指定的文件的内容。
如果文件名用引号引起来,则在源文件所在位置查找该文件。
如果在该位置没有找到文件,或如果文件名用尖括号括起来,
则将根据相应的规则查找该文件,这个规则同具体的实现有关。
被包含的文件本身也可包含#include指令
宏替换
#define name replace_text
可把一个较长的宏定义分成若干行,需在待续的行末尾加上一个反斜杠符\
#define指令定义的名字的作用域从其定义点开始,到被编译的源文件的末尾处结束.
可以用#undef取消对名字的宏定义,可保证后续的调用是函数调用,不是宏调用.
#undef getchar
int getchar(void) { ... }
关于#
形式参数不能用带引号的字符串替换.
但,如在替换文本中参数名以#作为前缀,
则结果将被扩展为由实际参数替换该参数的带引号的字符串.
在实际参数中,每个双引号将被替换为\",反斜杠将被替换为\\
因此,替换后的字符串是合法的字符串常量.
#define dprint(expr) printf(#expr " = %g\n", expr)
// 将得到printf("x/y" " = %g\n", x/y)
// 即为printf("x/y = %g\n", x/y)
dprint(x/y)
关于##
提供了一种连接实际参数的手段
如替换文本中的参数与##相邻,则该参数被实际参数替换,##与前后的空白符被删除,
对替换后的结果重新扫描.
#define paste(front, back) front ## back
// 将得到 name ## 1,进一步得到 name1
paste(name, 1)
条件包含
if 语句表达式:
if defined(名字)
该表达式遵循的规则:
当名字已经定义时,其值为1;否则,其值为0.
例:
为了保证hdr.h文件的内容只被包含一次,可以将该文件的内容包含在下列形式的条件语句中:
// if defined(x)
// if !defined(x)
#if !defined(HDR)
#define HDR
/* hdr.h文件的内容放在这里 */
#endif
// ifdef
// ifndef
#ifndef HDR
#define HDR
/* contents of hdr.h go here */
#endif
下面这段预处理代码首先测试系统变量SYSTEM,然后根据该变量的值确定包含哪个版本的头文件:
#if SYSTEM == SYSV
#define HDR "sysv.h"
#elif SYSTEM == BSD
#define HDR "bsd.h"
#elif SYSTEM == MSDOS
#define HDR "msdos.h"
#else
#define HDR "default.h"
#endif
#include HDR
学习参考资料:
C程序设计语言 第2版 新版