C++String的学习

news2025/7/27 5:30:56

1、C语言中的字符串

C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想(即面向对象编程(Object-Oriented Programming)),而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

String类题目,字符串相加

2、标准库中的String类

2.1string类的了解

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

2.2 auto和范围for

auto关键字:
1.在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
2.用auto声明指针类型时,用auto和auto*没有任何区别但用auto声明引用类型时则必须加&
3.当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
4.auto不能作为函数的参数,可以做返回值,但是建议谨慎使用
5.auto不能直接用来声明数组

#include<iostream>
using namespace std;
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;
	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;
}

auto 是 C++ 中提高代码简洁性和灵活性的重要工具
例如:

#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
	std::map<std::string, std::string> dict = { { "apple", "苹果" },{ "orange",
	"橙子" }, {"pear","梨"} };
	// auto的用武之地
	//std::map<std::string, std::string>::iterator it = dict.begin();
	//此刻的auto自动类型推导成std::map<std::string, std::string>::iterator
	auto it = dict.begin();
	while (it != dict.end())
	{
		cout << it->first << ":" << it->second << endl;
		++it;
	}
	return 0;
}

范围for:
1.,对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
2.范围for可以作用到数组和容器对象上进行遍历
3.范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。

#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
	int array[] = { 1, 2, 3, 4, 5 };
	// C++98的遍历
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
	{
		array[i] *= 2;
	}
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
	{
		cout << array[i] << endl;
	}
	// C++11的遍历
	for (auto& e : array)
		e *= 2;
		
	for (auto e : array)
		cout << e << " " << endl;
		
	string str("hello world");
	for (auto ch : str)
	{
		cout << ch << " ";
	}
	cout << endl;
	return 0;
}

2.3 string类的接口说明(常用)

1.string类对象的常见构造

constructor(构造)

函数名称功能说明
string()构造空的string类对象,即空字符串
string(const char& s )用C-string来构造string类对象
string(size_t n,char c)string类对象包含n个字符c
string(const string& s)拷贝构造函数
void Teststring()
{
	string s1; // 构造空的string类对象s1
	string s2("hello bit"); // 用C格式字符串构造string类对象s2
	string s3(s2); // 拷贝构造s3
}
2. string类对象的容量操作
函数名称功能说明
size(重点)返回字符串有效长度
length返回字符串有效长度(和size无差别)
capacity返回空间总大小
empty(重点)检测字符串是否为空串
clear(重点)清空有效字符
reserve(重点)为字符串预留空间
resize(重点)将有效字符的个数改成n个,多出的空间用字符c填充

注意:

  1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
  2. clear()只是将string中有效字符清空,不改变底层空间大小
  3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, charc)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
  4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。
3. string类对象的访问及遍历操作
函数名称功能说明
operator[]重点返回pos位置的字符,const string类对象调用
begin+endbegin获取第一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
rbegin+rend返回一个指向容器最后一个元素的反向迭代器+返回一个指向容器第一个元素之前位置的反向迭代器
范围forC++11支持更简洁的范围for的新遍历方式
4.string类对象的修改操作
函数名称功能说明
push_back在字符串结尾插入字符c
append在字符串后面追加一个字符
operator+= (重点)在字符串后追加字符串str
c_str(重点)返回c格式字符串
find+npos(重点)从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
rfind(重点)从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
substr(重点)在str中从pos位置开始,截取n个字符,然后将其返回

注意:

  1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c’三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
  2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。
5. string类非成员函数
函数名称功能说明
operator+尽量少用,因为传值返回,导致深拷贝效率低
operator>>(重点)输入运算符重载
operator<<(重点)输出运算符重载
geiline(重点)获取一行字符串
relational operators(重点)大小比较
6. vs和g++下string结构的说明

注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节
vs下string的结构
string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字符串的存储空间:
1.当字符串长度小于16时,使用内部固定的字符数组来存放
2.当字符串长度大于等于16时,从堆上开辟空间

union _Bxty
{ // storage for small buffer or pointer to larger one
	value_type _Buf[_BUF_SIZE];
	pointer _Ptr;
	char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;

这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。
其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量
最后:还有一个指针做一些其他事情。
故总共占16+4+4+4=28个字节。
在这里插入图片描述
g++下string的结构
g++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:
空间总大小
字符串有效长度
引用计数

struct _Rep_base
{
	size_type _M_length;
	size_type _M_capacity;
	_Atomic_word _M_refcount;
};

指向堆空间的指针,用来存储字符串。

3. string类的模拟实现

3.1string类的问题

// 为了和标准库区分,此处使用String
class String
{
public:
	/*String()
		:_str(new char[1])
	{*_str = '\0';}
	*/
	//String(const char* str = "\0") 错误示范
	//String(const char* str = nullptr) 错误示范
String(const char* str = "")
{
	// 构造String类对象时,如果传递nullptr指针,可以认为程序非
	if (nullptr == str)
	{
		assert(false);
		return;
	}
	_str = new char[strlen(str) + 1];
	strcpy(_str, str);
}
	~String()
{
	if (_str)
	{
		delete[] _str;
		_str = nullptr;
	}
}
private:
	char* _str;
};
// 测试
void TestString()
{
	String s1("hello bit!!!");
	String s2(s1);
}

在这里插入图片描述
说明:在上述代码中String类里面没有显示实现拷贝构造函数和赋值运算符重载,当s1取赋值s2的时候编译器就会走默认的拷贝构造函数,那么s1和s2就会指向同一个地址空间,当其中一个地址空间被销毁了之后,另外一个以为还存在,则会以为有效,当运行时就会出现报错,(在释放时同一块空间被释放多次而引起程序崩溃),这就是上述的浅拷贝。

3.2浅拷贝

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果在对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。

形象理解:可把对象成员看作不同 “数据盒子”,浅拷贝就像给新对象的盒子复制源对象盒子里的数据。若盒子存的是基本类型值,新盒子有独立值;若存的是指针(类似 “地址标签” ),新盒子就复制这个 “地址标签”,新、旧对象的指针会指向同一块堆内存区域 。比如有类 ClassA 包含指针成员 int* ptr,执行 ClassA obj2 = obj1;(obj1 已初始化 )的浅拷贝后,obj1.ptr 和 obj2.ptr 地址相同,都指向同一块堆内存。

#include <iostream>
class Test {
public:
    int* ptr;
    Test(int val) {
        ptr = new int(val); // 动态分配堆内存
    }
    ~Test() {
        delete ptr; // 析构时释放内存
    }
};
int main() {
    Test obj1(10);
    Test obj2 = obj1; // 浅拷贝,obj2.ptr 和 obj1.ptr 指向同一块内存
    // 此时 obj1 和 obj2 的析构函数都会去释放 ptr 指向的内存,会导致重复释放问题
    return 0;
}

3.3深拷贝

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。在这里插入图片描述
本质特征
深拷贝会创建全新的对象副本,并递归地复制原对象包含的所有数据内容,包括指针、引用等复杂成员指向的深层数据(如堆内存里的实际内容 )。新对象与原对象在内存中完全独立,修改一方的数据不会影响另一方。

3.3.1传统版写法的String类
```cpp
class String
{
public:
	String(const char* str = "")
	{
		// 构造String类对象时,如果传递nullptr指针,可以认为程序非
		if (nullptr == str)
		{
			assert(false);
			return;
		}
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}
	String(const String& s)
		: _str(new char[strlen(s._str) + 1])
	{
		strcpy(_str, s._str);
	}
	String& operator=(const String& s)
	{
		if (this != &s)
		{
			char* pStr = new char[strlen(s._str) + 1];
			strcpy(pStr, s._str);
			delete[] _str;
			_str = pStr;
		}
		return *this;
	}
	~String()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}
private:
	char* _str;
};
3.3.2现代版写法的String类
class String
{
public:
	String(const char* str = "")
	{
		if (nullptr == str)
		{
			assert(false);
			return;
		}
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}
	String(const String& s)
		: _str(nullptr)
	{
		String strTmp(s._str);
		swap(_str, strTmp._str);
	}
	// 对比下和上面的赋值那个实现比较好?
	String& operator=(String s)
	{
		swap(_str, s._str);
		return *this;
	}
	/*
	String& operator=(const String& s)
	{
		if(this != &s)
		{
			String strTmp(s);
			swap(_str, strTmp._str);
		}
		return *this;
	}
	*/
	~String()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}
private:
	char* _str;
};

3.4.写时拷贝

写时拷贝

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2399489.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

java day15 (数据库)

进入数据库的学习 DB 因为数据太多了&#xff0c;方便统一管理的软件 操作就不用改代码了&#xff0c;直接改数据库则可&#xff1b; 命令就是sql语句 这些都是关系型数据库&#xff0c;sql可以控制全部&#xff0c;至于具体的环境我以前就有安装过了&#xff1b; 理解&am…

多线程爬虫使用代理IP指南

多线程爬虫能有效提高工作效率&#xff0c;如果配合代理IP爬虫效率更上一层楼。作为常年使用爬虫做项目的人来说&#xff0c;选择优质的IP池子尤为重要&#xff0c;之前我讲过如果获取免费的代理ip搭建自己IP池&#xff0c;虽然免费但是IP可用率极低。 在多线程爬虫中使用代理I…

前端面试真题(第一集)

目录标题 1、跨域问题及解决方法同源策略生产环境解决方案开发环境解决方案其他解决方案 2、组件间通信方式Vue2中的组件通信方式Vue3中的组件通信方式通用注意事项 3、微信小程序生命周期微信小程序原生生命周期UniApp生命周期 4、微信小程序授权登录流程登录流程手机号获取 5…

TDengine 高级功能——流计算

简介 在时序数据的处理中&#xff0c;经常要对原始数据进行清洗、预处理&#xff0c;再使用时序数据库进行长久的储存&#xff0c;而且经常还需要使用原始的时序数据通过计算生成新的时序数据。在传统的时序数据解决方案中&#xff0c;常常需要部署 Kafka、Flink 等流处理系统…

05.字母异位词分组

题意理解 &#x1f9e0; 什么是“字母异位词”&#xff1f; 字母异位词是指由相同的字母组成&#xff0c;只是排列顺序不同的单词。 比如&#xff1a; "eat" 和 "tea" 是异位词&#xff0c;它们都包含 e、a 和 t。"ate" 也是它们的异位词。但…

Mac查看MySQL版本的命令

通过 Homebrew 查看&#xff08;如果是用 Homebrew 安装的&#xff09; brew info mysql 会显示你安装的版本、路径等信息。 你的终端输出显示&#xff1a;你并没有安装 MySQL&#xff0c;只是查询了 brew 中的 MySQL 安装信息。我们一起来看下重点&#xff1a; &#x1f9fe…

【.net core】【watercloud】树形组件combotree导入及调用

源码下载:combotree: 基于layui及zTree的树下拉框组件 链接中提供了组件的基本使用方法 框架修改内容 1.文件导入&#xff08;路径可更具自身情况自行设定&#xff09; 解压后将文件夹放在图示路径下&#xff0c;修改文件夹名称为combotree 2.设置路径&#xff08;设置layu…

2021 RoboCom 世界机器人开发者大赛-高职组(复赛)解题报告 | 珂学家

前言 题解 2021 RoboCom 世界机器人开发者大赛-高职组&#xff08;复赛&#xff09;解题报告。 模拟题为主&#xff0c;包含进制转换等等。 最后一题&#xff0c;是对向量/自定义类型&#xff0c;重定义小于操作符。 7-1 人工智能打招呼 分值: 15分 考察点: 分支判定&…

34.1STM32下的can总线实现知识(区分linux)_csdn

看过我之前的文章就知道&#xff0c;正点原子下的linux中CAN总线并没有讲的很明白&#xff0c;都是系统自带的&#xff01; 这里我找到江科大学长的can总线的讲解视频&#xff01; CAN总线入门教程-全面细致 面包板教学 多机通信_哔哩哔哩_bilibili 在这里我也会一步一步讲解CA…

2025年想冲网安方向,该考华为安全HCIE还是CISSP?

打算2025年往网络安全方向转&#xff0c;现在考证是不是来得及&#xff1f;考啥证&#xff1f; 说实话&#xff0c;网络安全这几年热得发烫&#xff0c;但热归热&#xff0c;入门门槛也不低&#xff0c;想进这个赛道&#xff0c;技术、项目经验、证书&#xff0c;缺一不可。 …

153页PPT麦肯锡咨询流程管理及企业五年发展布局构想与路径规划

麦肯锡咨询的流程管理以其高度结构化、数据驱动和结果导向的核心特点著称&#xff0c;旨在为客户提供清晰、可行且价值最大化的解决方案。其典型流程可概括为以下几个关键阶段&#xff1a;下载资料请查看文章中图片右下角信息 问题界定与结构化&#xff1a; 这是流程的基石。麦…

[特殊字符] 革命性AI提示词优化平台正式开源!

AI时代最强大的Prompt工程师已经到来&#xff01; 你是否还在为写不出高质量提示词而头疼&#xff1f;是否羡慕那些能够驾驭AI、让ChatGPT、Claude乖乖听话的"提示词大师"&#xff1f;今天&#xff0c;我们为你带来一个颠覆性的解决方案——TokenAI Auto-Prompt&…

我的概要设计模板(以图书管理系统为例)

一、总述 1.1 需求或目标 随着数字化阅读普及&#xff0c;传统图书馆管理方式效率低下、资源检索不便。为提升图书管理效率&#xff0c;方便读者借阅与查询&#xff0c;公司计划开发 “在线图书管理系统”&#xff0c;实现图书的电子化管理、快速检索、在线借阅等功能&#x…

DrissionPage爬虫包实战分享

一、爬虫 1.1 爬虫解释 爬虫简单的说就是模拟人的浏览器行为&#xff0c;简单的爬虫是request请求网页信息&#xff0c;然后对html数据进行解析得到自己需要的数据信息保存在本地。 1.2 爬虫的思路 # 1.发送请求 # 2.获取数据 # 3.解析数据 # 4.保存数据 1.3 爬虫工具 Dris…

iptables实战案例

目录 一、实验拓扑 二、网络规划 三、实验要求 四、环境准备 1.firewall &#xff08;1&#xff09;配置防火墙各大网卡IP并禁用 firewall和selinux &#xff08;2&#xff09;打开firewall路由转发 2.PC1&#xff08;内网&#xff09; &#xff08;1&#xff09;配置防…

Google AI 模式下的SEO革命:生成式搜索优化(GEO)与未来营销策略

一、搜索范式转变&#xff1a;从链接引导到答案交付 Google自2023年起逐步推出AI搜索功能&#xff0c;经历了SGE&#xff08;Search Generative Experience&#xff09;和Gemini阶段&#xff0c;最终在2025年全面上线了「AI Mode」搜索模式。与此同时&#xff0c;也保留了一种过…

SpringBoot中缓存@Cacheable出错

SpringBoot中使用Cacheable: 错误代码&#xff1a; Cacheable(value "FrontAdvertiseVOList", keyGenerator "cacheKey") Override public List<FrontAdvertiseVO> getFrontAdvertiseVOList(Integer count) {return this.list(Wrappers.<Adve…

iOS UIActivityViewController 组头处理

0x00 情形一 - (void)shareAction1 {// 当前 View 转成图片UIImage *image [self snapshotImage:self.view];NSArray *activityItems [image];UIActivityViewController *activityVC [[UIActivityViewController alloc] initWithActivityItems:activityItems applicationAc…

《TCP/IP 详解 卷1:协议》第3章:链路层

以太网和IEEE802局域网/城域网标准 IEEE802局域网/城域网标准 IEEE 802 是一组由 IEEE&#xff08;电气与电子工程师协会&#xff09;定义的局域网和城域网通信标准系列&#xff0c;涵盖了从物理层到链路层的多个网络技术。其中&#xff1a; IEEE 802.3 定义的是传统的以太网…

Elasticsearch从安装到实战、kibana安装以及自定义IK分词器/集成整合SpringBoot详细的教程(二)

package com.test.xulk.es.entity.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.test.xulk.es.entity.Hotel;public interface HotelMapper extends BaseMapper<Hotel> { }集成Springboot 项目里面 官方地址&#xff1a; Elasticsearch …