百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

PoEdu培训第四课-C++之STL c++ strpbrk

bigegpt 2024-10-31 12:18 6 浏览

C++是由C、object、STL以及泛型等四大块组成的C++语言联盟。下面我们就简单的了解以下STL中的string类。

我们使用的所有库函数都是编译器帮我们实现的,编译器根据C/C++或者其它语言的标准实现相应的库函数。所以有可能导致内个编译器实现的路径不同(版本不同)。

在C语言中提供的都是函数,而在C++中提供的将不再是函数了,而是变成类了。

需要特别注意的是

#include <string.h> // C语言的字符串操作函数头文件
#include <cstring> // C语言中符合C++标准的字符串操作函数
#include <string> // C++的STL可编程字符串头文件 // string和string.h这两个头文件是完全不同的
using std::string; // 我们可以指定只导入string类,这样避免了命名空间的污染。

我们先看一下什么是模板函数(泛型)

模板是一个自推倒类型,将会根据给出来的类型自动推倒。

比如我们的函数是支持重载的,我们的函数可以放入不同的类型,只要实现了一些相关类型的参数,它就能根据我们编写的这些函数自动匹配,但是这些重载函数需要写很多个才能满足我们的要求,如果我们使用模板,那么就可以用一个模板函数来实现。这个时候就是我们所说的模板编程或者泛型编程。

下面我们来看一下模板编程的一个交换例子

#include <string>
#include <iostream>

using std::string;

template <typename T> // 此时的 T 就相当于一个占位符
void Swap(T &lhs, T &rhs)
{
 T temp;
 temp = lhs;
 lhs = rhs;
 rhs = temp;
}

int main()
{
 char c1 = 'A', c2 = 'B';
 std::cout << "交换前:" << c1 << " " << c2 << std::endl;
 Swap(c1, c2);
 std::cout << "交换后:" << c1 << " " << c2 << std::endl << std::endl;

 int i1 = 1, i2 = 2;
 std::cout << "交换前:" << i1 << " " << i2 << std::endl;
 Swap(i1, i2);
 std::cout << "交换后:" << i1 << " " << i2 << std::endl << std::endl;

 double d1 = 1.1, d2 = 2.2;
 std::cout << "交换前:" << d1 << " " << d2 << std::endl;
 Swap(d1, d2);
 std::cout << "交换后:" << d1 << " " << d2 << std::endl << std::endl;

 return 0;
}

运行结果如下

我们只写一个模板函数就解决了以前要写好多个重载函数的问题,这是不是很高大上?这里看起来我们调用的是同一个函数,但是事实上是不是呢?我们一起来看一下反汇编吧,来探个究竟。

从反汇编上我们可以看出,看起来我们调用的同一个函数,但是事实上调用的函数地址是不一样的,这几个函数地址是编译器自动帮我们生成的,在功能上等同于帮我们自动生成了重载函数。

所谓的泛型,我们可以的鼻祖就是void*,它是一个内存级的交换,但是C++中的一些特性,使得我们不仅仅可以做一些内存级的交换,因为C++相对于C语言多了运算符重载,那么我们就可以通过运算符对我们的语句进行再次的抽象,所以我们能达到和void*效果一样的功能。

我们再举一个例子,来说明一些问题,请看下面代码:

#include <string>
#include <iostream>

using std::string;

template <typename T> // 此时的 T 就相当于一个占位符
void Swap(T &lhs, T &rhs)
{
 T temp = lhs;
 lhs += rhs;
 rhs += temp;
}

int main()
{
 char c1 = 'A', c2 = 'B';
 std::cout << "交换前:" << c1 << " " << c2 << std::endl;
 Swap(c1, c2);
 std::cout << "交换后:" << c1 << " " << c2 << std::endl << std::endl;

 int i1 = 1, i2 = 2;
 std::cout << "交换前:" << i1 << " " << i2 << std::endl;
 Swap(i1, i2);
 std::cout << "交换后:" << i1 << " " << i2 << std::endl << std::endl;

 double d1 = 1.1, d2 = 2.2;
 std::cout << "交换前:" << d1 << " " << d2 << std::endl;
 Swap(d1, d2);
 std::cout << "交换后:" << d1 << " " << d2 << std::endl << std::endl;

 return 0;
}

此时编译还能通过,说明我们传递进去的不管是char、int还是double类型,它们都有+=运算符的重载。如果我们自己定义一个新的类型,但是我们不重载+=运算符,那么此时还能通过编译码?请看下面的代码:

#include <string>
#include <iostream>

using std::string;

class Demo
{
};

template <typename T> // 此时的 T 就相当于一个占位符
void Swap(T &lhs, T &rhs)
{
 T temp = lhs;
 lhs += rhs;
 rhs += temp;
}

int main()
{
 char c1 = 'A', c2 = 'B';
 std::cout << "交换前:" << c1 << " " << c2 << std::endl;
 Swap(c1, c2);
 std::cout << "交换后:" << c1 << " " << c2 << std::endl << std::endl;

 int i1 = 1, i2 = 2;
 std::cout << "交换前:" << i1 << " " << i2 << std::endl;
 Swap(i1, i2);
 std::cout << "交换后:" << i1 << " " << i2 << std::endl << std::endl;

 double d1 = 1.1, d2 = 2.2;
 std::cout << "交换前:" << d1 << " " << d2 << std::endl;
 Swap(d1, d2);
 std::cout << "交换后:" << d1 << " " << d2 << std::endl << std::endl;

 Demo demo1, demo2;
 Swap(demo1, demo2);

 return 0;
}

此时我们再编译的时候,就会出现下面的错误

这是因为我们的自定义类中没有重载+=运算符,解决的办法很简单,我们自己实现一个+=运算符就ok了。

#include <string>
#include <iostream>

using std::string;

class Demo
{
public:
 Demo &operator+=(const Demo &other)
 {
 return *this;
 }
};

template <typename T> // 此时的 T 就相当于一个占位符
void Swap(T &lhs, T &rhs)
{
 T temp = lhs;
 lhs += rhs;
 rhs += temp;
}

int main()
{
 char c1 = 'A', c2 = 'B';
 std::cout << "交换前:" << c1 << " " << c2 << std::endl;
 Swap(c1, c2);
 std::cout << "交换后:" << c1 << " " << c2 << std::endl << std::endl;

 int i1 = 1, i2 = 2;
 std::cout << "交换前:" << i1 << " " << i2 << std::endl;
 Swap(i1, i2);
 std::cout << "交换后:" << i1 << " " << i2 << std::endl << std::endl;

 double d1 = 1.1, d2 = 2.2;
 std::cout << "交换前:" << d1 << " " << d2 << std::endl;
 Swap(d1, d2);
 std::cout << "交换后:" << d1 << " " << d2 << std::endl << std::endl;

 Demo demo1, demo2;
 Swap(demo1, demo2);

 return 0;
}

此时编译就能够顺利通过。

stl中的可变长字符串string类

它有以下的特性:

1. 可变长

它的基类有两个

basic_string<char>

basic_string<wchar_t>

其实,无论是char还是wchar_t都是由这两个基类来实现的。

那么string和wstring有什么区别呢?它们的差别在于编码集的不同,一个是窄字节(1byte),一个是宽字节(2byte),这两者只是字节上的不同,而并非编码上的不同。

typedef basic_string<char, char_traits<char>, allocator<char> >

string;

typedef basic_string<wchar_t, char_traits<wchar_t>, allocator<wchar_t> >

wstring;

截取固定长度,从头开始

string str(“123456”, 3);

取前n个字符进行构造的功能

如果我们截取的个数大于我们传入进去的字符串的长度时会怎样呢?会出错吗?我们来做一个实验看看就知道了,我给他传一个长度10进去,看看效果

这不是很好嘛!一点儿问题都没有,我们再给它的长度再大一点儿,换个100试试

这是什么?怎么会面跟了一行字符串,我们看一下监视窗口的str的值


从监视窗口来看,str确实是”123456”这个这个值啊,那么为什么我们用cout输出的时候,就会出现后面多余的字符串呢?我们再看一下其它的输出信息

从这个信息上可以看出,我们指定大小之后,编译器会给我们分配那么大的空间,但是超出我们给定的字符串长度的内容是怎么来的,我们看不出来,也先不去关心这个,最重要的是当我们使用str.c_str()这个方法时,就能正确的打印出”123456”,所以我们可以看出,string类实现的输入输出流跟我们想象的不一样。具体在哪儿不一样,我们以后再讨论。



但是问题又来了,跟我们上面出现的一样,当我们传入的数值大于我们传入的对象的长度会怎样呢?


从这个结果上来看,并没有给我们分配我们传入的大小,这一点我们就先记住吧,现在也解释不了太清楚,以后再解释吧!

string类的构造函数的一些总结,代码方式的总结:

#include <string>
#include <iostream>

using std::string;

int main()
{
 string str("123456789", 10); // 截取固定长度 从头开始
 //string str(10, 'A'); // 拿着后面相同的字符进行填充 10 个
 //string int
 string str1(str, 3); // 从str下标为 3 的位置开始截取,直到str的末尾

 //const char * int
 string str4("123455", 3); // 取前三个
 string str2(str, 3, 30000); // 从str下标为 3 的位置开始截取3个字符,不一定到末尾
 string str3("123455", 3, 3);// 先调用转换构造,生成一个临时对象,然后再调用上面的函数
 // 上面的一行代码等价于下面的两行代码
 // string temp("123455");
 // string(temp, 3, 3);
 std::cout << str << std::endl;
 std::cout << str1 << std::endl;
 std::cout << str2 << std::endl;
 std::cout << "str2.size():" << str2.size() << std::endl;
 std::cout << "str2.length():" << str2.length() << std::endl;
 std::cout << "str2.capacity():" << str2.capacity() << std::endl;
 std::cout << str3 << std::endl;

 return 0;
}

字符串的长度

#include <string>
#include <iostream>

using std::string;

int main()
{
 string str("123456789", 10); // 截取固定长度 从头开始
 // 需要注意的是,这里面传递的是一个迭代器
 string str1(str.begin() + 3, str.begin() + 5); // 如果使用C++的风格来截取字符串的话,推荐使用这种方式
 std::cout << str1 << std::endl;

 string str2(str, 3, str.length() - 8); // 如果使用C语言的风格来截取字符串的话,推荐使用这种方式
 std::cout << str2 << std::endl;

 return 0;
}

stl中的通用查找函数std::find函数的返回值是一个迭代器,而类中的专用的find函数的返回值是一个pos位置信息,所以在使用这个返回值的时候,需要判断一下是不是npos(无效的位置),

#include <string>
#include <iostream>
using std::string;
int main()
{
 string str;
 std::cout << str.max_size() << std::endl << std::endl; // 最最大值
 std::cout << str.capacity() << std::endl << std::endl; // 在不改变空间大小的情况下能存储的大小,是一个动态改变的
 std::cout << str.size() << std::endl;
 std::cout << str.length() << std::endl;
 std::cout << str.empty() << std::endl << std::endl; // true 为空 false为不空

 str = "1";
 std::cout << str.size() << std::endl;
 std::cout << str.length() << std::endl;
 std::cout << str.empty() << std::endl << std::endl;

 str = "12345678901234567890";;
 std::cout << str.size() << std::endl;
 std::cout << str.length() << std::endl;
 std::cout << str.capacity() << std::endl;
 std::cout << str.empty() << std::endl << std::endl;

 str.resize(100); // 重新分配大小
 std::cout << str.size() << std::endl;
 std::cout << str.length() << std::endl;
 std::cout << str.capacity() << std::endl;
 std::cout << str.empty() << std::endl << std::endl;

 str.resize(100, 'A');
 std::cout << str.size() << std::endl;
 std::cout << str.length() << std::endl;
 std::cout << str.capacity() << std::endl;
 std::cout << str.empty() << std::endl << std::endl;

 str = "";
 std::cout << str.size() << std::endl;
 std::cout << str.length() << std::endl;
 std::cout << str.empty() << std::endl << std::endl;

 return 0;
}

字符串的查找

查找字符

#include <string>
#include <iostream>

using std::string;

int main()
{
 string str("12345678");
 std::size_t pos = str.find('5'); // 返回要查找的字符的索引值
 std::cout << pos << std::endl;

 pos = str.find('9');
 std::cout << pos << std::endl;

 if (pos == std::string::npos)
 {
 std::cout << "未找到" << std::endl;
 }
 else
 {
 std::cout << pos << std::endl;
 }

 return 0;
}

查找字符串

需要补充的是,在查找字符的时候,我们也可以指定开始查找的位置

上面的都是find函数例子,string类中还有一个rfind的函数,它与find的功能是一模一样的,所不同的是find从前面查找,而rfind是从后面查找。以及find_first_of和find_last_of函数的用法都是一样的。

下面是关于find的总结:

#include <string>
#include <iostream>

using std::string;

int main()
{
 string str("12345678");
 std::size_t pos = str.find("45"); // 返回要查找的字符串的索引值

 if (pos == std::string::npos)
 {
 std::cout << "未找到" << std::endl;
 }
 else
 {
 std::cout << pos << std::endl;
 }

 std::cout << std::endl;

 //pos = str.find("78");
 //pos = str.find("78", 4); // 我们可以指定开始查找的位置 对于查找字符串和查找字符都是试用适用的
 pos = str.find('7', 4);

 pos = str.find("123488888", 0, 4); // 此时相当于在str中查找 "1234"这四个字符组成的字符串

 if (pos == std::string::npos)
 {
 std::cout << "未找到" << std::endl;
 }
 else
 {
 std::cout << pos << std::endl;
 }

 return 0;
}

下面两个函数比较有意思,我们重点体验一下

分别是find_first_not_of以及find_last_not_of两个函数

代码如下

#include <string>
#include <iostream>

using std::string;

int main()
{
 string str(" dhdhFIOWEHFIOjfoijewdiogjweOPJG POGJIOJS 8123456789 FBFKDJKVNDKVNKAHNCKOSAJNCKOVKDOIVJSDFIOKJ");
 std::size_t pos_begin = str.find_first_of("1234567890"); // 返回的是第10个元素,说明找的是
 // 当前字符串中任意一个字符不在str
 // 中的位置的结果
 std::size_t pos_end = str.find_first_not_of("1234567890", pos_begin);
 string num(str.begin() + pos_begin, str.begin() + pos_end);
 std::cout << num << std::endl;
 // 我们这样做的目的是很方便的就找到了中间的数字部分

 return 0;
}

下面是字符串类中的比较函数的使用

at函数和下标[]运算符的区别

#include <string>
#include <iostream>

using std::string;

int main()
{
 string str(" dhdhFIOWEHFIOjfoijewdiogjweOPJG POGJIOJS 8123456789 FBFKDJKVNDKVNKAHNCKOSAJNCKOVKDOIVJSDFIOKJ");
 std::size_t pos_begin = str.find_first_of("1234567890"); // 返回的是第10个元素,说明找的是
 // 当前字符串中任意一个字符不在str
 // 中的位置的结果
 std::size_t pos_end = str.find_first_not_of("1234567890", pos_begin);
 //string num(str.begin() + pos_begin, str.begin() + pos_end);
 // 我们还可以使用substr函数来提取字符串,它和上一行的代码实现的功能是一样的
 string num = str.substr(pos_begin, pos_end - pos_begin);
// std::cout << num << std::endl;

 num = str;

 //std::cout << num.at(4) << std::endl;
 //std::cout << num[4] << std::endl;
 // 当下标没有越界的情况下,这两者的功能是一样的,但是如果越界的话就会变得不同了

 std::cout << num[150] << std::endl; // 而这种情况会直接将程序退出,没有异常检测
 std::cout << num.at(150) << std::endl; // 此时在这里会抛出一个异常
 // 不知道怎么回事,我的电脑始终没有出现程序崩溃的现象,先把这个代码保存下来吧,以后再试试

 return 0;
}


字符串类中的c_str()和data()函数

#include <string>
#include <iostream>

using std::string;

void Foo(char *str)
{
}

int main()
{
 string str("POGJIOJS 8123456789 ");

 Foo(const_cast<char *>(str.c_str()));

 return 0;
}

最后,如果你想学C/C++可以私信小编“01”获取素材资料以及开发工具和听课权限哦!

相关推荐

悠悠万事,吃饭为大(悠悠万事吃饭为大,什么意思)

新媒体编辑:杜岷赵蕾初审:程秀娟审核:汤小俊审签:周星...

高铁扒门事件升级版!婚宴上‘冲喜’老人团:我们抢的是社会资源

凌晨两点改方案时,突然收到婚庆团队发来的视频——胶东某酒店宴会厅,三个穿大红棉袄的中年妇女跟敢死队似的往前冲,眼瞅着就要扑到新娘的高额钻石项链上。要不是门口小伙及时阻拦,这婚礼造型团队熬了三个月的方案...

微服务架构实战:商家管理后台与sso设计,SSO客户端设计

SSO客户端设计下面通过模块merchant-security对SSO客户端安全认证部分的实现进行封装,以便各个接入SSO的客户端应用进行引用。安全认证的项目管理配置SSO客户端安全认证的项目管理使...

还在为 Spring Boot 配置类加载机制困惑?一文为你彻底解惑

在当今微服务架构盛行、项目复杂度不断攀升的开发环境下,SpringBoot作为Java后端开发的主流框架,无疑是我们手中的得力武器。然而,当我们在享受其自动配置带来的便捷时,是否曾被配置类加载...

Seata源码—6.Seata AT模式的数据源代理二

大纲1.Seata的Resource资源接口源码2.Seata数据源连接池代理的实现源码3.Client向Server发起注册RM的源码4.Client向Server注册RM时的交互源码5.数据源连接...

30分钟了解K8S(30分钟了解微积分)

微服务演进方向o面向分布式设计(Distribution):容器、微服务、API驱动的开发;o面向配置设计(Configuration):一个镜像,多个环境配置;o面向韧性设计(Resista...

SpringBoot条件化配置(@Conditional)全面解析与实战指南

一、条件化配置基础概念1.1什么是条件化配置条件化配置是Spring框架提供的一种基于特定条件来决定是否注册Bean或加载配置的机制。在SpringBoot中,这一机制通过@Conditional...

一招解决所有依赖冲突(克服依赖)

背景介绍最近遇到了这样一个问题,我们有一个jar包common-tool,作为基础工具包,被各个项目在引用。突然某一天发现日志很多报错。一看是NoSuchMethodError,意思是Dis...

你读过Mybatis的源码?说说它用到了几种设计模式

学习设计模式时,很多人都有类似的困扰——明明概念背得滚瓜烂熟,一到写代码就完全想不起来怎么用。就像学了一堆游泳技巧,却从没下过水实践,很难真正掌握。其实理解一个知识点,就像看立体模型,单角度观察总...

golang对接阿里云私有Bucket上传图片、授权访问图片

1、为什么要设置私有bucket公共读写:互联网上任何用户都可以对该Bucket内的文件进行访问,并且向该Bucket写入数据。这有可能造成您数据的外泄以及费用激增,若被人恶意写入违法信息还可...

spring中的资源的加载(spring加载原理)

最近在网上看到有人问@ContextConfiguration("classpath:/bean.xml")中除了classpath这种还有其他的写法么,看他的意思是想从本地文件...

Android资源使用(android资源文件)

Android资源管理机制在Android的开发中,需要使用到各式各样的资源,这些资源往往是一些静态资源,比如位图,颜色,布局定义,用户界面使用到的字符串,动画等。这些资源统统放在项目的res/独立子...

如何深度理解mybatis?(如何深度理解康乐服务质量管理的5个维度)

深度自定义mybatis回顾mybatis的操作的核心步骤编写核心类SqlSessionFacotryBuild进行解析配置文件深度分析解析SqlSessionFacotryBuild干的核心工作编写...

@Autowired与@Resource原理知识点详解

springIOCAOP的不多做赘述了,说下IOC:SpringIOC解决的是对象管理和对象依赖的问题,IOC容器可以理解为一个对象工厂,我们都把该对象交给工厂,工厂管理这些对象的创建以及依赖关系...

java的redis连接工具篇(java redis client)

在Java里,有不少用于连接Redis的工具,下面为你介绍一些主流的工具及其特点:JedisJedis是Redis官方推荐的Java连接工具,它提供了全面的Redis命令支持,且...