我要去赶火车,走夜路,先活过那条哀鸣的狗,再回来认我的命。
前言
这是我自己学习C++的第六篇博客总结。后期我会继续把C++学习笔记开源至博客上。
上一期笔记是关于C++的模板初阶知识,没看的同学可以过去看看:
STL
STL容器概念
1. STL容器是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。
2. STL容器里面的函数都是C++标准库中的函数。
STL六大组件
string类
string类的优势
在C语言中,字符串是以'\0'结尾的一些字符的集合。C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合面向对象编程的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
string类的本质
1. 在使用string类时,必须包含 #include <string> 这一行。
2. string类的底层其实是一个储存字符的顺序表结构。
class string
{
public:
//成员函数
private:
//一个顺序表结构
char* arr;//一个存储字符的动态数组
int size;//字符数组的有效大小
int capacity;//字符数组的容量
};
auto关键字
1. auto可以让编译器自动推导变量类型。
2. auto一般用在变量类型比较长、复杂的地方,把变量类型直接用auto替代,更加方便。
3. auto一般和范围for连用,写起来简洁方便。
4. 如果想让auto声明引用类型变量,则需要使用auto&。
5. auto使用时,不能直接拿来声明数组。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
int main()
{
int r1 = 1;
auto r2 = r1;//自动推导为 int
auto r3 = &r2;//自动推导为 int*
int& r4 = r2;
auto r5 = r4;//自动推导为 int
auto& r6 = r4;//自动推导为 int&
return 0;
}
string类的常用接口说明
string类对象的常见构造
1. string(),什么也不需要传入,构造一个空的string类对象,即空字符串。
2. string(const char*s),传入一个字符串s,然后用字符串s进行构造。
3. string(size_t n,char c),构造一个string类对象,里面包含n个c字符。
4. string(const string&str),传入一个string类对象str,然后用str进行拷贝构造。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str1;
string str2("abcd");
string str3(5, 'c');
string str4(str2);
cout << str1 << endl;
cout << str2 << endl;
cout << str3 << endl;
cout << str4 << endl;
return 0;
}
//
//abcd
//ccccc
//abcd
1. string类对象的初始化分为两种。
2. 如果使用=号,则为拷贝初始化;如果不使用=号,则为赋值初始化。
3. string类对象的初始化时,必须传入一个字符串。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str1("hehe");
string str2 = "hehe";
cout << str1 << endl;
cout << str2 << endl;
return 0;
}
//hehe
//hehe
string类对象的容量操作
1. string.size(),返回字符串有效字符的大小。
2. string.capacity(),返回字符串底层容量大小。
3. string.empty(),检测字符串有效字符是否为空,为空返回true,为非空返回flase。
4. string.clear(),清空字符串的有效字符,此时字符串中有效字符大小为0,但底层容量大小不变。
5. string.reserve(size_t n),为字符串预留出空间,实际预留的空间通常比n大。
6. string.resize(size_t n,char c),将字符串中有效字符个数改成n个,如果有效字符有多出的空间,就用字符c填充。
7. string.clear()只是将字符串中的有效字符清空,只改变有效字符大小,并不改变底层容量大小。
8. string.resize(size_t n)与string.resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(size_t n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。
9. string.resize(size_t n) 在改变元素个数时,如果是将元素个数 增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。10. string.reserve(size_t n) 为字符串预留空间,不改变有效元素个数,当 reserve 的参数小于字符串的底层容量总大小时, reserver 不会改变容量大小 。11. string.resize(size_t n) 和 string.reserve(size_t n) 有 可能增大 底层容量,但是 不会减小 底层容量。
int main()
{
string str1("hello world");
cout << str1.size() << endl;//11
cout << str1.capacity() << endl;//15
cout << endl;
str1.clear();
cout << str1.size() << endl;//0
cout << str1.capacity() << endl;//15
cout << str1.empty() << endl;//1(为空)
cout << endl;
str1.resize(10, 'q');
cout << str1.size() << endl;//10
cout << str1.capacity() << endl;//15
cout << endl;
str1.resize(15);
cout << str1.size() << endl;//15
cout << str1.capacity() << endl;//15
cout << endl;
str1.reserve(100);
cout << str1.size() << endl;//15
cout << str1.capacity() << endl;//111
cout << endl;
str1.reserve(5);
cout << str1.size() << endl;//15
cout << str1.capacity() << endl;//111
cout << endl;
return 0;
}
string类对象的访问遍历操作
1. string[int pos],返回下标为pos位置的字符。
2. string.begin()、string.end(),string.begin()获取第一个字符的迭代器,string.end()获取最后一个字符的下一个位置的迭代器。
3. string.rbegin()、string.rend(),string.rbegin()获取最后一个字符的迭代器,string.rend()获取第一个字符的前一个位置的迭代器。
4. 注意反向迭代器进行迭代的步骤也是++,反向迭代器是用来反向遍历字符串的。
5. 范围for循环,用于有范围的集合进行遍历,C++11中引入了基于范围的for循环。for循环后的括号由冒号" : "分为两部分:第一部分是用于迭代的变量 (可以使用auto让编译器自动判断变量类型),第二部分则表示迭代的范围。
6. 范围for循环有着:自动迭代,自动取数据,自动判断结束的特点。
7. 范围for循环,如果需要对范围对象进行修改,则使用auto&来修饰迭代的变量;如果需要对较大的目标对象进行遍历,也可以使用auto&来修饰迭代的变量。
8. 范围for循环,是用于遍历容器的,它的底层也是迭代器,数组也能使用范围or循环。
//正常遍历
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str1("Test string");
for (int i = 0; i < str1.size(); i++)
{
cout << str1[i];
}
return 0;
}
//Test string
//[]操作符底层逻辑
class string
{
public:
char& operator[](int pos)//进行运算符重构
{
assert(pos < _size);
return _arr[pos];
}
private:
char* _arr;
int _size;
int _capacity;
};
//迭代器
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str1("Test string");
string::iterator it1 = str1.begin();//把iterator看成string类里面的一个内部类。
while (it1!=str1.end())//str1.end()表示str1的最后一个有效字符的下一位。
{
cout << *it1;//可以把it1看成指针
++it1;
}
return 0;
}
//Test string
//反向迭代器
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str1("hello world");
auto it1 = str1.rbegin();
while (it1 != str1.rend())
{
cout << *it1;
it1++;
}
return 0;
}
//dlrow olleh
//for遍历
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str1("hello world");
for(auto s:str1)
{
cout<<s;
}
return 0;
}
//hello world
//for修改
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str1("hello world");
for(auto& s:str1)
{
s++;
cout<<s;
}
return 0;
}
//ifmmp!xpsme
//循环数组
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
int main()
{
int arr1[] = { 1,2,3,4,5,6,7 };
for (auto& num : arr1)
{
cout << num;
}
return 0;
}
//1234567
string类对象的修改操作
1. string.push_back(char c),在字符串后尾插字符c。
2. string.append(char s),在字符串之后尾插字符串s。
3. +=,在字符串之后尾插字符或者字符串。
4. string.insert(int pos,char s)方法可以从下标为pos的位置往前插入字符串s。
5. string.erase(int pos,size_t len)方法可以从下标为pos的位置上,删除pos位置及其后面共len个字符。
6. replace(int pos,size_t len,char s)方法可以从下标为pos的位置上,替换pos位置及其后面共len个字符,替换为字符串s。
7. string.find()、string::npos,string.find()方法用于在字符串中查找相同的字符,如果找到了则返回该字符下标。如果string.find()找到了,则不等于string::npos,如果string.find()没找到,则等于string::npos。
8. string.substr(int pos,size_t len)方法可以从下标为pos的位置上,返回pos位置及其后面共n个字符。
9. reverse(string.begin(),string.end()),用于反转整个字符串。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str1("hello world");
str1.push_back('!');
str1.append(" hello world!");
cout << str1;
return 0;
}
//hello world! hello world!
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str1("hello world");
str1 += '!';
str1 += " hello world";
cout << str1;
return 0;
}
//hello world! hello world!
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main()
{
string str("abcdefg");
str.insert(0, "111");
cout << str << endl;
str.erase(0, 3);
cout << str;
return 0;
}
//111abcdefg
//abcdefg
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str("ab cd ef g");
int i = str.find(' ');
while (i != string::npos)
{
str.replace(i, 1, "%%");
i = str.find(' ');
}
cout << str;
return 0;
}
//ab%%cd%%ef%%g
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str("hello world");
auto s = str.substr(0, 2);
cout << s;
return 0;
}
//he
//反转字符串
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main()
{
string str("abcdefg");
reverse(str.begin(), str.end());
cout << str;
return 0;
}
//gfedcba
string类对象的输入
1. cin输入,string对象会自动忽略开头的空格和换行,并从第一个有效字符开始读取,然后在下一个空格或者换行处自动结束读取。
2. getline(cin,string)输入,会在换行处自动断开,可以输入带有空格的字符串。
3. getline(cin,string,'*')输入,可以指定'*'为结束字符,直到出现'*'才结束输入。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str1, str2;
cin >> str1 >> str2;
cout << str1 << endl;
cout << str2 << endl;
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str1, str2;
getline(cin, str1);
getline(cin, str2);
cout << str1 << endl;
cout << str2 << endl;
return 0;
}
模拟实现string类
#include <iostream>
#include <cstring>
#include <cassert>
using namespace std;
class String
{
public:
using iterator = char*;
String()
:_arr(new char[1] {'\0'})
, _size(0)
, _capacity(0)
//有效字符长度和底层容量都不计入'\0'的空间
{}
String(const char* s)
:_arr(new char[strlen(s) + 1])
, _size(strlen(s))
, _capacity(strlen(s))
{
strcpy(_arr, s);
}
String(const String& s)
{
_arr = new char[s._capacity + 1];
strcpy(_arr, s._arr);
_size = s._size;
_capacity = s._capacity;
}
~String()
{
delete[] _arr;
_arr = nullptr;
_size = 0;
_capacity = 0;
}
const char& operator[](size_t i) const//第一个const避免通过返回的引用修改原来的字符串内容,&防止函数进行值传递,第二个const防止函数修改原字符串内容
{
assert(i < _size);
return _arr[i];
}
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
iterator begin()//返回第一个字符的地址(指针)
{
return _arr;
}
iterator end()//返回最后一个字符的下一个位置的地址(指针)
{
return _arr + _size;
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _arr);
delete[] _arr;
_arr = tmp;
_capacity = n;
}
}
void push_back(char c)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
_arr[_size] = c;
_size++;
}
}
friend ostream& operator<<(ostream& os, const String& str);
friend istream& operator>>(istream& is, String& str);
protected:
char* _arr;
int _size;
int _capacity;
};
istream& operator>>(istream& is, String& str)
{
char c;
is >> c;//先读取第一个字符
while (c != ' ' && c != '\n')
{
str.push_back(c);
is >> c;
}
return is;
}
ostream& operator<<(ostream& os, const String& str)//const防止输出重载函数对str做出修改,&防止输出重载函数进行值传递
{
os << str._arr;
return os;
}
int main()
{
String s1("hello");
String s2 = s1;
cout << s1 << endl;
cout << s1[2] << endl;
auto it = s2.begin();
while (it != s2.end())
{
cout << *it << " ";
it++;
}
return 0;
}
深浅拷贝
1. 浅拷贝是指在进行拷贝时,仅仅复制原对象的数据成员的值,而不递归地复制指针或引用所指向的对象。这意味着如果数据成员中包含指针,浅拷贝只会复制指针的值(即地址),而不是分配新的内存来复制指针所指向的内容。因此,新旧对象可能会共享同一块动态分配的内存或资源。
2. 如果多个对象共享同一份资源, 则当一个对象销毁时就会将该资源释放掉 , 而另外对象不知道该资源已经被释放 ,所以当继续对资源进项操作时,就会发生发生了访问违规。3. 深拷贝是指 在进行拷贝时 , 不仅复制原对象的数据成员的值, 还会递归地复制所有动态分配的内存和其他引用的对象, 两个对象拥有独立的内存空间。
//浅拷贝
+------------------+ +------+
| original | | 42 |
| ptr ------------>|-----> | |
+------------------+ +------+
^
|
+------------------+ |
| copy | |
| ptr ------------>|--------------+
+------------------+
//深拷贝
+------------------+ +------+
| original | | 42 |
| ptr ------------>|-----> | |
+------------------+ +------+
+------------------+ +------+
| copy | | 42 |
| ptr ------------>|-----> | |
+------------------+ +------+
致谢
感谢您花时间阅读这篇文章!如果您对本文有任何疑问、建议或是想要分享您的看法,请不要犹豫,在评论区留下您的宝贵意见。每一次互动都是我前进的动力,您的支持是我最大的鼓励。期待与您的交流,让我们共同成长,探索技术世界的无限可能!