C++ Builder 初学问与答(十六-二一)

3 views
Skip to first unread message

江边之鸟

unread,
Dec 13, 2007, 3:28:38 AM12/13/07
to Linux开发联盟
16.C/C++语言在CB中的一些特定用法



  2)AnsiString是从Delphi中引进来的吗?
  答:CB的核心组件VCL是用Object Pascal语言写出的,所以CB的VCL组件的属性有很多都是使用long string,例如:
Text、Name、Caption等属性都是使用Object Pascal的long string。基于此关系,CB只好建立了和
ObjectPascal的long string相应的类别,我们将它称为AnsiString。
  3)AnsiString与String有什么区别?
  答: C++ Builder在Sysdefs.h头文件中有这样的定义:
  typedef AnsiString String;
  从这一点在看他二者是完全一样的,只是书写起来后者更方便一些,前者是意图更明了一些。
  4)能介绍一下AnsiString类的一些常见函数及其用法吗?
  答:当然可以了,下面就常见函数做一下简单介绍:

成员函数
语法
功能
c_str
char *_fastcall c_str() const
返回字符串数据的指针
Delete
void _fastcall Delete(int index,int count)
由index处开始删除count个字符
Insert
void _fastcall Insert(const AnsiString& str,int index)
由index处开始插入字符str到原字符串中
IsEmpty
bool _fastcall IsEmpty() const
返回字符串是否为空,true表示空字符串
Length
int _fastcall Length() const
返回字符串长度
LowerCase
AnsiString _fastcall LowerCase() const
将字符串中的大写字母改写成小写
UpperCase
AnsiString _fastcall UpperCase() const
将字符串中的小写字母改写成大写
Pos
int _fastcall Pos(cont AnsiString& substr)const;
找出子字符串在原字符串中由第几个位置开始
SubString
AnsiString _fastcall SubString(int index,int count)const
返回由index处向后取count个字符
ToDouble
double _fastcall ToDouble() const
将字符转成双精度数值
ToInt
int _fastcall ToInt() const
将字符转成整形
IntToStr
AnsiString _fastcall IntToStr(int Value)
将整形转成字符串
Trim
AnstString _fastcall Trim() const
返回一个新的字符串,它会将原字符串前后空白或控制字符清除后再返回
WideChar
wchar_t fastcall WideChar(wchart* dest,int destsize)const
转换AnsiString到一个宽字符数组(COM中常用)


[ 本帖最后由 boboo 于 2007-12-3 10:54 编辑 ]

论坛模式 推荐 收藏 分享给好友 管理

TAG:

boboo的个人空间 boboo 发布于2007-12-03 10:47:40
5)AnstString成员函数与传统字符串函数有何不同之处?  答:我们可以通过二者的一个对照表来查看他们的之间的不同之处:

AnsiString成员函数
功能
传统字符串函数
=
字符串拷贝
strcpy
+=
字符串合并
strcat
+
字符串连接

==,!+=,<,<=,>,>=
字符串比较
strcmp
c_str()
相互转换
无,但可以利用指针实现
Delete
删除子字符串

Insert
插入子字符串

Length
求字符串长度
strlen
LowerCase
字母转成小写
strlwr
Pos
找出子字符串
strstr
SetLength
设置字符串长度

ToInt
转成整形

ToDouble
转成双精度
sprintf
UpperCase
字母转成大写
strupr

  6)如何实现传统字符串与AnsiString字符串的相互转换?
  答:传统的C语言是使用字符来组成字符串的(字符串末尾必须有'\0'做为结束标志),其格式和AnsiString并不相同。由于在CB
中我大量使用AnsiString格式,所以有时也难免要进行一些转换,我们可以通过下列三种方法来实现:
  方法1:AnsiString字符串转换成字符串数组:
  先利用AnsiString类别中的c_str()方法转换成传统字符串数组,再用strcpy拷贝,将它拷贝到字符数组中去:
  char s1[20];
  strcpy(s1,Edit1->Text.c_str());
  方法2:AnsiString转换成传统字符串可以利用字符指针来实现:
  可以通过字符指针来实现:
  char *s;
  s=Edit->Text.c_str();
  方法3:利用字符指针实现传统字符串转换成AnsiString:
  char *s="试一试,看看能不能成功!";
  Edit->Text=s;

boboo的个人空间 boboo 发布于2007-12-03 10:48:27
7)Set类如何用?

  答:严格来说,CB的集合(Set)并不是一个类,而是一个类模板(Class Template)。它用来实现集合这个抽象数据类型。

  使用它可以定义一个实际的集合类型,它的一般声明模式是这样的:






  typedef Set<type,minval,maxval>ClassName;

  其中type用来指定集合元素的类型;minval指定集合元素的最小值;maxval指定集合元素的最大值。

  举个例子来说,若是我们定义一个以TupperSet为名的集合类,这个集合类的可能元素为26个大写字母,我们可以这样定义:

  typedef Set<char,'A','Z'>TupperSet;

  应用时我们可以使用这个集合类声明一个具体的集合对象,如:

  TupperSet UpperSet;

  8)能不能告诉我一下Set类有哪些常见的使用方法或操作符?

  答:当然可以了,以下是它的一些常见操作符和方法:

  Clear方法:清除集合对象内的所有元素,使集合成为空集;

  Contains方法:查询集合中是否包含指定的元素,其声明为:

  bool _fastcall Contains(const T el)const;
  operator-:两个集合进行求差运算,结果集合包含两个集合中不相同的元素;
  operator*:求两个集合的交集;
  operator+:求两个集合的并集;
  operator<<:添加一个元素到指定集合;
  operator>>:从集合中删除一个指定元素。
  其实Set类的应该比较广泛,比如判断我们是否按下鼠标左键,可以用如下的语句:
  if(Shift.Contains(ssLeft))。

  9)听说在CB中有一个可以定时触发的组件,能否介绍一下它的属性和事件吗?

  答:你说的是Timer组件,它位于组件栏的System组件页中。使用它并不难,因为他的属性、事件特别少:

  Enabled属性:布尔类型。缺省时为true,这时定时器将经过一段时间就触发OnTimer事件。在这个属性从false变为
true时,时钟将重新开始计时。

  Interval属性:这个属性用来设置每隔多长时间就触发一次OnTimer事件,以毫秒为单位。缺省为1000毫秒。

  OnTimer事件:这个事件在Enabled属性设置为true时,每隔Interval属性指定的时间触发一次。

  10)原来Timer组件如此简单,但我还有些不懂为什么说它是一个可找替循环语句,能否举一个例子说明下OnTimer事件?

  答:它不能取替循环语句的,当然在特定条件下,他比循环语句更有效,尤其是在涉及到按时循环时。在我编写的春辉网络电视里有一段
OnTimer事件代码,可供大家参考:

  void __fastcall TForm1::Timer1Timer(TObject *Sender)
  {
  String s1="春辉(CH)软件  作者:董维春 王岩  CH工作室";
  if(i<=54)
  {
  Label1->Caption=s1.SubString(i,18);
  i+=2;
  }
  else
  {
  i=1;
  }
  }

boboo的个人空间 boboo 发布于2007-12-03 10:52:40
11)在CB中听说有动态数组之说,我想问什么是动态数组?

答:动态数组(DynamicArray)是CB中引起的,它可以动态地改变数组长度,并且象一般数组那样易于使用。DynamicArray
可以说是VCL中的一个重大的革新。

12)动态数组是如何实现与应用的呢?

答:动态数组是以类的方式实现的。CB提供了DynamicArray类模板,使用这个类模板可以声明实际动态数组。使用下面的语法:

DynamicArray<type>ArrayName;

其中type为动态数组的数据类型,动态数组支持任何类型的数据、对象,甚至是一个动态数组对象。例如可以这样声明一个动态数组:

DynamicArray<DynamicArray<AnsiString>>aArray;

这相当于声明了一个二维动态数组。

动态数据具有一个Length属性,通过这个属性可以设置或获得数组的长度。例如:

DynamicArray<int>IntArray;

IntArray.Length=10;

ShowMessage("ArrayLength:"+IntToStr(IntArray.Length));

若是要释放一个动态数组,应该将该数组的Length属性设为0。

动态数组有Low和High两个属性,分别表示动态数组的起始下标和中止下标。起始下标总是为0,而中止下标总是等于Length-1。

动态数组实现了"="操作符,可以复制整个数组到另一个数组。同时,动态数组还提供了Copy和CopyRange方法,CopyRange方
法可以复制指定范围的数据。

13)VCL对象在堆中与在栈中创建是不是都可以?

答:VCL对象只能在堆中创建,在栈里不可以的。

栈是存放函数的所有动态局部变量及函数调用和返回的有关信息的一块内存。栈的内存管理严格遵循先进后出的顺序,这一点正是实现函数调用所需要
的。从栈中分配内存效率特别高。数据对象使用栈中的内存(如动态局部变量)比使用堆中内存会使程序运行更快。

堆提供了malloc()、calloc()、realloc()和new等函数获取内存空间的一块内存。从堆中获取内存比从栈中要慢得多,但
堆的内存管理却比栈灵活得多,任何时候你都可以从堆中获取(或释放)内存,我们可以按任意顺序进行。用来存放递归数据结构的内存几乎都要从堆中获取。用
来存放字符串的内存通常也从堆中获取,尤其是对那些在程序运行时可能出现的很长的字符串。

从堆中获取的内存要用free()、delete来释放,它本身不会自动释放。

由于Object Pascal中所有的对象都只能建构于堆中,无法和C++一样,能够在栈(在函数内创建类的对象)、数据区段(在函数外创建
类的对象)、堆(用new等函数来创建类的对象)三种地方建立对象,所以VCL类的对象我们只能在堆中创建。

如创建一个按钮对象,我们可以这样来创建:

TButton *btnMy= new Tbutton(NULL);

可以写成如下程式:类名 *对象名=new 类名();

()里面可以是你已创建的该类对象实体的名字,工程的名字,或NULL。

14)问:dynamic_cast是用来做什么的?

答:dynamic_cast 可以把某种对象强制转成另一个类,这里所谓的强制仍有其局限,也就是说,如果类转不过来,那么系统将不会进行转
换操作的。若类型转换无法成功则返回一个值是0的指针。若参数T是一个参考类型,而类的转换又失败了,系统将会丢出一个异常处理信息:
Bad_cast。但你放心这不会导致系统死机,所以可以放心使用。其程式:

dynamic_cast <T> (ptr)

T参数一定要是一个指针、void* 、或是已经定义过的类,而ptr参数则必须是一个指针(pointer) 或是一个引用
(reference)。如果T的类型是void*,那么ptr则是一个可以访问最下面类里的任何成员,当然这样的类将不可以是基础类。

boboo的个人空间 boboo 发布于2007-12-03 10:53:01
15)如何在CB中用Sender实现代码重用?

答:面向对象的编程工具的特点之一就是要提高代码重用性(Reuse),宝兰的BCB当然可以实现这一功能。我们都知道,在BCB中,大部分程
序代码都直接或间接的对应着一个事件,此程序称为事件处理句柄,它实际上就是一个过程。从应用程序的工程到窗口、组件和程序,BCB强调的是其开发过程
中每一层次的重用性,可以充分利用已编写过的代码来减少工作量,更会使你的程序变得优美。代码段间的共享都跟发生该事件的控件有关有关,需要根据控件类
型做出相应的处理,这时就要用到Sender参数。

每个函数的开头都有形如:

void _fastcall TForm1::Button1Click(TObject *Sender)

其中的Sender是一个TObject类型的参数,它告诉BCB哪个控件接收到这个事件并调用相应的处理过程。我们可以编写一个单一的事件处
理句柄,通过Sender参数和if语句或者case语句配合,来处理多个组件。在Delphi中可以用IS来测试Sender类型,或者用AS进行类
型转换, BCB我们只在用dynamic_cast来进行上面两个工作。

1.进行判断

我们用dynamic_case来测试Sender,以便找到调用这个事件的处理句柄或组件的类型。如,我们将窗口中的编辑框和标签的
Click事件的处理句柄都指向窗口的xxx函数(其实你只要先把一个控件的Click事件命名为xxx,并在其中写上共享代码,其它控件的Click
事件都指向xxx就行了),本例中的编辑框和标签对Click事件将有不同的反应,代码如下:

void __fastcall TForm1::xxx(TObject *Sender)

{

if(dynamic_cast<TEdit *>(Sender))

ShowMessage("This is a editbox");

if(dynamic_cast<TLabel *>(Sender))

ShowMessage("This is a label");

}

当然若是多个同类组件,只是想共用一个事件,那要比这简单多了。举个例子,若你的很多编辑框(Edit),你想在输入某一项的时候先把这一项清
空,你只要写一个OnEnter事件就可以了:

void __fastcall TForm1::Edit1Enter(TObject *Sender)

{

TEdit *Edittemp=(TEdit*)(Sender);//把不同的编辑框统一起来

Edittemp->Text="";



}

其它的Edit组件的OnEnter事件都指向Edit1Enter,这样就行了,试一下,是不是鼠标放在编辑框里一点就清空了J其实这里只是
把不同的编辑框(Sender清楚是那一个编辑框)统一起来,好用一个共同的事件来处理。你在同一组件共用同一事件时一定要注意这一点。

2.强制进行类型转换

将若干继承同一父类的子类强制转换成该父类。如窗口中有一个TEdit类控件和一个TMemo控件,它们实际上都继承于TCustomEdit
类,如果你要为二者的某一事件提供同样的处理,可以将二者的事件句柄都指向自定义的函数yyy,我们这里仍然是在OnEnter事件中(当然你完全可以
在其它事件中完成):

void __fastcall TForm1::yyy(TObject *Sender)

{

dynamic_cast<TCustomEdit &>(*Sender).Text="This is some demo
text";

}

或以下的格式:

void __fastcall TForm1::yyy(TObject *Sender)

{

dynamic_cast<TCustomEdit* >(Sender)->Text="This is some demo
text";

}

注意二者的区别,这其实这正是"."与"->"的不同之处,仔细品味一下,你会清楚的。

上面的两个程式均是先把TEdit类和TMemo类均强制转换成TCustomEdit类,再对其父类的属性进行赋值。

使用Sender参数可以通过单一函数段处理多类组件,真正体现了BCB的面向对象的重用性。

boboo的个人空间 boboo 发布于2007-12-03 10:53:17
17 事件及事件响应

16)问:从前面的编程过程可以看出,用C++Builder开发应用程序的一个特点是,大部分的编程工作是在响应事件和处理事件上。事件的产
生可能来自于系统,也可能来自于用户。您能给我详细介绍一下事件吗?

答:好的,这节课我们将对事件进行深入地探讨,并重点讲述常用的鼠标和键盘事件,以及这些事件的事件处理过程。

那么什么是事件呢?

事件是一种把发生的动作与代码相连的机制。更精确地讲,事件是一个方法指针,它指向某个类实例的方法。

对于应用程序开发人员来说,一个事件只是一个与系统动作相关的名字,例如鼠标单击事件OnClick,这个名字与一段代码相连。



17)问:这个问题您能说具体一点吗?

答:一个名为Button1的按钮有一个OnClick方法。在缺省情况下,C++Builder会在包含这个按钮的窗体中产生一个名为
Button1Click的事件处理过程,并把这个过程赋给OnClick。当按钮发生OnClick事件时,按钮就会调用这个赋给OnClick事件
的方法,也就是Button1Click。



18)问:我编写了一个程序,菜单File中有一个打开文件菜单命令,而工具条上经常也有一个打开文件的加速按钮。我是不是要将这个程序编写两
次呢?

答:不需要,在某些情况下,可能需要用一个事件处理过程响应几个不同的事件。这时只需要将几个事件共用一个事件处理过程就可以了。



19)问:怎样实现把一个已有的事件处理过程同另外一个事件相连呢?

答:首先选中这个组件,然后在对象编辑器中的Event页中选择这个事件,单击事件右边的下拉按钮,从一系列已有的事件处理过程中选择需要重用
的那个事件。



20)问:在编程中经常需要对鼠标事件进行响应,C++Builder是怎样处理鼠标事件的呢?

答:C++Builder提供了三种鼠标事件:OnMouseDown,OnMouseMove和OnMouseUp。另外还有最常用的
OnClick事件也与鼠标事件有关。



21)问:与一般鼠标事件相比,OnClick事件有什么特别之处?

答:OnClick事件表示在同一个组件上按下并释放鼠标左键。如果只是在组件上按下鼠标左键,而在组件之外释放鼠标左键,那么就不会产生
OnClick 事件,而会产生三种鼠标事件。另外,只有鼠标左键的单击才能产生OnClick事件,鼠标右键对组件的单击,例如要弹出快捷菜单,只能
产生鼠标事件。

鼠标事件处理过程一般包含五个参数

(1)Sender参数,用来指出产生鼠标事件的组件对象。

(2)Button参数,用来指出对哪个鼠标键进行响应,有mbRight右键、mbLeft左键和mbCenter中间键三种可能的取值。

(3)Shift参数,指定鼠标键同时按下的键,这些键可以是Shift键、Ctrl键和Alt键。

(4)X和Y参数指定光标所在的点的坐标。



22)问:拖曳事件是一种经常发生的事件,因为这种操作非常简单直观,所以在Windows环境中被广泛地使用着。例如,在Windows
XP操作系统中的文件拷贝操作,答,C++Builder是怎样处理拖曳事件的?

答:C++Builder根据拖放的过程,将开发步骤划分为四个阶段:开始拖放阶段、接受拖放项目、放下拖放项目、终止拖放操作。



23)问:开始拖曳阶段主要完成什么功能?

答:C++Builder的大多数组件都有一个叫做DragMode的属性,这个属性可以设置拖曳的方式。如果DragMode的值等于
dmAutomatic,那么表示只要用鼠标在这个组件上按一下就自动进入拖曳操作。但是通常把这个属性设置为dmManual,这就表示要开始拖曳操
作就必须在鼠标的OnMouseDown事件中显式地调用该组件的BeginDrag方法。

在组件的OnMouseDown事件处理函数中加入这样一段代码

第二条语句判断用户是否按下了鼠标左键,否则退出;

第三条语句计算用户按下了哪一个标签;

最后一条语句判断用户按下的是不是可以拖拽的标签,如果是就进入拖拽过程。

其中BeginDrag方法带有两个参数,一个是布尔类型的参数,如果把这个参数设置为True,表示立即进入拖曳操作。如果把这个参数设置
为 False,那么要等鼠标拖曳了一段距离后才进入拖曳操作。这有一个好处,那就是避免了用户一按下鼠标就激活了拖曳操作,第二个参数是一个整型值,
它用来设定鼠标要拖动多大一段距离才开始进入拖曳操作。



24)问:控件又是怎样接受拖曳的呢?

答:当你拖曳某个组件经过另一个控件时,将产生OnDragOver事件,在对象观察器中双击控件的OnDragOver事件域,生成它的处理
模板。

可以看出OnDragOver事件的事件处理过程中有几个参数:

(1)Source参数用来表示被拖曳的对象,我们一般使用该参数判断是否接受被拖曳的对象。

(2)X,Y参数用来表示鼠标当前的位置。

(3)State参数用来描述拖拽的状态。

(4)Accept参数用来设定这个控件是否接受这个拖拽事件。如果把Accept参数设为true,那么就表示在这个控件上释放鼠标将产生一
个表示拖拽放下的OnDragDrop事件。如果Accept参数设置为false,表示这个控件不产生Drag-Drop事件。可见这个控件是否接受
拖拽还取决于 Accept参数是怎样设置的。

boboo的个人空间 boboo 发布于2007-12-03 10:53:35
25)问:控件是怎样处理拖曳的呢?

答:当控件接受被拖曳的对象后会产生OnDragDrop事件,我们需要对这个事件进行响应,在对象观察器中双击控件的OnDragDrop事
件域,生成它的处理模板。

加入这样一段代码,

第二条语句判断被拖拽的对象是不是自己,否则不接受拖拽

第三条语句计算被拖拽的是哪一个标签

第四条语句计算被拖拽到哪一个标签

第五条语句的作用是如果被拖拽到的标签是有效标签并且和被拖拽的标签不是同一个标签的时候移动标签。



26)问:控件是怎样结束拖曳的呢?



答:当你释放鼠标时,被拖曳的对象将会产生OnEndDrag事件,响应该事件的事件处理过程有一个Target参数,如果这个参数是nil表
示这个对象未被接受,否则该参数就是接受这个对象的控件。在这个例子中被拖曳的对象就是标签组件

加入这样一段代码,它根据Target的值判断是否拖拽成功,并加以提示。



27)问:C++Builder提供了哪些方法来处理键盘方法?

答:C++Builder提供了三种键盘事件:OnKeyDown,OnKeyPress和OnKeyUp。这三种事件均可以获得用户的击
键。

要注意的是,如果用户按下的是快捷键或者热键,那么你并不需要编写事件处理过程,例如你按下了Alt+F激活了File菜单,这个操作由系统完
成,你不需要参与。



28)问:C++Builder提供的这三种键盘事件之间有什么区别?

答:按下任何键都会产生OnKeyDown和OnKeyUp事件,而OnKeyPress事件是由单个的字符键产生的。也就是说一些没有对应
ASCII字符的键,例如Ctrl, Shift, Alt, F1等功能键不会产生OnKeyPress事件。

OnKeyDown和OnKeyUp事件的事件处理过程中包含了最近一次击键发生时,控制键Alt,Shift或者Ctrl是否按下的信息,而
OnKeyPress事件只返回键的ASCII字符。

假设用户按下了Shift+P键,那么这三种键盘事件产生的顺序如下所示:

(1)按下Shift键时将产生OnKeyDown事件。

(2)保持Shift键再按下P时将产生OnKeyDown事件。

(3)由于按下了P键,所以产生了OnKeyPress事件。

(4)释放Shift键和P键,将产生OnKeyUp事件。

OnKeyDown和OnKeyUp事件的事件处理过程有三个参数,除了我们大家熟悉的参数Sender外,还有一个变量参数Key和控制键参
数 Shift。变量参数Key是一个字符类型的变量,它保存着用户按下的字符键。例如,要判断用户是否按下了字符键H,可以这样编写代码:

if (Key=='P'){...}

注意,这里如果是判断一个字母,那么需要小心字母的大小写。

要判断是否按下了控制键,可以用方法Contains

来判断用户是否按下了Shift键。

与前面两个事件相比,OnKeyPress事件的事件处理过程没有控制参数Shift,只有当你按下了单个字符键(例如字母键、数字键、空格键
等)时,这个事件才会产生,变量参数Key中才会包含这个字符。

29)问:学习事件及事件响应应该注意什么?

答:在学习事件时要抓住两个关键点:一是事件产生的条件,二是事件处理过程所使用的参数。前者使你能够正确地判断该事件是否能够产生,后者使你
能够正确地处理事件。

另外,还要理解多个同类事件的细微区别,就像三种键盘事件在相似的外表下,有着不同产生条件。
Reply all
Reply to author
Forward
0 new messages