Let's Go!

编程知识---杂乱

上一篇 / 下一篇  2009-05-13 00:49:54 / 个人分类:经典转载

浅谈C++的string类型
文章出处:http://www.diybl.com/course/3_program/c++/cppjs/2008215/99862.html

一,C语言的字符串

在C语言里,对字符串的处理一项都是一件比较痛苦的事情,因为通常在实现字符串的操作的时候都会用到最不容易驾驭的类型——指针。

比如下面这个例子:

//example 1:

char str[12] = "Hello";

char *p = str;

*p = ''h'';  //改变第一个字母

 

//example 2:

char *ptr = "Hello";

*ptr = ''h'';  //错误

 

第一个字符串时用数组开辟的,它是可以改变的变量。而第二个字符串则是一个常量,也就是字面值。ptr只是指向它的指针而已,而不能改变指向的内容。

看两者的汇编即可明了:

char p[] = "Hello";
004114B8  mov         eax,dword ptr [string "Hello" (4166FCh)]
004114BD  mov         dword ptr [ebp-10h],eax
004114C0  mov         cx,word ptr ds:[416700h]
004114C7  mov         word ptr [ebp-0Ch],cx

char *ptr = "Hello";
004114CB  mov         dword ptr [ebp-1Ch],offset string "Hello" (4166FCh)

可见用数组和用指针是完全不相同的。

要想通过指针来改变常量是错误,正确的写法应该是用const指针。

const char *ptr = "Hello";

 

 

二,初识string类

正是因为C风格字符串(以空字符结尾的字符数组)太过复杂难于掌握,不适合大程序的开发,所以C++标准库定义了一种string类,定义在头文件<string>。注意<string.h>和<cstring>都是错误的,这两个头文件主要定义C风格字符串操作的一些方法,譬如strlen(), strcpy()等。第一个是C的头文件格式,而第二个是C++风格的头文件,但是和<string.h>是一样的,它的目的是为了和C兼容。

看下面例子:

//example 3:

string str("world");   //可以用C风格字符串初始化

string words = "Hello";

string greet = words;

string  join = greet + words;  //可以像基本类型一样操作

 

但是如果试图把string类型的对象直接赋给C风格的字符串的话,编译器会报错的。

string var = "Olympic";

char *ptr = var;  //error!

 

但是实际应用中这个问题也难以避免,很多时候我们还是需要将string类型的转化为char*来实现自定义的操作,C++标准库也为了和之前用C写的程序兼容,于是可以用string的c_str()函数。

string var = "Olympic";

char *ptr = var.c_str(); //还不能被编译

 

但是c_str()为了防止意外地修改string对象,返回的是const指针,所以上面这段代码是不能被编译的。正确的应该是用const指针。

string var = "Olympic";

const char *p = var.c_str(); //Correct!

 

这个c_str()方法在C++IO流操作上也被广泛应用。

在打开文件时,如果要指定文件名,可以用C风格的字符串。如果用到string类型的字符串作为文件名时,就必须调用c_str()方法将其转换为一个C风格字符串。

//example 4:

string   filename;  //定义文件名称

cin >> filename;

ifstream.open(filename.c_str());  //要使用C风格字符串

 

三, 深入了解string类

对string有一定了解以后,我们可以来了解C++标准库定义的一系列丰富的字符串操作,当然都是基于string类型。从某一种程度上来说,string就是一种字符容器。

标准库为string定义了很多方法,包括构造、插入(insert)、替换(assign和replace)、比较(compare)、查找(find)、删除(erase)、连接(append)以及对子串的操作(substr)。而每一种操作都有很多种重载。

比如插入,除了包括标准容器的插入方式以外,string类本身还有一些特有的插入方法。

//example 5:

//与标准容器相同的插入操作:

str.insert(iter, value) //在迭代器iter之前插入value, 返回新元素的迭代器

str.insert(iter, n, value); //在迭代器iter之前插入n个value,返回void

str.insert(iter, begin, end); //在迭代器iter之前插入迭代器begin和end标记范围内的元素,返回void

//string类特有的插入方法:

str.insert(pos, n, ch); //在下标为pos的字符之前插入n个字符ch

str.insert(pos, str2); //在下标为pos的字符之前插入string类型的对象str2的副本

str.insert(pos1, str2, pos2, len); //在下标为pos1的z字符之前插入string类型str2中从下表为pos2开始的len个字符

str.insert(pos, cp); //在下标为pos的字符前插入字符指针cp指向的C风格字符串的副本

 

总之string是一种非常灵活的字符串类型,标准库让我们可以忽略内存管理和具体实现方式,我们只需要关注其接口就好。并且初学者在使用字符串的时候也应尽量使用这种类型,而不是C风格的字符串。

当然,无C语言学习经历的人可以例外。

 

 

参阅书籍:

<C++ Primer 4th>Stanley Lippman

<The C++ Programming Language> Bjarne Stroustrup>


 

Delphi

话题218757的标题是:Pchar 与String(50分)
             分类:Object Pascal(Delphi)

sess(2000-04-12 21:45)

PChar和String有什么不同,如何互相转换,动态连接传输字符串
建议用什么?

DreamTiger(2000-04-12 21:56)

var
 s:string;
 p:pchar;
begin
 s := 'test';
 p := PChar(s);

 p := 'test2';
 s := strpas(p);
end;

动态连接强烈建议用pchar。要注意的是,这是传了一个地址,
最好是一个全局变量的地址,这样不会很快破坏掉,在dll中
接收的时候拷贝到自己分配的空间中。反之也一样。

popeye(2000-04-12 21:58)

pchar是c,c++里指向以空结尾的字符串的指针,如: 'aaaaa空'
string是pascal里的字符串类型,和pchar结构不同.
可以通过pchar(string)转换到pchar类型,pchar多用于调用windows api函数

wrench(2000-04-13 02:02)

做DLL,COM最好不要用String,
PCHAR在Windows中才是标准

windz(2000-04-13 02:03)

//Pchar to string :
s:=StrPas(p);

//string to pchar :
p:=pchar(s);//进行强制转换。
//建议用:
getmem(p,length(s));
StrPCopy(p,s);

sess(2000-04-13 08:58)

DFreamTiger:对不起,我昨天晚上发完,MAIL后就下班啦。请问,调用DLL和DLL程序中要不要对PCHAR重新分配内存?急!谢谢!

DreamTiger(2000-04-13 09:05)

你可以看看:
http://www.gislab.ecnu.edu.cn/delphibbs/dispq.asp?LID=207101
这是我以前提的一个问题,里面有比较好的回答。
我当时是在dll中调用主程序中的函数,在主程序中调用dll函数也一样。

snowman2000(2000-04-13 09:10)

Pchar只是一个指针,如果只定义了一个Pchar变量,是不会分配内存的。
一般都将其指向一个字符数组来使用。
调用DLL传递的只是这个指针,当然不要重新分配内存。

sess(2000-04-13 09:32)

:snowman2000:分配内存是不是在主程序分配的?

lczhuohuo(2000-04-13 09:58)

PChar就是个指针,String使用了引用计数技术,而且在首字节里有字符
串长度信息

hsw(2000-05-07 00:00)

>>分配内存是不是在主程序分配的?
>>调用DLL和DLL程序中要不要对PCHAR重新分配内存?
一个DLLs拥有自己的数据段(DS),因而它声明的任何变量都为自己所私有。调用它的模块不能直接使用它定义的变量。要使用必须通过过程或函数界面才能完成。而对DLLs来说,它永远都没有机会使用调用它的模块中声明的变量。

  一个DLLs没有自己的堆栈段(SS),它使用调用它的应用程序的堆栈。因此在DLL中的过程、函数绝对不要假定DS = SS。一些语言在小模式编译下有这种假设,但使用Delphi可以避免这种情况。Delphi绝不会产生假定DS = SS的代码,Delphi的任何运行时间库过程/函数也都不作这种假定。需注意的是如果读者想嵌入汇编语言代码,绝不要使SS和DS登录同一个值。

  主程序的变量(包括全局变量),DLL是不能共享的,
但变量可以通过外部过程映入(Exports).
如果一定要主程序与DLL间共享变量,需要使用内存映射
(Memory ???)
 
另外,希望你给我加分(即使一分也好,我想留个纪念
毕竟,我打了非常多的字)

wjiachun(2000-08-23 10:08)

多人接受答案了。

DreamTiger-8,hsw-7,lczhuohuo-7,popeye-7,snowman2000-7,windz-7,wrench-7,的回答最终被接受。

 

 

如果是 java 中调用,
我所知道的方法是用 JNI 再把dll包装一层,
调用 JNI,然后在 JNI 中调用 dll 中的方法 ...

 

 

 

利用VC调用动态链接库中的函数

2006-10-29 08:00作者:刘涛出处:天极开发责任编辑:方舟
  自从微软推出16位的Windows操作系统起,此后每种版本的Windows操作系统都非常依赖于动态链接库(DLL)中的函数和数据,实际上Windows操作系统中几乎所有的内容都由DLL以一种或另外一种形式代表着,例如显示的字体和图标存储在GDI DLL中、显示Windows桌面和处理用户的输入所需要的代码被存储在一个User DLL中、Windows编程所需要的大量的API函数也被包含在Kernel DLL中。在Windows操作系统中使用DLL有很多优点,最主要的一点是多个应用程序、甚至是不同语言编写的应用程序可以共享一个DLL文件,真正实现了资源"共享",大大缩小了应用程序的执行代码,更加有效的利用了内存;使用DLL的另一个优点是DLL文件作为一个单独的程序模块,封装性、独立性好,在软件需要升级的时候,开发人员只需要修改相应的DLL文件就可以了,而且,当DLL中的函数改变后,只要不是参数的改变,程序代码并不需要重新编译。这在编程时十分有用,大大提高了软件开发和维护的效率。

  既然DLL那么重要,所以搞清楚什么是DLL、如何在Windows操作系统中开发使用DLL是程序开发人员不得不解决的一个问题。本实例针对这些问题,通过一个简单的例子,即调用在一个DLL中函数,实现用户输入数据的自加功能,全面地解析了在Visual C++编译环境下编程实现DLL、调用DLL中的函数的过程。程序编译运行后的界面效果如图一所示:


图一、调用DLL中的函数实现数据自加功能程序效果图

  一、实现方法

  1、DLL的概念

  DLL是建立在客户/服务器通信的概念上,包含若干函数、类或资源的库文件,函数和数据被存储在一个DLL(服务器)上并由一个或多个客户导出而使用,这些客户可以是应用程序或者是其它的DLL。DLL库不同于静态库,在静态库情况下,函数和数据被编译进一个二进制文件(通常扩展名为*.LIB),Visual C++的编译器在处理程序代码时将从静态库中恢复这些函数和数据并把他们和应用程序中的其他模块组合在一起生成可执行文件。这个过程称为"静态链接",此时因为应用程序所需的全部内容都是从库中复制了出来,所以静态库本身并不需要与可执行文件一起发行。在动态库的情况下,有两个文件,一个是引入库(.LIB)文件,一个是DLL文件,引入库文件包含被DLL导出的函数的名称和位置,DLL包含实际的函数和数据,应用程序使用LIB文件链接到所需要使用的DLL文件,库中的函数和数据并不复制到可执行文件中,因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,这样当一个或多个应用程序运行是再把程序代码和被调用的函数代码链接起来,从而节省了内存资源。从上面的说明可以看出,DLL和.LIB文件必须随应用程序一起发行,否则应用程序将会产生错误。

  微软的Visual C++支持三种DLL,它们分别是Non-MFC Dll(非MFC动态库)、Regular Dll(常规DLL)、Extension Dll(扩展DLL)。Non-MFC DLL指的是不用MFC的类库结构,直接用C语言写的DLL,其导出的函数是标准的C接口,能被非MFC或MFC编写的应用程序所调用。Regular DLL:和下述的Extension Dlls一样,是用MFC类库编写的,它的一个明显的特点是在源文件里有一个继承CWinApp的类(注意:此类DLL虽然从CWinApp派生,但没有消息循环),被导出的函数是C函数、C++类或者C++成员函数(注意不要把术语C++类与MFC的微软基础C++类相混淆),调用常规DLL的应用程序不必是MFC应用程序,只要是能调用类C函数的应用程序就可以,它们可以是在Visual C++、Dephi、Visual Basic、Borland C等编译环境下利用DLL开发应用程序。常规DLL又可细分成静态链接到MFC和动态链接到MFC上的,这两种常规DLL的区别将在下面介绍。与常规DLL相比,使用扩展DLL用于导出增强MFC基础类的函数或子类,用这种类型的动态链接库,可以用来输出一个从MFC所继承下来的类。扩展DLL是使用MFC的动态链接版本所创建的,并且它只被用MFC类库所编写的应用程序所调用。例如你已经创建了一个从MFC的CtoolBar类的派生类用于创建一个新的工具栏,为了导出这个类,你必须把它放到一个MFC扩展的DLL中。扩展DLL 和常规DLL不一样,它没有一个从CWinApp继承而来的类的对象,所以,开发人员必须在DLL中的DllMain函数添加初始化代码和结束代码。

  2、动态链接库的创建

  在Visual C++6.0开发环境下,打开File\New\Project选项,可以选择Win32 Dynamic-Link Library或MFC AppWizard[dll]来以不同的方式来创建Non-MFC Dll、Regular Dll、Extension Dll等不同种类的动态链接库。

  (一) Win32 Dynamic-Link Library方式创建Non-MFC DLL动态链接库

  每一个DLL必须有一个入口点,这就象我们用C编写的应用程序一样,必须有一个WINMAIN函数一样。在Non-MFC DLL中DllMain是一个缺省的入口函数,你不需要编写自己的DLL入口函数,用这个缺省的入口函数就能使动态链接库被调用时得到正确的初始化。如果应用程序的DLL需要分配额外的内存或资源时,或者说需要对每个进程或线程初始化和清除操作时,需要在相应的DLL工程的.CPP文件中对DllMain()函数按照下面的格式书写。

BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{
 switch( ul_reason_for_call )
 {
  case DLL_PROCESS_ATTACH:
   .......
  case DLL_THREAD_ATTACH:
   .......
  case DLL_THREAD_DETACH:
   .......
  case DLL_PROCESS_DETACH:
   .......
 }
 return TRUE;
}

  参数中,hMoudle是动态库被调用时所传递来的一个指向自己的句柄(实际上,它是指向_DGROUP段的一个选择符);ul_reason_for_call是一个说明动态库被调原因的标志,当进程或线程装入或卸载动态链接库的时候,操作系统调用入口函数,并说明动态链接库被调用的原因,它所有的可能值为:DLL_PROCESS_ATTACH: 进程被调用、DLL_THREAD_ATTACH: 线程被调用、DLL_PROCESS_DETACH: 进程被停止、DLL_THREAD_DETACH: 线程被停止;lpReserved为保留参数。到此为止,DLL的入口函数已经写了,剩下部分的实现也不难,你可以在DLL工程中加入你所想要输出的函数或变量了。

  我们已经知道DLL是包含若干个函数的库文件,应用程序使用DLL中的函数之前,应该先导出这些函数,以便供给应用程序使用。要导出这些函数有两种方法,一是在定义函数时使用导出关键字_declspec(dllexport),另外一种方法是在创建DLL文件时使用模块定义文件.Def。需要读者注意的是在使用第一种方法的时候,不能使用DEF文件。下面通过两个例子来说明如何使用这两种方法创建DLL文件。

  1)使用导出函数关键字_declspec(dllexport)创建MyDll.dll,该动态链接库中有两个函数,分别用来实现得到两个数的最大和最小数。在MyDll.h和MyDLL.cpp文件中分别输入如下原代码:

//MyDLL.h
extern "C" _declspec(dllexport) int Max(int a, int b);
extern "C" _declspec(dllexport) int Min(int a, int b);
//MyDll.cpp
#include<stdio.h>
#include"MyDll.h"
int Max(int a, int b)
{
 if(a>=b)return a;
 else
  return b;
}
int Min(int a, int b)
{
 if(a>=b)return b;
 else
  return a;
}

  该动态链接库编译成功后,打开MyDll工程中的debug目录,可以看到MyDll.dll、MyDll.lib两个文件。LIB文件中包含DLL文件名和DLL文件中的函数名等,该LIB文件只是对应该DLL文件的"映像文件",与DLL文件中,LIB文件的长度要小的多,在进行隐式链接DLL时要用到它。读者可能已经注意到在MyDll.h中有关键字"extern C",它可以使其他编程语言访问你编写的DLL中的函数。

  2)用.def文件创建工程MyDll

  为了用.def文件创建DLL,请先删除上个例子创建的工程中的MyDll.h文件,保留MyDll.cpp并在该文件头删除#include MyDll.h语句,同时往该工程中加入一个文本文件,命名为MyDll.def,再在该文件中加入如下代码:

  LIBRARY MyDll
  EXPORTS
  Max
  Min

  其中LIBRARY语句说明该def文件是属于相应DLL的,EXPORTS语句下列出要导出的函数名称。我们可以在.def文件中的导出函数后加@n,如Max@1,Min@2,表示要导出的函数顺序号,在进行显式连时可以用到它。该DLL编译成功后,打开工程中的Debug目录,同样也会看到MyDll.dll和MyDll.lib文件。

 

 

 


TAG:

 

评分:0

我来说两句

Open Toolbar