C++string类详解

Posted Corwttaml

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++string类详解相关的知识,希望对你有一定的参考价值。


文章目录


🎪 string类


C语言中,字符串是以’\\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问

并且OJ中的字符串也基本以string类出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数

🚀1.标准库中的string类

这里给大家推荐一个查库函数的网站,我们学习C++一般以这个作为参照:
string类详解

  1. 字符串是表示字符序列的类
  2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。
  3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信息,请参阅basic_string)。
  4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。
  5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作

总结:

  1. string是表示字符串的字符串类
  2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
  3. string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator>string;
  4. 不能操作多字节或者变长字符的序列

在使用string类时,必须包含#include头文件以及using namespace std

⭐1.1 string构造函数


常用接口如下:

constructor函数名称功能说明函数原型
string()构造空的string类对象,即空字符串string();
string(const char * s)用C-string来构造string类对象string (const char* s, size_t n);
string(size_t n, char c)string类对象中包含n个字符cstring (size_t n, char c);
string(const string&s)拷贝构造函数string (const string& str, size_t pos, size_t len = npos);
int main()

	string s1;
	string s2("hello wold");

	//隐式类型转换
	string s3 = "hello world";

	//从s3的第六个字符开始取三个字符
	string s4(s3, 6, 3);
	cout << s4 << endl;

	//超过了范围就取到末尾
	string s5(s3, 6, 12);
	cout << s5 << endl;

	//如果不给第三个参数呢? 官方文档给了一个缺省值 -- npos -- 42亿多
	//一个字符串不可能有那么长,所有默认取到结尾
	string s6(s3, 6);
	cout << s6 << endl;

	string s7("hello world", 6);
	cout << s7 << endl;

	string s8(10, 'x');
	cout << s8 << endl;

	//size_t是一种无符号整数类型就是unsigned int
	for (size_t i = 0; i < s2.size(); i++)
	
		s2[i]++;
	
	cout << s2 << endl;

	for (size_t i = 0; i < s2.size(); i++)
	
		cout << s2[i] << " ";
	

	//析构函数了解即可
	return 0;

对于拷贝构造函数,如果我们没有给第三个参数,会默认是缺省值npos,我们从官方文档可见,它是一个极大的数,我们所能用到的字符串长度不可能有这么大的,所以不给值的就默认是这个缺省值——即取到字符串末尾


对于析构函数自行看一下官方文档即可.

⭐1.2 string容量操作


常用接口如下:

函数名称功能说明函数原型
size返回字符串有效字符长度size_t size() const;
length返回字符串有效字符长度size_t length() const;
capacity返回空间总大小size_t capacity() const;
empty检测字符串释放为空串,是返回true,否则返回falsebool empty() const;
clear清空有效字符void clear();
reserve为字符串预留空间void reserve (size_t n = 0);
resize将有效字符的个数改成n个,多出的空间给出的字符c填充void resize (size_t n);void resize (size_t n, char c);

string的容量

int main()

	string s1("hello world");
	
	//字符串有效数据位数
	//size方法跟length是没有区别的
	//size是为了跟stl保持一致后面再加入的,建议用size
	cout << s1.size() << endl;//11
	cout << s1.length() << endl;//11

	//字符串容量
	cout << s1.capacity() << endl;//15

	//字符串所能达到的最大长度
	cout << s1.max_size() << endl;//字符串的最大长度,不同编译器的值可能不同

	return 0;


string的扩容

我们可以用push_back方法向string对象里面插入一个字符

//string扩容
int main()

	//观察扩容的情况,不同对象可能不同
	//对于库里面的string第一次是2倍扩容,其余次数是1.5倍扩容
	string s("hello world");

	size_t sz = s.capacity();
	cout << "capacity: " << sz << '\\n';
	cout << "making s grow:\\n";

	for (int i = 0; i < 100; ++i)
	
		s.push_back('c');
		if (sz != s.capacity())
		
			sz = s.capacity();
			cout << "capacity changed: " << sz << '\\n';
		
	 
	return 0;


实际上,string类的简单成员如下:、

class string

private:
	char* _ptr;
	char _buf[16];
	size_t _size;
	size_t _capacity;
;

当我们调试观察string内部成员,capacity超过16的话就不会存在buf数组里面了,而是存在于ptr申请的堆空间上了,如果没超过16那么就会存在buf数组上

但是我们知道,凡是关于扩容,我们的系统就会加大开销,所以无论我们是在做oj题还是在工作中,都尽量的事先扩好需要的容量,会大大的减小开销,以下是两个常用string扩容函数

reserve && resize

int main()

	string s1("hello world");

	//扩容但不初始化 111
	s1.reserve(100);
	cout << s1.size() << endl;//11
	cout << s1.capacity() << endl;//111

	string s2("hello world");

	//扩容初始化,未给第二个参数的话初始化成缺省值0
	s2.resize(100);
	cout << s2.size() << endl;//100
	cout << s2.capacity() << endl;//111

	//扩容初始化,将一百个位置全部初始化为x
	//但是字符串的100个位置全有值,所以字符串无变化
	s2.resize(100, 'x');
	cout << s2.size() << endl;
	cout << s2.capacity() << endl;

	//如果比size小,还可以删除数据,保留前五个数据不变,但容量不会缩容
	s2.resize(5);
	cout << s2.size() << endl;//5
	cout << s2.capacity() << endl;//111

	return 0;

resize初始化不会动原始数据的值,空间不足则开辟的空间,那段开辟的空间里面才会存放初始化的值

无论是reserve还是resize均不会缩容(即变小_capacity的值),因为底层的OS并不允许这么做,并且这么做会造成其它的一些问题

注意:

  1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
  2. clear()只是将string中有效字符清空,不改变底层空间大小。
  3. resize(size_t n)resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
  4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小

⭐1.3 string类对象的访问遍历操作

常用接口如下:

函数名称功能说明函数原型
operator[]返回pos位置的字符,const string类对象调用char& operator[] (size_t pos); const char& operator[] (size_t pos) const;
at返回pos位置的字符,const string类对象调用char& at (size_t pos);const char& at (size_t pos) const;
beginendbegin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器iterator begin();const_iterator begin() const;iterator end();const_iterator end() const;
rbeginrendbegin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器reverse_iterator rbegin();const_reverse_iterator rbegin() const;reverse_iterator rend();const_reverse_iterator rend() const;
范围forC++11支持更简洁的范围for的新遍历方式null

operator[] && at()

//string访问操作
int main()

	string s("hello world");

	cout << s[0] << endl;
	cout << s.at(0) << endl;

	//操作符越界:内部是直接assert断言的
	s[100];//断言终止

	//at方法越界的话是抛异常,可以被捕获到
	try
	
		s.at(100);
	
	catch (const exception& e)
	
		cout << e.what() << endl;//打印异常信息
	

	return 0;

C++有一套异常机制,一些数组越界之类的问题可以靠抛异常来解决,这个我们后面再谈.

begin | end && rbegin | rend

//string遍历操作
void Func(const string& str)

	//如果继续用正常迭代器编译不通过 - const对象不允许写权限放大
	//可以使用const迭代器,遍历和读容器的数据,不能写

	//string::const_iterator it = str.begin();
	auto it = str.begin();
	while (it != str.end())
	
		cout << *it << " ";
		it++;
	
	cout << endl;

	//string::const_reverse_iterator rit = str.rbegin();
	auto rit = str.rbegin();
	while (rit != str.rend())
	
		cout << *rit << " ";
		++rit;
	
	cout << endl;

//string内迭代器
int main()

	string s("hello world");

	//正向迭代器
	//string::iterator it = s.begin();

	auto it = s.begin();
	while (it != s.end())
	
		cout << *it << " ";
		it++;
	
	cout << endl;

	//反向迭代器
	//string::reverse_iterator rit = s.rbegin();

	auto rit = s.rbegin();
	while (rit != s.rend())
	
		cout << *rit << " ";
		++rit;
	
	cout << endl;

	Func(s);

	return 0;

对于迭代器这种类型比较复杂,建议用auto自动识别类型,因为const迭代器需要特别区分,所以比较容易出错.我们再用范围for试试遍历string对象(底层也是迭代器)

//范围for
int main()

	string str("hello world");
	for(auto c : str)
	
		cout << c << endl;
	
	return 0;

在string这里使用迭代器明显没有下标访问有用,那为啥还会存在迭代器呢??我们想想二叉树的遍历还能用下标吗?不能,而迭代器是通用的,并不拘泥于顺序表类型

C++11提供了cbegincend以及crbegincrend来区分const对象,了解即可

⭐1.4 string类对象的修改操作

函数名称功能说明函数原型
operator+=在字符串尾部追加一个字符串或字符
append在字符串后追加一个字符串
push_back在字符串后尾插字符c
assign把字符串赋给当前对象
insert在字符串任意位置插入一个字符串
erase删除任意位置的任意字符
replace替换掉字符串任意位置的字符串
swap交换两个字符串
pop_back将字符串尾部字符删除

注意:

  1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
  2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好

字符串追加

int main()
	string s1("hello");


	//追加一个字符
	s1.push_back(' *');
	cout << s1 << endl;

	//追加一个字符串
	s1.append("world");
	cout << s1 << endl;

	s1.clear();

	//通用 -- 推荐使用
	s1 += "hello ";
	s1 += 'w';
	s1 += "orld";
	cout << s1 << endl;

	return 0;

字符串插入删除替换

//insert  erase replace
int main()

	//insert:插入数据,不推荐经常使用 - 时间复杂度高,效率低
	string s1("world");

	//任意位置插入字符串
	s1.insert(0, "hello");
	cout << s1 << endl;

	//插入多个字符
	s1.insert(5, 3,  'a');
	cout << s1 << endl;
	
	//迭代器大法
	s1.insert(s1.begin()        + 5, ' ');
	cout << s1 << endl;
	
	//迭代器插入区间后面我们再讲

	//erase:删除数据,也存在挪动数据,效率低下
	string s2("hello world");

	//第五个位置开始删除1个字符
	s2.erase(5, 1);
	cout << s2 << endl;

	//传入迭代器,删除该位置字符
	s2.erase(s2.begin() + 5);
	cout << s2 << endl;

	//超过长度的时候,直接删到字符串尾部,因为给了缺省值npos
	s2.erase(5, 30);
	cout << s2 << endl;

	//replace:替换字符,效率也比较低下
	string s3("hello world");

	s3.replace(5, 2, "%20");
	cout << s3 << endl;

	//swap
	s1.swap(s2);
	cout << s1 << endl;
	cout << s2 << endl;

	swap(s1, s2);
	cout << s1 << endl;
	cout << s2 << endl;

	//有什么区别?
	//实际上我们直接库里面的swap函数会比用模板类生成的函数高效许多


	return 0;

⭐1.5 string类对象的常用功能

常用接口如下:

函数名称功能说明函数原型
c_str打印字符串吗,以\\0结尾标志结束
find从字符串pos位置开始往后找字符串,完全匹配则返回第一个字符出现的位置
refind从字符串pos位置开始往前找字符串,完全匹配则返回最后一个字符出现的位置
find_first_of从字符串pos位置开始从前往后找,找到输入的字符串中任意一个字符的位置并返回
find_last_of从字符串pos位置开始从后往前找,找到输入的字符串中任意一个字符的位置并返回
substr在str中从pos位置开始,截取n个字符,然后将其返回
void Test1()

	//c_str:将字符串识别成const char*,以\\0为准认定为末尾
	string s1("hello world");

	//区别:流插入按照size()打印,c_str按照\\0终止位置进行打印
	s1 += '\\0';
	s1 += "xxxxx";
	cout << s1 << endl;
	cout << s1.c_str() << endl;

	//我们可以用c_str来将string对象转换为const char*
	string filename("Work.cpp");
	FILE* fout = fopen(filename.c_str(), "r");
	if (fout == nullptr)
	
		perror("fopen fail");
		exit(-1);
	
	cout << "Work.cpp:" << endl;
	char ch = fgetc(fout);
	while (ch != EOF)
	  
		cout << ch;
		ch = fgetc(fout);
	
	fclose(fout);


void Test2()

	string file("string.cpp.tar.zip");

	//从后往前找rfind
	size_t pos = file.rfind('.');
	if (pos != string::npos)
	
		//string suffix = file.substr(pos, file.size() - pos);
		string suffix = file.substr(pos);
		cout << suffix << endl;
	

	// 取出url中的域名
	string url("http://www.cplusplus.com/reference/string/string/find/");
	cout << url << endl;
	size_t start = url.find("://");
	if (start == string::npos)
	
		cout << "invalid url" << endl;
		return 0;
	
	start += 3;
	size_t finish = url.find('/', start);
	string address = url.substr(start, finish - start);
	cout << address << endl;

	// 删除url的协议前缀
	pos = url.find("://");
	url.erase(0, pos + 3);
	cout << url << endl;

void Test3()

	//find_first_of 顺着找,一旦输入字符串中任意一个字符匹配成功则返回其位置
	//find_last_of 倒着找,一旦输入字符串中任意一个字符匹配成功则返回其位置
	string str("Please, replace the vowels in this sentence by asterisks.");
	size_t found = str.find_first_of("aeiou");
	while (found != string::npos)
	
		str[found] = '*';
		found = str.find_first_of("aeiou", found 

java中的String类常量池详解

test1:

package StringTest;

public class test1 {

    /**
     * @param args
     */
    public static void main(String[] args){
        String a = "a1";
        String b = "a"+ 1;
        System.out.println(a==b);
    }//true

}

test2:

package StringTest;

public class test2 {

    /**
     * @param args
     */
    public static void main(String[] args){
        String a = "ab";
        String bb = "b";
        String b = "a"+ bb;    //编译器不能确定为常量
        System.out.println(a==b);
    }//false

}

test3:

package StringTest;

public class test3 {

    /**
     * @param args
     */
    public static void main(String[] args){
        String a = "ab";
        final String bb = "b";
        String b = "a"+ bb;    //bb加final后是常量,可以在编译器确定b
        System.out.println(a==b);
    }//true

}

test4:

package StringTest;

public class test4 {

    /**
     * @param args
     */
    public static void main(String[] args){
        String a = "ab";
        final String bb = getBB();
        String b = "a"+ bb;//bb是通过函数返回的,虽然知道它是final的,但不知道具体是啥,要到运行期才知道bb的值
        System.out.println(a==b);
    }//false
    private static String getBB(){ return "b"; }

}

test5:

package StringTest;

public class test5 {

    /**
     * @param args
     */
    private static String a = "ab";
    public static void main(String[] args){
        String s1 = "a";
        String s2 = "b";
        String s = s1 + s2;//+的用法
        System.out.println(s == a);
        System.out.println(s.intern() == a);//intern的含义
    }//flase true

}

test6:

package StringTest;

public class test6 {

    /**
     * @param args
     */
    private static String a = new String("ab");
    public static void main(String[] args){
        String s1 = "a";
        String s2 = "b";
        String s = s1 + s2;
        System.out.println(s == a);
        System.out.println(s.intern() == a);
        System.out.println(s.intern() == a.intern());
    }//flase false true
}

String常量池详解:

  1.String使用private final char value[]来实现字符串的存储,也就是说String对象创建之后,就不能再修改此对象中存储的字符串内容,就是因为如此,才说String类型是不 可变的(immutable)。String类有一个特殊的创建方法,就是使用""双引号来创建.例如new String("i am")实际创建了2个
  String对象,一个是"i am"通过""双引号创建的,另一个是通过new创建的.只不过他们创建的时期不同,
  一个是编译期,一个是运行期!java对String类型重载了+操作符,可以直接使用+对两个字符串进行连接。运行期调用String类的intern()方法可以向String Pool中动态添加对象。
  
  例1
  String s1 = "sss111";
  //此语句同上
  String s2 = "sss111";
  System.out.println(s1 == s2); //结果为true
  例2
  String s1 = new String("sss111");
  String s2 = "sss111";
  System.out.println(s1 == s2); //结果为false
  例3
  String s1 = new String("sss111");
  s1 = s1.intern();
  String s2 = "sss111";
  System.out.println(s1 == s2);//结果为true
  例4
  String s1 = new String("111");
  String s2 = "sss111";
  String s3 = "sss" + "111";
  String s4 = "sss" + s1;
  System.out.println(s2 == s3); //true
  System.out.println(s2 == s4); //false
  System.out.println(s2 == s4.intern()); //true
  

  结果上面分析,总结如下:

   1.单独使用""引号创建的字符串都是常量,编译期就已经确定存储到String Pool中;

  2,使用new String("")创建的对象会存储到heap中,是运行期新创建的;

  3,使用只包含常量的字符串连接符如"aa" + "aa"创建的也是常量,编译期就能确定,已经确定存储到String Pool中;

  4,使用包含变量的字符串连接符如"aa" + s1创建的对象是运行期才创建的,存储在heap中;

  还有几个经常考的面试题:
  
  String s1 = new String("s1") ;
  String s2 = new String("s1") ;
  上面创建了几个String对象?
  答案:3个 ,编译期Constant Pool中创建1个,运行期heap中创建2个.(用new创建的每new一次就在堆上创建一个对象,用引号创建的如果在常量池中已有就直接指向,不用创建)

  String s1 = "s1";
  String s2 = s1;
  s2 = "s2";
  s1指向的对象中的字符串是什么?
  答案: "s1"。(永远不要忘了String不可变的,s2 = "s2";实际上s2的指向就变了,因为你不可以去改变一个String,)

--------------------------------------------------------------------------------------------------------------------------------------------------

String是一个特殊的包装类数据。可以用: 
String str = new String("abc"); 
String str = "abc"; 
两种的形式来创建,第一种是用new()来新建对象的,它会在存放于堆中。每调用一次就会创建一个新的对象。 
而第二种是先在栈中创建一个对String类的对象引用变量str,然后通过符号引用去字符串常量池里找有没有"abc",如果没有,则将"abc"存放进字符串常量池,并令str指向”abc”,如果已经有”abc” 则直接令str指向“abc”。 

比较类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==,下面用例子说明上面的理论。 
String str1 = "abc"; 
String str2 = "abc"; 
System.out.println(str1==str2); //true 
可以看出str1和str2是指向同一个对象的。 

String str1 =new String ("abc"); 
String str2 =new String ("abc"); 
System.out.println(str1==str2); // false 
用new的方式是生成不同的对象。每一次生成一个。 

因 此用第二种方式创建多个”abc”字符串,在内存中其实只存在一个对象而已. 这种写法有利与节省内存空间. 同时它可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。 

另 一方面, 要注意: 我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,创建了String类的对象str。担心陷阱!对象可能并没有被创建!而可能只是指向一个先前已经创建的 对象。只有通过new()方法才能保证每次都创建一个新的对象。 
由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。 
1. 首先String不属于8种基本数据类型,String是一个对象。 
因为对象的默认值是null,所以String的默认值也是null;但它又是一种特殊的对象,有其它对象没有的一些特性。 

2. new String()和new String(”")都是申明一个新的空字符串,是空串不是null; 

3. String str=”kvill”;String str=new String (”kvill”)的区别

看例1: 

String s0="kvill"; 
String s1="kvill"; 
String s2="kv" + "ill"; 
System.out.println( s0==s1 ); 
System.out.println( s0==s2 ); 
结果为: 
true 
true 

首先,我们要知结果为道Java会确保一个字符串常量只有一个拷贝。 
因 为例子中的s0和s1中的”kvill”都是字符串常量,它们在编译期就被确定了,所以s0==s1为true;而”kv”和”ill”也都是字符串常 量,当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以s2也同样在编译期就被解析为一个字符串常量,所以s2也是常量池中” kvill”的一个引用。所以我们得出s0==s1==s2;用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。 

看例2: 
String s0="kvill"; 
String s1=new String("kvill"); 
String s2="kv" + new String("ill"); 
System.out.println( s0==s1 ); 
System.out.println( s0==s2 ); 
System.out.println( s1==s2 ); 
结果为: 
false 
false 
false 

例 2中s0还是常量池中"kvill”的应用,s1因为无法在编译期确定,所以是运行时创建的新对象”kvill”的引用,s2因为有后半部分 new String(”ill”)所以也无法在编译期确定,所以也是一个新创建对象”kvill”的应用;明白了这些也就知道为何得出此结果了。 

4. String.intern(): 
再补充介绍一点:存在于.class文件中的常量池,在运行期被JVM装载,并且可以扩充。String的intern()方法就是扩充常量池的 一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用;看例3就清楚了 

例3: 
String s0= "kvill"; 
String s1=new String("kvill"); 
String s2=new String("kvill"); 
System.out.println( s0==s1 ); 
System.out.println( "**********" ); 
s1.intern(); 
s2=s2.intern(); //把常量池中"kvill"的引用赋给s2 
System.out.println( s0==s1); 
System.out.println( s0==s1.intern() ); 
System.out.println( s0==s2 ); 
结果为: 
false 
********** 
false //虽然执行了s1.intern(),但它的返回值没有赋给s1 
true //说明s1.intern()返回的是常量池中"kvill"的引用 
true 

最 后我再破除一个错误的理解:有人说,“使用 String.intern() 方法则可以将一个 String 类的保存到一个全局 String 表中 ,如果具有相同值的 Unicode 字符串已经在这个表中,那么该方法返回表中已有字符串的地址,如果在表中没有相同值的字符串,则将自己的地址注册到表中”如果我把他说的这个全局的 String 表理解为常量池的话,他的最后一句话,”如果在表中没有相同值的字符串,则将自己的地址注册到表中”是错的: 

看例4: 
String s1=new String("kvill"); 
String s2=s1.intern(); 
System.out.println( s1==s1.intern() ); 
System.out.println( s1+" "+s2 ); 
System.out.println( s2==s1.intern() ); 
结果: 
false 
kvill kvill 
true 

在这个类中我们没有声名一个”kvill”常量,所以常量池中一开始是没有”kvill”的,当我们调用s1.intern()后就在常量池中新添加了一个”kvill”常量,原来的不在常量池中的”kvill”仍然存在,也就不是“将自己的地址注册到常量池中”了。 
s1==s1.intern()为false说明原来的”kvill”仍然存在;s2现在为常量池中”kvill”的地址,所以有s2==s1.intern()为true。 

以上是关于C++string类详解的主要内容,如果未能解决你的问题,请参考以下文章

C++string类详解

c++string详解

C语言中有string吗?

C++ string 字符串类详解

C语言开发工具详解

C语言 · C++中map的用法详解