一、auto和范围for
. auto
在早期C++中auto的含义是 :使用auto修饰的变量,是具有自动存储器的局部变量,后来C++11中,为auto赋予了全新的含义 :auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
用auto声明指针类型时,用auto和auto*没有任何区别 ,但用auto声明引用类型时则必须加&。
需要注意的是,用auto声明指针类型时,右边可以给指针类型,也可以给其它类型,但如果用auto*,就只能给指针类型。


当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器会报错 ,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其它变量。


auto不能用来定义数组类型。


从C++14,auto可以作为函数返回类型,C++20开始,auto可以作为函数参数类型。
. 范围for
C++11中引入了基于范围的 for 循环,for循环后的括号由冒号分为两部分 ,第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围(自动迭代,自动取数据,自动判断结束)。
范围 for 可以作用到数组和容器对象上进行遍历。
范围 for 的底层其实就是替换为迭代器。

二、String的模拟实现
1. string的成员变量
cpp
class string
{
private:
//声明
char* _str;
size_t _size;//字符串存储字符的个数
size_t _capacity;//字符串的容量
static size_t npos;
};
2. 构造函数
cpp
size_t string::npos = -1;//后面要用到
string::string()
:_str(new char[1])
, _size(0)
, _capacity(0)
{
_str[0] = '\0';//初始化
}
3. reserve
cpp
void string::reserve(size_t n)
{
if (n > _capacity)
{
cout << "reserve()" << n << endl;
char* tmp = new char[n + 1];//开辟新空间
strcpy(tmp, _str);//拷贝数据
delete[] _str;//释放旧空间
_str = tmp;//指向新空间
_capacity = n;//更新容量
}
}

异地扩容,开辟新空间,将旧空间的内容拷贝到新空间里,释放旧空间,让指针指向新空间,更新容量的大小。
也有可能是原地扩容,但大概率都是异地扩容,这里不考虑原地扩容。
4. push_back
cpp
void string::push_back(char c)
{
if (_size == _capacity)
{
//扩容
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size] = c;
_size++;
_str[_size] = '\0';
}
首先检查是否还有容量存储数据,若没有就需要扩容,由于构造时初始容量为0,所以,需要判断,如果容量为0,我们就开4个空间,否则,开2倍的空间。此时添加数据,但是数组空间最后一个元素是\0,所以添加完数据之后,先更新_size,再在数组最后添加\0字符。
5. append
cpp
string& string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
//扩容
if (_size + len > 2 * _capacity)
{
reserve(_size + len);
}
else
{
reserve(2 * _capacity);
}
}
strcpy(_str + _size, str);
_size += len;
return *this;
}
append函数添加一个字符串,如果还有存储空间,就直接存储,若存储空间不够,就需要开辟空间 。那么,问题来了,我们需要开辟多大的空间呢?直接开2倍的空间,字符串太长怎么办,难道需要不断开辟新空间吗?那直接开辟一块很大的空间呢 ?字符串太短怎么办,浪费了许多空间。
所以,我们可以先求出字符串的长度,如果存储空间足够,直接存储就可以了,否则,先判断开2倍的空间是否足够,不够就直接开辟_size + len 个字符空间,反之,开辟2倍的空间就足够了。
最后更新_size的值。
6. insert
cpp
string& string::insert(size_t pos, size_t n, char c)
{
assert(pos >= 0 && pos <= _size);
assert(n > 0);
if (_size + n > _capacity)
{
//扩容
if (_size + n > 2 * _capacity)
{
reserve(_size + n);
}
else
{
reserve(2 * _capacity);
}
}
size_t end = _size + n;
while (end > pos + n - 1)
{
_str[end] = _str[end - n];
--end;
}
for (size_t i = 0; i < n; ++i)
{
_str[pos + i] = c;
}
_size += n;
return *this;
}
首先判断要插入的字符的个数是否大于0,字符要插入的位置是否合法。
接着判断存储空间是否足够存储 n 个字符,不够就考虑开2倍的空间还是直接开 _size + n 个字符的空间 ,原因同append函数,反之足够的话就开始插入字符。

紧接着就挪动数据,为插入数据预留位置空间。
cpp
string& string::insert(size_t pos, const char* str)
{
assert(pos >= 0 && pos <= _size);
size_t len = strlen(str);
insert(pos, len, 'x');
for (size_t i = 0; i < len; ++i)
{
_str[pos + i] = str[i];
}
return *this;
}
插入一个字符串,可以复用插入 n 个字符的操作,先随便插入一个指定的字符,然后在从 pos 位置开始覆盖。
7. erase
cpp
string& string::erase(size_t pos, size_t len)
{
assert(pos >= 0 && pos <= _size);
assert(len > 0);
//剩余字符的个数
size_t leftSize = _size - pos;
//pos位置及之后的数据删完了
if (len >= leftSize)
{
len = leftSize;
_str[pos] = '\0';
}
else
{
size_t start = pos + len;
while (start <= _size)
{
_str[pos++] = _str[start++];
}
}
_size -= len;
return *this;
}
分为两种情况 :第一种删除字符的个数大于等于 pos 位置开始剩余的字符个数,从 pos 位置全部删除,有一种简便操作就是把 pos 位置的字符设置为 \0 字符。
否则,就要挪动数据,更新 _size的值。
8. find
cpp
size_t string::find(const char* s, size_t pos)
{
assert(pos >= 0 && pos < _size);
char* ptr = strstr(this->_str + pos, s);
if (ptr == nullptr)
{
return npos;
}
return ptr - _str;
}
查找一个字符串,我们可以使用C语言库的 strstr 库函数查找,成功的话,会返回字符串在string对象中第一次匹配成功位置的指针。要得到匹配成功的下标,只需要让 ptr - _str就可以了。
cpp
size_t string::find(char c, size_t pos)
{
assert(pos < _size);
for (int i = pos; i < _size; ++i)
{
if (_str[i] == c)
{
return i;
}
}
return npos;
}
查找一个字符,只需要从 pos 位置开始遍历数组,判断是否有与字符 c 相等的字符就可以了。
9. substr
cpp
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
size_t leftSize = _size - pos;
if (len >= leftSize)
{
len = leftSize;
}
string tmp;
tmp.reserve(len);
for (size_t i = 0; i < len; ++i)
{
tmp += _str[pos + i];
}
//tmp是一个局部变量,出了作用域便会销毁
//这里需要一个拷贝构造函数(深拷贝)
//浅拷贝会存在多次析构的问题以及间接修改对象
return tmp;
}
如果从pos位置开始提取的 len 个字符大于剩余字符的个数,那么就将剩余字符全部提取。我们可以调用reserve函数,提前开辟好空间,这样就不用频繁申请空间。
但是,这里需要注意的一个点是 tmp 是一个局部对象,出了作用域便会销毁,构建一个临时对象去拷贝构造另一个 string 对象。由于未显示实现拷贝构造函数 ,所以拷贝构造函数的默认行为是浅拷贝。
这就会导致出现问题,两个string对象的_str指向了同一块空间,那么在析构时,就会将同一块空间进行多次释放,这不就造成野指针问题了吗!所以,我们需要显示实现拷贝构造函数。
10. 拷贝构造函数
cpp
//拷贝构造函数
string::string(const string& s)
{
clear();
_str = new char[s.capacity() + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
开辟一个和拷贝对象一样大小的空间,将拷贝对象的内容拷贝进新开辟的空间内,更新_size和_capacity。
11. clear
cpp
void clear()
{
_str[0] = '\0';
_size = 0;
}
清空数据,直接修改0号下标字符为 \0字符,更新_size。
12. 赋值运算符重载
cpp
//s2=s1;
string& string::operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[s.capacity() + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
赋值运算符默认也是浅拷贝的行为,所以,对于有指向资源的成员是非常危险的行为,因此,我们需要实现一个深拷贝的赋值运算符重载。
为了避免自己给自己赋值,从而导致自己的空间被释放,所以需要进行判断。如果不是自己给自己赋值,那就先释放原来的空间,在开辟一个新的空间,拷贝数据,更新_size和_capacity。
13. operator[]运算符重载
cpp
const char& string::operator[](size_t pos)const
{
assert(pos >= 0 && pos < _size);
return _str[pos];
}
底层是数组,直接利用下标访问即可。
14. operator+=运算符重载
cpp
string& string::operator+=(char c)
{
push_back(c);
return *this;
}
直接复用push_back操作即可。
cpp
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
+=一个字符串,复用append函数。
15. 判断两个string对象之间的大小关系
cpp
bool string::operator==(const string& s) const
{
return strcmp(_str, s._str) == 0;
}
bool string::operator!=(const string& s) const
{
return !(_str == s._str);
}
bool string::operator<(const string& s) const
{
return strcmp(_str, s._str) < 0;
}
bool string::operator<=(const string& s) const
{
return !(_str > s._str);
}
bool string::operator>(const string& s) const
{
return strcmp(_str, s._str) > 0;
}
bool string::operator>=(const string& s) const
{
return !(_str < s._str);
}
直接调用C语言中的strcmp函数,比较两个string对象中的字符串即可。
16. getline函数
cpp
istream& string::getline(istream& in, string& s, char delim)
{
s.clear();
char ch;
//阻塞
//in >> ch;
ch = in.get();
const int N = 1024;
int i = 0;
//避免空间给大,造成浪费
//空间给小,频繁扩容
char buff[N];
while (ch != delim)
{
buff[i++] = ch;
if (i == N - 1)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
delim是分割符,get是获取字符的库函数,只要输入的字符串中不包含delim分割符,就要一直输入。为了避免频繁扩容,我们创建了一个char buff 的数组,获取到的字符先存储在字符数组里,如果数组满了,就存储到 string 对象中,将 i 置为0,从头开始继续存储。如果字符串全部读取到数组里,数组未满,就需要判断数组里是否还有内容,有的话就添加到 string 对象中。
17. operator << 运算符重载
cpp
ostream& operator<<(ostream& out, const string& str)
{
for (auto e : str)
{
out << e;
}
return out;
}
out是ostream类的对象,直接遍历string对象输出即可。
18. operator >> 运算符重载
cpp
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch;
//阻塞
//in >> ch;
ch = in.get();
const int N = 1024;
int i = 0;
//避免空间给大,造成浪费
//空间给小,频繁扩容
char buff[N];
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == N - 1)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
原理和上面的getline函数是一样的,只不过换了一个分割符而已。
19. Iterator
cpp
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin()const
{
return _str;
}
const_iterator end()const
{
return _str + _size;
}
这里的 iterator 和 const_iterator 是被 typedef 命名过的 ,实际是 char* 和 const char*。
20. size、capacity、c_str、clear、~string
cpp
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
char* c_str()const
{
return _str;
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
//析构函数
string::~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
三、完整代码
. string.h
cpp
#pragma once
#include<iostream>
#include<assert.h>
#include<string.h>
#include<string>
using namespace std;
namespace LC
{
class string
{
typedef char* iterator;
typedef const char* const_iterator;
public:
//默认构造函数
string();
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin()const
{
return _str;
}
const_iterator end()const
{
return _str + _size;
}
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
char* c_str()const
{
return _str;
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
//拷贝构造函数
string(const string& s);
string& insert(size_t pos, const char* str);
string& insert(size_t pos, size_t n, char c);
void reserve(size_t n);
void push_back(char c);
string& append(const char* str);
string& erase(size_t pos, size_t len = npos);
const char& operator[](size_t pos)const;
string& operator+=(char c);
string& operator+=(const char* str);
size_t find(const char* s, size_t pos = 0);
size_t find(char c, size_t pos = 0);
string substr(size_t pos, size_t len = npos);
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
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;
string& operator=(const string& s);
istream& getline(istream& is, string& str, char delim);
//析构函数
~string();
private:
//声明
char* _str;
size_t _size;//字符串存储字符的个数
size_t _capacity;//字符串的容量
static size_t npos;
};
ostream& operator<<(ostream& out, const string& str);
istream& operator>>(istream& in, string& str);
}
cpp
#define _CRT_SECURE_NO_WARNINGS
#include"string.h"
namespace LC
{
size_t string::npos = -1;
string::string()
:_str(new char[1])
, _size(0)
, _capacity(0)
{
_str[0] = '\0';
}
//拷贝构造函数
string::string(const string& s)
{
clear();
_str = new char[s.capacity() + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
void string::reserve(size_t n)
{
if (n > _capacity)
{
cout << "reserve()" << n << endl;
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void string::push_back(char c)
{
if (_size == _capacity)
{
//扩容
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size] = c;
_size++;
_str[_size] = '\0';
}
string& string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
//扩容
if (_size + len > 2 * _capacity)
{
reserve(_size + len);
}
else
{
reserve(2 * _capacity);
}
}
strcpy(_str + _size, str);
_size += len;
return *this;
}
string& string::insert(size_t pos, const char* str)
{
assert(pos >= 0 && pos <= _size);
size_t len = strlen(str);
insert(pos, len, 'x');
for (size_t i = 0; i < len; ++i)
{
_str[pos + i] = str[i];
}
return *this;
}
string& string::insert(size_t pos, size_t n, char c)
{
assert(pos >= 0 && pos <= _size);
assert(n > 0);
if (_size + n > _capacity)
{
//扩容
if (_size + n > 2 * _capacity)
{
reserve(_size + n);
}
else
{
reserve(2 * _capacity);
}
}
size_t end = _size + n;
while (end > pos + n - 1)
{
_str[end] = _str[end - n];
--end;
}
for (size_t i = 0; i < n; ++i)
{
_str[pos + i] = c;
}
_size += n;
return *this;
}
ostream& operator<<(ostream& out, const string& str)
{
for (auto e : str)
{
out << e;
}
return out;
}
string& string::erase(size_t pos, size_t len)
{
assert(pos >= 0 && pos <= _size);
assert(len > 0);
//剩余字符的个数
size_t leftSize = _size - pos;
//pos位置及之后的数据删完了
if (len >= leftSize)
{
len = leftSize;
_str[pos] = '\0';
}
else
{
size_t start = pos + len;
while (start <= _size)
{
_str[pos++] = _str[start++];
}
}
_size -= len;
return *this;
}
const char& string::operator[](size_t pos)const
{
assert(pos >= 0 && pos < _size);
return _str[pos];
}
string& string::operator+=(char c)
{
push_back(c);
return *this;
}
string& string::operator+=(const char* str)
{
append(str);
return *this;
}
size_t string::find(const char* s, size_t pos)
{
assert(pos >= 0 && pos < _size);
char* ptr = strstr(this->_str + pos, s);
if (ptr == nullptr)
{
return npos;
}
return ptr - _str;
}
size_t string::find(char c, size_t pos)
{
assert(pos < _size);
for (int i = pos; i < _size; ++i)
{
if (_str[i] == c)
{
return i;
}
}
return npos;
}
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
size_t leftSize = _size - pos;
if (len >= leftSize)
{
len = leftSize;
}
string tmp;
tmp.reserve(len);
for (size_t i = 0; i < len; ++i)
{
tmp += _str[pos + i];
}
//tmp是一个局部变量,出了作用域便会销毁
//这里需要一个拷贝构造函数(深拷贝)
//浅拷贝会存在多次析构的问题以及间接修改对象
return tmp;
}
bool string::operator==(const string& s) const
{
return strcmp(_str, s._str) == 0;
}
bool string::operator!=(const string& s) const
{
return !(_str == s._str);
}
bool string::operator<(const string& s) const
{
return strcmp(_str, s._str) < 0;
}
bool string::operator<=(const string& s) const
{
return !(_str > s._str);
}
bool string::operator>(const string& s) const
{
return strcmp(_str, s._str) > 0;
}
bool string::operator>=(const string& s) const
{
return !(_str < s._str);
}
//s2=s1;
string& string::operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[s.capacity() + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
istream& operator>>(istream& in, string& s)
{
s.clear();
char ch;
//阻塞
//in >> ch;
ch = in.get();
const int N = 1024;
int i = 0;
//避免空间给大,造成浪费
//空间给小,频繁扩容
char buff[N];
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == N - 1)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
istream& string::getline(istream& in, string& s, char delim)
{
s.clear();
char ch;
//阻塞
//in >> ch;
ch = in.get();
const int N = 1024;
int i = 0;
//避免空间给大,造成浪费
//空间给小,频繁扩容
char buff[N];
while (ch != delim)
{
buff[i++] = ch;
if (i == N - 1)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
//析构函数
string::~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
}
. test.cc
cpp
#define _CRT_SECURE_NO_WARNINGS
#include"string.h"
void TestString1()
{
LC::string s1;
s1.push_back('h');
s1.push_back('e');
s1.push_back('l');
s1.push_back('l');
s1.push_back('o');
s1.push_back(' ');
s1.append("world");
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
s1.insert(11, 5, 'z');
cout << s1 << endl;
s1.insert(4, 3, 'y');
cout << s1 << endl;
s1.insert(0, 5, 'x');
cout << s1 << endl;
s1.insert(s1.size(), "welcome");
cout << s1 << endl;
s1.insert(10, "china");
cout << s1 << endl;
s1.insert(0, "to");
cout << s1 << endl;
}
void TestString2()
{
LC::string s1;
s1.push_back('h');
s1.push_back('e');
s1.push_back('l');
s1.push_back('l');
s1.push_back('o');
s1.push_back(' ');
s1.append("world");
s1.erase(11, 5);
cout << s1 << endl;
s1.erase(8, 5);
cout << s1 << endl;
s1.erase(3, 5);
cout << s1 << endl;
s1.erase(0, 2);
cout << s1 << endl;
}
void TestString3()
{
LC::string s1;
s1.push_back('h');
s1.push_back('e');
s1.push_back('l');
s1.push_back('l');
s1.push_back('o');
s1.push_back(' ');
s1.append("world");
cout << s1[3] << endl;
cout << s1[0] << endl;
s1 += ' ';
s1 += "welcome";
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
LC::string s2;
s2 += "china";
cout << s2.c_str() << endl;
cout << s1.find("come", 3) << endl;
cout << s1.find("hello", 2) << endl;
cout << s1.find("hello") << endl;
cout << s1.find("to", 5) << endl;
cout << s1.find('w', 10) << endl;
cout << s1.find('o') << endl;
cout << s1.find('z') << endl;
//编译器进行了优化,隐藏有BUG
//拷贝构造
/*LC::string s3 = s1.substr(3);
cout << s3 << endl;*/
//2022编译器会进行优化,这种写法会打破编译器的优化
LC::string s3;
//赋值重载
s3 = s1.substr(3);
cout << s3 << endl;
}
void TestString4()
{
LC::string s1;
s1.push_back('h');
s1.push_back('e');
s1.push_back('l');
s1.push_back('l');
s1.push_back('o');
LC::string s2;
s2.push_back('w');
s2.push_back('e');
s2.push_back('l');
s2.push_back('c');
s2.push_back('o');
cout << (s1 == s2) << endl;
cout << (s1 != s2) << endl;
cout << (s1 < s2) << endl;
cout << (s1 <= s2) << endl;
cout << (s1 > s2) << endl;
cout << (s1 >= s2) << endl;
LC::string s3;
cin >> s3;
cout << s3 << endl;
LC::string s4;
s4.getline(cin, s4, '#');
cout << s4 << endl;
}
int main()
{
TestString1();
//TestString2();
//TestString3();
//TestString4();
return 0;
}