《C程序设计语言(第2版·新版)》第4章 函数与程序结构

函数功能:隐藏操作细节,结构更加清晰,降低修改难度;

4.1 函数基本知识

返回值类型 函数名(参数声明表)
{
声明和语句
}
  函数在源文件中出现的次序可以任意;
  返回值类型省略则默认int;return可不带表达式,执行到最后右花括号也会返回:都是没有返回值的,合法,但未成功返回的“值”肯定是无用的;
  程序可看做变量定义与函数定义的集合;函数通过参数、返回值和外部变量通信;

4.2 返回非整型的函数

  函数与调用它的主函数在同一源文件中,并且类型不一致时,编译就会发现该错误;
  隐式声明:如果未声明过的一个名字出现在某表达式中,并且其后紧跟左圆括号,那么上下文会认为这是一个函数名,其返回值会被假定为int,其参数不做任何假设;
 
  return (表达式);表达式的值返回时会被转换为函数类型,这可能会丢失信息有些编译器会警告,故可显示类型转换;

4.3 外部变量

  C程序可看做一系列外部对象(包括变量和函数)构成;
  外部变量定义在函数外;函数不允许定义在其它函数中,故每一个函数也是“外部的”;
  ??(It)外部变量与函数默认具有“外部链接”性质:即使来自于单独编译的不同函数,通过同一个名字引用的都是同一个对象,据此函数可以借助外部变量通信,尤其函数需要共享大量信息时,同时也会使函数之间产生大量的数据联系,影响程序结构;
  后面会介绍怎样定义只使用在一个源文件中的外部变量和函数;后面再讨论怎么把程序分割成多个源文件;
 
  外部变量生存期:永久存在,两次函数调用之间保持不变;
 
  函数共享的“”,可定义为外部变量,同时定义一个指针保存下一个空闲栈位置;

4.4 作用域规则

  函数与外部变量可分开编译;一个程序可放在几个文件中;已编译过的函数可从库中加载;
 
  作用域:程序中可以使用该名字的部分;局部变量(包括函数参数)作用域在函数内;外部变量或函数作用域从声明处开始,到其所在(待编译的)文件末尾结束,函数调用它们时无需声明直接用;
 
  extern:如果要在外部变量定义之前使用它,或者定义与使用不在同一个源文件中,则声明中须强制使用extern;
  ??外部变量声明与定义的区分:
  放在所有函数外部的int sp;double val[MAX];定义外部变量sp及val,分配内存单元,同时作为该源文件其余部分的声明;
  extern int sp;extern double val[MAX]为源文件其余部分声明了外部变量sp及val(数组长度可在其他地方确定),但是并未建立变量或者分配存储单元;
 
  一个源程序的所有源文件中,一个外部变量只能在某个文件中定义一次;其他文件必须先进行extern声明后才能访问它;(??也可在定义所在源文件进行extern声明);外部变量初始化只能出现在其定义中;一个变量如果要先使用后定义,使用前也须extern声明一下;

4.5 头文件

  实际程序中,考虑到其各部分可能分别来自单独编译的库,所以常常将源程序分割成多个源文件;
考虑定义和声明的共享问题:(It)尽可能在一个头文件中专门集中各个源文件共享/公共部分(宏替换、函数原型等,简洁易修改),然后在各个需要的源文件中通过include "~.h"包含进去;较大规模程序可能需要多个头文件;

4.6 静态变量

  外部变量或函数的声明前加上static:该对象作用域限定为被编译源文件剩余部分,对其他文件不可见(同名也不会冲突);
  内部变量声明前加static:与自动变量一样只能在该函数内使用;不同之处在于不管该函数是否被调用,它一直存在一直占据存储空间,而非像自动变量那样随着函数的调用和退出而存在和消失;(It)该函数下次调用时可以使用上次调用留下的静态变量值,有时很有用(p57);

4.7寄存器变量

  在较高频使用的自动变量或函数形参的声明前可加register,编译器可以将它放入寄存器中从而加快速度,编译器也可以忽略此选项;
  实际底层硬件情况的限制:每个函数只有少量某些类型的变量可保存于寄存器;过量寄存器声明是无害的,编译器会忽略过量或不支持的寄存器变量声明;
  无论寄存器变量是否真的放在寄存器中,其地址总是不能访问的;

4.8 程序块结构

  C语言不能在函数中定义函数,但可以在函数的程序块结构中定义变量;变量声明(包括初始化)可以紧跟在任何标识复合语句开始的左花括号之后,在与之匹配的右花括号之前一直存在,并且它与该程序块以外的同名变量无任何关系;

4.9初始化

  不显式初始化:则外部变量和静态变量都将被初始化为0;自动变量和寄存器变量初值未定义(即无用信息);
  显式初始化:外部变量和静态变量=常量表达式,且只在程序开始前初始化一次;自动变量和寄存器变量每次进入函数或程序块时都将被初始化,且无需是常量表达式:可以包含之前已定义的值及函数调用;
  数组的初始化:
int day[]={31,28,31,30,31,30,31,30,31,30,31};
  忽略数组长度时,花括号中初始化表达式的个数将被编译器作为长度;数组长度多于个数,对外部变量、静态变量、自动变量,剩余未初始化的元素都将赋0;长度小于个数则出错;一个初始化表达式不能一次赋给多个元素;不能跳过前面元素直接初始化后面元素;
  字符数组初始化:
char pattern[]="ould";等价于char pattern[]={'o','u','l','d','\0'};数组长度是5;

4.10 递归

  函数可以直接或间接调用自身;
  快排函数:void qsort(int v[], int left, int right) //p74
  标准库中的qsort函数可以对任何类型的对象进行排序;
  递归并不节省存储开销(必须在某个地方维护一个存储处理值的栈),执行速度并不快;但其代码紧凑易于编写和理解;对于树等递归定义的数据结构使用起来非常方便;

4.11 C预处理器

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

文件包含:

#include "文件名" 或 #include <文件名>
  源文件中任何形如上述的行都将被替换为文件名指定的内容;对""则在源文件所在位置查找该文件,对未找到或<>则按相应规则(与具体实现有关)查找;
  #include指令常用来包含#define、extern声明、函数原型,在较大程序中这有利于避免错误;若某个被包含文件内容变化,显然所有依赖于它的源文件都必须重新编译;

宏替换:

宏定义:#define 名字 替换文本
  名字命名同变量,替换文本可以是任意字符串;替换文本需分行,则在待续行尾加\;作用域从定义点到源文件末尾;宏定义可以使用前面出现的宏定义;替换只对记号(即不在字符串内也不是某名字的一部分)有用;
  带参数的宏定义,例如:
#define max(A, B) ((A)>(B) ? (A) : (B))
  使用需谨慎:适当用圆括号以保证计算顺序正确;考虑表达式的例如自增自减等副作用;
  宏的价值:函数(如getchar)被定义为宏,可避免调用函数所需运行时的开销;
#undef 名字 可取消宏定义;
  ??名字中的形参不能用带引号字符串替换,若需要,则在替换文本中给参数前加上#;
  预处理运算符##:若替换文本中形参与它相邻,则形参被实参替换的同时##及其前后空白符会被删除,替换后的结果会重新扫描;此法可连接实际参数,例如:
#define paste(front, back) front ## back
则宏调用paste(name, 1)将建立记号name1;

条件包含:

#if-#elif-#else-#endif
  其中判断表达式必须是常量整型表达式(且不包含sizeof、(type)、enum量),可以是defined(名字)表达式(可用来避免重复包含某一名字或文件,多文件都使用这一方式,将不必考虑各个头文件之间的依赖关系);
#ifdef与#ifndef:专门测试某个名字是否已定义,例如:
 
#ifndef HDR
#define HDR
//放hdr.h的内容
#endif
 
它等价于
 
#if !define(HDR)
#define HDR
//放hdr.h的内容
#endif
posted @ 2015-09-17 12:52  fFaXzz  阅读(216)  评论(0编辑  收藏  举报