选择特殊符号

选择搜索类型

热门搜索

首页 > 百科 > 建设工程百科

构造函数

构造函数 ,是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们 即构造函数的重载。

构造函数基本信息

构造函数应用

C 构造函数

C 语言为类提供的构造函数可自动完成对象的初始化任务,全局对象和静态对象的构造函数在main()函数执行之前就被调用,局部静态对象的构造函数是当程序第一次执行到相应语句时才被调用。然而给出一个外部对象的引用性声明时,并不调用相应的构造函数,因为这个外部对象只是引用在其他地方声明的对象,并没有真正地创建一个对象。

C 的构造函数定义格式为:

class <类名>

{

public:

<类名>(参数表);

//...(还可以声明其它成员函数)

};

<类名>::<函数名>(参数表)

{

//函数体

}

如以下定义是合法的:

class T

{

public:

T(int a=0){ i=a; }//构造函数允许直接写在类定义内,也允许有参数表。

private:

int i;

};

如果一个类中没有定义任何的构造函数,那么编译器只有在以下三种情况,才会提供默认的构造函数:

1、如果类有虚拟成员函数或者虚拟继承父类(即有虚拟基类)时;

2、如果类的基类有构造函数(可以是用户定义的构造函数,或编译器提供的默认构造函数);

3、在类中的所有非静态的对象数据成员,它们对应的类中有构造函数(可以是用户定义的构造函数,或编译器提供的默认构造函数)。

<类名>::<类名>(){},即不执行任何操作。

例子

#include
 
  
usingnamespacestd;
classtime
{
public:
time()//constructor.构造函数
{
hour=0;
minute=0;
sec=0;
}
voidset_time();
voidshow_time();
private:
inthour,minute,sec;
};
intmain()
{
classtimet1;
t1.show_time();
t1.set_time();
t1.show_time();
return0;
}
voidtime::set_time()
{
cin>>hour>>minute>>sec;
}
voidtime::show_time()
{
cout<
   
   

程序运行情况:

0:0:0

10 11 11 回车

10:11:11

任何时候,只要创建类或结构,就会调用它的构造函数。类或结构可能有多个接受不同参数的构造函数。构造函数使得程序员可设置默认值、限制实例化以及编写灵活且便于阅读的代码。

PHP的构造函数

void __construct( [mixed args [, ...]] )

php 5 允行开发者在一个类中定义一个方法作为构造函数。具有构造函数的类会在每次创建对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。

注:如果子类中定义了构造函数则不会暗中调用其父类的构造函数。要执行父类的构造函数,需要在子类的构造函数中调用 parent::__construct()。

例子 使用新标准的构造函数

<"InBaseClassconstructor/n";}}
classSubClassextendsBaseClass{
function__construct(){
parent::__construct();print"InSubClassconstructor/n";}}
$obj=newBaseClass();$obj=newSubClass();
"para" label-module="para">

为了实现向后兼容性,如果 php 5 在类中找不到__construct()函数,它就会尝试寻找旧式的构造函数,也就是和类同名的函数。因此唯一会产生兼容性问题的情况是:类中已有一个名为__construct()的方法,但它却又不是构造函数。

Python的构造函数

定义格式为

class <类名>:

__init__(self [,参数表]):

#函数体

#其它的方法和属性

构造函数其他特点

1.它的函数名与类名相同;

2.它可以重载;

3.不能指定返回类型,即使是void也不行;

4.虽然在一般情况下,构造函数不被显式调用,而是在创建对象时自动被调用。但是并不是不能被显式调用。有些时候是一定要显式调用的,只要是父类有带参的构造函数,在子类中就必须显式的调用父类的构造函数,因为子类的构造器在实例化时无法找到父类的构造函数(当父类有自己写的无参构造函数时,子类也不用显式调用)。

void__destruct( void )

php 5 引入了析构函数的概念,这类似于其它面向对象的语言,如 C 。析构函数会在到某个对象的所有引。

构造函数语法

  • Java, C , C#, ActionScript和PHP 4中的命名规范会要求构造器函数的名称与它所在类的名称相同。

  • PHP 5 建议的构造器函数名称为__construct。为了保证向下兼容,__construct方法无法找到时会调用任何跟类名同名的方法作为构造器。从 PHP 5.3.3 起,这种途径只对非名字空间的类有效。

  • 在Perl里,构造器被约定俗成地命名为"new",并且会完成创建对象的大量工作。

  • 在 Perl 的 Moose 对象系统中,构造函数(叫做 new)是自动生成的,程序员可以通过指定一个 BUILD 方法来对其进行扩充。

  • 在 Visual Basic .NET里,构造器被命名为New,是个 Sub。

  • Python里构造器的被分为 __new__ 和 __init__ 两个方法。__new__方法负责为实例分配内储存空间,并接受自身的类作为参数(一般命名为 cls)。__init__方法接受被新建的实例作为参数(一般称为 self)。

  • Object Pascal 的构造函数用关键字 constructor 标识,并且可以起任意名字(但一般来说会被起名为 Create)。

  • Objective-C 的构造函数分成两个方法,alloc 和 init。alloc 方法分配内存,init 负责初始化。new 方法会调用 alloc 和 init 两者。

构造函数内存机制

在 Java, C# 和 VB .NET 里,构造器会在一种叫做堆的特殊数据结构里创建作为引用类型的实例。值类型(例如 int, double 等等)则会创建在叫做栈的有序数据结构里。VB .NET and C# 会允许用new来创建值类型的实例。然而在这些语言里,即使使用这种方法创建的对象依然只会在栈里。

在 C 里,不用 new 创建的对象会保存在栈里,使用 new 创建时则会在堆里。它们必须分别使用析构函数或者delete操作才能被删除。

构造函数语言细节

构造函数Java

在Java里,构造器和其他方法的主要差别在于:

  • 构造器不具有任何显性返回类型。

  • 构造器无法被直接“new” invokes them).

  • 构造器无法被标示为synchronized, final, abstract, native, 或者static

Java 里的构造器会按照以下顺序完成下列工作:

  1. 将类变量初始到缺省值。(byte, short, int, long, float, 和 double 变量会默认设为它们相应的0值,booleans 会被设为 false, chars 会被设为空字符(''),对象引用会被设为 null)

  2. 引用父类的构造器,如果没有定义任何构造器。

  3. 将实例变量初始化到指定值。

  4. 执行构造器内的代码。

在 Java 中可以通过关键词super访问父类的构造器。

publicclassExample{//Definitionoftheconstructor.publicExample(){this(1);}//OverloadingaconstructorpublicExample(intinput){data=input;//Thisisanassignment}//Declarationofinstancevariable(s).privateintdata;}
//Codesomewhereelse
//Instantiatinganobjectwiththeaboveconstructor
Examplee=newExample(42);

不接收任何参数的构造器被称作“无参数构造器”。

构造函数Visual Basic .NET

在Visual Basic .NET中, 构造函数以"New"为定义方法,并且必须是个 Sub。

ClassFoobarPrivatestrDataAsString'ConstructorPublicSubNew(ByValsomeParamAsString)strData=someParamEndSubEndClass
'codesomewhereelse
'instantiatinganobjectwiththeaboveconstructor
DimfooAsNewFoobar(".NET")

构造函数C#

publicclassMyClass{privateinta;privatestringb;//ConstructorpublicMyClass():this(42,"string"){}//OverloadingaconstructorpublicMyClass(inta,stringb){this.a=a;this.b=b;}}
//Codesomewhere
//Instantiatinganobjectwiththeconstructorabove
MyClassc=newMyClass(42,"string");

C# 静态构造函数

在C#中,静态构造函数是用来初始化任何静态数据。静态构造函数也称为“类构造函数”,由于类构造函数在生成的 MSIL 里名为“.cctor”,因此也被称为“cctor”。

静态构造函数允许复杂的静态变量初始化。

静态构造函数在该类第一次被访问时调用,任何使用该类的操作(无论是调用静态函数、属性还是访问静态变量,还是构造类的实例)都会引发静态构造函数的执行。静态构造函数是线程安全的,并且是单例的。当用在泛型类中时,静态构造函数对于泛型的每个实例化都调用一次。静态变量也同样如此。

publicclassMyClass{privatestaticint_A;//NormalconstructorstaticMyClass(){_A=32;}//StandarddefaultconstructorpublicMyClass(){}}//Codesomewhere
//Instantiatinganobjectwiththeconstructorabove
//rightbeforetheinstantiation
//Thevariablestaticconstructorisexecutedand_Ais32
MyClassc=newMyClass();

构造函数C

C 的构造函数以该类的类名为标识,且不写返回值类型也无法返回值 :

classC{public:C(void){...}};

构造函数的函数体执行是在各个成员构造完之后才开始,因此要想更改成员的构造方式需要使用成员初始化列表:

classD:publicB{public:D(void):B("Hello,world!"){...}};

复制构造函数接受同类对象的左值引用(一般为 const T &)、移动构造函数接受右值引用(一般为 T&&):

classE{public:E(constE&e){...}//CopyconstructorE(E&&e){...}//Moveconstructor};

C 中,程序员若未对某类定义构造函数(以及赋值函数、析构函数),编译器在满足条件时会定义相应的函数。

构造函数Ruby

irb(main):001:0>classExample
Classirb(main):002:1>definitialize
irb(main):003:2>puts"Hellothere"
irb(main):004:2>end
irb(main):005:1>end=>nil
irb(main):006:0>ExampleClass.new
Hellothere
=>#
     

查看详情

构造函数造价信息

  • 市场价
  • 信息价
  • 询价

函数信号源

  • HP 8116A 频率 1 mHz to 50 MHz(MHz)
  • 惠普
  • 13%
  • 北京市北方思源电子技术中心
  • 2022-12-07
查看价格

函数发生器

  • mx-9000 测量范围 2M(Hz)
  • 惠普
  • 13%
  • 北京市北方思源电子技术中心
  • 2022-12-07
查看价格

任意波形函数信号发生器

  • 规格:采样率125MS/s、模拟宽度25MHz;型号:AFG1022;品种:函数/任意波发生器
  • Tektronix/泰克
  • 13%
  • 上海顺测电子有限公司
  • 2022-12-07
查看价格

任意波形函数信号发生器

  • 品种:函数/任意波发生器;型号:AFG2021;规格:采样率250MS/s、输出频率20MHz
  • Tektronix/泰克
  • 13%
  • 宁波协创计量仪器有限公司
  • 2022-12-07
查看价格

任意波形函数信号发生器

  • 规格:采样率125MS/s、模拟宽度25MHz;型号:AFG1022;品种:函数/任意波发生器
  • Tektronix/泰克
  • 13%
  • 宁波协创计量仪器有限公司
  • 2022-12-07
查看价格

函数发生器

  • mx-9000 测量范围 2M(Hz)
  • 1500台
  • 1
  • 中档
  • 含税费 | 不含运费
  • 2015-06-14
查看价格

函数信号源

  • HP 8116A 频率 1 mHz to 50 MHz(MHz)
  • 8584台
  • 1
  • 惠普
  • 中高档
  • 含税费 | 含运费
  • 2015-05-13
查看价格

大型儿童构造

  • 1.名称:大型儿童构造 2.含图纸全部内容(如屋面、墙面、走道、楼梯、栏杆)及单个的成品构件(如成品儿童陀螺椅、涂鸦黑板、组合器械、儿童器械二、儿童器械三等) 3.其他要求,详见设计图纸
  • 1套
  • 1
  • 中高档
  • 含税费 | 含运费
  • 2021-09-15
查看价格

函数信号发生器

  • HP3325B 外形尺寸 133.4 x 425.5 x 498.5(mm)
  • 5803台
  • 1
  • 惠普
  • 中高档
  • 不含税费 | 含运费
  • 2015-08-10
查看价格

GRC成品构造

  • 规格:400mm×400mm,h=3.55m
  • 12根
  • 3
  • 中档
  • 不含税费 | 含运费
  • 2020-08-18
查看价格

构造函数主要特点

1.构造函数的命名必须和类名完全相同。在java中普通函数可以和构造函数同名,但是必须带有返回值;

2.构造函数的功能主要用于在类的对象创建时定义初始化的状态。它没有返回值,也不能用void来修饰。这就保证了它不仅什么也不用自动返回,而且根本不能有任何选择。而其他方法都有返回值,即使是void返回值。尽管方法体本身不会自动返回什么,但仍然可以让它返回一些东西,而这些东西可能是不安全的;

3.构造函数不能被直接调用,必须通过new运算符在创建对象时才会自动调用;而一般的方法是在程序执行到它的时候被调用的;

4.当定义一个类的时候,通常情况下都会显示该类的构造函数,并在函数中指定初始化的工作也可省略,不过Java编译器会提供一个默认的构造函数.此默认构造函数是不带参数的。而一般的方法不存在这一特点;

5.构造函数有回滚的效果,构造函数抛出异常时,构造的是一个不完整对象,会回滚,将此不完整对象的成员释放(c )

6.当一个类只定义了私有的构造函数,将无法通过new关键字来创建其对象,当一个类没有定义任何构造函数,C#编译器会为其自动生成一个默认的无参的构造函数。

7.在Python中构造函数必须通过重写__init__方法实现

查看详情

构造函数常见问题

查看详情

构造函数文献

Excel函数应用之函数简介 Excel函数应用之函数简介

Excel函数应用之函数简介

格式:doc

大小:2.6MB

页数: 104页

Excel函数应用之函数简介

用构造模糊隶属函数方法对医学过程的综合评价 用构造模糊隶属函数方法对医学过程的综合评价

用构造模糊隶属函数方法对医学过程的综合评价

格式:pdf

大小:2.6MB

页数: 4页

定义了“医学过程评价”的概念,提出了一种用构造多元隶属函数的方法对医学过程综合评价的模型。该模型框架应用于心肌梗塞病人康复期预后及尘肺疗效综合评价,均得到了满意的结果。由于该模型具有坚实的理论基础,因此该模型框架可推广应用于大部分医学过程的评价

cfiledialog构造函数

CFileDialog构造一个CFileDialog对象操作

DoModal显示对话框并使用户可以进行选择

GetPathName返回选定文件的完整路径

GetFileName返回选定文件的文件名

GetFileExt返回选定文件的扩展文件名

GetFileTitle返回选定文件的标题

GetNextPathName返回下一个选定文件的完整路径

GetReadOnlyPref返回选定文件的只读状态

GetStartPosition返回文件名列表的第一个元素位置

可覆盖的函数

OnShareViolation发生共享冲突时调用

OnFileNameOK确认键入对话框中的文件名

OnLBSelChangedNotify当列表框选择改变时调用

OnInitDone处理WM_NOTIFY CDN_INITDONE消息

OnFileNameChange处理WM_NOTIFY CDN_SELCHANGE消息

OnFolderChange处理WM_NOTIFY CDN_FOLDERCHANGE消息

OnTypeChange处理WM_NOTIFY CDN_TYPECHANGE消息

文件选择对话框的使用:首先构造一个对象并提供相应的参数,构造函数原型如下:

CFileDialog::CFileDialog( BOOL bOpenFileDialog, LPCTSTR lpszDefExt = NULL, LPCTSTR lpszFileName = NULL, DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, LPCTSTR lpszFilter = NULL, CWnd* pParentWnd = NULL );

参数意义如下:

bOpenFileDialog 为TRUE则显示打开对话框,为FALSE则显示保存对话文件对话框。

lpszDefExt 指定默认的文件扩展名。

lpszFileName 指定默认的文件名。

dwFlags 指明一些特定风格。

lpszFilter 是最重要的一个参数,它指明可供选择的文件类型和相应的扩展名。参数格式如:

"Chart Files (*.xlc)|*.xlc|Worksheet Files (*.xls)|*.xls|Data Files (*.xlc;*.xls)|*.xlc; *.xls|All Files (*.*)|*.*||";文件类型说明和扩展名间用 | 分隔,同种类型文件的扩展名间可以用 ; 分割,每种文件类型间用 | 分隔,末尾用 || 指明。

pParentWnd 为父窗口指针。

查看详情

cfiledialog简介

可以用构造函数提供的方式使用CFileDialog,也可以从CFileDialog派生出自己的对话类并编写一个构造函数来适应你的需要。每种情况下,对话框都与标准MFC对话框一样工作。因为它们都是CCommonDialog类的派生类。

要使用CFileDialog,先用CFileDialog构造函数构造一个对象,当创建了一个对话框后,可以设置或修改m_ofn结构中的任何值,以初始化对话框控件的值或状态。m_ofn结构是OPENFILENAME类型的。要了解更多信息,可参阅联机文档"Win32 SDK"中的OPENFILENAME结构。

初始化对话框控件后,调用DoModal成员函数显示对话框并使用户输入路径和文件。DoModal返回不论是用户选择了OK(IDOK)还是取消(IDCANCEL)按钮。

当DoModal返回IDOK,可以使用某一个CFileDIalog的公共成员函数获取用户输入的信息。

CFileDIalog包含许多保护成员,使你可以处理常用的共享冲突、文件名合法性检查、列表框改变通知。这些保护成员对许多应用来说用处不大,因为缺省处理是自动的。对这些函数来说,消息映射入口是不必要的,因为它们是标准虚函数。

可以使用Windows CommDlgExtendError函数判断在初始化对话框时是否是发生了错误,并获取关于错误的更多信息。

析构一个CFileDialog对象是自动,无须调用CDialog::EndDialog。

要使用户选用多个文件,可在调用DoModal之前设置OFN_ALLOWMULTISELECT标志。你应提供文件名缓冲区来放置返回的多个文件名的列表,这通过用一个分配了的缓冲区指针替换m_ofn.lpstrFile来实现,要在创建了CFileDialog之后调用DoModal之前进行此操作。另外,必须用m_ofn.lpstrFile指向的缓冲区字节数来设置m_ofn.nMaxFile。

CFileDialog依赖于Windows3.1及以后版本中的COMMDLG.DLL。

如果从CFileDialog中派生出一个新类,可用消息映射处理。要扩展消息处理,从CWnd中派生一个类,向新类中加入一个消息映射并为新消息提供成员函数,无须提供一个钩子函数来定制对话框。

要定制对话框,从CFileDialog中派生一个对象,提供一个定制对话模板,从扩展控件中加入一个消息映射,处理通知消息。任意未处理的消息将传递给基类。

无须定制钩子函数。

#include <afxdlgs.h>

CFileDialog类的成员

查看详情

pdo关键概念

连接管理

连接是通过创建 PDO 基类的实例而建立的。不管您想要使用哪种驱动程序,您总是使用 PDO 类名。构造函数接受用于指定数据源(即 DSN)的参数,可能还包括用户名和密码参数(如果有的话)。最后一个参数用于传递附加的调优参数到 PDO 或底层驱动程序 -- 后面很快会有更详细的论述。下面是一个简短的连接到 DB2 的示例脚本:

清单 2. 如何使用 PDO 连接到 DB2

try { $dbh = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2'); echo "Connected\n";} catch (Exception $e) { echo "Failed: " . $e->getMessage();}

odbc:SAMPLE 告诉 PDO 它应该使用 ODBC 驱动程序,并且应该使用 "SAMPLE" 数据库。如果使用一个驱动程序管理器,那么可以用一个 ODBC 级数据源名称替代 SAMPLE。实际上,在冒号字符之后可以指定任何有效的 ODBC 数据源连接字符串。

如果连接成功,您将看到消息 "Connected",否则,PDO 将抛出一个 PDOException,解释为什么连接失败。可能的原因包括无效的参数,不正确的用户/密码,甚至是您忘了装载驱动程序。

值得注意的是,除非您捕捉从构造函数抛出的异常,否则,如果 PHP 脚本未能连接到数据库,它将终止。这与传统的 PHP 数据库扩展有很大的不同。对于不喜欢异常的人来说,只有两个"硬故障(hard-failure)"点可能抛出异常,这是其中一个点(另一个地点是,当您试图使用事务时缺乏对事务的支持)。对于所有其他错误,PDO 将使用您选择的 错误处理设置。

连接将保持开放状态,直到所有对它的引用被释放。如果在主脚本的顶端打开连接,并将其句柄存储在一个全局变量中,那么该连接将一直处于开放状态,直到脚本结束,或者直到 $dbh 变量被设为 null。如果在一个函数中打开连接,并且只将句柄存储在一个本地变量中,那么当函数返回时,连接将被关闭。这些语义对于 PHP 中的任何对象都是一样的,没有什么特别的地方。

ODBC 连接池如果您使用的是 Windows,或者如果您选择在 UNIX 型平台上使用一个 ODBC 驱动程序管理器,那么值得注意的是,PDO_ODBC 将自动尝试使用该驱动程序管理器的 ODBC 连接池特性。这个特性类似于 PHP 级连接缓存,不要求专门请求一个持久的连接。此外,缓存是在 ODBC 级进行的,这意味着在同一个进程中运行的其他组件(例如在 IIS 下运行的 ASP/.Net 脚本)也能利用相同的连接池。

对于流量较大的站点,让 PHP 在不同请求的间隙中缓存打开的连接,使得每个进程(每个惟一的连接参数集)只需花费一次建立连接的成本,这样做常常很有益处。虽然这听起来像是一个不错的想法,但您应该仔细评估这样做对系统的影响,因为当大量缓存的连接空闲在那里的时候,就会适得其反。

要建立一个缓存的连接(如果您更熟悉传统的数据库扩展的话,也可以说是 *pconnect()),需要在实例化数据库连接时传递一个属性:

清单 3. 如何用 PDO 连接到 DB2,使用持久(缓存)连接

try { $dbh = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2', array(PDO_ATTR_PERSISTENT => true)); echo "Connected\n"; } catch (Exception $e) { echo "Failed: " . $e->getMessage(); }

事务提交

至此,您已经通过 PDO 连接到了 DB2,在发出查询之前,您应该理解 PDO 是如何管理事务的。如果之前没有接触过事务,那么首先要知道事务的 4 个特征:原子性(Atomicity)、一致性(Consistency)、独立性(Isolation)和持久性(Durability),即 ACID。用外行人的话说,对于在一个事务中执行的任何工作,即使它是分阶段执行的,也一定可以保证该工作会安全地应用于数据库,并且在工作被提交时,不会受到来自其他连接的影响。事务性工作可以根据请求自动撤销(假设您还没有提交它),这使得脚本中的错误处理变得更加容易。

事务通常是通过把一批更改积蓄起来、使之同时生效而实现的。这样做的好处是可以大大提高这些更新的效率。换句话说,事务可以使脚本更快,而且可能更健壮(不过需要正确地使用事务才能获得这样的好处)。

警告只有在通过 PDO::beginTransaction() 启动事务的情况下,才会发生自动回滚。如果手动地发出开始一个事务的查询,那么 PDO 就无法知道该事务,从而不能在必要时进行回滚。

不幸的是,并不是每种数据库都支持事务,所以当第一次打开连接时,PDO 需要在所谓的"自动提交(auto-commit)"模式下运行。自动提交模式意味着,如果数据库支持事务,那么您所运行的每一个查询都有它自己的隐式事务,如果数据库不支持事务,每个查询就没有这样的事务。如果您需要一个事务,那么必须使用 PDO::beginTransaction() 方法来启动一个事务。如果底层驱动程序不支持事务,那么将会抛出一个 PDOException(无论错误处理设置是怎样的:这总是一个严重错误状态)。在一个事务中,可以使用 PDO::commit() 或 PDO::rollBack() 来结束该事务,这取决于事务中运行的代码是否成功。

DB2 特性虽然我认为事务通常要更快一些,但您还是应该自己评估事务是否真的可以加快代码。例如,在高并发环境中您可能会发现,过度使用事务会增加锁开销。如果在应用程序中出现这种情况,那么建议的补救办法是在一般情况下使用自动提交,而对于真正需要全部 ACID 特征的代码部分则仍然使用事务。

当脚本结束时,或者当一个连接即将被关闭时,如果有一个未完成的事务,那么 PDO 将自动回滚该事务。这是一种安全措施,有助于避免在脚本非正常结束时出现不一致的情况 -- 如果没有显式地提交事务,那么假设有某个地方会出现不一致,所以要执行回滚,以保证数据的安全性。

清单 4. 在事务中执行批处理

try { $dbh = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2', array(PDO_ATTR_PERSISTENT => true)); echo "Connected\n"; $dbh->setAttribute(PDO_ATTR_ERRMODE, PDO_ERRMODE_EXCEPTION); $dbh->beginTransaction(); $dbh->exec("insert into staff (id, first, last) values (23, 'Joe', 'Bloggs')"); $dbh->exec("insert into salarychange (id, amount, changedate) values (23, 50000, NOW())"); $dbh->commit(); } catch (Exception $e) { $dbh->rollBack(); echo "Failed: " . $e->getMessage(); }

在上面的示例中,假设我们为一个新雇员创建一组条目,这个雇员有一个 ID 号,即 23。除了输入这个人的基本数据外,我们还需要记录雇员的薪水。两个更新分别完成起来很简单,但通过将这两个更新包括在 beginTransaction() 和 commit() 调用中,就可以保证在更改完成之前,其他人无法看到更改。如果发生了错误,catch 块可以回滚事务开始以来发生的所有更改,并打印出一条错误消息。

并不是一定要在事务中作出更新。您也可以发出复杂的查询来提取数据,还可以使用那种信息构建更多的更新和查询。当事务在活动时,可以保证其他人在工作进行当中无法作出更改。事实上,这不是 100% 的正确,但如果您之前没有听说过事务的话,这样介绍也未尝不可。

关于 PHP 应用程序中安全性的说明

PHP Security Consortium 虽然本文表明在使用 PDO 时不再需要引用输入,但这不是说您应该盲目地使数据通过数据库。XSS 攻击是很实际的危险。您应该总是确保对传入应用程序的不受信任的数据应用适当的过滤器,并采取措施避免让不受信任的数据在站点上发出 HTML 或 javascript。 请访问 The PHP Security Consortium 以了解关于这些危险的更多知识,以及应该如何避免这些危险。

很多 PHP 脚本中一个常见的缺陷是缺乏输入检验。这种缺陷可以被利用,从而招致 XSS(Cross Site Scripting)以及 SQL 入侵攻击。在 SQL 入侵中,不受信任的数据(例如发给 Web 网页的反馈)和其他文本被衔接在一起,构成一个查询。攻击者可以蓄意地安排他们的输入,使之溢出引号之外,并在您想运行的真正查询后面链接上任意一个查询。这种攻击使攻击者可以更新、插入或删除数据,甚至可能可以看到数据库中的任意信息。

XSS 也是一个类似的问题。不过这一次不受信任的数据瞄准的是浏览站点的人们,而不是应用程序本身。通过提交包含 HTML 或 javascript 组合的文本,攻击者期望您之后会将那种数据直接输出到其他访问站点的人那里,从而使恶意代码可以在站点访问者的浏览器上运行。

在编写应用程序时,需要同时考虑这两种攻击。如果小心地检验和过滤输入,这两种攻击都是可以防止的。对 XSS 的处理很有技巧性,所以在这里我不便多讲(不过可以从侧栏找到有用的参考资料)。相比之下,SQL 入侵更容易对付。您只需在构造查询之前,适当地排除每块不受信任的数据。这种事情有点烦杂,特别是当您有大量的字段要处理时,很容易忘记做这件事。

虽然这是有用的(并且也是重要的)信息,但是您可能想知道,为什么我要花时间提到这一点,本文的重点不是结合使用 PDO 和 DB2 吗?原因是这样的:PHP 现在得到很广泛的部署,自然地,大量流行的基于 PHP 的应用程序也得到了广泛的部署。每当某一种这样的应用程序(和 PHP 本身没有联系)被发现存在漏洞时,PHP 常常被误认为是不安全的,可被利用的或者有缺陷的。为了避免将来出现这样的情况,我们可以采取的一个措施是鼓励应用程序开发人员多考虑安全问题,从而减少由诚实的错误导致的损害。扯远了,下面继续介绍其他关键概念。

预处过程

很多更成熟的数据库都支持预处理语句的概念。什么是预处理语句?您可以把预处理语句看作您想要运行的 SQL 的一种编译过的模板,它可以使用变量参数进行定制。预处理语句可以带来两大好处:

查询只需解析(或准备)一次,但是可以用相同或不同的参数执行多次。当查询准备好后,数据库将分析、编译和优化执行该查询的计划。对于复杂的查询,这个过程要花比较长的时间,如果您需要以不同参数多次重复相同的查询,那么该过程将大大降低应用程序的速度。通过使用预处理语句,可以避免重复分析/编译/优化周期。简言之,预处理语句使用更少的资源,因而运行得更快。 提供给预处理语句的参数不需要用引号括起来,驱动程序会处理这些。如果应用程序独占地使用预处理语句,那么可以确保没有 SQL 入侵发生。(然而,如果您仍然将查询的其他部分建立在不受信任的输入之上,那么就仍然存在风险)。 预处理语句是如此有用,以致 PDO 实际上打破了在目标 4 中设下的规则:如果驱动程序不支持预处理语句,那么 PDO 将仿真预处理语句。

下面是使用预处理语句的两个例子。第一个例子 通过替换指定占位符的 name 和 value,执行一次插入。而 第二个例子 使用问号占位符执行一条 select 语句。

清单 4. 使用预处理语句的重复插入

$stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (:name, :value)"); $stmt->bindParam(':name', $name);$stmt->bindParam(':value', $value); // insert one row$name = 'one';$value = 1;$stmt->execute(); // insert another row with different values$name = 'two';$value = 2; $stmt->execute();

清单 5. 使用预处理语句取数据

$stmt = $dbh->prepare("SELECT * FROM REGISTRY where name = ?"); if ($stmt->execute(array('one'))) { while ($row = $stmt->fetch()) { print_r($row); }}

如果数据库驱动程序支持,您还可以绑定输出和输入参数。输出参数通常用于从存储过程获取值。输出参数使用起来比输入参数要复杂一些,当绑定一个给定的输出参数时,必须知道该参数的长度。如果为参数绑定的值大于您建议的长度,那么就会产生错误。

清单 6. 带输出参数调用存储过程

$stmt = $dbh->prepare("CALL sp_returns_string(?)"); $stmt->bindParam(1, $return_value, PDO_PARAM_STR, 4000); // call the stored procedure$stmt->execute(); print "procedure returned $return_value\n";

您还可以指定同时具有输入和输出值的参数,其语法类似于输出参数。在接下来的例子中,字符串 'hello' 被传递给存储过程,当存储过程返回时,hello 被替换为该存储过程返回的值。

清单 7. 带输入/输出参数调用存储过程

$stmt = $dbh->prepare("CALL sp_takes_string_returns_string(?)"); $value = 'hello'; $stmt->bindParam(1, $value, PDO_PARAM_STR|PDO_PARAM_INPUT_OUTPUT, 4000); // call the stored procedure$stmt->execute(); print "procedure returned $value\n";

错误处理

PDO 提供了 3 种不同的错误处理模式,以满足不同风格的编程:

PDO_ERRMODE_SILENT 这是默认模式。PDO 将只设置错误代码,以通过 errorCode() 和 errorInfo() 方法对语句和数据库对象进行检查。如果错误是由于对语句对象的调用而产生的,那么可以在那个对象上调用 errorCode() 或 errorInfo() 方法。如果错误是由于调用数据库对象而产生的,那么可以在那个数据库对象上调用上述两个方法。 PDO_ERRMODE_WARNING 除了设置错误代码以外,PDO 还将发出一条传统的 E_WARNING 消息。如果您只是想看看发生了什么问题,而无意中断应用程序的流程,那么在调试/测试当中这种设置很有用。 PDO_ERRMODE_EXCEPTION 除了设置错误代码以外,PDO 还将抛出一个 PDOException,并设置其属性,以反映错误代码和错误信息。这种设置在调试当中也很有用,因为它会放大脚本中产生错误的地方,从而可以非常快速地指出代码中有问题的潜在区域(记住,如果异常导致脚本终止,则事务将自动回滚)。 异常模式另一个有用的地方是,与传统的 PHP 风格的警告相比,您可以更清晰地构造自己的错误处理,而且,比起以静寂方式以及显式地检查每个数据库调用的返回值,异常模式需要的代码/嵌套也更少。 PDO 定制了使用 SQL-92 SQLSTATE 错误代码字符串的标准;不同 PDO 驱动程序负责将它们本地代码映射为适当的 SQLSTATE 代码。例如,SQLSTATE 是用于 DB2(以及通常的 ODBC)的本地错误代码格式,这是多么方便啊!errorCode() 方法返回一个 SQLSTATE 代码。如果您需要关于一个错误的更多特定的信息,PDO 还提供了一个 errorInfo() 方法,该方法将返回一个数组,其中包含 SQLSTATE 代码、特定于驱动程序的错误代码以及特定于驱动程序的错误字符串。

分页数据、滚动游标和定位更新

在 Web 应用程序中,一种常见的范例是对查询结果进行分页。如果您使用一个 Internet 搜索引擎,那么很可能每天都会做这样的事。您输入搜索词,然后得到前 10-20 个匹配项。如果您想看到更多搜索结果,可以单击 "next page" 链接。如果想回头看前面看过的结果,可以单击 "previous page" 链接。记得在几年前,当我第一次在 Web 上使用这样的东西时,我对自己说:"为什么我不能通过滚动查看所有数据呢?" 问题的答案说简单也简单,说复杂也复杂 -- 我只想说,HTTP 不会智能地使数据库上的可滚动游标一直处于开放状态,即便如此,需要大量传输的 Web 应用程序也会很快地消耗掉大量开放的可滚动游标。因此,最简单的解决方案是为用户显示所有的匹配项 -- 但是用户很容易迷失在大量的结果当中。比较符合逻辑的措施是人工地将数据格式化到多个页面上,使用户可以每次查看一部分可以管理的数据。

所以人们编写可以取所有数据的 PHP 应用程序,然后只显示前 10 行。根据下一次请求,应用程序又显示 11-20 行,依此类推。这对于只返回少量数据的查询来说很不错,但是,如果有很多匹配项(比如多于 100),那么先取全部数据然后丢弃其中的 90%,这种做法很浪费。PHP 的创始人 Rasmus Lerdorf 就这种情形特地为 MySQL 发明了一个特殊的 "LIMIT, OFFSET" 子句。它允许您通知数据库,您只对一小部分行感兴趣,这样它就不会取其他不需要的行了。其语法(或非常类似的东西)已经被其他流行的开放源代码数据库采纳,但并不是所有数据库都提供了相同的语法。 Troels Arvin 收集了一些非常有用的信息,对不同 RDBMS 所支持的语法进行了比较。

如果您想在以 DB2 为后台数据库的 PHP 应用程序中实现分页结果,那么可以(也应该)使用下面示例中的语法。这里我们假设有一个 books 表,表中包含书名和作者,我们现在想要每次在一页中显示 10 个以上结果:

清单 8. 使用 SQL Standard "Window Functions" 实现数据分页

$db = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2'); // the offset is passed in from the user when they click on a link // this cast to integer ensures that no SQL injection can occur $offset = (int)$_GET['offset'];$stmt = $db->prepare("select * from ( select ROW_NUMBER() OVER (ORDER BY author) as rownum, * from books ) as books_windowWHERE rownum > $offset AND rownum <= (10 + $offset)"); if ($stmt->execute()) { while (($row = $stmt->fetch()) !== false) { print_r($row); }}

Cloudscape 说明在撰写本文之际,Cloudscape 在其 SQL 实现中还不支持 ROW_NUMBER(),所以需要使用可滚动游标。

现在,如果您要编写一个更通用的应用程序,并希望实现分页的结果集,但是不想专门编写很多的代码,并且也不想使用更重量级的抽象层,Troels Arvin 的非常有帮助的 RDBMS 信息建议,您可以使用游标作为更轻便(稍微慢一点)的方案。碰巧的是,PDO 具有这方面的 API 级的支持。下面将谈到如何使用这种支持来达到与上面示例相同的效果:

清单 9. 使用滚动游标实现数据分页

$db = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2'); $stmt = $db->prepare("select * from books order by author", array( PDO_ATTR_CURSOR => PDO_CURSOR_SCROLL)); // the offset is passed in from the user when they click on a link // this cast to integer ensures that no SQL injection can occur $offset = (int)$_GET['offset'];if ($stmt->execute()) { // moves the cursor to the requested offset and fetches the first for ($tofetch = 10, $row = $stmt->fetch(PDO_FETCH_ASSOC, PDO_FETCH_ORI_REL, $offset); $row !== false && $tofetch-- > 0; $row = $stmt->fetch(PDO_FETCH_ASSOC)) { print_r($row); } }

需要强调的是,虽然滚动游标对于更冗长的 window 函数方案来说是一个很方便的替代方案,但这种方案要慢很多。如果在一个传输量比较少的环境中进行测试,您可能发现不了速度上的差异,但当规模扩大时,您就会开始发现速度降慢带来的痛苦。

定位更新

可滚动游标的另一个用途是,基于 SQL 中无法表达的重大标准驱动更新。如果您有一个 Web 页面链接的表,并且需要在每晚的批处理过程中更新那个表,以反映 Web 页面当前大小,那么可以编写如下代码:

清单 10. 使用滚动游标作出定位更新

$db = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2'); // create a named, scrolling, updateable cursor $stmt = $db->prepare("select url, size from links FOR UPDATE OF size", array( PDO_ATTR_CURSOR => PDO_CURSOR_SCROLL, PDO_ATTR_CURSOR_NAME => 'link_pos'));if ($stmt->execute()) { // a statement for applying our updates. // Notice the WHERE CURRENT OF clause mentions "link_pos", // which is the name of the cursor we're using to select the data $upd = $db->prepare("UPDATE links set size = ? WHERE CURRENT OF link_pos"); // grab each row while (($row = $stmt->fetch()) !== false) { // There are much more efficient ways to do this; // this is a brief example only: grab all the content // from the URL $content = file_get_conents($row['url']); // and measure its length $size = strlen($content) // and pass that as a parameter to our update statement $upd->execute(array($size)); }}

大型对象

在应用程序中的某个地方,您可能发现需要在数据库中存储"大型(large)"数据。大型通常意味着"大约 4kb 或 4kb 以上",尽管在没有"大型"数据之前 DB2 最大可以处理 32kb 的数据。 大型对象可以是文本的,也可以是二进制的。PDO 允许在 bindParam() 或 bindColumn() 调用中通过使用 PDO_PARAM_LOB 类型代码来使用大型数据类型。PDO_PARAM_LOB 告诉 PDO 将数据映射为流,所以可以使用 PHP Streams API 来操纵这样的数据。下面是一个示例:

程序功能

清单 11. 从数据库取一副图像

$db = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2'); $stmt = $db->prepare("select contenttype, imagedata from images where id=?"); $stmt->execute(array($_GET['id']));list($type, $lob) = $stmt->fetch(); header("Content-Type: $type");fpassthru($lob);

上面的介绍很简明扼要。现在让我们试试另一面,将上传的图像插入到一个数据库中:

清单 12. 将图像插入数据库中

$db = new PDO('odbc:SAMPLE', 'db2inst1', 'ibmdb2'); $stmt = $db->prepare("insert into images (id, contenttype, imagedata) values (?, ?, ?)"); $id = get_new_id(); // some function to allocate a new ID // assume that we are running as part of a file upload form // You can find more information in the PHP documentation $fp = fopen($_FILES['file']['tmp_name'], 'rb');$stmt->bindParam(1, $id); $stmt->bindParam(2, $_FILES['file']['type']); $stmt->bindParam(3, $fp, PDO_PARAM_LOB); $stmt->execute();

这两个例子都是宏观层次的。请记住,被取的大型对象是一个流,可以通过所有常规的流函数来使用它,例如 fgets()、fread()、fgetcsv() 和 stream_get_contents()。

关于全球化、NLS 和字符集的简要说明

在越来越多的 PHP 应用程序中,越来越重要的一点是让应用程序能够在全球范围内使用。从实践角度来讲,这意味着应用程序需要能够正确地处理多种语言(例如英语和日语)中的数据,并且其功能性不变。这是一个很大的专题,做起来很有技巧性。实现全球化要走的第一步是采用一种适合所有数据的全球编码,例如 UTF-8。这是一种 ASCII 兼容的编码,它可以使用特殊字符序列为整个 unicode 字符集编码。UTF-8 也是一种多字节编码。

与常规 ASCII 字符串相比,多字节编码的字符串处理起来要棘手一点,因为一个或多个字符对应于一个给定的字母 -- 例如,UTF-8 允许最多 6 个字符的序列映射到字符串中的一个字母。ASCII 字符在 UTF-8 中仍具有相同的表示,因此,如果只是处理不带任何特殊音调的纯英文文本,则 UTF-8 看上去就像是 ASCII。这意味着类似的字符串函数(作用于字节而不是字符位置),例如 strlen() 和 substr(),可能得不到预期的效果,这取决于 UTF-8 字符串中的内容。幸运的是,PHP iconv 扩展为这些函数提供了一些编码感知的替代函数。例如,您可以使用 iconv_strlen() 来得出字符串中的字符数,而不是使用 strlen()。类似地,您可以不用 strpos() 或 substr(),而使用 iconv_strpos() 和 iconv_substr()。

iconv 扩展为您提供了在处理多种编码下的数据时所需的基本工具。应用程序应该尽量确保所有数据都是 UTF-8 编码的。如果用适当的 Content-Type 标记 Web 页面,那么大多数浏览器将发送 UTF-8 编码的数据,可以确信,一定有一个编码类型属性可应用于 HTML FORM 标签。

接下来的一步是设置 DB2 实例,使它在您与之交互时使用 UTF-8。这很容易办到,只需在 DB2 的命令行提示符中运行以下命令:

清单 14. 设置 DB2 实例,使之使用 UTF-8

$ db2set DB2CODEPAGE=1208

完成这样的更改后,从 DB2 实例取到的所有文本数据都是 UTF-8 编码的。同样,DB2 期望您输入的所有文本也是 UTF-8 编码的。当应用程序的每个部分都使用 UTF-8 时,应用程序就可以全球使用了,并且能够显示任何语言的文本,只要这种语言的文本可以用 UTF-8 编码。前面我已经暗示过,这只是通往国际化大道的第一步。还有很多其他的事情需要考虑,例如本地化(采用给定用户的地区设置来显示日期、时间、重量和度量,将通用文本翻译成用户本地的语言)、从右到左或双向(bi-di)文本布局,等等。

值得注意的是,PDO 不对该数据做任何特殊的事情。有些驱动程序允许更改为一个连接使用的编码,但是在 PDO 级没有处理这种事情的特殊逻辑。其原因是,PHP 内部完全不知道 unicode,所以在这里试图使 PDO 知道 unicode 是没有意义的。如果您对这方面的专题感兴趣,那么您会欣喜地得知,PHP 的 unicode 支持很快就要出现,不过我也不知道它初次露面的确切日期。

查看详情

相关推荐

立即注册
免费服务热线: 400-888-9639