C++博客 :: 首页 :: 联系 :: 聚合  :: 管理
  117 Posts :: 2 Stories :: 61 Comments :: 0 Trackbacks

常用链接

留言簿(8)

搜索

  •  

最新评论

阅读排行榜

评论排行榜

      函数是C++语言的基本功能单位,一般来说,函数的功能单一,函数代码不可过长。函数接口设计清晰明了,返回值和错误代码的返回分开。除了普通的函数之外,还有内联函数、回调函数等。在不同语言之间的调用函数,提供了不同的调用规范。所谓预处理是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所做的工作。预处理是C++语言的一个重要功能,它由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分进行处理,处理完毕自动进入对源程序的编译。C++语言提供了多种预处理功能,如宏定义、文件包含、条件编译等。合理地使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。

1、 在C++中调用C编译器的函数

      1.1   C++中调用C编译器的函数的原理

      C++语言,曾经有一叫法是带类的C语言,那也就是说它的前身是C语言,所以说,用C写的函数,基本上是可以在C++中调用的。比如说,C语言标准库中的函数,在C++中是可以调用的。但是    C++毕竟跟C不同,那怎样才能顺利地把C函数应用到C++中呢?下面来讨论这个问题。

      C++语言支持函数重载,C语言不支持函数重载。函数被C++编译后在库中的名字与C语言的不同。

      1.2   实例代码

      假设某个C函数的声明如下:

    void foo(int x, int y);

      该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字用来支持函数重载和类型安全连接。由于编译后的名字不同,C++程序不能直接调用C函数。C++提供了一个C连接交换指定符号extern”C”来解决这个问题。例如

extern “C”
{
void foo(int x, int y);
//其他函数
}

或者写成
extern “C”
{
#include “myheader.h”
//其他C头文件
}

      这就告诉C++编译器,函数foo是个C连接,应该到库中找名字_foo而不是找_foo_int_intC++编译器开发商已经对C标准库的头文件做了extern”C”处理,所以可以用#include直接引用这些头文件。

      如果调用自己写的C函数,可以把C函数集合成一个库,然后把其相应的头文件用extern”C”处理,用#include直接引用这些头文件即可。

2、 内联函数和宏的比较

      2.1 内联函数的概念

      在C++中,可以将函数指定为内联(inline)的,这样,编译器会在调用该函数的每个地方产生该函数的一个副本。使用内联函数可以减少函数调用带来的开销,不过应该在函数很简单而且在调用次数相对较少的情况下,才将其指定为内联的。

      内联函数的基本思想在于将每个函数调用以它的代码体来替换。用不着统计专家出面就可以看出,这种做法很可能会增加整个目标代码的体积。在一台内存有限的计算机里,过分地使用内联所产生的程序会因为有太大的体积而导致可用空间不够。即使可以使用虚拟内存,内联造成的代码膨胀也可能会导致不合理的页面调度行为(系统颠簸),这将使你的程序运行慢得象在爬。(当然,它也为磁盘控制器提供了一个极好的锻炼方式:)
      过多的内联还会降低指令高速缓存的命中率,从而使取指令的速度降低,因为从主存取指令当然比从缓存要慢。另一方面,如果内联函数体非常短,编译器为这个函数体生成的代码就会真的比为函数调用生成的代码要小许多。如果是这种情况,内联这个函数将会确实带来更小的目标代码和更高的缓存命中率!要牢记在心的一条是,
inline指令就象register,它只是对编译器的一种提示,而不是命令。也就是说,只要编译器愿意,它就可以随意地忽略掉你的指令,事实上编译器常常会这么做。例如,大多数编译器拒绝内联"复杂"的函数。
      摘自
Effective   C++2e,建议看一下《深度探索C++对象模型〉

      2.2 内联函数和宏的比较

      两者相同之处是,在其出现的地方将代码替换。但是区别很大。

      对于宏来说,C++中不赞成使用,除非程序中一定要用宏时。宏只是在编译前(编译处理阶段)将程序中有关字符串替换成宏体,也不进行参数类型等的检查,容易出错

      对于内联函数来说,其不是通过函数调用实现的,是在调用该函数的程序处将它展开,这是在程序的编译期间完成的,期间进行诸如类型检测等过程,减少了错误的发生

      2.3 应用规则

      ·宏尽量少用,或不用。

      ·内联函数不宜过大,过复杂

      ·内联函数是建议性而非指令性的,过大或过复杂,会被系统忽略,而当成一般函数用了。

      ·内联函数一般不要超过5行,而且经常要用时才声明称内联函数

3、 #include<filename.h>和#include “filename.h”的区别

      3.1 包含文件

      #inculde预处理指令的作用是在指令处展开被包含的文件,包含可以是多重的,也就是说,一个被包含的文件中海可以包含其他的文件。

      3.2 包含头文件有两种格式

      一种就是#include<文件名>,另一种是#include ”文件名

      用尖括号包括头文件,这是C++的标准方式,一般这些头文件都存放于C++系统目录中的include子目录下,C++预处理碰到这种情况后,就到include目录下搜索给出的文件。

      用引号包括头文件,适用于用户自己写的头文件中,预处理碰到这种格式,就先在当前目录下进行搜索,如果找不到,再到系统目录下进行搜索。

4、 回调函数的概念与操作技巧

      4.1 回调函数的概念

      回调函数就是在调用某个函数(通常是API函数)时,将用户自己的一个函数(这个函数为回调函数)的地址作为参数传递给那个函数。而那个函数在需要的时候,利用传递的地址调用用户自己定义的函数,这时在回调函数中处理消息或完成一定得操作。

      4.2 回调函数的实现

      回调函数调用是利用其函数地址调用函数的,实现函数的功能。要实现一个回调函数,首先要实现一个函数指针,让这个指针指向功能函数,然后利用函数指针,隐式调用函数。

#include<iostream.h>
//声明功能函数:
void fun()
{
  cout
<<"this is a callback test"<<endl;
}

//声明指针函数:
void (*p) (); //指针p指向一个参数为空,返回值为void的功能函数
//声明调用函数caller,函数的参数是返回值为void,参数为空的函数指针:
void caller(void (*ptr)())
{
  ptr();
}

//主函数实现
int main()
{
  p
=fun;
  caller(p);
  
return 0;
}

       4.3 回调函数的应用

       回调函数在Windows系统编程中用的较多,比如MouseProGetMsgProcEnumWindowsDrawState

例如API函数

EnumFonts(HDC hdc,
LPCTSTR lpFaceName,
FONTENUMPROC,
PARAM lParam
);

在使用时需要定义一个回调函数:

Int CALLBACK EnumFontsProc(
CONST LOCFONT 
*lplf,
CONST TEXTMETRIC 
*lptm,
DWORD dwTtype,
LPARAM lpDate
);

      然后写EnumFonts(hdc,null,EnumFontsProc)即可。在使用计时器时,把计时器消息发送给程序的另一个函数,接收这些计时器消息的函数被称为“回调”函数,这是一个在程序中,但是由Windows调用的函数。程序的窗口过程实际上也是一类回调函数,当注册窗口类时,要将函数的地址告诉Windows,当发送消息给程序时,Windows调用此函数。

5、 函数的调用规范

      当程序被执行时,CPU无法知道函数的调用需要多少个、什么样的参数,那么也就是说计算机无法知道如何给这个函数传递参数。函数的调用时由调用者和被调用者协作完成的。计算机为此专门提供了栈的数据结构,支持参数的传递。函数调用时,参数执行压栈操作,然后开始调用函数,函数被调用以后,参数值出栈,参与函数功能实现后,进行栈的清理工作。在参数传递中,当参数多于一个时,按照什么顺序压栈,函数调用后,由谁把堆栈回复。

      在高级语言中,通过函数调用规范(Calling Conventions)来说明这三个问题。常见的调用规范有:stdcallcdecl

      1.    stdcall调用规范

      这是Win API函数使用的调用规范。参数从右向左一次传递并压入堆栈,由被调用函数负责堆栈的清退。该规范生产代码比_cdecl更小,但当函数有可变个数的参数时会转为_cdecl规范。在Windows中,宏WINAPICALLBACK都定义为_stdcall

      stdcall调用规范声明的语法为:

int _stdcall function(int a, int b)

      利用stdcall调用,参数从右向左压入堆栈,函数自身修改堆栈,调用时函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸。以上述这个函数为例,参数b首先被压栈,然后是参数a,函数调用function(1,2),而在编译时,这个函数的名字被翻译成_function@8

      2.   cdecl调用规范

      cdecl调用约定又被称为C调用约定,是C语言默认的调用约定,它的定义语法是:

int function(int a, int b) //不加修饰就是C调用约定

int _cdecl function(int a, int b) //明确指出C调用约定

      cdecl调用约定的参数压栈顺序是和stdcall一样的,参数首先由右向左压入堆栈。所不同的是,函数本身不清理堆栈,调用者负责清理堆栈。由于这种变化,C调用约定允许函数的参数个数是不固定的,这也是C语言的一大特色。对于前面的function函数,该修饰自动在函数名前加前导的下划线,因此函数名在符号表中被记录为_function

      3.   函数调用约定导致的常见问题

      如果定义的约定和使用的约定不一致,将导致堆栈被破坏,导致严重问题,下面是两种常见的问题。

      ·函数原型声明和函数体定义不一致

      ·DLL导入函数时声明了不同的函数约定

如果还想获得更多关于《Visual C++代码参考与技巧大全》的内容,可点击下面网址,

http://www.cppblog.com/kangnixi/archive/2010/01/13/105591.html

posted on 2010-01-26 14:05 烟皑 阅读(470) 评论(0)  编辑 收藏 引用 所属分类: 《Visual C++代码参考与技巧大全》学习笔记

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理