这篇文章首发于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字)

【原创】自制string类型(已完成)的更多相关文章

  1. 【原创】【长期更新】【未完待续】自制vector类型

    继<自制string类型>以来的第二篇自制类型的文章.马上要开学了,时间也不多了,争取在今年写完吧. 目录 一,vector类型简单介绍 1.简介 1.1.STL 1.2.vector 2 ...

  2. 【原创】Java和C#下String类型中的==和equals的原理与区别

    一.Java下 1.几个例子 public static void main(String[] arge) { String str1 = new String("1234"); ...

  3. JSP中的“小饼干”Cookie,用来存储数组的方式(下方已String类型的数组为例:)

    1.Cookie常用方法中,存储数据的方式: Cookie cookie = new Cookie("key","Value"); response.addCo ...

  4. 【原创】【自制系列】自制stack类型(泛型)

    前言 自制类型的第三篇,stack类型.stack是指栈,其实我个人认为stack是最好写的类型,没有之一.关于queue类型需要涉及到循环队列避免浪费内存,但是stack的插入删除都是对于栈顶而言, ...

  5. java内存分配和String类型的深度解析

    [尊重原创文章出自:http://my.oschina.net/xiaohui249/blog/170013] 摘要 从整体上介绍java内存的概念.构成以及分配机制,在此基础上深度解析java中的S ...

  6. Java日期的格式String类型GMT,GST换算成日期Date种类

    请尊重他人的劳动成果.转载请注明出处:Java日期格式化之将String类型的GMT,GST日期转换成Date类型 http://blog.csdn.net/fengyuzhengfan/articl ...

  7. ElasticSearch 5学习(9)——映射和分析(string类型废弃)

    在ElasticSearch中,存入文档的内容类似于传统数据每个字段一样,都会有一个指定的属性,为了能够把日期字段处理成日期,把数字字段处理成数字,把字符串字段处理成字符串值,Elasticsearc ...

  8. 把《c++ primer》读薄(3-1 标准库string类型初探)

    督促读书,总结精华,提炼笔记,抛砖引玉,有不合适的地方,欢迎留言指正. 问题1:养成一个好习惯,在头文件中只定义确实需要的东西 using namespace std; //建议需要什么再using声 ...

  9. C++标准库string类型

    string类型支持长度可变的字符串,C++标准库将负责管理与存储字符相关的内存,以及提供各种有用的操作.标准库string类型的目的就是满足对字符串的一般应用. 本文地址:http://www.cn ...

随机推荐

  1. Spring Cloud Alibaba - Spring Cloud Stream 整合 RocketMQ

    Spring Cloud Stream 简介 在微服务的开发过程中,可能会经常用到消息中间件,通过消息中间件在服务与服务之间传递消息,不管你使用的是哪款消息中间件,比如RabbitMQ.Kafka和R ...

  2. 6.算法竞赛中的常用JAVA API :Math类(转载)

    6.算法竞赛中的常用JAVA API :Math类 求最值 最小值 Math.min(int a, int b) Math.min(float a, float b) Math.min(double ...

  3. Linux C中strcpy , strncpy , strlcpy 的区别

    strcpy ,strncpy ,strlcpy的用法 好多人已经知道利用strncpy替代strcpy来防止缓冲区越界. 但是如果还要考虑运行效率的话,也许strlcpy是一个更好的方式. 1. s ...

  4. 用notepad2代替notepad

    Windows自带的notepad.exe功能比较弱,notepad2是一个比较好的替代方案,但在任何系统调用notepad的时候都能用notepad2代替并不是一件容易的事,下面是一个解决方法: h ...

  5. linux下安装redis-6.0.6、配置redis远程连接

    官网下载安装包redis-6.0.6.tar.gz https://redis.io/ 上传到服务器之后使用tar -zxvf进行解压,解压后如下: 进入解压的文件之后我们可以看到他的配置文件(配置文 ...

  6. 题解—God Knows

    考场上以为就是转化成一个无向图然后以为无向图有什么性质可以搞出来来着. 果然应验了那句话,一个思路想太久想不出来一般是假的. 所以这种一看就需要转化的题要多尝试能往哪转化,而不是按住一个思路不动. 只 ...

  7. 题解 [SDOI2010]所驼门王的宝藏

    传送门 保分题再度爆零,自闭ing×2 tarjan没写vis数组,点权算的也有点问题 这题情况3的连边有点麻烦,考场上想了暴力想了二分就是没想到可以直接拿map水过去 不过map果然贼慢,所以这也是 ...

  8. 【Tools】SSHUsage

    SSH(Secure Shell 的缩写)是一种网络协议,用于加密两台计算机之间的通信,并且支持各种身份验证机制.还能对操作者进行认证(authentication)和授权(authorization ...

  9. flutter canvas圆圈转圈动画

    import 'dart:math'; import 'dart:ui'; import 'package:flutter/material.dart'; void main() => runA ...

  10. IOC--框架进阶

    IOC控制反转 含义:把高层对底层的依赖 转移到由第三方决定 避免高层对底层的直接依赖 使得程序架构具有良好的扩展性和稳定性 理解:就是一种目的--解除依赖 DI依赖注入 含义:在构造对象是 可以自动 ...