带参宏定义与函数的问题

   如果编译不是标准的,则可能仅支持以上宏名中的几个,或根本不支持。记住编译程序也许还提供其它预定义的宏名。
   __DATE__宏指令含有形式为月/日/年的串,表示源文件被翻译到代码时的日期。
   __TIME__宏指令包含源代码翻译到目标代码的时间。串形式为时:分:秒。
   如果实现是标准的,则宏__STDC__含有十进制常量1。如果它含有任何其它数,则实现是非标准的。

20.宏定义防止使用时错误


   INT_MAX和A都不会再被展开, 然而解决这个问题的方法很简单. 加多一层中间转换宏.
   加这层宏的用意是把所有宏的参数在这层里全部展开, 那么在转换宏里的那一个宏(_STR)就能得到正确的宏参数


第一个宏中由于没有对变参起名,我们用默认的宏__VA_ARGS__来替代它。第二个宏中,我们显式地命名变参为args,那么我们在宏定义中就可以用args来代指变参了。同C语言的stdcall一样,变参必须作为参数表的最后一项出现。当上面的宏中我们只能提供第一个参数templt时,C标准要求我们必须写成:myprintf(templt,);的形式。这时的替换过程为:

   这是一个语法错误,不能正常编译。这个问题一般有两个解决方法。首先,GNU CPP提供的解决方法允许上面的宏调用写成:

   很明显,这里仍然会产生编译错误(非本例的某些情况下不会产生编译错误)。除了这种方式外,c99和GNU CPP都支持下面的宏定义方式:

   这时,##这个连接符号充当的作用就是当__VAR_ARGS__为空的时候,消除前面的那个逗号。那么此时的翻译过程如下:

   宏的第一个参数就设置为变参,因此下面的几种使用方式都是正确的:

宏使用中的陷阱    这里列出了一些宏使用中容易出错的地方,以及合适的使用方式。

   宏的定义不一定要有完整的、配对的括号,但是为了避免出错并且提高可读性,最好避免这样使用。

   由于宏只是简单的替换,宏的参数如果是复合结构,那么通过替换之后可能由于各个参数之间的操作符优先级高于单个参数内部各部分之间相互作用的操作符优先级,如果我们不用括号保护各个宏参数,可能会产生预想不到的情形。比如:

   这显然不是调用者的初衷。为了避免这种情况发生,应当多写几个括号:

   通常情况下,为了使函数模样的宏在表面上看起来像一个通常的C语言调用一样,通常情况下我们在宏的后面加上一个分号,比如下面的带参宏:

   这样会由于多出的那个分号产生编译错误。为了避免这种情况出现同时保持MY_MACRO(x);的这种写法,我们需要把宏定义为这种形式:

   这样只要保证总是使用分号,就不会有任何问题。

   这里的Side Effect是指宏在展开的时候对其参数可能进行多次Evaluation(也就是取值),但是如果这个宏参数是一个函数,那么就有可能被调用多次从而达到不一致的结果,甚至会发生更严重的错误。比如:

   这时foo()函数就被调用了两次。为了解决这个潜在的问题,我们应当这样写min(X,Y)这个宏:

   ({......})的作用是将内部的几条语句中最后一条的值返回,它也允许在内部声明变量(因为它通过大括号组成了一个局部Scope)。

写好C语言,漂亮的宏定义很重要,使用宏定义可以防止出错,提高可移植性,可读性,方便性 等等。下面列举一些成熟软件中常用得宏定义。。。。。。

1,防止一个头文件被重复包含

2,重新定义一些类型,防止由于各种平台和编译器的不同,而产生的类型字节数差异,方便移植。

3,得到指定地址上的一个字节或字

6,得到一个结构体中field所占用的字节数

7,按照LSB格式把两个字节转化为一个Word

8,按照LSB格式把一个Word转化为两个字节

9,得到一个变量的地址(word宽度)

10,得到一个字的高位和低位字节

11,返回一个比X大的最接近的8的倍数

12,将一个字母转换为大写

13,判断字符是不是10进值的数字

14,判断字符是不是16进值的数字

15,防止溢出的一个方法

16,返回数组元素的个数

18,对于IO空间映射在存储空间的结构,输入输出处理

19,使用一些宏跟踪调试

A N S I标准说明了五个预定义的宏名。它们是:

如果编译不是标准的,则可能仅支持以上宏名中的几个,或根本不支持。记住编译程序

也许还提供其它预定义的宏名。

_ D AT E _宏指令含有形式为月//年的串,表示源文件被翻译到代码时的日期。

源代码翻译到目标代码的时间作为串包含在_ T I M E _中。串形式为时:分:秒。

如果实现是标准的,则宏_ S T D C _含有十进制常量1。如果它含有任何其它数,则实现是

当定义了_DEBUG,输出数据信息和所在文件所在行

20,宏定义防止使用是错误

宏在日常编程中的常见使用

这里列出了一些宏使用中容易出错的地方,以及合适的使用方式。

宏的定义不一定要有完整的、配对的括号,但是为了避免出错并且提高可读性,最好避免这样使用。

命令#define定义了一个标识符及一个串。在源程序中每次遇到该标识符时,均以定义的串代换它。ANSI标准将标识符定义为宏名,将替换过程称为宏
替换。命令的一般形式为:

? 该语句没有分号。在标识符和串之间可以有任意个空格,串一旦开始,仅由一新行结束。

? 宏名定义后,即可成为其它宏名定义中的一部分。

? 宏替换仅仅是以文本串代替宏标识符,前提是宏标识符必须独立的识别出来,否则不进行替换。例如: #define XYZ

? 如果串长于一行,可以在该行末尾用一反斜杠' /'续行。

处理器命令#error强迫编译程序停止编译,主要用于程序调试。

命令#i nclude使编译程序将另一源文件嵌入带有#i nclude的源文件,被读入的源文件必须用双引号或尖括号括起来。例如:

这两行代码均使用C编译程序读入并编译用于处理磁盘文件库的子程序。

将文件嵌入#i nclude命令中的文件内是可行的,这种方式称为嵌套的嵌入文件,嵌套层次依赖于具体实现。

如果显式路径名为文件标识符的一部分,则仅在哪些子目录中搜索被嵌入文件。否则,如果文件名用双引号括起来,则首先检索当前工作目录。如果未发现文件,
则在命令行中说明的所有目录中搜索。如果仍未发现文件,则搜索实现时定义的标准目录。

如果没有显式路径名且文件名被尖括号括起来,则首先在编译命令行中的目录内检索。

如果文件没找到,则检索标准目录,不检索当前工作目录。

有几个命令可对程序源代码的各部分有选择地进行编译,该过程称为条件编译。商业软件公司广泛应用条件编译来提供和维护某一程序的许多顾客版本。

#if的一般含义是如果#if后面的常量表达式为true,则编译它与#endif之间的代码,否则跳过这些代码。命令#endif标识一个#if块的

跟在#if后面的表达式在编译时求值,因此它必须仅含常量及已定义过的标识符,不可使用变量。表达式不许含有操作符sizeof(sizeof也是编译

#else命令的功能有点象C语言中的else;#else建立另一选择(在#if失败的情况下)。

#elif命令意义与ELSE IF 相同,它形成一个if else-if阶梯状语句,可进行多种编译选择。

#elif 后跟一个常量表达式。如果表达式为true,则编译其后的代码块,不对其它#elif表达式进行测试。否则,顺序测试下一块。

条件编译的另一种方法是用#ifdef与#ifndef命令,它们分别表示"如果有定义"及"如果无定义"。

命令#undef 取消其后那个前面已定义过有宏名定义。一般形式为:

命令# line改变__LINE__与__FILE__的内容,它们是在编译程序中预先定义的标识符。命令的基本形式如下:

其中的数字为任何正整数,可选的文件名为任意有效文件标识符。行号为源程序中当前行号,文件名为源文件的名字。命令# line主要用于调试及其它特殊

注意:在#line后面的数字标识从下一行开始的数字标识。

ANSI标准说明了C中的五个预定义的宏名。它们是:

如果编译不是标准的,则可能仅支持以上宏名中的几个,或根本不支持。记住编译程序也许还提供其它预定义的宏名。

__LINE__及__FILE__宏指令在有关# line的部分中已讨论,这里讨论其余的宏名。

__DATE__宏指令含有形式为月/日/年的串,表示源文件被翻译到代码时的日期。

源代码翻译到目标代码的时间作为串包含在__TIME__中。串形式为时:分:秒。

如果实现是标准的,则宏__STDC__含有十进制常量1。如果它含有任何其它数,则实现是非标准的。编译C++程序时,编译器自动定义了一个预处理名

注意:宏名的书写由标识符与两边各二条下划线构成。

宏体中,#的功能是将其后面的宏参数进行字符串化操作(Stringfication),简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个

而##被称为连接符(concatenator),用来将两个Token连接为一个Token。注意这里连接的对象是Token就行,而不一定是宏的变
量。比如你要做一个菜单项命令名和函数指针组成的结构体的数组,并且希望在函数名和菜单项命令名之间有直观的、名字上的关系。那就可以使用:宏参数##
固定部分。当然还可以n个##符号连接 n+1个Token,这个特性也是#符号所不具备的。

#@的功能是将其后面的宏参数进行字符化。

9、C宏中的变参...

第一个宏中由于没有对变参起名,我们用默认的宏__VA_ARGS__来替代它。第二个宏中,我们显式地命名变参为args,那么我们在宏定义中就可以
用args来代指变参了。同C语言的stdcall一样,变参必须作为参数表的最后有一项出现。当上面的宏中我们只能提供第一个参数templt时,C

这是一个语法错误,不能正常编译。这个问题一般有两个解决方法。首先,GNU CPP提供的解决方法允许上面的宏调用写成:

很明显,这里仍然会产生编译错误(非本例的某些情况下不会产生编译错误)。除了这种方式外,c99和GNU CPP都支持下面的宏定义方式:

这样如果templt合法,将不会产生编译错误。

在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma指令对
每个编译器给出了一个方法,在保持与C和C ++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且
对于每个编译器都是不同的。

其格式一般为: #Pragma Para,其中Para 为参数,下面来看一些常用的参数。

(1)message 参数。 Message 参数是我最喜欢的一个参数,它能够在编译信息输出窗口中输出相应的信息,这对于源代码信息的控制是非常
重要的。其使用方法为:

当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。

当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。
假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法

当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示"_

X86 macro activated!"。我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了。

(2)另一个使用得比较多的pragma参数是code_seg。格式如:

它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它。

只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,这条指令实际上在VC6中就已经有了,但是考虑到兼容性并没有太多的使用它。

(4)#pragma hdrstop表示预编译头文件到此为止,后面的头文件不进行预编译。BCB可以预编译头文件以加快链接的速度,但如果所有头文
件都进行预编译又可能占太多磁盘空间,所以使用这个选项排除一些头文件。

有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译。你可以用#pragma startup指定编译优先级,如果使用了

这里n代表一个警告等级(1---4)。

#pragma warning( push, n)保存所有警告信息的现有的警告状态,并且把全局警告等级设定为n。
#pragma warning( pop )向栈中弹出最后一个警告信息,在入栈和出栈之间所作的一切改动取消。例如:

在这段代码的最后,重新保存所有的警告信息(包括4705,4706和4707)。

该指令将一个注释记录放入一个对象文件或可执行文件中。
常用的lib关键字,可以帮我们连入一个库文件。

传统的到出 DLL 函数的方法是使用模块定义文件 (.def),Visual C++ 提供了更简洁方便的方法,那就

上面的导出函数的名称也许不是我的希望的,我们希望导出的是原版的"MyExportFunction"。还好,VC 提供了一个预处理指示
符"#pragma"来指定连接选项 (不仅仅是这一个功能,还有很多指示功能) ,如下:

这下就天如人愿了:)。如果你想指定导出的顺序,或者只将函数导出为序号,没有 Entryname,这个预处理指示符 (确切地说是连接器) 都能够 实现,看看 MSDN 的语法说明:

@ordinal 指定顺序;NONAME 指定只将函数导出为序号;DATA 关键字指定导出项为数据项。

⑨每个编译程序可以用#pragma指令激活或终止该编译程序支持的一些编译功能。例如,对循环优化功能:

函数会产生一条有唯一特征码100的警告信息,如此可暂时终止该警告。

每个编译器对#pragma的实现不同,在一个编译器中有效在别的编译器中几乎无效。可从编译器的文档中查看。

#pragma pack规定的对齐长度,实际使用的规则是:

? 结构,联合,或者类的数据成员,第一个放在偏移为0的地方,以后每个数据成员的对齐,按照#pragma pack指定的数值和这
个数据成员自身长度中,比较小的那个进行。

? 也就是说,当#pragma pack的值等于或超过所有数据成员长度的时候,这个值的大小将不产生任何效果。

? 而结构整体的对齐,则按照结构体中最大的数据成员 和 #pragma pack指定值之间,较小的那个进行。

注:关于宏函数的内容在另外的专题。关于宏使用的误区在描述宏的时候已经在文中提到了,最后再给出一个例子,描述的Side Effect是指宏在展开
的时候对其参数可能进行多次Evaluation(也就是取值)对程序造成的错误影响。

假设在一个系统中,有一个32b的寄存器(REG)保存状态,其中高16b表示一种含义,低16b表示另一种含义(这在程序中经常出现)。现在要把高低
16b分开,不考虑实际中的特殊要求,将代码写成:

对于这种写法完成的功能在大多数情况是足够了,这里不讨论。主要谈论这种写法的负面影响,如果在程序中分别在不同的语句中使用High16bit和
Low16bit,那么就可能那就是Side effect,特别寄存器REG是状态寄存器,他的状态可能随时变化,那么引起的问题就是高低16b根本
取的不是同一个时刻状态寄存器。这种错误在程序中找出就比较难了。在这里我把条件弱化了,试想在一个宏体中,如果对参数多次取值也是可能引起问题,那就 更难了。

1.宏定义和函数的区别

宏:宏定义是C提供的三种预处理功能的其中一种,这三种预处理包括:

1.不带参数的宏定义:

标示符就是可以替换字符串的宏名称,编译器在进行预处理过程中对使用宏替换的地方展开,用“字符串”替换宏名称,这样做的好处就是可以对数字或者随机数值指定一个有代表意义的名称,提高程序的可读性和通用性,在后期维护中可以方便的修改宏定义中字符串的数值大小或者其他数值类型,从而可以控制整个代码中所有引用宏名的地方。

从占用资源的角度看,编译器对宏替换不会占用或者分配内存,和函数比较,调用子函数需要分配内存,增加系统开销,但子函数可以把程序结构模块化,提高程序的可读性和聚合度,对比之下,宏也可以有参数,如果在程序中为了不调用子函数而减小开销,那么所有过程都写在一个函数中,并且没有自注释的名称,程序的可读性就会降低,毕竟代码是处理现实世界中事务数据关系的一种抽象,但不是一个人的,应该是像一首简洁,优美的诗,描述了美好的事务,所以折中之下,宏替换是个不错的选择。

      虽然宏替换占用了编译器的时间,所谓“有得必有失”,减小了程序执行的资源消耗,这未尝不是一种平衡。

(2)使用宏可提高程序的通用性和易读性,减少不一致性,减少输入错误和便于修改。例如:数组大小常用宏定义 ,可以理解数组大小代表具体含义,便于二次维护。

(3)预处理是在编译之前的处理,而编译工作的任务之一就是语法检查,预处理不做语法检查。

(4)宏定义末尾不加分号;

(5)宏定义写在函数的花括号外边,作用域为其后的程序,通常在文件的最开头。 (6)可以用#undef命令终止宏定义的作用域

(8)字符串" "中永远不包含宏

(9)宏定义不分配内存,变量定义分配内存。

(1)实参如果是表达式容易出问题

(2)宏名和参数的括号间不能有空格

(3)宏替换只作替换,不做计算,不做表达式求解

(4)函数调用在编译后程序运行时进行,并且分配内存。宏替换在编译前进行,不分配内存

(5)宏的哑实结合不存在类型,也没有类型转换。

(6)函数只有一个返回值,利用宏则可以设法得到多个值

(7)宏展开使源程序变长,函数调用不会

(8)宏展开不占运行时间,只占编译时间,函数调用占运行时间(分配内存、保留现场、值传递、返回值。

引用:宏定义其他冷门、重点知识(实际程序设计中也会用到)

  程序中有"NAME"则,它会不会被替换呢?

  可以吗?也就是说,可不可以用把标识符的字母替换成别的东西?

  程序中有上面的宏定义,并且,程序里有句:

  四个题答案都是否定的。

  第二个,宏定义前面的那个必须是合法的用户标识符

  第三个,宏定义也不是说后面东西随便写,不能把字符串的两个""拆开。

  第四个:只替换标识符,不替换别的东西。NAMELIST整体是个标识符,而没有NAME标识符,所以不替换。

也就是说,这种情况下记住:#define 第一位置第二位置

  (1) 不替换程序中字符串里的东西。

  (2) 第一位置只能是合法的标识符(可以是关键字)

  (3) 第二位置如果有字符串,必须把""配对。

  (4) 只替换与第一位置完全相同的标识符 。

  注意事项和无参宏差不多。

  则,输入FUN(345)会被替换成什么?

  其实,如果这么写,无论宏的实参是什么,都不会影响其被替换成"a"的命运。

  也就是说,""内的字符不被当成形参,即使它和一模一样。

  那么,你会问了,我要是想让这里输入FUN(345)它就替换成"345"该怎么实现呢?

  请看下面关于#的用法

3、 有参宏定义中#的用法

  #用于把宏定义中的参数两端加上字符串的""

  一般由任意字符都可以做形参,但以下情况会出错:

  STR())这样,编译器不会把“)”当成STR()的参数。

  STR(,)同上,编译器不会把“,”当成STR的参数。

  STR(A,B)如果实参过多,则编译器会把多余的参数舍去。(VC++2008为例)

  STR((A,B))会被解读为实参为:(A,B),而不是被解读为两个实参,第一个是(A第二个是B)。

  4、 有参宏定义中##的用法

  则会将形参str的前面加上L

  5、 多行宏定义:

##的用法和多行宏定义用法。

所以根据上述特点使用好宏定义,可以写出优美的代码,防止程序中的不一致错误,提高可移植性,可读性,方便修改维护。只要所有引用宏定义的地方都可以做到一次修改就完成。

另外在很多源代码中有些宏就是函数名称。直接重新定义了函数的名称,增加了代码的可阅读程度。

 条件编译:最开始可能也是出于提高程序设计的方便,对于源程序中某些内容只在特定条件下进行编译,于是乎条件编译就出现了,引用百科“一般情况下,源程序中所有的行都参加编译。但有时希望对其中一部分内容只在满足一定条件下才进行编译,即对一部分内容指定编译条件,这就是“条件编译”(conditional compile)。条件编译语句排版时,需考虑以下三种位置:1)条件编译语句块与函数定义体之间不存在相互嵌套(主要在(.h)文件中);2)条件编译语句块嵌套在函数体之外(主要在(.c)文件中);3)条件编译语句嵌套在函数体内 (主要在(.c)文件中)。条件编译指令将决定那些代码被编译,而哪些是不被编译的。可根据表达式的值或某个特定宏是否被定义来确定编译条件。”

作用:预处理时,把“文件名”指定的文件内容复制到本文件,再对合并后的文件进行编译。

1.双引号和尖引号的区别:双引号表示系统现在当前目录中寻找file所在的目录,若找不到,再按系统指定的

系统指定的标准方式寻找其它目录;尖引号仅查找按系统标准方式指定的目录.

2.一个#define只能指定一个包含文件,如 果要把多个文件都嵌入到源文件之中,则必须使用多个#define。

从理论上说,#include命令可以包含任何类型的文件,只要这些文件的内容被扩展后符合C语言语法。

2、被包含文件与其所在文件,在预处理后,成为一个文件,因此,如果被包含文件定义有全局变量,在其

它文件中不必用extern关键字声明。但一般不在被包含文件中定义变量。

一个c++程序可以包含很多个函数,源代码可能会很长,可以将这些函数分散保存在不同的源文件中,以多文件结构的形式来组织和管理源代码。

程序员可以在c++源程序中插入一些特殊指令,其作用是告诉编译器该如何编译本程序,正式编译源程序之前,编译器将预先处理这些特殊指令,因此它们被称为编译预处理指令

本章将介绍c++语言中几种特殊形式的函数,以及由c++编译系统为程序员提供的一些常用函数,这些函数被统称为系统函数

程序设计可能会面临比较复杂的数据,这时程序员需要基于基本数据类型来自己定义新的数据类型,这就是自定义数据类型。数组就是一种自定义数据类型,本章讲讲解几种常用的自定义数据类型

一个c++程序可以包含很多个函数,源代码可能会很长,可以将这些函数分散保存在不同的源文件中,以多文件结构的形式来组织和管理源代码。

结构化程序设计,将一个复杂任务分解成多个模块,编码时,讲每个模块定义成一个函数,基于团队协作开发程序时,可以将不同模块的程序设计和编码任务交由不同的程序员去完成,程序员各自独立编程,将所编写的源程序代码保存在自己的源文件中,这样可以互不干扰,基于团队协模式开发的c++程序会自然形成多文件结构

工程:一个程序开发项目又称为工程,一个工程通常包含多个源程序文件,c++语言的源程序文件名为.cpp。一个源程序文件可以包括多个函数,一个函数只能集中放在一个源程序文件中,不能将函数定义分开存放在不同文件中,一个程序工程中可以包括很多个函数,但是只有一个主函数,函数名必须为main()

编译器:使用编译器将c++语言翻译成机器语言,编译时,同一源程序文件中的所有函数,被统一编译,因此一个源程序文件被称为一个编译单元,每个源程序编译后都生成一个目标程序,目标程序的扩展名通常为.obj,目标程序是机器语言的程序,机器语言与cpu相关,相同的c++源程序,可以被不同编译器编译,生成不同机器语言的目标程序,从而运行于不同类型的cpu之上,

连接器:每个源程序文件编译后都生成一个目标程序文件,使用连接器,将多个目标程序连接在一起,最终生成一个可执行程序文件。在windows操作系统上,可执行操作文件的扩展名通常为.exe,可执行程序是最终的程序,可以被计算机硬件执行。软件产品销售的是可执行程序,而源程序则是软件厂家的机密。可执行程序文件可以复制、分发安装并执行,但很难被阅读修改。

多文件结构:在多文件程序中,一个文件定义的函数,可以被其他文件函数调用。可以被其他文件调用的函数称为外部函数,一个文件中定义的全局变量,也可以被其他文件中的函数访问,可以被其他文件访问的全局变量,被称为外部全局变量。换句话说,一个文件中的函数,可以调用其他文件中的外部函数, 也可以访问其他文件中的外部全局变量,调用外部函数时,需要先申明再调用,访问外部全局变量时,也需要现声明再访问,声明的作用,是将外部函数或外部全局变量的作用域,延申到本程序文件中来。下面以养鱼池造价测试程序进行拆分说明,多文件结构的具体语法和规则:

源程序文件1.cpp:内容包括全局变量和主函数的定义,假设由程序员甲所编写。

double r1,r2; //? 全局变量:分别保存圆形清水池和污水池的半径 /*下来语句将使用键盘输入原始数据,保存到对应的原始变量中*/

源程序2.cpp:包括两个用于计算的子函数,由程序员乙编写

1.1 声明外部函数原型和外部全局变量的语法细则

  • 声明外部函数原型,使用extern关键字,声明 外部函数时,extern关键字也可以省略。

    使用extern关键字的作用是更明确的指出,所声明的函数是一个外部函数,

  • 声明外部全局变量时,必须加上extern关键字,不能初始化。否则就变成了另外一条全局变量定义语句。连接时,会出现和其他文件定义的全局变量重名的错误。

  • 外部函数可以只声明不调用,外部全局变量可以只声明不访问,也就是说,只声明不使用不会引起语法错误。

  1. 源程序文件所定义的函数默认都是外部函数,可以被其他文件中的函数调用;所定义的全局变量默认都是外部全局变量,也可以被其他文件中的函数访问。
  2. 外部函数和外部全局变量被多文件结构中的所有文件共享,其他文件只要经过声明都可以使用这些函数和全局变量
  3. 多文件结构中,所有的外部函数不能重名,所有的外部全局变量不能重名,所有的外部函数和外部全局变量之间也不能重名

某些情况下,一个文件中定义的某些函数或全局变量只供文件内部的其他函数使用,c++语言可以将这些函数或全局变量定义成静态的,即静态函数静态全局变量,另外c++语言还可以定义静态局部变量

  1. 定义函数时,再函数头前加static关键字,该函数就被定义为静态函数。静态函数只能被本文件内部的其他函数调用,其他文件不能调用,即使经过声明也不行。

    static关键字,将所定义的函数作用域,限制在本文件范围内,禁止延申到其他文件,合理定义静态函数,可以防止其他文件对该函数的误调用。不同文件中的静态函数可以重名

  2. 全局变量具有文件作用域,静态和非静态指的是他们能否被其他文件中的函数访问。

    定义全局变量时,在语句前加static关键字,该变量就被定义为静态全局变量,静态全局变量只能被本文将内的函数访问,其他文件不能访问,即使经过声明也不行。

    static关键字,将所定义的全局变量作用域,限制在本文件范围内,禁止延申到其他文件,合理定义静态全局变量,可以防止其他文件对该全局变量的误操作。不同文件中的静态全局变量可以重名

  3. 局部变量只具有块作用域,只能在本函数内部使用、访问,任何时候都不能被其他函数访问,更别说其他文件中的函数,c++语言中,局部变量有静态和非静态之分,但含义与全局变量相比较,是不一样的。

    定义方法:在函数体或复合语句中定义的变量就是局部变量,在函数体或复合语句中定义局部变量时,在语句前加“static”关键字,该变量就被定义为静态局部变量。

    作用域:在源程序中具有块作用域(与普通局部变量相同)

    内存分配:普通的局部变量在执行时,是自动分配内存的,而静态局部变量是静态分配内存,就是程序执行时立即分配内存,一直到程序执行结束才释放;存放在静态储存区(与全局变量相同)。

    综上所述,静态局部变量其作用域与局部变量相同,程序执行时,其内存生存期和存放位置与全局变量一样。也就是说,静态局部变量是一种居于全局变量和局部变量之间的一种折中变量。

    演示程序:对比静态和非静态局部变量

    int x=0; //定义普通局部变量(非静态)x,初始化为0 x在动态储存区,计算机执行到其定义语句时,才分配内存,函数调用结束,其内存即被释放,第一次运行func结束时,x的值就被丢失了。再次调用时,从新赋值。 y储存在静态内存区,程序加载时即分配内存,并初始化为0,其内存释放要等到源程序结束。因此第一次调用结束释放func的栈帧,不会影响到y的值。

    c++语言中的static关键字,是一个多义词,使用这个关键字定义局部变量和全局变量时,其含义是不一样的,程序员应该根据上下文来推断它的含义。

团队协作开发时,加上某个程序员编写了一个c++程序文件(假设为1.cpp),其中定义了一组函数,也定义了若干全局变量。其他程序员需要访问1.cpp中的函数或全局变量,就需要对这些函数或全局变量进行声明,访问多少个外部函数或全局变量,就需要写多少条声明语句。对于项目组的所有程序员,只要访问1.cpp中的函数或全局变量,就都需要在自己的程序文件中编写声明语句。编写这些声明语句时重复而枯燥的工作,为此c++语言引入了头文件(header)的概念。

程序员甲在编写好1.cpp源程序文件后,另外再编写一个头文件,其中包含1.cpp所有外部函数和全局变量的声明语句。习惯上将这个头文件命名为1.h(或1.hpp),即与源文件同名,扩展名为.h或.hpp,项目组的其他程序员需要访问1.cpp中的函数或全局变量,只要再自己的程序文件增加语句:#include "1.h",该语句的作用就是将头文件1.h中的所有声明语句自动插入到该语句位置,这就消除了以往一条一条手工编写声明语句的繁琐。

double r1,r2; //? 全局变量:分别保存圆形清水池和污水池的半径 /*下来语句将使用键盘输入原始数据,保存到对应的原始变量中*/

头文件的内容主要包含,外部函数的申明,外部全局变量的申明,还包含一些共用的符号常量定义等等。插入头所使用的include指令是一种特殊指令,被称为编译预处理指令。

程序员可以再c++源程序中插入一些特殊指令,其作用时告诉编译器如何编译本程序。正式编译源程序之前,编译器预先处理这些特殊指令,他们被称为编译预处理指令,例如插入文件头所使用的"#include"指令

编译预处理指令,不属于c++语言的主体,是其附属组成部分,其作用时方便程序员使用c++语言编程,常用的编译预处理指令有:

在c++源程序中,编译预处理指令可以写在代码的任意位置,每条指令单独写一行,必须以井号"#"开头,不加分号";"结束符。

编写c++源程序时,程序员可以使用文件包含指令(#include)将某个指定文件的内容插入到程序代码的任意位置,通常是用于将某个头文件(.h)插入到源程序文件(.cpp)中。

编译一个c++源程序时,c++编译器会首先进行预处理,预处理时,编译器将指定文件的内容插入该代码位置,详细语法如下:

//编译预处理指令语法:文件报告指令
#include <文件名> //缺省路径时,编译器将到标准目录下搜索指定的文件。
#include "文件名" //缺省路径时,编译器将首先在当前目录下搜索,如果找不到指定文件,编译器将到标准目录下搜索指定的文件。
  • 文件名:通常不写完整的全路径文件名,而使用缺省值路径,只写头文件名,通常头文件被集中存放在2个目录下,一是c++编译器安装目录下的"include"子目录,该目录称为标准目录,二是源程序文件所在的目录,称为当前目录
  • #include <文件名>:缺省路径时,编译器将到标准目录下搜索指定的文件。
  • #include "文件名":缺省路径时,编译器将首先在当前目录下搜索,如果找不到指定文件,编译器将到标准目录下搜索指定的文件。
  • 文件必须以井号"#"开头,不加分号";"结束符。

用一个标识符来表示一段代码文本,这就称为一个(macro),其中的标识符称为宏名,所表示的代码文本称为宏文本,宏需要先定义再使用,习惯上,宏名用大写字母来命名。程序员编写指令时,使用宏定义指令#define来定义一个新的宏,这样凡是再后续代码中,需要用到宏代码的地方都可以用宏名来代替。宏可以使代码更加简洁,易于阅读。

宏定义指令是一个编译预处理指令,预处理时,编译器将源程序中所有的宏名自动替换回原来的宏文本,这称为宏替换或宏展开。c++语言有3种形式的宏:

已经定义的宏,可以用宏删除指令(#undef)删除。

  • 宏名需复合标识符的命名规则
  • 宏文本指定宏名所表示的字符串,可以由任意字符组成,不需要加引号。

无参宏主要用于定义符号常量。

  • 宏名:复合标识符的命名规则
  • 参数列表:指定若干可被替换的参数,参数间用逗号","隔开,
  • 宏文本:指定宏名所表示的字符串,可以由任意字符组成,不需要加引号,字符串通常是包含参数的表达式

利用有参宏,可以实现简单的函数功能。通过实例代码,可以写出一个计算圆面积程序。

使用有参宏的实参可以是表达式,例如:

AREA(3+4) //执行预处理时,会展开为3.*3+4,这显然时不对的, 这是因为定义有参宏时的没有考虑到运算符计算的优先级问题。
  • 宏名需要符合标识符命名规范,无宏文本。

定义空宏是为了配合条件编译指令使用。下面将做介绍,已经定义的有参宏、无参宏、空宏,都是用宏删除指令(#undef)删除。

#undef 宏明//宏名就是指定将被删除的宏

删除宏以后,宏就不能再继续使用了。但可以再次被定义

程序开发过程中,源程序可能有多个版本,例如不同语种的版本,或者是调试版本和发行版本,如果用不同的程序文件存放不同版本的源代码,文件数量将迅速增加,也容易导致代码修改时的不一致问题,条件编译指令运行程序员将不同版本的源代码写在同一程序文件中,便于管理维护修改,条件编译指令,有2种格式。

编译预处理指令语法:条件编译指令(格式1)

  • 编译器在编译这段代码时,如果指定的空宏已定义,则编译代码段1,否则编译代码段2
  • 如果没有代码段2,则可以省略#else
/* 以下2条指令是中英文版公用的指令,故无需指定条件编译*/

编译预处理指令语法:条件编译指令(格式2)

  • 编译器在编译这段代码时,如果指定的常量表达式结果不为0,则编译代码段1,否则编译代码段2,常量表达式只能包含常量或符号常量
  • 如果没有代码段2,则可以省略#else
/* 以下2条指令是中英文版公用的指令,故无需指定条件编译*/

3.1 带默认形参的函数

  • 定义函数或声明函数原型时可以指定形式参数的默认值,这就是带默认形参值的函数
  • 调用带默认形参值得函数时,如果给出实参值,则将实参值赋给形参变量,如果没有,则将默认值赋给形参变量。

例子:人民币汇率转换表(2015年6月19日)

带默认形参值函数的语法细则:

  • 带默认值的形参:调用时如果给出实参值,则将实参值赋值给形参变量,如果没有,则将默认值赋值给形参变量,不带默认值的形参在调用时必须给出实参,否则属于语法错误。
  • 在函数原型声明中指定默认值:如果函数定义在调用语句之后,应该在调用语句之前对函数原型进行声明。可以在声明语句中指定形式参数的默认值,此时函数定义中不能再指定默认值。函数具有文件作用域,同一函数在相同的作用域中只能指定一次默认值。
  • 如果函数定义在其他文件中,应该在调用语句之前声明外部函数的原型,可以在声明语句中指定形式参数在本文件中的默认值,并且可以与原函数定义中的默认值不同。
  • 同一函数在不同的作用域中可以指定不同的默认值。如果多个默认值同时有效,调用函数时根据局部优先原则选择默认值。

演示程序:在不同作用域为函数形参指定默认值

fun(); //使用文件作用域的默认值,函数fun的显示结果为10 fun(); //使用块作用域(局部优先),fun的显示结果为20 void fun(int p) //在申明函数时,已经指定了p的默认值,此时不能再指定

带默认值的形参,必须定义在形参列表的后面,形参列表中,可能有的形参带默认值,有的不带,定义函数或声明函数原型时,必须把带默认值的形参放在不带默认值的形参的后面。

调用函数时通过函数名指定调用哪个函数,函数名是函数的标识,通常,同一文件之中的函数不能重名,不文件中的非静态函数(外部函数)之间也不能重名,

c++语言规定,如果两个函数的形参个数不同或数据类型不同,那么这两个函数就可以重名,重名的函数被称为重载函数。

将两个或两个以上函数定义为重载函数的原因,是这些函数的功能相同或相近,使用相同的名字方便码农记忆,也不用绞尽脑子去想如何起不同的名字

// Max为重载函数名,功能是求最大值。

C++在编译语言时,由编译器根据调用语句中实参的个数和类型,来自动调用形参最匹配的那个重载函数,简而言之就是通过形参和实参的匹配原则来调用重载函数。

注意:在应用重载函数时,如果两个函数仅仅是返回值类型不同或形参名不同,那么不能讲这两个函数命名为重载函数,否则会出现语法错误。也不能将两个功能差异很大的函数命名为重载函数,虽然没有语法错误,但是会在运用中给程序员造成混淆。

调用函数:函数跳转和数据传递需要执行一些额外的操作,实现相同的功能,单一主函数程序比主函数+子函数程序执行速度更快,及函数跳转会降低程序的执行效率,但函数是团队分工协作和代码重用的基础,函数能够提高程序的开发效率。

内联函数:内联函数是一种特殊的函数,它在保障程序开发效率的同时,不会减低程序的执行效率。

其原理是:编译源程序时将函数代码直接嵌入到每一个调用语句处,而在执行时不再进行函数跳转和数据传递。

//使用inline关键字申明内联函数

内联函数需要在被调用的函数前加上inline关键值进行申明。

内联函数需要是简单的函数,编译器不能保证程序员所定义或申明的内联函数最终都能按照内联的方式进行编译,如果该函数的函数体比较复杂,比如有循环语句,那么编译器将自动按照非内联函数的方式进行编译。

内联函数只有被多次调用,其执行效率才能体现出来,因此一般只是将频繁调用的简单函数,定义为内联函数。

3.4 带形参和返回值的主函数

计算机系统包括硬件和软件2部分,操作系统是计算机中最基础最重要的软件,操作系统直接运行于硬件之上,启动计算机后,计算机自动加载执行,只有在操作系统运行之后,其他软件才能运行,用户执行某个程序,其实是向操作系统下达了一个执行程序的指令。在命令行操作系统中,如果要执行某个程序,就可以下达类似于:test.exe或test指令,然后回车,操作系统接收该指令,然后将程序文件读入内存,找到该程序的主函数main,从主函的第一条指令开始执行,程序执行结束后,从主函数main返回操作系统。

可以将操作系统执行某个程序的过程,理解成操作系统调用该程序主函数main 的过程。

用户在向操作系统下的程序执行指令的同时,可以传递某些原始数据。也就是说,一个程序的主函数main可以定义形参,接收数据,可以定义返回值,将自己的运行状态返回给操作系统。

c++程序中的主函数main()的语法细则:

  • 一个c++程序必须有并取只有1个名为main的主函数。
  • 主函数是程序执行的起点
  • 主函数的类型应该为int型,需要返回一个int型整数
    1. 定义形参来接收实参数据
    2. 也可以省略形参(此时操作系统所传递过来的实参数据将被忽略)

定义有形参的主函数main:

  • argc标识所接收到的参数个数;
  • argv是一个char型指针数组,数组元素分别为argv[0]~arbv[argc-1]。参数以字符串形式传递,其中argc[0]所指向的字符数组存放的是该文件程序的文件名,argc[1]所指向的字符数组存放的是第1个实参数据,......;
  • 采用无参形式时,操作系统所传递过来的实参数据将被忽略
  • 主函数通过返回值将自己的运转状态返回给操作系统,通常0表示正常,用-1表示异常。

代码示例:带形参和返回值的主函数定义格式

Microsoft c++编译器对主函数语法处理的差异

美国微软公司开发的c++编译器,在对c++语法的处理上有一定的差异,主要提现在visual c++ 6.0和visual studio系列这两个集成开发环境的使用上。

  • visual c++ 6.0中,主函数可以没有返回值,也就是主函数可以定义为void,例如:

    //此处定义函数体代码 return; //如果该语句是最后一条语句,则可以省略
  • 该环境在主函数的定义上,于其他环境有所区别,该系列可以使用应用程序向导来新建开发项目,例如:假设我们新建win32控制台应用程序,该环境将自动创建一个c++源文件,该文件的文件头和主函数如下:

    system("pause");//添加该语句,暂停程序执行,以便程序员检查运行结果。

    这是因为visual studio集成开发环境同时支持ANSI编码和Unicode编码的程序开发,使用_tmain()命名主函数,可以很方便的在两种字符代码之间切换。

    初学者使用visual studio时,可以选择空项目,然后自己添加新建源程序文件,在return语句之前,记得加入一个system("pause");添加该语句,暂停程序执行,以便程序员检查运行结果。

  • 使用Windows图形界面程序时

    需要将主函数定义为WinMain()或_tWinMain(),这个时windows运用程序执行的起点。这两个名字是微软公司为Windows图形界面的主函数所指定的函数名。并不是c++标准指定的。

假设要求一个数N的阶乘,也就是求N!

递推法利用已知条件(0!= 1)和地推公式:

逐步递推求出1!、2!、... ...、直到求出N!

递推法求解问题的基本思想是从已知条件出发,根据地推公司由简到繁,逐步逼近,最终求出问题的解,这种方式也称为正向递推。正向递推的每一步骤都是已知问题n-1的解,递推求解问题n的解,这些递推步骤是在重复计算递推公式,可使用循环结构来描述递推算法。

递归法是程序设计中一种基于“函数嵌套调用”原理求解问题的方法。递归法求解问题的过程分2步完成:

  1. 按照递推公式由繁到简,将求问题n的解降阶成求问题n-1的解,直到满足已知条件(称为递归终结条件)不能降阶为止,这个过程称为逆向递推;

  2. 函数逐级返回结果,最终求出问题的解,这个过程称为回归。

c++语言将直接或间接调用自身的函数,称为递归函数

与前面所讲解的重载函数,带默认形参值、内联函数的函数不同的是,递归函数不仅仅是一个语法概念,其背后还暗含了递归法求解问题的算法设计思想。

递归函数的定义和调用:

函数类型 函数名(形式参数列表)
 if(递归终结条件) //条件一
 取得已知结果 //条件二
 按照递归公式调用自身 //条件三
  1. 计算机在执行函数调用语句跳转到被调函数时,为其形参及函数体中定义的局部变量分配内存,建立被调函数的栈帧
  2. 函数可以嵌套调用,每增加一级函数调用,栈帧就增加一个,每退出一级函数调用,栈帧就减少一个
  3. 计算机执行递归函数的过程就是递归函数不断嵌套调用自身不断建立新栈帧的过程,即逆向递推的过程。
  4. 递归终结条件成立时停止调用,开始逐级返回结果,退出递归函数并依次释放栈帧,这就是回归的过程。

递归的函数嵌套调用,次数必须是有限的,无线调用,会不停建立新的栈帧,最终超出程序的内存占用,会导致栈溢出的错误。

  1. 递归法比递推法速度慢。

    递推算法使用循环算法来实现,速度块,递归算法,使用函数调用,需要额外的操作, 因此速度慢

  2. 递归法比递推法适用范围广

    能用递推求解的算法,一定可以使用递归来求解,反之则不然。

程序员编写的函数可以在下个项目中继续使用,这就是重用函数的代码,随着时间的推移,程序员将积累越来越多的函数,重用这些函数,可以显著的提高开发效率

c++也预先编写了很多常用的函数,提供给广大程序员使用,这些函数统称为系统函数

c++语言是在C语言基础上发展而来的,C语言是机构化程序设计语言,系统函数是其重要的附属组成部分。C语言函数的源代码,被编译成机器语言,以库文件的形式,随编译器系统提供。库文件的名称,通常为”.lib“,库文件通常存在编译器安装文件目录中的lib子目录中,这些库文件被称为标准C库

调用标准C库中的系统函数,都需要先声明其函数原型,为了方便程序员,C语言预先编写好这些系统函数的原型声明语句,并按功能分别保存在若干个不同的头文件中。程序员只要用#include指令,包含相应的头文件,就可以调用这些系统函数。程序链接时,被调用的系统函数代码将被连接到可执行程序文件之中。

c++语言全盘继承了C语言的标准C库,另外又增加了一些新的库,新库中包括了新增的系统函数,但更对的是新增了面向对象的系统类库,这些新库,就被称为c++标准库。c++标准库引入了命名空间的概念,所有的新增的系统函数和系统类库,都定义在命名空间std当中。

4.1 C语言系统函数

系统函数极大的扩展了C语言的功能,程序员能够站在更高的起点上开发程序,程序员在调用系统函数前,需阅读系统提供的手册,学习各系统函数的功能及调用语法,并用#include指令包含相应的头文件。标准C库的头文件的扩展名都是“.h

C语言本身没有输入输出语句,而是通过输入输出函数来实现输入和输出,下面介绍两个标准的输入函数以及2个标准的输出函数。使用这些输入输出函数,需要使用#include指令包含头文件<stdio.h>

格式化输入函数:scanf

  • 参数format是格式控制字符串,其中包括格式符和分隔符,格式符是以”%“开头的字符串,用于指定输入数据的类型或格式,参见表6-2
  • 变量地址列表指定保存输入数据的变量地址,一次可为多个变量输入数据,此时应为每个变量指定一个格式符,格式符应与变量的数据类型一致,多个变量地址之间用逗号”,“隔开,多个格式符之间通常用空格或逗号隔开,输入数据时相应地也用空格或逗号隔开,以回车结束。
  • 返回值是int型,返回输入数据的个数。
  • 调用函数时,程序暂停执行,等待用户从键盘输入数据,以回车结束。

格式化输出函数printf:

  • 参数format是格式化控制字符串,其中包括格式符和非格式符,格式符是以“%”开头,用于指定输出数据的类型或格式,参见表6-2,非格式符原样输出
  • printf中的格式符可以指定输出数据的域宽(即显示时的占位宽度),实际数据达不到域宽时补空格,输出实数时还可以指定输出的精度(即输出几位小数)
  • 表达式列表指定需要输出的常量、变量表达式,一次性可以输出多个表达式,此时应为每个表达式指定一个格式符,格式符应与表达式结果的数据类型一致,多个表达式之间用逗号“,”隔开
  • 返回值是int型,返回输出数据的个数
  • 调用该函数时将从右到左的顺序计算各表达式,然后按从左至右的顺序显示各表达式的结果。
  • 调用该函数时,程序暂停执行,等待用户从键盘输入一个字符串,以回车结束
  • 返回值是int型,返回所输入字符的ASCII编码值
  • 调用该函数将变量c中的字符输出到显示器上,实际上,变量c中保存的式字符的ASCII码值,
  • 返回值是int型,返回所输出的字符的ASCII码值

求一元二次方程根的演示程序

首先要判断,则,否则x无实数根

4.3 字符串处理函数

4.4动态内存分配函数

/* 函数malloc返回所分配内存的首地址,且必须制定分配的内存单元的大小,返回值其类型为“void”,

我们在编写程序时,可以使用各种渠道得到的函数,但是C语言和C++都规定,所有的外部函数不能重名,但我们在使用外来函数时,不同的机构不同的程序员开发的函数难免会重名,比如山东的德州和美国的德州,单独看德州两个字,这两个地方重名了,但是如果加上国度,这两个地方就能够准确的区分开来,这个国度就是命名空间的概念。

C++引入了命名空间的概念,不同的程序员在各自的命名空间内定义外部函数和全局变量,就可以消除重名的问题。

在命名空间中定义函数和全局变量,使用namespace关键值进行申明。然后将函数和全局变量定义在其后的一对大括号{}之中。例子如下:

访问命名空间中的函数和全局变量

通过命名空间名称+双冒号::+全局变量标识符/函数名的方式进行访问:

双冒号(::)称之为作用域运算符

2、使用关键字using先申明各个标识符的命名空间

也可以先声明各个标识符的命名空间,使用关键字using,如果事先声明了要访问的各个标识符,再访问的时候,可以省略命名空间名。

//再通过标识符访问,访问时可以省略命名空间

3、统以申明命名空间的所有标识符

也可以先统一声明命名空间里的所有标识符,再通过标识符直接访问。

//再通过标识符访问,访问时可以省略命名空间

通过这种方式访问方式是最简单直接的。

namespace都是C++语言保留的关键字,C++语言还有个默认的匿名的命名空间,如果程序员编写函数或全局变量,定义时没有定义在任何命名空间里,则默认就属于该匿名命名空间,C++标准库就引入了命名空间的概念,所有函数实体,例如外部函数、全局变量或对象等,都定义再命名空间std当中,使用这些函数实体,除了需要使用#include指令包含相应的头文件之外,还需要声明其命名空间,例如需要使用cin>>和cout<<对象输入输出数据,则除了需要用#include指令包含头文件<iostream>之外,还需要声明其命名空间std,就是using

c++语言全盘继承了c语言的语法规则,同时也全盘继承了C语言的标准C库。c++标准库新增了一些系统函数,更多是新增了面向对象程序设计的系统类库

1、c++语言系统类库

经常使用的cin,cout输入输出语句,实际上就是c++标准库预先定义好的对象,cin是c++标准库定义的一个标准输入流对象,而cout则是一个标准输出流对象,使用cin和cout对象使用#include指令包含相应的文件头“iostream”,还需要声明其命名空间“std”

c++语言是在c语言基础上发展而来的,可以在很大程度上替代c语言,例如

  1. 用cin,cout输入/输出流对象,代替原来的输入输出函数
  2. 用字符串类string,代替原来的字符数组和字符串处理函数
  3. 用new和delete这2个运算符,代替原来的动态内存分配函数:malloc和free

C语言是机构化设计语言,它使用函数来提很多常使用的功能,c++语言继承了这些机构化程序设计方法,但又新增了面向对象程序设计方法,就是使用类和对象来实现程序功能。

4.8 多文件结构下程序员与函数的关系

  1. 为别人提供函数的程序员
    • 将常用功能或算法定义成函数,保存到源文件(扩展名为.cpp);将源程序文件编译成目标代码文件(扩展名为.obj);通常还会进一步将目标代码打包成一个函数库文件(扩展名通常为.lib)
    • 为函数库中的函数编写声明语句,集中保存在一个头文件中(扩展名为.h)
    • 发布或小数函数库产品,其中包含库文件和头文件,库文件为目标代码(即机器语言),其他程序员只能调用,无法阅读修改,而头文件是函数声明语句的源码,其它程序员可以阅读,以了解函数的功能与调用接口
    • 可以使用系统函数,也可以获得(比如购买)第三方开发的函数库
    • 编写程序时调用别人函数库中的函数,以实现某种特定的功能,调用前需要用#include指令包含相应的文件头
    • 连接时,连接器将函数库中被调函数代码与自己的程序连接到一起,形成最终语句。

运用结构化程序设计,可以将一个大型功能,分解成多个模块,分而治之,c++语言以函数的形式来描述各个模块,这就是函数的定义。然后再通过调用,将各个算法模块组装起来,最终形成一个完整的算法流程。

一个编写好的c++函数可以备同一项目的多个程序员调用,也可以在今后的项目中继续使用,这就是函数代码的重用。

市场上还要很多厂家为程序员提供编写好的,可以实习的各种不同功能得函数库,本章将以Windows图形用户界面GUI程序,win32 AP1函数库进行讲解如何开发一个windows图形界面程序

Windows图形用户界面程序编程原理

  1. 使用图形界面设计器设计图形界面窗口,设计好的窗口备称为资源
  2. 编写主函数,创建并显示窗口。
  3. Windows系统负责监控鼠标并捕获用户操作,如果用户对窗口进行了操作(例如点击了某个按钮),那么Windows操作系统自动调用该窗口对应的处理函数(被称为窗口过程
  4. 调用时会以消息的形式传递参数,消息参数用于区分用户进行了何种操作,消息参数以整数来标记用户所做的不同操作,为了方便程序员,win32 API将这些编号定义成易于记忆的符号常量,例如WM_COMMAND(表示点击了某个按钮或菜单)、WM_CLOSED(点击了关闭窗口的x号)等。

windows操作系统调用某个窗口过程,并传递消息参数,这边称为向该窗口过程发送消息;窗口过程接收消息,然后进行处理,这被称为对消息的响应。

Windows图形用户界面运行方式:

  1. 主函数负责创建程序窗口,主函数是由程序员编写语句定义的

  2. Windows将捕捉鼠标操作,调用窗口过程函数,并传递WM_COMMAND消息,窗口过程不是程序员在主函数中编写函数调用语句来调用的,而是由Windows操作系统自动调用的。像窗口过程这样由Windows操作系统调用的函数,称为回调函数。

  3. 窗口过程函数,根据接收到的消息参数,可以判断出用户进行了何种操作,并按照程序功能执行相应的算法并显示结果。因此程序员需要在窗口操作过程中编写功能实现的语句。

  1. 使用图形界面设计器来设计窗口界面
  2. 新建一个c++源程序文件,在其中编写对应的完整的c++程序代码。

c++语言提供了比较完善的基本数据类型,其中包括整型(int、short、long)、浮点型(float、double)、字符型(char)和布尔类型(bool)等4大类

程序员可以根据需要为这些基本数据类型重新命名一个别名,或基于这些基本数据定义新的复杂数据类型,这些类型被统称为自定义数据类型,可以使用自定义数据类型来定义变量。

本节将介绍“typedef”类型定义语句,以及枚举、结构体、联合体等常用自定义数据类型的定义和使用方法。

可以使用typedef关键字为基本数据类型,命名一个别名,或者为指针,数组定义新的数据类型

为基本数据类型命名一个别名:

通过typedef统一数组类型的使用,也能简化代码,是程序易于阅读。也可以将类型定义与编译预处理指令搭配使用。

布尔类型的一个主要特征是其值域只有2个取值,即真和假,分别用关键字true和false表示,实际的程序处理任务也会碰到和bool类型相似的数据类型,他们的值是有限的,我们称他们的值域是可枚举的。例如一个星期只有星期一到星期日,这个值域就是可枚举的值域。

c++语言可以将值域可枚举的数据定义成新的数据类型,这些数据类型被统称为枚举类型,值域中的每个取值称之为一个枚举元素。

  • enum是定义枚举类型的关键字
  • 枚举类型名需要符合标识符命名规则,
  • 枚举常量是表示各个枚举元素的名称,须符合标识符的命名规则,相当于是一个符号常量
  • 计算机内部储存枚举型数据时,用整数表示每个枚举常量,默认情况下,枚举常量1=0,枚举常量2=1,... ...,枚举常量n=n-1,可以定义时为枚举常量另行指定其他的值。
x=mon;//为x赋值,mon是枚举常量,内部数值是1

注:枚举变量不能直接用整数赋值,因为两者数据类型不同。

6.3 联合体类型和结构体类型

c++语言可以将多个变量组合在一起形成一个逻辑上的整体,使变量成为整体的一个成员,变量成员的类型可以相同,也可以不同,将多个变量成员组成的整体定义成新的数据类型,这种数据类型能够描述程序涉及所面临的复杂数据。

  • 如果多个变量成员不会同时使用,那就可以将它们定义成联合体类型,联合体类型的特点是变量成员共用同一内存单元,同一时刻只能保存一个成员的数据。定义联合体的目的是节约内存。

    union 联合体类型名{ 数据类型1 变量成员名1; 数据类型2 变量成员名2; ... ... 数据类型n 变量成员名n;};
    
    • union是定义联合体类型的关键字
    • 联合体类型名需符合标识符的命名规则
    • 变量成员之间的数据类型可以相同,也可以不同,但变量名不能相同。
    /*定义联合体变量数据类型 UType*/
    /*使用联合体类型可以定义联合体变量,定义时需要union关键字,联合体变量定义好后,
    可以访问其中的成员,访问联合体成员的语法形式是:联合体变量名.成员名其中圆点"."称为成员运算符*/
    

    联合体类型也称为共用体类型,每个联合体变量所占用的字节数,等于其最大成员的字节数。因为联合变量的成员是共用同一内存单元,故联合体变量同一时刻只能保持一个成员的数据。应当准确理解联合体类型的这个特点,否则会造成数据丢失。

  • 如果多个变量成员同时使用,那就将他们定义成结构体类型,结构体类型的特点是个变量成员单独分配内存单元,分别保存各自的数据。定义结构体数据的目的是存储复杂数据。

    c++语言可以将多个变量组合在一起形成逻辑上的整体,使变量称为整体的一个成员,变量成员的类型可以相同,也可以不同,如果多个变量成员需要同时使用,那就可以将它们定义成结构体类型。

    /*在上述代码种,学生的学号,姓名,成绩,年龄都存在关联关系可以将其看作一个整体。 它们都是某个学生个人信息的一部分。学生信息需要同时包含这些部分,否则信息就不完整了。 所以可以将学生信息定义成结构体类型*/
    • struct是定义结构体类型的关键字;
    • 结构体类型名需符合标识符的命名规则
    • 变量成员之间的数据类型可以相同,也可以不同,但变量名不能相同。

    与联合体变量不同的是,结构体数据类型的各个成员,单独分配内存单元,分别保存各自的数据,每个结构体变量占用的字节数,等于其所有成员占用字节数的总和。

    我们也可以定义结构体的指针变量来保存某个结构体的内存地址。然后通过指针变量间接访问结构体变量及其成员,实例:

    由于通过(*p)的方式访问结构体成员比较繁琐,c++语言引入了一种新的更加直观的运算符,就是指向运算符:”->“,

我要回帖

更多关于 c语言带参函数 的文章

 

随机推荐