3.C语言:函数与程序结构

Function

  • 函数定义与声明
  • 外部变量与作用域
  • 头文件
  • 静态变量static
  • 寄存器变量
  • 程序块结构(作用域)
  • 初始化
  • 递归
  • C预处理器

函数定义与声明

函数定义形式:

返回值类型函数明(参数声明表)
{
	声明和语句
}

函数定义中的各构成部分都可以省略。

不执行任何操作的函数有时很有用,它可以在程序开发期间用以保留位置(留待以后填充代码)。如果函数定义中省略了返回值类型,则默认为int类型。
程序可以看成是变量定义和函数定义的集台。函数之间的通信可以通过参数、函数返回值以及外部变量进行。

被调用函数通过return语句向调用者返回值,return语句的后面可以跟任何表达式:

return 表达式;

如果函数带有参数,则要声明它们;如果没有参数,则使用void进行声明。函数声明和定义的参数类型一定要对应。

外部变量与作用域

C语言程序可以看成由一系列的外部对象构成,这些外部对象可能是变量或函数。
默认情况下,外部变量与函数具有下列性质:通过同一个名字对外部变量的所有引用(即使这种引用来自于单独编译的不同函数)实际上都是引用同一个对象(标准中把这一性质称为外部链接)。
因为外部变量可以在全局范围内访问,这就为函数之间的数据交换提供了一种可以代替函数参数与返回值的方式。任何函数都可以通过名字访问一个外部变量,当然这个名字需要通过某种方式进行声明。
如果函数之间需要其享大量的变量,使用外部变量要比使用一个很长的参数表更方便、有效。

  • 名字的作用域指的是程序中可以使用该名字的部分。
    另一方面,如果要在外部变量的定义之前使用该变量,或者外部变量的定义与变量的使用不在同一个源文件中,则必须在相应的变量声明中强制性地使用关键extern。
  • 定义:
int sp;
double val[MAXVAL];
  • 声明
extern int sp;
extern double val[];

在一个源程序的所有源文件中,一个外部变量只能在某个文件中定义一次,而其它文件可以通extern声明来访问它(定义外部变量的源文件中也可以包含对该外部变量的extern 声明)。外部变量的定义中必须指定数组的长度,extern声明则不一定要指定数组的长度。
外部变量的初始化只能出现在其定义中。

头文件

对于某些中等规模的程序,最好只用一个头文件存放程序中各部分共享的对象。较大的程序需要使用更多的头文件,我们需要精心地组织它们。

静态变量static

与extern关键字是冲突的。
static声明限定外部变量与函数,可以将其后声明的对象的作用域限定为被编译源文件的剩余部分。通static限定外部对象,可以达到隐藏外部对象的目的。

  1. 静态外部变量:只能被当前文件下的函数使用,不会和同一程序中的其他文件中的相同名字相冲突。
  2. 静态函数:,如果把函数声明static类型,则该函数名除了对该函数声明所在的文件可见外,其它文件都无法访问。(单个文件内部使用)
  3. 静态内部变量:stati类型的内部变量是一种只能在某个特定函数中使用但一直占据存储空间的变量。

寄存器变量

register声明告诉编译器,它所声明的变量在程序中使用频率较高。其思想是,将register变量放在机器的寄存器中,这样可以使程序更小、执行速度更快。但编译器可以忽略此选项。
registe 声明只适用于自动变量以及函数的形式参数。声明形式:

register int x;
register char c;
f(register unsigned m, register long n)
{
	register int i;
}

另外,无论寄存器变量实际上是不是存放在寄存器中,它的地址都是不能访问的。

程序块结构(作用域)

自动变量(包括形式参数)也可以隐藏同名的外部变量与函数。如

int x;
int y;
f(double x)
{
	double y;
}

在一个好的程序设计风格中,应该避免出现变量名隐藏外部作用域中相同名字的情况,否则,很可能引起混乱和错误。

初始化

  • 在不进行显式初始化的情况下,外部变量和静态变量都将被初始化,而自动变量和寄存器变量的初值则没有定义(即初值为无用的信息)。
    对于外部变量与静态变量来说,初始化表达式必须是常量表达式,且只初始化一。
    对于自动变量与寄存器变量,则在每次进入函数或程序块时都将被初始化。
  • 数组的初始化可以在声明的后面紧跟一个初始化表达式列表,初始化表达式列表用花括号括起来,各初始化表达式之间通过逗号分隔。当省略数组的长度时,编译器将把花括号中初始化表达式的个数作为数组的长,如下数组长度为12.
int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

如果初始化表达式的个数比数组元索数少,则对外部变量、静态变量和自动变量来说,没有初始化表达式的元素将被初始化为0,如果初始化表达式的个数比数组元素数多,则是错误的。

  • 字符数组的初始化比较特殊:可以用一个字符串来代替用花括号括起来并用逗号分隔的
    初始化表达式序列
char pattern[] = "ould ";
char pattern[] = { 'o', 'u', 'l', 'd'}; //这种情况下,数组的长度个字符加上一个字符串结束符'\0'

递归

语言中的函数可以递归调用,即函数可以直接或间接调用自身。
递归并不节省存储器的开销,因为递归调用过程中必须在某个地方维护一个存储处理值的栈。递归的执行速度并不快,但递归代码比较紧凑,并且比相应的非递归代码更易于编写与理解。在描述树等递归定义的数据结构时使用递归尤其方便。
每个递归函数都有两个部分:基线条件和递归条件。递归条件指的是函数调用自身,而基线条件则指的是函数不再调用自身,从而避免无限循环。

  • 栈(stack)
    栈是一种简单的数据结构。
    栈有两种操作:压入和弹出。(先进后出)
  • 调用栈:用于存储多个函数的变量,被称为调用栈。
    计算机在内部使用被称为调用栈的栈。
    在递归函数使用过程中。调用另一个函数时,当前函数暂停并处于为完成状态。

C预处理器

预处理器是编译过程中单独执行的第一个步骤。

  1. 文件包含
    文件包含指令(即#include指令)使得处理大量的的#define指令以及声明更加方便。
#include "文件名"
#include <文件名>

有上述文件包含的行都将被替换为由文件名指定的文件的内容。如果文件名用引号引起来,则在源文件所在位置查找该文件;如果在该位置没有找到文件,或者如果文件名用尖括号括起来,则将根据相应的规则查找该文件。
源文件的开始处通常都会有多个#include指令,它们用以包含常见的#define语句和extern声明,或从头文件中访问库函数的函数原型声明,比如
在大的程序中,#include指令是将所有声明捆绑在一起的较好的方法。它保证所有的源文件都具有相同的定义与变量声明,这样可以避免出现一些不必要的错误。很自然,如果某个包含文件的内容发生了变化,那么所有依赖于该包含文件的源文件都必须重新编译。

  1. 宏替换
    宏替换
#define 名字 替换文本

后续所有出现名字记号的地方都将被替换为替换文本。

如果在替换文本中,参数名以#作为前缀则结果将被扩展为由实际参数替换该参数的带引号的字符串。

#define dprint(expr) printf(#expr " = %g\n", expr)

预处理器运算符##为宏扩展提供了一种连接实际参数的手段。如果替换文本中的参数与##相邻,则该参数将被实际参数替换,##与前后的空白符将被删除,并对替换后的结果重新扫描。

#define paste(front, back) front ## back
  1. 条件包含
#if
#endif
#elif
#else
#ifndef
#define

还可以使用条件语句对预处理本身进行控制,这种条件语句的值是在预处理执行的过程中进行计算。这种方式为在编译过程中根据计算所得的条件值选择性地包含不同代码提供了一种手段。
如果多个头文件能够一致地使用这种方式,那么,每个头文件都可以将它所依赖的任何头文件包含进来,用户不必考虑和处理头文件之间的各种依赖关系。
常用为了保证文件的内容只被包含过一次,将文件的内容包含在以下形式的条件语句中。

#ifndef HDR
#define EDR
/* hdr.文件的内容放在这*/
#endif

你可能感兴趣的:(C语言,c语言,算法,开发语言)