随笔 - 9  文章 - 1  trackbacks - 0
<2019年12月>
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

常用链接

留言簿(1)

随笔档案

搜索

  •  

最新评论

阅读排行榜

评论排行榜

一回调函数

我们经常在C++设计时通过使用回调函数可以使有些应用(如定时器事件回调处理、用回调函数记录某操作进度等)变得非常方便和符合逻辑,那么它的内在机制如何呢,怎么定义呢?它和其它函数(比如钩子函数)有何不同呢?

使用回调函数实际上就是在调用某个函数(通常是API函数)时,将自己的一个函数(这个函数为回调函数)的地址作为参数传递给那个函数。

而 那个函数在需要的时候,利用传递的地址调用回调函数,这时你可以利用这个机会在回调函数中处理消息或完成一定的操作。至于如何定义回调函数,跟具体使用的 API函数有关,一般在帮助中有说明回调函数的参数和返回值等。C++中一般要求在回调函数前加CALLBACK(相当于FAR PASCAL),这主要是说明该函数的调用方式。

至于钩子函数,只是回调函数的一个特例。习惯上把与SetWindowsHookEx函数一起使用的回调函数称为钩子函数。也有人把利用VirtualQueryEx安装的函数称为钩子函数,不过这种叫法不太流行。

也可以这样,更容易理解:回调函数就好像是一个中断处理函数,系统在符合你设定的条件时自动调用。为此,你需要做三件事:

1.          声明;

2.          定义;

3.          设置触发条件,就是在你的函数中把你的回调函数名称转化为地址作为一个参数,以便于系统调用。

声明和定义时应注意:回调函数可以作为类的成员函数,但必须声明为静态方法。

回调函数、消息和事件例程


       调用(calling)机制从汇编时代起已经大量使用:准备一段现成的代码,调用者可以随时跳转至此段代码的起始地址,执行完后再返回跳转时的后续地址。 CPU为此准备了现成的调用指令,调用时可以压栈保护现场,调用结束后从堆栈中弹出现场地址,以便自动返回。借堆栈保护现场真是一项绝妙的发明,它使调用 者和被调者可以互不相识,于是才有了后来的函数和构件。

       此调用机制并非完美。回调函数就是一例。函数之类本是为调用者准备的美餐,其烹制者应对食客了如指掌,但实情并非如此。例如,写一个快速排序函数供他人调 用,其中必包含比较大小。麻烦来了:此时并不知要比较的是何类数据--整数、浮点数、字符串?于是只好为每类数据制作一个不同的排序函数。更通行的办法是 在函数参数中列一个回调函数地址,并通知调用者:君需自己准备一个比较函数,其中包含两个指针类参数,函数要比较此二指针所指数据之大小,并由函数返回值 说明比较结果。排序函数借此调用者提供的函数来比较大小,借指针传递参数,可以全然不管所比较的数据类型。被调用者回头调用调用者的函数(够咬嘴的),故 称其为回调(callback)。

       回调函数使程序结构乱了许多。Windows API 函数集中有不少回调函数,尽管有详尽说明,仍使初学者一头雾水。恐怕这也是无奈之举。

无论何种事物,能以树形结构单向描述毕竟让人舒服些。如果某家族中孙辈又是某祖辈的祖辈,恐怕无人能理清其中的头绪。但数据处理之复杂往往需要构成网状结构,非简单的客户/服务器关系能穷尽。

       Windows 系统还包含着另一种更为广泛的回调机制,即消息机制。消息本是 Windows 的基本控制手段,乍看与函数调用无关,其实是一种变相的函数调用。发送消息的目的是通知收方运行一段预先准备好的代码,相当于调用一个函数。消息所附带的 WParam 和 LParam 相当于函数的参数,只不过比普通参数更通用一些。应用程序可以主动发送消息,更多情况下是坐等 Windows 发送消息。一旦消息进入所属消息队列,便检感兴趣的那些,跳转去执行相应的消息处理代码。操作系统本是为应用程序服务,由应用程序来调用。而应用程序一旦 启动,却要反过来等待操作系统的调用。这分明也是一种回调,或者说是一种广义回调。其实,应用程序之间也可以形成这种回调。假如进程 B 收到进程 A 发来的消息,启动了一段代码,其中又向进程 A 发送消息,这就形成了回调。这种回调比较隐蔽,弄不好会搞成递归调用,若缺少终止条件,将会循环不已,直至把程序搞垮。若是故意编写成此递归调用,并设好 终止条件,倒是很有意思。但这种程序结构太隐蔽,除非十分必要,还是不用为好。

       利用消息也可以构成狭义回调。上面所举排序函数一例,可以把回调函数地址换成窗口 handle。如此,当需要比较数据大小时,不是去调用回调函数,而是借 API 函数 SendMessage 向指定窗口发送消息。收到消息方负责比较数据大小,把比较结果通过消息本身的返回值传给消息发送方。所实现的功能与回调函数并无不同。当然,此例中改为消 息纯属画蛇添脚,反倒把程序搞得很慢。但其他情况下并非总是如此,特别是需要异步调用时,发送消息是一种不错的选择。假如回调函数中包含文件处理之类的低 速处理,调用方等不得,需要把同步调用改为异步调用,去启动一个单独的线程,然后马上执行后续代码,其余的事让线程慢慢去做。一个替代办法是借 API 函数 PostMessage 发送一个异步消息,然后立即执行后续代码。这要比自己搞个线程省事许多,而且更安全。

       如今我们是活在一个 object 时代。只要与编程有关,无论何事都离不开 object。但 object 并未消除回调,反而把它发扬光大,弄得到处都是,只不过大都以事件(event)的身份出现,镶嵌在某个结构之中,显得更正统,更容易被人接受。应用程序 要使用某个构件,总要先弄清构件的属性、方法和事件,然后给构件属性赋值,在适当的时候调用适当的构件方法,还要给事件编写处理例程,以备构件代码来调 用。何谓事件?它不过是一个指向事件例程的地址,与回调函数地址没什么区别。

       不过,此种回调方式比传统回调函数要高明许多。首先,它把让人不太舒服的回调函数变成一种自然而然的处理例程,使编程者顿觉气顺。再者,地址是一个危险的 东西,用好了可使程序加速,用不好处处是陷阱,程序随时都会崩溃。现代编程方式总是想法把地址隐藏起来(隐藏比较彻底的如 VB 和 Java),其代价是降低了程序效率。事件例程(?)使编程者无需直接操作地址,但并不会使程序减速。
(例程似乎是进程的台湾翻译。)

三精妙比喻:回调函数还真有点像您随身带的BP机:告诉别人号码,在它有事情时Call您。

          回调用于层间协作,上层将本层函数安装在下层,这个函数就是回调,而下层在一定条件下触发回调,例如作为一个驱动,是一个底层,他在收到一个数据时,除了 完成本层的处理工作外,还将进行回调,将这个数据交给上层应用层来做进一步处理,这在分层的数据通信中很普遍。其实回调和API非常接近,他们的共性都是 跨层调用的函数。但区别是API是低层提供给高层的调用,一般这个函数对高层都是已知的;而回调正好相反,他是高层提供给底层的调用,对于低层他是未知 的,必须由高层进行安装,这个安装函数其实就是一个低层提供的API,安装后低层不知道这个回调的名字,但它通过一个函数指针来保存这个回调,在需要调用 时,只需引用这个函数指针和相关的参数指针。       其实:回调就是该函数写在高层,低层通过一个函数指针保存这个函数,在某个事件的触发下,低层通过该函数指针调用高层那个函数。

四调用方式
       软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。同步调用是一种阻塞式调用,调用方要等待对方执行完毕 才返回,它是一种单向调用;回调是一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口;异步调用是一种类似消息或事件的机制,不过它 的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。回调和异步调用的关系非常紧密,通常我们使用 回调来实现异步消息的注册,通过异步调用来实现消息的通知。同步调用是三者当中最简单的,而回调又常常是异步调用的基础。
    
       对于不同类型的语言(如结构化语言和对象语言)、平台(Win32、JDK)或构架(CORBA、DCOM、WebService),客户和服务的交互除 了同步方式以外,都需要具备一定的异步通知机制,让服务方(或接口提供方)在某些情况下能够主动通知客户,而回调是实现异步的一个最简捷的途径。

       对于一般的结构化语言,可以通过回调函数来实现回调。回调函数也是一个函数或过程,不过它是一个由调用方自己实现,供被调用方使用的特殊函数。

       在面向对象的语言中,回调则是通过接口或抽象类来实现的,我们把实现这种接口的类成为回调类,回调类的对象成为回调对象。对于象C++或Object Pascal这些兼容了过程特性的对象语言,不仅提供了回调对象、回调方法等特性,也能兼容过程语言的回调函数机制。

       Windows平台的消息机制也可以看作是回调的一种应用,我们通过系统提供的接口注册消息处理函数(即回调函数),从而实现接收、处理消息的目的。由于Windows平台的API是用C语言来构建的,我们可以认为它也是回调函数的一个特例。

       对于分布式组件代理体系CORBA,异步处理有多种方式,如回调、事件服务、通知服务等。事件服务和通知服务是CORBA用来处理异步消息的标准服务,他们主要负责消息的处理、派发、维护等工作。对一些简单的异步处理过程,我们可以通过回调机制来实现。

       下面我们集中比较具有代表性的语言(C、Object Pascal)和架构(CORBA)来分析回调的实现方式、具体作用等。

       过程语言中的回调(C)
       (1 )函数指针
       回调在C语言中是通过函数指针来实现的,通过将回调函数的地址传给被调函数从而实现回调。因此,要实现回调,必须首先定义函数指针,请看下面的例子:

       void Func(char *s);// 函数原型
       void (*pFunc) (char *);//函数指针

       可以看出,函数的定义和函数指针的定义非常类似。

       一般的化,为了简化函数指针类型的变量定义,提高程序的可读性,我们需要把函数指针类型自定义一下。

       typedef void(*pcb)(char *);

       回调函数可以象普通函数一样被程序调用,但是只有它被当作参数传递给被调函数时才能称作回调函数。

       被调函数的例子:

       void GetCallBack(pcb callback)
       {
       /*do something*/
       }
       用户在调用上面的函数时,需要自己实现一个pcb类型的回调函数:
       void fCallback(char *s) 
       {
       /* do something */
       } 
       然后,就可以直接把fCallback当作一个变量传递给GetCallBack,
       GetCallBack(fCallback);

       如果赋了不同的值给该参数,那么调用者将调用不同地址的函数。赋值可以发生在运行时,这样使你能实现动态绑定。

       (2 )参数传递规则
       到目前为止,我们只讨论了函数指针及回调而没有去注意ANSI C/C++的编译器规范。许多编译器有几种调用规范。如在Visual C++中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示其调用规范(默认为_cdecl)。C++ Builder也支持_fastcall调用规范。调用规范影响编译器产生的给定函数名,参数传递的顺序(从右到左或从左到右),堆栈清理责任(调用者或 者被调用者)以及参数传递机制(堆栈,CPU寄存器等)。

       将调用规范看成是函数类型的一部分是很重要的;不能用不兼容的调用规范将地址赋值给函数指针。例如:

       // 被调用函数是以int为参数,以int为返回值
       __stdcall int callee(int);

       // 调用函数以函数指针为参数
       void caller( __cdecl int(*ptr)(int));

       // 在p中企图存储被调用函数地址的非法操作
       __cdecl int(*p)(int) = callee; // 出错

       指针p和callee()的类型不兼容,因为它们有不同的调用规范。因此不能将被调用者的地址赋值给指针p,尽管两者有相同的返回值和参数列

       (3 )应用举例
       C语言的标准库函数中很多地方就采用了回调函数来让用户定制处理过程。如常用的快速排序函数、二分搜索函数等。

       快速排序函数原型:

       void qsort(void *base, size_t nelem, size_t width, int (_USERENTRY *fcmp)(const void *, const void *));
       二分搜索函数原型:
       void *bsearch(const void *key, const void *base, size_t nelem,
       size_t width, int (_USERENTRY *fcmp)(const void *, const void *));

       其中fcmp就是一个回调函数的变量。

       下面给出一个具体的例子:

       #include <stdio.h>
       #include <stdlib.h>

       int sort_function( const void *a, const void *b);
       int list[5] = { 54, 21, 11, 67, 22 };

       int main(void)
       {
int x;

qsort((void *)list, 5, sizeof(list[0]), sort_function);
for (x = 0; x < 5; x++)
printf("%i/n", list[x]);
return 0;
}

int sort_function( const void *a, const void *b)
{
return *(int*)a-*(int*)b;
}

posted @ 2012-08-14 14:56 爱走小路 阅读(225) | 评论 (0)编辑 收藏
VC 回调函数及使用方法 
回调函数说白了就是事件响应程序,Windows的每个消息可以理解为一个事件,事件的响应代码要由用户自己来定义。用户定义了事件响应的代码,但还要Windows知道这段代码的位置(要不然Windows就不知道如何去调用,这也没有用),于是用户需要将回调函数的指针告诉Windows,最典型的例子是在窗口类的结构(WNDCLASS)中给lpfnWndProc分量赋回调函数指针值。

  回调函数的参数格式是由回调函数的调用者(一般是Windows)来定义的,而回调函数的实现者必须遵循这种格式。Windows程序是以事件驱动模型为基础的,这就必然要用到回调函数这种机制。
  要透彻了解回调函数,多看看SDK Samples。而MFC中的消息映射机制已经将窗口消息响应的回调函数隐藏起来了,这也符合C++的编程思想,回调函数终究是一种全局函数,它不能在类中实现,而消息映射机制的目的是使消息响应的代码最终封装在窗口类(CWnd类的子类)中。

如果有时间,不妨看看MESSAGE_MAP宏,消息映射是回调函数,只是这种回调函数的用法不同而已。普通的回调函数是要你提供地址,传进某个函数,由它去调用;而消息映射函数,却是由你定义函数,由MESSAGE_MAP宏去取得地址,并实现它的调用。

回调函数是一个程序员不能显式调用的函数;通过将回调函数的地址传给调用者从而实现调用。要实现回调,必须首先定义函数指针。尽管定义的语法有点不可思议,但如果你熟悉函数声明的一般方法,便会发现函数指针的声明与函数声明非常类似。

typedef void (*f1) ();// 为函数指针声明类型定义
void (*p) (); //p是指向某函数的指针
void func1() 
{
 /* do something */
 printf("From func1(), Hello World!/n");
}

void caller(void(*ptrfunc1)())
{
 ptrfunc1(); /* 调用ptr指向的函数 */ 
}

//typedef bool (*f2) (int *);// 为函数指针声明类型定义
//bool (*q) (int *); //p是指向某函数的指针

bool func2(int* t_i) 
{
 /* do something */
 printf("From func2() = %d, Hello World!/n", (*t_i)++);
 return true;
}

void caller2(bool (*ptrfunc2)(int *), int * i)
{
 ptrfunc2(i); /* 调用ptr指向的函数 */ 
}


int main(int argc, char* argv[])
{
 printf("From main(), Hello World!/n");
 printf("/n");

 //无参数调用
 p = func1; /* 传递函数地址地址 */
 caller(p); /* 传递函数地址到调用者 */

 //有参数调用
 int i = 0; 
 for (int j = 0; j < 10; j++)
 {
  caller2(func2, &i); //* 传递函数地址到调用者 */
 }

 //有参数调用第二次

  i = 0;
 //q = func2; /* 传递函数地址地址 */
 //caller2(q, &i); /* 传递函数地址到调用者 */

 printf("/n");
 printf("From main(), Hello World!/n");

 getchar();

 return 0;
}


#include <stdlib.h>
#include <stdio.h>
int Test1()
{
   int i;
   for (i=0; i<30; i++)
   {
     printf("The %d th charactor is: %c/n", i, (char)('a' + i%26));
   }
   return 0;
}

int Test2(int num)
{
   int i;
   for (i=0; i<num; i++)
   { 
    printf("The %d th charactor is: %c/n", i, (char)('a' + i%26));
   }
   return 0;
}

void Caller1(int (*ptr)())
//指向函数的指针作函数参数
{
   (*ptr)();
}

void Caller2(int n, int (*ptr)(int n))
//指向函数的指针作函数参数,这里第一个参数是为指向函数的指针服务的,
{
   //不能写成void Caller2(int (*ptr)(int n)),这样的定义语法错误。
   (*ptr)(n);
   return;
}
int main()

{

   printf("************************/n");

   Caller1(Test1); //相当于调用Test1();

   printf("************************/n");

   Caller2(30, Test2); //相当于调用Test2(30);

   return 0;

}

/*
int main()

{

   printf("************************/n");

   Caller1(&Test1); //相当于调用Test1();

   printf("&&&&&&************************/n");

   Caller2(30, &Test2); //相当于调用Test2(30);

   return 0;

}
*/

posted @ 2012-08-14 14:53 爱走小路 阅读(244) | 评论 (0)编辑 收藏

一、基本知识
指针和引用的声明方式:
声明指针: char* pc;
声明引用: char c = 'A'
                  char& rc = c;

它们的区别:
①从现象上看,指针在运行时可以改变其所指向的值,而引用一旦和某个对象绑定后就不再改变。这句话可以理解为:指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变,但是指定的对象其内容可以改变。
②从内存分配上看,程序为指针变量分配内存区域,而不为引用分配内存区域,因为引用声明时必须初始化,从而指向一个已经存在的对象。引用不能指向空值。
③从编译上看,程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后就不会再改,因此指针可以改变指向的对象(指针变量中的值可以改),而引用对象不能改。这是使用指针不安全而使用引用安全的主要原因。从某种意义上来说引用可以被认为是不能改变的指针。
④不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高。因为在使用引用之前不需要测试它的合法性。相反,指针则应该总是被测试,防止其为空。
⑤理论上,对于指针的级数没有限制,但是引用只能是一级。如下:
  int** p1;         // 合法。指向指针的指针
  int*& p2;         // 合法。指向指针的引用
  int&* p3;         // 非法。指向引用的指针是非法的
  int&& p4;         // 非法。指向引用的引用是非法的
  注意上述读法是从左到右。
 
程序1:
#include "stdio.h"
int main(void)
{
  // 声明一个char型指针pc,且让它指向空值
  char* pc = 0;
  char a = 'a';
  // 声明一个引用rc,且让它引用变量a
  char& rc = a;
  printf("%d, %c\n", pc, rc);
 
  char *pc2;
  // 声明一个指针,但可以不初始化
  pc2 = pc;
 
  // char& rc2;
  // 上面语句编译时,会产生如下错误:
  // error C2530: 'rc2' : references must be initialized
  // 即,应用必须初始化
  // rc = *pc;
  // 上面语句编译不会有问题,但运行时,会报如下错误:
  // "0x00401057"指令引用的"0x00000000"内存。该内存不能为"read"
  // 说明引用在任何情况下,都不能指向空值
 
  return 0;
}


程序2:
#include <iostream>
#include <string>
using namespace std;
int main(void)
{
  string s1("Hello");
  string s2("World");
  // printf("%s\n", s1); 不能用printf输出s1,而应该用cout
 
  cout << "s1的地址 = "<< &s1 << endl;                        // &s1 = 0012FF64
  cout << "s2的地址 = "<< &s2 << endl;                        // &s2 = 0012FF54
 
  string& rs = s1;                                                           // 1. 定义一个引用rs,rs引用s1
  cout << "引用rs的地址 = " << &rs << endl;                  // &rs = 0012FF64
 
  string* ps = &s1;                                                         //定义一个指针ps, ps指向s1
  cout << "指针ps的地址 = " << ps << endl;                    // ps = 0012FF64
 
  cout << rs << ", " << *ps << endl;                          // Hello, Hello
  // 如果没有#include <string>,上面的语句在编译的时候,会出现如下错误:
  // error C2679: binary '<<' : no operator defined which takes a right-
  // hand operand of type 'class std::basic_string<char,struct
  // std::char_traits<char>,class std::allocator<char> >'
  // (or there is no acceptable  conversion)
 
  rs = s2;                                                      // 2. rs仍旧引用s1, 但是s1现在的值是"World"
  ps = &s2;                                                   // ps现在指向s2
 
  cout << "引用rs的地址 = " << &rs << endl;                  // &rs = 0012FF64 未改变
  cout << "引用rs的值 = " << rs << endl;                       // rs = "World" 已改变
 
  cout << "指针ps的地址 = " << ps << endl;                        // ps = 0012FF54  已改变
  cout << "指针ps所指地址的内容 = " << *ps << endl;          // *ps = World    已改变
 
  cout << "s1的地址 = "<< &s1 << endl;                        // 3. &s1 = 0012FF64 未改变
  cout << "s1的值 = " << s1 << endl;                             // 4. s1 = World  已改变
 
  return 0;
}
 
可以认为:
引用就是变量的别名,在引用初始化的时候就已经确定,以后不能再改变。见程序2的粗体字语句。第1句,声明了rs引用s1,s1的值为”Hello”,从这以后,rs实际上就相当于变量s1了,或者从更本质的意义上来说,rs的地址就是初始化时s1的地址了,以后都不会再改变。这应该比较好理解,比如我们在程序中定义了一个变量a,不管我们如何给a赋值,但它的地址是不会改变的;
 
第2句,rs仍旧指向初始化时s1的地址,但此处的赋值就相当于重新给s1赋值,因此我们从第3句和第4句可以看到,s1的地址并没有发生变化,但是其值已经发生了变化。

二、作为参数传递
利用引用的这个特性,可以用它作为函数的传出参数。如程序3:
#include <iostream>
#include <string>
using namespace std;
int newEvaluation(string& aStr)
{
  string bStr("Hello,");
  aStr = bStr + aStr;
 
  return 0;
}
 
int main(void)
{
  string aStr("Patrick!");
  newEvaluation(aStr);
  std::cout << aStr << endl;                      // 输出结果:"Hello, Patrick!"
 
  return 0;
}
 
而一般变量,则不能从函数内部传值出来,比如程序4:
#include <iostream>
#include <string>
using namespace std;
 
int newEvaluation(string aStr)
{
  string bStr("Hello,");
  aStr = bStr + aStr;
 
  return 0;
}
 
int main(void)
{
  string aStr("Patrick!");
  newEvaluation(aStr);
  std::cout << aStr << endl;                     // 输出结果:"Patrick!",aStr的值没有变化
 
  return 0;
}
 
当然程序3引用传递的方式也可以写成指针传递的方式,如程序5:
#include <iostream>
#include <string>
using namespace std;
 
int newEvaluation(string* const aStr)
{
  string bStr("Hello,");
  *aStr = bStr + *aStr;
 
  return 0;
}
 
int main(void)
{
  string aStr("Patrick!");
  newEvaluation(&aStr);
  std::cout << aStr << endl;                      // 输出结果:"Hello, Patrick!"
 
  return 0;
}
 
注意程序中的陷井,如程序6:
#include <iostream.h>
int *pPointer;
void SomeFunction()
{
  int nNumber;
  nNumber = 25;
  //让指针指向nNumber
  pPointer = &nNumber;
}
 
void main()
{
  SomeFunction();    //为pPointer赋值
  //为什么这里失败了?为什么没有得到25
  cout << "Value of *pPointer: " << *pPointer << endl;
}
 
这段程序先调用了SomeFunction函数,创建了个叫nNumber的变量,接着让指针pPointer指向了它。可是问题出在哪儿呢?当函数结束后,nNumber被删掉了,因为这一个局部变量。局部变量在定义它的函数执行完后都会被系统自动删掉。也就是说当SomeFunction 函数返回主函数main()时,这个变量已经被删掉,但pPointer还指着变量曾经用过的但现在已不属于这个程序的区域。
 
尽管在SomeFunction中使用所谓的动态分配内存。程序7中也存在陷井:
#include <iostream.h>
int *pPointer;
 
void SomeFunction()
{
    int intNumber = 25;
    // 让指针指向一个新的整型
    pPointer = new int;
    pPointer = &intNumber;
}
 
void main()
{
    SomeFunction();                                           // 为pPointer赋值
    cout<< "Value of *pPointer: " << *pPointer << endl;
    delete pPointer;
}
原因也如上面所言,intNumber的作用范围仅限于SomeFunction中,离开了SomeFunction,那么intNumber就不存在了,那么&intNumber即intNumber的地址就变得没有意义了,因此,该地址所指向的值是不确定的。如果改为下面的程序就不会有问题了。

程序8:
#include <iostream.h>
int *pPointer;
 
void SomeFunction()
{
    int intNumber = 25;
    // 让指针指向一个新的整型
    pPointer = new int(intNumber);
}
 
void main()
{
    SomeFunction();                                           // 为pPointer赋值
    cout<< "Value of *pPointer: " << *pPointer << endl;
    delete pPointer;
}

三、指针的指针
前面说到,指针是没有级数限制的。
程序9:
#include<stdio.h>
#include<stdlib.h>
 
void main(void)
{
    int i, j;
    int a[10], b[3][4], *p1, *p2, **p3;   
    for(i = 0; i < 10; i++)
       scanf("%d", &a[i]);               
 
    for(i = 0; i < 3; i++)
       for(j = 0; j < 4; j++)
           scanf("%d", &b[i][j]);         
 
    p1 = a;
    p3 = &p1;
    for(i = 0; i < 10; i++)
       printf("%4d", *(*p3+i));            
    printf("\n");
 
    for(p1 = a; p1 - a < 10; p1++)    
    {
       p3 = &p1;
       printf("%4d", **p3);
    }
    printf("\n");
 
    for(i = 0; i < 3; i++)                
    {
       p2 = b[i];
       p3 = &p2;
       for(j = 0; j < 4; j++)
       printf("%4d",*(*p3+j));
       printf("\n");
    }
 
    for(i = 0; i < 3; i++)                
    {
       p2 = b[i];
       for(p2 = b[i]; p2-b[i] < 4; p2++)
       {
           p3 = &p2;
           printf("%4d", **p3);
       }
       printf("\n");
    }
}

输出的结果:
    1   2   3   4   5   6   7   8   9   10
    1   2   3   4   5   6   7   8   9   10
    11  12  13  14
    15  16  17  18
    19  20  21  22
    11  12  13  14
    15  16  17  18
    19  20  21  22

四、函数指针和函数引用
函数指针是C++最大的优点之一。和使用普通指针相比,高级程序员只要有可能都更愿意使用引用,因为引用更容易处理一些。然而,当处理函数时,函数引用对比函数指针就未必有这个优势了。现有的代码很少使用函数引用。下面将向介绍如何函数指针、如何使用函数引用以及分别在什么情况下使用它们。

① 函数指针的例子
#include <iostream>
void print(int i)
{
    std::cout << i << std::endl;
}
 
void multiply(int& nDest, int nBy)
{
    nDest *= nBy;
}
 
void print_something()
{
    std::cout << "something" << std::endl;
}
 
int sayHello()
{
    std::cout << "Hello, World!" << std::endl;
    return 10;
}
 
int main()

    void (*pFunction_1)(int);
    pFunction_1 = &print;
    pFunction_1(1);
    // 输出结果为1
 
    void (*pFunction_2)(int&, int) = &multiply;
    int i = 1;
    pFunction_2(i, 10);
    std::cout << "i = " << i << std::endl;
    // 输出结果为10
   
    void (*pFunction_3)();
    pFunction_3 = &print_something;
    pFunction_3();
    // 输出结果为something
 
    int (*pFunction_4)();
    pFunction_4 = &sayHello;
    int a = pFunction_4();
    // 输出结果为Hello, World!
    std::cout << a << std::endl;
    // 输出结果为10
   
    return 0;
}
 
② 函数引用的例子
#include <iostream>
void print(int i)
{
    std::cout << i << std::endl;
}
 
void print2(int i)
{
    std::cout << i << std::endl;
}
 
void multiply(int& nDest, int nBy)
{
    nDest *= nBy;
}
 
void print_something()
{
    std::cout << "something" << std::endl;
}
 
int sayHello()
{
    std::cout << "Hello, World!" << std::endl;
    return 10;
}
 
 
int main()
{  
    // void (&rFunction_1)(int);
    // 错误:未初始化引用!引用必须初始化
 
    void (&rFunction_2)(int) = print;
    rFunction_2(1);
    // 输出1
 
    rFunction_2 = print2;
    rFunction_2(2);
    // 输出2
 
    void (&rFunction_3)(int&, int) = multiply;
    int i = 1;
    rFunction_3(i, 10); 
    std::cout << i << std::endl;
    // 输出10
 
    void (&rFunction_4)() = print_something;
    rFunction_4();
    // 输出something
 
    int (&rFunction_5)();
    rFunction_5 = sayHello;
    int a = rFunction_5();   // 输出Hello, World!
    std::cout << a << std::endl;
    // 输出10
 
    return 0;
}
 
③ 函数指针和函数引用作为函数参数
#include <iostream>
 
void print(int i)
{
    std::cout << i << std::endl;
}
 
void print2(int i)
{
    std::cout << i * 2 << std::endl;
}
 
void printSomething()
{
    std::cout << "Something" << std::endl;
}
 
void sayHello()
{
    std::cout << "Hello, World!" << std::endl;
}
 
void call_p_func(void (*func)(int))
{
    func(1);
    func(2);
    func(3);
}
 
void call_r_func(void (&func)(int))
{
    func(1);
    func(2);
    func(3);
}
 
void call_p_function(void (*func)())
{
    func();
}
 
int main()

    std::cout << "函数指针作为参数" << std::endl;
    call_p_func(&print);
    call_p_func(&print2);
    call_p_function(&printSomething);
    call_p_function(&sayHello);
    call_p_function(sayHello);
    // 上面两句对于某些编译器来说是一样的,但是推荐使用前者的写法,
    // 这样可以是程序的可读性更好一些
 
    std::cout << "函数引用作为参数" << std::endl;
    call_r_func(print);
    call_r_func(print2);
 
    return 0;
}
 
总结:
函数指针的声明使用方式:
<想要指向的函数之返回类型>(*函数指针的名称)<想要指向的函数之参数类型…>
如要想声明一个函数指针指向以下函数:
void print(int i)
{
    std::cout << i << std::endl;
}
那么就可以如下操作:
void (*pFunction)(int);
然后如下用函数的地址给pFunction赋值:
pFunction = &print;
在然后,pFunction就可以和函数print一样使用了,比如,
pFunction(1);
等等。
 
函数引用的声明和使用方式:
<欲引用的函数之返回类型>(&函数引用的名称)<欲引用的函数之参数类型…>=<欲引用的函数的名称>,至所以如此,是引用在声明的时候必须初始化,引用不能指向空值。
如要想声明一个函数引用指向以下函数:
void print(int i)
{
    std::cout << i << std::endl;
}
那么就可以如下操作:
void (&rFunction)(int)=print;
在然后,rFunction就可以和函数print一样使用了,比如,
rFunction(1);
等等。

五、const修饰指针和引用
大致而言,const修饰指针和引用分三种情况,即const修饰指针、const修饰引用和const修饰指针的引用。下面分别讨论之。
 
① const修饰指针
   const修饰指针又分为三种情况,即const修饰指针本身、const修饰指针所指的变量(或对象)以及const修饰指针本身和指针所指的变量(或对象)。
 
    a. const修饰指针本身
    在这种情况下,指针本身是常量,不能改变,任何修改指针本身的行为都是非法的,例如:
    double pi = 3.1416;            
    double* const PI = &pi;
 
    double alpha = 3.14;
    PI = &alpha;                   // 错误。因为指针PI是常量,不能再被改变。
    *PI = alpha;                   // OK。虽然指针PI不能被改变,但指针所指的变量或者对象可变。
   
    b. const修饰指针指向的变量(或对象)
    在这种情况下,指针本身可以改变,但const所修饰的指针所指向的对象不能被改变,例如:
    double pi = 3.1416;
    const double* PI = &pi;
 
    double alpha = 3.14;
    *PI = alpha;    // 错误。因为PI所指向的内容是常量,因此*PI不能被改变。
    PI = &alpha;    // OK。虽然指针所指的内容不能被改变,但指针PI本身可改变。从而通过这种方式改变*PI。
    
    c. const修饰指针本身和指针所指向的变量(或对象)
    在这种情况下,指针本身和指针指向的变量(或对象)均不能被改变,例如:
    double pi = 3.146;
    const double* const PI = &pi;
    //double const* const PI = &pi;
    cout << "PI = " << PI << endl;
    cout << "*PI = " << *PI << endl;
 
    double alpha = 3.14;
    //*PI = alpha;                 // 错误。因为PI所指向的内容是常量,因此*PI不能被改变。
    //PI = &alpha;                 // 错误。因为指针PI是常量,不能再被改变。
 
② const修饰引用
   const修饰引用没有指针修饰指针那么复杂,只有一种形式。引用本身不能被改变,但所指向的对象是可以被改变的,见上面“一、基本知识”。
    double pi = 3.1416;
    //const double& PI = pi;
    double const& PI = pi;                  //和上面一句是等价的
    //double& const PI = pi;                //有问题。很多编译器会产生warning
    cout << PI << endl;
 
③ const修饰指针引用
   我们用例子来说明。
   double pi = 3.14;
   const double* pPI = &pi;
   //const double*& rPI = &pi;             //错误。不能将double* 转换成const double *&
   const double*& rPI = pPI;               //OK。声明指针引用的正确方法

说明:const double*& rPI = &pi; 为什么会出现错误呢?我们知道,引用是被引用对象的别名,正因为如此,由于rPI是pPI的别名,因此rPI和pPI的类型必须完全一致。从上面的代码段我们可以看到,rPI的类型是const double*,而&pi的类型是double*,因此这句程序是错误的。
 
下面这段代码和 ① 中的b中的情形对应(即内容不可变,指针可变):
double pi = 3.1416;
double api = 3.14;
const double* pPI = &pi;
const double* pAPI = &api;
const double*& rPI = pPI;
const double*& rAPI = pPI;

*rAPI = api;                 // 错误。指针所指向的值不能被直接改变
rAPI = pAPI;               // OK。指针本身可以被改变

指针引用的用法还有其它的情形,由于罕用,故此不谈及。

posted @ 2012-08-14 14:48 爱走小路 阅读(82) | 评论 (0)编辑 收藏
     摘要: 什么是泛型程序设计  我们可以简单的理解为:使用模板的程序设计就是泛型程序设计。就像我们我们可以简单的理解面向对象程序设计就是使用虚函数的程序设计一样。STL是什么   作为一个C++程序设计者,STL是一种不可忽视的技术。Sandard Template Library (STL):标准模板库,更准确的说是 C++ 程序设计语言标准模板库。学习过MFC的人知道,MF...  阅读全文
posted @ 2012-08-14 11:05 爱走小路 阅读(1522) | 评论 (0)编辑 收藏
虚函数联系到多态,多态联系到继承。所以本文中都是在继承层次上做文章。没了继承,什么都没得谈。 

下面是对C++的虚函数这玩意儿的理解。 

一, 什么是虚函数(如果不知道虚函数为何物,但有急切的想知道,那你就应该从这里开始) 

简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离;用形象的语言来解释就是实现以共同的方法,但因个体差异而采用不同的策略。下面来看一段简单的代码 

class A{ 

public: 

void print(){ cout<<”This is A”<<endl;} 

}; 

class B:public A{ 

public: 

void print(){ cout<<”This is B”<<endl;} 

}; 

int main(){ //为了在以后便于区分,我这段main()代码叫做main1 

A a; 

B b; 

a.print(); 

b.print(); 



通过class A和class B的print()这个接口,可以看出这两个class因个体的差异而采用了不同的策略,输出的结果也是我们预料中的,分别是This is A和This is B。但这是否真正做到了多态性呢?No,多态还有个关键之处就是一切用指向基类的指针或引用来操作对象。那现在就把main()处的代码改一改。 

int main(){ //main2 

A a; 

B b; 

A* p1=&a; 

A* p2=&b; 

p1->print(); 

p2->print(); 



运行一下看看结果,哟呵,蓦然回首,结果却是两个This is A。问题来了,p2明明指向的是class B的对象但却是调用的class A的print()函数,这不是我们所期望的结果,那么解决这个问题就需要用到虚函数 

class A{ 

public: 

virtual void print(){ cout<<”This is A”<<endl;} //现在成了虚函数了 

}; 

class B:public A{ 

public: 

void print(){ cout<<”This is B”<<endl;} //这里需要在前面加上关键字virtual吗? 

}; 

毫无疑问,class A的成员函数print()已经成了虚函数,那么class B的print()成了虚函数了吗?回答是Yes,我们只需在把基类的成员函数设为virtual,其派生类的相应的函数也会自动变为虚函数。所以,class B的print()也成了虚函数。那么对于在派生类的相应函数前是否需要用virtual关键字修饰,那就是你自己的问题了。 

现在重新运行main2的代码,这样输出的结果就是This is A和This is B了。 

现在来消化一下,我作个简单的总结,指向基类的指针在操作它的多态类对象时,会根据不同的类对象,调用其相应的函数,这个函数就是虚函数。 

二, 虚函数是如何做到的(如果你没有看过《Inside The C++ Object Model》这本书,但又急切想知道,那你就应该从这里开始) 

虚函数是如何做到因对象的不同而调用其相应的函数的呢?现在我们就来剖析虚函数。我们先定义两个类 

class A{ //虚函数示例代码 

public: 

virtual void fun(){cout<<1<<endl;} 

virtual void fun2(){cout<<2<<endl;} 

}; 

class B:public A{ 

public: 

void fun(){cout<<3<<endl;} 

void fun2(){cout<<4<<endl;} 

}; 

由于这两个类中有虚函数存在,所以编译器就会为他们两个分别插入一段你不知道的数据,并为他们分别创建一个表。那段数据叫做vptr指针,指向那个表。那个表叫做vtbl,每个类都有自己的vtbl,vtbl的作用就是保存自己类中虚函数的地址,我们可以把vtbl形象地看成一个数组,这个数组的每个元素存放的就是虚函数的地址,请看图 

通过上图,可以看到这两个vtbl分别为class A和class B服务。现在有了这个模型之后,我们来分析下面的代码 

A *p=new A; 

p->fun(); 

毫无疑问,调用了A::fun(),但是A::fun()是如何被调用的呢?它像普通函数那样直接跳转到函数的代码处吗?No,其实是这样的,首先是取出vptr的值,这个值就是vtbl的地址,再根据这个值来到vtbl这里,由于调用的函数A::fun()是第一个虚函数,所以取出vtbl第一个slot里的值,这个值就是A::fun()的地址了,最后调用这个函数。现在我们可以看出来了,只要vptr不同,指向的vtbl就不同,而不同的vtbl里装着对应类的虚函数地址,所以这样虚函数就可以完成它的任务。 

而对于class A和class B来说,他们的vptr指针存放在何处呢?其实这个指针就放在他们各自的实例对象里。由于class A和class B都没有数据成员,所以他们的实例对象里就只有一个vptr指针。通过上面的分析,现在我们来实作一段代码,来描述这个带有虚函数的类的简单模型。 

#include<iostream> 

using namespace std; 

//将上面“虚函数示例代码”添加在这里 

int main(){ 

void (*fun)(A*); 

A *p=new B; 

long lVptrAddr; 

memcpy(&lVptrAddr,p,4); 

memcpy(&fun,reinterpret_cast<long*>(lVptrAddr),4); 

fun(p); 

delete p; 

system("pause"); 



用VC或Dev-C++编译运行一下,看看结果是不是输出3,如果不是,那么太阳明天肯定是从西边出来。现在一步一步开始分析 

void (*fun)(A*); 这段定义了一个函数指针名字叫做fun,而且有一个A*类型的参数,这个函数指针待会儿用来保存从vtbl里取出的函数地址 

A* p=new B; 这个我不太了解,算了,不解释这个了 

long lVptrAddr; 这个long类型的变量待会儿用来保存vptr的值 

memcpy(&lVptrAddr,p,4); 前面说了,他们的实例对象里只有vptr指针,所以我们就放心大胆地把p所指的4bytes内存里的东西复制到lVptrAddr中,所以复制出来的4bytes内容就是vptr的值,即vtbl的地址 

现在有了vtbl的地址了,那么我们现在就取出vtbl第一个slot里的内容 

memcpy(&fun,reinterpret_cast<long*>(lVptrAddr),4); 取出vtbl第一个slot里的内容,并存放在函数指针fun里。需要注意的是lVptrAddr里面是vtbl的地址,但lVptrAddr不是指针,所以我们要把它先转变成指针类型 

fun(p); 这里就调用了刚才取出的函数地址里的函数,也就是调用了B::fun()这个函数,也许你发现了为什么会有参数p,其实类成员函数调用时,会有个this指针,这个p就是那个this指针,只是在一般的调用中编译器自动帮你处理了而已,而在这里则需要自己处理。 

delete p;和system("pause"); 这个我不太了解,算了,不解释这个了 

如果调用B::fun2()怎么办?那就取出vtbl的第二个slot里的值就行了 

memcpy(&fun,reinterpret_cast<long*>(lVptrAddr+4),4); 为什么是加4呢?因为一个指针的长度是4bytes,所以加4。或者memcpy(&fun,reinterpret_cast<long*>(lVptrAddr)+1,4); 这更符合数组的用法,因为lVptrAddr被转成了long*型别,所以+1就是往后移sizeof(long)的长度 

三, 以一段代码开始 

#include<iostream> 

using namespace std; 

class A{ //虚函数示例代码2 

public: 

virtual void fun(){ cout<<"A::fun"<<endl;} 

virtual void fun2(){cout<<"A::fun2"<<endl;} 

}; 

class B:public A{ 

public: 

void fun(){ cout<<"B::fun"<<endl;} 

void fun2(){ cout<<"B::fun2"<<endl;} 

}; //end//虚函数示例代码2 

int main(){ 

void (A::*fun)(); //定义一个函数指针 

A *p=new B; 

fun=&A::fun; 

(p->*fun)(); 

fun = &A::fun2; 

(p->*fun)(); 

delete p; 

system("pause"); 



你能估算出输出结果吗?如果你估算出的结果是A::fun和A::fun2,呵呵,恭喜恭喜,你中圈套了。其实真正的结果是B::fun和B::fun2,如果你想不通就接着往下看。给个提示,&A::fun和&A::fun2是真正获得了虚函数的地址吗? 

首先我们回到第二部分,通过段实作代码,得到一个“通用”的获得虚函数地址的方法 

#include<iostream> 

using namespace std; 

//将上面“虚函数示例代码2”添加在这里 

void CallVirtualFun(void* pThis,int index=0){ 

void (*funptr)(void*); 

long lVptrAddr; 

memcpy(&lVptrAddr,pThis,4); 

memcpy(&funptr,reinterpret_cast<long*>(lVptrAddr)+index,4); 

funptr(pThis); //调用 



int main(){ 

A* p=new B; 

CallVirtualFun(p); //调用虚函数p->fun() 

CallVirtualFun(p,1);//调用虚函数p->fun2() 

system("pause"); 



现在我们拥有一个“通用”的CallVirtualFun方法。 

这个通用方法和第三部分开始处的代码有何联系呢?联系很大。由于A::fun()和A::fun2()是虚函数,所以&A::fun和&A::fun2获得的不是函数的地址,而是一段间接获得虚函数地址的一段代码的地址,我们形象地把这段代码看作那段CallVirtualFun。编译器在编译时,会提供类似于CallVirtualFun这样的代码,当你调用虚函数时,其实就是先调用的那段类似CallVirtualFun的代码,通过这段代码,获得虚函数地址后,最后调用虚函数,这样就真正保证了多态性。同时大家都说虚函数的效率低,其原因就是,在调用虚函数之前,还调用了获得虚函数地址的代码。 

最后的说明:本文的代码可以用VC6和Dev-C++4.9.8.0通过编译,且运行无问题。其他的编译器小弟不敢保证。其中,里面的类比方法只能看成模型,因为不同的编译器的低层实现是不同的。例如this指针,Dev-C++的gcc就是通过压栈,当作参数传递,而VC的编译器则通过取出地址保存在ecx中。所以这些类比方法不能当作具体实现 
posted @ 2012-08-14 11:02 爱走小路 阅读(81) | 评论 (0)编辑 收藏
CString位于头文件afx.h中。
CString 的 成员函数
CString的构造函数
CString( );
例:CString csStr;

CString( const CString& stringSrc );
例:CString csStr("ABCDEF中文123456");
CString csStr2(csStr);

CString( TCHAR ch, int nRepeat = 1 );
例:CString csStr('a',5);
//csStr="aaaaa"

CString( LPCTSTR lpch, int nLength );
例:CString csStr("abcdef",3);
//csStr="abc"

CString( LPCWSTR lpsz );
例:wchar_t s[]=L"abcdef";
CString csStr(s);
//csStr=L"abcdef"

CString( const unsigned char* psz );
例:const unsigned char s[]="abcdef";
const unsigned char* sp=s;
CString csStr(sp);
//csStr="abcdef"

CString( LPCSTR lpsz );
例:CString csStr("abcdef");
//csStr="abcdef"

int GetLength( ) const;
返回字符串的长度,不包含结尾的空字符。
例:csStr="ABCDEF中文123456";
printf("%d",csStr.GetLength()); //16

void MakeReverse( );
颠倒字符串的顺序
例:csStr="ABCDEF中文123456";
csStr.MakeReverse();
cout<<csStr; //654321文中FEDCBA

void MakeUpper( );
将小写字母转换为大写字母
例:csStr="abcdef中文123456";
csStr.MakeUpper();
cout<<csStr; //ABCDEF中文123456

void MakeLower( );
将大写字母转换为小写字母
例:csStr="ABCDEF中文123456";
csStr.MakeLower();
cout<<csStr; //abcdef中文123456

int Compare( LPCTSTR lpsz ) const;
区分大小写比较两个字符串,相等时返回0,大于时返回1,小于时返回-1
例:csStr="abcdef中文123456";
csStr2="ABCDEF中文123456";
cout<<csStr.CompareNoCase(csStr2); //0

int CompareNoCase( LPCTSTR lpsz ) const;
不区分大小写比较两个字符串,相等时返回0,大于时返回1,小于时返回-1
例:csStr="abcdef中文123456";
csStr2="ABCDEF中文123456";
cout<<csStr.CompareNoCase(csStr2); //-1

int Delete( int nIndex, int nCount = 1 )
删除字符,删除从下标nIndex开始的nCount个字符
例:csStr="ABCDEF";
csStr.Delete(2,3);
cout<<csStr; // ABF
//当nIndex过大,超出对像所在内存区域时,函数没有任何操作。
//当nIndex为负数时,从第一个字符开始删除。
//当nCount过大,导致删除字符超出对像所在内存区域时,会发生无法预料的结果。
//当nCount为负数时,函数没有任何操作。


int Insert( int nIndex, TCHAR ch )
int Insert( int nIndex, LPCTSTR pstr )

在下标为nIndex的位置,插入字符或字符串。返回插入后对象的长度
例:csStr="abc";
csStr.Insert(2,'x');
cout<<csStr; //abxc
csStr="abc";
csStr.Insert(2,"xyz");
cout<<csStr; //abxyzc
//当nIndex为负数时,插入在对象开头
//当nIndex超出对象末尾时,插入在对象末尾


int Remove( TCHAR ch );
移除对象内的指定字符。返回移除的数目
例:csStr="aabbaacc";
csStr.Remove('a');
cout<<csStr; //bbcc

int Replace( TCHAR chOld, TCHAR chNew );
int Replace( LPCTSTR lpszOld, LPCTSTR lpszNew );

替换字串
例:csStr="abcdef";
csStr.Replace('a','x');
cout<<csStr; //xbcdef
csStr="abcdef";
csStr.Replace("abc","xyz");
cout<<csStr; //xyzdef

void TrimLeft( );
void TrimLeft( TCHAR chTarget );
void TrimLeft( LPCTSTR lpszTargets );

从左删除字符,被删的字符与chTarget或lpszTargets匹配,一直删到第一个不匹配的字符为止
例:csStr="aaabaacdef";
csStr.TrimLeft('a');
cout<<csStr; //baacdef
csStr="aaabaacdef";
csStr.TrimLeft("ab");
cout<<csStr; //cdef
//无参数时删除空格

void TrimRight( );
void TrimRight( TCHAR chTarget );
void TrimRight( LPCTSTR lpszTargets );

从右删除字符,被删的字符与chTarget或lpszTargets匹配,一直删到第一个不匹配的字符为止
例:csStr="abcdeaafaaa";
csStr.TrimRight('a');
cout<<csStr; //abcdeaaf
csStr="abcdeaafaaa";
csStr.TrimRight("fa");
cout<<csStr; //abcde
//无参数时删除空格


void Empty( );
清空
例:csStr="abcdef";
csStr.Empty();
printf("%d",csStr.GetLength()); //0

BOOL IsEmpty( ) const;
测试对象是否为空,为空时返回零,不为空时返回非零
例:csStr="abc";
cout<<csStr.IsEmpty(); //0;
csStr.Empty();
cout<<csStr.IsEmpty(); //1;

int Find( TCHAR ch ) const;
int Find( LPCTSTR lpszSub ) const;
int Find( TCHAR ch, int nStart ) const;
int Find( LPCTSTR pstr, int nStart ) const;

查找字串,nStart为开始查找的位置。未找到匹配时返回-1,否则返回字串的开始位置
例:csStr="abcdef";
cout<<csStr.Find('b'); //1
cout<<csStr.Find("de"); //3
cout<<csStr.Find('b',3); //-1
cout<<csStr.Find('b',0); //1
cout<<csStr.Find("de",4); //-1
cout<<csStr.Find("de",0); //3
//当nStart超出对象末尾时,返回-1。
//当nStart为负数时,返回-1。


int FindOneOf( LPCTSTR lpszCharSet ) const;
查找lpszCharSet中任意一个字符在CString对象中的匹配位置。未找到时返回-1,否则返回字串的开始位置
例:csStr="abcdef";
cout<<csStr.FindOneOf("cxy"); //2

CString SpanExcluding( LPCTSTR lpszCharSet ) const;
返回对象中与lpszCharSet中任意匹配的第一个字符之前的子串
例:csStr="abcdef";
cout<<csStr.SpanExcluding("cf"); //ab

CString SpanIncluding( LPCTSTR lpszCharSet ) const;
从对象中查找与lpszCharSe中任意字符不匹配的字符,并返回第一个不匹配字符之前的字串
例:csStr="abcdef";
cout<<csStr.SpanIncluding("fdcba"); //abcd

int ReverseFind( TCHAR ch ) const;
从后向前查找第一个匹配,找到时返回下标。没找到时返回-1
例:csStr="abba";
cout<<csStr.ReverseFind('a'); //3

void Format( LPCTSTR lpszFormat, ... );
void Format( UINT nFormatID, ... );

格式化对象,与C语言的sprintf函数用法相同
例:csStr.Format("%d",13);
cout<<csStr; //13

TCHAR GetAt( int nIndex ) const;
返回下标为nIndex的字符,与字符串的[]用法相同
例:csStr="abcdef";
cout<<csStr.GetAt(2); //c
//当nIndex为负数或超出对象末尾时,会发生无法预料的结果。


void SetAt( int nIndex, TCHAR ch );
给下标为nIndex的字符重新赋值
例:csStr="abcdef";
csStr.SetAt(2,'x');
cout<<csStr; //abxdef
//当nIndex为负数或超出对象末尾时,会发生无法预料的结果。


CString Left( int nCount ) const;
从左取字串
例:csStr="abcdef";
cout<<csStr.Left(3); //abc
//当nCount等于0时,返回空。
//当nCount为负数时,返回空。
//当nCount大于对象长度时,返回值与对象相同。


CString Right( int nCount ) const;
从右取字串
例:csStr="abcdef";
cout<<csStr.Right(3); //def
//当nCount等于0时,返回空。
//当nCount为负数时,返回空。
//当nCount大于对象长度时,返回值与对象相同。


CString Mid( int nFirst ) const;
CString Mid( int nFirst, int nCount ) const;

从中间开始取字串
例:csStr="abcdef";
cout<<csStr.Mid(2); //cdef
csStr="abcdef";
cout<<csStr.Mid(2,3); //cde
//当nFirst为0和为负数时,从第一个字符开始取。
//当nFirst等于对象末尾时,返回空字串。
//当nFirst超出对象末尾时,会发生无法预料的结果。
//当nCount超出对象末尾时,返回从nFirst开始一直到对象末尾的字串
//当nCount为0和为负数时,返回空字串。


LPTSTR GetBuffer( int nMinBufLength );
申请新的空间,并返回指针
例:csStr="abcde";
LPTSTR pStr=csStr.GetBuffer(10);
strcpy(pStr,"12345");
csStr.ReleaseBuffer();
pStr=NULL;
cout<<csStr //12345
//使用完GetBuffer后,必须使用ReleaseBuffer以更新对象内部数据,否则会发生无法预料的结果。


void ReleaseBuffer( int nNewLength = -1 );
使用GetBuffer后,必须使用ReleaseBuffer以更新对象内部数据
例:csStr="abc";
LPTSTR pStr=csStr.GetBuffer(10);
strcpy(pStr,"12345");
cout<<csStr.GetLength(); //3(错误的用法)
csStr.ReleaseBuffer();
cout<<csStr.GetLength(); //5(正确)
pStr=NULL;
//CString对象的任何方法都应在ReleaseBuffer之后调用

LPTSTR GetBufferSetLength( int nNewLength );
申请新的空间,并返回指针
例:csStr="abc";
csStr.GetBufferSetLength(20);
cout<<csStr; //abc
count<<csStr.GetLength(); //20;
csStr.ReleaseBuffer();
count<<csStr.GetLength(); //3;
//使用GetBufferSetLength后可以不必使用ReleaseBuffer。
posted @ 2012-08-10 17:48 爱走小路 阅读(170) | 评论 (0)编辑 收藏

C、传统 C++

#include <assert.h>    设定插入点
#include <ctype.h>    字符处理
#include <errno.h>     定义错误码
#include <float.h>    浮点数处理
#include <fstream.h>   文件输入/输出
#include <iomanip.h>    参数化输入/输出
#include <iostream.h>   数据流输入/输出
#include <limits.h>    定义各种数据类型最值常量
#include <locale.h>    定义本地化函数
#include <math.h>     定义数学函数
#include <stdio.h>    定义输入/输出函数
#include <stdlib.h>    定义杂项函数及内存分配函数
#include <string.h>    字符串处理
#include <strstrea.h>   基于数组的输入/输出
#include <time.h>     定义关于时间的函数
#include <wchar.h>     宽字符处理及输入/输出
#include <wctype.h>    宽字符分类

标准 C++ 

#include <algorithm>    通用算法
#include <bitset>      位集容器
#include <cctype>
#include <cerrno>
#include <clocale>
#include <cmath>
#include <complex>     复数类
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque>      双端队列容器
#include <exception>    异常处理类
#include <fstream>
#include <functional>    定义运算函数(代替运算符)
#include <limits>
#include <list>       线性列表容器
#include <map>       映射容器
#include <iomanip>
#include <ios>      基本输入/输出支持
#include <iosfwd>    输入/输出系统使用的前置声明
#include <iostream>
#include <istream>     基本输入流
#include <ostream>     基本输出流
#include <queue>      队列容器
#include <set>       集合容器
#include <sstream>     基于字符串的流
#include <stack>      堆栈容器    
#include <stdexcept>    标准异常类
#include <streambuf>   底层输入/输出支持
#include <string>     字符串类
#include <utility>     通用模板类
#include <vector>     动态数组容器
#include <cwchar>
#include <cwctype>

C99 增加

#include <complex.h>  复数处理
#include <fenv.h>    浮点环境
#include <inttypes.h>  整数格式转换
#include <stdbool.h>   布尔环境
#include <stdint.h>   整型环境
#include <tgmath.h>  通用类型数学宏

posted @ 2012-08-10 17:47 爱走小路 阅读(59) | 评论 (0)编辑 收藏

50条忠告:(其中有几条觉得写的不够贴切,所以删了,发了余下的部分)

 

1.把C++当成一门新的语言学习;

2.看《Thinking In C++》,不要看《C++变成死相》;

3.看《The C++ Programming Language》和《Inside The C++ Object Model》,不要因为他们很难而我们自己是初学者所以就不看;

4.不要被VC、BCB、BC、MC、TC等词汇所迷惑——他们都是集成开发环境,而我们要学的是一门语言;

5.不要放过任何一个看上去很简单的小编程问题——他们往往并不那么简单,或者可以引伸出很多知识点;

6.会用Visual C++,并不说明你会C++;

7.学class并不难,template、STL、generic programming也不过如此——难的是长期坚持实践和不遗余力的博览群书;

8.如果不是天才的话,想学编程就不要想玩游戏——你以为你做到了,其实你的C++水平并没有和你通关的能力一起变高——其实可以时刻记住:学C++是为了编游戏的;

9.看Visual C++的书,是学不了C++语言的;

16.把时髦的技术挂在嘴边,还不如把过时的技术记在心里;

18.学习编程最好的方法之一就是阅读源代码;

19.在任何时刻都不要认为自己手中的书已经足够了;

20.请阅读《The Standard C++ Bible》(中文版:标准C++宝典),掌握C++标准;

21.看得懂的书,请仔细看;看不懂的书,请硬着头皮看;

22.别指望看第一遍书就能记住和掌握什么——请看第二遍、第三遍;

23.请看《Effective C++》和《More Effective C++》以及《Exceptional C++》;

24.不要停留在集成开发环境的摇篮上,要学会控制集成开发环境,还要学会用命令行方式处理程序;

25.和别人一起讨论有意义的C++知识点,而不是争吵XX行不行或者YY与ZZ哪个好;

26.请看《程序设计实践》,并严格的按照其要求去做;

27.不要因为C和C++中有一些语法和关键字看上去相同,就认为它们的意义和作用完全一样;

28.C++绝不是所谓的C的“扩充”——如果C++一开始就起名叫Z语言,你一定不会把C和Z语言联系得那么紧密;

29.请不要认为学过XX语言再改学C++会有什么问题——你只不过又在学一门全新的语言而已;

30.读完了《Inside The C++ Object Model》以后再来认定自己是不是已经学会了C++;

31.学习编程的秘诀是:编程,编程,再编程;

32.请留意下列书籍:《C++面向对象高效编程(C++ Effective Object-Oriented Software Construction)》《面向对象软件构造(Object-Oriented Software Construction)》《设计模式(Design Patterns)》《The Art of Computer Programming》;

34.请把书上的程序例子亲手输入到电脑上实践,即使配套光盘中有源代码;

35.把在书中看到的有意义的例子扩充;

36.请重视C++中的异常处理技术,并将其切实的运用到自己的程序中;

37.经常回顾自己以前写过的程序,并尝试重写,把自己学到的新知识运用进去;

38.不要漏掉书中任何一个练习题——请全部做完并记录下解题思路;

39.C++语言和C++的集成开发环境要同时学习和掌握;

40.既然决定了学C++,就请坚持学下去,因为学习程序设计语言的目的是掌握程序设计技术,而程序设计技术是跨语言的;

41.就让C++语言的各种平台和开发环境去激烈的竞争吧,我们要以学习C++语言本身为主;

42.当你写C++程序写到一半却发现自己用的方法很拙劣时,请不要马上停手;请尽快将余下的部分粗略的完成以保证这个设计的完整性,然后分析自己的错误并重新设计和编写(参见43);

43.别心急,设计C++的class确实不容易;自己程序中的class和自己的class设计水平是在不断的编程实践中完善和发展的;

44.决不要因为程序“很小”就不遵循某些你不熟练的规则——好习惯是培养出来的,而不是一次记住的;

45.每学到一个C++难点的时候,尝试着对别人讲解这个知识点并让他理解——你能讲清楚才说明你真的理解了;

46.记录下在和别人交流时发现的自己忽视或不理解的知识点;

47.请不断的对自己写的程序提出更高的要求,哪怕你的程序版本号会变成Version 100.XX;

48.保存好你写过的所有的程序——那是你最好的积累之一;

49.请不要做浮躁的人;

50.请热爱C++!

posted @ 2012-08-10 17:46 爱走小路 阅读(165) | 评论 (1)编辑 收藏
     摘要: C++:STL标准入门汇总 学无止境!!! 第一部分:(参考百度百科) 一、STL简介 STL(Standard Template Library,标准模板库)是惠普实验室开发的一系列软件的统称。它是由Alexander Stepanov、Meng Lee和David R Musser在惠普实验室工作时所开发出来 的。现在虽说它主要出现在C++中,但在被引入C++之前该技术就已经存...  阅读全文
posted @ 2012-08-10 17:45 爱走小路 阅读(93) | 评论 (0)编辑 收藏
仅列出标题