1.再谈string
string为什么要被设计成模板?日常使用string好像都是char*,char*不够使用吗,为什么要设计成模板呢?
1.1 关于编码
//计算机的存储如何区分呢?
int main()
{
//比如在C语言中,有整型
//如果是有符号的话,设计了原、反、补码
//负数要存储它的补码,方便运算
//浮点数要设计到存精度等问题
int a = 0;
return 0;
}
那么如何去更好地表示其它的各种文字呢?
1.2 ASCII编码
ASCII_百度百科 (baidu.com)
ASCII编码表示美国信息技术的编码,在一开始设计时,只考虑了存储美国的文字信息
内存中存储字母时,不会把字母存储进去,内存中都是0、1组合的二进制代码,所以,要在二进制代码代表的值与符号间,建立一个一一映射对应的关系,这个关系表,一般称为编码表
ASCII表就是值与美国的文字所建立的拥有一一映射对应的关系的编码表
int main()
{
char str[] = "hello world";
return 0;
}
//要显式时,发现它是字符串,会去查它的编码表
1.3 unicode
统一码_百度百科 (baidu.com)
计算机向全世界推广后,不单单需要能够表示汉字,还需要能够表示其他国家的文字符号,有些国家的文字比较简单,有些可能比汉字还要复杂。因此,设计出了万国码。
所以要表示汉字,也要使得每个汉字都对应一个数字
那如何表示汉字呢?也是用一个byte表示一个汉字吗?
要知道1个byte才8个bit位,这样的话才只能表示256个汉字,显然不能很好地表示方案有三种:
1.3.1 UTF-8
int main()
{
//UTF-8 主流使用,一个值可能在多个字节上面存储
//但windows喜欢使用gbk,linux喜欢使用UTF-8
char str1[] = "hello world";//12byte
char str2[] = "工大";//5byte
char str3[] = "工大 hello";//可以混着使用,11byte
cout << sizeof(str1) << endl;
cout << sizeof(str2) << endl;
cout << sizeof(str3) << endl;
char* a = str3;
cout << *a << endl;
return 0;
}
UTF-8的缺点:变长,意味着识别比较复杂,太不统一,比如做屏蔽时;
或是有些情况下文字不需要兼容ASCII,这时就要使用其它方式了
1.3.2 UTF-16
1.3.3 UTF-32
1.4设计成模板的原因
int main()
{
//为了更好地兼容这两种编码,类型进行了延申
//C++11之前,设计出了宽字节,一个char占据2byte
//用来更好地表示其它的编码,比如UTF-16
wchar_t ch1; //宽字节
cout << sizeof(ch1) << endl;//2byte
char16_t ch2;
char32_t ch3;
cout << sizeof(ch2) << endl;//2byte
cout << sizeof(ch3) << endl;//4byte
return 0;
}
这也是string要设计成模板的原因,它可以传不同的模板参数,比如char16_t、char32_t等,可以适用不同的编码,表示更多国家的不同的文字。
1.5何为乱码
比如某文字默认使用UTF-8来存储,但显示时没有使用对于的编码表来查找,就会出现乱码。
存储格式和解释方式没有对应起来
1.6GBK
GBK字库_百度百科 (baidu.com)
虽然unicode是全世界的编码,但它未必非常适合汉字的表达。GBK是中国创造的、更适合汉字表达的编码表。
int main()
{
char s1[] = "你好!!";
s1[0]++;//陪
s1[0]++;
s1[0]++;
s1[0]++;
s1[3]++;//耗
s1[3]++;
s1[3]++;//号
//在净网行动中非常有用
//黑名单词库
return 0;
}
2.模拟实现string
2.1无参的构造和析构
//string.h
#pragma once
namespace jxy
{
class string
{
public:
//初始化的顺序是按照声明的顺序,而不是初始化列表出现的顺序
string(const char* str)
:_size(strlen(str))
,_capacity(_size)//capacity一般不算'\0'
{
_str = new char[_capacity+1];//开空间时要多开一个
strcpy(_str, str);//strcpy会把'\0'也拷贝过去
}
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
const char* c_str() const
{
return _str;
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
void test_string1()
{
string s1("hello world");
cout << s1.c_str() << endl;
}
}
//Test.cpp
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
//这里间接包了string的相关函数
//#include"string.h"
//注意,如果写在这里可能会报错,因为std的展开在下面
//编译器为了追求编译速度,只会向上查找
using namespace std;
#include"string.h"//编译器实际编译时是没有.h的,在预处理阶段就在.c或者.cpp展开了
int main()
{
jxy::test_string1();
return 0;
}
2.2单参数的构造
错误示范:
namespace jxy
{
class string
{
public:
string()
:_str(nullptr)
,_size(0)
,_capacity(0)
{}
};
void test_string1()
{
string s2;
cout << s2.c_str() << endl;
//这样会崩溃
//char*有个特点,自定义类型识别会以字符串去识别,会直接去解引用,遇到'\0'才终止
//这里空指针解引用就会出错
std::string s1;
cout << s1.c_str() << endl;//库里面的是没问题的
}
}
正确写法1:
namespace jxy
{
class string
{
public:
string()
:_str(new char[1]{'\0'})//这里需要开空间
,_size(0)
,_capacity(0)
{
//_str[0] = '\0';//或者在这里初始化
}
};
void test_string1()
{
string s2;
cout << s2.c_str() << endl;
}
}
正确写法2:在无参构造处给上缺省参数
namespace jxy
{
class string
{
public:
//const char* str="\0" 这样写不够规范
string(const char* str="")//C语言规定常量字符串后面默认有'\0'
:_size(strlen(str))
,_capacity(_size)
{
_str = new char[_capacity+1];
strcpy(_str, str);
}
};
void test_string1()
{
string s2;
cout << s2.c_str() << endl;
}
}
2.3 size、capacity和c_str
namespace jxy
{
class string
{
public:
size_t capacity() const
{
return _capacity;
}
size_t size() const
{
return _size;
}
const char* c_str() const
{
return _str;
}
};
private:
char* _str;
size_t _size;
size_t _capacity;
}
2.4遍历数组
2.4.1[]
namespace jxy
{
class string
{
public:
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const//给const对象使用
{
assert(pos < _size);
return _str[pos];
}
};
//...
}
2.4.2迭代器
namespace jxy
{
class string
{
public:
typedef char* iterator;//迭代器可以实现成指针,也可以不
//可以使用原生指针来代替iterator
iterator begin()
{
return _str;
}
iterator end()
{
return _str+_size;
}
};
//...
}
2.4.3范围for
namespace jxy
{
class string
{
//...
};
void test_string2()
{
//1. []
string s1("hello world");
for (size_t i = 0; i <s1.size() ; i++)
{
cout << s1[i] << " ";
}
cout << endl;
//2.迭代器
string::iterator it=s1.begin();//在里面typedef的或者是内部类
while (it != s1.end())
{
cout << *it << " ";
it++;
}
cout << endl;
//3.范围for
//这里还支持范围for
for (auto ch : s1)//底层代码和上面的迭代器类似
{
//auto ch = *it;
cout << ch << " ";
}
cout << endl;
//范围for的本质是替换成迭代器,编译时直接替换过去
//而且有很严格的规范,名字变化一下都是不可以的
}
}
2.5复用reserve实现尾插
实现尾插的三种方式:
1.push_back
2.append
3.+=
namespace jxy
{
class string
{
public:
void reserve(size_t n)
{
assert(n > _capacity);
char* str1 = new char[n+1];//失败会抛异常
strcpy(str1,_str);//会把'\0'拷贝过去
delete[] _str;//越界会在这里崩溃
_str = str1;
_capacity = n;
}
void push_back(char ch)
{//扩容问题
if (_size == _capacity)
{
reserve(_capacity==0 ? 4 :_capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
//...
};
void test_string3()
{
string s1("hello world");
cout << s1.c_str() << endl;
s1.push_back('0');
cout << s1.c_str() << endl;
s1.append("hello lxy");
cout << s1.c_str() << endl;
s1 += '$';
cout << s1.c_str() << endl;
s1+="hellox";
cout << s1.c_str() << endl;
}
}
2.6 insert和erase
insert错误写法:
namespace jxy
{
class string
{
public:
void insert(size_t pos,char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
size_t end = _size;//头插会出问题,这里是无符号数
//int end = _size; //改为有符号数也是行不通的
//在操作符,如 >= 两边,类型不同时,会发生类型提升
//这里end虽然是有符号数,但会被提升成无符号数
while (end >= pos)
{
_str[end + 1] = _str[end];
end--;
}
_str[pos] = ch;
_size++;
}
//...
};
void test_string4()
{
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;
}
}
正确写法1:
namespace jxy
{
class string
{
public:
void insert(size_t pos,char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
size_t end = _size+1;
while (end>pos)
{
_str[end] = _str[end-1];
end--;
}
_str[pos] = ch;
_size++;
}
//...
};
}
正确写法2:
namespace jxy
{
class string
{
public:
void insert(size_t pos,char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
int end = _size;
while (end>=(int)pos)//或者把这里强转一下,避免出现类型提升
{
_str[end] = _str[end-1];
end--;
}
_str[pos] = ch;
_size++;
}
//...
};
}
namespace jxy
{
class string
{
public:
void insert(size_t pos,size_t len=npos)
{
}
void insert(size_t pos,const char* str)
{
}
void erase(size_t pos,size_t len=npos)
{
}
private:
char* _str;
size_t _size;
size_t _capacity;
//注意
//const static size_t npos =-1; //特例
//这里是特殊情况,按理说不能在这里初始化
//这里给值给的是缺省值,缺省值是给初始化列表使用的
//静态成员变量不会去执行初始化列表,它不属于对象,它属于整个类,按理说要在类外面初始化
//但是const修饰的静态的整型可以,所以这里既是定义,又是定义初始化
//const static double npos = 1.2;//这样都不会去支持
const static size_t npos;
};
const size_t string::npos = -1;
}
2.7运算符重载
namespace jxy
{
class string
{
public:
//运算符重载
//设计成非成员函数更好,便于模板的使用
bool operator<(const string& str1)
{
return strcmp(_str, str1._str)<0;
}
bool operator==(const string& str1)
{
return strcmp(_str, str1._str)==0;
}
bool operator<=(const string& str1)
{
return *this<str1 || *this==str1;
}
bool operator>=(const string& str1)
{
return !(*this<str1);
}
bool operator>(const string& str1)
{
return !(*this<=str1);
}
bool operator!=(const string& str1)
{
return !(*this == str1);
}
//...
};
}
2.8流插入
namespace jxy
{
class string
{
public:
typedef const char* const_iterator;//const迭代器指向的内容不能修改
//指针本身可以修改,指向的内容不能修改
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
//...
};
//没有必要定义为友元,想访问私有成员变量才需要定义为友元
ostream& operator<<(ostream& out, const string& s)
{
1.
//for (size_t i = 0; i < s.size(); i++)
//{
// out << s[i];
//}
//return out;
//2.可以使用范围for,但要替换成const迭代器
for (auto ch : s)
out << ch;
return out;
}
}
2.9流提取和clear
namespace jxy
{
class string
{
public:
void clear()
{
_str[0] = '\0';
_size = 0;
}
//...
};
//没有必要定义为友元,想访问私有成员变量才需要定义为友元
istream& operator>>(istream& in,string& s)
{//流提取要从缓冲区中取一个个的字符
s.clear();
char ch;
//in >> ch; //这样写获取不到空格
ch = in.get();
while (ch != ' ' && ch != '\n')
{
s += ch;
//in >> ch;
ch = in.get();
}
return in;
}
void test_string6()
{
string s1("hello world");
cout << s1 << endl;
cin >> s1;
cout << s1 << endl;
}
}