原创 2007年10月15日 14:49:00
  • VC中使用ADO进行数据库操作

    2007-07-26 10:11:12

    ADO技术简介

    ADO是为MS的强大的数据访问接口 OLE DB 设计的,上一个便于使用的应用程序层。
    OLE DB 为任何数据源都提供了高性能的访问,包括:
    关系型数据库、非关系型数据库、电子邮件、文件系统、文本和图形以及自定义业务对象等。

    ADO 2.0 实际上是基于 MSADO15.DLL 这个动态链接库的,
    这个库文件的名字虽然和 ADO 1.5 的一样,但是它实现了更新的接口。

    ADO 2.0 里的新技术有:
    1、异步操作和事件模型
    2、数据集的持续性
    3、层次化的数据传输
    ADO特点概述

    用ADO访问数据元的特点可概括如下:
    易于使用,可以说这是ADO最重要的特点之一。
    ADO 是高层数据库访问技术,相对与ODBC来说,具有面性对象的特点。
    同时,在 ADO 对象结构中,对象与对象之间的层次结构不适非常明显,
    这会给编写数据库程序带来更多的便利。
    比如,在应用程序中如果要使用记录集对象,不一定要先建立连接、会话对象,
    如果需要就可以直接构造记录集对象。总是,已经没有必要去关心对象的构造层次和构造顺序了。
    可以访问多种数据源。和 OLE DB 一样,使应用程序具有很好的通用性和灵活性。
    访问数据源效率高。
    方便的Web应用。ADO 可以以 ActiveX 控件的形式出现,这就大大方便了Web应用程序的编制。
    技术编程接口丰富。 ADO 支持 Visual C++、Visual Basic、VBS、JS等。
    ADO 的对象
    Connection
    用于表示和数据源的连接,以及处理一些命令和事务。
    Command
    用于执行某些命令来进行诸如查询、修改数据库结构的操作。
    Recordset
    用于处理数据源的表格集,它是在表中修改、检索数据的最主要的方法。
    Field
    描述数据集中的列信息。
    Parameter
    用于对传递给数据源的命令赋参数值。
    Error
    用于承载所产生所无的详细信息。
    Property
    通过属性,每个ADO对象借此来让用户描述和控制自身的行为。
    Set
    集合是一种可以方便的包含其他特殊类型对象的对象类型。 ADO 提供4种类型的集合:
    ●Connection 对象具有Error集合。
    ●Command 对象具有Parameter集合。
    ●Recordset 对象具有Fields集合。
    ●Connection、Command、Recordset、Field 对象都具有Property集合。
    Event
    事件模型是异步操作的基础,这是 ADO 2.0 引进的新特性。
    在 Visual C++ 中使用 ADO

    1、引入ADO库文件
    使用ADO前必须在工程的stdafx.h头文件里用直接引入符号#import引入ADO库文件,
    以使编译器能正确编译。代码如下所示:

    用#import引入ADO库文件
    基本流程
    (1)初始化COM库,引入ADO库定义文件
    (2)用Connection对象连接数据库
    (3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记
    录集进行查询、处理。
    (4)使用完毕后关闭连接释放对象。
    使用_ConnectionPtr接口
    _ConnectionPtr主要是一个连接接口,取得与数据库的连接。它的连接字符串可以是自己直接写,也可以指向一个ODBC DSN。
    HRESULT Connection15::Open (_bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options )
    ConnectionString 为连接字串,
    UserID 是用户名,
    Password 是登陆密码,
    Options 是连接选项,用于指定Connection对象对数据的更新许可权,
    Options可以是如下几个常量:
    adModeUnknown: 缺省。当前的许可权未设置
    adModeRead: 只读
    adModeWrite: 只写
    adModeReadWrite: 可以读写
    adModeShareDenyRead: 阻止其它Connection对象以读权限打开连接
    adModeShareDenyWrite: 阻止其它Connection对象以写权限打开连接
    adModeShareExclusive: 阻止其它Connection对象打开连接
    adModeShareDenyNone: 允许其它程序或对象以任何权限建立连接
    ◆常用的数据库连接方法:


    (1)通过JET数据库引擎对ACCESS2000数据库的连接
    m_pConnection->Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C://test.mdb","","",adModeUnknown);

    (2)通过DSN数据源对任何支持ODBC的数据库进行连接:
    m_pConnection->Open("Data Source=adotest;UID=sa;PWD=;","","",adModeUnknown);
    //m_pConnection->Open("DSN=test;","","",0); //连接叫作test的ODBC数据源

    (3)不通过DSN对SQL SERVER数据库进行连接:
    m_pConnection->Open("driver={SQL Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown);
    其中Server是SQL服务器的名称,DATABASE是库的名称


    ◆先介绍Connection对象中两个有用的属性ConnectionTimeOut与State
    ConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如:
    m_pConnection->ConnectionTimeout = 5; //设置超时时间为5秒
    m_pConnection->Open("Data Source=adotest;","","",adModeUnknown);

    State属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过
    读取这个属性来作相应的处理,例如:
    if(m_pConnection->State)
    m_pConnection->Close(); //如果已经打开了连接则关闭它_ConnectionPtr pConn;

    if (FAILED(pConn.CreateInstance("ADODB.Connection")))
    {
    AfxMessageBox("Create Instance failed!");
    return;
    }


    CString strSRC;
    strSRC="Driver=SQL Server;Server=";
    strSRC+="suppersoft";
    strSRC+=";Database=";
    strSRC+="mydb";
    strSRC+=";UID=SA;PWD=";

    CString strSQL = "Insert into student(no,name,sex,address) values(3,'aaa','male','beijing')";

    _variant_t varSRC(strSRC);
    _variant_t varSQL(strSQL);
    _bstr_t bstrSRC(strSRC);

    if (FAILED(pConn->Open(bstrSRC,"","",-1)))
    {
    AfxMessageBox("Can not open Database!");
    pConn.Release();
    return;
    }

    COleVariant vtOptional((long)DISP_E_PARAMNOTFOUND,VT_ERROR);

    pConn->Execute(_bstr_t(strSQL),&vtOptional,-1);

    pConn.Release();

    AfxMessageBox("ok!");


    使用_RecordsetPtr接口(以连接SQL Server为例)

    _RecordsetPtr pPtr;
    if (FAILED(pPtr.CreateInstance("ADODB.Recordset")))
    {
    AfxMessageBox("Create Instance failed!");
    return FALSE;
    }

    CString strSRC;
    strSRC="Driver=SQL Server;Server=";
    strSRC+="210.46.141.145";
    strSRC+=";Database=";
    strSRC+="mydb";
    strSRC+=";UID=sa;PWD=";
    strSRC+="sa";

    CString strSQL = "select id,name,gender,address from personal";

    _variant_t varSRC(strSRC);
    _variant_t varSQL(strSQL);

    if(FAILED(pPtr->Open(varSQL,varSRC,adOpenStatic,adLockOptimistic,adCmdText)))
    {
    AfxMessageBox("Open table failed!");
    pPtr.Release();
    return FALSE;
    }

    while(!pPtr->GetadoEOF())
    {
    _variant_t varNo;
    _variant_t varName;
    _variant_t varSex;
    _variant_t varAddress;

    varNo = pPtr->GetCollect ("id");
    varName = pPtr->GetCollect ("name");
    varSex = pPtr->GetCollect ("gender");
    varAddress = pPtr->GetCollect ("address");

    CString strNo =(char *)_bstr_t(varNo);
    CString strName =(char *)_bstr_t(varName);
    CString strSex =(char *)_bstr_t(varSex);
    CString strAddress =(char *)_bstr_t(varAddress);

    strNo.TrimRight();
    strName.TrimRight();
    strSex.TrimRight();
    strAddress.TrimRight();

    int nCount = m_list.GetItemCount();
    int nItem = m_list.InsertItem (nCount,_T(""));
    m_list.SetItemText (nItem,0,strNo);
    m_list.SetItemText (nItem,1,strName);
    m_list.SetItemText (nItem,2,strSex);
    m_list.SetItemText (nItem,3,strAddress);

    pPtr->MoveNext();
    }

    pPtr->Close();
    pPtr.Release();


    使用_CommandPtr接口

    _CommandPtr接口返回一个Recordset对象,并且提供了更多的记录集控制功能,以下代码示例了使用_CommandPtr接口的方法:
    代码11:使用_CommandPtr接口获取数据
    _CommandPtr pCommand;
    _RecordsetPtr pRs;
    pCommand.CreateInstance(__uuidof(Command));
    pCommand->ActiveConnection=pConn;
    pCommand->CommandText="select * from student";
    pCommand->CommandType=adCmdText;
    pCommand->Parameters->Refresh();
    pRs=pCommand->Execute(NULL,NULL,adCmdUnknown);
    _variant_t varValue = pRs->GetCollect("name");
    Cstring strValue=(char*)_bstr_t(varValue);


    关于数据类型转换

    由于COM对象是跨平台的,它使用了一种通用的方法来处理各种类型的数据,
    因此Cstring 类和COM对象是不兼容的,我们需要一组API来转换COM对象和C++类型的数据。
    _vatiant_t和_bstr_t就是这样两种对象。它们提供了通用的方法转换COM对象和C++类型的数据。

    执行SQL命令并取得结果记录集


    为了取得结果记录集,我们定义一个指向Recordset对象的指针:
    _RecordsetPtr m_pRecordset;
    并为其创建Recordset对象的实例:
    m_pRecordset.CreateInstance("ADODB.Recordset");

    SQL命令的执行可以采用多种形式,下面我们一进行阐述。

    ◆(1)利用Connection对象的Execute方法执行SQL命令
    Execute方法的原型如下所示:

    _RecordsetPtr Connection15::Execute (_bstr_t CommandText, VARIANT * RecordsAffected, long Options )
    其中
    CommandText 是命令字串,通常是SQL命令。
    RecordsAffected 是操作完成后所影响的行数,
    Options 表示CommandText中内容的类型,Options可以取如下值之一:
    adCmdText: 表明CommandText是文本命令
    adCmdTable: 表明CommandText是一个表名
    adCmdProc: 表明CommandText是一个存储过程
    adCmdUnknown: 未知

    Execute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。
    try
    {
    _variant_t ra;
    m_pConnection->Execute("CREATE TABLE 学生信息(学号 INTEGER,姓名 TEXT,年龄 INTEGER,生日 DATETIME)",&ra,adCmdText);
    m_pConnection->Execute("INSERT INTO 学生信息(学号,姓名,年龄,生日) VALUES (112105, '程红秀',22,'1982-08-16')",&ra,adCmdText);//往表格里面添加记录
    m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM 学生信息",&ra,adCmdText); //执行SQL统计命令得到包含记录条数的记录集
    _variant_t vCount = m_pRecordset->GetCollect((_variant_t)(long)(0)); //取得第一个字段的值放入vCount变量
    m_pRecordset->Close();
    CString message;
    message.Format("共有%d条记录",vCount.lVal);
    AfxMessageBox(message);
    }

    catch (_com_error e)
    { ...}


    ◆(2)利用 Command对象 来执行SQL命令
    try
    {
    _CommandPtr m_pCommand;
    m_pCommand.CreateInstance("ADODB.Command");
    m_pCommand->ActiveConnection = m_pConnection; //关键的一句,将建立的连接赋值给它

    m_pCommand->CommandText="INSERT INTO 学生信息(学号,姓名,年龄,生日) VALUES (112105, '程红秀',22,'1982-08-16')";
    m_pCommand->Execute(NULL,NULL,adCmdText);
    m_pCommand->CommandText="SELECT COUNT(*) FROM 学生信息";
    m_pRecordset=m_pCommand->Execute(NULL,NULL,adCmdText);

    _variant_t vCount = m_pRecordset->GetCollect((_variant_t)(long)0); //取得第一个字段的值
    CString str;
    str.Format("共有%d条记录",vCount.lVal);
    AfxMessageBox(str);

    m_pRecordset->Close();

    }
    catch (_com_error e) {...}

    在这段代码中我们只是用Command对象来执行了SELECT查询语句,
    Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。


    ◆(3)直接用Recordset对象进行查询取得记录集
    例如
    m_pRecordset->Open("SELECT * FROM 学生信息",_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);

    Open方法的原型是这样的:
    HRESULT Recordset15::Open ( const _variant_t & Source, const _variant_t & ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options )
    其中:
    ①Source是数据查询字符串
    ②ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象)
    ③CursorType光标类型,它可以是以下值之一,请看这个枚举结构:
    enum CursorTypeEnum
    {
    adOpenUnspecified = -1, //不作特别指定
    adOpenForwardOnly = 0, //前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用
    adOpenKeyset = 1, //采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。
    adOpenDynamic = 2, //动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。
    adOpenStatic = 3 //静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。
    };
    ④LockType锁定类型,它可以是以下值之一,请看如下枚举结构:
    enum LockTypeEnum
    {
    adLockUnspecified = -1, //未指定
    adLockReadOnly = 1, //只读记录集
    adLockPessimistic = 2, //悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制
    adLockOptimistic = 3, //乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作
    adLockBatchOptimistic = 4, //乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。
    };
    ⑤Options请参考本文中对Connection对象的Execute方法的介绍


    记录集的遍历、更新

    根据我们刚才通过执行SQL命令建立好的 学生信息 表,它包含四个字段:学号,姓名,年龄,生日
    以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条
    记录,更改其年龄,保存到数据库。

    try
    {
    _variant_t vUsername,vBirthday,vID,vOld;
    _RecordsetPtr m_pRecordset;
    m_pRecordset.CreateInstance("ADODB.Recordset");
    m_pRecordset->Open("SELECT * FROM 学生信息",_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
    while(!m_pRecordset->adoEOF)
    {
    vID = m_pRecordset->GetCollect(_variant_t((long)0)); //取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行
    vUsername = m_pRecordset->GetCollect("姓名"); //取得姓名字段的值
    vOld = m_pRecordset->GetCollect("年龄");
    vBirthday = m_pRecordset->GetCollect("生日");
    TRACE("id:%d,姓名:%s,年龄:%d,生日:%s/r/n",
    vID.lVal,
    (LPCTSTR)(_bstr_t)vUsername,
    vOld.lVal,
    (LPCTSTR)(_bstr_t)vBirthday); //在DEBUG方式下的OUTPUT窗口输出记录集中的记录
    m_pRecordset->MoveNext(); //移到下一条记录
    }
    m_pRecordset->MoveFirst(); //移到首条记录
    m_pRecordset->Delete(adAffectCurrent); //删除当前记录
    for(int i=0;i<3;i++) //添加三条新记录并赋值
    {
    m_pRecordset->AddNew(); //添加新记录
    m_pRecordset->PutCollect("学号",_variant_t((long)(i+10)));
    m_pRecordset->PutCollect("姓名",_variant_t("王斌年"));
    m_pRecordset->PutCollect("年龄",_variant_t((long)21));
    m_pRecordset->PutCollect("生日",_variant_t("1930-3-15"));
    }
    m_pRecordset->Move(1,_variant_t((long)adBookmarkFirst)); //从第一条记录往下移动一条记录,即移动到第二条记录处
    m_pRecordset->PutCollect(_variant_t("年龄"),_variant_t((long)45)); //修改其年龄
    m_pRecordset->Update(); //保存到库中
    } catch (_com_error e){}

    关闭记录集与连接


    记录集或连接都可以用Close方法来关闭
    m_pRecordset->Close(); //关闭记录集
    m_pConnection->Close(); //关闭连接

    在stdafx.h中进行宏定义:
    #if !defined CATCH_ERROR
    #define CATCH_ERROR /
    { /
    CString strComError; /
    strComError.Format("错误编号: %08lx/n错误信息: %s/n错误源: %s/n错误描述: %s", /
    e.Error(), /
    e.ErrorMessage(), /
    (LPCSTR) e.Source(), /
    (LPCSTR) e.Description()); /
    ::MessageBox(NULL,strComError,"错误",MB_ICONEXCLAMATION); /
    }
    #endif
    使用方法:
    try
    { ...}
    catch(_com_error e)
    {
    CATCH_ERROR;
    }

    #import "c:/program files/common files/system/ado/msado15.dll"no_namespaces rename("EOF" adoEOF")


    这行语句声明在工程中使用ADO,但不使用ADO的名字空间,并且为了避免常数冲突,将常数EOF改名为adoEOF。
    现在不需添加另外的头文件,就可以使用ADO接口了。
    其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库.

    2、初始化OLE/COM库环境
    必须注意的是,ADO库是一组COM动态库,这意味应用程序在调用ADO前,必须初始化OLE/COM库环境。
    在MFC应用程序里,一个比较好的方法是在应用程序主类的InitInstance成员函数里初始化OLE/COM库环境。

    BOOL CMyAdoTestApp::InitInstance()
    {
    if(!AfxOleInit())//这就是初始化COM库
    {
    AfxMessageBox(“OLE初始化出错!”);
    return FALSE;
    }

    ……

    }



    3、ADO接口简介

    ADO库包含三个基本接口:_ConnectionPtr接口、_CommandPtr接口和_RecordsetPtr接口。

    _ConnectionPtr接口返回一个记录集或一个空指针。
    通常使用它来创建一个数据连接或执行一条不返回任何结果的SQL语句,
    如一个存储过程。使用_ConnectionPtr接口返回一个记录集不是一个好的使用方法。
    对于要返回记录的操作通常用_RecordserPtr来实现。
    用_ConnectionPtr操作时要想得到记录条数得遍历所有记录,而用_RecordserPtr时不需要。

    _CommandPtr接口返回一个记录集。
    它提供了一种简单的方法来执行返回记录集的存储过程和SQL语句。
    在使用_CommandPtr接口时,你可以利用全局_ConnectionPtr接口,也可以在_CommandPtr接口里直接使用连接串。
    如果你只执行一次或几次数据访问操作,后者是比较好的选择。
    但如果你要频繁访问数据库,并要返回很多记录集,那么,你应该使用全局_ConnectionPtr接口创建一个数据连接,
    然后使用_CommandPtr接口执行存储过程和SQL语句。

    _RecordsetPtr是一个记录集对象。
    与以上两种对象相比,它对记录集提供了更多的控制功能,如记录锁定,游标控制等。
    同_CommandPtr接口一样,它不一定要使用一个已经创建的数据连接,
    可以用一个连接串代替连接指针赋给_RecordsetPtr的connection成员变量,
    让它自己创建数据连接。
    如果你要使用多个记录集,
    最好的方法是同Command对象一样使用已经创建了数据连接的全局_ConnectionPtr接口,
    然后使用_RecordsetPtr执行存储过程和SQL语句。

     
























  • C语言中对文件的操作

    2007-06-26 10:04:18

    C语言有关文件操作的函数

    13.1C语言文件
    1,两种文件存取方式(输入,输出方式)
    顺序存取
    直接存取
    2,数据的两种存放形式
    文本文件
    二进制文件

    13.2文件指针
    定义文件类型指针变量的一般形式:
    FILE *指针变量名;
    例如:
    FILE *fp1,*fp2;13.3打开文件
    在使用文件之前,需打开文件.在C里使用fopen函数打开文件.格式为:
    fopen(文件名,文件使用方式);
    此函数返回一个指向FILE类型的指针.如:
    FILE *fp;
    fp=fopen("file_1","r");
    如果调用成功,fp就指向file_1,否则返回为NULL,所以为了保证文件的正确使用,要进行测试.采用如下语句:
    If((fp=fopen("file_1","r"))==NULL)
    {
    printf("Cannot open this file/n");
    exit(0);
    }
    最常用的文件使用方式及其含义如下:
    1,"r".为读而打开文本文件.(不存在则出错)
    2,"rb".为读而打开二进制文件.
    3,"w".为写而打开文本文件.(若不存在则新建,反之,则从文件起始位置写,原内容将被覆盖)
    4,"wb".为写而打开二进制文件.
    5,"a".为在文件后面添加数据而打开文本文件.(若不存在,则新建;反之,在原文件后追加)
    6,"ab".为在文件后面添加数据而打开一个二进制文件.
    最常用的文件使用方式及其含义如下:
    7,"r+".为读和写而打开文本文件.(读时,从头开始;在写数据时,新数据只覆盖所占的空间,其后不变)
    8,"rb+".为读和写而打开二进制文件.只是在随后的读写时,可以由位置函数设置读和写的起始位置.
    9,"w+".首先建立一个新文件,进行写操作,随后可以从头开始读.(若文件存在,原内容将全部消失)
    10,"wb+".功能与"w+"同.只是在随后的读写时,可以由位置函数设置读和写的起始位置.
    最常用的文件使用方式及其含义如下:
    11,"a+".功能与"a"相同;只是在文件尾部添加新的数据后,可以从头开始读.
    12,"ab+".功能与"a+"相同;只是在文件尾部添加新数据之后,可以由位置函数设置开始读的起始位置.


    13.4关闭文件当文件的读写操作完成之后,使用fclose函数关闭文件.格式如下:
    fclose(文件指针)
    如:fclose(fp);


    13.5调用getc(fgetc)和putc(fputc)函数进行输入和输出
    例如:把从键盘输入的文本按原样输出到名为file_1.dat文件中,用字符@作为键盘输入结束标志.
    #include
    Void main()
    {
    FILE *fpout;
    char ch;
    if(fpout=fpopen("file_1","w")==NULL)
    {
    printf("Cannot open this file!/n");
    exit(0);
    }
    ch=getchar();
    while(ch!='@')
    { fputc(ch,fpout); ch=getchar(); }
    fclose(fpout);
    }

    1,调用putc(或fputc)函数输出一个字符
    调用形式为:
    putc(ch,fp);
    功能是:将字符ch写到文件指针fp所指的文件中去.当输出成功,putc函数返回所输出的字符;否则,返回一个EOF值.EOF是在stdio.h库函数文件中定义的符号常量,其值等于-1.

    2.调用getc(或fgetc)函数输入一个字符
    调用形式为:
    ch=getc(pf);
    功能是:从pf指定的文件中读如一个字符,并把它作为函数值返回.
    例如:把一个已存在磁盘上的file_1.dat文本文件中的内容,原样输出到终端屏幕上.
    #include
    void main(){
    FILE *fpin;
    char ch;
    if((fpin=fopen("file_1.dat","r"))==NULL)
    { printf("Cann't open this file!/n");exit(0);}
    ch=fgetc(fpin);
    while (ch!=EOF)
    { putchar(ch);
    ch=fgetc(fpin);}
    fclose(fpin);
    }


    13.6判断文件结束函数feof
    EOF可以作为文本文件的结束 标志,但不能作为二进制文件的结束符.feof函数既可以判断二进制文件,又可以判断文本文件.
    例:编写程序,用于把一个文本文件(源)复制到另一个文件(目的)中,源文件名和目的文件名由命令行输入.命令形式如下:
    可执行程序名 源文件名 目的文件名
    #include
    void filecopy(FILE* ,FILE *);
    void main(int argc,char *argv[]){
    FILE *fpin,*fpout;
    if(argc==3)
    { fpin=fopen(argv[1],"r");
    fpout=fopen(argv[2],"w");
    filecopy(fpin,fpout);
    fclose(fpin);
    fclose(fpout);
    }
    else if(argc>3)
    printf("The file names too many!!/n";
    else
    printf("There are no file names for input or output!!/n );
    }

    void filecopy(FILE *fpin,FILE *fpout)
    {
    char ch;
    ch=getc(fpin);
    while(!feof(fpin))
    {putc(ch,fpout);
     ch=getc(fpin);}
    }

    13.7fscanf函数和fprintf函数
    1,fscanf函数
    fscanf只能从文本文件中按格式输入,和scanf函数相似,只不过输入的对象是磁盘上文本文件中的数据.调用形式为:
    fscanf(文件指针,格式控制字符串,输入项表)
    例如:fscanf(fp,"%d%d",&a,&b);
    fscanf(stdin,"%d%d",&a,&b);
    等价于scanf("%d%d",&a,&b);
    2.fprintf函数
    fprintf函数按格式将内存中的数据转换成对应的字符,并以ASCII代码形式输出到文本文件中.Fprintf函数和printf函数相似,只是将输出的内容按格式存放到磁盘的文本文件中.调用形式如下:
    fprintf(文件指针,格式控制字符串,输出项表)
    如:fprintf(fp,"%d %d",x,y);
    以下语句 fprintf(stdout,"%d %d",x,y)

    13.8fgets函数和fputs函数
    1,fgets函数
    fgets函数用来从文件中读入字符串.调用形式如下:
    fgets(str,n,fp);
    函数功能是:从fp所指文件中读入n-1个字符放入str为起始地址的空间内;如果在未读满n-1个字符时,则遇到换行符或一个EOF结束本次读操作,并已str作为函数值返回.

    2,fputs函数
    fput函数把字符串输出到文件中.函数调用形式如下:
    fputs(str,fp);
    注意:为了便于读入,在输出字符串时,应当人为的加诸如"/n"这样的字符串.


    13.9fread函数和fwrite函数
    fread and fwrite函数用来读,写二进制文件.它们的调用形式如下:
    fread(buffer,size,count,fp);
    fwrite(buffer,size,count,fp);
    buffer:要输入或输出的数据块的首地址
    count:每读写一次,输入或输出数据块的个数
    size:每个数据块的字节数
    fp:文件指针

    例如有如下结构体:
    struct st{
    char num[8];
    float mk[5];
    }pers[30];

    以下循环将把这30个元素中的数据输出到fp所指文件中.
    for(i=0;i<30;i++)
    fwrite(&pers[i],sizeof(struct st),1,fp);

    以下语句从fp所指的文件中再次将每个学生数据逐个读入到pers数组中.
    i=0;
    fread(&pers[i],sizeof(struct st),1,fp);
    while(!feof(fp))
    { i++;
    fread(&pers[i],sizeof(struct st),1,fp);
    }

    13.10文件定位函数
    1,fseek函数
    fseek函数用来移动文件位置指针到指定的位置上,接着的读或写操作将从此位置开始.函数的调用形式如下:
    fseek(pf,offset,origin)
    pf:文件指针
    offset:以字节为单位的位移量,为长整形.
    origin:是起始点,用来指定位移量是以哪个位置为基准的.

    位移量的表示方法
    标识符 数字 代表的起始点
    SEEK_SET 0 文件开始
    SEEK_END 2 文件末尾
    SEEK_CUR 1 文件当前位置
    假设pf已指向一个二进制文件,则;
    fseek(pf,30L,SEEK_SET)
    fseek(pf,-10L*sizeof(int),SEEK_END)
    对于文本文件,位移量必须是0;如:
    fseek(pf,0L,SEEK_SET)
    fseek(pf,0L,SEEK_END)

    2. ftell函数
    ftell函数用以获得文件当前位置指针的位置,函数给出当前位置指针相对于文件开头的字节数.如;
    long t;
    t=ftell(pf);
    当函数调用出错时,函数返回-1L.
    我们可以通过以下方式来测试一个文件的长度:
    fseek(fp,0L,SEEK_END);
    t=ftell(fp);
    3.rewind函数
    调用形式为:
    rewind(pf);
    函数没有返回值.函数的功能是使文件的位置指针回到文件的开头.

    13.10文件应用
    在磁盘上的test.txt文件中放有10个不小于2的正整数,用函数调用方式编写程序.要求实现:
    1,在被调函数prime中,判断和统计10个整数中的素数以及个数.
    2,在主函数中将全部素数追加到磁盘文件test.txt的尾部,同时输出到屏幕上.
    #include
    #include
    Int prime(int a[],int n)
    {
    int I,j,k=0,flag=0;
    for(i=0;i { for(j=2;j if(a[i]%j==0)
    { flag=0; break;}
    else flag=1;
    if(flag)
    {a[k]=a[i];k++;}
    }
    return k;
    }
    void main(){
    int n,I,a[10];
    FILE *fp;
    fp=fopen("test1-2.txt","r+");
    for(n=0;n<10;n++)
    fscanf(fp,"%d",&a[n]);
    n=prime(a,n);
    fseek(fp,o,2);
    for(i=0;i {printf("%3d",a[i]);
    fprintf(fp,"%3d",a[i]);
    }
    fclose(fp);
     

  • vc连接数据库的方法

    2007-06-26 09:31:54

    1.ACCESS 2000

        _ConnectionPtr m_pConn;
        CString m_sConn="Provider=Microsoft.Jet.OLEDB.4.0.1;Data Source=d://db1.mdb";
        m_pConn.CreateInstance("ADODB.Connection");
        try
        {
            HRESULT hr=m_pConn->Open((_bstr_t)m_sConn,"","",adConnectUnspecified);   
            if (FAILED(hr))
            {
                AfxMessageBox("不能连接数据库 source!");
                return FALSE;
            }
        }
        catch(_com_error e)
        {
            AfxMessageBox("不能连接数据库 error!");
            return FALSE;
        }

    2.SQL Server 2000

        _ConnectionPtr m_pConn;
        CString m_sConn="Provider=SQLOLEDB.1;Data Source=192.168.3.9;Initial
    Catalog=sode"; //sode是数据库服务器192.168.3.9上的一个数据库
        m_pConn.CreateInstance("ADODB.Connection");
        try
        {
            HRESULT hr=m_pConn->Open((_bstr_t)m_sConn,"sa","mapper",adConnectUnspecified);   
            if (FAILED(hr))
            {
                AfxMessageBox("不能连接数据库 source!");
                return FALSE;
            }
        }
        catch(_com_error e)
        {
            AfxMessageBox("不能连接数据库 error!");
            return FALSE;
        }

    3.Oracle 9i

        _ConnectionPtr m_pConn;
        CString m_sConn="Provider=MSDAORA.1;Data Source=sode_192.168.3.9"; //使用
    ms连接库,sode为SID,192.168.3.9为机器ip
        m_pConn.CreateInstance("ADODB.Connection");
        try
        {
            HRESULT hr=m_pConn->Open((_bstr_t)m_sConn,"sodeUser","sodePw",adConnectUnspecified);   
            if (FAILED(hr))
            {
                AfxMessageBox("不能连接数据库 source!");
                return FALSE;
            }
        }
        catch(_com_error e)
        {
            AfxMessageBox("不能打开数据库 error!");
            return FALSE;
        }

  • vc中如何实现单文档多试图的功能

    2007-06-15 11:15:48

    多视图是VC开发中经常要用到的技术之一,一般地实现单文档多视图有两种方式1)通过视图分割的技术(使用CSplitterWnd实现),将窗口分割为多个部分,每个部分显示各自显示不同的视图,这种技术实现起来比较简单,并且相关的资料也很多。2)通过一个文档关联多个视图,窗口显示整个视图。第二种实现较第一种复杂,这里给出详细的实现方法。

    Step 1:使用VC 6.0新建一个Project,命名为:MultiView。除选择单文档属性外,一切使用“默认”方式。于是你可以获得五个类:CMainFrame CMultiViewAppCMultiViewDocCMultiViewView,和CAboutDlg

     

    Step 2:新建一个新的视图View,添加一个新的MFC ClassInsert>New Class),基类为CView(或者CView的派生子类,如CEditView等)。类的名字为CAnotherView,这就是新的视图;并为CAnotherView添加GetDocument的实现:

    CMultiViewDoc* CAnotherView::GetDocument()

    {

           return (CMultiViewDoc*)m_pDocument;

    }

     

    Step 3:在CMultiViewApp添加成员变量记录这两个视图:

    private:

           CView* m_pFirstView;

           CView* m_pAnotherView;

    给程序菜单IDR_MAINFRAME添加一个菜单项目“视图”,该菜单项有两个子菜单“视图一”和“视图二”,添加相应函数(void CMultiViewApp:: OnShowFirstview()和void CMultiViewApp:: OnShowSecondview());

     

    Step 4:创建新的视图:在BOOL CMultiViewApp::InitInstance()中添加代码:

    …….

    //创建一个新的视图

           CView* m_pActiveView = ((CFrameWnd*)m_pMainWnd)->GetActiveView();

           m_pFirstView = m_pActiveView;

          

           m_pAnotherView = new CAnotherView();

     

           //文档和视图关联

           CDocument* m_pDoc = ((CFrameWnd*)m_pMainWnd)->GetActiveDocument();

     

           CCreateContext context;

           context.m_pCurrentDoc = m_pDoc;

     

           //创建视图

           UINT m_IDFORANOTHERVIEW = AFX_IDW_PANE_FIRST + 1;

           CRect rect;

           m_pAnotherView->Create(NULL,NULL,WS_CHILD,rect,m_pMainWnd,

    m_IDFORANOTHERVIEW,&context);

        ……

     

    Step 5:现在已经创建了视图,并且都和文档关联起来了。现在要作的就是视图间的转换。在void CMultiViewApp:: OnShowFirstview()中添加实现代码:

    void CMultiViewApp::OnShowFirstview()

    {

           // TODO: Add your command handler code here

           UINT temp = ::GetWindowLong(m_pAnotherView->m_hWnd, GWL_ID);

        ::SetWindowLong(m_pAnotherView->m_hWnd, GWL_ID, ::GetWindowLong(m_pFirstView->m_hWnd, GWL_ID));

        ::SetWindowLong(m_pFirstView->m_hWnd, GWL_ID, temp);

     

           m_pAnotherView->ShowWindow(SW_HIDE);

           m_pFirstView->ShowWindow(SW_SHOW);

                

           ((CFrameWnd*)m_pMainWnd)->SetActiveView(m_pFirstView); 

           ((CFrameWnd*) m_pMainWnd)->RecalcLayout();

        m_pFirstView->Invalidate();

    }

     

    void CMultiViewApp:: OnShowSecondview()中添加实现代码:

    void CMultiViewApp::OnShowSecondview()

    {

           // TODO: Add your command handler code here

           UINT temp = ::GetWindowLong(m_pAnotherView->m_hWnd, GWL_ID);

        ::SetWindowLong(m_pAnotherView->m_hWnd, GWL_ID, ::GetWindowLong(m_pFirstView->m_hWnd, GWL_ID));

        ::SetWindowLong(m_pFirstView->m_hWnd, GWL_ID, temp);

     

           m_pFirstView->ShowWindow(SW_HIDE);

           m_pAnotherView->ShowWindow(SW_SHOW);      

     

           ((CFrameWnd*)m_pMainWnd)->SetActiveView(m_pAnotherView); 

           ((CFrameWnd*) m_pMainWnd)->RecalcLayout();

        m_pAnotherView->Invalidate();

    }

     

    Step 6:为了演示,这里将不同的视图给予一个标记,在CMultiViewViewCAnotherViewOnDraw方法中分别添加以下代码:

    pDC->TextOut(400,300,"First View");

           pDC->TextOut(400,320,pDoc->GetTitle());

    pDC->TextOut(400,300,"Another View");

           pDC->TextOut(400,320,pDoc->GetTitle());

     

    至此就大功告成了,但是实现过程中有4点说明:

    1)  实现中由于使用到相关的类,因此在必要的地方要include相关的头文件,这里省略;CAnotherView的默认构造函数是Protected的,需要将其改为Public,或者提供一个产生CAnotherView对象的方法(因要创建视图对象);

    2)  这里给出的是一个示例代码,实际开发中可以通过参考实现获得自己想要实现的具体应用情况(例如视图类的不同、数量不同,更重要的还有业务逻辑的不同实现等);

    3)  本文的示例代码已上传到Blog,可以通过下面的地址获得代码
     



    Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1062132

  • vc 中线程同步问题的解决

    2007-06-07 10:15:35

    摘要: 多线程同步技术是计算机软件开发的重要技术,本文对多线程的各种同步技术的原理和实现进行了初步探讨。

    关键词: VC++6.0; 线程同步;临界区;事件;互斥;信号量;

    正文

    使线程同步

      在程序中使用多线程时,一般很少有多个线程能在其生命期内进行完全独立的操作。更多的情况是一些线程进行某些处理操作,而其他的线程必须对其处理结果进行了解。正常情况下对这种处理结果的了解应当在其处理任务完成后进行。

      如果不采取适当的措施,其他线程往往会在线程处理任务结束前就去访问处理结果,这就很有可能得到有关处理结果的错误了解。例如,多个线程同时访问同一个全局变量,如果都是读取操作,则不会出现问题。如果一个线程负责改变此变量的值,而其他线程负责同时读取变量内容,则不能保证读取到的数据是经过写线程修改后的。

      为了确保读线程读取到的是经过修改的变量,就必须在向变量写入数据时禁止其他线程对其的任何访问,直至赋值过程结束后再解除对其他线程的访问限制。象这种保证线程能了解其他线程任务处理结束后的处理结果而采取的保护措施即为线程同步。

      线程同步是一个非常大的话题,包括方方面面的内容。从大的方面讲,线程的同步可分用户模式的线程同步和内核对象的线程同步两大类。用户模式中线程的同步方法主要有原子访问和临界区等方法。其特点是同步速度特别快,适合于对线程运行速度有严格要求的场合。

      内核对象的线程同步则主要由事件、等待定时器、信号量以及信号灯等内核对象构成。由于这种同步机制使用了内核对象,使用时必须将线程从用户模式切换到内核模式,而这种转换一般要耗费近千个CPU周期,因此同步速度较慢,但在适用性上却要远优于用户模式的线程同步方式。

    临界区

      临界区(Critical Section)是一段独占对某些共享资源访问的代码,在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。

      临界区在使用时以CRITICAL_SECTION结构对象保护共享资源,并分别用EnterCriticalSection()和LeaveCriticalSection()函数去标识和释放一个临界区。所用到的CRITICAL_SECTION结构对象必须经过InitializeCriticalSection()的初始化后才能使用,而且必须确保所有线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。否则临界区将不会起到应有的作用,共享资源依然有被破坏的可能。

    图1 使用临界区保持线程同步

    下面通过一段代码展示了临界区在保护多线程访问的共享资源中的作用。通过两个线程来分别对全局变量g_cArray[10]进行写入操作,用临界区结构对象g_cs来保持线程的同步,并在开启线程前对其进行初始化。为了使实验效果更加明显,体现出临界区的作用,在线程函数对共享资源g_cArray[10]的写入时,以Sleep()函数延迟1毫秒,使其他线程同其抢占CPU的可能性增大。如果不使用临界区对其进行保护,则共享资源数据将被破坏(参见图1(a)所示计算结果),而使用临界区对线程保持同步后则可以得到正确的结果(参见图1(b)所示计算结果)。代码实现清单附下:

    // 临界区结构对象
    CRITICAL_SECTION g_cs;
    // 共享资源
    char g_cArray[10];
    UINT ThreadProc10(LPVOID pParam)
    {
     // 进入临界区
     EnterCriticalSection(&g_cs);
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[i] = 'a';
      Sleep(1);
     }
     // 离开临界区
     LeaveCriticalSection(&g_cs);
     return 0;
    }
    UINT ThreadProc11(LPVOID pParam)
    {
     // 进入临界区
     EnterCriticalSection(&g_cs);
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[10 - i - 1] = 'b';
      Sleep(1);
     }
     // 离开临界区
     LeaveCriticalSection(&g_cs);
     return 0;
    }
    ……
    void CSample08View::OnCriticalSection()
    {
     // 初始化临界区
     InitializeCriticalSection(&g_cs);
     // 启动线程
     AfxBeginThread(ThreadProc10, NULL);
     AfxBeginThread(ThreadProc11, NULL);
     // 等待计算完毕
     Sleep(300);
     // 报告计算结果
     CString sResult = CString(g_cArray);
     AfxMessageBox(sResult);
    }


      在使用临界区时,一般不允许其运行时间过长,只要进入临界区的线程还没有离开,其他所有试图进入此临界区的线程都会被挂起而进入到等待状态,并会在一定程度上影响。程序的运行性能。尤其需要注意的是不要将等待用户输入或是其他一些外界干预的操作包含到临界区。如果进入了临界区却一直没有释放,同样也会引起其他线程的长时间等待。换句话说,在执行了EnterCriticalSection()语句进入临界区后无论发生什么,必须确保与之匹配的LeaveCriticalSection()都能够被执行到。可以通过添加结构化异常处理代码来确保LeaveCriticalSection()语句的执行。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。

      MFC为临界区提供有一个CCriticalSection类,使用该类进行线程同步处理是非常简单的,只需在线程函数中用CCriticalSection类成员函数Lock()和UnLock()标定出被保护代码片段即可。对于上述代码,可通过CCriticalSection类将其改写如下:

    // MFC临界区类对象
    CCriticalSection g_clsCriticalSection;
    // 共享资源
    char g_cArray[10];
    UINT ThreadProc20(LPVOID pParam)
    {
     // 进入临界区
     g_clsCriticalSection.Lock();
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[i] = 'a';
      Sleep(1);
     }
     // 离开临界区
     g_clsCriticalSection.Unlock();
     return 0;
    }
    UINT ThreadProc21(LPVOID pParam)
    {
     // 进入临界区
     g_clsCriticalSection.Lock();
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[10 - i - 1] = 'b';
      Sleep(1);
     }
     // 离开临界区
     g_clsCriticalSection.Unlock();
     return 0;
    }
    ……
    void CSample08View::OnCriticalSectionMfc()
    {
     // 启动线程
     AfxBeginThread(ThreadProc20, NULL);
     AfxBeginThread(ThreadProc21, NULL);
     // 等待计算完毕
     Sleep(300);
     // 报告计算结果
     CString sResult = CString(g_cArray);
     AfxMessageBox(sResult);
    }


    管理事件内核对象

      在前面讲述线程通信时曾使用过事件内核对象来进行线程间的通信,除此之外,事件内核对象也可以通过通知操作的方式来保持线程的同步。对于前面那段使用临界区保持线程同步的代码可用事件对象的线程同步方法改写如下:

    // 事件句柄
    HANDLE hEvent = NULL;
    // 共享资源
    char g_cArray[10];
    ……
    UINT ThreadProc12(LPVOID pParam)
    {
     // 等待事件置位
     WaitForSingleObject(hEvent, INFINITE);
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[i] = 'a';
      Sleep(1);
     }
     // 处理完成后即将事件对象置位
     SetEvent(hEvent);
     return 0;
    }
    UINT ThreadProc13(LPVOID pParam)
    {
     // 等待事件置位
     WaitForSingleObject(hEvent, INFINITE);
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[10 - i - 1] = 'b';
      Sleep(1);
     }
     // 处理完成后即将事件对象置位
     SetEvent(hEvent);
     return 0;
    }
    ……
    void CSample08View::OnEvent()
    {
     // 创建事件
     hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
     // 事件置位
     SetEvent(hEvent);
     // 启动线程
     AfxBeginThread(ThreadProc12, NULL);
     AfxBeginThread(ThreadProc13, NULL);
     // 等待计算完毕
     Sleep(300);
     // 报告计算结果
     CString sResult = CString(g_cArray);
     AfxMessageBox(sResult);
    }


      在创建线程前,首先创建一个可以自动复位的事件内核对象hEvent,而线程函数则通过WaitForSingleObject()等待函数无限等待hEvent的置位,只有在事件置位时WaitForSingleObject()才会返回,被保护的代码将得以执行。对于以自动复位方式创建的事件对象,在其置位后一被WaitForSingleObject()等待到就会立即复位,也就是说在执行ThreadProc12()中的受保护代码时,事件对象已经是复位状态的,这时即使有ThreadProc13()对CPU的抢占,也会由于WaitForSingleObject()没有hEvent的置位而不能继续执行,也就没有可能破坏受保护的共享资源。在ThreadProc12()中的处理完成后可以通过SetEvent()对hEvent的置位而允许ThreadProc13()对共享资源g_cArray的处理。这里SetEvent()所起的作用可以看作是对某项特定任务完成的通知。

      使用临界区只能同步同一进程中的线程,而使用事件内核对象则可以对进程外的线程进行同步,其前提是得到对此事件对象的访问权。可以通过OpenEvent()函数获取得到,其函数原型为:

    HANDLE OpenEvent(
     DWORD dwDesiredAccess, // 访问标志
     BOOL bInheritHandle, // 继承标志
     LPCTSTR lpName // 指向事件对象名的指针
    );


      如果事件对象已创建(在创建事件时需要指定事件名),函数将返回指定事件的句柄。对于那些在创建事件时没有指定事件名的事件内核对象,可以通过使用内核对象的继承性或是调用DuplicateHandle()函数来调用CreateEvent()以获得对指定事件对象的访问权。在获取到访问权后所进行的同步操作与在同一个进程中所进行的线程同步操作是一样的。

      如果需要在一个线程中等待多个事件,则用WaitForMultipleObjects()来等待。WaitForMultipleObjects()与WaitForSingleObject()类似,同时监视位于句柄数组中的所有句柄。这些被监视对象的句柄享有平等的优先权,任何一个句柄都不可能比其他句柄具有更高的优先权。WaitForMultipleObjects()的函数原型为:

    DWORD WaitForMultipleObjects(
     DWORD nCount, // 等待句柄数
     CONST HANDLE *lpHandles, // 句柄数组首地址
     BOOL fWaitAll, // 等待标志
     DWORD dwMilliseconds // 等待时间间隔
    );


      参数nCount指定了要等待的内核对象的数目,存放这些内核对象的数组由lpHandles来指向。fWaitAll对指定的这nCount个内核对象的两种等待方式进行了指定,为TRUE时当所有对象都被通知时函数才会返回,为FALSE则只要其中任何一个得到通知就可以返回。dwMilliseconds在饫锏淖饔糜朐赪aitForSingleObject()中的作用是完全一致的。如果等待超时,函数将返回WAIT_TIMEOUT。如果返回WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1中的某个值,则说明所有指定对象的状态均为已通知状态(当fWaitAll为TRUE时)或是用以减去WAIT_OBJECT_0而得到发生通知的对象的索引(当fWaitAll为FALSE时)。如果返回值在WAIT_ABANDONED_0与WAIT_ABANDONED_0+nCount-1之间,则表示所有指定对象的状态均为已通知,且其中至少有一个对象是被丢弃的互斥对象(当fWaitAll为TRUE时),或是用以减去WAIT_OBJECT_0表示一个等待正常结束的互斥对象的索引(当fWaitAll为FALSE时)。 下面给出的代码主要展示了对WaitForMultipleObjects()函数的使用。通过对两个事件内核对象的等待来控制线程任务的执行与中途退出:

    // 存放事件句柄的数组
    HANDLE hEvents[2];
    UINT ThreadProc14(LPVOID pParam)
    {
     // 等待开启事件
     DWORD dwRet1 = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE);
     // 如果开启事件到达则线程开始执行任务
     if (dwRet1 == WAIT_OBJECT_0)
     {
      AfxMessageBox("线程开始工作!");
      while (true)
      {
       for (int i = 0; i < 10000; i++);
       // 在任务处理过程中等待结束事件
       DWORD dwRet2 = WaitForMultipleObjects(2, hEvents, FALSE, 0);
       // 如果结束事件置位则立即终止任务的执行
       if (dwRet2 == WAIT_OBJECT_0 + 1)
        break;
      }
     }
     AfxMessageBox("线程退出!");
     return 0;
    }
    ……
    void CSample08View::OnStartEvent()
    {
     // 创建线程
     for (int i = 0; i < 2; i++)
      hEvents[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
      // 开启线程
      AfxBeginThread(ThreadProc14, NULL);
      // 设置事件0(开启事件)
      SetEvent(hEvents[0]);
    }
    void CSample08View::OnEndevent()
    {
     // 设置事件1(结束事件)
     SetEvent(hEvents[1]);
    }


      MFC为事件相关处理也提供了一个CEvent类,共包含有除构造函数外的4个成员函数PulseEvent()、ResetEvent()、SetEvent()和UnLock()。在功能上分别相当与Win32 API的PulseEvent()、ResetEvent()、SetEvent()和CloseHandle()等函数。而构造函数则履行了原CreateEvent()函数创建事件对象的职责,其函数原型为:

    CEvent(BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );


      按照此缺省设置将创建一个自动复位、初始状态为复位状态的没有名字的事件对象。封装后的CEvent类使用起来更加方便,图2即展示了CEvent类对A、B两线程的同步过程:

    图2 CEvent类对线程的同步过程示意

    B线程在执行到CEvent类成员函数Lock()时将会发生阻塞,而A线程此时则可以在没有B线程干扰的情况下对共享资源进行处理,并在处理完成后通过成员函数SetEvent()向B发出事件,使其被释放,得以对A先前已处理完毕的共享资源进行操作。可见,使用CEvent类对线程的同步方法与通过API函数进行线程同步的处理方法是基本一致的。前面的API处理代码可用CEvent类将其改写为:

    // MFC事件类对象
    CEvent g_clsEvent;
    UINT ThreadProc22(LPVOID pParam)
    {
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[i] = 'a';
      Sleep(1);
     }
     // 事件置位
     g_clsEvent.SetEvent();
     return 0;
    }
    UINT ThreadProc23(LPVOID pParam)
    {
     // 等待事件
     g_clsEvent.Lock();
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[10 - i - 1] = 'b';
      Sleep(1);
     }
     return 0;
    }
    ……
    void CSample08View::OnEventMfc()
    {
     // 启动线程
     AfxBeginThread(ThreadProc22, NULL);
     AfxBeginThread(ThreadProc23, NULL);
     // 等待计算完毕
     Sleep(300);
     // 报告计算结果
     CString sResult = CString(g_cArray);
     AfxMessageBox(sResult);
    }

      信号量内核对象

      信号量(Semaphore)内核对象对线程的同步方式与前面几种方法不同,它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。

    图3 使用信号量对象控制资源

    下面结合图例3来演示信号量对象对资源的控制。在图3中,以箭头和白色箭头表示共享资源所允许的最大资源计数和当前可用资源计数。初始如图(a)所示,最大资源计数和当前可用资源计数均为4,此后每增加一个对资源进行访问的线程(用黑色箭头表示)当前资源计数就会相应减1,图(b)即表示的在3个线程对共享资源进行访问时的状态。当进入线程数达到4个时,将如图(c)所示,此时已达到最大资源计数,而当前可用资源计数也已减到0,其他线程无法对共享资源进行访问。在当前占有资源的线程处理完毕而退出后,将会释放出空间,图(d)已有两个线程退出对资源的占有,当前可用计数为2,可以再允许2个线程进入到对资源的处理。可以看出,信号量是通过计数来对线程访问资源进行控制的,而实际上信号量确实也被称作Dijkstra计数器。

      使用信号量内核对象进行线程同步主要会用到CreateSemaphore()、OpenSemaphore()、ReleaseSemaphore()、WaitForSingleObject()和WaitForMultipleObjects()等函数。其中,CreateSemaphore()用来创建一个信号量内核对象,其函数原型为:

    HANDLE CreateSemaphore(
     LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全属性指针
     LONG lInitialCount, // 初始计数
     LONG lMaximumCount, // 最大计数
     LPCTSTR lpName // 对象名指针
    );


      参数lMaximumCount是一个有符号32位值,定义了允许的最大资源计数,最大取值不能超过4294967295。lpName参数可以为创建的信号量定义一个名字,由于其创建的是一个内核对象,因此在其他进程中可以通过该名字而得到此信号量。OpenSemaphore()函数即可用来根据信号量名打开在其他进程中创建的信号量,函数原型如下:

    HANDLE OpenSemaphore(
     DWORD dwDesiredAccess, // 访问标志
     BOOL bInheritHandle, // 继承标志
     LPCTSTR lpName // 信号量名
    );


      在线程离开对共享资源的处理时,必须通过ReleaseSemaphore()来增加当前可用资源计数。否则将会出现当前正在处理共享资源的实际线程数并没有达到要限制的数值,而其他线程却因为当前可用资源计数为0而仍无法进入的情况。ReleaseSemaphore()的函数原型为:

    BOOL ReleaseSemaphore(
     HANDLE hSemaphore, // 信号量句柄
     LONG lReleaseCount, // 计数递增数量
     LPLONG lpPreviousCount // 先前计数
    );


      该函数将lReleaseCount中的值添加给信号量的当前资源计数,一般将lReleaseCount设置为1,如果需要也可以设置其他的值。WaitForSingleObject()和WaitForMultipleObjects()主要用在试图进入共享资源的线程函数入口处,主要用来判断信号量的当前可用资源计数是否允许本线程的进入。只有在当前可用资源计数值大于0时,被监视的信号量内核对象才会得到通知。

      信号量的使用特点使其更适用于对Socket(套接字)程序中线程的同步。例如,网络上的HTTP服务器要对同一时间内访问同一页面的用户数加以限制,这时可以为没一个用户对服务器的页面请求设置一个线程,而页面则是待保护的共享资源,通过使用信号量对线程的同步作用可以确保在任一时刻无论有多少用户对某一页面进行访问,只有不大于设定的最大用户数目的线程能够进行访问,而其他的访问企图则被挂起,只有在有用户退出对此页面的访问后才有可能进入。下面给出的示例代码即展示了类似的处理过程:

    // 信号量对象句柄
    HANDLE hSemaphore;
    UINT ThreadProc15(LPVOID pParam)
    {
     // 试图进入信号量关口
     WaitForSingleObject(hSemaphore, INFINITE);
     // 线程任务处理
     AfxMessageBox("线程一正在执行!");
     // 释放信号量计数
     ReleaseSemaphore(hSemaphore, 1, NULL);
     return 0;
    }
    UINT ThreadProc16(LPVOID pParam)
    {
     // 试图进入信号量关口
     WaitForSingleObject(hSemaphore, INFINITE);
     // 线程任务处理
     AfxMessageBox("线程二正在执行!");
     // 释放信号量计数
     ReleaseSemaphore(hSemaphore, 1, NULL);
     return 0;
    }
    UINT ThreadProc17(LPVOID pParam)
    {
     // 试图进入信号量关口
     WaitForSingleObject(hSemaphore, INFINITE);
     // 线程任务处理
     AfxMessageBox("线程三正在执行!");
     // 释放信号量计数
     ReleaseSemaphore(hSemaphore, 1, NULL);
     return 0;
    }
    ……
    void CSample08View::OnSemaphore()
    {
     // 创建信号量对象
     hSemaphore = CreateSemaphore(NULL, 2, 2, NULL);
     // 开启线程
     AfxBeginThread(ThreadProc15, NULL);
     AfxBeginThread(ThreadProc16, NULL);
     AfxBeginThread(ThreadProc17, NULL);
    }


    图4 开始进入的两个线程

    图5 线程二退出后线程三才得以进入

    上述代码在开启线程前首先创建了一个初始计数和最大资源计数均为2的信号量对象hSemaphore。即在同一时刻只允许2个线程进入由hSemaphore保护的共享资源。随后开启的三个线程均试图访问此共享资源,在前两个线程试图访问共享资源时,由于hSemaphore的当前可用资源计数分别为2和1,此时的hSemaphore是可以得到通知的,也就是说位于线程入口处的WaitForSingleObject()将立即返回,而在前两个线程进入到保护区域后,hSemaphore的当前资源计数减少到0,hSemaphore将不再得到通知,WaitForSingleObject()将线程挂起。直到此前进入到保护区的线程退出后才能得以进入。图4和图5为上述代脉的运行结果。从实验结果可以看出,信号量始终保持了同一时刻不超过2个线程的进入。

      在MFC中,通过CSemaphore类对信号量作了表述。该类只具有一个构造函数,可以构造一个信号量对象,并对初始资源计数、最大资源计数、对象名和安全属性等进行初始化,其原型如下:

    CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );


      在构造了CSemaphore类对象后,任何一个访问受保护共享资源的线程都必须通过CSemaphore从父类CSyncObject类继承得到的Lock()和UnLock()成员函数来访问或释放CSemaphore对象。与前面介绍的几种通过MFC类保持线程同步的方法类似,通过CSemaphore类也可以将前面的线程同步代码进行改写,这两种使用信号量的线程同步方法无论是在实现原理上还是从实现结果上都是完全一致的。下面给出经MFC改写后的信号量线程同步代码:

    // MFC信号量类对象
    CSemaphore g_clsSemaphore(2, 2);
    UINT ThreadProc24(LPVOID pParam)
    {
     // 试图进入信号量关口
     g_clsSemaphore.Lock();
     // 线程任务处理
     AfxMessageBox("线程一正在执行!");
     // 释放信号量计数
     g_clsSemaphore.Unlock();
     return 0;
    }
    UINT ThreadProc25(LPVOID pParam)
    {
     // 试图进入信号量关口
     g_clsSemaphore.Lock();
     // 线程任务处理
     AfxMessageBox("线程二正在执行!");
     // 释放信号量计数
     g_clsSemaphore.Unlock();
     return 0;
    }
    UINT ThreadProc26(LPVOID pParam)
    {
     // 试图进入信号量关口
     g_clsSemaphore.Lock();
     // 线程任务处理
     AfxMessageBox("线程三正在执行!");
     // 释放信号量计数
     g_clsSemaphore.Unlock();
     return 0;
    }
    ……
    void CSample08View::OnSemaphoreMfc()
    {
     // 开启线程
     AfxBeginThread(ThreadProc24, NULL);
     AfxBeginThread(ThreadProc25, NULL);
     AfxBeginThread(ThreadProc26, NULL);
    }

      互斥内核对象

      互斥(Mutex)是一种用途非常广泛的内核对象。能够保证多个线程对同一共享资源的互斥访问。同临界区有些类似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。与其他几种内核对象不同,互斥对象在操作系统中拥有特殊代码,并由操作系统来管理,操作系统甚至还允许其进行一些其他内核对象所不能进行的非常规操作。为便于理解,可参照图6给出的互斥内核对象的工作模型:

    图6 使用互斥内核对象对共享资源的保护
    图(a)中的箭头为要访问资源(矩形框)的线程,但只有第二个线程拥有互斥对象(黑点)并得以进入到共享资源,而其他线程则会被排斥在外(如图(b)所示)。当此线程处理完共享资源并准备离开此区域时将把其所拥有的互斥对象交出(如图(c)所示),其他任何一个试图访问此资源的线程都有机会得到此互斥对象。

      以互斥内核对象来保持线程同步可能用到的函数主要有CreateMutex()、OpenMutex()、ReleaseMutex()、WaitForSingleObject()和WaitForMultipleObjects()等。在使用互斥对象前,首先要通过CreateMutex()或OpenMutex()创建或打开一个互斥对象。CreateMutex()函数原型为:

    HANDLE CreateMutex(
     LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性指针
     BOOL bInitialOwner, // 初始拥有者
     LPCTSTR lpName // 互斥对象名
    );

      参数bInitialOwner主要用来控制互斥对象的初始状态。一般多将其设置为FALSE,以表明互斥对象在创建时并没有为任何线程所占有。如果在创建互斥对象时指定了对象名,那么可以在本进程其他地方或是在其他进程通过OpenMutex()函数得到此互斥对象的句柄。OpenMutex()函数原型为:

    HANDLE OpenMutex(
     DWORD dwDesiredAccess, // 访问标志
     BOOL bInheritHandle, // 继承标志
     LPCTSTR lpName // 互斥对象名
    );

      当目前对资源具有访问权的线程不再需要访问此资源而要离开时,必须通过ReleaseMutex()函数来释放其拥有的互斥对象,其函数原型为:

    BOOL ReleaseMutex(HANDLE hMutex);

      其唯一的参数hMutex为待释放的互斥对象句柄。至于WaitForSingleObject()和WaitForMultipleObjects()等待函数在互斥对象保持线程同步中所起的作用与在其他内核对象中的作用是基本一致的,也是等待互斥内核对象的通知。但是这里需要特别指出的是:在互斥对象通知引起调用等待函数返回时,等待函数的返回值不再是通常的WAIT_OBJECT_0(对于WaitForSingleObject()函数)或是在WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1之间的一个值(对于WaitForMultipleObjects()函数),而是将返回一个WAIT_ABANDONED_0(对于WaitForSingleObject()函数)或是在WAIT_ABANDONED_0到WAIT_ABANDONED_0+nCount-1之间的一个值(对于WaitForMultipleObjects()函数)。以此来表明线程正在等待的互斥对象由另外一个线程所拥有,而此线程却在使用完共享资源前就已经终止。除此之外,使用互斥对象的方法在等待线程的可调度性上同使用其他几种内核对象的方法也有所不同,其他内核对象在没有得到通知时,受调用等待函数的作用,线程将会挂起,同时失去可调度性,而使用互斥的方法却可以在等待的同时仍具有可调度性,这也正是互斥对象所能完成的非常规操作之一。

      在编写程序时,互斥对象多用在对那些为多个线程所访问的内存块的保护上,可以确保任何线程在处理此内存块时都对其拥有可靠的独占访问权。下面给出的示例代码即通过互斥内核对象hMutex对共享内存快g_cArray[]进行线程的独占访问保护。下面给出实现代码清单:

    // 互斥对象
    HANDLE hMutex = NULL;
    char g_cArray[10];
    UINT ThreadProc18(LPVOID pParam)
    {
     // 等待互斥对象通知
     WaitForSingleObject(hMutex, INFINITE);
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[i] = 'a';
      Sleep(1);
     }
     // 释放互斥对象
     ReleaseMutex(hMutex);
     return 0;
    }
    UINT ThreadProc19(LPVOID pParam)
    {
     // 等待互斥对象通知
     WaitForSingleObject(hMutex, INFINITE);
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[10 - i - 1] = 'b';
      Sleep(1);
     }
     // 释放互斥对象
     ReleaseMutex(hMutex);
     return 0;
    }
    ……
    void CSample08View::OnMutex()
    {
     // 创建互斥对象
     hMutex = CreateMutex(NULL, FALSE, NULL);
     // 启动线程
     AfxBeginThread(ThreadProc18, NULL);
     AfxBeginThread(ThreadProc19, NULL);
     // 等待计算完毕
     Sleep(300);
     // 报告计算结果
     CString sResult = CString(g_cArray);
     AfxMessageBox(sResult);
    }

      互斥对象在MFC中通过CMutex类进行表述。使用CMutex类的方法非常简单,在构造CMutex类对象的同时可以指明待查询的互斥对象的名字,在构造函数返回后即可访问此互斥变量。CMutex类也是只含有构造函数这唯一的成员函数,当完成对互斥对象保护资源的访问后,可通过调用从父类CSyncObject继承的UnLock()函数完成对互斥对象的释放。CMutex类构造函数原型为:

    CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );

      该类的适用范围和实现原理与API方式创建的互斥内核对象是完全类似的,但要简洁的多,下面给出就是对前面的示例代码经CMutex类改写后的程序实现清单:

    // MFC互斥类对象
    CMutex g_clsMutex(FALSE, NULL);
    UINT ThreadProc27(LPVOID pParam)
    {
     // 等待互斥对象通知
     g_clsMutex.Lock();
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[i] = 'a';
      Sleep(1);
     }
     // 释放互斥对象
     g_clsMutex.Unlock();
     return 0;
    }
    UINT ThreadProc28(LPVOID pParam)
    {
     // 等待互斥对象通知
     g_clsMutex.Lock();
     // 对共享资源进行写入操作
     for (int i = 0; i < 10; i++)
     {
      g_cArray[10 - i - 1] = 'b';
      Sleep(1);
     }
     // 释放互斥对象
     g_clsMutex.Unlock();
     return 0;
    }
    ……
    void CSample08View::OnMutexMfc()
    {
     // 启动线程
     AfxBeginThread(ThreadProc27, NULL);
     AfxBeginThread(ThreadProc28, NULL);
     // 等待计算完毕
     Sleep(300);
     // 报告计算结果
     CString sResult = CString(g_cArray);
     AfxMessageBox(sResult);
    }

      小结

      线程的使用使程序处理更够更加灵活,而这种灵活同样也会带来各种不确定性的可能。尤其是在多个线程对同一公共变量进行访问时。虽然未使用线程同步的程序代码在逻辑上或许没有什么问题,但为了确保程序的正确、可靠运行,必须在适当的场合采取线程同步措施。

  • c++中的const的用法

    2007-05-10 10:04:36

    const给人的第一印象就是定义常量。

    (1)const用于定义常量。

         例如:const int N = 100;const int M = 200;
         这样程序中只要用到 N、M 就分别代表为整型100、200,N、M 为一常量,在程序中不可改变。
         但有人说他编程时从来不用const定义常量。我相信。但他是不懂得真正的编程艺术,用const定义常量不仅能方便我们编程而且能提高程序的清晰性。你是愿意看到程序中100、200 满天飞,还是愿意只看到简单清晰的N、M。相信有没有好处你慢慢体会。
         还有人说他不用const定义常量,他用#define宏定义常量。可以。但不知道你有没有发现有时#define宏并没有如你所愿在定义常量。下面我们比较比较const和#define。
         1。
         (a) const定义常量是有数据类型的:
         这样const定义的常量编译器可以对其进行数据静态类型安全检查,而#define宏定义的常量却只是进行简单的字符替换,没有类型安全检查,且有时还会产生边际效应(不如你愿处)。所谓边际效应举例如下:
               #define N 100
               #define M 200 + N
               当程序中使用 M*N 时,原本想要 100 * (200+ N )的却变成了 100 * 200 + N。
         (b)#define宏定义常量却没有。#define <宏名><字符串>,字符串可以是常数、表达式、格式串等。在程序被编译的时候,如果遇到宏名就哟内指定的字符串进行替换,然后再进行编译。
         2。
         有些调试程序可对const进行调试,但不对#define进行调试。
         3。
         当定义局部变量时,const作用域仅限于定义局部变量的函数体内。但用#define时其作用域不仅限于定义局部变量的函数体内,而是从定义点到整个程序的结束点。但也可以用#undef取消其定义从而限定其作用域范围。只用const定义常量,并不能起到其强大的作用。const还可修饰函数形式参数、返回值和类的成员函数等。从而提高函数的健壮性。因为const修饰的东西能受到c/c++的静态类型安全检查机制的强制保护,防止意外的修改。

    (2)const修饰函数形式参数

         形式参数有输入形式参数和输出形式参数。参数用于输出时不能加const修饰,那样会使函数失去输出功能。因为const修饰的东西是不能改变的。
         const只能用于修饰输入参数。
         谈const只能用于修饰输入参数之前先谈谈C++函数的三种传递方式。
         C++函数的三种传递方式为:值传递、指针传递和引用传递。简单举例说明之,详细说明请参考别的资料。
         值传递:
           void fun(int x){
                 x += 5;       //修改的只是y在栈中copy x,x只是y的一个副本,在内存中重新开辟的一块临时空间把y的值 送给了x;这样也增加了程序运行的时间,降低了程序的效率。
           }
           void main(void){
                 int y = 0;
                 fun(y);
                 cout<</"y = /"<<y<<endl;  //y = 0;
           }
         指针传递:
            void fun(int *x){
                 *x += 5;      //修改的是指针x指向的内存单元值
            }
            void main(void){
                 int y = 0;
                 fun(&y);
                 cout<<<</"y = /"<<y<<endl;  //y = 5;
            }
          引用传递:
             void fun(int &x){
                 x += 5;      //修改的是x引用的对象值 &x = y;
            }
            void main(void){
                 int y = 0;
                 fun(y);
                 cout<<<</"y = /"<<y<<endl;  //y = 5;
            }
          看了传递方式后我们继续来谈“const只能用于修饰输入参数”的情况。

             当输入参数用“值传递”方式时,我们不需要加const修饰,因为用值传递时,函数将自动用实际参数的拷贝初始化形式参数,当在函数体内改变形式参数时,改变的也只是栈上的拷贝而不是实际参数。
             但要注意的是,当输入参数为ADT/UDT(用户自定义类型和抽象数据类型)时,应该将“值传递”改为“const &传递”,目的可以提高效率。
             例如:
                void fun(A a);//效率底。函数体内产生A类型的临时对象用于复制参数 a,但是临时对象的
                              //构造、复制、析构过程都将消耗时间。
                void fun(A const &a);//提高效率。用“引用传递”不需要产生临时对象,省了临时对象的
                                     //构造、复制、析构过程消耗的时间。但光用引用有可能改变a,所以加const


             当输入参数用“指针传递”方式时,加const修饰可防止意外修改指针指向的内存单元,起到保护作用。
             例如:
                void funstrcopy(char *strdest,const char *strsrc)//任何改变strsrc指向的内存单元,
                                                                 //编译器都将报错
                些时保护了指针的内存单元,也可以保护指针本身,防止其地址改变。
             例如:
               void funstrcopy(char *strdest,const char *const strsrc)

    (3)const修饰函数的返回值

         如给“指针传递”的函数返回值加const,则返回值不能被直接修改,且该返回值只能被赋值给加const修饰的同类型指针。
         例如:
            const char *GetChar(void){};
          赋值 char *ch = GetChar();//错误    const char *ch = GetChar();//正确

    (4)const修饰类的成员函数(函数定义体)

         任何不会修改数据成员的函数都应用const修饰,这样当不小心修改了数据成员或调用了非const成员函数时,编译器都会报错。
         const修饰类的成员函数形式为:int GetCount(void)  const;
    (5)用传引用给const取代传值
    缺省情况下,C++ 以传值方式将对象传入或传出函数(这是一个从 C 继承来的特性)。除非你特别指定其它方式,否则函数的参数就会以实际参数(actual argument)的拷贝进行初始化,而函数的调用者会收到函数返回值的一个拷贝。这个拷贝由对象的拷贝构造函数生成。这就使得传值(pass-by-value)成为一个代价不菲的操作。例如,考虑下面这个类层级结构:

    class Person {
     public:
      Person(); // parameters omitted for simplicity
      virtual ~Person(); // see Item 7 for why this is virtual
      ...

     private:
      std::string name;
      std::string address;
    };

    class Student: public Person {
     public:
      Student(); // parameters again omitted
      ~Student();
      ...

     private:
      std::string schoolName;
      std::string schoolAddress;
    };

      现在,考虑以下代码,在此我们调用一个函数—— validateStudent,它得到一个 Student 参数(以传值的方式),并返回它是否验证有效的结果:

    bool validateStudent(Student s); // function taking a Student
    // by value

    Student plato; // Plato studied under Socrates

    bool platoIsOK = validateStudent(plato); // call the function

      当这个函数被调用时会发生什么呢?

      很明显,Student 的拷贝构造函数被调用,用 plato 来初始化参数 s。同样明显的是,当 validateStudent 返回时,s 就会被销毁。所以这个函数的参数传递代价是一次 Student 的拷贝构造函数的调用和一次 Student 的析构函数的调用。

      但这还不是全部。一个 Student 对象内部包含两个 string 对象,所以每次你构造一个 Student 对象的时候,你也必须构造两个 string 对象。一个 Student 对象还要从一个 Person 对象继承,所以每次你构造一个 Student 对象的时候,你也必须构造一个 Person 对象。一个 Person 对象内部又包含两个额外的 string 对象,所以每个 Person 的构造也承担着另外两个 string 的构造。最终,以传值方式传递一个 Student 对象的后果就是引起一次 Student 的拷贝构造函数的调用,一次 Person 的拷贝构造函数的调用,以及四次 string 的拷贝构造函数调用。当 Student 对象的拷贝被销毁时,每一个构造函数的调用都对应一个析构函数的调用,所以以传值方式传递一个 Student 的全部代价是六个构造函数和六个析构函数!

      好了,这是正确的和值得的行为。毕竟,你希望你的全部对象都得到可靠的初始化和销毁。尽管如此,如果有一种办法可以绕过所有这些构造和析构过程,应该变得更好,这就是:传引用给 const(pass by reference-to-const):

    bool validateStudent(const Student& s);

      这样做非常有效:没有任何构造函数和析构函数被调用,因为没有新的对象被构造。被修改的参数声明中的 const 是非常重要的。 validateStudent 的最初版本接受一个 Student 值参数,所以调用者知道它们屏蔽了函数对它们传入的 Student 的任何可能的改变;validateStudent 也只能改变它的一个拷贝。现在 Student 以引用方式传递,同时将它声明为 const 是必要的,否则调用者必然担心 validateStudent 改变了它们传入的 Student。

      以传引用方式传递参数还可以避免切断问题(slicing problem)。当一个派生类对象作为一个基类对象被传递(传值方式),基类的拷贝构造函数被调用,而那些使得对象的行为像一个派生类对象的特殊特性被“切断”了。你只剩下一个纯粹的基类对象——这没什么可吃惊的,因为是一个基类的构造函数创建了它。这几乎绝不是你希望的。例如,假设你在一组实现一个图形窗口系统的类上工作:

    class Window {
     public:
      ...
      std::string name() const; // return name of window
      virtual void display() const; // draw window and contents
    };

    class WindowWithScrollBars: public Window {
     public:
      ...
      virtual void display() const;
    };

      所有 Window 对象都有一个名字,你能通过 name 函数得到它,而且所有的窗口都可以显示,你可一个通过调用 display 函数来做到这一点。display 为 virtual 的事实清楚地告诉你:一个纯粹的基类的 Window 对象的显示方法有可能不同于专门的 WindowWithScrollBars 对象的显示方法。

      现在,假设你想写一个函数打印出一个窗口的名字,并随后显示这个窗口。以下这个函数的写法是错误的:

    void printNameAndDisplay(Window w) // incorrect! parameter
    {
     // may be sliced!
     std::cout << w.name();
     w.display();
    }

      考虑当你用一个 WindowWithScrollBars 对象调用这个函数时会发生什么:

    WindowWithScrollBars wwsb;

    printNameAndDisplay(wwsb);

      参数 w 将被作为一个 Window 对象构造——它是被传值的,记得吗?而且使 wwsb 表现得像一个 WindowWithScrollBars 对象的特殊信息都被切断了。在 printNameAndDisplay 中,全然不顾传递给函数的那个对象的类型,w 将始终表现得像一个 Window 类的对象(因为它就是一个 Window 类的对象)。特别是,在 printNameAndDisplay 中调用 display 将总是调用 Window::display,绝不会是 WindowWithScrollBars::display。

      绕过切断问题的方法就是以传引用给 const 的方式传递 w:

    void printNameAndDisplay(const Window& w) // fine, parameter won’t
    {
     // be sliced
     std::cout << w.name();
     w.display();
    }

      现在 w 将表现得像实际传入的那种窗口。

      如果你掀开编译器的盖头偷看一下,你会发现用指针实现引用是非常典型的做法,所以以引用传递某物实际上通常意味着传递一个指针。由此可以得出结论,如果你有一个内建类型的对象(例如,一个 int),以传值方式传递它常常比传引用方式更高效。那么,对于内建类型,当你需要在传值和传引用给 const 之间做一个选择时,没有道理不选择传值。同样的建议也适用于 STL 中的迭代器(iterators)和函数对象(function objects),因为,作为惯例,它们就是为传值设计的。迭代器(iterators)和函数对象(function objects)的实现有责任保证拷贝的高效并且不受切断问题的影响。(这是一个“规则如何变化,依赖于你使用 C++ 的哪一个部分”的实例。)

      内建类型很小,所以有人就断定所有的小类型都是传值的上等候选者,即使它们是用户定义的。这样的推论是不可靠的。仅仅因为一个对象小,并不意味着调用它的拷贝构造函数就是廉价的。很多对象——大多数 STL 容器也在其中——容纳的和指针一样,但是拷贝这样的对象必须同时拷贝它们指向的每一样东西。那可能是非常昂贵的。

      即使当一个小对象有一个廉价的拷贝构造函数,也会存在性能问题。一些编译器对内建类型和用户定义类型并不一视同仁,即使他们有同样的底层表示。例如,一些编译器拒绝将仅由一个 double 组成的对象放入一个寄存器中,即使在常规上它们非常愿意将一个纯粹的 double 放入那里。如果发生了这种事情,你以传引用方式传递这样的对象更好一些,因为编译器理所当然会将一个指针(引用的实现)放入寄存器。

      小的用户定义类型不一定是传值的上等候选者的另一个原因是:作为用户定义类型,它的大小常常变化。一个现在较小的类型在将来版本中可能变得更大,因为它的内部实现可能会变化。甚至当你换了一个不同的 C++ 实现时,事情都可能会变化。例如,就在我这样写的时候,一些标准库的 string 类型的实现的大小就是另外一些实现的七倍。

      通常情况下,你能合理地假设传值廉价的类型仅有内建类型及 STL 中的迭代器和函数对象类型。对其他任何类型,请遵循本 Item 的建议,并用传引用给 const 取代传值。

      Things to Remember

      ·用传引用给 const 取代传值。典型情况下它更高效而且可以避免切断问题。

      ·这条规则并不适用于内建类型及 STL 中的迭代器和函数对象类型。对于它们,传值通常更合适。
      
    只在总结,也许不够专业,不够全面,请大家指教。


     

  • 密码文件的管理

    2007-04-26 15:03:01

    概要:Oracle关系数据库系统以其卓越的性能获得了广泛的应用,而保证数据库的安全性是数据库管理工作的重要内容。本文是笔者在总结Oracle数据库安全管理工作的基础上,对Oracle数据库系统密码文件的创建、使用和维护作了详细的介绍,供大家参考。

    关键词:Oracle数据库 密码文件

      在Oracle数据库系统中,用户如果要以特权用户身份(INTERNALSYSDBASYSOPER)登录Oracle数据库可以有两种身份验证的方法:即使用与操作系统集成的身份验证或使用Oracle数据库的密码文件进行身份验证。因此,管理好密码文件,对于控制授权用户从远端或本机登录Oracle数据库系统,执行数据库管理工作,具有重要的意义。

      Oracle数据库的密码文件存放有超级用户INTERNALSYS的口令及其他特权用户的用户名/口令,它一般存放在ORACLE_HOMEDATABASE目录下。

      一、 密码文件的创建:

      在使用Oracle Instance Manager创建一数据库实例的时侯,在ORACLE_HOMEDATABASE目录下还自动创建了一个与之对应的密码文件,文件名为PWDSID.ORA,其中SID代表相应的Oracle数据库系统标识符。此密码文件是进行初始数据库管理工作的基础。在此之后,管理员也可以根据需要,使用工具ORAPWD.EXE手工创建密码文件,命令格式如下:

    C: >ORAPWD FILE= FILENAME > PASSWORD
    =
    PASSWORD ENTRIES=< MAX_USERS >



      各命令参数的含义为:

      FILENAME:密码文件名;

      PASSWORD:设置INTERNALSYS帐号的口令;

      MAX_USERS:密码文件中可以存放的最大用户数,对应于允许以SYSDBASYSOPER权限登录数据库的最大用户数。由于在以后的维护中,若用户数超出了此限制,则需要重建密码文件,所以此参数可以根据需要设置得大一些。

      有了密码文件之后,需要设置初始化参数REMOTE_LOGIN_PASSWORDFILE来控制密码文件的使用状态。

      二、 设置初始化参数REMOTE_LOGIN_PASSWORDFILE

      在Oracle数据库实例的初始化参数文件中,此参数控制着密码文件的使用及其状态。它可以有以下几个选项:

      NONE:指示Oracle系统不使用密码文件,特权用户的登录通过操作系统进行身份验证;

      EXCLUSIVE:指示只有一个数据库实例可以使用此密码文件。只有在此设置下的密码文件可以包含有除INTERNALSYS以外的用户信息,即允许将系统权限SYSOPERSYSDBA授予除INTERNALSYS以外的其他用户。

      SHARED:指示可有多个数据库实例可以使用此密码文件。在此设置下只有INTERNALSYS帐号能被密码文件识别,即使文件中存有其他用户的信息,也不允许他们以SYSOPERSYSDBA的权限登录。此设置为缺省值。
    ----
    REMOTE_LOGIN_PASSWORDFILE参数设置为EXCLUSIVESHARED情况下,Oracle系统搜索密码文件的次序为:在系统注册库中查找ORA_SID_PWFILE参数值(它为密码文件的全路径名);若未找到,则查找ORA_PWFILE参数值;若仍未找到,则使用缺省值ORACLE_HOMEDATABASEPWDSID.ORA;其中的SID代表相应的Oracle数据库系统标识符。

    三、 向密码文件中增加、删除用户:

      当初始化参数REMOTE_LOGIN_PASSWORDFILE设置为EXCLUSIVE时,系统允许除INTERNALSYS以外的其他用户以管理员身份从远端或本机登录到Oracle数据库系统,执行数据库管理工作;这些用户名必须存在于密码文件中,系统才能识别他们。由于不管是在创建数据库实例时自动创建的密码文件,还是使用工具ORAPWD.EXE手工创建的密码文件,都只包含INTERNALSYS用户的信息;为此,在实际操作中,可能需要向密码文件添加或删除其他用户帐号。

      由于仅被授予SYSOPERSYSDBA系统权限的用户才存在于密码文件中,所以当向某一用户授予或收回SYSOPERSYSDBA系统权限时,他们的帐号也将相应地被加入到密码文件或从密码文件中删除。由此,向密码文件中增加或删除某一用户,实际上也就是对某一用户授予或收回SYSOPERSYSDBA系统权限。

      要进行此项授权操作,需使用SYSDBA权限(或INTERNAL帐号)连入数据库,且初始化参数REMOTE_LOGIN_PASSWORDFILE的设置必须为EXCLUSIVE。具体操作步骤如下:

      创建相应的密码文件;

      设置初始化参数REMOTE_LOGIN_PASSWORDFILEEXCLUSIVE

      使用SYSDBA权限登录:

    CONNECT SYSinternal_user_passsword AS SYSDBA



      启动数据库实例并打开数据库;

      创建相应用户帐号,对其授权(包括SYSOPERSYSDBA):
      授予权限:GRANT SYSDBA TO user_name
      收回权限:REVOKE SYSDBA FROM user_name

      现在这些用户可以以管理员身份登录数据库系统了;

      四、 使用密码文件登录:

      有了密码文件后,用户就可以使用密码文件以SYSOPERSYSDBA权限登录Oracle数据库实例了,注意初始化参数REMOTE_LOGIN_PASSWORDFILE应设置为EXCLUSIVESHARED。任何用户以SYSOPERSYSDBA的权限登录后,将位于SYS用户的Schema之下,以下为两个登录的例子:

      1. 以管理员身份登录:

      假设用户scott已被授予SYSDBA权限,则他可以使用以下命令登录:

    CONNECT scotttiger AS SYSDBA



      2. INTERNAL身份登录:

    CONNECT INTERNALINTERNAL_PASSWORD

    五、密码文件的维护:

      1. 查看密码文件中的成员:

      可以通过查询视图V$PWFILE_USERS来获取拥有SYSOPERSYSDBA系统权限的用户的信息,表中SYSOPERSYSDBA列的取值TRUEFALSE表示此用户是否拥有相应的权限。这些用户也就是相应地存在于密码文件中的成员。

      2. 扩展密码文件的用户数量:

      当向密码文件添加的帐号数目超过创建密码文件时所定的限制(即ORAPWD.EXE工具的MAX_USERS参数)时,为扩展密码文件的用户数限制,需重建密码文件,具体步骤如下:

      a) 查询视图V$PWFILE_USERS,记录下拥有SYSOPERSYSDBA系统权限的用户信息;

      b) 关闭数据库;

      c) 删除密码文件;

      d) ORAPWD.EXE新建一密码文件;

      e) 将步骤a中获取的用户添加到密码文件中。

      3. 修改密码文件的状态:

      密码文件的状态信息存放于此文件中,当它被创建时,它的缺省状态为SHARED。可以通过改变初始化参数REMOTE_LOGIN_PASSWORDFILE的设置改变密码文件的状态。当启动数据库事例时,Oracle系统从初始化参数文件中读取REMOTE_LOGIN_PASSWORDFILE参数的设置;当加载数据库时,系统将此参数与口令文件的状态进行比较,如果不同,则更新密码文件的状态。若计划允许从多台客户机上启动数据库实例,由于各客户机上必须有初始化参数文件,所以应确保各客户机上的初始化参数文件的一致性,以避免意外地改变了密码文件的状态,造成数据库登陆的失败。

      4. 修改密码文件的存储位置:

      密码文件的存放位置可以根据需要进行移动,但作此修改后,应相应修改系统注册库有关指向密码文件存放位置的参数或环境变量的设置。

      5. 删除密码文件:

      在删除密码文件前,应确保当前运行的各数据库实例的初始化参数REMOTE_LOGIN_PASSWORDFILE皆设置为NONE。在删除密码 

  • 如何配置数据库联结

    2007-03-27 10:04:45

    如何配置才能使客户端连到数据库:
          要使一个客户端机器能连接oracle数据库,需要在客户端机器上安装oracle的客户端软件,唯一的例外就是java连接数据库的时候,可以用jdbc thin模式,不用装oracle的客户端软件。加入你在机器上装了oracle数据库,就不需要在单独在该机器上安装oracle客户端了,因为装oracle数据库的时候会自动安装oracle客户端。
          用过sql server数据库然后又用oracle的新手可能会有这样的疑问:问什么我用sql server的时候不用装sql server的客户端呢?原因很简单,sql server也是microsoft的,它在操作系统中集成了sql server客户端,如果microsoft与oracle有协议,将oracle客户端也集成到操作系统中,那我们也就不用在客户端机器装oracle客户端软机就可访问数菘饬耍还夂孟袷遣豢赡苁迪值氖虑椤?
          也有的人会问:为什么在sql server中没有侦听端口一说,而在oracle中要配置侦听端口?其实sql server中也有侦听端口,只不过microsoft将侦听端口固定为1433,不允许你随便改动,这样给你一个错觉感觉sql server中没有侦听端口,咳,microsoft把太多的东西都封装到黑盒子里,方便使用的同时也带来的需要副作用。而oracle中的侦听端口直接在配置文件中,允许随便改动,只不过无论怎样改动,要与oracle服务器端设置的侦听端口一致。
      
    好,言归正传,我们如何做才能使客户端机器连接到oracle数据库呢?
    A.  安装相关软件
    B.  进行适当的配置
      
    A.在适当的位置安装适当的软件:
    在客户端机器:
          1.在客户端机器上安装ORACLE的Oracle Net通讯软件,它包含在oracle的客户端软件中。
          2.正确配置了sqlnet.ora文件:   
               NAMES.DIRECTORY_PATH = (TNSNAMES, ….)      
               NAMES.DEFAULT_DOMAIN=DB_DOMAIN   
             一般情况下我们不用NAMES.DEFAULT_DOMAIN参数。如果想不用该参数用#注释掉或将该参数删除即可,对于NAMES.DIRECTORY_PATH参数采用缺省值即可,对于NAMES.DEFAULT_DOMAIN参数有时需要注释掉,在下面有详细解释。
          3.正确配置了tnsname.ora文件
      
    在服务器端机器:
        1.保证listener已经启动
        2.保证数据库已经启动。
       如果数据库没有启动,用:
             Oracle 9i:
                  dos>sqlplus “/ as sysdba”
                  sqlplus> startup
            Oracle 8i:
                  dos>svrmgrl
                  svrmgrl>connect internal
                  svrmgrl>startup
           命令启动数据库
          如果listener没有启动,用:
          lsnrctl start [listener name]
          lsnrctl status [listener name]
          命令启动listener
      
    B.进行适当的配置
        如何正确配置tnsname.ora文件:
         可以在客户端机器上使用oracle Net Configuration Assistant或oracle Net Manager图形配置工具对客户端进行配置,该配置工具实际上修改tnsnames.ora文件。所以我们可以直接修改tnsnames.ora文件,下面以直接修改tnsnames.ora文件为例:
          该文件的位置为: …/network/admin/tnsnames.ora     (for windows)
               …/network/admin/tnsnames.ora     (for unix)
          此处,假设服务器名为testserver,服务名为orcl.testserver.com,使用的侦听端口为1521,则tnsnams.ora文件中的一个test网络服务名(数据库别名)为:
    test =
       (DEscrīptION=
         (ADDRESS_LIST=
           (ADDRESS=(PROTOCOL=TCP)(HOST=testserver)(PORT=1521))
           )
         (CONNECT_DATA=(SERVICE_NAME=orcl.testserver.com)
         )
       )
       此处的笑脸为)。
       红色的内容为需要根据实际情况修改的内容,现解释如下:
        PROTOCOL:客户端与服务器端通讯的协议,一般为TCP,该内容一般不用改。
         HOST:数据库侦听所在的机器的机器名或IP地址,数据库侦听一般与数据库在同一个机器上,所以当我说数据库侦听所在的机器一般也是指数据库所在的机器。在UNIX或WINDOWS下,可以通过在数据库侦听所在的机器的命令提示符下使用hostname命令得到机器名,或通过ipconfig(for WINDOWS) or ifconfig(for UNIX)命令得到IP地址。需要注意的是,不管用机器名或IP地址,在客户端一定要用ping命令ping通数据库侦听所在的机器的机器名,否则需要在hosts文件中加入数据库侦听所在的机器的机器名的解析。
       PORT:数据库侦听正在侦听的端口,可以察看服务器端的listener.ora文件或在数据库侦听所在的机器的命令提示符下通过lnsrctl status [listener name]命令察看。此处Port的值一定要与数据库侦听正在侦听的端口一样。
       SERVICE_NAME:在服务器端,用system用户登陆后,sqlplus> show parameter service_name命令察看。
      
    如何利用配置的网络服务名连接到数据库:
         用sqlplus程序通过test网络服务名进行测试,如sqlplus system/manager@test。如果不能连接到数据库,则在tnsname.ora文件中的test网络服务名(net service)后面加上Oracle数据库的DB_Domain参数值,通过用sqlplus> show parameter db_domain命令察看。此处db_domain参数值为testserver.com,将其加到网络服务名后面,修改后的tnsname.ora中关于该网络服务名的内容为:
    test.testserver.com =
       (DEscrīptION=
         (ADDRESS_LIST=
           (ADDRESS=(PROTOCOL=TCP)(HOST=testserver)(PORT=1521))
         )
         (CONNECT_DATA=(SERVICE_NAME=orcl.testserver.com)
         )
       )
       此处的笑脸为)。
        用sqlplus程序通过test.testserver.com网络服务名测试,如sqlplus system/manager@test.testserver.com。
      
        关于为什们在网络服务名后面加db_domain参数,需要了解sql*plus连接数据库的原理,我在后面解决12154常见故障中给出了详细的说明。
      
    如果上面的招数还不奏效的话,只好用一下乾坤大挪移了。
    将客户端的网络服务名部分
    test.testserver.com =
       (DEscrīptION=
         (ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=testserver)(PORT=1521))
         )
         (CONNECT_DATA=(SERVICE_NAME=orcl.testserver.com)
         )
       )
    此处的笑脸为)。
         拷贝到服务器的tnsnames.ora文件中。然后再服务器端用sqlplus system/manager@test.testserver.com连接到数据库。
    如果能连接成功,说明你的客户端与服务器端的网络有问题。
         如果连接不成功,用前面的部分检查网络服务名部分部分是否正确,如果确信网络服务名部分正确而且所有的客户端都连不上数据库则可能为系统TCP/IP或Oracle系统有问题,建议重新安装数据库。
      
    常见故障解决办法:
    TNS-12154 (ORA-12154):TNS:could not resolve service name
         该错误表示用于连接的网络服务名在tnsnames.ora文件中不存在,如上面的tnsnames.ora中的网络服务名只有test,假如用户在连接时用sqlplus system/manager@test1则就会给出TNS-12154错误。
         要注意的是,有时即使在tnsnames.ora文件中有相应的网络服务名,可是用该网络服务名连接时还会出错,出现这种情况的典型配置如下(在客户端的机器上):
    sqlnet.ora文件:
          NAMES.DIRECTORY_PATH = (TNSNAMES, ….)
          NAMES.DEFAULT_DOMAIN = server.com
    tnsnames.ora文件:
          test =
             (DEscrīptION=
                  (ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=testserver)(PORT=1521))
                  )
                 (CONNECT_DATA=(SERVICE_NAME=orcl.testserver.com)
                 )
            )
        此处的笑脸为)。
       
       sql*plus运行基本机理:
         在用户输入sqlplus system/manager@test后,sqlplus程序会自动到sqlnet.ora文件中找NAMES.DEFAULT_DOMAIN参数,假如该参数存在,则将该参数中的值取出,加到网络服务名的后面,即此例中你的输入由sqlplus system/manager@test自动变为sqlplus system/manager@test.server.com ,然后再到tnsnames.ora文件中找test.server.com网络服务名,这当然找不到了,因为该文件中只有test网络服务名,所以报错。解决的办法就是将sqlnet.ora文件中的NAMES.DEFAULT_DOMAIN参数注释掉即可,如#NAMES.DEFAULT_DOMAIN = server.com。假如NAMES.DEFAULT_DOMAIN参数不存在,则sqlplus程序会直接到tnsnames.ora文件中找test网络服务名,然后取出其中的host,port,tcp,service_name,利用这些信息将连接请求发送到正确的数据库服务器上。
         另外原则上tnsnames.ora中的配置不区分大小写,但是我的确遇到区分大小写的情况,所以最好将使用的网络服务与tnsnames.ora中配置的完全一样。
      
    ORA-12514: TNS:listener could not resolve SERVICE_NAME given in connect Descrīptor.
       该错误表示能在tnsnames.ora中找到网络服务名,但是在tnsnames.ora中指定的SERVICE_NAME与服务器端的SERVICE_NAME不一致。解决的办法是修改tnsnames.ora中的SERVICE_NAME。
      
    易混淆术语介绍:
    Db_name:对一个数据库(Oracle database)的唯一标识,该数据库为第一章讲到的Oracle database。这种表示对于单个数据库是足够的,但是随着由多个数据库构成的分布式数据库的普及,这种命令数据库的方法给数据库的管理造成一定的负担,因为各个数据库的名字可能一样,造成管理上的混乱。为了解决这种情况,引入了Db_domain参数,这样在数据库的标识是由Db_name和Db_domain两个参数共同决定的,避免了因为数据库重名而造成管理上的混乱。这类似于互连网上的机器名的管理。我们将Db_name和Db_domain两个参数用’.’连接起来,表示一个数据库,并将该数据库的名称称为Global_name,即它扩展了Db_name。Db_name参数只能由字母、数字、’_’、’#’、’$’组成,而且最多8个字符。
      
    Db_domain:定义一个数据库所在的域,该域的命名同互联网的’域’没有任何关系,只是数据库管理员为了更好的管理分布式数据库而根据实际情况决定的。当然为了管理方便,可以将其等于互联网的域。
      
    Global_name:对一个数据库(Oracle database)的唯一标识,oracle建议用此种方法命令数据库。该值是在创建数据库是决定的,缺省值为Db_name. Db_domain。在以后对参数文件中Db_name与Db_domain参数的任何修改不影响Global_name的值,如果要修改Global_name,只能用ALTER DATABASE RENAME GLOBAL_NAME TO <db_name.db_domain>命令进行修改,然后修改相应参数。
      
    Service_name:该参数是oracle8i新引进的。在8i以前,我们用SID来表示标识数据库的一个实例,但是在Oracle的并行环境中,一个数据库对应多个实例,这样就需要多个网络服务名,设置繁琐。为了方便并行环境中的设置,引进了Service_name参数,该参数对应一个数据库,而不是一个实例,而且该参数有许多其它的好处。该参数的缺省值为Db_name. Db_domain,即等于Global_name。一个数据库可以对应多个Service_name,以便实现更灵活的配置。该参数与SID没有直接关系,即不必Service name 必须与SID一样。
      
    Net service name:网络服务名,又可以称为数据库别名(database alias)。是客户端程序访问数据库时所需要,屏蔽了客户端如何连接到服务器端的细节,实现了数据库的位置透明的特性。
  • ebs R12 杂收杂发 代码

    DECLARE   l_iface_rec        mtl_transactions_interface%ROWTYPE;   l_iface_lot_rec    mtl_transactio...
    • lyyong2007
    • lyyong2007
    • 2015年11月23日 20:58
    • 286

    伤寒杂病论白话文

    白话版伤寒论 1、太阳病的基本症候特征,是脉象浮、头痛、项部拘急不舒、畏寒。 2、太阳病,发热,汗出,畏风,头痛,项部拘急不舒,脉象浮缓的,就叫做中风。 3、太阳病,已经发热,或者还未发热,畏冷...
    • q123456789098
    • q123456789098
    • 2016年05月22日 17:48
    • 17240

    未来不迎,当下不杂,过往不恋

    罗辑思维出的《罗辑思维:成大事者不纠结 》 ,书中大多数内容都听过,当故事听,很少会细细的回味。微信读书里看到了这本书,随手翻翻,发现这本书值得仔细玩味。尤其关于“纠结”这个话题,罗辑思维给出了良方,...
    • pan_tian
    • pan_tian
    • 2016年03月24日 13:47
    • 4009

    android 教您打造属于自己的注解(@interface) 优雅与便捷并行

    我们平常在开发web项目的时候,经常会使用SSH来构建我们的项目,也有很多程序猿喜欢用注解来减少代码量。但是各位屌丝程序猿们,大家有没有想过这个注解是怎么实现的呢。我们又该如何写出像注解这么优雅的代码...
    • u013598660
    • u013598660
    • 2015年04月22日 17:30
    • 1594

    【数据库】一些杂杂的概念

    关系型数据库:         关系数据库,是建立在关系模型基础上的数据库,借助于集合代数等数学概念和方法来处理数据库中的数据。关系数据库中的核心内容是关系即二维表。 关系模型:      ...
    • youyaecho
    • youyaecho
    • 2016年06月27日 20:02
    • 173

    Eclipse中web项目部署至Tomcat步骤

    Eclipse中web项目部署至Tomcat步骤
    • master_yao
    • master_yao
    • 2017年07月03日 10:29
    • 1083

    linux的混杂设备驱动

    介绍混杂设备 驱动
    • qqliyunpeng
    • qqliyunpeng
    • 2016年10月20日 17:28
    • 568

    POJ杂题(水杂)

    poj 3673Cow Multiplication 题目链接 题目大意: 解题思路:二重循环累加及行了 代码链接 poj 3749破译密码题目链接 题目大意:中文题,意思很好理解。 解题思路:直接...
    • twobqn123
    • twobqn123
    • 2013年12月27日 22:37
    • 392

    摄像头图像质量常用指标的测试方法

    自动曝光主要是对某可选区域内画面亮度分量(y 信号)进行评估,若y 偏小,增大曝光量,反之减少。...
    • wangbaodong070411209
    • wangbaodong070411209
    • 2017年11月28日 15:18
    • 311

    小杂程序(给自己看的)

    #include #include #include "FreeImage.h" float angle=0.0; float angle1=0.0; float r=0.0; float xr...
    • jiuweihuaxiang
    • jiuweihuaxiang
    • 2013年11月27日 18:17
    • 325
    内容举报
    返回顶部
    收藏助手
    不良信息举报
    您举报文章:
    举报原因:
    原因补充:

    (最多只允许输入30个字)