c++(String)

STL六大组件

仿函数、算法(查找、归并、分类)、迭代器(iterator)、空间配置器(allocator和内存池先(只申请空间但是并没有初始化,避免频繁从堆中申请内存影响效率))、容器(string、vector)、配接器

string

c语言的字符串不能很好的按需进行修改,比如因为字符串一般存储在字符数组当中,而字符数组的大小是定死的,如果要对字符数组里面的内容进行调整可能会受限,所以有了string这个类型,但是其底层管理的依旧还是字符数组,支持增删查改。

stirng也是类模板typedef过来的:typedef basic_string<char> string

basic_string<T>:这个类模板发展了许多的类,来应对不同的需要。如下:typedef basic_string<wchar_t> wstring 

typedef basic_string<char32_t> u32string 

typedef basic_string<char16_t> u16string

一个汉字可以看成ASCII码表上的一个字符,但只用一个字节表示汉字,由于状态只有256种太少不适用->unicode(万国):UTF-8(一个字节为单位,可以用1个字节或者2个、3个、4个字节来编码,对不同范围的字符是同不同长度的编码。比如常见的汉字采用2个字节,生僻的汉字采用4个字节)、UTF-16、UTF-32(更加的统一但是也更加的浪费)。

int main()
{
	char str1[] = "apple";
	char str2[] = "高手";
	cout << sizeof(str1) << endl;//6
	cout << sizeof(str2) << endl;//5,看下面的监视我么们可以发现每一个汉字需要两个字符表示,而每个字符都是负数,这是因为ASCII码用了正数的部分,导致如果想要兼容ASCII的话那么汉字编码的部分只能使用负数部分,如果和ascii码混用的话,会有问题,我们拿到两个字符,到底是对应着一个汉字,还是两个ASCII符号。现在我们可以拿到一个字符,看第一个bit位的数值,如果是0对应着ASCII码,如果是1就和下面一个字符联合对应一个汉字。
	return 0;
}

int main()
{
	char str2[] = "高手";
	str2[3]--;
	cout << str2 << endl;
	str2[3]--;
	cout << str2 << endl;
	str2[3]--;
	cout << str2 << endl;
	str2[3]--;
	cout << str2 << endl;
	str2[3]--;
	cout << str2 << endl;
	str2[3]--;
	cout << str2 << endl;
	return 0;
}

由上可知,gbk编码是将同音字编码到一块的。

 string使用

int main()
{
	string s1;//无参构造string()
	string s2("hello world");//string (const string& str)
	string s3 = "hello world";//单参数构造函数可以直接赋值,因为支持隐式类型转换:构造+拷贝=>构造
	string s4(s3, 6, 3);//string (const string& str, size_t pos, size_t len = npos)从下标pos位置开始拷贝npos长度
	cout << s4 << endl;
	string s5(s3, 6, 13);//npos超过s5的末尾时,停止拷贝
	cout << s5 << endl;
	string s6(s3, 6);//static const size_t npos = -1;相当于将32bit的1赋值给一个无符号整形,解读成42亿多
	cout << s6 << endl;
	string s7("hello world", 5); //string(const char* s, size_t n)拷贝字符串s的前n个位置的值
	cout << s7 << endl;
	string s8(10, '*');//string (size_t n, char c)字符串填充n个字符c
	cout << s8 << endl;
	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] << " ";
	}
	cout << endl;
	return 0;
}

int main()
{
	string s1("hello world");
	cout << s1.size() << endl;
	cout << s1.length() << endl;//length和size的结果是一样的,功能一样,size()之所以出现是为了和stl库的其它结构保持一致
	cout << s1.max_size() << endl;//意义不大
	cout << s1.capacity() << endl;//capacity是不包含'/0'的,但实际上是开了'\0'的空间。也就是如果结果是15,实际上开了16个字节的空间
	return 0;
}
int main()
{
	string s1("hello");
	s1.push_back(' ');
	s1.push_back('!');//插入一个字符
	cout << s1 << endl;
	s1.append("world");//插入一个字符串
	cout << s1 << endl;
	s1 += ' ';
	s1 += '!';
	s1 += "world";//其实+=的底层实现还是push_back和append
	cout << s1 << endl;
}
class string
{
private:
	char* _ptr;
	char _buf[16];
	size_t _size;
	size_t _capacity;
};//其实string结构和这个比较类似,当需要存储的字符串空间比较小的时候直接存在_buf数组当中,相当于浪费了一个ptr指针,但是ptr指针指向的堆区空间此时是不需要开辟的
//当需要存储的字符串的大小大于16的时候就不存储在_buf当中了,直接存储在ptr所指向的堆区空间上了,这个时候浪费了一个16个字节大小的字符数组,这也是x86环境下sizeof(string)的大小是28=12+char _buf[16]的原因
int main()
{
	// 观察扩容情况  -- 1.5倍扩容
	string s;
	cout << sizeof(s) << endl;//28
	cout << "making s grow:\n";
	size_t sz = s.capacity();
	cout << "capacity changed: " << sz << '\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;

 由下图可知第一次的capacity是15,但是根据string的类结构我们可以知道这并不是额外开辟的,而是本身结构自带的,而且也应证了capacity是不计算'\0'的。所以第一次正经在堆区开辟的空间的大小其实是32。但是不同的编译器扩容的原理是不一样的 。

reserve(100)只会改变capacity但是不会改变size,也就是将capacity扩容到100。

resize(100)是size和capacity都会编程100,空间填入缺省值'\0'。也可以s1.resize(100,'x'),指定用字符'x'来填充capacity。可以理解为扩容+初始化。也可以用来缩小size,s1.resize(5)

就是只保留5个,而capacity是不会动的。这是因为缩容是不支持原地缩容,如果想要原地缩容就必须支持释放部分空间,但是这在底层是不支持的,会增加内存管理的难度,缩容是开辟一块新的空间再按需拷贝部分数据,这样的实现代价是非常大的。

 迭代器:

迭代器对于string这个容器而言和[]下标或者范围for比较,用起来并不是那么的方便。但是string必须要有迭代器,目的是为了和其他的容器在使用上保持异种一致。就好像string有了length()函数之后为什么还要有capacity()函数。[]下标在string容器比较好用,但是如果更换了容器就不一定了。

int main()
{
	string s1("hello world");
	string::iterator it = s1.begin();
	while (it != s1.end())//采用迭代器进行遍历。end是最后字符串最后一个有效字符的下一个位置,类似于c语言字符串的'\0'
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	for (auto ch : s1)//采用范围for来进行遍历,但是auto的底层实现和迭代器是一样的
	{
		cout << ch << " ";
	}
	cout << endl;

	return 0;
}
///
int main()
{
	string s1("hello world");
	string::reverse_iterator rit = s1.rbegin();//反向迭代器,从后向前,而且反向迭代器的类型名称又发生了变化
	while (rit != s1.rend())//rbegin是最后一个有效字符的位置,也就是d的位置。而rend的位置是第一个字符的前一个位置
	{
		cout << *rit << " ";
		++rit;//注意是++
	}
}
///
void Func(const string& s)
{
	// 遍历和读容器的数据,不能写,只能读取
	string::const_iterator it = s.begin();//此时迭代器的类型变成了const类型的的string的迭代器:const_iterator begin() const;
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	auto rit = s.rbegin();
	while (rit != s.rend())//时迭代器的类型变成了const类型的的string的反向迭代器:string::const_reverse_iterator rit = s.rbegin();
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
}
//迭代器的类型在这里共有4种。const做为关键字修饰定义出来的对象和变量是不可修改的

at()和[]的区别

int main()
{
	string s1("hello world");
	try
	{
		s1.at(100);//越界了是抛异常,处理方式相对比较合理。
        si[100];//越界之后是采用的assert断言,所以程序会直接中断,比较激进
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

int main()
{
	//insert/erase不推荐经常使用,能少用就少用。因为他们可能都存在要挪动数据,效率低下。删除的话如果全部删除还好说,但是如果只是删除中间的部分的话代价就会非常的大
	string s1("world");
	s1.insert(0, "hello");
	cout << s1 << endl;
	s1.insert(5, 1, ' ');//string& insert (size_t pos, size_t n, char c)从下标pos开始插入n个字符c
	cout << s1 << endl;
	s1.insert(5, " ");//string& insert(size_t pos, const char* s)从下标pos开始插入字符串s
	cout << s1 << endl;
	s1.insert(s1.begin()+5, ' ');//从begin()+5的位置开始插入
	cout << s1 << endl;
	string s2("hello world");
	s2.erase(5, 1);//string& erase (size_t pos = 0, size_t len = npos);
	cout << s2 << endl;
	s2.erase(s2.begin() + 5);//iterator erase (iterator p)
	cout << s2 << endl;
	string s3("hello world");
	s3.erase(5, 30);
	cout << s3 << endl;
    string s4("hello world");
	s4.erase(5);//string& erase (size_t pos = 0, size_t len = npos)这里采用缺省值直接全删除了
	cout << s4 << endl;
	return 0;
}

 replace函数并不建议,因为会涉及到空间不够扩容和挪动数据,效率不够

int main()
{
	string s1("hello world");
	s1.replace(5, 1, "%%d");
	cout << s1 << endl;
	string s2("hello world i love you");
	size_t num = 0;
	for (auto ch : s2)//提前统计空格的数量
	{
		if (ch == ' ')
			++num;
	}
	s2.reserve(s2.size() + 2 * num);//提前开空间,避免repalce时扩容
	size_t pos = s2.find(' ');
	while (pos != string::npos)//string::npos是静态局部常量static const size_t,
	{
		s2.replace(pos, 1, "%20");
		pos = s2.find(' ', pos + 3);//减少重复搜索,直接从上次搜索的地方开始
	}
	cout << s2 << endl;
	// 下面是采用空间换时间的方法
	string s3("hello world i love you");
	string newStr;
	num = 0;
	for (auto ch : s3)
	{
		if (ch == ' ')
			++num;
	}
	newStr.reserve(s3.size() + 2 * num);
	for (auto ch : s3)
	{
		if (ch != ' ')
			newStr += ch;
		else
			newStr += "%20";
	}
	cout << newStr << endl;
	return 0;
}

 String::swap(void swap (string& str))和swap的区别?

string::swap是根据string类定义的成员函数,所以更有针对性,可以采用更高效的方法进行交换,直接交换两个string对象中_ptr的指向就可以了。但是标准库里面的swap泛函数效率就比较低了,是 通过新建一个临时变量进行swap的。

int main()
{
	string s1("hello world");
	cout << s1 << endl;//const char* c_str() const:调用的是string的<<运算符重载函数
	cout << s1.c_str() << endl;//调用的是iostream库里面的<<运算符重载
	cout << (void*)s1.c_str() << endl;//这里打印的是地址
	cout << s1 << endl;
	cout << s1.c_str() << endl;
	s1 += '\0';
	s1 += '\0';
	s1 += "xxxxx";
	cout << s1 << endl;//是根据s1的size来进行打印的
	cout << s1.c_str() << endl;//打印字符串,遇到'\0'就截止了
	//c_str()函数的作用:为c语言提供一个函数接口,更好的兼容。虽然在有些时候功能有些重合
	string filename("test.cpp");//此时fopen是没有办法打开string类的
	FILE* fout = fopen(filename.c_str(), "r");
	if (fout == nullptr)
		perror("fopen fail");
	char ch = fgetc(fout);
	while (ch != EOF)
	{
		cout << ch;
		ch = fgetc(fout);//不写的话会造成死循环,也就是一直读取同一个字符
	}
	fclose(fout);
	return 0;
}

int main()
{
	string file("string.cpp.tar.zip");
	size_t pos = file.rfind('.');//从后向前找
	if (pos != string::npos)
	{
		string suffix = file.substr(pos);//string substr (size_t pos = 0, size_t len = npos) const:从pos位置开始读取len长度的字符
		cout << suffix << endl;
	}
	string url("http://www.cplusplus.com/reference/string/string/find/");
	cout << url << endl;
	size_t start = url.find("://");//find找字符串的位置
	if (start == string::npos)
	{
		cout << "invalid url" << endl;
	}
	start += 3;
	size_t finish = url.find('/', start);
	string address = url.substr(start, finish - start);
	cout << address << endl;
	return 0;
}

int main()
{
	std::string str("Please, replace the vowels in this sentence by asterisks.");
	std::size_t found = str.find_first_of("abcdv");//从头找str中"abcdv"中的任意一个字符就返回下标。find_last_not_of就是从str的最末尾开始向前找
	while (found != std::string::npos)
	{
		str[found] = '*';
		found = str.find_first_of("abcdv", found + 1);//优化防止从头开始找
	}
	std::cout << str << '\n';
	string s1("hello world");
	string s2("hello world");
	s1 == s2;
	s1 == "hello world";
	"hello world" == s1;
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值