【C++】STL简介、sting类详解、浅拷贝问题的解决

目录

STL简介:

string的使用:

为什么要学习string类:

了解string类:

 常用接口:

1、构造(constructor)

2、析构(destructor)

 3、迭代器

4、容量相关 

5、访问及遍历操作 

6、元素遍历 

7、修改操作

8、其他 

浅拷贝问题的解决:

 浅拷贝的问题:

 通过深拷贝的方式解决浅拷贝的问题:

通过写时拷贝解决浅拷贝问题:


STL简介:

什么是STL:

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

STL六大组件:

string的使用:

为什么要学习string类:

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

了解string类:

1、string是表示字符串的字符串类

2、该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。

3、string再底层实际是:basic_string模板类的别名,typedef basic_string < char, char_traits, allocator > sting;

4、不能操作多字节或者变长字符的序列

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

 常用接口:

1、构造(constructor)

①string():默认构造函数,得到一个长度为0的空串

②string(const string& str):这是一个拷贝构造函数,用已经存在的字符串str拷贝构造新的字符串。

③string(const string& str, size_t pos, size_t len = npos):使用字符串str,从pos下标开始,长度为len这一部分字符串来构造新的字符串(len是一个缺省参数,默认值为mpos,如果不给定,默认到字符串str的末尾)

④string(const char* s):使用c语言中的字符串来构造新的string对象

⑤string(const char* s, size_t n):使用c语言中的字符串s中前n个字符来构造string对象

⑥string(size_t n, char c):使用n个字符c来构造string对象。

⑦ string(Inputlterator first, Inputlterator last):使用迭代器给字符串的部分区间构造

operator=(赋值):

①string& operator = (const string& str);

②string& operator=(const char* s);

③string& operator=(char c); 

  

2、析构(destructor)

~string();

每个对象在声明周期结束时会自动调用该析构函数。

 3、迭代器

暂时理解是一个类似指针的东西

begin() && end()

rbegin() && rend()

都是左闭右开。

4、容量相关 

①size_t size() const;

②size_ length() const:size()和length的功能完全一致,都是获取字符串的有效长度。

有两个的原因:length是因为沿用c原有的习惯保留下来的,string类最初只有length, 引入STL之后,为了兼容又加入了size, 它是作为STL容器属性存在的,便于符合STL的接口规则,以便用于STL的算法。

③size_t capacity() const:获取string对象在底层维护的顺序表的容量。

void resize(size_t newsize)  |  void resize(size_t newsize, char c)

作用:resize将string对象现有的有效元素的个数更新为newsize个:

newsize <= oldsize:将原有的元素个数减小到newsize个,不会修改容量。

newsize > oldsize:

        newsize <= capacity:多出来的元素直接用ch填充,如果没有给定ch,默认使用char()填充,char()等价于0;

        newsize > capacity:1、开辟新空间。2、拷贝元素。3、释放旧空间

⑤、void reverse(size_t newcapacity = 0);

作用:扩容,只改变容量,不会修改有效元素的个数。

newcapacity <= oldcapacity:直接忽略。如果oldcapacity > 15, 会将底层的容量缩小为15,因为在string类的内部除了维护顺序表必须的三个成员变量以外,还有一个char array[BUF_SIZE],该数组的大小为16,由于字符串字符串是以‘\0’结尾,所以最后一位留给‘\0’,不会算在容量中。

newcapacity > oldcapacity:扩容。

验证:

newcapacity <= oldcapacity:

newcapacity < oldcapacity && oldcapacity > 15 && newcapacity <= 15 && newcapacity > size

 

在string的底层还有一个char array[BUF_SIZE]数组,为了提高效率,默认的情况下string中的内容存储在该数组中,只有在数组容量不够的时候,才会为其开辟新的空间。

newcapcity > oldcapacity:

 在vs中,以1.5倍进行扩容;在linux中,是以2倍进行扩容的。

⑥void clear():清空有效元素,不改变空间的大小。

5、访问及遍历操作 

①operator[]:访问pos位置的元素

char& operator[](size_t pos) | const char& operator[](size_t pos)const

②at:访问pos位置的元素。

注意:[] 和at在访问元素时效果一样;[]在访问越界时会触发assert断言,终止程序;at在访问越界,会抛出异常,用户可以对其进行捕获。

③front(C++11):获取string的第一个字符

④back(C++11):获取sring的最后一个字符

为什么这些函数都会提供const和非const两份代码?

因为const对象不能调用非const的成员函数,所以必须提供两份代码。 

如下图,调用的就是const类型的函数:

6、元素遍历 

①for循环 + [] 访问

②迭代器

③范围for

    string s1("hello");
	
	// for循环
	for (int i = 0; i < s1.size(); i++) {
		cout << s1[i];
	}
	cout << endl;

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

	//范围for
	for (auto& e : s1) {
		cout << e;
	}
	cout << endl;

7、修改操作

1、operator+=

string& operator+=(const string& str):末尾追加string

string& operator+=(const char* s):末尾追加c中的字符串

string& operator+=(const char c):末尾追加字符

2、append

①string& append(const string& str):末尾追加一个str

②string& append(const string& str, size_t subpos, size_t sublen):将str从subpos开始,长度为sblen的字串加到末尾

③ string& append(const char* s):在末尾追加一个c中的字符串。

④ string& append(const char* s, size_t n):在末尾追加字符串从0号位置开始,长度为n的子串。

⑤ string& append(size_t n, char c):在末尾追加n个char类型的c

⑥string& append(InputIterator first, InputIlterator last):将迭代器firet到last区间的内容追加到字符串的末尾

3、push_back:末尾追加一个字符

4、insert:

 ①string& insert(size_t pos, const string& str):在pos处插入字符串str

②string& insert(size_t pos, const string& str, size_t subpod, size_t sublen):在pos处插入str从subpos开始,长度为sublen的子串

③ string& insert(size_t pos,  const char* s):在pos中插入C中的字符串s

④ string& insert(size_t pos,  const char* s, size_t n):在pos中插入字符串s的前n个字符

⑤ string& insert(size_t pos,  size_t n, char c) |  string& insert(iterator p, size_t n, char c):在pos处插入n个字符c

5、erase:

①string& erase(size_t pos = 0, size_t len = npos):删除从pos开始,长度为len的子串;这两个参数均为缺省参数,默认删除整个字符串

②irerator erase(iterator p):删除p位置的元素,返回下一个元素的位置

注意:在删除最后一个位置时,返回值时非法的,无法再次被使用

③iterator erase(iterator first, iterator last):删除first到last之间的元素。 

6、swap

void swap(string& str):交换两个string类型的对象

string::swap效率比全局的swap要高,在交换字符串的时候强烈建议使用

  

8、其他 

1、c_str

const char* c_str() const:以c语言的方式返回字符串

2、find

①size_t find(const string& str, size_t pos = 0)const:在目标字符串中查找str字符串出现的位置,默认从头开始查找。找到返回首个字符在目标字符串中的位置,找不到返回npos

②size_t find(const char* s, size_t pos = 0)const:功能与上面一个一致,唯一的区别是查询的是c中的字符串

③ size_t find(const char* s, size_t pos = 0, size_t n)const:查询c中字符串的前n个字符

④size_t find(char c, size_t pos = 0)const:查询字符c的位置,默认从0下标开始查询

npos是一个静态成员常量,用来表示size_t的最大值。该值在string中表示字符串的末尾定义:static const size_t npos = -1。

3、rfind

功能与find类似,只不过是从字符串的末尾向起始位置查找  

4、find_first_of

与find类似,只不过是查找第一次出现的某一个字符或字符串

①size_t find_first_of(const string& str, size_t pos = 0)const:找第一次出现str的位置,默认从头开始找

②size_t find_first_of(const char* s, size_t pos = 0)const:找第一次出现c语言字符串s的位置,默认从头开始查找。

③size_t find_first_of(const char* s, size_t pos,size_t n)const:找第一次出现c语言字符串s从pos开始,长度为n的子串的位置

④size_t find_first_of(char c, size_t pos = 0)const:找第一次出现字符c的位置,默认从起始位置开始查找。

6、find_last_of

与rfind类似,只不过是查找第一次出现的某个字符或者字符串

7、substr

string& substr(size_t pos, size_t len = mpos) const:截取子串,pos为截取的起始位置,len表示截取的长度。

浅拷贝问题的解决:

 浅拷贝的问题:

浅拷贝也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中有资源管理,最后就会导致多个对象共享同一份资源,在调用析构函数时就会释放两次导致崩溃。

上面这个程序就会崩溃,原因:编译器调用默认的拷贝构造函数,默认的拷贝构造函数只是将s1中的成员变量的内容原封不动的拷贝至s2;test()执行完毕后,分别调用析构函数,在释放的过程中一块相同的地址被释放了两次,所以程序崩溃。

下面这个程序也会崩溃:

原因:在赋值过程中,s2中的_str直接指向s1的空间,因此s2原来的空间就会丢失,造成内存泄漏。同时,s2、s1指向同一块内存空间,在函数结束时,对象调用调用析构函数,多次释放。 

 通过深拷贝的方式解决浅拷贝的问题:

本质:让每一个对象都拥有一份独立的资源

1、传统版解决方式(申请新空间):

解决拷贝构造:

	myString(const myString& s)
	:_str(new char[strlen(s._str) + 1]){
		strcpy(_str, s._str);
	}

解决赋值运算符重载:

	myString& operator=(const myString& s) {
		if (this != &s) {
			char* temp = new char[strlen(s._str) + 1];
			strcpy(temp, s._str);
			delete[] _str;
			_str = temp;
		}
		return *this;
	}

2、现代版解决方式(巧用构造函数)

 拷贝构造:

	myString(const myString& s)
		//_str必须初始化为nullptr,否则temp声明周期结束后调用析构函数,会崩溃
		:_str(nullptr) {
		myString temp(s._str); //调用构造函数,内容为s对象的内容
		swap(_str, temp._str); //通过swap函数将两个指针指向改变,_str指针指向上面申请的空间
	}

赋值运算符:

	myString& operator=(const myString& s) {
		if (this != &s) {
			myString temp(s._str);  //与拷贝构造的思想一样。
			swap(_str, temp._str);
		}
		return *this;
	}

②巧妙使用值传递

	myString& operator=(myString s) {
		swap(_str, s._str);  //巧妙使用值传递,因为在值传递的过程中会调用构造函数创建一个临时
		return *this;       //对象,效果和上面的方法一样。
	}

通过写时拷贝解决浅拷贝问题:

 写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。

引用计数:用来记录资源使用者的个数。在构造时,将资源计数给成1,每增加一个对象使用该资源,就给计数增加1;当某个对象被销毁时,先给该计数减一,然后再检查是否需要释放资源,如果计数为1,说明该对象是资源的最后一个使用者,将该资源释放,否则就不释放。

写时拷贝是在上面的基础上解决修改内容的问题的,假如s1和s2共用同一块内存空间,此时计数器为2,现在想要将s2的内容修改,具体步骤如下:

1、为s2对象开辟新空间

2、将原来的空间的计数器值减减

3、将s1的内容拷贝至s2新开辟的空间中,并将s2中计数器设置为1

4、s2再新空间中进行修改操作。 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值