*C++:string

一.STL简介

1.STL

STL(standard template libaray- 标准模板库 ) C++ 标准库的重要组成部分 ,不仅是一个可复用的组件库,而且 是一个包罗数据结构与算法的软件框架

2.STL六大组件

二.标准库里的string类

标准string库网址:string - C++ Reference

1.string构造函数

int main()
{
    string s1;
    string s2("张三");
    string s3("hello world");
    string s4(10, '*');       //10个*
    string s5(s2);            //拷贝构造
    string s6(s3, 0, 5);      //以s3为基准,拷贝第0到第5个字符
    string s7(s3, 6);         //第三个参数没有给的时候会自动填充一个npos(-1),指向最后一个位置
    string s8(s3, 6, 100);    //超出string长度的时候,会以最后一个位置为结尾

    cout << s1 << endl;
    cout << s2 << endl;
    cout << s3 << endl;
    cout << s4 << endl;
    cout << s5 << endl;
    cout << s6 << endl;
    cout << s7 << endl;
    cout << s8 << endl;
}

2.赋值运算符重载(operator=)

int main()
{
	string s1;
	string s2("张三");

	s1 = s2;             //str
	cout << s1 << endl;
 
 	s1 = "1111";         //char*
	cout << s1 << endl; 

	s1 = '2';            //char
	cout << s1 << endl;

	return 0;
}

大于小于号运算符重载:

cout << (s1 == s2) << endl; //0
cout << (s1 > s2) << endl;  //0

3.string尾插(字符,字符串)

(1).尾插一个字符(push_back)

int main()
{
	string s1("hello");
	// 尾插一个字符
	s1.push_back('7');    //hello7
}
(2).尾插一个字符串(append,+=)

不过在一些情况下+=是更好的选择:

int main()
{
	// 增
	string s1("hello");
	// 尾插一个字符
	s1.push_back('7');
	// 尾插一个字符串
	s1.append("world");
    //    +=
	s1 += '7';                //本质上+=字符就是调用push_back
	s1 += "world";            //本质上+=字符串就是调用append
}

4.迭代器(iterator)

对于一个字符串,在C语言中我们使用下标+[ ],在C++的string里面,我们可以使用迭代器来实现字符串的遍历。

在这之前,我们先学会begin()和end()的使用方法:

所以实际上,这两个东西就是返回一个指向字符串起始/末尾字符的迭代器。

C语言遍历string的方法:

int main()
{
	string s1("hello world");
	cout << s1 << endl;

	// 遍历string
	cout << s1.size() << endl;   //不包括\0

    // 下标+[]
	for (size_t i = 0; i < s1.size(); i++)
	{
		cout << s1[i] << " ";
	}
}

C++可以使用迭代器来实现遍历:

int main()
{
	string s1("hello world");
	cout << s1 << endl;

    //迭代器
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		// 写
		(*it)--;
		++it;
	}
	cout << endl;

	it = s1.begin();
	while (it != s1.end())
	{
		// 读
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

上面是正向迭代器,还有一个反向迭代器reverse_iterator,如果需要的是只读的对象,还可以有const_iterator和const_reverse_iterator。

范围for

相比于迭代器,范围for的迭代方式往往更加简洁:

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

	// 范围for   应用范围更广   //缺点:只能正向遍历,不能反向遍历
	// 底层替换为迭代器
	//for (char& ch : s1)
	for (auto& ch : s1)
	{
		// 写 
		ch--;
	}
	cout << endl;

	for (char ch : s1)
	{
		// 读
		cout << ch << " ";
	}
	cout << endl;
    return 0;
}

5.内存相关函数和扩容机制

(1).内存相关函数
int main()
{
	string s1("hello world");
	cout << s1.size() << endl;
	cout << s1.length() << endl;     //size 和 length本质上是一样的,推荐使用size
	cout << s1.max_size() << endl;   //最大长度(没啥用)
	cout << s1.capacity() << endl;     //已开辟的空间大小(不同类型的编译器结果是不一样的)
}

clear()函数可以用来清空string,但是只能清空size,清空不了capacity,因为可能后续还会使用所以不会清理。

int main()
{
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	s1.clear();     //clear只清理size,不清理capacity
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
}

reserve

reserve会开一块空间,vs会比给定的空间多扩容一点,XSell按实际开辟。

改变开辟空间的大小需要先清理数据,直接reserve不会起作用。

void TestPushBackReserve()
{
	string s;
	s.reserve(100);   //这里就是提前开好了空间,后续不再进行改变 
	//vs实际开出来的大小会比100(给出来的数)大   XShell就是按照实际的开辟   原则就是必须比给的数大
	size_t sc = s.size();
	size_t sz = s.capacity();
	cout << "capacity changed: " << sc << '\n';
	cout << "capacity changed: " << 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';
		}
	}

	//想要改变空间大小需要先clear(),然后再设置新的空间大小
	s.clear(); 
	cout << "capacity changed: " << sz << '\n';

	s.reserve(10);   //不清数据是不改变的
	sz = s.capacity();
	cout << "capacity changed: " << sz << '\n';
}

// reserve // 保留
// reverse // 反转
int main()
{
	TestPushBackReserve();

	return 0;
}
resize
int main()
{
	string s1("hello world");

	// 开空间
	s1.reserve(100);
	cout << s1.size() << endl;//11
	cout << s1.capacity() << endl;//111
	  
	// 开空间+填值初始化
	s1.resize(200);   //resize是对开辟出来的空间的200个对象初始化占用size大小,reserve比要求的的多一点
	s1.resize(200, 'x'); //hello worldxxxxxxxxxxxxxxxxxxxxxxxx...  如果空间大于原本,先进行扩容然后再填值初始化
	cout << s1.size() << endl;//200
	cout << s1.capacity() << endl;//207

	s1.resize(5);   //如果小于size的长度,则缩小显示个数,但是不会缩容  //hello
	cout << s1.size() << endl;//5
	cout << s1.capacity() << endl;//207

	s1.clear();   //可以把s1里面的内容给清除(size归0),但是空间不会在clear()函数上清除(capacity不归0)
	s1.resize(0);
	cout << s1.size() << endl;//0
	cout << s1.capacity() << endl;//207

	//没啥用,可以不记
	s1.shrink_to_fit();    //这个函数名义上是把s1空间调整至合适的大小,但实际上空间大小还是会比实际稍微大一点点
	cout << s1.size() << endl;//0
	cout << s1.capacity() << endl;//15


	//目前缩容的唯一办法:调用clear()函数后重新reserve
	return 0;
}
(2).扩容机制(vs)
int main()
{
    string s1("hello world");

    size_t old = s1.capacity();
    for (size_t i = 0; i < 100; i++)
    {
	    s1 += 'x';
	    //vs扩容机制:除第一次是原本的两倍,剩下的都是上一次容量的1.5倍
	    //XShell是二倍扩容
    	if (old != s1.capacity()) 
	    {
	    	cout << "扩容:" << s1.capacity() << endl;
	      	old = s1.capacity();	
	    }
    }
}

6.at和错误分析

(1).at

有个引用符号,说明是可以修改的(下面会输出xello world)

int main()
{
	try {
		string s1("hello world");
		s1.at(0) = 'x';
		cout << s1 << endl;
		//s1[15];  // 暴力处理
		s1.at(15); // 温和的错误处理
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}
(2).错误分析

如上图,类似于try-catch这样的就是一个错误检查,比如我们用s1.at(15);这是一个温柔检查,会抛出一个异常(即进入catch),如果直接用s1[15]会直接中断,因为[]是有强制性的,它是决不允许越界访问的。

s1.at(15);

s1[15];

7.插入删除

(1).insert

int main()
{
	string s1;

	s1.insert(0, "hello");  //在第几个位置插入
	cout << s1 << endl;

	s1.insert(5, "world");
	cout << s1 << endl;

	s1.insert(0, 10, 'x');  //第0开始插入10个x
	cout << s1 << endl;

	s1.insert(s1.begin()+10, 10, 'y');
	cout << s1 << endl;

	return 0;
}

(2).erase

int main()
{
	string s1("hello world");
	s1.erase(5, 1);   //如果类似于(5,7)超出了界限,则直接把后面的删掉,不会报错
	cout << s1 << endl;

	s1.erase(5);    //从第5个位置开始后面的全删掉
	cout << s1 << endl;

	try{
		s1.erase(11);   //如果位置超了,会抛出一个异常
		cout << s1 << endl;
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
		
	string s2("hello world");
	s2.erase(0, 1);
	cout << s2 << endl;

	s2.erase(s2.begin());
	cout << s2 << endl;

	return 0;
}

其中要注意,s.erase()对应的作用,s1.erase(5);是传入了一个范围,s2.erase(s2.begin());是传入了一个指向具体位置的迭代器。所以前者为删除一串数据,后者是删除一个确定的元素。

(3).replace

int main()
{
	// world替换成 xxxxxxxxxxxxxxxxxxxxxx
	string s1("hello world hello bit");
	s1.replace(6, 5, "xxxxxxxxxxxxxxxxxxxxxx");   //第6个位置起的5个字符,替换成xxxxxxxxxxxxxxxxxxxxxx
	cout << s1 << endl;
    
    //replace没有缺省值
	s1.replace(6, 23, "yyyyy");
	cout << s1 << endl;

	// 所有空格替换成20%
	string s2("hello world hello bit");
	string s3;
	for (auto ch : s2)
	{
		if (ch != ' ')
		{
			s3 += ch;
		}
		else
		{
			s3 += "20%";
		}
	}

	s2 = s3;
	cout << s2 << endl;           //string的流插入重载
	cout << s2.c_str() << endl;   //本质上是调用的底层的private里的char* str

	return 0;
}

8.查找

(1).find


int main()
{
	string url = "ftp://www.baidu.com/?tn=65081411_1_oem_dg";

	// 协议:ftp
	// 域名:www.baidu.com
	// 资源名:?tn=65081411_1_oem_dg
	size_t pos1 = url.find("://");
	//会返回第一个匹配项的第一个字符的位置。且不给第二个参数的情况下,默认从第一个字符开始找
	//如果没有相匹配的字符,返回string::npos  (-1)
    //请注意,与成员find_first_of不同,每当搜索多个字符时,仅匹配其中一个字符是不够的,整个序列都必须匹配。
	cout << pos1 << endl;
	string protocol;
	if (pos1 != string::npos)
	{
		protocol = url.substr(0, pos1);    //子串,把第一个参数的位置到第二个参数的位置提取出来
	}
	cout << protocol << endl;

	string domain;
	string uri;

	size_t pos2 = url.find('/', pos1 + 3);
	if (pos2 != string::npos)
	{
		domain = url.substr(pos1 + 3, pos2 - (pos1 + 3));
		uri = url.substr(pos2 + 1);
	}
	cout << domain << endl;
	cout << uri << endl;

	//rfind 从后往前找的

	return 0;
}

*  substr

str 中从 pos 位置开始,截取 n 个字符,然后将其返回
(2).find_first_of

int main()
{
	string url = "ftp://www.baidu.com/?tn=65081411_1_oem_dg";

	size_t pos1 = url.find_first_of(":&/");
	cout << pos1 << endl;

	return 0;
}

(size_t pos1 = url.find_first_of(":&/");)

如果是用的find,会返回-1,这就是find和find_first_of的区别。find与成员find_first_of不同,每当搜索多个字符时,仅匹配其中一个字符是不够的,整个序列都必须匹配。

(size_t pos1 = url.find(":&/");)

9.例题

. - 力扣(LeetCode). - 备战技术面试?力扣提供海量技术面试资源,帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。icon-default.png?t=O83Ahttps://leetcode.cn/problems/add-strings/description/

class Solution {
public:
    string addStrings(string num1, string num2) {
        int end1 = num1.size()-1;
        int end2 = num2.size()-1;

        string strRet;
        int carry = 0;
        //只要还有数,就得接着算,所以用或
        while(end1 >= 0 || end2 >= 0) 
        {
            //没有数的进来只能置0,有数的进来置其距离'0'字符的距离(即实际数字)
            int val1 = end1 >= 0 ? num1[end1] - '0' : 0;
            int val2 = end2 >= 0 ? num2[end2] - '0' : 0;

            int ret = val1 + val2 + carry;
            carry = ret / 10;
            ret = ret % 10;
            strRet.insert(strRet.begin(), ret + '0');

            --end1;
            --end2;
        }

        if(carry == 1)
        {
            strRet.insert(strRet.begin(), '1');
        }

        return strRet;
    }
};

三.vs下string结构说明

首先看下面一个例子:

int main()
{
	std::string s1("hello world");
	std::string s3("hello worldxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");

	cout << sizeof(s1) << endl;  
	cout << sizeof(s3) << endl;

	return 0;
}

我们可以看到对应的sizeof结果都是40,他具体是怎么存储的呢?进入调试:

可以看到,string有一个buf数组,字符串的内容保存在这个buf数组里。

但是对于s3,他的数据不在buf数组里:

可以看到string存在于_ptr指向的堆空间中。

由此可见:size<16,存在数组中;size>=16,存在_ptr指向的堆空间中。
作用:提供buf数组能提高效率,不过当size较大时会导致buf这块空间没有被使用浪费,不过这样的浪费是可以原谅的,相当于用空间换时间。

四.string类模拟实现

五.深浅拷贝及写时拷贝

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值