当前位置: 首页 > news >正文

C++——认识STL及使用及实现第一个容器string

✨✨ 欢迎大家来到小伞的大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:C++学习
小伞的主页:xiaosan_blog

1. 什么是STL

1.1 STL的版本

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

原始版本

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.2 STL的六大组件

2.使用第一个容器string

string - C++ Reference (cplusplus.com)

查看C++参考对string的解释,我们会发现string与字符串有关,

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

2.1 auto和范围for

在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

auto不能作为函数的参数,可以做返回值,但是建议谨慎使用

auto不能直接用来声明数组

int func1()
{return 10;
} // 不能做参数
//void func2(auto a)
//{}// 可以做返回值,但是建议谨慎使用
auto func3()
{return 3;
} int main()
{int a = 10;auto b = a;auto c = 'a';auto d = func1();// 编译报错:rror C3531: “e”: 类型包含“auto”的符号必须具有初始值设定项//auto e;//typeid().name可以打印变量类型cout << typeid(b).name() << endl;cout << typeid(c).name() << endl;cout << typeid(d).name() << endl;int x = 10;auto y = &x;auto* z = &x;auto& m = x;cout << typeid(x).name() << endl;cout << typeid(y).name() << endl;cout << typeid(z).name() << endl;auto aa = 1, bb = 2;// 编译报错:error C3538: 在声明符列表中,“auto”必须始终推导为同一类型//auto cc = 3, dd = 4.0;// 编译报错:error C3318: “auto []”: 数组不能具有其中包含“auto”的元素类型//auto array[] = { 4, 5, 6 };return 0;
}
#include<iostream>
#include <string>using namespace std;#include <map>
int main()
{map<string, string> dict = { { "apple", "ping guo" },{ "orange","cheng zi" }};auto it = dict.begin();while (it != dict.end()){cout << it->first << ":" << it->second << endl;++it;}return 0;
}

 auto适用大部分的容器,如:“字符串”“顺序表”......

2.2 范围for

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。

范围for可以作用到数组和容器对象上进行遍历

范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。

#include<iostream>
#include<string>
using namespace std;int main() {int arr[] = { 1,2,3,4,5,6,7,8,9 };// C++98的遍历//采用计算长度,利用for循环遍历for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i){arr[i] *= 2;} for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i){cout << arr[i] << endl;}// C++11的遍历//auto会自己遍历for (auto& e : arr)e *= 2;for (auto e : arr)cout << e << " " << endl;string str("hello world");for (auto ch : str){cout << ch << " ";} cout << endl;return 0;
}

容器遍历实际就是替换为迭代器,这个从汇编层也可以看到,采用范围for会减少访问指针时的报错。

2.3 string类的常用接口说明(注意下面我只讲解最常用的接口)


int main() {string s1;//构造空的string类对象s1string s2("hello world");//构造的string类对象s1string s3(s2);//拷贝构造return 0;
}

2.3 string类对象的容量操作

函数名称功能说明

size(重点)返回字符串有效字符长度

length返回字符串有效字符长度

capacity返回空间总大小

empty(重点)检测字符串释放为空串,是返回true,否则返回false

clear(重点)清空有效字符

reserve(重点)reserve为字符串预留空间

resize(重点)将有效字符的个数调整成n个字符,多出的空间用字符c填充

#include<iostream>
#include<string>
using namespace std;void test1(string& s) {//返回字符串有效字符(不包括'\0')cout << s.size()<< endl;cout << s.length()<< endl;//返回空间总大小cout << s.capacity() << endl;//检测字符串释放为空串,是返回true,否则返回falsecout << s.empty() << endl;//清空有效字符s.clear();cout << s.empty() << endl;//为字符串预留空间s.reserve(40);//将有效字符的个数调整成n个字符,多出的空间用字符c填充s.resize(60);
}void test2(string& s) {//string s2("hello world");//返回空间总大小:15cout << s.capacity() << endl;//预留空间<空间大小s.reserve(10);//返回空间大小:15cout << s.capacity() << endl;//预留空间>空间大小s.reserve(500);//返回空间大小:cout << s.capacity() << endl;s.reserve(20);//返回空间大小:15cout << s.capacity() << endl;
}void test3() {string s;size_t sz = s.capacity();cout << "making s grow:\n";for (int i = 0; i < 100; ++i){//从 15 31 47 70 105 157 235,依次增加空间,基本符合1.5倍空间开辟,后面会2倍开辟空间//而在gcc的环境下,空间为2倍开辟s.push_back('c');if (sz != s.capacity()){sz = s.capacity();cout << "capacity changed: " << sz << '\n';}}
}void test4() {string s;// 测试reserve是否会改变string中有效元素个数s.reserve(100);cout << s.size() << endl;cout << s.capacity() << endl;// 测试reserve参数小于string的底层空间大小时,是否会将空间缩小s.reserve(50);cout << s.size() << endl;cout << s.capacity() << endl;//0 111 0 111
}int main() {string s1;//构造空的string类对象s1string s2("hello world");//构造的string类对象s1string s3(s2);//拷贝构造//test1(s2);test2(s2);return 0;
}

reserve(为字符串预留空间):注意其小于有效字符大小时,不改变其空间大小

resize(将有效字符的个数调整成n个字符,多出的空间用字符c填充):注意其小于有效字符大小时,会删除其字符

2.4 string类对象的访问及遍历操作

函数名称功能说明

operator[](重点)返回pos位置的字符,const string类对象调用

begin+ endbegin获取一个字符的迭代器 + end获取最后一个字符下一个位

置的迭代器

rbegin + rend

rbegin:返回一个反向迭代器,该迭代器指向字符串的最后一个字符(即其反向开头

rend:返回一个反向迭代器,该迭代器指向字符串第一个字符之前的理论元素(被视为其反向端

范围forC++11支持更简洁的范围for的新遍历方式

operate[] :返回pos位置的字符,const string类对象调用

void test5() {string s1("hello world");const string s2("Hello world");cout << s1 << " " << s2 << endl;//支持下标的访问及更改(注意const对象不能更改)cout << s1[0] << " " << s2[0] << endl;s1[0] = 'H';cout << s1 << endl;// s2[0] = 'h';   代码编译失败,因为const类型对象不能修改
}

begin+ end:begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器

void test6() {string s("hello world");// 3种遍历方式:// 需要注意的以下三种方式除了遍历string对象,还可以遍历是修改string中的字符,// 另外以下三种方式对于string而言,第一种使用最多// 1. for+operator[]for (size_t i = 0; i < s.size(); ++i)cout << s[i];cout << endl;// 2.迭代器:我们还可以自己实现begin和end迭代器以适配其他容器string::iterator it = s.begin();//it:获取s的第一个字符的迭代器while (it != s.end())//end获取最后一个字符迭代器{//相当于地址,此时需要解引用cout << *it ;++it;}cout << endl;// string::reverse_iterator rit = s.rbegin();// C++11之后,直接使用auto定义迭代器,让编译器推到迭代器的类型//反向迭代器auto rit = s.rbegin();while (rit != s.rend()) {cout << *rit ;rit++;}cout << endl;// 3.范围for,在编译层可以看到,其实是使用迭代器,但如果该改变begin与end的名,则不能使用,而iterator可以for (auto ch : s)cout << ch ;
}

hello world
hello world
dlrow olleh(因为rbegin为反向迭代器):
auto rit = s.rbegin();当rit++时,rit是往s第一个字符的方向;

hello world

2.5 string类对象的修改操作

函数名称功能说明

push_back在字符串后尾插字符c

append在字符串后追加一个字符串

operator+= (重点)在字符串后追加字符串str

c_str(重点)返回C格式字符串

find + npos(重点)从字符串pos位置开始往后找字符c,返回该字符在字符串中第一次出现的位置

rfind从字符串pos位置开始往前找字符c,返回该字符在字符串中最后一次出现的位置

substr在str中从pos位置开始截取n个字符,然后将其返回

void test7() {string str;str.push_back(' ');   // 在str后插入空格str.append("hello");  // 在str后追加一个字符"hello"str += 'w';           // 在str后追加一个字符'w'   str += "orld";          // 在str后追加一个字符串"orld"cout << str << endl;cout << str.c_str() << endl;   // 以C语言的方式打印字符串// 获取file的后缀string file("str.ing.cpp");size_t pos = file.rfind('.');//返回该字符出现的最后一次的位置string suffix(file.substr(pos, file.size() - pos));//在str中从pos位置开始,截取n个字符,然后将其返回cout << suffix << endl;// 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;//找到www.,删除前部分size_t finish = url.find('/', start);//查找第一次出现的位置string address = url.substr(start, finish - start);cout << address << endl;// 删除url的协议前缀pos = url.find("://");url.erase(0, pos + 3);//删除0到pos+3的位置cout << url << endl;
}

2.6 string类非成员函数

函数功能说明

operator+尽量少用,因为传值返回,导致深拷贝效率低

operator>> (重点)输入运算符重载

operator<< (重点)输出运算符重载

getline (重点)获取一行字符串

relational operators (重点)大小比较

getline(获取一行字符串):

之前在C语言中我们通常scanf("%[^\n]",&s):需要先定义字符串数组。fget()函数    

scanf("%[^\n]",&s);(这里是可以存放空格的);

char c[20];
fgets(c, sizeof(c), stdin);(可以存放空格)
puts(c);

而在C++中准备好了getline()函数,我们不需要提前准备好s的大小

string s;
getline(cin,s);
cout << s;


3.string容器的实现

3.1 string.h(实现接口)

对于调用频繁的函数,包在头文件中实现,效率更高,程序结构也更规范

#pragma once#define _CRT_SECURE_NO_WARNINGS 1
#pragma once#include<iostream>
#include<string>
#include<assert.h>
using namespace std;namespace sui
{class string{public://迭代器 普通类型与const类型typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}//构造函数,这里为全缺省函数""相当于初始化'\0';string(const char* str = ""){_size = strlen(str);_capacity = _size;//字符串中'\0'算一个字节_str = new char[_capacity + 1];strcpy(_str, str);}//析构函数~string(){if (_str) {free(_str);_str = nullptr;_size = _capacity = 0;}}const char* c_str() const{return _str;}void clear(){_str[0] = '\0';_size = 0;}size_t size() const{return _size;}size_t capacity() const{return _capacity;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}//实现接口void reserve(size_t n);void push_back(char ch);void append(const char* str);string& operator+=(char ch);string& operator+=(const char* str);void insert(size_t pos, char ch);void insert(size_t pos, const char* str);void erase(size_t pos, size_t len = npos);private://初始化成员char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;static const size_t npos;};
}

3.2 string.cpp

void reserve(size_t n);

    void string::reserve(size_t n) {
        if (n > _capacity) {
            char* tmp = new char [n + 1];//多开辟一个字节存放'\0';

           //字符串拷贝
            strcpy(tmp, _str);
            delete[] _str;//注意[],因为new[],所以要delete[]与之匹配

            _str = tmp;
            _capacity = n;//_capacity不包含'\0';
        }
    }

void push_back(char ch);(插入字符)

void string::push_back(char ch) {

//插入之前,得判断是否剩余空间存放字符
    if (_size == _capacity) {

//三目操作符
        reserve(_capacity == 0 ? 4 : _capacity * 2);
    }
    _str[_size] = ch;
    ++_size;

//因为_str[_size] = ch;覆盖了原存在的'\0';
    _str[_size] = '\0';
}

void append(const char* str);

void string::append(const char* str) {
    size_t len = strlen(str);
    if (_size + len > _capacity)
    {
        // 大于2倍,需要多少开多少,小于2倍按2倍扩
        reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);

    }
    strcpy(_str + _size, str);//在_str + _size的位置后插入str;
    _size += len;
}

string& operator+=(char ch);

string& string::operator+=(char ch) {
    push_back(ch);
    return *this;
}

string& operator+=(const char* str);

string& string::operator+=(const char* str)
 {
      append(str);
      return *this;
 }

void insert(size_t pos, char ch);

void string::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[end] = ch;
    _size++;
}

void insert(size_t pos, const char* str);

void string::insert(size_t pos, const char* str) {

    assert(pos <= _size);

    size_t len = strlen(str);
    if (_size + len > _capacity)
    {
        // 大于2倍,需要多少开多少,小于2倍按2倍扩
        reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
    }

    size_t end = _size + len;

   //找到目标位置的首个字符位置,移动,从前向后覆盖
    while (end > pos + len - 1)
    {
        _str[end] = _str[end - len];
        --end;
    }

   //覆盖目标位置

    for (size_t i = 0; i < len; i++) {
        _str[i + pos] = str[i];
    }

    _size += len;
}

void erase(size_t pos, size_t len = npos)默认设定为-1;

(注意:不要省略npos)

    void string::erase(size_t pos, size_t len ) {
        assert(pos <= _size);
        size_t end = pos;
        while (end < pos + len - 1) {
            _str[end] = _str[end + len];
            --end;
        }
        _size -= len;
    }

相关文章:

  • 关于若依前端界面缓存问题
  • 【Tomcat】常见面试题整理 共34题
  • Docker本地部署Chatbot Ollama搭建AI聊天机器人并实现远程交互
  • 如何在 macOS 上恢复未保存的 Excel 文件 – 文件恢复的最佳方法
  • IP地址与智能家居能够碰撞出什么样的火花呢?
  • more、less 命令:阅读文本
  • 记录一次排查sql server 服务调用异常的问题
  • C++:fstream类中seekg()/seekp()与tellg()/tellp()的用法详解
  • 等保测评中的密码学应用分析
  • 基于CentOS7上安装MicroK8s(最小生产的 Kubernetes)
  • uni-app - - - - -vue3使用i18n配置国际化语言
  • Qt系统相关——事件
  • 东华大学《2020年+2022年824自动控制原理真题》 (完整版)
  • 【Android】页面启动耗时统计流程梳理
  • Git从了解到操作
  • 《网管员必读——网络组建》(第2版)电子课件下载
  • Android开源项目规范总结
  • Consul Config 使用Git做版本控制的实现
  • css属性的继承、初识值、计算值、当前值、应用值
  • Druid 在有赞的实践
  • October CMS - 快速入门 9 Images And Galleries
  • RxJS: 简单入门
  • session共享问题解决方案
  • 从零到一:用Phaser.js写意地开发小游戏(Chapter 3 - 加载游戏资源)
  • 服务器之间,相同帐号,实现免密钥登录
  • 干货 | 以太坊Mist负责人教你建立无服务器应用
  • 你真的知道 == 和 equals 的区别吗?
  • 前端之React实战:创建跨平台的项目架构
  • 算法系列——算法入门之递归分而治之思想的实现
  • 网络应用优化——时延与带宽
  • 微信小程序实战练习(仿五洲到家微信版)
  • 学习JavaScript数据结构与算法 — 树
  • 说说我为什么看好Spring Cloud Alibaba
  • ​香农与信息论三大定律
  • #13 yum、编译安装与sed命令的使用
  • #Linux杂记--将Python3的源码编译为.so文件方法与Linux环境下的交叉编译方法
  • #window11设置系统变量#
  • #常见电池型号介绍 常见电池尺寸是多少【详解】
  • $GOPATH/go.mod exists but should not goland
  • ( )的作用是将计算机中的信息传送给用户,计算机应用基础 吉大15春学期《计算机应用基础》在线作业二及答案...
  • (33)STM32——485实验笔记
  • (C语言)fgets与fputs函数详解
  • (javascript)再说document.body.scrollTop的使用问题
  • (Oracle)SQL优化基础(三):看懂执行计划顺序
  • (pojstep1.3.1)1017(构造法模拟)
  • (二)构建dubbo分布式平台-平台功能导图
  • (排序详解之 堆排序)
  • (数位dp) 算法竞赛入门到进阶 书本题集
  • (译)2019年前端性能优化清单 — 下篇
  • (转)mysql使用Navicat 导出和导入数据库
  • .“空心村”成因分析及解决对策122344
  • .bat批处理出现中文乱码的情况
  • .net core 6 redis操作类
  • .NET Micro Framework初体验(二)
  • .Net OpenCVSharp生成灰度图和二值图