提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、pandas是什么?
- 二、使用步骤
- 1.引入库
- 2.读入数据
- 总结
前言
提示:这里可以添加本文要记录的大概内容:
例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。
提示:以下是本篇文章正文内容,下面案例可供参考
一、【STL简介]
网上有句话说:“不懂STL,不要说你会C++”。STL是C++中的优秀作品,有了它的陪伴,许多底层的数据结构
以及算法都不需要自己重新造轮子,站在前人的肩膀上,健步如飞的快速开发。1.1【 什么是STL】
STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。1.2【 STL的版本】
原始版本
Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意
运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使
用。 HP 版本--所有STL实现版本的始祖。
P. J. 版本
由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低,
符号命名比较怪异。
RW版本
由Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般。
SGI版本
由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好,
可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。我们后面学习STL要阅读部分源代码,
主要参考的就是这个版本。1.3【STL的六大组件】
1.4【STL的缺陷】
1. STL库的更新太慢了。这个得严重吐槽,上一版靠谱是C++98,中间的C++03基本一些修订。C++11出来已经相隔了13年,STL才进一步更新。
2. STL现在都没有支持线程安全。并发环境下需要我们自己加锁。且锁的粒度是比较大的。
3. STL极度的追求效率,导致内部比较复杂。比如类型萃取,迭代器萃取。
4. STL的使用会有代码膨胀的问题,比如使用vector/vector/vector这样会生成多份代码,当然这是模板语法本身导致的。
二、【string】
C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
这里附一份优质博客以供参考:
C++面试中string类的一种正确写法 | 酷 壳 - CoolShell
2.1 【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;2.2 【string类的常用接口说明】
1.【 string类对象的常见构造】
参考资料:
http://www.cplusplus.com/reference/string/string/string/
using namespace std; void TestString1() { string s1; string s2("hello world"); string s3(s2); cout << &s1 << endl; cout << s1 << endl; cout << &s2 << endl; cout << s2 << endl; cout << &s3 << endl; cout << s3 << endl; } int main() { TestString1(); return 0; }
2. 【string类对象的容量操作】
参考资料:
size:http://www.cplusplus.com/reference/string/string/size/
length:http://www.cplusplus.com/reference/string/string/length/
capacity:http://www.cplusplus.com/reference/string/string/capacity/
empty:http://www.cplusplus.com/reference/string/string/empty/
clear:http://www.cplusplus.com/reference/string/string/clear/
reserve:http://www.cplusplus.com/reference/string/string/reserve/
resize:http://www.cplusplus.com/reference/string/string/resize/
string ::size和string::length都是同义词并返回相同的值
void TestString2() { string s1("hello world"); cout << s1.max_size() << endl; cout << s1.capacity() << endl; cout << s1.size() << endl; cout << s1.length() << endl; cout << s1 << endl; s1.reserve(100); cout << s1.capacity() << endl; cout << s1.size() << endl; cout << s1.length() << endl; s1.resize(20,'!'); cout << s1.capacity() << endl; cout << s1.size() << endl; cout << s1.length() << endl; cout << s1 << endl; cout << s1.empty() << endl; s1.clear(); cout << s1 << endl; cout << s1.empty() << endl; }
这里我们可以看到初始容量是15,那么达到它的容量了以后会怎么办呢?会进行扩容吗?
答案是会的,会自动扩容,初始容量为15,第一次扩容为31,之后按接近1.5倍的速率增长,
// 利用reserve提高插入数据的效率,避免增容带来的开销 //==================================================================================== void TestPushBack() { string s; size_t sz = s.capacity(); 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'; } } }
我们如果知道容量,那么就可以在需要扩容之前用reverse来控制容量,因此我们是可以是利用reverse来提高插入效率的。
void Teststring2() { // 注意:string类对象支持直接用cin和cout进行输入和输出 string s("hello, bit!!!"); cout << s.size() << endl; cout << s.length() << endl; cout << s.capacity() << endl; cout << s << endl; // 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小 s.clear(); cout << s.size() << endl; cout << s.capacity() << endl; // 将s中有效字符个数增加到10个,多出位置用'a'进行填充 // “aaaaaaaaaa” s.resize(20, 'a'); cout << s << endl; cout << s.size() << endl; cout << s.capacity() << endl; // 将s中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充 // "aaaaaaaaaa\0\0\0\0\0" // 注意此时s中有效字符个数已经增加到15个 s.resize(15); cout << s.size() << endl; cout << s.capacity() << endl; cout << s << endl; // 将s中有效字符个数缩小到5个 s.resize(5); cout << s.size() << endl; cout << s.capacity() << endl; cout << s << endl; }
【注意】:
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不会改变容量大小。3.【 string类对象的访问及遍历操作】
参考资料:
operator [ ] :http://www.cplusplus.com/reference/string/string/operator%5B%5D/
迭代器iteraor:
begin: http://www.cplusplus.com/reference/string/string/begin/
end:http://www.cplusplus.com/reference/string/string/end/
rbegin:http://www.cplusplus.com/reference/string/string/rbegin/
rend:http://www.cplusplus.com/reference/string/string/rend/
这里可以看一下具体使用方法:
void TestString3() { string s3("hello world"); const string s4("aaaaa"); cout << s3[0] << endl;//可以直接使用[]打印 cout << s4[0] << endl;//可以直接使用[]打印 s3[0] = 'H';//可直接修改 //s4[0] = 'A';//此时s4类似常量字符串,不能修改。 // //1.For循环+operator[] ; for (int i = 0; i < s3.size(); i++) { cout << s3[i]; } cout << endl; //2.迭代器进行遍历 string::iterator it = s3.begin(); /// <summary> /// 这里也可以替换为auto it=s3.begin /// </summary> while (it != s3.end()) { cout << *it; it++; } cout << endl; string::reverse_iterator rit = s3.rbegin(); while (rit != s3.rend()) { cout << *rit; rit++; } cout << endl; //3.范围for遍历 for (auto ch : s3) { cout << ch ; } } int main() { //TestString1(); //TestString2(); //TestPushBack(); TestString3(); return 0; }
4. 【string类对象的修改操作】
push_back:http://www.cplusplus.com/reference/string/string/push_back/
append:http://www.cplusplus.com/reference/string/string/append/
operator+=:http://www.cplusplus.com/reference/string/string/operator+=/c_str:http://www.cplusplus.com/reference/string/string/c_str/
find+npos:
http://www.cplusplus.com/reference/string/string/find/
http://www.cplusplus.com/reference/string/string/npos/
rfind:http://www.cplusplus.com/reference/string/string/rfind/
subster:http://www.cplusplus.com/reference/string/string/substr/
使用实例:
void TestString1() { string s1("hello world"); cout << s1 << endl; s1.push_back('a'); cout << s1 << endl; s1.append("b"); cout << s1 << endl; s1 += 'c'; s1 += "abc"; cout << s1 << endl; cout << s1.c_str() << endl; }
rfind默认从后向前寻找,rfind找的是字符串中某个字符最后出现的位置,而find找的是某个字符首次出现的位置,而当字符串中不包含某个字符时,就会返回npos
先面来看一个示例:
void TestString2() { string file("Test.cpp.pp"); size_t pos = file.rfind('.'); cout << pos << endl; string suffix(file.substr(pos, file.size() - pos)); cout << suffix << endl; string url("http://www.cplusplus.com/reference/string/string/find/"); size_t start = url.find("/"); if (start == string::npos) { cout << "invalid url" << endl; return; } cout << start << endl; }
下面是整体的使用用例:
void TestString3() { // npos是string里面的一个静态成员变量 // static const size_t npos = -1; // 取出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; } start += 3; size_t finish = url.find('/', start); string address = url.substr(start, finish - start); cout << address << endl; // 删除url的协议前缀 size_t pos = url.find("://"); url.erase(0, pos + 3); cout << url << endl; }
【注意】:
1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。5.【string类非成员函数】
参考资料:
operator++:http://www.cplusplus.com/reference/string/string/operator+/
operator>>:http://www.cplusplus.com/reference/string/string/operator%3E%3E/
operator<<:http://www.cplusplus.com/reference/string/string/operator%3C%3C/
getline:http://www.cplusplus.com/reference/string/string/getline/
relational operators:http://www.cplusplus.com/reference/string/string/operators/
string类中还有一些其他的操作,这里不一一列举,大家在需要用到时不明白了查文档即可。
6.【vs和g++下string结构的说明】
【注意】:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。
【vs下string的结构】:
string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字符串的存储空间:
当字符串长度小于16时,使用内部固定的字符数组来存放当字符串长度大于等于16时,从堆上开辟空间这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。
其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量
最后:还有一个指针做一些其他事情。
故总共占16+4+4+4=28个字节。【g++下string的结构】
g++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:
空间总大小字符串有效长度引用计数指向堆空间的指针,用来存储字符串。但是string类中仍然有一些缺陷:
STL 的string类怎么啦?_stl里为什么没讲string-CSDN博客
2.3【string的模拟实现】
我们为了跟更加深入了解string除了了解其使用方法以外,最好能够模拟实现string:
下面是代码部分:【String.h部分】:
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <assert.h> #include <string> #include <stdbool.h> #include <iostream> using namespace std; namespace str { class String { public: void swap(str::String& s); const static size_t npos = -1; typedef char* iterator; typedef const char* const_iterator; String(const char* str = ""); ~String(); String(const String& s); String& operator=(const str::String& s); size_t capacity() const; size_t size() const; const char* c_str() const; iterator begin(); iterator end(); const_iterator begin() const; const_iterator end() const; const char& operator[](size_t pos)const; void push_back(char ch); void append(const char* str); void reserve(size_t n); String& operator+=(char ch); String& operator+=(const char* str); void Insert(size_t pos, const char* str); void Insert(size_t pos, char ch); void erase(size_t pos, size_t len = npos); bool operator<(const String& s)const; bool operator==(const String& s)const; bool operator>(const String& s)const; bool operator<=(const String& s)const; bool operator!=(const String& s)const; bool operator>=(const String& s)const; void clear(); void resize(size_t n, char ch = '\0'); size_t find(char ch, size_t pos = 0); size_t find(const char* sub, size_t pos = 0); String substr(size_t pos, size_t len = npos); private: char* _str; size_t _size; size_t _capacity; }; } ostream& operator<<(ostream& out, const str::String& s); istream& operator>>(istream& in, str::String& s);
【String.cpp部分】:
#define _CRT_SECURE_NO_WARNINGS #include "String.h" //void str::String::swap(str::String& s) //{ // std::swap(_str, s._str);//加上std的原因是防止其与库里的swap冲突,因此指定这里的swap是std库 // std::swap(_size, s._size);//中的swap // std::swap(_capacity, s._capacity); //} // s2(s1); //str::String::String(const str::String& s) // :_str(nullptr) // , _size(0) // , _capacity(0) //{ // String tmp(s._str);//拷贝构造调用构造,构建出来的tmp再与this进行交换。 // swap(tmp); //} // s2=s1; //str::String& str::String::operator=(const str::String& s) //{ // if (this != &s) // { // String tmp(s); // //this->swap(tmp); // swap(tmp); // } // // return *this; //} // s2 = s3 //str::String& str::String::operator=(str::String tmp)//s3拷贝构造tmp //{ // swap(tmp);//交换tmp与s2 // // return *this; //} str::String::String(const char* str) :_size(strlen(str)) ,_capacity(_size) { _str = new char[_capacity + 1]; strcpy(_str, str); } str::String::String(const str::String& s) { _str = new char[s._capacity + 1]; strcpy(_str, s._str); _size = s._size; _capacity = s._capacity; } str::String& str::String::operator=(const str::String& s) { if (this != &s) { char* tmp = new char[s._capacity + 1]; strcpy(tmp, s._str); delete[] _str; _str = tmp; _size = s._size; _capacity = s._capacity; } return *this; } str::String::~String() { delete[] _str; _str = nullptr; _size = _capacity = 0; } size_t str::String::capacity() const { return _capacity; } size_t str::String::size() const { return _size; } const char* str::String:: c_str() const { return _str; } str::String::iterator str::String:: begin() { return _str; } str::String::iterator str::String::end() { return _str + _size; } str::String::const_iterator str::String::begin() const { return _str; } str::String::const_iterator str::String::end() const { return _str + _size; } const char& str::String::operator[](size_t pos)const { assert(pos < _size); return _str[pos]; } void str::String::reserve(size_t n)//按需调整空间 { if (_capacity < n)//当前容量小于指定容量就进行扩容,将数组的内容扩大。 { char* tmp = new char[n + 1];//多开的1个空间是为了存储‘\0’ strcpy(tmp, _str);//之后将原字符串中的内容拷贝到新空间 delete[] _str;//并释放原空间 _str = tmp;//将原数组指向新开辟的数组空间 _capacity = n;//并更新容量 } } void str::String::push_back(char ch) { if (_size == _capacity)在进行尾插之前,要先判断是否需要扩容 { reserve((_capacity == 0) ? 4 : _capacity * 2); } _str[_size] = ch; _size++; _str[_size] = '\0'; } void str::String::append(const char* str) { size_t len = strlen(str); if (_size + len >= _capacity) { reserve(_size + len); } strcpy(_str + _size, str); _size += len; } str::String& str::String::operator+=(char ch) { push_back(ch); return *this; } str::String& str::String::operator+=(const char* str) { append(str); return *this; } void str::String::Insert(size_t pos, char ch) { if (_size == _capacity)//判断是否需要扩容 { reserve((_capacity == 0) ? 4 : 2 * _capacity); } size_t end = _size + 1;//这里定义end位于‘\0’之后的一个位置 while (end > pos)//将pos之后的数据全都向后移 { _str[end] = _str[end-1]; end--; } _str[pos] = ch;//将pos位置处放上需要插入的字符 _size++;//最后将_size++即可 /*int end = _size; while (end >=(int) pos)//这里转换成int的原因是,pos为无符号整数,直接进行比较大小会导致end {//进行类型提升将end也当作无符号整数,当end--为-1时,由于end变为无符整形,-1=4294967295 _str[end+1] = _str[end];//会大于0 end--; } _str[pos] = ch; _size++;*/ } bool str::String:: operator<(const String& s)const { return strcmp(_str, s._str) < 0; } bool str::String:: operator==(const String& s)const { return strcmp(_str, s._str)== 0; } bool str::String:: operator>(const String& s)const { return strcmp(_str, s._str) > 0; } bool str::String:: operator<=(const String& s) const { return *this < s || *this == s; } bool str::String::operator>=(const String& s) const { return !(*this < s); } bool str::String::operator!=(const String& s) const { return !(*this == s); } void str::String::clear() { _str[0] = '\0'; _size = 0; } ostream& operator<<(ostream& out, const str::String& s) { /*for (size_t i = 0; i < s.size(); i++) { out << s[i]; } return out;*/ for (auto ch : s)//范围for遍历字符串 { out << ch; } return out; /*str::String::const_iterator it = s.begin(); while (it != s.end()) { out << *it; it++; } return out;*/ } istream& operator>>(istream& in, str::String& s) { s.clear();//在流提取之前要先将字符串里之前的数据清空,不然提取到的新字符串就会尾插在旧数据 char ch;//之后 ch = in.get();//由于cin类似于scanf遇到空格就不会再进行读取了因此这里需要用到istream里的get char buffer[129];//这里定义一个数组,用来存放读取到的字符,由于是在函数中创建的,不会占用 size_t i = 0;//字符串的空间,跟随函数的生命周期 while (ch != ' ' && ch != '\n') { buffer[i++] = ch; if (i == 128)//如果读取的字符太长数组都存满了,还没有读取结束 {//就将获取到的字符先放入到字符串中,之后重新进行读取 buffer[i] = '\0'; s += buffer; i = 0; } ch = in.get(); } if (i != 0)//如果字符不长,则可以直接将其放到字符串中 { buffer[i] = '\0'; s += buffer; } return in; } void str::String::Insert(size_t pos, const char* str) { assert(pos < _size); size_t len = strlen(str); if (_size + len > _capacity) { reserve(len + _size); } //int end = _size + len; //while (end > pos) //{ // _str[end] = _str[end - len]; // end--; //} int end = _size; while (end >= (int)pos) { _str[end + len] = _str[end]; end --; } strncpy(_str + pos, str, len); _size += len; } void str::String::erase(size_t pos, size_t len ) { assert(pos < _size); if (len == npos || len + pos > _size) { _str[pos] = '\0'; _size = pos; } else { size_t begin = pos + len; while (begin <= _size) { _str[begin - len] = _str[begin]; begin++; } _size -= len; } } void str::String::resize(size_t n, char ch) { if (n <= _size) { _str[n] = '\0'; _size = n; } else { reserve(n); while (_size < n) { _str[_size] = ch; ++_size; } _str[_size] = '\0'; } } size_t str::String::find(char ch, size_t pos) { assert(pos < _size); for (size_t i = pos; i < _size; i++) { if (_str[i] == ch) { return i; } } return npos; } size_t str::String::find(const char* sub, size_t pos) { assert(pos < _size); const char* p = strstr(_str + pos, sub); if (p) { return p - _str; } else { return npos; } } str::String str::String::substr(size_t pos, size_t len ) { assert(pos < _size); str::String s; size_t end = pos + len; if (len == npos || len + pos > _size) { len = _size - pos; end = _size; } s.reserve(len); for (size_t i = pos; i < end; i++) { s += _str[i]; } return s; }
【Test.cpp部分】:
#define _CRT_SECURE_NO_WARNINGS #include "String.h" //void test_string1() //{ // str::String s1("hello world"); // cout << s1.c_str() << endl; // // str::String s2; // cout << s2.c_str() << endl; // // for (size_t i = 0; i < s1.size(); i++) // { // cout << s1[i] << " "; // } // cout << endl; // // str::String::iterator it = s1.begin(); // while (it != s1.end()) // { // (*it)++; // cout << *it << " "; // ++it; // } // cout << endl; // // for (auto& ch : s1) // { // ch++; // cout << ch << " "; // } // cout << endl; // // cout << s1.c_str() << endl; //} // void test_string2() { str::String s1("hello world"); cout << s1.c_str() << endl; s1.push_back(' '); s1.append("hello bit hello bit"); cout << s1.c_str() << endl; s1 += '#'; s1 += "*********************"; cout << s1.c_str() << endl; str::String s2; s2 += '#'; s2 += "*********************"; cout << s2.c_str() << endl; } void test_string3() { str::String s1("hello world"); cout << s1.c_str() << endl; s1.Insert(5, '%'); cout << s1.c_str() << endl; s1.Insert(s1.size(), '%'); cout << s1.c_str() << endl; s1.Insert(0, '%'); cout << s1.c_str() << endl; } void test_string4() { str::String s1("hello world"); str::String s2("hello world"); cout << (s1 >= s2) << endl; //s1[0] = 'z'; cout << (s1 >= s2) << endl; cout << s1 << endl; //cin >> s1; cout << s1 << endl; } void Test_string1() { str::String s1("hello world"); str::String s2; //cout << s1 << endl; //cout << s2 << endl; for (size_t i = 0; i < s1.size(); i++) { cout << s1[i] << " "; } cout << endl; str::String::iterator it = s1.begin(); while (it != s1.end()) { (*it)++; cout << *it << " "; ++it; } cout << endl; for (auto& ch : s1) { ch++; cout << ch << " "; } cout << endl; cout << s1.c_str() << endl; } void test_string5() { str::String s1("hello world"); s1.Insert(5, "abc"); cout << s1 << endl; s1.Insert(0, "xxx"); cout << s1 << endl; s1.erase(0, 3); cout << s1 << endl; s1.erase(5, 100); cout << s1 << endl; s1.erase(2); cout << s1 << endl; } void test_string6() { str::String s1("hello world"); cout << s1 << endl; s1.resize(5); cout << s1 << endl; s1.resize(25, 'x'); cout << s1 << endl; } void test_string7() { str::String s1("test.cpp.tar.zip"); //size_t i = s1.find('.'); //size_t i = s1.rfind('.'); //string s2 = s1.substr(i); //cout << s2 << endl; str::String s3("https://legacy.cplusplus.com/reference/string/string/rfind/"); //string s3("ftp://www.baidu.com/?tn=65081411_1_oem_dg"); // 协议 // 域名 // 资源名 str::String sub1, sub2, sub3; size_t i1 = s3.find(':'); if (i1 != str::String::npos) sub1 = s3.substr(0, i1); else cout << "没有找到i1" << endl; size_t i2 = s3.find('/', i1 + 3); if (i2 != str::String::npos) sub2 = s3.substr(i1 + 3, i2 - (i1 + 3)); else cout << "没有找到i2" << endl; sub3 = s3.substr(i2 + 1); cout << sub1 << endl; cout << sub2 << endl; cout << sub3 << endl; } void test_string8() { str::String s1("hello world"); str::String s2 = s1; cout << s1 << endl; cout << s2 << endl; str::String s3("xxxxxxxxxxxxxxxxxxx"); s2 = s3; cout << s2 << endl; cout << s3 << endl; } void test_string9() { str::String s1("hello world"); cin >> s1; cout << s1 << endl; cout << s1.size() << endl; cout << s1.capacity() << endl; } int main() { //Test_string1(); //test_string1(); //test_string2(); //test_string3(); //test_string4(); //test_string6(); //test_string7(); //test_string8(); //test_string9(); return 0; }
下面是老生常谈的问题了,也就是在调用拷贝构造时会出现的两个指针指向一块空间的情况,这样再调用析构函数时,就会导致同一块空间连续析构两次。
说明:上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。
这里可以通过增加一个引用计数来解决,也就是说增加一个变量用来统计某块空间被多少个变量所引用。
那么什么是写时拷贝呢?
写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。参考资料:
C++ STL string的Copy-On-Write技术 | 酷 壳 - CoolShell
C++的std::string的“读时也拷贝”技术! | 酷 壳 - CoolShell
三、【vector】
3.1 【vector的介绍】
参考资料:
http://www.cplusplus.com/reference/vector/vector/
1. vector是表示可变大小数组的序列容器。
2. 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
6. 与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好。
3.2【vector的使用】1.【vector的构造】
代码:
void TestConstructure() { vector<int> a;//无参构造 vector<int> arr(4, 100);//创建一个大小为4,数组元素为100的数组。 vector<int> brr(arr.begin(), arr.end());//以arr的始迭代器和arr的末迭代器构造 vector<int> crr(brr);//用brr拷贝构造crr }
2.【vector中的迭代器】
void TestIterator() { vector<int> arr(10, 0); vector<int>::iterator it = arr.begin(); int i = 1; while (it != arr.end()) { *it = i; cout << *it << " "; i++; it++; } cout << endl; int myints[] = { 16,2,77,29 }; vector<int> fifth(myints, myints + sizeof(myints) / sizeof(int));//迭代器构造 cout << "The contents of fifth are:"<<endl; for (vector<int>::iterator it = fifth.begin(); it != fifth.end(); ++it) cout << ' ' << *it; cout << '\n'; }
3.【容量操作】
void TestCapacity() { int a[] = { 1,2,3,4,4,55,6,7,8,9,100 }; int len = sizeof(a) / sizeof(int); vector<int> arr(a, a+len); cout << " arr的大小为:" << arr.size()<<endl; for (int i=0;i<arr.size();i++) { cout << arr[i] << " "; } cout << endl; // 将有效元素个数设置为n个,如果时增多时,增多的元素使用data进行填充 // 注意:resize在增多元素个数时可能会扩容 arr.resize(2 * len, 0); cout << " arr的大小为:" << arr.size()<<endl; for (auto ch : arr) { cout << ch << " "; } cout << endl; arr.resize(100); cout << " arr的大小为:" << arr.size()<<endl; // 往vecotr中插入元素时,如果大概已经知道要存放多少个元素 // 可以通过reserve方法提前将容量设置好,避免边插入边扩容效率低 vector<int> v; size_t sz = v.capacity(); cout <<"容量为:" << v.capacity() << endl; v.reserve(100);// 提前将容量设置好,可以避免一遍插入一遍扩容 cout << "容量为:" << v.capacity() << endl; cout << "making bar grow:\n"; for (int i = 0; i < 100; ++i) { v.push_back(i); if (sz != v.capacity())//相等就代表扩容,打印新的容量 { sz = v.capacity(); cout << "capacity changed: " << sz << '\n'; } } } //v.reserve(100); // size = 0 capacity 100 //v.resize(100); // size = 100 capacity 100
需要注意的是size和capacity的变化:
void TestChange() { vector<int> v; cout << "v的大小为:" << v.size() << endl; // set some initial content: for (int i = 1; i < 10; i++) v.push_back(i); v.resize(5); cout << "v的大小为:" << v.size() << endl; v.resize(8, 100); cout << "v的大小为:" << v.size() << endl; v.resize(12); cout << "v的大小为:" << v.size() << endl; cout << "v contains:"; for (size_t i = 0; i < v.size(); i++) cout << ' ' << v[i]; cout << '\n'; size_t sz; vector<int> V; sz = V.capacity(); cout << "making V grow:\n"; for (int i = 0; i < 100; ++i) { V.push_back(i); if (sz != V.capacity()) { sz = V.capacity(); cout << "capacity changed: " << sz << '\n'; } } }
capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。
resize在开空间的同时还会进行初始化,影响size。4.【增删查改】
void Add_Delete_Find_Modify() { vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(5); v.push_back(6); v.push_back(7); v.push_back(8); v.push_back(9); v.push_back(10); cout << "v的大小为:" << v.size() << endl; cout << "v的容量为:" << v.capacity() << endl; auto it = v.begin(); cout << "正向打印:"; while (it != v.end()) { cout << *it << " "; ++it; } cout << endl; // vector<int>::reverse_iterator rit = v.rbegin(); auto rit = v.rbegin(); cout << "反向打印:"; while (rit != v.rend()) { cout << *rit << " "; ++rit; } cout << endl; // 在指定位置前插入值为val的元素,比如:8之前插入1000,如果没有则不插入 // 1. 先使用find查找8所在位置 // 注意:vector没有提供find方法,如果要查找只能使用STL提供的全局find it = find(v.begin(), v.end(), 8); v.insert(it, 1000); for (auto ch : v) { cout << ch<<" "; } cout << endl; cout << "v的大小为:" << v.size() << endl; cout << "v的容量为:" << v.capacity() << endl; it = find(v.begin(), v.end(), 3); // 删除pos位置的数据 v.erase(it); for (auto ch : v) { cout << ch<<" "; } cout << endl; cout << "v的大小为:" << v.size() << endl; cout << "v的容量为:" << v.capacity() << endl; // operator[]+index 和 C++11中vector的新式for+auto的遍历 // vector使用这两种遍历方式是比较便捷的。 v[0] = 10; cout << v[0] << endl; cout << "v的大小为:" << v.size() << endl; cout << "v的容量为:" << v.capacity() << endl; // 1. 使用for+[]小标方式遍历 vector<int> swapv; swapv.swap(v); cout << "交换后:" << endl; cout << "v data:"; for (size_t i = 0; i < v.size(); ++i) cout << v[i] << " "; cout << endl; cout << "swapv data:"; for (size_t i = 0; i < swapv.size(); ++i) cout << swapv[i] << " "; cout << endl; cout << "v的大小为:" << v.size() << endl; cout << "v的容量为:" << v.capacity() << endl; cout << "再次交换后:" << endl; v.swap(swapv); cout << "v data:"; for (size_t i = 0; i < v.size(); ++i) cout << v[i] << " "; cout << endl; cout << "swapv data:"; for (size_t i = 0; i < swapv.size(); ++i) cout << swapv[i] << " "; cout << endl; cout << "v的大小为:" << v.size() << endl; cout << "v的容量为:" << v.capacity() << endl; v.clear(); cout << "clear后的数据:"<<endl; for (auto ch : v) { cout << ch<<" "; } cout << endl; cout << "v的大小为:" << v.size() << endl; cout << "v的容量为:" << v.capacity() << endl; v.shrink_to_fit(); cout << "shrink_to_fit后:"<<endl; cout << "v的大小为:" << v.size() << endl; cout << "v的容量为:" << v.capacity() << endl; }
总结
提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。