【原创】自制string类型(已完成)
这篇文章首发于360doc http://www.360doc.com/content/21/0526/17/73755266_979099504.shtml ,其实360doc里面的那个也是我的帐号,发在那里没什么人看,就发到这里来了。
文章原创,转载需注明原作者。
前后总计两个多月,总算是写完了。完成日期:2021.8.1.
若本文有错误,请大家在评论区中指出,我会尽我所能加以改正。
目录
第一章 前言和准备工作
1.1.前言
1.2.准备工作
第二章 string类函数——简单版
2.1.最简单的string
2.2.string类型的输入和输出
2.3.查找子串,插入,删除和替换
2.4.at函数和size函数
第三章:运算符重载和构造函数
3.1.赋值运算符
3.2.“+”和“+=”的重载
3.3.比较运算符的重载
3.4.下标运算符的重载
3.5.构造函数
第四章:string类函数(续)
4.1.内存动态分配
4.2.append函数
4.3.begin和end迭代器
4.4.getline函数
第五章:拾遗
5.1.string.h和string
5.2.字符数组
5.3.string类的本质
第一章 前言和准备工作
1.1.前言
1.1.1.前言
C++语言相比C语言,比较大和好用的变化就是类(class)这一功能。这次,我们尝试来根据类这一新功能,构造一些比较简单的数据结构。这次,我们挑战一下string这一类型。
1.1.2.string是什么
string是C++STL容器中自带的一个库,是关于字符串的。字符串的概念什么的这里就不细讲了,比较简单。有了string,在C++中,就可以更加方便的使用字符串了,相比较于C语言而言,不需要使用strcpy,strcmp等函数,直接使用运算符即可。它的读入和输出和cin,cout结合,也有专门的函数getline等。同时,string也可以和C语言的老版本字符串进行转换,兼容一些老的函数。
1.2.准备工作
1.2.1.工具
一台电脑,一个C++编译器(笔者这里使用的是Dev C++,各位读者可以根据自己的喜好,例如Visual C++,CodeBlocks等)
1.2.2.学习资源
如果有不懂的地方,提供一些参考的在线学习资源。实际笔者也是从这里参考了很多用法。
http://c.biancheng.net/cplus/
https://www.runoob.com/cplusplus/cpp-tutorial.html
http://www.weixueyuan.net/cpp/rumen/
第二章:string类函数——简单版
2.1.最简单的string
string是一个类型,我们构造string类型,也就需要定义一个string类型。定义类型,我们一般在C语言会使用typedef struct的方式,我们这里既然用上了C++,就用一下C++的新功能——类。
类其实是一个类似于结构体的东西,把很多东西打包在一起。类中除了可以包含变量,同时也可以包含成员函数,构造函数,析构函数以及运算符重载等。这里,我们暂且先把这个字符串本身和他的大小信息包含在整个类里面。话不多说,我们看代码。
class String{ public: char *str; int size; };
这里string的s是大写为了避免和标准库的string重名。可以看到,类的定义和结构体的定义类似。public说明这些成员是公共访问的。
接下来,我们需要写成员函数。我们先写最简单的也是最重要的init函数。事实上,string是没有init的,但是在写构造函数之前,我们先用最简单的成员函数的形式写一个init,用于初始化,给指针分配空间。(str是指针,为了使得str长度可变)
class String{ char *str; int size; void init(void){ str=(char*)malloc(100); } };
首先是成员函数的位置,写在class里面。第二是写法,无需指定所属类,直接写str即可。
如果比较了解C++特性的,可能会问为什么用malloc不用new。这里用malloc是为了方便扩大空间(扩大要用realloc),这样数组就是可变的了。
本文之后为了方便和节省空间,就不把整个代码贴出来了,只写本次要写的函数,大家注意一下函数的位置。
接下来,为了让标准函数可以访问(例如printf),需要一个函数用于把这个字符串转换为C风格字符串,也就是c_str函数。
const char *c_str(void){ return str; }
这样,以后用printf就方便了。
2.2.string类型的输入和输出
2.2.1.输入
string自带的是使用cin进行输入,但是我们这里也没法用cin,只能简单的写一个get函数来实现输入。为了方便,get可以包含多种版本。各位读者也可以根据自己的喜好来设计更多版本。
版本1 get()输入字符串,空格停止
版本2 get(字符个数)输入字符串,到达字符个数停止
版本3 get(停止字符)输入字符串,输入时遇到停止字符停止
C++有函数重载功能,多个函数只要参数不同,就会当作不同函数处理。C++编译器会根据类型来判断调用哪一个函数。
void get(void){ scanf("%s",str); } void get(int n){ fgets(str,n,stdin); } void get(char c){ int i=0; for(;;){ str[i]=getchar(); if(str[i]==c)break; ++i; } }
fgets函数是从文件读入的函数,第二个参数用于指定字符总数。
多说一句,新版本的C++标准中是不支持gets函数的,因为不安全(容易数组越界),对于VC编译器新增了gets_s函数,部分编译器还是保留gets的,但是为了兼容性,建议把gets全部写成fgets的形式。
2.2.2.输出
字符串的输出也有很多种方式。这里列出比较常见的两种。
版本1 put()输出字符串,遇到'\0’结束
版本2 put(字符数量)输出字符串,到达指定数量结束
实现起来其实也很简单。
void put(void){ for(int i=0;str[i]!='\0';i++) putchar(str[i]); } void put(int n){ for(int i=0;i<n;i++) putchar(str[i]); }
很简单。两个都是使用for循环来输出,只不过循环条件不同,一个是不等于“\0”,另一个是小于n。
多说几句,实际上,'\0’实际上就是0,所以也可以写成str[i]!=0。但是根据习惯原因,我们一般写作'\0’,表示这是一个字符串。但是,有人是这样写的:
str[i]!=NULL;
这样写就有点问题了。NULL其实也是0,但是一般用于指针较多。很多机器上面其实NULL在stdio.h是这样定义的:
#define NULL ((void*)0)
这样的话,0是void*类型的,而不是char类型的。所以,上面的代码可能会报错。C++对于类型转换是很严格的。
2.3.查找子串,插入,删除和替换
2.3.1.find函数
我们这一节来讨论查找,插入,删除和替换。想要阅读这一节的话,最好有一点线性表的功底。
先说查找。标准库的find,我们只用两种。
find(String subs) find(int n,String subs)
当然,其实参数还可以是char*类型的c风格字符串。这里是一定要区分开来的,因为参数为c风格字符串的话,就不是sub.str而是直接写成subs了。看到这里,我们其实知道,string和c风格字符串完全不是一个东西,(一个是类,一个是数组或者说是指针)必须写两种形式。
为了节约篇幅。这里只列出参数为String的东西了。至于参数为char*的话...把后面的.str删掉即可。
int find(String subs){ for(int i=0;i<strlen(str)-strlen(subs.str);i++) if(strcmp(str+i,subs.str)==0)return i; return -1; }
反复查找,直到最后一个。避免比较时候数组越界,最终位置在str长度减去subs长度。
如果一直没有,返回-1。实际上应该是string::npos,很多环境里面都定义为-1。
2.3.2.insert函数
insert函数用于往字符串里面插入一个字符串。函数的形式为:insert(插入位置,插入字串)。它将会在原字符串的插入位置后面插入字符串。例如。”abef”在第2个位置插入”cd”,结果为”abcdef”。
我们按照上面这个样例,来分析一下算法。
首先,我们根据cd的长度,来把插入点后面的东西后移。假设插入点为ins,插入字符串为subs。
for(i=strlen(str)-1;i>ins;i--){ str[i+strlen(subs)]=str[i]; }
我们注意插入的操作是从后往前的移动。
然后,需要在最后放入’\0’这一结束符。应该是在往str[strlen(s)-1+t_size+1]这个位置。
然后,我们需要把字符串subs拷贝到这个位置去。但是,不可以用一般的strcpy,这样会在最后放入一个’\0’,就结束了这个字符串,所以,我们不可以用strcpy。但是为了复制字符串,我们只能自制一个strcpy。因为没有'\0’,就叫做nz_strcpy(no zero的缩写)
void nz_strcpy(char *dest,const char *src){ while(*src!=0){ *dest=*src;++dest;++src; } } (中略) void insert(int ins,char *subs){ int i; ins--;//数组下标从0开始 int subs_size=strlen(subs);//取得subs长度,方便s元素后移 for(i=strlen(str)-1;i>ins;i--){ str[i+subs_size]=str[i];//移动元素 } str[strlen(str)-1+subs_size+1]='\0';//最终的结束符 nz_strcpy(str+ins+1,subs);//复制字符串 }
很简单吧。这就是线性表插入的基本操作。
2.3.3.erase操作
erase(int p,int n)
删除从p开始的n个字符。
void erase(int p,int n){ int front=p+1,rear=p+n; while(str[rear]!=’\0’){ str[front]=str[rear]; ++front;++rear; } str[front]=’\0’; }
我们使用覆盖的方法,设置一头一尾两个指针,每次把尾指针的内容复制到头指针,直到尾指针指向的字符为0。如果不为0,那么就继续下一个字符。例如,把abcdefg的第三个字符到第五个字符删除。我们用列表的方式来看一下。
1 2 3 4 5 6 7
a b c d e f g
a b F d e F g
a b f G e f G
a b f g 0 f g
其中,大写字母表示头指针和尾指针所在的位置。可以看到,把后面的字符逐个放到前面,最后添上0即可。因为添上了0,最后不用删除,字符串自动结束。
2.3.4.replace操作
其实我感觉replace和insert非常类似。replace(int start,int end,char *str);把start至end的区间全部替换成str。相当于先删除start-end的区间,然后再插入str。所以,偷懒的办法如下。
void replace(int st,int en,char *str){ erase(st,en); insert(st,str); }
这样做即可。
2.3.5.拾遗
事实上,类似于find,erase,insert,replace等函数的实现,实际上都有很多类型。就例如insert,这里就有大约七八种。
basic_string& insert (size_type p0 , const E * s); //在p0前面插入s basic_string& insert (size_type p0 , const E * s, size_type n); //将s的前n个字符插入p0位置 basic_string& insert (size_type p0, const basic_string& str); basic_string& insert (size_type p0, const basic_string& str,size_type pos, size_type n); //选取 str 的子串 basic_string& insert (size_type p0, size_type n, E c); //在下标 p0 位置插入 n 个字符 c iterator insert (iterator it, E c); //在 it 位置插入字符 c void insert (iterator it, const_iterator first, const_iterator last); //在字符串前插入字符 void insert (iterator it, size_type n, E c) ; //在 it 位置重复插入 n 个字符 c
(参考自http://c.biancheng.net/view/1449.html)
事实上,这些都使用了函数重载功能。如果要全部写起来,比较麻烦,而且很多函数可能我们平时不会用到,本文只挑选了部分出来。下面讲述一下对函数转换的方法。
例如,
basic_string& insert (size_type p0 , const E * s, size_type n); //将s的前n个字符插入p0位置
这一个。
首先,对这类函数的编写的步骤。第一步,对已知条件进行转化,根据后面的参数得出要操作的字符串。例如,这里,根据s和n,我们需要对s取前n个字符,把结果存放入s。第二步,使用标准函数。我们把标准函数的代码搬过来。
在例如,
basic_string& insert (size_type p0, size_type n, E c); //在下标 p0 位置插入 n 个字符 c
我们只需要先根据n和c,构建出要插入的字符串s,然后执行标准函数即可。
char s[n]; for(int i=0;i<n;i++)s[i]=c;
两句话即可转换。
关于其他的函数,我们可以参考笔者一开始给出的几个网站进行了解,尝试编写出更多的函数。
2.4.at函数和size函数
2.4.1.at函数
听前面的各种数组操作,有的人应该已经厌烦了吧。如果笔者这里继续写insert,erase,replace的各种新方法的话,估计各位又要犯困了。(笑)所以,这一节换换口味,讲几个简单的函数:at和size。
at函数类似于取字符串的一个字符。一般我们更加常用的方法是用下标,但是下标涉及到运算符重载,比较复杂。所以,这里我们先进行at函数的制作。
char at(int i){ assert(i<=size); return str[i]; }
我们一般只会用到函数的第二句语句,第一句assert可能不太常用,这里我们就来讲解一下。
assert(表达式);
如果表达式为真,不做任何操作。否则,如果表达式为0,那么就输出异常。如果想看看assert效果的话,可以在程序里写一个assert(0),看程序的反应。程序应该会输出”asseration failed”一句话,然后直接终止运行。输出的文字,根据环境不同,可能结果也会不同。
这里,为了不让数组越界,这里就用了一个assert检验下标i是否小于等于size。
事实上,标准库的at就有这个功能,笔者的dev c++环境会输出这个。
terminate called after throwing an instance of 'std::out_of_range'
what(): basic_string::at: __n (which is 100) >= this->size() (which is 3)
This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
2.4.2.size函数
其实写到这里,笔者感觉之前的东西有问题。size之前做了成员变量,不可以做函数,所以这里必须先把size成员变量改掉。名字就叫做len吧。
class String{ int len;//这里! char *str; ... };
size很简单,只需要调用strlen即可。
int size(){ return strlen(str); }
同样,还有一个功能完全相同的函数length。
int length(){ return strlen(str); }
很简单吧。
第三章:运算符重载和构造函数
3.1.赋值运算符
3.1.1.运算符重载
到这里,我们函数就做的差不多了。
我们一般使用的运算符,都是用自己的功能了。例如,+运算符是做加法。但是,对于字符串,+的作用完全不同,是字符串连接。所以,这就涉及到一个知识点,运算符重载。
为了方便,用成员函数很麻烦,所以,我们完全可以用运算符来完成这一功能,改变运算符自己的功能,叫做运算符重载。
运算符重载,在系统内部实际上是调用函数。例如,s=a+b,实际上就是s=a.operator+(b)。
operator+就是一个函数。
3.1.2.=的重载
与其啰啰嗦嗦说一大堆,不如自己动手去做做。
String operator=( String s){ strcpy(str,s.str); return *this; } String operator=(const char *s){ strcpy(str,s); return *this; }
运算符重载的一般格式如下:
类名 operator运算符(参数){ 操作; return *this; }
这是二元运算符的一般重载方式。
如果一个运算符R有X个参数,那么就称R为X元运算符。例如*,/,=都是二元运算符。而+,-即可以做一元运算符(正负号),也可以做二元运算符(一般的加减法)。
例如,一个二元运算符R的参数为A,B,那么这个运算记作A.operatorR(B),当作一个成员函数使用。其实等号运算符重载的写法为s.operator=(c)。而为了简便,s.operator=(c)可以简便写做s=c。这是不是非常类似于真正的string类型了?
而return *this返回的是什么?this是一个指针,指向当前在操作的类对象。所以,我们需要执行return *this,返回当前对象,这样才能正确执行我们的操作。
这样,我们的函数就全部完成了。
3.1.3.运算符重载的广泛运用
C++的标准输入和输出流中,都是用运算符重载来做的。
cin>>a;
cout<<p[i]<<endl;
中,<<和>>都是重载的运算符。此时,cin和cout肯定不是函数(因为函数不可以做计算),其实是一个变量。
<<符和>>符是左移位运算符和右移位运算符。至于cin和cout使用它的原因不知道,大概是为了看上去方便吧。
所以,上面的语句还可以写成;
cout.operator<<(p[i]).operator<<(endl);
顺便提一句,cout是ostream类的对象,cin是istream的对象。当然,本文不是讲cin和cout的,是讲string类型的,所以这里就不详细描述了。
3.2.“+”和“+=”的重载
3.2.1.strcat函数
strcat是C语言的用于字符串连接的标准函数。
strcat(字符串1,字符串2)把字符串2连接到字符串1后面。
3.2.2.正题
String operator+(String s){ strcat(str,s.str); return *this; } String operator+=(String s){ strcat(str,s.str); return *this; }
加号运算符和“+=”运算符都可以起到字符串连接的作用。非常简单。调用strcat即可。
3.3.比较运算符的重载
3.3.1.strcmp
惯例,我们先介绍c库标准函数。
strcmp比较两个字符串大小。
strcmp(a,b)
a>b 返回值>0
a<b 返回值<0
a=b 返回值=0
3.3.2.自制strcmp
这里先偏离正题,说说c库函数strcmp的自制。
int strcmp(char *s1;char *s2){ while(*s1==*s2){ ++s1;++s2;//如果相等,就下一个字符 } return *s1-*s2;//这里一定是不相等的字符,相减即可 }
一开始的while循环不断比较s1和s2,如果相等,指针++,指向下一个字符。
最后退出循环时,一定是不相等的,两个数相减,用作差法比较大小。
但是有一个问题,如果字符串相等,那就会比较到数组后面去,使得数组越界。我们必须保证第一个循环条件为字符不等于'\0',也就是不结束。
int strcmp(char *s1,char *s2){ while(*s1==*s2 && *(s1+1)!=0 && *(s2+1)!=0){ ++s1;++s2; } return *s1-*s2; }
保证下一个字符不等于0,这样做就可以了。
3.3.3.比较运算符的重载
比较运算符主要有大于,小于,等于三个。我们同样按照strcmp来进行重载,单手注意返回值变成了真和假。
bool operator>(String s){ if(strcmp(str,s.str)>0))return 1; else return 0; } bool operator<(String s){ if(strcmp(str,s.str)<0))return 1; else return 0; } bool operator==(String s){ if(strcmp(str,s.str)==0))return 1; else return 0; }
当然,我们还需要考虑参数为char*的字符串的情况,这里就略了。
制作起来非常简单,只需要调用strcmp即可。多说一下,如果是C语言用多的人可能不知道bool是什么,其实bool是一种特殊的1字节变量,只能存放1和0,表示真和假。逻辑运算符的返回就是真假。如果给bool类型赋值为任意一个非0值,那么视作赋值为1。所有不等于0的值都看作为真,这就是可以把if(a!=0)写作if(a)的原因。
3.3.4.compare函数
compare也是一个string的成员函数,也用于比较字符串大小。我们只看代码,猜一猜它的功能。
bool compare(String s){ if(strcmp(str,s.str)==0)return 1; else return -1; }
3.4.下标运算符的重载
3.4.1.什么是下标
我们之前在学习数组的时候,应该看到过这样的描述:
数组中每一个数都有一个编号,这个编号就是下标。
我们在引用数组元素时,经常用到s[i]这种写法,实际上i就是一个下标,而[]就是下标运算符。
3.4.2.下标运算符的重载
首先我们要搞清楚一点,下标是可以作为一个左值来进行赋值的。也就是说,我们是可以这样写的:s[i]='a’
而一般的函数是不能这样写的:s.at(i)='a’
所以,如何让这个下标运算符可以被赋值是一个问题。我们先抛开这个问题,来写程序。
char operator[] (int i){ return str[i]; }
好像很简单...等等!如果写一个类似于s[i]='a’的语句,会报错吗?
error: lvalue required as left operand of assignment
也就是说,这里是不可以作为一个左值来赋值的。因为这样的语句,函数返回的是一个常量a,所以这个语句也是表示’a’='a’,是非法的。所以,我们需要在函数声明的地方动点手脚。
char &operator[] (int i){ return str[i]; }
在前面加上&符号即可。&符号这里不是取地址的意思,而是表示引用。例如,交换两个变量的值,C++语言可以用引用的方法,这样写。
void swap(int &a,int &b);
这样,如果把a和b传递过去,形参就是对实参的引用,就实现了交换。同理,用引用的方式,就可以赋值了。
3.5.构造函数
3.5.1.构造函数
构造函数是在定义的时候执行的函数。例如,我们初始化string类型,一定看见过这样的语句:
string s("abcd")
在定义的时候初始化。
3.5.2.写法
构造函数没有返回值,且构造函数的名称与类名一致。
String(const char *s){ strcpy(str,s); }
但是注意构造函数只要定义了,我们就需要在初始化时写上构造函数,不然会出错。
String s;//错误
String s("");//正确
为了方便,我们写一个空构造函数。
String(void){ }
这样,在不写括号的情况下,c++会自动执行空函数,也就是说,初始化时,不加括号默认执行空构造函数。
趁热打铁,还有几个构造函数,我们一起实现一下。
String(int n,char c){ for(int i=0;i<n;i++) str[i]=c; }
初始化为若干相同字符。
不过突然想到一个问题,在初始化调用构造函数时,还没有init,所以str是一个未初始化的指针,会出错,构造函数也没有意义了。我们需要把init的过程写进构造函数里面,至于size先定义为100吧,如果不够再realloc...等等!用new分配的空间无法realloc,我们还是用stdlib.h库中的malloc分配,这样还能realloc。这样一来,init函数就没用了,可以把它删掉了,而且也更像真正的string类型了。
操作非常简单,在每一个构造函数加上一句str=(char*)malloc(100);即可。运行结果一样,真不错。
第四章:string类函数(续)
4.1.内存动态分配
之前运算符重载的所有内容都讲完了。我们换个口味,继续说函数。
首先我们要知道,str=(int*)malloc(100)指给str分配100个空间。一开始,我们没有写成在类中直接写char str[100]而是特意在构造函数这样写,就是为了以后可以重新扩大内存。现在,我们需要在内存不够时重新扩大内存。
扩大内存,我们不能用new,必须使用realloc函数用于分配过的指针再次分配。所以,我们需要在string中新增一个成员变量size,说明现在字符串有几个空间,以及变量free,就是size-strlen(str),说明剩余多少内存可用。
class String { public: char *str; int size; ... ... void insert(int ins,String t){ int tmp=strlen(str)+strlen(t.str); if(tmp>size){ str=(char*)realloc(str,tmp); size=tmp; } ... } String operator +=(String s){ if(strlen(s.str)+strlen(str)>size){ str=realloc(str,strlen(s.str)+strlen(str)); size=strlen(s.str)+strlen(str); } ... } String operator =(String s){ if(strlen(s.str)>size){ str=realloc(str,strlen(s.str)); size=strlen(s.str); } ... } };
这里在每一个函数之前都加上了字符长度判断,至于构造函数只需要加上size的赋值即可,不用多说。
4.2.append函数
4.2.1.append函数简介
在字符串后面追加字符串。
本文在这里只简单地写两种函数重载。第一种:直接在string的后面加上string(或char*字符数组)
第二种:在string的后面加上string的前n个字符。
4.2.2.编写函数
介绍完了,我们开始正式编写。首先我们找到函数的结束位置,然后在结束位置后面把需要的字符串拷贝过来即可。
上图是一张简图,表示函数的执行方式。
如果需要指定所加上的字符总数,那么我们使用strncpy就可以了。
void append(String s){ int pos; char *t=str; for(pos=0;t[pos]!='\0';++pos){} strcpy(t+pos,s.str); } void append(String s,int n){ int pos; char *t=str; for(pos=0;t[pos]!='\0';++pos){} strncpy(t+pos,s.str,n); }
4.3.begin和end迭代器
begin指向第一个字符,end指向最后一个字符。于是本节就这样写完了。
char begin(void){ return str[0]; } char end(void){ int i; for(i=0;str[i]!='\0';i++){} return str[i-1]; }
4.4.getline函数
getline读入一整行。
void getline(istream cin,string s){ int i; for(i=0;s[i]!='\n';i++)s[i]=getchar();//读入到行末尾 }
其实函数参数中的那个cin根本没有用到,只是装装样子而已。(cin定义在iostream中,是istream类型的变量)
第五章:拾遗
5.1.string.h和string
有些人根本不知道下面三个头文件的区别,经常在使用的时候搞混。
#include<string.h> #include<cstring> #include<string>
解释:
string.h是老版本C语言的头文件,cstring是新版本C++新增加的头文件,功能等同于string.h,包含的是字符数组(char*)相关的函数(例如strcpy,strcmp,strlen等)。
string是C++的头文件,包含的是string类(即string,本文写的那些函数)只有使用这个函数,才可以写那些有关string的代码。例如:
#include<iostream> #include<string> string s; int main(){ cin>>s; cout<<s.size(); }
其中定义的string类型和size函数都是这个头文件里面的。
到现在我还见到很多人在这种时候#include<cstring>,在部分编译器会编译错误!
5.2.字符数组
5.2.1.字符数组常识
上一节我们说了string.h,那么老版本的字符数组如何使用?
字符串在这种情况下是一个数组。
char s[100];
如果需要对这个数组做输入和输出,可以使用scanf或printf的%s参数。cout可以输出字符数组,但是cin不能输入字符数组。
原因:
cout<<"hello";
用双引号括起来的都是字符数组。虽然在使用string的时候,新的string可以和字符数组混用,但是这是两个东西。(所以本文中,所有函数都要写两遍,一遍是针对string的,一遍是针对字符数组的)
5.2.2.进阶——字符数组和指针
请看两种数组的声明,有什么区别?
char *s="abc"; char s[10]="abc";
不妨尝试执行下面的代码:
int main(){ s[0]='1'; cout<<s; }
运行结果:
上面的声明:segmentation fault(即runtime error,运行时崩溃错误)
下面的声明:输出1bc
可以看到,使用指针进行初始化,得到的是一个字符串常量。但是使用数组进行初始化,效果相当于拷贝字符串。(只有在这种情况下'='可以做拷贝字符串的作用)
(2021.8.8补充:
使用指针形式初始化是使得这个指针指向那个abc,也就是指向字符串常量。而对于字符串常量,内存是受到特殊保护的,只能读取不能写入,因此会发生错误。
而使用数组的形式,是对字符串进行拷贝操作,数组所在的内存区域没有受到特殊保护,因此可以写入。)
并且,使用指针形势初始化时,会出现一个warning。
warning: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]
5.3.string类的本质
终于写到最后一节了,最后我们来讨论string类的本质。
本质非常简单,这就是一个普通的类,和其他的stack,queue没有什么区别,就是一个数组+一堆成员函数。
因此,也是5.2中的结论,双引号括起来的不是string,而是const char*字符串。因此,string中,成员函数的参数,定义了两种形式,一种string,一种字符数组。使用上去,就像是string和字符数组完全通用。string的本质,还是一个普通的类。
当然,如果C++允许重载双引号的话(双引号也不是运算符),string估计就可以完全和字符数组通用了。就像这样:
String operator ""(const char *s){ String Str=s; return Str; }
双引号也不是运算符,上面的只是瞎写而已,如果真的要完全通用的话...就算到了宇宙毁灭,这也是不可能的吧。
(全文完,总字数7608字)