My C++ Blog

  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  14 随笔 :: 0 文章 :: 0 评论 :: 0 Trackbacks

2008年12月3日 #

 

1. 原理
 
首先我们来看一下linker的 /subsystem 选项
 
该选项的语法形式如下:
/SUBSYSTEM:{CONSOLE|EFI_APPLICATION|EFI_BOOT_SERVICE_DRIVER|
EFI_ROM|EFI_RUNTIME_DRIVER|NATIVE|POSIX|WINDOWS|WINDOWSCE}
[,major[.minor]]
 
这个链接选项告诉操作系统如何运行可执行文件
 
CONSOLE:
win32 字符模式应用程序,此种类型的应用程序在运行的时候会产生一个类似DOS
窗口的控制台窗口,如果在应用程序的主函数为main()或者wmain(),在默认情况下
该应用程序就是一个控制台应用程序
 
Extensible Firmware Interface
和CPU具体架构相关的一个参数选项,并不常用,在这里暂不详细介绍.
如果对此有兴趣的可以访问intel主页来查看相关内容
 
NATIVE;
设备驱动器选项,如果/DRIVER:WDM选项被设定的话,该链接选项(NATIVE)就为默认选项
 
POSIX:
在windows NT 种运行在POSIX子系统上的应用程序
 
WINDOWS:
该类型的应用程序不产生console窗口,该类型的应用程序的窗口由用户自己创建,简而言之
就是一个标准的Win32 application,其入口地址为WinMain()函数或者wWinMain()函数的地址
如果你在应用程序种定义的主函数为WinMain或者wWinMain,在默认情况下该应用程序就是一个
Win32 Application !
 
WINDOWSCE:
运行在windows CE上的应用程序
 
major and minor (optional):
主版本号和次版本号,该选项为可选,该选项为0~65535之间的十进制整数
 
从上面可以看出如果我们建立一个win32 console application的话,linker的/subsystem选项应该为
CONSOLE,可以在VC开发环境的project->setting->link->project option中看到!
 
接下来我们再看看应用程序是如何运行的!
我们知道用VC编写的程序,运行的时候是需要 C\C++运行库支持的.当我们运行一个C/C++程序的时候
链接器会首先寻找应用程序的启动函数,例如:
如果你建立了一个console程序的话,编译器得链接开关会是以下这种形式
/subsystem:"console" /entry:"mainCRTStartup" (ANSI)
/subsystem:"console" /entry:"wmainCRTStartuup" (UNICODE)
 
如果你建立了一个win32 application,编译器得链接开关则会是一下形式
/subsystem:"windows" /entry:"WinMain" (ANSI)
/sbusystem:"windows" /entry:"wWinMain" (UINCODE)
 
上面的两种形式可以再project->setting->link->project option中看到
上面的subsystem和entry并不需要都设置,如果你只设置了/subsystem:"console"
的话,那么默认的entry开关在默认情况下应为/entry:"mainCRTStartup"
反之,如果你在应用程序中定义了main函数的话,默认情况下,你的/subsystem开关
应该为/system:"console"
 

在默认情况下/subsystem 和/entry开关是匹配的,也就是
console对应mainCRTStartup或者wmainCRTStartup
windows对应WinMain或者wWinMain
 
但是我们也可以通过手动改动的方式使他们不匹配
 

例如我们可以这样改动
 
#pragma comment( linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" ) // 设置入口地址
 

int main(int argc, char* argv[])
{
MessageBox(NULL, "hello", "Notice", MB_OK);
return 0;
}
 
在默认情况下链接器看到/subsystem下是windows选项的时候,它会自动寻找WinMain或者wWinMain
 
但我们强制指定入口地址,这样运行程序的时候默认的console窗口就会隐藏!
 

上面是在代码中使用#pragma指令来设置,还有一种就是直接在开发环境的
project->setting->link->project option中手工改动!
 
在明白了通过/subsystem选项可以控制链接程序的类型后,我们可以根据需要来生成具有控制台的Windows窗口程序。
 

2. 生成具有console窗口的Win32窗口程序(不使用MFC)

 
使用Visual Studio.Net 2003建立一个Win 32窗口项目(不使用MFC):Win32WithConsole,在项目的属性对话框中,依次选择‘配置属性’->‘链接器’->‘system’,在‘子系统’一项中,将‘Windows (/SUBSYSTEM:WINDOWS)’改为‘控制台(/SUBSYSTEM:CONSOLE)’ 。现在,该项目所生成的可执行文件的入口函数将是mainCRTStartup或是wmainCRTStartup,我们只需要定义一个main函数,并进行适当的入口参数转换,同时在该main函数中调用原来的入口函数_tWinMain即可。下面是Win32WithConsole.cpp文件中我们需要添加的main函数:
 
int _tmain(int argc, _TCHAR* argv[])
{
 
 HINSTANCE hInstance = (HINSTANCE)GetModuleHandle(NULL);
 HINSTANCE hPreInstance = NULL;
 TCHAR szCmdLine[1024];
 szCmdLine[0] = 0;
 LPTSTR lpCmdLine = szCmdLine;
 for ( int i = 1; i < argc; i++ )
 {
  if ( i > 1 )
  {
   _tcscpy(lpCmdLine, _T(" "));
   lpCmdLine = lpCmdLine + _tcslen(_T(" "));
  }
  _tcscpy(lpCmdLine, argv[i]);
  lpCmdLine = lpCmdLine + _tcslen(argv[i]);
 }
 lpCmdLine = szCmdLine;
 int nCmdShow = SW_SHOWNORMAL;
 
 
 int ret = _tWinMain(hInstance, hPreInstance, lpCmdLine, nCmdShow);
 return 0;
}
 
可以参考附加的文件Win32WithConsole.rar。
 

3.生成具有console窗口的MFC窗口应用程序

 
使用向导生成一个多文档的MFC应用程序,MFCWithConsole。同样,将该项目配置为‘控制台(/SUBSYSTEM:CONSOLE)’,下面我们需要找到MFC应用程序的入口函数。通过调试该程序,我们可以发现,MFC框架通过AfxWinMain来调用项目中全局CWinApp变量theApp的InitInstance成员函数,从而启动整个应用程序。因此,我们可以使用两种方式来显式调用AfxWinMain函数,从而创建一个具有console窗口的MFC窗口应用程序。
 
第一种方法是在MFCWithConsole项目中加入AfxWinMain的定义,该函数的定义可以从winmain.cpp文件中,下面是其具体内容:
 
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
 LPTSTR lpCmdLine, int nCmdShow)
{
 ASSERT(hPrevInstance == NULL);
 
 int nReturnCode = -1;
 CWinThread* pThread = AfxGetThread();
 CWinApp* pApp = AfxGetApp();
 
 // AFX internal initialization
 if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
  goto InitFailure;
 
 // App global initializations (rare)
 if (pApp != NULL && !pApp->InitApplication())
  goto InitFailure;
 
 // Perform specific initializations
 if (!pThread->InitInstance())
 {
  if (pThread->m_pMainWnd != NULL)
  {
   TRACE(traceAppMsg, 0, "Warning: Destroying non-NULL m_pMainWnd\n");
   pThread->m_pMainWnd->DestroyWindow();
  }
  nReturnCode = pThread->ExitInstance();
  goto InitFailure;
 }
 nReturnCode = pThread->Run();
 
InitFailure:
#ifdef _DEBUG
 // Check for missing AfxLockTempMap calls
 if (AfxGetModuleThreadState()->m_nTempMapLock != 0)
 {
  TRACE(traceAppMsg, 0, "Warning: Temp map lock count non-zero (%ld).\n",
   AfxGetModuleThreadState()->m_nTempMapLock);
 }
 AfxLockTempMaps();
 AfxUnlockTempMaps(-1);
#endif
 
 AfxWinTerm();
 return nReturnCode;
}
 

第二种方法是显式加载mfc71d.dll或是mfc71.dll,并调用其中的AfxWinMain函数。不过,这两个动态链接库都是使用NONAME的方式来导出函数的,因此只能通过函数序号的方式来调用AfxWinMain函数。通过在IDA Pro中对这两个动态链接库进行反编译,我们可以发现AfxWinMain在mfc71d.dll中的序号为1589,而在mfc71.dll中的序号为1207,下面即是使用动态链接库的方式调用AfxWinMain的方法。
 
// wrong
// typedef int __stdcall (*MYPROC)(HINSTANCE, HINSTANCE,LPTSTR, int);
typedef int (__stdcall *AFXWINMAIN_FUNC)(HINSTANCE, HINSTANCE,LPTSTR, int);
 
#ifdef _DEBUG
#define MFC_DLL_NAME _T("mfc71d.dll")
#define AFXWINMAIN_ORDINAL 1589
#else
#define MFC_DLL_NAME _T("mfc71.dll")
#define AFXWINMAIN_ORDINAL 1207
#endif
 

int _tmain()
{
 
#ifndef _AFXDLL
 char _afxInitAppState = (char)(AfxInitialize(FALSE, _MFC_VER), atexit(&_AfxTermAppState));
#else
 char _afxInitAppState = (char)(AfxInitialize(FALSE, _MFC_VER));
#endif
 
 HINSTANCE hinstLib = LoadLibrary(MFC_DLL_NAME);
 AFXWINMAIN_FUNC ProcAdd;
 int ret = 0;
 
 // If the handle is valid, try to get the function address.
 
 if (hinstLib != NULL)
 {
  ProcAdd = (AFXWINMAIN_FUNC) GetProcAddress(hinstLib, MAKEINTRESOURCE(AFXWINMAIN_ORDINAL));
 
  // If the function address is valid, call the function.
 
  if (NULL != ProcAdd)
  {
 
   HINSTANCE hInstance = (HINSTANCE)GetModuleHandle(NULL);
   HINSTANCE hPrevInstance = NULL;
   LPTSTR lpCmdLine = NULL;
   int nCmdShow = SW_SHOWNORMAL;
   ret = (*ProcAdd)(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
  }
 
  // Free the DLL module.
 
  FreeLibrary(hinstLib);
 }
 
 return ret;
 
}
 

需要说明的是,在GetProcAddress函数中,我们需要调用MAKEINTRESOURCE来将函数序号进行转化。 另外,对于AFXWINMAIN_FUNC的定义一定要加上__stdcall的调用约定,因为AfxWinMain是采用__stdcall方式来调用的。最后还有一点需要注意,我们需要使用AfxInitialize来注册退出函数,否则程序将不能正确退出
posted @ 2008-12-03 20:04 IP 阅读(455) | 评论 (0)编辑 收藏

2008年11月8日 #

C++/OPP/OOD系列:
层级一:语法/语意(C++)
[Lippman2000] Essential C++ [推荐]
Essential C++,by Stanley B. Lippman Addison Wesley Longman 2000,276 pages
Essential C++ 中文版 ,侯俊杰 译,282页
[Gregory95] C++:The Core Language
C++:The Core Language by Gregory Satir 1995 O'Reilly
C++语言核心,张铭泽 译 ,236页
[Deitel98] The Complete C++ Training Course
The Complete C++ Training Course 2/e by Harvey M.Deitel 1998 Prentice Hall
C++大学教程(第二版),邱仲潘等 译,816页
[Stevens2000] Standard C++ Bible
Standard C++ Bible 2000 Al Stevens IDG
标准C++宝典,林丽闽等 译,766页
[Eckel2000] Thinking in C++ [第二版翻译不大好,建议看原版]
Thinking in C++ 2/e Bruce Eckel 2000 1470 pages Prentice Hall
C++ 编程思想,刘宗田等 译,420页
[Lippman98] C++Primer [有点C++基础再看,强烈推荐!]
C++ Primer,3rd Editoin,by Stanley Lippman and Josee Lajoie
Addison Wesley Longman,1998 1237 pages
C++ Primer 中文版,侯俊杰 译,1999,1237页
[Struostrup2000] [专家级,需要一定水平]
The C++ Programming Language,Special Editoin,by Bjarne Stroustrup
Addison Wesley Longman,2000,1017 pages
C++程序语言经典本,裘宗燕 译,机械工业出版社 1999
[ANSI C++] C++规格书 1998.9.1 PDF格式
ANSI C++ 1996 Draft

层级二:专家经验(C++/OOP)
[Meyers96] More Effective C++ [不错]
More Effective C++,by Scott Meyers,Addison Wesley,1996,318pages
More Effective C++中文版,侯俊杰,培生 2000. 318页
[Meyers98] Effective C++ [推荐]
Effective C++,Second Edition,by Scott Meyers,Addison Wesley Longman,1998.256pages
Effective C++ 2/e 中文版,侯俊杰,培生 2000.256页
[Sutter99] Exceptional C++ [不错]
Exceptional C++,by Herb Sutter,Addison Wesley Longman,2000.208pages
Exceptional C++中文版,侯俊杰,培生 2000.248页
[Sutter2001]More Exceptional C++
More Exceptional C++ by Herb Sutter,Addison Wesley Longman,2001.
层级三:底层机制(C++ Object Model)
[Ellis90] The Annotated C++ Reference Manual
The Annotated C++ Reference Manual,by Margaret A.Ellis and Bjarne Stroustrup
Addison Wesley Longman,1990,447 pages.
[Lippman96] Inside the C++ Object Model [good]
Inside the C++ Object Model,by Stanley Lippman,Addison Wesley Longman,1996,280pages
深度探索C++物件模型,侯俊杰 译

层级四:设计观念的复用(C++/Patterns)
[Gamma95] Design Patterns:Elements of Reusable Object Oriented Software, [强烈推荐]
by Erich Gamma,Richard Helm,Ralph Johnson,and John Vlissides,Addison Wesley,1995.395pages
设计模式,李英军等译,机械工业出版社,2000.254页
[Alex2001]Modern C++ Design: Generic Programming and Design Patterns Applied [比较深奥]
by Andrei Alexandrescu,Addison-Wesley,2001,352Paper

Genericity/STL系列:
第一个境界是使用STL:
[Josuttis99]:The C++ Standard Library -A Tutorial and Reference,by Nicolai M.Josuttis,
Addison Wesley 1999.799pages
中文版,侯捷译 [听说还可以]
第二个境界是了解泛型技术的内涵与STL的学理:
[Austern98]:Generic Programming and the STL -Using and Extending the C++ Standard
Template library,by Matthew H.Austern,Addison Wesley 1998.548page

第三个境界是扩充STL:
[Stepanov2001]:C++ Standard Template Library by P.J.Plauger,Alexander A.Stepanov,
Meng Lee,David R.Musser,Prentice Hall 2001
这些就是你应该看的书,如果你想成为高手。
当然,因为很多书的内容会有所重叠,所以不是每本都必看的。在每个层次里看1~2本就差不多了,当然,有时间多看几本有利无弊

 原文地址 http://blog.sina.com.cn/s/blog_4b0ca80c010007ds.html
posted @ 2008-11-08 02:07 IP 阅读(322) | 评论 (0)编辑 收藏

通过写xml配置文件方式实现多语言
--------------------------------------------------------------------------------
那就把字符串资源放在xml文件中,载入字符串的时候从xml读取
--------------------------------------------------------------------------------
读取xml文件
--------------------------------------------------------------------------------
能给个例子么,你们说的我知道
--------------------------------------------------------------------------------
读写XML文件可用MSXML4
//引入
#import "C:\WINNT.0\system32\msxml4.dll"
//DOM的DOC对象定义
MSXML2::IXMLDOMDocumentPtr   pXMLDoc;
//解析
void CMainFrame::OnParsexmldoc()
{
//加载文件
pXMLDoc->load("c:\\XML\\test.xml");
//在树中查找名为City的节点,"//"表示在任意一层查找
MSXML2::IXMLDOMElementPtr  childNode ;
childNode = (MSXML2::IXMLDOMElementPtr)(pXMLDoc->selectSingleNode("//AUTHOR"));
//得到节点类型
MSXML2::DOMNodeType nodeType;
childNode->get_nodeType(&nodeType);
//节点名称
BSTR var;
CString name;
childNode->get_nodeName(&var);
name = (char*)(_bstr_t)var;
//节点值
VARIANT varVal;
childNode->get_nodeTypedValue(&varVal);
CString strValue = (char*)(_bstr_t)varVal;
//节点属性,放在链表中
MSXML2::IXMLDOMNamedNodeMapPtr pAttrs = NULL;
MSXML2::IXMLDOMNodePtr pAttrItem;
childNode->get_attributes(&pAttrs);
long nCount ;
pAttrs->get_length(&nCount);
for(int i = 0 ; i < nCount ; i++)
{                
pAttrs->get_item(i,&pAttrItem);
//我们可以通过函数get_nodeName,get_nodeTypedValue得到属性名和属性值
//也可以直接得到
CString strAttrName   = (char*)(_bstr_t)pAttrItem->nodeName;
CString strAttrValue  = (char*)(_bstr_t)pAttrItem->nodeTypedValue;
}
}
//创建
void CMainFrame::OnCreatexmldoc()
{
MSXML2::IXMLDOMElementPtr  xmlRoot;
 //根节点的名称为china
    pXMLDoc->raw_createElement((_bstr_t)(char*)"china", &xmlRoot);
//MSXML2::IXMLDOMNamedNodeMapPtr pAttrs = NULL;
//pAttrs->length=2;
//xmlRoot->raw_setAttributeNode(
//xmlRoot->setAttributeNode(attributes.setAttribute("fengjie","fengjie test root attribute");
xmlRoot->setAttribute("fengjie","ddd test root attribute");
pXMLDoc->raw_appendChild(xmlRoot, NULL);
MSXML2::IXMLDOMElementPtr  childNode ;        
pXMLDoc->raw_createElement((_bstr_t)(char*)"City:dd", &childNode);
childNode->Puttext("WuHan");//节点值
childNode->setAttribute("population","8,000,000");//属性名,属性值
childNode->setAttribute("area","10000");
xmlRoot->appendChild(childNode);
pXMLDoc->raw_createElement((_bstr_t)(char*)"City:xx", &childNode);
childNode->Puttext("ShangHai");
childNode->setAttribute("population","12,000,000");
childNode->setAttribute("area","12000");
xmlRoot->appendChild(childNode);
//保存到文件
//如果不存在就建立,存在就覆盖
pXMLDoc->save("c:\\XML\\he.xml");
posted @ 2008-11-08 02:06 IP 阅读(1128) | 评论 (0)编辑 收藏

http://www.firstobject.com/dn_markupmethods.htm

学习笔记

在一个目录下的搜索,即FindElem()不分先后出现顺序。

(一) 先讲一下XML中的物殊字符,手动填写时注意一下。

字符                  字符实体
&                          &amp;或&
'                          &apos;或'
>                          &gt;或>
<                          &lt;或&<
"                      &quot;或"


(二) CMarkup类的源代码。

这是目前的最新版本;

这是官网示例文件,取出里面的Markup.cpp和Markup.h,导入你的工程里面,CMarkup类就可以用了;

下载地址:http://www.firstobject.com/Markup90.zip


(三) 创建一个XML文档。

CMarkup xml;
xml.AddElem( "ORDER" );
xml.AddChildElem( "ITEM" );
xml.IntoElem();
xml.AddChildElem( "SN", "132487A-J" );
xml.AddChildElem( "NAME", "crank casing" );
xml.AddChildElem( "QTY", "1" );
xml.Save("c:\\UserInfo.xml");

效果如下:

<ORDER>
<ITEM>
<SN>132487A-J</SN>
<NAME>crank casing</NAME>
<QTY>1</QTY>
</ITEM>
</ORDER>
(四) 浏览特定元素
CMarkup xml;
xml.Load("UserInfo.xml");
while ( xml.FindChildElem("ITEM") )
{
          xml.IntoElem();
          xml.FindChildElem( "SN" );
          CString csSN = xml.GetChildData();
          xml.FindChildElem( "QTY" );
    int nQty = atoi( xml.GetChildData() );
          xml.OutOfElem();
}
(五)增加元素和属性
添加在最后面,使用的是AddElem;添加在最前面,使用InsertElem。
CMarkup xml;
xml.Load("c:\\UserInfo.xml");
xml.AddElem( "ORDER" );
xml.IntoElem(); // 进入 ORDER



        xml.AddElem( "ITEM" );
          xml.IntoElem(); // 进入 ITEM
          xml.AddElem( "SN", "4238764-A" ); //添加元素
          xml.AddElem( "NAME", "bearing" );//添加元素
          xml.AddElem( "QTY", "15" );//添加元素
          xml.OutOfElem(); // 退出 ITEM 
xml.AddElem( "SHIPMENT" );
xml.IntoElem(); // 进入 SHIPMENT
xml.AddElem( "POC" );//添加元素
xml.SetAttrib( "type", "non-emergency");//添加属性
xml.IntoElem(); // 进入 POC
xml.AddElem( "NAME", "John Smith");//添加元素
xml.AddElem( "TEL", "555-1234");//添加元素
xml.Save("c:\\UserInfo.xml");

效果如下:

<ORDER>
<ITEM>
<SN>132487A-J</SN>
<NAME>crank casing</NAME>
<QTY>1</QTY>
</ITEM>
<ITEM>
<SN>4238764-A</SN>
<NAME>bearing</NAME>
<QTY>15</QTY>
</ITEM>
<SHIPMENT>
<POC type="non-emergency">
<NAME>John Smith</NAME>
<TEL>555-1234</TEL>
</POC>
</SHIPMENT>
</ORDER>

(六) 修改元素和属性

如将POC中的属性type改成:change;

元素TEL改成:123456789

           CMarkup xml;
if (xml.Load("UserInfo.xml"))
{
      CString strUserID = _T("");
      xml.ResetMainPos();
      if (xml.FindChildElem("SHIPMENT"))
      {
       xml.IntoElem();
       if (xml.FindChildElem("POC"))
       {
        xml.IntoElem();
        CString str_type=xml.GetAttrib("type");
        MessageBox(str_type);
        xml.SetAttrib("type","change");
        strUserID = xml.GetData();
    
        if (xml.FindChildElem("TEL"))
        {
         xml.IntoElem();
         xml.SetData("123456789");
         xml.Save("UserInfo.xml");
         return;
        }
       }
      }
}

(七)删除元素:

删除SN=132487A-J的项目。

CMarkup xml;
if (xml.Load("UserInfo.xml"))
{
      CString strUserID = _T("");
      xml.ResetMainPos();
      if (xml.FindChildElem("ITEM"))
      {
       xml.IntoElem();
       CString str_sn;
       xml.FindChildElem("SN");
       str_sn=xml.GetChildData();
       if(str_sn=="132487A-J")
       {
        xml.RemoveElem();
        xml.Save("UserInfo.xml");
       }
      }
}

posted @ 2008-11-08 02:04 IP 阅读(1173) | 评论 (0)编辑 收藏

首先到http://www.firstobject.com/下载CMarkup教学版,解压后里面是一个DEMO,将Markup.h .cpp拷贝并添加到工程中,第一次编译可能会出现预编译错误,解决的方法在Markup.cpp最前面include "stdafx.h",或者关闭预编译。
  以下转自http://www.sqlite.com.cn/MySqlite/12/211.Html
//----------UserInfo.xml--------------
<?xml version="1.0" encoding="UTF-8" ?>
<UserInfo>
<UserID>luo</UserID>
<UserID>lin</UserID>
</UserInfo>


1。生成UserInfo.xml
CMarkup xml;

xml.SetDoc("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n");
xml.AddElem("UserInfo");
xml.IntoElem();
xml.AddElem("UserID","luo");
xml.AddElem("UserID","lin");
xml.OutOfElem();
xml.Save("UserInfo.xml");

2。浏览特定元素
//----查看所有UserID----
CMarkup xml;
xml.Load("UserInfo.xml");

BOOL bFind = true;
xml.ResetMainPos();
while (xml.FindChildElem("UserID");)
{

//此时接点还是父接点
CString strTagName = _T("");
CString strData = _T("");

strTagName = xml.GetChildTagName();
strData = xml.GetChildData();
TRACE("\n---tagName:%s,Data:%s--\n",strTagName,strData);

}

/********************这样也可以******************
CMarkup xml;
xml.Load("UserInfo.xml");
BOOL bFind = true;
xml.ResetMainPos();

while (xml.FindChildElem("UserID");)
{

xml.IntoElem();
CString strTagName = _T("");
CString strData = _T("");
strTagName = xml.GetTagName();
strData = xml.GetData();
TRACE("\n---tagName:%s,Data:%s--\n",strTagName,strData);
xml.OutOfElem();

}*/

/**************这样也可以**********************
CMarkup xml;
xml.Load("UserInfo.xml");

BOOL bFind = true;
xml.ResetMainPos();
xml.FindElem(); //UserInfo
xml.IntoElem();

while (xml.FindElem("UserID");)
{

CString strTagName = _T("");
CString strData = _T("");

strTagName = xml.GetTagName();
strData = xml.GetData();

TRACE("\n---tagName:%s,Data:%s--\n",strTagName,strData);

}
*/

//-------------结果----------
---tagName:UserID,Data:luo--
---tagName:UserID,Data:lin—

3。修改
//--------把UserID为”luo”改为”flypigluo”-----------
BOOL bLoadXml = false;
CMarkup xml;
bLoadXml = xml.Load("UserInfo.xml");

if (bLoadXml)
{

CString strUserID = _T("");
xml.ResetMainPos();
xml.FindElem();
xml.IntoElem();


while (xml.FindElem("UserID"))
{

strUserID = xml.GetData();
if (strUserID=="luo")
{

xml.SetData(CString("flypig")+strUserID);
xml.Save("UserInfo.xml");
break;

}

}

}

4。添加

4。1 添在最后面(使用的是AddElem)
//加在最后面
BOOL bLoadXml = false;
CMarkup xml;
bLoadXml = xml.Load("UserInfo.xml");

if (bLoadXml)
{

xml.ResetMainPos();
xml.FindElem();
xml.IntoElem();
xml.AddElem("UserID","luoluo");
xml.OutOfElem();
xml.Save("UserInfo.xml");

}

//--------原来为------

<?xml version="1.0" encoding="UTF-8" ?>
<UserInfo>
<UserID>luo</UserID>
<UserID>lin</UserID>
</UserInfo>

//---------添加后为-------------
<?xml version="1.0" encoding="UTF-8" ?>
<UserInfo>
<UserID>luo</UserID>
<UserID>lin</UserID>
<UserID>luoluo</UserID>
</UserInfo>

4。2 加在最前面(使用InsertElem)
BOOL bLoadXml = false;
CMarkup xml;
bLoadXml = xml.Load("UserInfo.xml");

if (bLoadXml)

{

xml.ResetMainPos();

xml.FindElem();
xml.IntoElem();
xml.InsertElem("UserID","AddUserIDHead");
xml.OutOfElem();
xml.Save("UserInfo.xml");

}

//----原来为—
<?xml version="1.0" encoding="UTF-8" ?>
<UserInfo>
<UserID>luo</UserID>
<UserID>lin</UserID>
</UserInfo>

 

//------修改后-----
<?xml version="1.0" encoding="UTF-8" ?>
<UserInfo>
<UserID>AddUserIDHead</UserID>
<UserID>luo</UserID>
<UserID>lin</UserID>
</UserInfo>

5。删除
CMarkup xml;
xml.Load("UserInfo.xml");
BOOL bFind = true;
xml.ResetMainPos();

while (bFind)

{

bFind = xml.FindChildElem("UserID");

if (bFind)
{//此时接点还是父接点

CString strData = _T("");
strData = xml.GetChildData();

if (strData=="luo")

{

xml.RemoveChildElem();
xml.Save("UserInfo.xml");
break;

}

}

}

6。查找

见第二点浏览。类似

上面的例子里面没有属性这些东西,下面的一个例子是有属性的
//UserInfo1.xml
<?xml version="1.0" encoding="UTF-8" ?>
<UserInfo>
<User UserID="UserID00" UserPwd="UserPwd00" UserLevel="158" />
<User UserID="UserID01" UserPwd="UserPwd01" UserLevel="162" />
<User UserID="UserID02" UserPwd="UserPwd02" UserLevel="165" />
<User UserID="UserID03" UserPwd="UserPwd03" UserLevel="168" />
<User UserID="UserID04" UserPwd="UserPwd04" UserLevel="171" />
<User UserID="UserID05" UserPwd="UserPwd05" UserLevel="175" />
<User UserID="UserID06" UserPwd="UserPwd06" UserLevel="178" />
<User UserID="UserID07" UserPwd="UserPwd07" UserLevel="181" />
<User UserID="UserID08" UserPwd="UserPwd08" UserLevel="184" />
<User UserID="UserID09" UserPwd="UserPwd09" UserLevel="188" />
</UserInfo>

7。产生
CString strID = _T("");
CString strPwd = _T("");
CMarkup xml;
xml.SetDoc("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
xml.AddElem("UserInfo");

xml.IntoElem();

for (int i=0; i<10; i++)
{     //产生一个随机数,作为权限

srand(UINT(time(NULL)+i));
int nLevel = rand()%256;
strID.Format("UserID%02d",i);
strPwd.Format("UserPwd%02d",i);
xml.AddElem("User");
xml.AddAttrib("UserID",strID);
xml.AddAttrib("UserPwd",strPwd);
xml.AddAttrib("UserLevel",nLevel);

}

xml.OutOfElem();
xml.Save("UserInfo1.xml");

8。查找
CMarkup xml;
BOOL bLoad = false;
BOOL bFind = false;
bLoad = xml.Load("UserInfo1.xml");

if (bLoad)
{

CString strID;
CString strPwd;
xml.ResetMainPos();
xml.FindElem(); //UserInfo
while (xml.FindChildElem("User"))
{

strID = xml.GetChildAttrib("UserID");
strPwd = xml.GetChildAttrib("UserPwd");
TRACE("\n----id:%s,pwd:%s-------\n",strID,strPwd);

 

if (strID=="UserID00"&&strPwd=="UserPwd00")
{

bFind = true;
break;

}

}

}

 

if (bFind)
{

TRACE("\n---- find------\n");
}
else
{

TRACE("\n----no find------\n");
}

9。修改
CMarkup xml;
BOOL bLoad = false;
BOOL bFind = false;
bLoad = xml.Load("UserInfo1.xml");

if (bLoad)

{

CString strID;
CString strPwd;

xml.ResetMainPos();
xml.FindElem(); //UserInfo

while (xml.FindChildElem("User"))

{

strID = xml.GetChildAttrib("UserID");
strPwd = xml.GetChildAttrib("UserPwd");
TRACE("\n----id:%s,pwd:%s-------\n",strID,strPwd);

if (strID=="UserID00"&&strPwd=="UserPwd00")
{

bFind = true;
xml.SetChildAttrib("UserID",strID+CString("Modify"));
xml.Save("UserInfo1.xml");
break;

}

}

}

if (bFind)
{

TRACE("\n---- find------\n");
}
else
{

TRACE("\n----no find------\n");
}

posted @ 2008-11-08 02:01 IP 阅读(382) | 评论 (0)编辑 收藏

2008年6月27日 #

删除指定文件。

object.DeleteFile ( filespec[, force] );

参数

object

必选项。 应为 FileSystemObject 的名称。

filespec

必选项。 要删除的文件的名称。 filespec 可以在最后的路径成分中包含通配字符。

force

可选项。 Boolean 值,如果要删除设置了只读属性的文件,则为 true ;如果不删除则为 false (默认)。

说明

如果找不到匹配的文件则出错。 DeleteFile 方法在遇到第一个错误时终止。 出错后不试图回滚或撤消出错前做的修改。


如果成功返回一个非0值
失败返回0  可以用GetLastError函数得到错误信息

下面的例子说明了 DeleteFile 方法的用法。

function DeleteFile(filespec)
{
   var fso;
   fso = new ActiveXObject("Scripting.FileSystemObject");
   fso.DeleteFile(filespec);
}

我用DeleteFile(spath)去删除一个文件时,竟然没有删除掉.(其中CString spath,spath是一个带有文件名的路径).在CSDN上查了一下发现是EXCEL进程没有关闭.(我做的是EXCEL导入和导出)
程序的过程是:
在导出一张EXCEL表时,会弹出一个另存为的路径选择窗口,可以给要导出的文件命一个名字,如果该名字在所选择的路径下存在了,则提示是否覆盖,要是覆盖则删除该文件,并重新创建一个相同的该文件,否则则获取定义的新的路径+文件名.
如果我已打开了一个文件(该文件的进程存在,而文件本身已经关闭了),在后续的覆盖中,也选择了该文件,就会出现无法删除该文件的情况,必须把该文件的进程关闭.
但是如何获取错误信息呢?
解决方法如下:方法一、
由于DeleteFile()函数是一个BOOL型的,如果删除成功的话,返回非零(即 1 ),否则则返回0.
所以应在返回值为0时,获取错误信息,方法是:
LPVOID lpMsgBuf;
if (!FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &lpMsgBuf,
0,
NULL ))
{
// Handle the error.
return;
}

// Process any inserts in lpMsgBuf.
// …

// Display the string.
MessageBox( (LPCTSTR)lpMsgBuf, “Error”, MB_OK | MB_ICONINFORMATION );

// Free the buffer.
LocalFree( lpMsgBuf );

方法二
VC有个小工具的。输入错误码,就可以看到原因了。
Tools->Error Lookup
如何获取错误码:
DWORD dwerr = GetLastError();
如果dwerr==0的话,则成功.否则有错误.
在调试的时候将dwerr的值输入到Error Lookup,(并非只是在调试的时候用,只要知道错误码
什么时候都可以查看错误信息)
例如,在value中输入32,则error为”另一个程序正在使用此文件,进程无法访问。”

posted @ 2008-06-27 23:04 IP 阅读(5532) | 评论 (0)编辑 收藏

2008年5月11日 #

用 umask 命令,在 /etc/init.dev 文件中进行系统范围内的、或在 .profile 文件中进行的本地的文件权限默认设置。这个命令指示用 777 减去这个数字来获取默认的权限: 
# umask 022
这将为用户创建的所有新文件生成一个默认的文件权限 755。

 

umask 

    1.作用
    umask设置用户文件和目录的文件创建缺省屏蔽值,若将此命令放入profile文件,就可控制该用户后续所建文件的存取许可。它告诉系统在创建文件时不给谁存取许可。使用权限是所有用户。 

    2.格式
    umask [-p] [-S] [mode] 

    3.参数
    -S:确定当前的umask设置。
    -p:修改umask 设置。
    [mode]:修改数值。 

    4.说明
     传统Unix的umask值是022,这样就可以防止同属于该组的其它用户及别的组的用户修改该用户的文件。既然每 个用户都拥有并属于一个自己的私有组,那么这种“组保护模式”就不在需要了。严密的权限设定构成了Linux安全的基础,在权限上犯错误是致命的。需要注 意的是,umask命令用来设置进程所创建的文件的读写权限,最保险的值是0077,即关闭创建文件的进程以外的所有进程的读写权限,表示为-rw--- ----。在~/.bash_profile中,加上一行命令umask 0077可以保证每次启动Shell后, 进程的 umask权限都可以被正确设定。 

    5.应用实例 

umask -S
u=rwx,g=rx,o=rx
umask -p 177
umask -S
u=rw,g=,o=
 
    上述5行命令,首先显示当前状态,然后把umask值改为177,结果只有文件所有者具有读写文件的权限,其它用户不能访问该文件。这显然是一种非常安全的设置。

 

Linux的系统安全命令

虽 然Linux和Windows NT/2000系统一样是一个多用户的系统,但是它们之间有不少重要的差别。对于很多习惯了Windows系统 的管理员来讲,如何保证Linux操作系统安全、可靠将会面临许多新的挑战。本文将重点介绍Linux系统安全的命令。 

    passwd 

    1.作用
    passwd命令原来修改账户的登陆密码,使用权限是所有用户。 

    2.格式
    passwd [选项] 账户名称 

    3.主要参数 
    -l:锁定已经命名的账户名称,只有具备超级用户权限的使用者方可使用。
    -u:解开账户锁定状态,只有具备超级用户权限的使用者方可使用。 
    -x, --maximum=DAYS:最大密码使用时间(天),只有具备超级用户权限的使用者方可使用。
    -n, --minimum=DAYS:最小密码使用时间(天),只有具备超级用户权限的使用者方可使用。
    -d:删除使用者的密码, 只有具备超级用户权限的使用者方可使用。 
    -S:检查指定使用者的密码认证种类, 只有具备超级用户权限的使用者方可使用。 

    4.应用实例 

$ passwd
Changing password for user cao.
Changing password for cao
(current) UNIX password:
New UNIX password:
Retype new UNIX password:
passwd: all authentication tokens updated successfully.
 


    从上面可以看到,使用passwd命令需要输入旧的密码,然后再输入两次新密码。 

    su 

    1.作用
    su的作用是变更为其它使用者的身份,超级用户除外,需要键入该使用者的密码。 

    2.格式
    su [选项]... [-] [USER [ARG]...] 

    3.主要参数
    -f , --fast:不必读启动文件(如 csh.cshrc 等),仅用于csh或tcsh两种Shell。
     -l , --login:加了这个参数之后,就好像是重新登陆为该使用者一样,大部分环境变 量(例如HOME、SHELL和USER等)都是以该使用者(USER)为主,并且工作目录也会改变。如果没有指定USER,缺省情况是root。
    -m, -p ,--preserve-environment:执行su时不改变环境变数。
    -c command:变更账号为USER的使用者,并执行指令(command)后再变回原来使用者。
    USER:欲变更的使用者账号,ARG传入新的Shell参数。 

    4.应用实例
    变更账号为超级用户,并在执行df命令后还原使用者。    su -c df root 

    umask 

    1.作用
    umask设置用户文件和目录的文件创建缺省屏蔽值,若将此命令放入profile文件,就可控制该用户后续所建文件的存取许可。它告诉系统在创建文件时不给谁存取许可。使用权限是所有用户。 

    2.格式
    umask [-p] [-S] [mode] 

    3.参数
    -S:确定当前的umask设置。
    -p:修改umask 设置。
    [mode]:修改数值。 

    4.说明
     传统Unix的umask值是022,这样就可以防止同属于该组的其它用户及别的组的用户修改该用户的文件。既然每 个用户都拥有并属于一个自己的私有组,那么这种“组保护模式”就不在需要了。严密的权限设定构成了Linux安全的基础,在权限上犯错误是致命的。需要注 意的是,umask命令用来设置进程所创建的文件的读写权限,最保险的值是0077,即关闭创建文件的进程以外的所有进程的读写权限,表示为-rw--- ----。在~/.bash_profile中,加上一行命令umask 0077可以保证每次启动Shell后, 进程的 umask权限都可以被正确设定。 

    5.应用实例 

umask -S
u=rwx,g=rx,o=rx
umask -p 177
umask -S
u=rw,g=,o=
 


    上述5行命令,首先显示当前状态,然后把umask值改为177,结果只有文件所有者具有读写文件的权限,其它用户不能访问该文件。这显然是一种非常安全的设置。 

    chgrp 

    1.作用
    chgrp表示修改一个或多个文件或目录所属的组。使用权限是超级用户。 

    2.格式
    chgrp [选项]... 组 文件...
    或
    chgrp [选项]... --reference=参考文件 文件... 

    将每个<文件>的所属组设定为<组>。 

    3.参数
    -c, --changes :像 --verbose,但只在有更改时才显示结果。
    --dereference:会影响符号链接所指示的对象,而非符号链接本身。
    -h, --no-dereference:会影响符号链接本身,而非符号链接所指示的目的地(当系统支持更改符号链接的所有者,此选项才有效)。
    -f, --silent, --quiet:去除大部分的错误信息。
    --reference=参考文件:使用<参考文件>的所属组,而非指定的<组>。
    -R, --recursive:递归处理所有的文件及子目录。
    -v, --verbose:处理任何文件都会显示信息。 

    4.应用说明
     该命令改变指定指定文件所属的用户组。其中group可以是用户组ID,也可以是/etc/group文件中用户组 的组名。文件名是以空格分开的要改变属组的文件列表,支持通配符。如果用户不是该文件的属主或超级用户,则不能改变该文件的组。 

    5.应用实例
    改变/opt/local /book/及其子目录下的所有文件的属组为book,命令如下:
    $ chgrp - R book /opt/local /book 

    chmod 

Chmod使用格式:
Chmod [参数][模式]<文件或目录>
参数:-R:改变目录及其所有子目录的文件权限。
举例:
#chmod u+x inittab
#chmod ug+wx,o-x inittab
#chmod 0644 inittab
#chmod 0755 inittab
#chmod –R 700 ~

目录权限的补充说明:
1、目录的只读访问不允许使用cd进入目录,必须要有执行的权限才能进入。
2、只有执行权限只能进入目录,不能看到目录下的内容,要想看到目录下的文件名和目录名,需要可读权限。
3、一个文件能不能被删除,主要看该文件所在的目录对用户是否具有写权限,如果目录对用户没有写权限,则该目录下的所有文件都不能被删除,文件所有者除外


对 特殊位的举例说明: 操作这些特殊位与操作文件权限的命令是一样的, 都是 chmod. 有两种方法来操作, 1) chmod u+s temp : 为temp文件加上setuid标志. (setuid 只对文件有效) chmod g+s tempdir :为tempdir目录加上setgid标志 (setgid 只对目录有效) chmod o+t tempdir : 为temp文件加上sticky标志 (sticky只对目录有效)

2) 采用八进制方式. 对一般文件通过三组八进制数字来置标志, 如 666, 777, 644等. 如果设置这些特殊标志, 则在这组数字之外外加一组八进制数字. 如 4666, 2777等.
设 置完这些标志后, 可以用 ls -l 来查看. 如果有这些标志, 则会在原来的执行标志位置上显示. 如 rwsrw-r-- 表示有setuid标志 rwxrwsrw- 表示有setgid标志 rwxrw-rwt 表示有sticky标志 那么原来的执行标志x到哪里去了呢? 系统是这样规定的, 如果本来在该位上有x, 则这些特殊标志显示为小写字母 (s, s, t). 否则, 显示为大写字母 (S, S, T) 


    1.作用
    chmod命令是非常重要的,用于改变文件或目录的访问权限,用户可以用它控制文件或目录的访问权限,使用权限是超级用户。 

    2.格式
    chmod命令有两种用法。一种是包含字母和操作符表达式的字符设定法(相对权限设定);另一种是包含数字的数字设定法(绝对权限设定)。 

    (1)字符设定法 
    chmod [who] [+ | - | =] [mode] 文件名 

    ◆操作对象who可以是下述字母中的任一个或它们的组合
    u:表示用户,即文件或目录的所有者。 
    g:表示同组用户,即与文件属主有相同组ID的所有用户。
    o:表示其它用户。 
    a:表示所有用户,它是系统默认值。 

    ◆操作符号 
    +:添加某个权限。 
    -:取消某个权限。 
    =:赋予给定权限,并取消其它所有权限(如果有的话)。 

    ◆设置mode的权限可用下述字母的任意组合
    r:可读。 
    w:可写。 
    x:可执行。 
    X:只有目标文件对某些用户是可执行的或该目标文件是目录时才追加x属性。 
    s:文件执行时把进程的属主或组ID置为该文件的文件属主。方式“u+s”设置文件的用户ID位,“g+s”设置组ID位。 
    t:保存程序的文本到交换设备上。 
    u:与文件属主拥有一样的权限。 
    g:与和文件属主同组的用户拥有一样的权限。 
    o:与其它用户拥有一样的权限。 
    文件名:以空格分开的要改变权限的文件列表,支持通配符。
    一个命令行中可以给出多个权限方式,其间用逗号隔开。 

    (2) 数字设定法 
    数字设定法的一般形式为: chmod [mode] 文件名 

    数字属性的格式应为3个0到7的八进制数,其顺序是(u)(g)(o)文件名,以空格分开的要改变权限的文件列表,支持通配符。 

     数字表示的权限的含义如下:0001为所有者的执行权限;0002为所有者的写权限;0004为所有者的读权限; 0010为组的执行权限;0020为组的写权限;0040为组的读权限;0100为其他人的执行权限;0200为其他人的写权限;0400为其他人的读权 限;1000为粘贴位置位;2000表示假如这个文件是可执行文件,则为组ID为位置位,否则其中文件锁定位置位;4000表示假如这个文件是可执行文 件,则为用户ID为位置位。 

    3.实例
    如果一个系统管理员写了一个表格(tem)让所有用户填写,那么必须授权用户对这个文件有读写权限,可以使用命令:#chmod 666 tem 

     上面代码中,这个666数字是如何计算出来的呢?0002为所有者的写权限,0004为所有者的读权限,0020为 组的写权限,0040为组的读权限,0200为其他人的写权限,0400为其他人的读权限,这6个数字相加就是666(注以上数字都是八进制数),结果见 图1所示。 



图1 用chmod数字方法设定文件权限


    从图1可以看出,tem文件的权限是-rw-rw-rw-,即用户对这个文件有读写权限。 

    如果用字符权限设定使用下面命令:
    #chmod a =wx tem 
    chown 

    1.作用
    更改一个或多个文件或目录的属主和属组。使用权限是超级用户。 

    2.格式
    chown [选项] 用户或组 文件 

    3.主要参数
    --dereference:受影响的是符号链接所指示的对象,而非符号链接本身。
    -h, --no-dereference:会影响符号链接本身,而非符号链接所指示的目的地(当系统支持更改符号链接的所有者,此选项才有效)。
    --from=目前所有者:目前组只当每个文件的所有者和组符合选项所指定的,才会更改所有者和组。其中一个可以省略,这已省略的属性就不需要符合原有的属性。
    -f, --silent, --quiet:去除大部分的错误信息。
    -R, --recursive:递归处理所有的文件及子目录。
    -v, --verbose:处理任何文件都会显示信息。 

    4.说明
     chown将指定文件的拥有者改为指定的用户或组,用户可以是用户名或用户ID;组可以是组名或组ID;文件是以空 格分开的要改变权限的文件列表,支持通配符。系统管理员经常使用chown命令,在将文件拷贝到另一个用户的目录下以后,让用户拥有使用该文件的权限。  

    5.应用实例
    1.把文件shiyan.c的所有者改为wan 
    $ chown wan shiyan.c 

    2.把目录/hi及其下的所有文件和子目录的属主改成wan,属组改成users。 
    $ chown - R wan.users /hi 

    chattr 

    1.作用
    修改ext2和ext3文件系统属性(attribute),使用权限超级用户。 

    2.格式
    chattr [-RV] [-+=AacDdijsSu] [-v version] 文件或目录 

    3.主要参数
    -R:递归处理所有的文件及子目录。
    -V:详细显示修改内容,并打印输出。
    -:失效属性。
    +:激活属性。
    = :指定属性。
    A:Atime,告诉系统不要修改对这个文件的最后访问时间。
    S:Sync,一旦应用程序对这个文件执行了写操作,使系统立刻把修改的结果写到磁盘。 
    a:Append Only,系统只允许在这个文件之后追加数据,不允许任何进程覆盖或截断这个文件。如果目录具有这个属性,系统将只允许在这个目录下建立和修改文件,而不允许删除任何文件。 
    i:Immutable,系统不允许对这个文件进行任何的修改。如果目录具有这个属性,那么任何的进程只能修改目录之下的文件,不允许建立和删除文件。 
    D:检查压缩文件中的错误。
    d:No dump,在进行文件系统备份时,dump程序将忽略这个文件。 
    C:Compress,系统以透明的方式压缩这个文件。从这个文件读取时,返回的是解压之后的数据;而向这个文件中写入数据时,数据首先被压缩之后才写入磁盘。 
    s:Secure Delete,让系统在删除这个文件时,使用0填充文件所在的区域。 
    u:Undelete,当一个应用程序请求删除这个文件,系统会保留其数据块以便以后能够恢复删除这个文件。 

    4.说明
     chattr命令的作用很大,其中一些功能是由Linux内核版本来支持的,如果Linux内核版本低于2.2,那 么许多功能不能实现。同样-D检查压缩文件中的错误的功能,需要2.5.19以上内核才能支持。另外,通过chattr命令修改属性能够提高系统的安全 性,但是它并不适合所有的目录。chattr命令不能保护/、/dev、/tmp、/var目录。 

    5.应用实例
    1.恢复/root目录,即子目录的所有文件
    # chattr -R +u/root 

    2.用chattr命令防止系统中某个关键文件被修改
    在Linux下,有些配置文件(passwd ,fatab)是不允许任何人修改的,为了防止被误删除或修改,可以设定该文件的“不可修改位(immutable)”,命令如下:
    # chattr +i /etc/fstab 

    sudo 

    1.作用
    sudo是一种以限制配置文件中的命令为基础,在有限时间内给用户使用,并且记录到日志中的命令,权限是所有用户。 

    2.格式 

sudo [-bhHpV] [-s <shell>] [-u <用户>] [指令]
sudo [-klv]
 


    3.主要参数 

-b:在后台执行命令。
-h:显示帮助。
-H:将HOME环境变量设为新身份的HOME环境变量。
-k:结束密码的有效期,即下次将需要输入密码。
-l:列出当前用户可以使用的命令。
-p:改变询问密码的提示符号。
-s <shell>:执行指定的Shell。
-u <用户>:以指定的用户为新身份,不使用时默认为root。
-v:延长密码有效期5分钟。
 


    4.说明
     sudo命令的配置在/etc/sudoers文件中。当用户使用sudo时,需要输入口令以验证使用者身份。随后 的一段时间内可以使用定义好的命令,当使用配置文件中没有的命令时,将会有报警的记录。sudo是系统管理员用来允许某些用户以root身份运行部分/全 部系统命令的程序。一个明显的用途是增强了站点的安全性,如果需要每天以超级用户的身份做一些日常工作,经常执行一些固定的几个只有超级用户身份才能执行 的命令,那么用sudo是非常适合的。 

    ps 

    1.作用
    ps显示瞬间进程 (process) 的动态,使用权限是所有使用者。 

    2.格式
    ps [options] [--help] 

    3.主要参数
    ps的参数非常多, 此出仅列出几个常用的参数。 

-A:列出所有的进程。 
-l:显示长列表。
-m:显示内存信息。
-w:显示加宽可以显示较多的信息。 
-e:显示所有进程。
a:显示终端上的所有进程,包括其它用户的进程。
-au:显示较详细的信息。
-aux:显示所有包含其它使用者的进程。
 


    4.说明
     要对进程进行监测和控制,首先要了解当前进程的情况,也就是需要查看当前进程。ps命令就是最基本、也是非常强大的 进程查看命令。使用该命令可以确定有哪些进程正在运行、运行的状态、进程是否结束、进程有没有僵尸、哪些进程占用了过多的资源等。图2给出了ps-aux 命令详解。大部分信息都可以通过执行该命令得到。最常用的三个参数是u、a、x。下面就结合这三个参数详细说明ps命令的作用:ps  aux 



图2 ps-aux命令详解


     图2第2行代码中,USER表示进程拥有者;PID表示进程标示符;%CPU表示占用的CPU使用率;%MEM占用的物理内存使用率;VSZ 表示占用的虚拟内存大小;RSS为进程占用的物理内存值;TTY为终端的次要装置号码。 

     STAT表示进程的状态,其中D为不可中断的静止(I/O动作);R正在执行中;S静止状态;T暂停执行;Z不存在,但暂时无法消除;W没有 足够的内存分页可分配;高优先序的进程;N低优先序的进程;L有内存分页分配并锁在内存体内 (实时系统或 I/O)。START为 进程开始时间。TIME为执行的时间。COMMAND是所执行的指令。 

    4.应用实例
    在进行系统维护时,经常会出现内存使用量惊人,而又不知道是哪一个进程占用了大量进程的情况。除了可以使用top命令查看内存使用情况之外,还可以使用下面的命令:
    ps aux | sort +5n 

    who 

    1.作用
    who显示系统中有哪些用户登陆系统,显示的资料包含了使用者ID、使用的登陆终端、上线时间、呆滞时间、CPU占用,以及做了些什么。 使用权限为所有用户。 

    2.格式
    who - [husfV] [user] 

    3.主要参数 

-h:不要显示标题列。 
-u:不要显示使用者的动作/工作。
-s:使用简短的格式来显示。
-f:不要显示使用者的上线位置。 
-V:显示程序版本。
 


    4.说明
     该命令主要用于查看当前在线上的用户情况。如果用户想和其它用户建立即时通信,比如使用talk命令,那么首先要确 定的就是该用户确实在线上,不然talk进程就无法建立起来。又如,系统管理员希望监视每个登录的用户此时此刻的所作所为,也要使用who命令。who命 令应用起来非常简单,可以比较准确地掌握用户的情况,所以使用非常广泛。 

    动手练习 

    1.使用Linux命令检测系统入侵者
     安装过Mandrake Linux和Red Hat Linux的用户都会知道, Linux系统会内置三种不同级别(标准、高、更高)的防火墙,当进行了Linux服务器的安装和一些基本的设置后,服务器应该说是比较安全的,但是也会 有黑客通过各种方法利用系统管理员的疏忽侵入系统。如何快速查找黑客非常重要。一般来说,可以使用命令查询黑客是否入侵,见表1。 


表1 查询黑客入侵现象的命令对应表



    举例说明,如果黑客嗅探网络,那么它必须使网卡接口处于混杂模式,使用下面命令进行查询: 

#ifconfig -a
eth0  Link encap:Ethernet  HWaddr 00:00:E8:A0:25:86
      inet addr:192.168.1.7  Bcast:192.168.1.255  Mask:255.255.255.0
      UP BROADCAST RUNNING PROMISCUOUS  MTU:1500  Metric:1
......
 


     从这个命令的输出中,可以看到上面讲到的这些概念。第一行的00:00:E8:A0:25:86是mac地址,第二 行的192.168.1.7是IP地址,第四行讲的是接收数据状态,这时正在被黑客嗅探。一般而言,网卡有几种接收数据帧的状态,如Broadcast、 Multicast、Promiscuous等。Broadcast是指接收所有类型为广播报文的数据帧;Multicast是指接收特定的组播报文; Promiscuous则是通常说的混杂模式,是指对报文中的目的硬件地址不加任何检查、全部接收的工作模式。 

    2.限制su命令的滥用
     我们知道,超级用户在Linux中有最大的权利,几乎所有黑客都想得到这个目标。Linux可以增加对切换到超级用 户的限制。使用PAM(Pluggable Authentication Modules)可以禁止除在wheel组以外的任何人 su成root,修改/etc/pam.d/su文件,除去屏蔽标识#。使用/usr/sbin/usermod G10  bjecadm将bjecadm这个账号加入gid为10的组,就是wheel组。命令如下: 

/etc/pam.d/su   # 使用密码验证#
auth sufficient /lib/security/pam_wheel.so debug 
# 限制只有wheel组用户才可以切换到root#
auth required /lib/security/pam_wheel.so use_uid
chmod -G10 bjecadm
 


    另外,每当用户试图使用su命令进入系统用户时,命令将在/usr/adm/sulog文件中写一条信息,若该文件记录了大量试图用su进入root的无效操作信息,则表明了可能有人企图破译root口令。 

    Linux命令有着强大的功能。对于Linux系统管理员来说,往往只需要通过各种安全命令技巧,组合构成安全防线。从计算机安全的角度看,世界上没有绝对安全的计算机系统,Linux系统也不例外。


其他信息列述

 

1.1 Linux操作系统概述
开放性的系统
多用户多任务的系统
具有出色的稳定性和速度性能
具有可靠的系统安全性
提供了丰富的网络功能
标准兼容性和可移植性
提供了良好的用户界面
Linux系统的组成
Kernel(内核)和版本
Linux 发行套件
Linux Shell
Linux 文件系统
Linux 文件系统标准结构
1.2 红旗Linux的安装
1.2.1 安装前的准备
1.2.2 使用安装光盘从CD-ROM安装
1.2.3 使用Linux启动盘从硬盘安装
1.2.1 安装前的准备
收集计算机硬件信息
规划硬盘空间
选取工作站类型:至少需要1.2G左右空间;最多需要1.5G左右空间。
选取服务器类型:至少需要650M左右空间,最多需要1.2G左右空间。
选取便携式类型:与工作站类型所需空间相当。
选取自定义类型:至少需要350M左右空间,最多需要2.4G左右空间。
规划网络配置信息
1.2.2 使用安装光盘从CD-ROM安装
设置CMOS
安装
安装红旗linux
Server和Workstation模式:自动分割硬盘
Custom:手工分割 ? Mount Point
/ ? root根分区(建议:256MB)
SWAP ?交换分区(建议:略小于实际内存2倍)
/usr:? 安装软件存放位置(建议:2.5GB)
/home:? 视用户多少而定
/var:? 存放临时文件(建议:256MB)
/boot: ? 存放启动文件(建议:32MB)
1.2.3 Linux的其他安装方式
本地安装
远程网络安装
使用Linux启动盘从硬盘安装
制作启动软盘
1、在DOS下创建:
进入dosutils子目录,运行rawrite程序 。
输入../images/boothd.img 。
2、在linux下创建:
#dd if=/images/boothd.img of=/dev/fd0 bs=1440
修改CMOS设置中的引导顺序
明确系统文件在硬盘中的存放位置
使用Linux安装启动盘从远程FTP服务器安装
远程网络安装Linux系统的方法和本地硬盘安装类似,也需要制作启动软盘。制作启动软盘的步骤和前面相同,唯一不同的是制作启动软盘时使用的软盘镜像文件是bootnet.img。
1.3、linux安装须具备的知识
磁盘的标识:
IDE硬盘:/dev/hdxx:其中第一个X表示第几块硬盘,采用a、b、c、d分别对应四块硬盘,第二个X表示分区,1-4表示主分区或扩展分区,逻辑分区从5开始。
SCSI硬盘:/dev/sdxx
光盘:/dev/cdrom
软盘:/dev/fd0


1.4 Linux运行级别和系统的启动和关闭
21.4.1 Linux的运行级别和切换
21.4.2 Linux的启动过程
1.4.1 Linux的运行级别和切换
Linux的运行级别
Linux运行级别的切换
Linux的启动、关闭和重新启动
Linux的运行级别
Linux运行级别的切换
在inittab文件中,操作initdefault将在系统初始化之后启动预设的运行级别,用户可以通过更改此项设置来改变系统的预设运行级别。
用户也可以在系统运行过程当中来改变系统的运行级别,方法是用init命令,后面加上要切换到的运行级别。
Linux的启动、关闭和重新启动
linux启动
红 旗linux在启动过程中首先加载Linux内核,在内存中执行内核操作,检查硬件,挂载根文件系统,然后启动init进程。init进程就会根据 inittab文件中的设置来使系统进入预设的运行级别,读取相关的配置文件和脚本程序,最后启用相关服务,完成整个系统的启动。

Linux的重启方法
Reboot
Init 6
Ctrl+alt+delete
Shutdown –r time [warning-message]
如:#shutdown –r +3 “system will be down in 3 minites,please save you work”
#shutdown –r now
#shutdown –r 03:15


Linux关机的方法
Halt
Init 0
Shutdown –h time [warning-message]
如:#shutdown –h now
1.5 Linux的初步使用
1.5.1 Linux的字符运行方式
1.5.2 linux的图形界面
1.5.3 常见问题
1.5.1 Linux的字符运行方式
登录和注销 :
超级用户登录后的操作提示符是“#”;普通用户登录后的操作提示符是“$”
若要注销登录,用户可以在当前的登录终端上输入logout命令或使用Ctrl+d热键进行。
终端之间的切换采用:alt+f1—f7
1.5.2 linux的图形界面
图形界面下运行终端命令:rxvt
图形界面切换到字符界面:ctrl+alt +f1—f6
图形界面下的注销:ctrl+alt+delete
图形界面下的锁定:ctrl+alt+L

1.5.3 常见问题
最基本的安全问题
root口令丢失的解决方法
删除Linux操作系统
最基本的安全问题
如果机箱有锁,应该上锁,并保证钥匙与机箱分离放置;
若机箱没有锁,如果必要,当正常运行后断开电源按钮和复位按钮的连接线;
禁止三键热启功能,修改/etc/inittab,将此行注释掉;
禁止BIOS中的软驱启动功能,并设置BIOS开机密码;
禁止公开root密码,若有多个系统管理员则应该避免root密码的扩散;
必须准备引导软盘以防硬盘无法启动时使用。
root口令丢失的解决方法
使用单用户模式 ,重设root密码
在红旗linux4桌面版中,在开始菜单中选3按e,再选2按e,把3改成1,回车再按b。

删除Linux操作系统
首先要修改MBR,删除LILO。
在DOS或Windows下用fdisk命令加上/mbr参数来完成 。
重新格式化ext3分区为FAT32分区或NTFS分区。
 

hls
查看公开信息
发送悄悄话给hls
给hls发送Email
查找hls发表的更多帖子
添加 hls 到好友列表

第2章:linux shell和常用命令
2.1 linux常用命令
2.2 shell
2.3 vi的使用
2.1 linux的常用命令
第一节:文件目录类命令
1、 查看联机帮助信息
man 命令 如:#man ls
info 命令 如:#info cd
2、列出当前目录或指定目录的文件名和目录名
ls [选项] 文件或目录
常用[选项]如下:
-a:显示所有的文件,包括以“.”开头的隐含文件。
-l:长格式输出


-m:宽行输出
-F:以各种符号表示不同的文件类型
--color:彩色输出
-R:递归输出
3、touch
功能:修改文件的创建日期或以当前系统日期创建一个空文件。
-d:修改文件的日期。
#touch –d 20030123 test.txt

 


4、cp
功能:复制文件
用法:cp [选项] 源文件或目录 目标文件或目录
选项:
a: 该选项通常在拷贝目录时使用。它保留链接、文件属性,并递归地拷贝目录,其作用等于dpR选项的组合。
- d 拷贝时保留链接。
- f 删除已经存在的目标文件而不提示。
- i 和f选项相反,在覆盖目标文件之前将给出提示要求用户确认。回答y时目标文件将被覆盖,是交互式拷贝。
- p 此时cp除复制源文件的内容外,还将把其修改时间和访问权限也复制到新文件中。
- r 若给出的源文件是一目录文件,此时cp将递归复制该目录下所有的子目录和文件。此时目标文件必须为一个目录名。

5、mv
功能:给文件或目录改名或将一个文件或目录移到另一个目录
用法:mv [选项] 源文件或目录 目标文件或目录
-i 交互方式操作。如果mv操作将导致对已存在的目标文件的覆盖,此时系统询问是否重写,要求用户回答y或n,这样可以避免误覆盖文件。
- f 禁止交互操作。在mv操作要覆盖某已有的目标文件时不给任何指示,指定此选项后,i选项将不再起作用。

6、rm
功能:删除文件或目录
用法:rm [选项] 文件…
- f 强制删除
- r 指示rm将参数中列出的全部目录和子目录均递归地删除。
- i 进行交互式删除

7、cd
功能:改变工作目录。
语法:cd [directory]
用法:
#cd ..返回上一层目录
#cd ~进入自家目录
8、pwd
功能:显示当前工作目录
用法:#pwd

9、mkdir
功能:创建一个目录(类似MSDOS下的md命令)。
语法:mkdir [选项] dir-name
- m 对新建目录设置存取权限。也可以用chmod命令设置。
- p 可以是一个路径名称。此时若路径中的某些目录尚不存在, 加上此选项后, 系统将自动建立好那些尚不存在的目录,即一次可以建立多个目录。
#mkdir –m a=rwx test
#mkdir –m u=rwx,g=rx,o=rx test1
#mkdir –m 755 test2
#mkdir –p test3/test4(test3和test4均为新目录)

10、rmdir
功能:删除空目录。
语法:rmdir [选项] dir-name
- p 递归删除目录dirname,当子目录删除后其父目录为空时,也一同被删除。
11、file
功能:查看文件类型
语法:file 文件名

12、cat
功能:查看文本文件的内容
语法:cat 文件名
13、more
功能:分屏显示文本文件的内容。
14、less
功能:显示文本文件的内容,可使用pageup和pagedown上翻页下翻页。

15、head
功能:查看文件的开头部分内容
语法:head [行数] 文件名
用法:#head test.txt:显示前10行内容
#head -20 test.txt 显示前20行内容。
16、tail
功能:查看文件的结尾部分内容。
语法:tail [行数] 文件名
默认的行数为10行。

17、sort
功能:对文本文件中的各行进行排序
用法:sort 文件名
例:#sort 123.txt >456.txt 将123.txt排序后重定向到456.txt文件中。
18、uniq
功能:将重复行从输出文件中删除,只留下每条记录的唯一样本
语法: uniq 文件名
#uniq 456.txt文件中的重复行删除后输出

19、pr
功能:处理文件以便打印,输出到标准输出
语法:pr [参数] 文件名列表
[参数]
-d:将输出的每一行后加一新的空白行
-n:输出行号
如:#pr –n 123.txt


20、ln
功能:建立链接文件
语法:ls [-s] 源文件名 目标文件名
说 明:链接文件分为硬连接和软连接,硬连接相当于一个数据源有两个文件名,删除一个文件另一个文件不变,修改一个文件的内容另一个文件的内容也跟着改变。硬 连接不能和另一个目录链接,也不能和其他文件系统的文件进行链接。软链接相当于快捷方式,没有上面的限制,加-s参数创建软链接。

 

 

21、wc
功能:统计文件的行数、字符数和单词数。
语法:wc [-lwc] 文件名
-l: 只显示行数,-w:只显示单词数,-c:只显示字符总数。
22、whatis
功能:可以用一行内容显示对命令行后输入的关键词的说明。
语法:whatis 关键词
用法:#whatis find whereis

23、Whereis
功能:确定指定文件的源程序/二进制程序和手册部分的位置。
用法:#whereis ls
24、which
功能:显示可执行命令的路径和它的别名。
用法:#which ls
25、locate
功能:可以查找具体文件或命令的路径,可以查找具体的字符串或子串
用法:locate 文件名或关键字

26、du
功能:统计文件和目录所占用的磁盘空间
语法:du [-ask] 文件名或目录名
-a:显示对涉及到的所有文件的统计,而不仅仅统计目录
-s:只打印出合计数
-k:以kB字节数显示

Find使用范例
find . -name ls.txt
find . -name ls.txt –print
find / -name 'c??’ –print
find / -name 'f*’ –print
find . -name 'f*’ –exec ls –l {} \;
find . -name f\* –ok rm {} \; (交互式提问)
find . -perm 644 –mtime 4
find . -name 'c??’ –o -name 'd??’


28、grep
功能:在文件中搜寻匹配的行并进行输出
语法:grep [参数]<要找的字串><原文件>
-num:输出匹配行前后各num行的内容
-A num:输出匹配行后num行的内容
-B num:输出匹配行前num行的内容
-i:忽略大小写的区别
-v:只显示出那些不包括某字串的行和文件,和默认的相反

二、文件压缩和归档类命令
1、gzip
功能:是一种压缩程序,特点是可以得到最佳的压缩率,但速度较慢。
语法:gzip [-vd9] 文件名
-v:冗长型选项,可以显示每个文件的大小等
-d:解压
-9:产生最佳压缩效果,但速度较慢。

2、gunzip
功能:可以把压缩的文件解压成原始文件状态,可以解压扩展名为.gz,.z,.Z和.tgz等类型的压缩文件
语法:gunzip [-v] 文件名
-v:显示解压缩文件的冗长结果

3、tar
功能:可以归档多个文件和目录到一个.tar文件下,还可以从一个归档文件中抽取一个文件和目录。
语法:tar [-c][-r][-t][-x][-v][-z][f 文件名] 文件和目录名
-c:创建归档文件
-r:增加文件到归档文件中
-t:查看归档文件中的文件
-x:解开归档文件
-v:显示冗长信息
-z:进行压缩和解压


如: #tar –cvf back.tar 文件1 目录1 文件2
#tar –rvf back.tar 文件3
#tar –tf back.tar
#tar –xvf back.tar
#tar –czvf back.tar.gz 文件1 目录1 文件2
#tar –xzvf back.tar.gz

 

4、安装以rpm方式提供的软件
Rpm(the red hat package manager)是一个开放的软件包管理系统。
功能:可以安装和卸载RPM包的软件
#rpm –ivh *.rpm 安装RPM包;
#rpm –ivh –force *.rpm 在原先安装的基础上再强行安装一次;
#rpm –Uvh *.rpm 升级rpm包
#rpm –qa 查找列出系统中所有安装的rpm包
#rpm –q sendmail:查看sendmail包的情况
#rpm –ql sendmail:查看sendmail安装的位置
#rpm –e *.rpm 卸载rpm包
#rpm - qlp name.rpm 查看name.rpm有哪些文件
#rpm - qf name.rpm 查看已经装好的文件属于哪个rpm包
#rpm2cpio filename.rpm 使用“rpm2cpio”来从RPM文档中提取文件

5、安装以源代码方式提供的软件
(1)、解包解压:
#tar –xzvf *.tar.gz 解包解压后会在当前目录下建立一个子目录,如xxxx
(2)、#cd xxxx
(3)、#./configure
(4)、#make
(5)、#make install

三、系统状态类命令
1、dmesg
功能:显示引导时内核显示的状态信息
#dmesg |grep -4 “eth0”显示状态信息中与eth0相关的前后4行内容
2、uname
功能:显示当前的系统信息
#uname -a


3、uptime
功能:显示当前时间,自从上次重新引导之后系统运行的时间,服务器和多少用户链接以及系统前1、5、15分钟的负载信息。
4、who
功能:显示当前登录在系统上的用户信息。
-r:查看系统运行等级
-w,在登录帐号后面显示一个字符来表示用户的信息状态:
+:允许写信息; -:不允许写信息; ?:不能找到终端设备

5、w
功能:查看其他登录的用户(who增强版)
第一行输出内容:当前时间,系统启动到现在的时间,登录用户的数目,系统在最近1秒、5秒和15秒的平均负载
第二行输出内容:登录帐号、终端名称、远程主机名、登录时间、空闲时间、JCPU、PCPU、当前正在运行进程的命令行。
*JCPU时间指的是和该终端(tty)连接的所有进程占用的时间
*PCPU时间则是指当前进程(即在WHAT项中显示的进程)所占用的时间

6、whoami
功能:显示当前用户名
7、hostname
功能:显示系统的主机名
8、cal [月份] [年份]
功能:显示日历
9、bc
功能:计算器,使用quit退出
10、date
功能:显示或修改日期时间。


11、df
功能:报告文件系统磁盘空间的使用情况
语法:df[参数]
[参数]
-h:用常见的格式显示出大小(例如:1K,23M,2G等)
-t:只显示指定类型的文件系统
12、free
功能:查看当前内存和交换空间的使用情况
四、网络类命令
1、write
功能:向另外一个用户发信息,以Ctrl+D作为结束,普通用户发信息受到mesg状态影响。
语法:write <用户名>
2、wall
功能:向所有用户广播信息,普通用户受到mesg状态影响。
语法:wall [message]

3、mesg
功能:显示或设置是否接受其他用户发来的信息。
语法:mesg [参数]
[参数]
y:接受从其他用户发来的信息
n:不接受从其他用户发来的信息
#mesg
显示当前是否接受其他用户发来的信息

4、ping
功能:通过检查网络中其他主机的应答信息,来确认网络的连通性。
语法:ping [参数] 主机名(或ip地址)
参数:
-c count:共发出count次信息。
-R:显示路由表的详细信息

5、telnet
功能:远程登录
语法:telnet [<主机名>][:端口号]
6、ifconfig
功能:配置网络接口
语法:
ifconfig [interface] [up][down][netmask mask]
#ifconfig
#ifconfig eth0 192.168.0.3 netmask 255.255.255.0 up

7、netstat
功能:显示本地系统的网络连接状态
语法:netstat [-a][-r][-c][-i]
-a:显示所有本地系统中的网络连接
-r:显示路由表
-c:显示连续的网络连接状态
-i:显示全部网络接口信息。

8、ftp
功能:文件传输
语法:ftp [<主机名>]
子命令:
?:列出所有的FTP命令;
pwd:显示远程主机的当前目录
lcd:切换和显示本机主机的当前目录
ls:列出远程主机当前目录下的内容
!dir:列出本机主机的当前目录下的内容
cd:切换远程主机的目录

get:下载一个文件
mget:成批下载文件
put:上传一个文件
mput:成批上传文件
prompt:使用mget和mput时是否采用交互式询问
bye:中止一个FTP连接
open:打开一个FTP连接
close:关闭一个FTP连接
binary:采用二进制模式传输
ascii:采用ascii模式传输
type:查看传输模式

2.2 shell
一、shell简介
二、shell功能

一、shell简介
(一)、定义:shell是用户与操作系统内核之间的接口,具有命令解释器和编程语言的双重功能。
(二)、shell中的命令分为内部命令和外部命令,内部命令包含在shell自身之中的,如cd,exit等,查看内部命令的方法可用help命令;而外部命令是存在于文件系统某个目录下的具体的可执行程序,如cp等,查看外部命令的路径可用which。

(三)、命令解释过程
二、shell功能
(一)、命令行解释
1、交互模式
2、后台运行:如#fsck / &
(二)、通配符
*:表示任一串字符
?:表示任一个字符
[…]:匹配方括号内的任意字符
[!...]或[^…]:匹配除方括号内的任意字符
[a-zA-Z]:匹配首字符是字母的所有文件


(三)、重定向
1、>:输出重定向 如:#ls >1.txt
2、>>:追加重定向
如:#cat /etc/inittab >>1.txt
3、<:输入重定向 如#wc </etc/passwd
4、<<!...!:输入重定向特例
如#wc <<!
hello
it is a nice day
!
5、2>:错误重定向
6、&>:同时实现输出重定向和错误重定向

(四)、管道操作符 |
功能:把一个命令的输出内容作为另外一个命令的输入内容。
#ls |more:分屏显示
#dmesg |grep eth0
(五)、命令替换符` `
功能:把一个命令的输出内容作为另外一个命令的参数。
#cd `pwd`
#tar –cvf zsan.tar `find / -user “zsan”`
#tar –cvf conf.tar `ls /etc *.conf`

(六)、命令执行顺序
1、;按顺序执行命令 如#date;cal
2、&&逻辑与的关系,只有前面的命令执行成功后面的命令才会被执行。
如:#mail
zsan@sje.cn <123.txt &&rm –fr 123.txt
3、||逻辑或的关系,只有前面的命令执行失败后面的命令才会被执行。
如:#write zsan <123.txt ||mail
zsan@sje.cn <123.txt
4、( )组合命令行中的命令,改变执行顺序
如:#date;cal|wc与#(date;cal)|wc

(七)、自动补全:在bash下输入命令时不必把命令输全,bash就能判断出用户所要输入的命令,可通过tab键完成自动补全功能。
(八)、命令别名
功能:可以使工作变得轻松的工具
语法:alias [<别名>=“<原文件名>”]
#alias 显示所有的别名
#alias md=“mkdir” 建立别名
#unalias md 取消别名

(九)、命令历史
功能:可以记录一定数目的以前在shell中输入的命令以避免重复劳动
1、查看所有的历史命令:#history
2、查看某条历史命令:#history n
3、引用历史命令:#!<命令编号>

2.3 vi的使用

一、编辑模式下的常用命令
1、0:移光标到当前行的行首
2、$:移光标到当前行的行尾
3、H:移光标到当前屏第一行的行首
4、M:移光标到当前屏中间行的行首
5、L:移光标到当前屏最后一行的行首
6、pageup、pagedown:上翻页,下翻页
7、ctrl+g:显示状态信息
8、X:删除一个字符
9、dd:删除或剪切光标所在的行
10、d0:删除光标处到行首的内容
11、d$:删除光标处到行尾的内容
12、u:取消上次命令
13、. :重复操作
14、YY:复制当前行
15、p:粘贴

二、命令状态下操作:
1、:n 跳行
2、:w 保存
3、:q 退出(须先保存)
4、:w filename 另存为
5、:wq 保存退出
6、:q! 不保存强制退出
7、:a,b w filename 将a行到b行的内容另存为
8、:.,$ w filename 将当前行到行尾的内容另存为
9、:1,. W filename 将第一行到当前行的内容另存为
10、:/string 在全文中查找string字符串
11、:a,b s/string1/string2/g 将a行到b行之间的所有string1替换成string2
12、:% s/string1/string2/g 将全文中的string1替换成string2
13、:e filename 新建文件
14、: r filename 打开一个文件
15、:f filename 重命名当前文件

16、:n1,n2 co n3 将n1和n2行的内容复制到n3行位置上
17、:n1,n2 m n3 将n1和n2行的内容移到n3行的位置上
18、:n1,n2 d 将n1到n2行的内容删除掉
19、:set number 设置行号
20、:set autoindent 设置自动缩进
21、:!Cmd 执行shell命令
22、:r ! Cmd 将shell命令的执行结果作为文件的内容
23、:sh 暂时退出vi到系统下,按ctrl+d结束
24、:X 文件保存退出前加密。
 

hls
查看公开信息
发送悄悄话给hls
给hls发送Email
查找hls发表的更多帖子
添加 hls 到好友列表

第三章:文件系统管理
3.1 文件系统概述
一、文件管理
(一)、文件的类型
1、普通文件:包括文本文件和二进制文件
2、目录文件
3、链接文件:包括硬链接和软链接。
4、特殊文件:
设备文件:包括字符类设备(如打印机)和块设备(如硬盘)以及空设备(null)
管道文件:是一个先进先出(FIFO)的缓冲区。

文件类型及其代表字符

(二)、文件权限
1、chmod
功能:改变文件(目录)的访问权限
权限的表示法有两种:
一是采用符号标记模式进行更改
二是采用八进制数指定新的访问权限
符号表示法的格式:[ugoa][+-=][rwx]
u:表示文件的所有者;
g:表示文件所有者同组的用户;
o:其他用户
a:所有用户
+:添加权限
-:撤消权限
=:指定权限
r:读权限
w:写权限
x:执行权(或对目录的访问权)

八进制数字表示法:
用1—4个八进制数来表示,其中:
第一位:
用4表示setuid:设置使文件在执行阶段具有文件所有者的权限,如/usr/bin/passwd的权限设置 ;
用2表示setgid:该权限只对目录有效. 目录被设置该位后, 任何用户在此目录下创建的文件都具有和该目录所属的组相同的组;
用1 表示sticky bit :该位可以理解为防删除位,一个文件是否可以被某用户删除, 主要取决于该文件所在的目录是否对该用户具有写权限. 如果没有写权限, 则这个目录下的所有文件都不能被删除, 同时也不能添加新的文件. 如果希望用户能够添加文件但同时不能删除别的文件, 则可以对目录使用sticky bit位. 用户对目录设置该位后, 就算对目录具有写权限, 也不能删除不属于该用户的文件。

第二位:用来设置文件所有者的权限,用4表示可读,用2表示可写,用1表示可执行
第三位:用来设置文件所在组用户的权限,用4表示可读,用2表示可写,用1表示可执行
第四位:用来设置其他用户的权限,用4表示可读,用2表示可写,用1表示可执行。


Chmod使用格式:
Chmod [参数][模式]<文件或目录>
参数:-R:改变目录及其所有子目录的文件权限。
举例:
#chmod u+x inittab
#chmod ug+wx,o-x inittab
#chmod 0644 inittab
#chmod 0755 inittab
#chmod –R 700 ~

目录权限的补充说明:
1、目录的只读访问不允许使用cd进入目录,必须要有执行的权限才能进入。
2、只有执行权限只能进入目录,不能看到目录下的内容,要想看到目录下的文件名和目录名,需要可读权限。
3、一个文件能不能被删除,主要看该文件所在的目录对用户是否具有写权限,如果目录对用户没有写权限,则该目录下的所有文件都不能被删除,文件所有者除外


对 特殊位的举例说明: 操作这些特殊位与操作文件权限的命令是一样的, 都是 chmod. 有两种方法来操作, 1) chmod u+s temp : 为temp文件加上setuid标志. (setuid 只对文件有效) chmod g+s tempdir :为tempdir目录加上setgid标志 (setgid 只对目录有效) chmod o+t tempdir : 为temp文件加上sticky标志 (sticky只对目录有效)

2) 采用八进制方式. 对一般文件通过三组八进制数字来置标志, 如 666, 777, 644等. 如果设置这些特殊标志, 则在这组数字之外外加一组八进制数字. 如 4666, 2777等.
设 置完这些标志后, 可以用 ls -l 来查看. 如果有这些标志, 则会在原来的执行标志位置上显示. 如 rwsrw-r-- 表示有setuid标志 rwxrwsrw- 表示有setgid标志 rwxrw-rwt 表示有sticky标志 那么原来的执行标志x到哪里去了呢? 系统是这样规定的, 如果本来在该位上有x, 则这些特殊标志显示为小写字母 (s, s, t). 否则, 显示为大写字母 (S, S, T)


2、chown
功能:修改文件(目录)所有者和组别
语法:
chown [参数]<用户名[.组名]><文件或目录>
参数:-R:递归地修改目录及其下面内容的所有权。
#chown zsan history.doc
#chown –R zsan.sales /home/zsan

3、chgrp
功能:改变文件的组所有权
格式:chgrp [参数]<组><文件或目录>
[参数]:-R:递归地将指定目录下的所有文件和子目录的组名修改为指定的组。
#chgrp –R student /home/st01
4、umask
功能:用于设置文件的默认生成掩码,告诉系统当创建一个文件或目录时不应该赋予其哪些权限。

(三)ext2(ext3)文件系统安全选项
A:Atime。告诉系统不要修改对这个文件的最后访问时间
S: Sync。一旦应用程序对这个文件执行了写操作,使系统立刻把修改的结果写到磁盘。 a:Append Only。系统只允许在这个文件之后追加数据,不允许任何进程覆盖或者截断这个文件。如果目录具有这个属性,系统将只允许在这个目录下建立和修改文件,而 不允许删除任何文件。 i:Immutable。系统不允许对这个文件进行任何的修改。如果目录具有这个属性,那么任何的进程只能修改目录之下的文件,不允许建立和删除文件。
ext2(ext3)文件系统安全选项
d:No dump。在进行文件系统备份时,dump程序将忽略这个文件。 C:Compress。系统以透明的方式压缩这个文件。从这个文件读取时,返回的是解压之后的数据;而向这个文件中写入数据时,数据首先被压缩之后,才写 入磁盘。 s:Secure Delete。让系统在删除这个文件时,使用0填充文件所在的区域。 u:Undelete。当一个应用程序请求删除这个文件,系统会保留其数据块以便以后能够恢复删除这个文件。
chattr修改文件属性
chattr +i filename 设置i属性
chattr –i filename 取消i属性
lsattr 查看设置的属性


二、linux支持的文件系统:
Ext2或ext3文件系统
FAT(适用各种版本的DOS)
NTFS(适用Windows NT -- Windows 2000)
VFAT和FAT32(适用Windows 9x)
HFS(适用MacOS)
HPFS(适用OS/2)
Swap(交换分区)

3.2 fdisk分区工具
#fdisk –l /dev/hda
#fdisk /dev/hda
:m 列出所有的分区命令
:p 显示分区情况
:d 删除原有的分区
:n 增加新分区
:t 改变分区类型
:l 查看分区类型
:a 设置活动分区
: w 保存退出
: q 不保存退出


3.3 使用mkfs创建文件系统
mkfs命令的格式是:mkfs 选项 设备名
例如:
mkfs –t ext2 –c /dev/hda2
-t:指定文件系统类型
-c:建立文件系统前先检测有无坏块

#mke2fs –c /dev/hda2
3.4 挂载和卸载文件系统
挂载文件系统
卸载文件系统
挂载文件系统
mount /dev/sdb1 /tmp/test1
mount /mnt/floppy
mount
注意
挂载目录必须存在
Linux专门提供了挂载目录/mnt
不要在挂载目录下进行挂载操作
将软盘或光盘放入驱动器后在实施挂载操作
卸载前不要取出软盘或光盘
不能在同一个目录下挂载两个文件系统
卸载文件系统
umount /mnt/cdrom
umount /dev/sdb1
umount /tmp/test1
mount –a:卸载所有挂载设备

注意:不能在挂载目录下进行卸载操作
3.5 软盘、光盘、USB硬盘的使用
1、软盘格式化:fdformat –n /dev/fd0H1440
2、建立文件系统
mkfs –t ext2 –c /dev/fd0H1440
3、挂载文件系统
mount –t ext2 /dev/fd0H1440 /mnt/floppy
4、使用
cp /etc/lilo.conf /mnt/floppy
mkdir /mnt/floppy/mydir1
5、卸载
umount /mount/floppy
1、挂载文件系统
mount –t iso9660 /dev/cdrom /mnt/cdrom

mount /mnt/cdrom
2、卸载
umount /mnt/cdrom
eject(弹出光盘)
USB硬盘的使用
USB硬盘在Linux系统下是被模拟成SCSI设备来使用的,因此对应的设备文件是/dev/sda,如果有多块USB硬盘,则设备文件依次是/dev/sdb、/dev/sdc等。
用mount命令加-o loop选项挂载光盘镜像文件
1、#mkdir /mnt/iso
2、#mount –o loop rfdesktop4.iso /mnt/iso


3.6在系统启动时自动挂装文件系统
要系统自动挂载文件系统必须修改系统配置文件/etc/fstab。系统启动所要挂载的文件系统、挂载点、文件系统类型等都记录在/etc/fstab文件里。
3.7 文件系统管理的常用命令
df:显示文件系统的统计数据
du:显示硬盘使用的统计信息
ln:创建链接文件
tar:文件打包
fsck:文件系统检查命令
dd:文件拷贝命令
fsck
功能:检查文件系统
语法:fsck [选项]<设备名>
选项:
-t fstype:检查的文件系统类型
-A 检查所有在/etc/fstab中列出的文件系统
-p:整理文件系统,自动修正所有问题
注意:
1、当检查一个文件系统时,应该确保处于未挂装状态
2、当要检查根文件系统时,应该使用急救软盘或安装光盘重新引导机器后时行

fsck命令输入的错误代码

Linux引导盘的制作
#cd /lib/modules
#mkbootdisk --device /dev/fd0 2.4.20-8
制作引导盘的好处:
1、替代LILO
2、紧急情况下使用引导盘和急救盘恢复系统
3、当其他系统覆盖了LILO
dd:文件拷贝命令
制作Linux安装启动盘
dd if=boot.img of=/dev/fd0 bs=1440k
制作redhat linux急救盘
#dd if=rescue.img of=/dev/fd0
rescue.img存放在redhat linux安装光盘的images目录中。
备份和恢复软盘数据
dd if= /dev/fd0 of=backup
dd if=backup of= /dev/fd0


第四章:用户管理
本章主要内容
4.1 用户和组管理概述
4.2 用户帐号的管理
4.3 组的管理
4.4 磁盘限额

4.1 用户和组管理概述
一、帐号管理包括用户的管理、组的管理和口令的管理
二、linux下的用户分为三类:
1、超级用户:超级用户的uid和gid均为0
2、普通用户:普通用户的uid和gid是在500-60000范围内的一个值
3、伪用户:uid的值是在1-499之间,不能登录,只是满足系统管理的要求,如bin,sys,lp 等。

三、linux下的组分为标准组和私有组:
1、当在创建一个新用户时,若没有指定他所属于的组,系统就会建立一个和该用户同名的私有组;
2、标准组可以容纳多个用户,若使用标准组,在创建一个新的用户时就可以指定他所属于的组。
四、帐号系统文件
1、linux下的帐号系统文件主要有/etc/passwd,/etc/group,/etc/shadow ,这些文件只有超级用户才可以修改


2、其他文件或目录
(1)、默认设置文件
/etc/login.defs,/etc/default/useradd
(2)、默认设置目录
/etc/skel
2.1 用户帐号管理
一、创建新的用户帐号系统将会
1、在/etc/passwd中添加一笔记录
2、创建用户的自家目录
3、在用户自家目录中设置默认的配置文件
4、设置用户初始口令
二、创建帐号
Useradd [<选项>]<用户名>

 

[<选项>]
-u uid:指定新用户的uid,默认为使用当前最大的uid加1
-g group:指定新用户所在的组,该组必须已经存在
-G group:指定新用户的附加组
-d dir :指定新用户的自家目录
-s shell:指定新用户使用的shell,默认为bash,也可以使用chsh命令修改,/etc/shells
-c comment:说明新用户的附加信息,如全名等
-e expire:指定用户的登录失交效时间,格式yyyy-mm-dd
-m 建立新用户的自家目录
-M 不建立新用户的自家目录
-R 建立一个系统用户

举例:
#useradd –g ll –e 12/31/2001 lei
#passwd lei
三、删除已存在的用户帐号
Userdel –r lei
-r :表示连同自家目录一起删除
四、修改用户属性
Usermod [<选项>]<用户名>
其中[<选项>]和useradd中的选项基本相同,但增加
-l:更改用户的帐号名
-L:锁定用户
-U:取消帐号锁定
#usermod –l xjlei –d /home/xjlei –G girl lei


五、成为超级用户
修改用户的uid和gid为0即可。
命令:usermod –u 0 –o zsan
-o:强迫修改uid
六、禁用和恢复用户帐户
命令:passwd [<选项>]<用户名>
[选项]
-l:锁定用户
-u:对被锁定的用户解锁
-d:删除用户密码
-S:查看用户帐号的状态,如是否被锁定等

 

七、切换到其他用户
命令:su [-] [username]
说明:如果目录用户是普通用户,则可以切换到超级用户或其他普通用户,但需要口令,如果目录用户是超级用户,则可以直接切换到普通用户,不需要密码。
[-] :连同工作环境一起切换。


八、sudo
sudo是可以让普通用户执行某个超级用户执行的程序,如:sudo vi /etc/shadow
所有这些配置项要保存在/etc/sudoers文件中
sudo文件中语法如下:
1、用Host_Alias关键字定义主机列表
如:Host_Alias RED=www,ftp
2、用User_Alias关键字定义用户别名列表
如:User_Alias US=wdn,zsan
3、用Cmnd_Alias关键字定义命令别名列表
如:Cmnd_Alias CMDS=/bin/rm,/bin/chown

一个实例:/etc/sudoers
User_Alias US=wdn
User_Alias US1=lsi,wemz
User_Alias US2=zsan
US ALL=ALL
US1 ALL=/sbin/shutdown
US2 ALL=ALL,!/bin/su

4.3 组的管理
一、添加组
命令:groupadd [<选项>]<组名>
[选项]
-g gid:指定新组的gid,默认为使用当前最大的gid加1
-r:建立一个系统用户组,不指定-g选项,则分配一个1-499之间的值
#groupadd –g 888 ll

二、删除组
命令:groupdel <组名>
三、修改组的属性
命令:groupmod [<选项>]<组名>
其中选项与groupadd选项基本相同,但增加一个-n选项,用于修改新的组名。
#groupmod –n girl ll
四、为标准组添加用户
命令:gpasswd [选项][用户][组]
-a:把用户加入组
-d:把用户从组中删除
#gpasswd –a zsan sales

 

五、查看当前用户属于哪些组
groups [username]
六、切换到某组运行(必须已属此组)
newgrp [group]

 

七、其他命令
1、id命令
功能:可以查看一个用户的uid和gid
格式:id 用户名
2、finger
功能:查看用户的相关信息
格式:finger 用户名
3、chfn
功能:修改用户信息
格式:chfn 用户名
4.4 磁盘限额
一、安装磁盘限额
在安装光盘中找到quota的rpm软件包。如:
#rpm –ivh quota-version.i386.rpm
可用rpm –q quota查看是否安装
二、启用系统的quota功能
编辑etc/fstab,在要启用磁盘限额的分区
(如/home)行中的添加相应参数如
/dev/hda6 /home ext3 rw defaults,usrquota,grpquota 0 2

三、创建quota文件
在/home分区中添加aquota.user和aquota.group文件
#touch aquota.user
#touch aquota.group
系统重启,运行quotacheck –avug命令在配额文件中创建磁盘信息。
四、设置用和组的quota
对 一个用户的磁盘使用限制有两种,一种是软限制(soft limit),一种是硬限制(hard limit),硬限制是分配给用户的最大磁盘空间,使用完了就立即拒绝用户再存放文件,而当用户的磁盘使用空间超过了软限制时,在一定期限内仍可继续存储 文件,但系统会有警告。而这个期限可用edquota –t 来设置

1、为用户设置quota
#edquota –u zsan
2、为组设置quota
#edquota –g sales
3、若为多个用户重复设置quota
#edquota –p 参考用户 待设置用户
#edquota –p zsan lsi wdn
四、启动quota
#quotaon -avug

五、查看磁盘限额的情况
#quota zsan
#quota –a
六、显示有关quota的摘要信息
# repquota /home
 

hls
查看公开信息
发送悄悄话给hls
给hls发送Email
查找hls发表的更多帖子
添加 hls 到好友列表

第五章 进程管理
本章主要内容
5.1 进程的基本概念
5.2 进程的管理和监控
5.3 进程的自动执行
5.1 进程的基本概念
一、程序、进程和作业的概念
1、程序是机器指令的集合,一般以文件的形式存储在磁盘上,是静态的。
2、进程是一个程序在地址空间中的一次执行活动,是动态的。
3、作业是指用户提交给计算机进行加工的一项任务,是由用户程序、数据以及某种形式的控制信息组成。

二、进程的类型
1、交互进程:由一个shell启动的进程,既可以在前台运行,也可以在后台运行。
2、批处理进程:不与特定的终端相关联,提交到等待队列中顺序执行进程。
3、守护进程:在linux启动是初始化,需要时运行于后台的一些服务进程。

三、进程的启动方式:
1、手工启动,分为前台启动和后台启动,通常情况下,我们执行一个命令就会启动一个进程。
#ls / –R >test 前台启动
#ls / -R >test & 后台启动
2、调度启动:事先设置好,根据用户要求让系统自动启动。
2.2 进程的管理和监控
一、查看系统中的进程
1、ps [选项]
[选项]
-a 显示所有用户的进程,但不包括没有控制终端的进程
-u 显示用户名和启动时间
-x 显示没有控制终端的进程


PS命令输出的重要信息的含义
PID:进程号
PPID:父进程的进程号
TTY:进程启动的终端
STAT:进程当前的状态,S代表休眠,R代表运行,T代表追踪或停止,Z代表僵尸进程,W代表进程没有固定的pages,<表示高优先级的进程,N代表低优先级的进程。
TIME:进程自从启动以来占用CPU的总时间
COMMAND/CMD:进程的命令名
USER:用户名
%CPU:占用CPU的时间与总时间的百分比
%mem:占用内存与系统总内存的百分比
SIZE:进程代码大小

二、控制系统中的进程
1、kill
功能:杀死一个进程
格式:kill -9 进程号
2、renice
功能:改变一个正在运行进程的优先级
格式:renice –n pid
-n:进程的优先级,大于0是降低优先级,小于0是提高优先级,优先级的范围是从19到-20

3、 nohup
功能:当用户退出登录后,进程仍然能够在后台继续运行.
语法:nohup myprogram &
4、top
功能:以动态的方式显示进程状态
Top命令的显示栏说明:
PID:进程号
User:进程的所有者
Pri:进程运行的优先级
Ni:nice值,表示进程的优先级
VIRT:进程占用的虚拟内存的总量
RES:进程占用的非swap内存的总量
SHR:和其他进程共享内存的总量。
S:进程的状态
TIME+:进程的累积运行时间

 

Top中的子命令
l,t,m:调整显示的摘要信息,l是平均负载,t是cpu状态,m是内存信息
f:用来添加、删除显示栏位
u:列出某个用户的进程
n:设置显示总进程数的最大值
k,r:k用来杀死某个进程,r是renice
q:退出TOP

 

5、进程的挂起和恢复
按ctrl+z可以暂停正在运行的进程,
执行jobs可以显示当前终端下有哪些程序在后台运行以及哪些进程被挂起了。
执行fg可以在前台恢复一个被挂起的进程。
执行bg可以在后台恢复一个被挂成的进程。
5.3 进程的自动运行
1、at
功能:指定在某一时刻执行命令
格式:at [选项] 时间
选项:
-l:列出已经排定的任务
-d n 删除序号为n的任务
-v:显示命令将被执行的时间


时间表示法:
HH:MM 当天中的某小时、某分钟,如果此时刻已经过去了,则推迟到下一天的这一时刻。
Midnight:午夜
noon:中午
MM/DD/YY:某月、某日、某年
now+计时:从现在起某时刻后,如now+3days表示从现在起,三天以后

2、batch
功能:任务会在系统负载较小的时候运行。
3、应用程序corn
对于要在每天或每周都要定期执行的任务,则可以使用corn来排定。
格式:crontab –e
可以调动VI来编辑计划任务表。如:
00 03 * * * rm –fr /ftp/incoming/temp/*
分钟 小时 日 月 星期 命令
(00-59)(00-23)(01-31)(01-12)(01-07)

#crontab –l 查看用户的计划表
#crontab –r 删除用户的计划表
4、linux自动安排系统维护任务
/etc/crontab
/etc/cron.daily
/etc/cron.monthy
/etc/cron.weekly
/etc/cron.hourly

Linux 文件命令精通指南

虽然 GUI 桌面(如 KDE 和 GNOME)能够帮助用户利用 Linux 特性,而无需关于命令行接口的功能知识,但还是经常会需要更多的功能和灵活性。而且,基本熟悉这些命令对于在 shell 脚本中正确地使某些功能自动化仍然是必需的。

这 篇文章是关于 Linux 文件命令的一个“速成教程”,它是为那些刚接触这个操作系统或者只是需要补充这方面知识的用户提供的。它包含了对一些更有用的命令的一个简明的概述以及关 于它们的最强大的应用的指导。下面包含的信息 — 结合一些实验 — 将使您能够容易地掌握这些基本的命令。(注意:当涉及到一个与 Oracle 集群文件系统 (OCFS) 结合的内核时,这些命令中的某些命令的行为可能会稍微有所不同。在此情况下,Oracle 提供了一个 OCFS 工具集,该工具集可以为文件命令应用提供一个更好的选择。)

注意,这里包含的所有示例都在 SUSE Linux 8.0 Professional 上进行了测试。虽然没有理由相信它们在其它的系统上将不能工作,但如果出现问题,您应当查看您的文档,以了解可能的变化。

背景概念

在深入研究规范之前,让我们回顾一些基础知识。

文件和命令

在 Linux/UNIX 操作系统中,所有事物都被当作文件来处理:硬件设备(包括键盘和终端)、目录、命令本身,当然还有文件。这个奇怪的惯例实际上是 Linux/UNIX 的能力和灵活性的基础。

大多数(几乎是全部)的命令形式如下:


command [option] [source file(s)] [target file]


获取帮助

最有用的命令之一是那些提供帮助的命令(特别是对那些学习 Linux 的人而言)。Linux 中的两个重要的信息来源是联机参考手册,或 man 页面和 whatis 工具。您可以用 whatis 命令来访问一个不熟悉的命令的 man 页面。


$ whatis echo


要了解关于这个命令的更多信息,可以使用:


$ man echo


如果您不知道某个特殊任务所需的命令,您可以用 man -k (也称为 apropos)和一个主题来生成可能的命令。例如:


$ man -k files


一个很有用但常常被忽视的命令可以提供关于使用 man 本身的信息:


$ man man


您可以用 SPACEBAR 来浏览任意的 man 页面;UP ARROW 将向上翻滚文件。.要退出,则输入 q,!,或 CTRL-Z。

用户类别

记得那句名言“所有动物一例平等但有些动物比其他动物更加平等”吗?在 Linux 世界中,根用户掌管一切。

根 用户可以以另一个用户名 su (源自 "superuser")登录。要执行诸如添加一个新用户、打印机或文件系统之类的任务,必须作为根用户登录或者用 su 命令和根用户密码切换到超级用户。系统文件(包括控制初始化过程的系统文件)归根用户所有。虽然可能允许普通用户对它们进行读操作,但出于系统安全性的原 因,编辑的权利将留给根用户。

BASH shell

虽然提供了其它的 shell,但 BASH (Bourne Again Shell) 是 Linux 的默认 shell。它结合了与它同名的 Bourne shell 的特性和 Korn、C 和 TCSH shell 的特性。

BASH 内置的命令 history 默认记录最后输入的 500 条命令。可以通过在命令提示符下输入 history 来查看它们。要检索某个特定的命令,可以在命令提示符下按 UP ARROW 或 DOWN ARROW,或在历史列表中输入它的编号,并在编号前面加上 "!",例如:


$ !49


您还可以通过一条命令在历史列表中离位置最靠前的项目的距离来执行该命令:如果在历史列表中有 53 个事件,$ !-3 将执行事件号 51。

像 UNIX/Linux 世界的其它 shell 一样,BASH 使用了特殊的环境变量来方便系统管理。例如:


HOME,用户主目录
PATH,Linux 用来搜索您输入的命令的可执行镜像的搜索路径
HISTSIZE,系统保存的历史事件的数量

除了这些保留的关键字之外,您还可以定义您自己的环境变量。例如,Oracle 使用 ORACLE_HOME (还有其它一些变量),要使 Oracle 安装成功完成,必须在您的环境中设置这些变量。

可以在提示符下临时设置变量:


$HISTSIZE=100


或者,在 /etc/profile (需要根用户权限)中进行系统范围的永久设置,或在 .profile 中进行局部永久设置。

可以通过 echo 命令,并用一个 $ 符号来访问一个环境变量的值,进而查看该值。



$ echo $HOME
/home/bluher


可以用 env 命令来查看当前所有的环境变量。

正则表达式和通配符

许多 Linux 命令使用通配符 * 和 ? 来匹配任意数量的字符或分别匹配任意的单个字符;正则模式匹配表达式利用一个句点 (.) 来匹配除“换行符”之外的任意单个字符。这两种情况下都使用方括号 ([ ]) 来

posted @ 2008-05-11 15:38 IP 阅读(549) | 评论 (0)编辑 收藏

2008年5月6日 #

<本文中排序都是采用的从小到大排序>

一、对int类型数组排序

int num[100];

Sample:

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

qsort(num,100,sizeof(num[0]),cmp);

二、对char类型数组排序(同int类型)

char word[100];

Sample:

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

qsort(word,100,sizeof(word[0]),cmp);

三、对double类型数组排序(特别要注意)

double in[100];

int cmp( const void *a , const void *b )
{
return *(double *)a > *(double *)b ? 1 : -1;
}

qsort(in,100,sizeof(in[0]),cmp);

四、对结构体一级排序

struct In
{
double data;
int other;
}s[100]

//按照data的值从小到大将结构体排序,关于结构体内的排序关键数据data的类型可以很多种,参考上面的例子写

int cmp( const void *a ,const void *b)
{
return (*(In *)a).data > (*(In *)b).data ? 1 : -1;
}

qsort(s,100,sizeof(s[0]),cmp);

五、对结构体二级排序

struct In
{
int x;
int y;
}s[100];

//按照x从小到大排序,当x相等时按照y从大到小排序

int cmp( const void *a , const void *b )
{
struct In *c = (In *)a;
struct In *d = (In *)b;
if(c->x != d->x) return c->x - d->x;
else return d->y - c->y;
}

qsort(s,100,sizeof(s[0]),cmp);

六、对字符串进行排序

struct In
{
int data;
char str[100];
}s[100];

//按照结构体中字符串str的字典顺序排序

int cmp ( const void *a , const void *b )
{
return strcmp( (*(In *)a)->str , (*(In *)b)->str );
}

qsort(s,100,sizeof(s[0]),cmp);

七、计算几何中求凸包的cmp

int cmp(const void *a,const void *b) //重点cmp函数,把除了1点外的所有点,旋转角度排序
{
struct point *c=(point *)a;
struct point *d=(point *)b;
if( calc(*c,*d,p[1]) < 0) return 1;
else if( !calc(*c,*d,p[1]) && dis(c->x,c->y,p[1].x,p[1].y) < dis(d->x,d->y,p[1].x,p[1].y)) //如果在一条直线上,则把远的放在前面
return 1;
else return -1;
}

PS:

其中的qsort函数包含在<stdlib.h>的头文件里,strcmp包含在<string.h>的头文件里
posted @ 2008-05-06 01:00 IP 阅读(152) | 评论 (0)编辑 收藏

C/C++中的日期和时间

撰文/周翔


摘要:
本文从介绍基础概念入手,探讨了在C/C++中对日期和时间操作所用到的数据结构和函数,并对计时、时间的获取、时间的计算和显示格式等方面进行了阐述。本文还通过大量的实例向你展示了time.h头文件中声明的各种函数和数据结构的详细使用方法。

关键字:UTC(世界标准时间),Calendar Time(日历时间),epoch(时间点),clock tick(时钟计时单元)

1.概念
在C/C++中,对字符串的操作有很多值得注意的问题,同样,C/C++对时间的操作也有许多值得大家注意的地方。最近,在技术群中有很多网友也多次问到过C++语言中对时间的操作、获取和显示等等的问题。下面,在这篇文章中,笔者将主要介绍在C/C++中时间和日期的使用方法.

通过学习许多C/C++库,你可以有很多操作、使用时间的方法。但在这之前你需要了解一些“时间”和“日期”的概念,主要有以下几个:

Coordinated Universal Time(UTC):协调世界时,又称为世界标准时间,也就是大家所熟知的格林威治标准时间(Greenwich Mean Time,GMT)。比如,中国内地的时间与UTC的时差为+8,也就是UTC+8。美国是UTC-5。

Calendar Time:日历时间,是用“从一个标准时间点到此时的时间经过的秒数”来表示的时间。这个标准时间点对不同的编译器来说会有所不同,但对一个编译系统来说,这个标准时间点是不变的,该编译系统中的时间对应的日历时间都通过该标准时间点来衡量,所以可以说日历时间是“相对时间”,但是无论你在哪一个时区,在同一时刻对同一个标准时间点来说,日历时间都是一样的。

epoch:时间点。时间点在标准C/C++中是一个整数,它用此时的时间和标准时间点相差的秒数(即日历时间)来表示。

clock tick:时钟计时单元(而不把它叫做时钟滴答次数),一个时钟计时单元的时间长短是由CPU控制的。一个clock tick不是CPU的一个时钟周期,而是C/C++的一个基本计时单位。

我们可以使用ANSI标准库中的time.h头文件。这个头文件中定义的时间和日期所使用的方法,无论是在结构定义,还是命名,都具有明显的C语言风格。下面,我将说明在C/C++中怎样使用日期的时间功能。

2. 计时

C/C++中的计时函数是clock(),而与其相关的数据类型是clock_t。在MSDN中,查得对clock函数定义如下:

clock_t clock( void );

这个函数返回从“开启这个程序进程”到“程序中调用clock()函数”时之间的CPU时钟计时单元(clock tick)数,在MSDN中称之为挂钟时间(wal-clock)。其中clock_t是用来保存时间的数据类型,在time.h文件中,我们可以找到对它的定义:

#ifndef _CLOCK_T_DEFINED
typedef long clock_t;
#define _CLOCK_T_DEFINED
#endif

很明显,clock_t是一个长整形数。在time.h文件中,还定义了一个常量CLOCKS_PER_SEC,它用来表示一秒钟会有多少个时钟计时单元,其定义如下:

#define CLOCKS_PER_SEC ((clock_t)1000)

可以看到每过千分之一秒(1毫秒),调用clock()函数返回的值就加1。下面举个例子,你可以使用公式clock()/CLOCKS_PER_SEC来计算一个进程自身的运行时间:

void elapsed_time()
{
printf("Elapsed time:%u secs.\n",clock()/CLOCKS_PER_SEC);
}

当然,你也可以用clock函数来计算你的机器运行一个循环或者处理其它事件到底花了多少时间:

#include “stdio.h”
#include “stdlib.h”
#include “time.h”

int main( void )
{
   long    i = 10000000L;
   clock_t start, finish;
   double  duration;
   /* 测量一个事件持续的时间*/
   printf( "Time to do %ld empty loops is ", i );
   start = clock();
   while( i-- )      ;
   finish = clock();
   duration = (double)(finish - start) / CLOCKS_PER_SEC;
   printf( "%f seconds\n", duration );
   system("pause");
}

在笔者的机器上,运行结果如下:

Time to do 10000000 empty loops is 0.03000 seconds

上面我们看到时钟计时单元的长度为1毫秒,那么计时的精度也为1毫秒,那么我们可不可以通过改变CLOCKS_PER_SEC的定义,通过把它定义的大一些,从而使计时精度更高呢?通过尝试,你会发现这样是不行的。在标准C/C++中,最小的计时单位是一毫秒。

3.与日期和时间相关的数据结构

在标准C/C++中,我们可通过tm结构来获得日期和时间,tm结构在time.h中的定义如下:

#ifndef _TM_DEFINED
struct tm {
        int tm_sec;     /* 秒 – 取值区间为[0,59] */
        int tm_min;     /* 分 - 取值区间为[0,59] */
        int tm_hour;    /* 时 - 取值区间为[0,23] */
        int tm_mday;    /* 一个月中的日期 - 取值区间为[1,31] */
        int tm_mon;     /* 月份(从一月开始,0代表一月) - 取值区间为[0,11] */
        int tm_year;    /* 年份,其值等于实际年份减去1900 */
        int tm_wday;    /* 星期 – 取值区间为[0,6],其中0代表星期天,1代表星期一,以此类推 */
        int tm_yday;    /* 从每年的1月1日开始的天数 – 取值区间为[0,365],其中0代表1月1日,1代表1月2日,以此类推 */
        int tm_isdst;   /* 夏令时标识符,实行夏令时的时候,tm_isdst为正。不实行夏令时的进候,tm_isdst为0;不了解情况时,tm_isdst()为负。*/
        };
#define _TM_DEFINED
#endif

ANSI C标准称使用tm结构的这种时间表示为分解时间(broken-down time)。

而日历时间(Calendar Time)是通过time_t数据类型来表示的,用time_t表示的时间(日历时间)是从一个时间点(例如:1970年1月1日0时0分0秒)到此时的秒数。在time.h中,我们也可以看到time_t是一个长整型数:

#ifndef _TIME_T_DEFINED
typedef long time_t;         /* 时间值 */
#define _TIME_T_DEFINED      /* 避免重复定义 time_t */
#endif

大家可能会产生疑问:既然time_t实际上是长整型,到未来的某一天,从一个时间点(一般是1970年1月1日0时0分0秒)到那时的秒数(即日历时间)超出了长整形所能表示的数的范围怎么办?对time_t数据类型的值来说,它所表示的时间不能晚于2038年1月18日19时14分07秒。为了能够表示更久远的时间,一些编译器厂商引入了64位甚至更长的整形数来保存日历时间。比如微软在Visual C++中采用了__time64_t数据类型来保存日历时间,并通过_time64()函数来获得日历时间(而不是通过使用32位字的time()函数),这样就可以通过该数据类型保存3001年1月1日0时0分0秒(不包括该时间点)之前的时间。

在time.h头文件中,我们还可以看到一些函数,它们都是以time_t为参数类型或返回值类型的函数:

double difftime(time_t time1, time_t time0);
time_t mktime(struct tm * timeptr);
time_t time(time_t * timer);
char * asctime(const struct tm * timeptr);
char * ctime(const time_t *timer);

此外,time.h还提供了两种不同的函数将日历时间(一个用time_t表示的整数)转换为我们平时看到的把年月日时分秒分开显示的时间格式tm:

struct tm * gmtime(const time_t *timer);                                         
struct tm * localtime(const time_t * timer);

通过查阅MSDN,我们可以知道Microsoft C/C++ 7.0中时间点的值(time_t对象的值)是从1899年12月31日0时0分0秒到该时间点所经过的秒数,而其它各种版本的Microsoft C/C++和所有不同版本的Visual C++都是计算的从1970年1月1日0时0分0秒到该时间点所经过的秒数。

4.与日期和时间相关的函数及应用
在本节,我将向大家展示怎样利用time.h中声明的函数对时间进行操作。这些操作包括取当前时间、计算时间间隔、以不同的形式显示时间等内容。

4.1 获得日历时间

我们可以通过time()函数来获得日历时间(Calendar Time),其原型为:

time_t time(time_t * timer);

如果你已经声明了参数timer,你可以从参数timer返回现在的日历时间,同时也可以通过返回值返回现在的日历时间,即从一个时间点(例如:1970年1月1日0时0分0秒)到现在此时的秒数。如果参数为空(NUL),函数将只通过返回值返回现在的日历时间,比如下面这个例子用来显示当前的日历时间:

#include "time.h"
#include "stdio.h"
int main(void)
{
struct tm *ptr;
time_t lt;
lt =time(NUL);
printf("The Calendar Time now is %d\n",lt);
return 0;
}

运行的结果与当时的时间有关,我当时运行的结果是:

The Calendar Time now is 1122707619

其中1122707619就是我运行程序时的日历时间。即从1970年1月1日0时0分0秒到此时的秒数。

4.2 获得日期和时间

这里说的日期和时间就是我们平时所说的年、月、日、时、分、秒等信息。从第2节我们已经知道这些信息都保存在一个名为tm的结构体中,那么如何将一个日历时间保存为一个tm结构的对象呢?

其中可以使用的函数是gmtime()和localtime(),这两个函数的原型为:

struct tm * gmtime(const time_t *timer);                                         
struct tm * localtime(const time_t * timer);

其中gmtime()函数是将日历时间转化为世界标准时间(即格林尼治时间),并返回一个tm结构体来保存这个时间,而localtime()函数是将日历时间转化为本地时间。比如现在用gmtime()函数获得的世界标准时间是2005年7月30日7点18分20秒,那么我用localtime()函数在中国地区获得的本地时间会比世界标准时间晚8个小时,即2005年7月30日15点18分20秒。下面是个例子:

#include "time.h"
#include "stdio.h"
int main(void)
{
struct tm *local;
time_t t;
t=time(NUL);
local=localtime(&t);
printf("Local hour is: %d\n",local->tm_hour);
local=gmtime(&t);
printf("UTC hour is: %d\n",local->tm_hour);
return 0;
}

运行结果是:

Local hour is: 15
UTC hour is: 7

4.3 固定的时间格式

我们可以通过asctime()函数和ctime()函数将时间以固定的格式显示出来,两者的返回值都是char*型的字符串。返回的时间格式为:

星期几 月份 日期 时:分:秒 年\n\0
例如:Wed Jan 02 02:03:55 1980\n\0

其中\n是一个换行符,\0是一个空字符,表示字符串结束。下面是两个函数的原型:

char * asctime(const struct tm * timeptr);
char * ctime(const time_t *timer);

其中asctime()函数是通过tm结构来生成具有固定格式的保存时间信息的字符串,而ctime()是通过日历时间来生成时间字符串。这样的话,asctime()函数只是把tm结构对象中的各个域填到时间字符串的相应位置就行了,而ctime()函数需要先参照本地的时间设置,把日历时间转化为本地时间,然后再生成格式化后的字符串。在下面,如果t是一个非空的time_t变量的话,那么:

printf(ctime(&t));

等价于:

struct tm *ptr;
ptr=localtime(&t);
printf(asctime(ptr));

那么,下面这个程序的两条printf语句输出的结果就是不同的了(除非你将本地时区设为世界标准时间所在的时区):

#include "time.h"
#include "stdio.h"
int main(void)
{
struct tm *ptr;
time_t lt;
lt =time(NUL);
ptr=gmtime(&lt);
printf(asctime(ptr));
printf(ctime(&lt));
return 0;
}

运行结果:

Sat Jul 30 08:43:03 2005
Sat Jul 30 16:43:03 2005

4.4 自定义时间格式

我们可以使用strftime()函数将时间格式化为我们想要的格式。它的原型如下:

size_t strftime(
   char *strDest,
   size_t maxsize,
   const char *format,
   const struct tm *timeptr
);

我们可以根据format指向字符串中格式命令把timeptr中保存的时间信息放在strDest指向的字符串中,最多向strDest中存放maxsize个字符。该函数返回向strDest指向的字符串中放置的字符数。

函数strftime()的操作有些类似于sprintf():识别以百分号(%)开始的格式命令集合,格式化输出结果放在一个字符串中。格式化命令说明串strDest中各种日期和时间信息的确切表示方法。格式串中的其他字符原样放进串中。格式命令列在下面,它们是区分大小写的。

%a 星期几的简写
%A 星期几的全称
%b 月分的简写
%B 月份的全称
%c 标准的日期的时间串
%C 年份的后两位数字
%d 十进制表示的每月的第几天
%D 月/天/年
%e 在两字符域中,十进制表示的每月的第几天
%F 年-月-日
%g 年份的后两位数字,使用基于周的年
%G 年分,使用基于周的年
%h 简写的月份名
%H 24小时制的小时
%I 12小时制的小时
%j 十进制表示的每年的第几天
%m 十进制表示的月份
%M 十时制表示的分钟数
%n 新行符
%p 本地的AM或PM的等价显示
%r 12小时的时间
%R 显示小时和分钟:hh:mm
%S 十进制的秒数
%t 水平制表符
%T 显示时分秒:hh:mm:ss
%u 每周的第几天,星期一为第一天 (值从0到6,星期一为0)
%U 第年的第几周,把星期日做为第一天(值从0到53)
%V 每年的第几周,使用基于周的年
%w 十进制表示的星期几(值从0到6,星期天为0)
%W 每年的第几周,把星期一做为第一天(值从0到53)
%x 标准的日期串
%X 标准的时间串
%y 不带世纪的十进制年份(值从0到99)
%Y 带世纪部分的十进制年份
%z,%Z 时区名称,如果不能得到时区名称则返回空字符。
%% 百分号

如果想显示现在是几点了,并以12小时制显示,就象下面这段程序:

#include “time.h”
#include “stdio.h”
int main(void)
{
struct tm *ptr;
time_t lt;
char str[80];
lt=time(NUL);
ptr=localtime(&lt);
strftime(str,100,"It is now %I %p",ptr);
printf(str);
return 0;
}

其运行结果为:
It is now 4PM

而下面的程序则显示当前的完整日期:

#include <stdio.h>
#include <time.h>

void main( void )
{
        struct tm *newtime;
        char tmpbuf[128];
        time_t lt1;
        time( &lt1 );
        newtime=localtime(&lt1);
        strftime( tmpbuf, 128, "Today is %A, day %d of %B in the year %Y.\n", newtime);
        printf(tmpbuf);
}

运行结果:

Today is Saturday, day 30 of July in the year 2005.

4.5 计算持续时间的长度

有时候在实际应用中要计算一个事件持续的时间长度,比如计算打字速度。在第1节计时部分中,我已经用clock函数举了一个例子。Clock()函数可以精确到毫秒级。同时,我们也可以使用difftime()函数,但它只能精确到秒。该函数的定义如下:

double difftime(time_t time1, time_t time0);

虽然该函数返回的以秒计算的时间间隔是double类型的,但这并不说明该时间具有同double一样的精确度,这是由它的参数觉得的(time_t是以秒为单位计算的)。比如下面一段程序:

#include "time.h"
#include "stdio.h"
#include "stdlib.h"
int main(void)
{
time_t start,end;
start = time(NUL);
system("pause");
end = time(NUL);
printf("The pause used %f seconds.\n",difftime(end,start));//<-
system("pause");
return 0;
}

运行结果为:
请按任意键继续. . .
The pause used 2.000000 seconds.
请按任意键继续. . .

可以想像,暂停的时间并不那么巧是整整2秒钟。其实,你将上面程序的带有“//<-”注释的一行用下面的一行代码替换:

printf("The pause used %f seconds.\n",end-start);

其运行结果是一样的。

4.6 分解时间转化为日历时间

这里说的分解时间就是以年、月、日、时、分、秒等分量保存的时间结构,在C/C++中是tm结构。我们可以使用mktime()函数将用tm结构表示的时间转化为日历时间。其函数原型如下:

time_t mktime(struct tm * timeptr);

其返回值就是转化后的日历时间。这样我们就可以先制定一个分解时间,然后对这个时间进行操作了,下面的例子可以计算出1997年7月1日是星期几:

#include "time.h"
#include "stdio.h"
#include "stdlib.h"
int main(void)
{
struct tm t;
time_t t_of_day;
t.tm_year=1997-1900;
t.tm_mon=6;
t.tm_mday=1;
t.tm_hour=0;
t.tm_min=0;
t.tm_sec=1;
t.tm_isdst=0;
t_of_day=mktime(&t);
printf(ctime(&t_of_day));
return 0;
}

运行结果:

Tue Jul 01 00:00:01 1997

现在注意了,有了mktime()函数,是不是我们可以操作现在之前的任何时间呢?你可以通过这种办法算出1945年8月15号是星期几吗?答案是否定的。因为这个时间在1970年1月1日之前,所以在大多数编译器中,这样的程序虽然可以编译通过,但运行时会异常终止。

5.总结

本文介绍了标准C/C++中的有关日期和时间的概念,并通过各种实例讲述了这些函数和数据结构的使用方法。笔者认为,和时间相关的一些概念是相当重要的,理解这些概念是理解各种时间格式的转换的基础,更是应用这些函数和数据结构的基础。

参考文献

[1] 标准C++程序设计教程,电子工业出版社,2003。
[2] MSDN Library, Microsoft Corporation,2003。

(2005年7月31日更新)

posted @ 2008-05-06 00:59 IP 阅读(158) | 评论 (0)编辑 收藏

2008年5月2日 #

<作者:陶汉军>

本文面向的读者:学习过C++程序设计语言(也就是说学习过Template),但是还没有接触过STL的STL的初学者。这实际上是我学习STL的一篇笔记,老鸟就不用看了。

什么是泛型程序设计
  我们可以简单的理解为:使用模板的程序设计就是泛型程序设计。就像我们我们可以简单的理解面向对象程序设计就是使用虚函数的程序设计一样。

STL是什么
  作为一个C++程序设计者,STL是一种不可忽视的技术。Sandard Template Library (STL):
标准模板库,更准确的说是 C++ 程序设计语言标准模板库。学习过MFC的人知道,MFC是微软公司创建的 C++ 类库。而与之类似的是 STL 是模板库,只不过 STL 是 ANSI/ISO 标准的一部分,而 MFC 只不过是微软的一个产品而已。也就是说STL是所有C++编译器和所有操作系统平台都支持的一种库,说它是一种库是因为,虽然STL是一种标准,也就是说对所有的编译器来说,提供给C++程序设计者的接口都是一样的。也就是说同一段STL代码在不同编译器和操作系统平台上运行的结果都是相同的,但是底层实现可以是不同的。 令人兴奋的是,STL的使用者并不需要了解它的底层实现。 试想一下,如果我们有一把能打开所有锁的钥匙,那将是多么令人疯狂啊。嘎嘎。这个歪梦我做了20多年鸟。
  STL的目的是标准化组件,这样你就不用重新开发它们了。你可以仅仅使用这些现成的组件。STL现在是C++的一部分,因此不用额外安装什么。它被内建在你的编译器之内。

为什么我们需要学习STL
  • STL是 C++的ANSI/ISO 标准的一部分,可以用于所有C++语言编译器和所有平台(Windows/Unix/Linux..)。STL的同一版本在任意硬件配置下都是可用的;
  • STL 提供了大量的可复用软件组织。例如,程序员再也不用自己设计排序,搜索算法了,这些都已经是STL的一部分了。嘎嘎,有意思吧;
  • 使用STL 的应用程序保证了得到的实现在处理速度和内存利用方面都是高效的,因为STL设计者们已经为我们考虑好了;
  • 使用STL编写的代码更容易修改和阅读,这是当然的鸟。因为代码更短了,很多基础工作代码已经被组件化了;
  • 使用简单,虽然内部实现很复杂;

  虽然,STL的优点甚多,但是STL的语法实在令初学者人头疼,许多人望而却步。可是STL是每个C++程序设计者迟早都要啃的一块骨头。因为越来越多的C++代码是用STL编写的,看不懂麻烦就大鸟。越来越多的人在用STL,不懂就无法和别人一起合作了。好事多磨嘛,早点学习早点解脱。

下面让我们来看几段代码吧:(你觉得头疼就不要看了)

//stl_cpp_1.cpp
#include <iostream>
double mean(double *array, size_t n)
{
double m=0;
for(size_t i=0; i<n; ++i){
m += array[i];
}
return m/n;
}
int main(void)
{
double a[] = {1, 2, 3, 4, 5};
std::cout<<mean(a, 5)<<std::endl;    // will print 3
return 0;
}
好懂吧,除了那个std有点让人不舒服以外?这是一段普通的没有使用STL的C++代码。再看下面一段:
//stl_cpp_2.cpp
#include <vector>
#include <iostream>
int main(void)
{
std::vector<double> a;
std::vector<double>::const_iterator i;
a.push_back(1);
a.push_back(2);
a.push_back(3);
a.push_back(4);
a.push_back(5);
for(i=a.begin(); i!=a.end(); ++i){
std::cout<<(*i)<<std::endl;
}
return 0;
}
  如果你真的没有接触过STL的话,你会问,呀,vector 是啥呀?我会告诉你,那是一排美女。嘎嘎。这可不是个比喻,表想歪鸟。这是一段纯种的STL代码,看到尖括号了吧,知道那是模板了吧。看到a.push_back(5),a.begin(),a.end()你不感觉奇怪么?可是我们并没有定义这些函数啊。
//stl_cpp_3.cpp
#include <vector>
#include <iostream>
int main(void)
{
std::vector<int> q;
q.push_back(10);
q.push_back(11);
q.push_back(12);
std::vector<int> v;
for(int i=0; i<5; ++i){
v.push_back(i);
}
std::vector<int>::iterator it = v.begin() + 1;
it = v.insert(it, 33);
v.insert(it, q.begin(), q.end());
it = v.begin() + 3;
v.insert(it, 3, -1);
it = v.begin() + 4;
v.erase(it);
it = v.begin() + 1;
v.erase(it, it + 4);
v.clear();
return 0;
}
  这一段你又看到了新东西了吧,iterator???不罗嗦了,等你看完这篇文章,回头再看就简单了。在正式介绍STL之前,我们需要花点时间来了解一下模板和命名空间。
  关于模板的其他细节,读者可以参阅《C++ Templates 中文版》(有点费脑子哦)。在这里,我只简单的介绍一下模板类和函数模板的概念。
  模板是C++中实现代码重用机制的一种工具,可以实现类型参数化,把类型定义为参数。函数模板和类模板允许用户构造模板函数和模板类。


图1

下面我们来看一段函数模板的例子:
//stl_cpp_4.cpp
#include<iostream.h>
#include<string.h>
//定义函数模板
template<class T>  //template 是关键字,T 表示一种待实例化的类型
//template<typename T>  也是对的
T max(T a, T b)//函数模板,函数名为 max,此函数有2个T类型的参数,返回类型为T
{
return (a>b)?a:b;
}
//在此例实例化的时候,T可以是多种类型的,int,char,string…
int main(void)
{
int x=2,y=6;
double x1=9.123,y1=12.6543;
cout<<"把T实例化为int:"<<max(x,y)<<endl;//实例化函数模板,把T实例化为int
cout<<"把T实例化为double:"<<max(x1,y1)<<endl;
 //实例化函数模板,把T实例化为double
 getchar(); //这一行代码用来在dos下查看结果,也可以用cin.get();
}
下面再看看,类模板:
//stl_cpp_5.cpp
#include<iostream.h>
//定义名为ex_class的类模板
template < typename T>  class ex_class
{
T value;
public:
ex_class(T v) { value=v; }
void set_value(T v) { value=v; }
T get_value(void) {return value;}
};
//main()函数中测试ex_class类模板
int main(void)
{
//测试int类型数据
ex_class <int> a(5),b(10);
cout<<"a.value:"<<a.get_value()<<endl;
cout<<"b.value:"<<b.get_value()<<endl;
//测试char类型数据
ex_class <char> ch(''A'');
cout<<"ch.value:"<<ch.get_value()<<endl;
ch.set_value(''a'');
cout<<"ch.value:"<<ch.get_value()<<endl;
//测试double类型数据
ex_class <double> x(5.5);
cout<<"x.value:"<<x.get_value()<<endl;
x.set_value(7.5);
cout<<"x.value:"<<x.get_value()<<endl;
}
命名空间(名字空间)
  命名空间是C++的一种机制,用来把单个标识符下的大量有逻辑联系的程序实体组合到一起。此标识符作为此组群的名字。命名空间用关键字namespace 来定义:
//stl_cpp_6.cpp
#include <iostream>
using namespace std;
namespace printA
{
print()  {cout<<"using namespace printA….."<<endl; };
}
namespace printB
{
print()  {cout<<"using namespace printB….."<<endl; };
}
int main(void)
{
printA::print();    //测试命名空间printA, ::是作用域解析运算符
printB::print();
}
命名空间可以嵌套定义:
namespace A
{
functiong1(){};
namespace B
{ }
}
  一个namespace是指一个具名的范围(named scope)。namespace被用来将相关的声明划归在一起,将不相关的代码部分隔开。命名空间只是命名了一个特殊的作用域,当程序很大,而且需要多人合作的时候,命名空间就显得特别的重要。比如2个程序员A,B 在同一个程序中定义了函数 pop(),如果没有使用命名空间,则会出错,而且这种错误难以检测出来。为了安全起见,他们可以定义不同的命名空间 A和B,在用的时候可以使用A::pop()和B::pop()来区分。
  在STL中,标准库的全部成员在预先定义的命名空间std中。如果要用类模板vector ,有两种方法:一是在程序的前面添加预处理指令:
   #include <vector>
using namespace std;
第二种方法是:
   #include <vector>
using std::vector;
动态绑定和静态绑定
  所谓绑定是指,对于参与多态行为的类型,他们具有多态行为的接口是在公共基类的设计中就预先确定的。而非绑定则对于参与多态行为的类型,他们的接口没有预先定义。
  在C++中通过继承实现的多态是动态绑定,通过模板实现的多态是静态绑定。动态绑定的接口是在运行期间(动态)完成的,静态绑定的接口是在编译期间(静态)完成的。好了,有了以上的知识我们可以来学习STL 了。

STL 的组成
  STL有三大核心部分:容器(Container)、算法(Algorithms)、迭代器(Iterator),容器适配器(container adaptor),函数对象(functor),除此之外还有STL其他标准组件。
  • 容器:装东西的东西,装水的杯子,装咸水的大海,装人的教室……STL里的容器是可容纳一些数据的模板类;
  • 算法:就是往杯子里倒水,往大海里排污,从教室里撵人……STL里的算法,就是处理容器里面数据的方法,操作;
  • 迭代器:往杯子里倒水的水壶,排污的管道,撵人的那个物业管理人员……STL里的迭代器:遍历容器中数据的对象;

  对存储于容器中的数据进行处理时,迭代器能从一个成员移向另一个成员。他能按预先定义的顺序在某些容器中的成员间移动。对普通的一维数组、向量、双端队列和列表来说,迭代器是一种指针。
知道了吧?嘎嘎,当然了,你猜到了,那是我在瞎扯蛋。

下面让我们来看看专家是怎么说的:

  • 容器(container):容器是数据在内存中组织的方法,例如,数组、堆栈、队列、链表或二叉树(不过这些都不是STL标准容器)。STL中的容器是一种存储T(Template)类型值的有限集合的数据结构,容器的内部实现一般是类。这些值可以是对象本身,如果数据类型T代表的是Class的话。
  • 算法(algorithm):算法是应用在容器上以各种方法处理其内容的行为或功能。例如,有对容器内容排序、复制、检索和合并的算法。在STL中,算法是由模板函数表现的。这些函数不是容器类的成员函数。相反,它们是独立的函数。令人吃惊的特点之一就是其算法如此通用。不仅可以将其用于STL容器,而且可以用于普通的C++数组或任何其他应用程序指定的容器。
  • 迭代器(iterator):一旦选定一种容器类型和数据行为(算法),那么剩下唯一要他做的就是用迭代器使其相互作用。可以把达代器看作一个指向容器中元素的普通指针。可以如递增一个指针那样递增迭代器,使其依次指向容器中每一个后继的元素。迭代器是STL的一个关键部分,因为它将算法和容器连在一起。

下面我将依次介绍STL的这三个主要组件。

容器
  STL中的容器有队列容器和关联容器,容器适配器(congtainer adapters:stack,queue,priority queue),位集(bit_set),串包(string_package)等等。
  在本文中,我将介绍list,vector,deque等队列容器,和set和multisets,map和multimaps等关联容器,一共7种基本容器类。
  队列容器(顺序容器):队列容器按照线性排列来存储T类型值的集合,队列的每个成员都有自己的特有的位置。顺序容器有向量类型、双端队列类型、列表类型三种。
基本容器——顺序容器
  向量(vector容器类):#include <vector>,vector是一种动态数组,是基本数组的类模板。其内部定义了很多基本操作。既然这是一个类,那么它就会有自己的构造函数。vector 类中定义了4中种构造函数:

  • 默认构造函数,构造一个初始长度为0的空向量,
    如:vector<int> v1;
  • 带有单个整形参数的构造函数,此参数描述了向量的初始大小。这个构造函数还有一个可选的参数,这是一个类型为T的实例,描述了各个向量种各成员的初始值;
    如:vector<int> v2(init_size,0); 如果预先定义了:int init_size;他的成员值都被初始化为0;
  • 复制构造函数,构造一个新的向量,作为已存在的向量的完全复制,
    如:vector<int> v3(v2);
  • 带两个常量参数的构造函数,产生初始值为一个区间的向量。区间由一个半开区间[first,last](MS word的显示可能会有问题,first前是一个左方括号,last后面是一个右圆括号)来指定。
    如:vector<int> v4(first,last)
  • 下面一个例子用的是第四种构造方法,其它的方法读者可以自己试试。

    //stl_cpp_7.cpp
    //程序:初始化演示
    #include <cstring>
    #include <vector>
    #include <iostream>
    using namespace std;
    int ar[10] = {  12, 45, 234, 64, 12, 35, 63, 23, 12, 55  };
    char* str = "Hello World";
    int main(void)
    {
    vector <int> vec1(ar, ar+10);          //first=ar,last=ar+10,不包括ar+10
    vector <char> vec2(str, str+strlen(str));  //first=str,last= str+strlen(str),不包括最后一个
    cout<<"vec1:"<<endl;
    //打印vec1和vec2,const_iterator是迭代器,后面会讲到
    //当然,也可以用for (int i=0; i<vec1.size(); i++)cout << vec[i];输出
    //size()是vector的一个成员函数
    for(vector<int>::const_iterator p=vec1.begin();p!=vec1.end(); ++p)
    cout<<*p;
    cout<<''\n''<<"vec2:"<<endl;
    for(vector<char>::const_iterator p1=vec2.begin();p1!=vec2.end(); ++p1)
    cout<<*p1;
    getchar();
    return 0;
    }
    
      为了帮助理解向量的概念,这里写了一个小例子,其中用到了vector的成员函数:begin(),end(),push_back(),assign(),front(),back(),erase(),empty(),at(),size()。
    //stl_cpp_8.cpp
    #include <iostream>
    #include <vector>
    using namespace std;
    typedef vector<int> INTVECTOR;//自定义类型INTVECTOR
    //测试vector容器的功能
    void main(void)
    {
    //vec1对象初始为空
    INTVECTOR vec1;
    //vec2对象最初有10个值为6的元素
    INTVECTOR vec2(10,6);
    //vec3对象最初有3个值为6的元素,拷贝构造
    INTVECTOR vec3(vec2.begin(),vec2.begin()+3);
    //声明一个名为i的双向迭代器
    INTVECTOR::iterator i;
    //从前向后显示vec1中的数据
    cout<<"vec1.begin()--vec1.end():"<<endl;
    for (i =vec1.begin(); i !=vec1.end(); ++i)
    cout << *i << " ";
    cout << endl;
    //从前向后显示vec2中的数据
    cout<<"vec2.begin()--vec2.end():"<<endl;
    for (i =vec2.begin(); i !=vec2.end(); ++i)
    cout << *i << " ";
    cout << endl;
    //从前向后显示vec3中的数据
    cout<<"vec3.begin()--vec3.end():"<<endl;
    for (i =vec3.begin(); i !=vec3.end(); ++i)
    cout << *i << " ";
    cout << endl;
    //测试添加和插入成员函数,vector不支持从前插入
    vec1.push_back(2);//从后面添加一个成员
    vec1.push_back(4);
    vec1.insert(vec1.begin()+1,5);//在vec1第一个的位置上插入成员5
    //从vec1第一的位置开始插入vec3的所有成员
    vec1.insert(vec1.begin()+1,vec3.begin(),vec3.end());
    cout<<"after push() and insert() now the vec1 is:" <<endl;
    for (i =vec1.begin(); i !=vec1.end(); ++i)
    cout << *i << " ";
    cout << endl;
    //测试赋值成员函数
    vec2.assign(8,1);   // 重新给vec2赋值,8个成员的初始值都为1
    cout<<"vec2.assign(8,1):" <<endl;
    for (i =vec2.begin(); i !=vec2.end(); ++i)
    cout << *i << " ";
    cout << endl;
    //测试引用类函数
    cout<<"vec1.front()="<<vec1.front()<<endl;//vec1第零个成员
    cout<<"vec1.back()="<<vec1.back()<<endl;//vec1的最后一个成员
    cout<<"vec1.at(4)="<<vec1.at(4)<<endl;//vec1的第五个成员
    cout<<"vec1[4]="<<vec1[4]<<endl;
    //测试移出和删除
    vec1.pop_back();//将最后一个成员移出vec1
    vec1.erase(vec1.begin()+1,vec1.end()-2);//删除成员
    cout<<"vec1.pop_back() and vec1.erase():" <<endl;
    for (i =vec1.begin(); i !=vec1.end(); ++i)
    cout << *i << " ";
    cout << endl;
    //显示序列的状态信息
    cout<<"vec1.size(): "<<vec1.size()<<endl;//打印成员个数
    cout<<"vec1.empty(): "<<vec1.empty()<<endl;//清空
    }
    
      push_back()是将数据放入vector(向量)或deque(双端队列)的标准函数。Insert()是一个与之类似的函数,然而它在所有容器中都可以使用,但是用法更加复杂。end()实际上是取末尾加一,以便让循环正确运行--它返回的指针指向最靠近数组界限的数据。
      在Java里面也有向量的概念。Java中的向量是对象的集合。其中,各元素可以不必同类型,元素可以增加和删除,不能直接加入原始数据类型。

    双端队列(qeque容器类):#include <deque>
      deque(读音:deck,意即:double queue)容器类与vector类似,支持随机访问和快速插入删除,它在容器中某一位置上的操作所花费的是线性时间。与vector不同的是,deque还支持从开始端插入数据:
    push_front()。此外deque也不支持与vector的capacity()、reserve()类似的操作。
    //stl_cpp_9.cpp
    #include <iostream>
    #include <deque>
    using namespace std;
    typedef deque<int> INTDEQUE;//有些人很讨厌这种定义法,呵呵
    //从前向后显示deque队列的全部元素
    void put_deque(INTDEQUE deque, char *name)
    {
    INTDEQUE::iterator pdeque;//仍然使用迭代器输出
    cout << "The contents of " << name << " : ";
    for(pdeque = deque.begin(); pdeque != deque.end(); pdeque++)
    cout << *pdeque << " ";//注意有 "*"号哦,没有"*"号的话会报错
    cout<<endl;
    }
    //测试deqtor容器的功能
    void main(void)
    {
    //deq1对象初始为空
    INTDEQUE deq1;
    //deq2对象最初有10个值为6的元素
    INTDEQUE deq2(10,6);
    //deq3对象最初有3个值为6的元素
    //声明一个名为i的双向迭代器变量
    INTDEQUE::iterator i;
    //从前向后显示deq1中的数据
    put_deque(deq1,"deq1");
    //从前向后显示deq2中的数据
    put_deque(deq2,"deq2");
    //从deq1序列后面添加两个元素
    deq1.push_back(2);
    deq1.push_back(4);
    cout<<"deq1.push_back(2) and deq1.push_back(4):"<<endl;
    put_deque(deq1,"deq1");
    //从deq1序列前面添加两个元素
    deq1.push_front(5);
    deq1.push_front(7);
    cout<<"deq1.push_front(5) and deq1.push_front(7):"<<endl;
    put_deque(deq1,"deq1");
    //在deq1序列中间插入数据
    deq1.insert(deq1.begin()+1,3,9);
    cout<<"deq1.insert(deq1.begin()+1,3,9):"<<endl;
    put_deque(deq1,"deq1");
    //测试引用类函数
    cout<<"deq1.at(4)="<<deq1.at(4)<<endl;
    cout<<"deq1[4]="<<deq1[4]<<endl;
    deq1.at(1)=10;
    deq1[2]=12;
    cout<<"deq1.at(1)=10 and deq1[2]=12 :"<<endl;
    put_deque(deq1,"deq1");
    //从deq1序列的前后各移去一个元素
    deq1.pop_front();
    deq1.pop_back();
    cout<<"deq1.pop_front() and deq1.pop_back():"<<endl;
    put_deque(deq1,"deq1");
    //清除deq1中的第2个元素
    deq1.erase(deq1.begin()+1);
    cout<<"deq1.erase(deq1.begin()+1):"<<endl;
    put_deque(deq1,"deq1");
    //对deq2赋值并显示
    deq2.assign(8,1);
    cout<<"deq2.assign(8,1):"<<endl;
    put_deque(deq2,"deq2");
    }
    
      上面我们演示了deque如何进行插入删除等操作,像erase(),assign()是大多数容器都有的操作。关于deque的其他操作请参阅附录。

    表(List容器类):#include <list>
       List又叫链表,是一种双线性列表,只能顺序访问(从前向后或者从后向前),图2是list的数据组织形式。与  前面两种容器类有一个明显的区别就是:它不支持随机访问。要访问表中某个下标处的项需要从表头或表尾处(接近该下标的一端)开始循环。而且缺少下标预算符:operator[]。


    图2

      同时,list仍然包涵了erase(),begin(),end(),insert(),push_back(),push_front()这些基本函数,下面我们来演示一下list的其他函数功能。

    merge():合并两个排序列表;
    splice():拼接两个列表;
    sort():列表的排序;
    //stl_cpp_10.cpp
    #include <iostream>
    #include <string>
    #include <list>
    using namespace std;
    void PrintIt(list<int> n)
    {
    for(list<int>::iterator iter=n.begin(); iter!=n.end(); ++iter)
    cout<<*iter<<" ";//用迭代器进行输出循环
    }
    int main(void)
    {
    list<int> listn1,listn2;
    //给listn1,listn2初始化
    listn1.push_back(123);
    listn1.push_back(0);
    listn1.push_back(34);
    listn1.push_back(1123);
    //now listn1:123,0,34,1123
    listn2.push_back(100);
    listn2.push_back(12);
    //now listn2:12,100
    listn1.sort();
    listn2.sort();
    //给listn1和listn2排序
    //now listn1:0,34,123,1123         listn2:12,100
    PrintIt(listn1);
    cout<<endl;
    PrintIt(listn2);
    listn1.merge(listn2);
    //合并两个排序列表后,listn1:0,12,34,100,123,1123
    cout<<endl;
    PrintIt(listn1);
    cin.get();
    }
    
      上面并没有演示splice()函数的用法,这是一个拗口的函数。用起来有点麻烦。图3所示是splice函数的功能。将一个列表插入到另一个列表当中。list容器类定义了splice()函数的3个版本:
    splice(position,list_value);
    splice(position,list_value,ptr);
    splice(position,list_value,first,last);
    
      list_value是一个已存在的列表,它将被插入到源列表中,position是一个迭代参数,他当前指向的是要进行拼接的列表中的特定位置。


    图3
    listn1:123,0,34,1123   listn2:12,100
    
      执行listn1.splice(find(listn1.begin(),listn1.end(),0),listn2);之后,listn1将变为:123,12,100,34,1123。即把listn2插入到listn1的0这个元素之前。其中,find()函数找到0这个元素在listn1中的位置。值得注意的是,在执行splice之后,list_value将不复存在了。这个例子中是listn2将不再存在。
      第二个版本当中的ptr是一个迭代器参数,执行的结果是把ptr所指向的值直接插入到position当前指向的位置之前.这将只向源列表中插入一个元素。
      第三个版本的first和last也是迭代器参数,并不等于list_value.begin(),list_value.end()。First指的是要插入的列的第一个元素,last指的是要插入的列的最后一个元素。

    如果listn1:123,0,34,1123 listn2:12,43,87,100 执行完以下函数之后
    listn1.splice(find(listn1.begin(),listn1.end(),0),++listn2.begin(),--listn2.end());
    listn1:123,43,87,0,34,1123  listn2:12,100
    
      以上,我们学习了vector,deque,list三种基本顺序容器,其他的顺序容器还有:slist,bit_vector等等。

    另一种容器——关联容器(有点费解哦,出去让脑子清醒一下再回来看)
      与前面讲到的顺序容器相比,关联容器更注重快速和高效地检索数据的能力。这些容器是根据键值(key)来检索数据的,键可以是值也可以是容器中的某一成员。这一类中的成员在初始化后都是按一定顺序排好序的。

    集和多集(set 和multiset 容器类):#include <set>
      一个集合(set)是一个容器,它其中所包含的元素的值是唯一的。这在收集一个数据的具体值的时候是有用的。集合中的元素按一定的顺序排列,并被作为集合中的实例。如果你需要一个键/值对(pair)来存储数据,map(也是一个关联容器,后面将马上要讲到)是一个更好的选择。一个集合通过一个链表来组织,在插入操作和删除操作上比向量(vector)快,但查找或添加末尾的元素时会有些慢。
      在集中,所有的成员都是排列好的。如果先后往一个集中插入:12,2,3,123,5,65
      则输出该集时为:2,3,5,12,65,123
      集和多集的区别是:set支持唯一键值,set中的值都是特定的,而且只出现一次;而multiset中可以出现副本键,同一值可以出现多次。

    Set和multiset的模板参数:
    template<class key, class compare, class Allocator=allocator>
    
      第一个参数key是所存储的键的类型,第二个参数是为排序值而定义的比较函数的类型,第三个参数是被实现的存储分配符的类型。在有些编译器的具体实现中,第三个参数可以省略。第二个参数使用了合适形式的迭代器为键定义了特定的关系操作符,并用来在容器中遍历值时建立顺序。集的迭代器是双向,同时也是常量的,所以迭代器在使用的时候不能修改元素的值。

    Set定义了三个构造函数:
    默认构造函数:
    explicit set(const Compare&=compare());
    如:set<int,less<int> > set1;
      less<int>是一个标准类,用于形成降序排列函数对象。升序排列是用greater<int>。通过指定某一预先定义的区间来初始化set对象的构造函数:
    template<class InputIterator> set(InputIterator, InputIterator,\ const Compare&=compare());
    如:set<int ,less<int> >set2(vector1.begin(),vector1.end());

    复制构造函数:

    set(const set<Key,Compare&>);
    如:set<int ,less<int> >set3(set2); 

    下面我们来看一个简单的集和多集的插入例程:

    //stl_cpp_11.cpp
    #include <iostream>
    #include <set>
    using namespace std;
    int main(void)
    {
    set<int> set1;
    for(int i=0; i<10; ++i)
    set1.insert(i);
    for(set<int>::iterator p=set1.begin();p!=set1.end();++p)
    cout<<*p<<"";
    if(set1.insert(3).second)//把3插入到set1中
    //插入成功则set1.insert(3).second返回1,否则返回0
    //此例中,集中已经有3这个元素了,所以插入将失败
    cout<<"set insert success";
    else
    cout<<"set insert failed";
    int a[] = {4, 1, 1, 1, 1, 1, 0, 5, 1, 0};
    multiset<int> A;
    A.insert(set1.begin(),set1.end());
    A.insert(a,a+10);
    cout<<endl;
    for(multiset<int>::iterator p=A.begin();p!=A.end();++p)
    cout<<*p<<" ";
    cin.get();
    return 0;
    }
    
      在集之间可以进行并集(set_union())、交集(set_intersection())、差集(set_diffrence())d等操作,功能强大。

    映射和多重映射(map 和multimap):#include <map>
      映射和多重映射基于某一类型Key的键集的存在,提供对T类型的数据进行快速和高效的检索。对map而言,键只是指存储在容器中的某一成员。Map不支持副本键,multimap支持副本键。Map和multimap对象包涵了键和各个键有关的值,键和值的数据类型是不相同的,这与set不同。set中的key和value是Key类型的,而map中的key和value是一个pair结构中的两个分量。Map支持下表运算符operator[],用访问普通数组的方式访问map,不过下标为map的键。在multimap中一个键可以对应多个不同的值。

    下面的例程说明了map中键与值的关系。
     //stl_cpp_12.cpp
    #include <iostream>
    #include <map>
    using namespace std;
    int main(void)
    {
    map<char,int,less<char> > map1;
    map<char,int,less<char> >::iterator mapIter;
    //char 是键的类型,int是值的类型
    //下面是初始化,与数组类似
    //也可以用map1.insert(map<char,int,less<char> >::value_type(''c'',3));
    map1[''c'']=3;
    map1[''d'']=4;
    map1[''a'']=1;
    map1[''b'']=2;
    for(mapIter=map1.begin();mapIter!=map1.end();++mapIter)
    cout<<" "<<(*mapIter).first<<": "<<(*mapIter).second;
    //first对应定义中的char键,second对应定义中的int值
    //检索对应于d键的值是这样做的:
    map<char,int,less<char> >::const_iterator ptr;
    ptr=map1.find(''d'');
    cout<<''\n''<<" "<<(*ptr).first<<" 键对应于值:"<<(*ptr).second;
    cin.get();
    return 0;
    }
    
      从以上例程中,我们可以看到map对象的行为和一般数组的行为类似。Map允许两个或多个值使用比较操作符。下面我们再看看multimap:
    //stl_cpp_13.cpp
    #include <iostream>
    #include <map>
    #include <string>
    using namespace std;
    int main(void)
    {
    multimap<string,string,less<string> >mulmap;
    multimap<string,string,less<string> >::iterator p;
    //初始化多重映射mulmap:
    typedef multimap<string,string,less<string> >::value_type vt;
    typedef string s;
    mulmap.insert(vt(s("Tom "),s("is a student")));
    mulmap.insert(vt(s("Tom "),s("is a boy")));
    mulmap.insert(vt(s("Tom "),s("is a bad boy of blue!")));
    mulmap.insert(vt(s("Jerry "),s("is a student")));
    mulmap.insert(vt(s("Jerry "),s("is a beatutiful girl")));
    mulmap.insert(vt(s("DJ "),s("is a student")));
    //输出初始化以后的多重映射mulmap:
    for(p=mulmap.begin();p!=mulmap.end();++p)
    cout<<(*p).first<<(*p).second<<endl;
    //检索并输出Jerry键所对应的所有的值
    cout<<"find Jerry :"<<endl;
    p=mulmap.find(s("Jerry "));
    while((*p).first=="Jerry ")
    {
    cout<<(*p).first<<(*p).second<<endl;
    ++p;
    }
    cin.get();
    return 0;
    }
    
      在map中是不允许一个键对应多个值的,在multimap中,不支持operator[],也就是说不支持map中允许的下标操作。

    算法(algorithm):#inlcude <algorithm>
      STL中算法的大部分都不作为某些特定容器类的成员函数,他们是泛型的,每个算法都有处理大量不同容器类中数据的使用。值得注意的是,STL中的算法大多有多种版本,用户可以依照具体的情况选择合适版本。中在STL的泛型算法中有4类基本的算法:
    • 变序型队列算法,可以改变容器内的数据;
    • 非变序型队列算法,处理容器内的数据而不改变他们;
    • 排序值算法,包涵对容器中的值进行排序和合并的算法,还有二叉搜索算法 $$通用数值算法;

    注:STL的算法并不只是针对STL容器,对一般容器也是适用的。

    变序型队列算法(mutating algorithms):
      又叫可修改的序列算法。这类算法有复制(copy)算法、交换(swap)算法、替代(replace)算法、删除(remove)算法,移动(transfer)算法、翻转(reverse)算法等等。这些算法可以改变容器中的数据(数据值和值在容器中的位置)。下面介绍2个比较常用的算法reverse()和copy()。

    //stl_cpp_14.cpp
    #include <iostream>
    #include <algorithm>
    #include <iterator>//下面用到了输出迭代器ostream_iterator
    using namespace std;
    int main(void)
    {
    int arr[6]={1,12,3,2,1215,90};
    int arr1[7];
    int arr2[6]={2,5,6,9,0,-56};
    copy(arr,(arr+6),arr1);//将数组aar复制到arr1
    cout<<"arr[6] copy to arr1[7],now arr1: "<<endl;
    for(int i=0;i<7;i++)
    cout<<" "<<arr1[i];
    reverse(arr,arr+6);//将排好序的arr翻转
    cout<<''\n''<<"arr reversed ,now arr:"<<endl;
    copy(arr,arr+6,ostream_iterator<int>(cout, " "));//复制到输出迭代器
    swap_ranges(arr,arr+6,arr2);//交换arr和arr2序列
    cout<<''\n''<<"arr swaped to arr2,now arr:"<<endl;
    copy(arr,arr+6,ostream_iterator<int>(cout, " "));
    cout<<''\n''<<"arr2:"<<endl;
    copy(arr2,arr2+6,ostream_iterator<int>(cout, " "));
    cin.get();
    return 0;
    }
    
    revese()的功能是将一个容器内的数据顺序翻转过来,它的原型是:
      template<class Bidirectional >
    void reverse(Bidirectional first, Bidirectional last);
    
    将first和last之间的元素翻转过来,上例中你也可以只将arr中的一部分进行翻转:
       reverse(arr+3,arr+6);这也是有效的。First和last需要指定一个操作区间。
    
    Copy()是要将一个容器内的数据复制到另一个容器内,它的原型是:
       Template<class InputIterator ,class OutputIterator>
    OutputIterator copy(InputIterator first, InputIterator last, OutputIterator result);
    
      它把[first,last-1]内的队列成员复制到区间[result,result+(last-first)-1]中。泛型交换算法:Swap()操作的是单值交换,它的原型是:
    template<class T>
    void swap(T& a,T& b);
    
    swap_ranges()操作的是两个相等大小区间中的值,它的原型是:
      template<class ForwardIterator1, class ForwardIterator2>
    ForwardIterator2 swap_ranges(ForwardIterator1 first1,ForwardIterator1 last1, \
    ForwardIterator1 first2);
    
      交换区间[first1,last1-1]和[first2, first2+(last1-first1)-1]之间的值,并假设这两个区间是不重叠的。

    非变序型队列算法(Non-mutating algorithm)
      又叫不可修改的序列算法。这一类算法操作不影响其操作的容器的内容,包括搜索队列成员算法,等价性检查算法,计算队列成员个数的算法。我将用下面的例子介绍其中的find(),search(),count():
    //stl_cpp_15.cpp
    #include <iostream>
    #include <vector>
    #include <algorithm>
    using namespace std;
    int main(void)
    {
    int a[10]={12,31,5,2,23,121,0,89,34,66};
    vector<int> v1(a,a+10);
    vector<int>::iterator result1,result2;//result1和result2是随机访问迭代器
    result1=find(v1.begin(),v1.end(),2);
    //在v1中找到2,result1指向v1中的2
    result2=find(v1.begin(),v1.end(),8);
    //在v1中没有找到8,result2指向的是v1.end()
    cout<<result1-v1.begin()<<endl; //3-0=3或4-1=3,屏幕结果是3
    cout<<result2-v1.end()<<endl;
    int b[9]={5,2,23,54,5,5,5,2,2};
    vector<int> v2(a+2,a+8);
    vector<int> v3(b,b+4);
    result1=search(v1.begin(),v1.end(),v2.begin(),v2.end());
    cout<<*result1<<endl;
    //在v1中找到了序列v2,result1指向v2在v1中开始的位置
    result1=search(v1.begin(),v1.end(),v3.begin(),v3.end());
    cout<<*(result1-1)<<endl;
    //在v1中没有找到序列v3,result指向v1.end(),屏幕打印出v1的最后一个元素66
    vector<int> v4(b,b+9);
    int i=count(v4.begin(),v4.end(),5);
    int j=count(v4.begin(),v4.end(),2);
    cout<<"there are "<<i<<" members in v4 equel to 5"<<endl;
    cout<<"there are "<<j<<" members in v4 equel to 2"<<endl;
    //计算v4中有多少个成员等于 5,2
    cin.get();
    return 0;
    }
    
    find()的原型是:
    template<class InputIterator,class EqualityComparable>
    InputIterator find(InputIterator first, InputIterator last,\
    const EqualityComparable& value);
    
      其功能是在序列[first,last-1]中查找value值,如果找到,就返回一个指向value在序列中第一次出现的迭代,如果没有找到,就返回一个指向last的迭代(last并不属于序列)。 search()的原型是:
    template <class ForwardIterator1, class ForwardIterator2>
    ForwardIterator1 search(ForwardIterator1 first1, ForwardIterator1 last1,\
    ForwardIterator2 first2, ForwardIterator2 last2);
    
      其功能是在源序列[first1,last1-1]查找目标序列[first2,last2-1]如果查找成功,就返回一个指向源序列中目标序列出现的首位置的迭代。查找失败则返回一个指向last的迭代。 Count()的原型是:
    template <class InputIterator, class EqualityComparable>
    iterator_traits<InputIterator>::difference_type count(InputIterator first,\
    InputIterator last, const EqualityComparable& value);
    
      其功能是在序列[first,last-1]中查找出等于value的成员,返回等于value得成员的个数。

    排序算法(sort algorithm)
      这一类算法很多,功能强大同时也相对复杂一些。这些算法依赖的是关系运算。在这里我只介绍其中比较简单的几种排序算法:sort(),merge(),includes()
    //stl_cpp_16.cpp
    #include <iostream>
    #include <algorithm>
    using namespace std;
    int main(void)
    {
    int a[10]={12,0,5,3,6,8,9,34,32,18};
    int b[5]={5,3,6,8,9};
    int d[15];
    sort(a,a+10);
    for(int i=0;i<10;i++)
    cout<<" "<<a[i];
    sort(b,b+5);
    if(includes(a,a+10,b,b+5))
    cout<<''\n''<<"sorted b members are included in a."<<endl;
    else
    cout<<"sorted a dosn`t contain sorted b!";
    merge(a,a+10,b,b+5,d);
    for(int j=0;j<15;j++)
    cout<<" "<<d[j];
    cin.get();
    return 0;
    }
    
    sort()的原型是:
    template <class RandomAccessIterator>
    void sort(RandomAccessIterator first, RandomAccessIterator last);
    
      功能是对[first,last-1]区间内的元素进行排序操作。与之类似的操作还有:partial_sort(), stable_sort(),partial_sort_copy()等等。 merge()的原型是:
    template <class InputIterator1, class InputIterator2, class OutputIterator>
    OutputIterator merge(InputIterator1 first1, InputIterator1 last1,\
    InputIterator2 first2, InputIterator2 last2,OutputIterator result);
    
      将有序区间[first1,last1-1]和[first2,last2-1]合并到[result, result + (last1 - first1) + (last2 - first2)-1]区间内。

    Includes()的原型是:
    template <class InputIterator1, class InputIterator2>
    bool includes(InputIterator1 first1, InputIterator1 last1,\
    InputIterator2 first2, InputIterator2 last2);
    
      其功能是检查有序区间[first2,last2-1]内元素是否都在[first1,last1-1]区间内,返回一个bool值。

    通用数值算法(generalized numeric algorithms)
      这一类算法还不多,涉及到专业领域中有用的算术操作,独立包涵于头文件<numeric>中(HP版本的STL中是<algo.h>)。这里不作介绍。
      STL中的算法大都有多种版本,常见的版本有以下4中:
    • 默认版本,假设给出了特定操作符;
    • 一般版本,使用了成员提供的操作符;
    • 复制版本,对原队列的副本进行操作,常带有 _copy 后缀;
    • 谓词版本,只应用于满足给定谓词的队列成员,常带有 _if 后缀;

      以上我们学习了STL容器和算法的概念,以及一些简单的STL容器和算法。在使用算法处理容器内的数据时,需要从一个数据成员移向另一个数据成员,迭代器恰好实现了这一功能。下面我们来学习STL迭代器 。

    迭代器(itertor):#include<iterator>
      迭代器实际上是一种泛化指针,如果一个迭代器指向了容器中的某一成员,那么迭代器将可以通过自增自减来遍历容器中的所有成员。迭代器是联系容器和算法的媒介,是算法操作容器的接口。在运用算法操作容器的时候,我们常常在不知不觉中已经使用了迭代器。
    STL中定义了6种迭代器:

    • 输入迭代器,在容器的连续区间内向前移动,可以读取容器内任意值;
    • 输出迭代器,把值写进它所指向的队列成员中;
    • 前向迭代器,读取队列中的值,并可以向前移动到下一位置(++p,p++);
    • 双向迭代器,读取队列中的值,并可以向前向后遍历容器;
    • 随机访问迭代器, vector<T>::iterator,list<T>::iterator等都是这种迭代器 ;
    • 流迭代器,可以直接输出、输入流中的值;

      实际上,在前面的例子中,我们不停的在用迭代器。下面我们用几个例子来帮助理解这些迭代器的用法。
    下面的例子用到了输入输出迭代器:

    // stl_cpp_17.cpp
    #include <iostream>
    #include <fstream>
    #include <iterator>
    #include <vector>
    #include <string>
    using namespace std;
    int main(void)
    {
    vector<string> v1;
    ifstream file("Text1.txt");
    if(file.fail())
    {
    cout<<"open file Text1.txt failed"<<endl;
    return 1;
    }
    copy(istream_iterator<string>(file),istream_iterator<string>(),inserter(v1,\
    v1.begin()));
    copy(v1.begin(),v1.end(),ostream_iterator<string>(cout," "));
    cout<<endl;
    cin.get();
    return 0;
    }
    
      这里用到了输入迭代器istream_iterator,输出迭代器ostream_iterator。程序完成了将一个文件输出到屏幕的功能,先将文件读入,然后通过输入迭代器把文件内容复制到类型为字符串的向量容器内,最后由输出迭代器输出。Inserter是一个输入迭代器的一个函数(迭代器适配器),它的使用方法是:
    inserter (container ,pos);

      congtainer是将要用来存入数据的容器,pos是容器存入数据的开始位置。上例中,是把文件内容存入(copy())到向量v1中。

    现在我们已经对STL的三大基本组件有了一个大概的了解,下面让我们一起来看看STL的其他标准组件。

    函数对象(functor或者funtion objects):#include <function>
      函数对象又称之为仿函数。函数对象将函数封装在一个对象中,使得它可作为参数传递给合适的STL算法,从而使算法的功能得以扩展。可以把它当作函数来使用。用户也可以定义自己的函数对象。下面让我们来定义一个自己的函数对象。

    // stl_cpp_18.cpp
    #include <iostream>
    using namespace std;
    struct int_max{
    int operator()(int x,int y){return x>y?x:y; }
    };//operator() 重载了"()", (int x,int y)是参数列表
    int main(void)
    {
    cout<<int_max()(3,4)<<endl;
    cin.get();
    return 0;
    }
    
      这里的int_max()就是一个函数对象,struct关键字也可以用class来代替,只不过struct默认情况下是公有访问权限,而class定义的是默认私有访问权限。下面我们来定义一个STL风格的函数对象:
    // stl_cpp_19.cpp
    #include <iostream>
    #include <vector>
    using namespace std;
    struct adder : public unary_function<double, void>
    {
    adder() : sum(0) {}
    double sum;
    void operator()(double x) { sum += x; }
    };
    int main(void)
    {
    double a[5]={0.5644,1.1,6.6,8.8,9.9};
    vector<double> V(a,a+5);
    adder result = for_each(V.begin(), V.end(), adder());
    cout << "The sum is " << result.sum << endl;
    cin.get();
    return 0;
    }
    
      在这里,我们定义了一个函数对象adder(),这也是一个类,它的基类是unary_function函数对象。unary_function是一个空基类,不包涵任何操作或变量。只是一种格式说明,它有两个参数,第一个参数是函数对象的使用数据类型,第二个参数是它的返回类型。基于它所定义的函数对象是一元函数对象。(注:用关键字struct或者class定义的类型实际上都是"类")
      STL内定义了各种函数对象,否定器、约束器、一元谓词、二元谓词都是常用的函数对象。函数对象对于编程来说很重要,因为他如同对象类型的抽象一样作用于操作。

    适配器(adapter)
      适配器是用来修改其他组件接口的STL组件,是带有一个参数的类模板(这个参数是操作的值的数据类型)。STL定义了3种形式的适配器:容器适配器,迭代器适配器,函数适配器。
    • 容器适配器:包括栈(stack)、队列(queue)、优先(priority_queue)。使用容器适配器,stack就可以被实现为基本容器类型(vector,dequeue,list)的适配。可以把stack看作是某种特殊的vctor,deque或者list容器,只是其操作仍然受到stack本身属性的限制。queue和priority_queue与之类似。容器适配器的接口更为简单,只是受限比一般容器要多;
    • 迭代器适配器:修改为某些基本容器定义的迭代器的接口的一种STL组件。反向迭代器和插入迭代器都属于迭代器适配器,迭代器适配器扩展了迭代器的功能;
    • 函数适配器:通过转换或者修改其他函数对象使其功能得到扩展。这一类适配器有否定器(相当于"非"操作)、帮定器、函数指针适配器。

    结束语
      如果你理解了算法、迭代器、容器,那么你几乎就了解了 STL。关于STL的其他方面,新手都是不常用的,可以暂时以理解STL的组成的编程思想为主。这篇文章里用到了19个cpp代码,每个代码都在Windows 2000+ Dev-C++ 4.9.9.0和windows 2000+VC环境下通过编译运行。读者可以通过copy/paste到任何一款C++编译器中运行。无论你想不想学STL,先运行一下STL代码吧。编程快乐,好好学习,天天向上。

    我是新手,欢迎高手批评,欢迎STL学习者交流: Email:taohanjunjiang@yahoo.com.cn QQ:370679790


    【附录】

    附录一:Dev-C++ 和VC
      在Dev-C++ 4.9.9.0 +windows 2000下,不允许出现,void main(){} 而必须为 int main() {return 0;} ,或者 int main(){},奇怪的是VC对 int main(){} 的写法会提出警告,而必须为 int main() {return 0;}。建议使用标准格式 int main(void) {……return 0;}
      Dev-C++而且不支持头文件方式,#include <iostream.h>是不对的,而必须为#include <iostream> using namespace std; VC两种格式都支持。建议使用#include <iostream> using namespace std;
      如果用纯C/C++编程,建议使用比较简单的C++编译器,甚至是命令行下的编译方式。功能强大的IDE甚至会误导编程者。还要花费不少的时间来学习使用IDE开发环境。Dev比VC要简单一些,只是不适合做大型工程,而且编译时间比VC稍慢一点,纯C++编程建议使用。他有插入时间和头文件注释模板的功能,作者可以方便地插入编程、程序更改时间,方便地填写程序说明信息。
      在Dev下可以导入VC工程,而且保持原来的VC工程文件,但是Dev对VC的支持还不够,在编译的时候会遇到错误。
      在编写dos程序的时候,建议在程序的末尾return之前加上:getchar();cin.get();之类的代码,以方便看运行结果。

    附录二:中小型程序段的编辑工具
      在编辑程序过程中并不一定非要在特定的IDE环境中,在有些专业化的文本编辑工具中编辑代码会更有利于代码的修改和编辑。这里介绍几种更能比较强大的文本编辑器。

    • UltraEdit-32;
    • EditPlus;
    • SourceInsight;
    • Vim;
    posted @ 2008-05-02 03:27 IP 阅读(459) | 评论 (0)编辑 收藏

    仅列出标题  下一页