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

C++——string的简单使用与深浅拷贝的理解(建议收藏)

目录

string的常用接口说明

string类对象的常见构造

string类对象的容量操作

深浅拷贝 

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

三种遍历方式与迭代器的使用

string类对象的修改操作

尾部插入

取出文件名的后缀

扩容问题

 头部插入与中间插入

删除

         比较 

字符串与整型之间的互相转换

相关OJ题目


 

string的常用接口说明

string类对象的常见构造

void Teststring()
{
    string s1;//构造空的string类对象s1
    string s2("hello world");//用C格式字符串构造string类对象s2
    string s3(s2);//拷贝构造s3
    
    
}

string类对象的容量操作

void Teststring1()
{
	string s("hello world");
	cout << s.size() << endl;//计算字符串的有效长度
	cout << s.length() << endl;//计算字符串的有效长度
	cout << s.capacity() << endl;//计算字符串的空间总大小
	cout << s << endl;//输出字符串
	s.clear();//将s中的字符串清空,只清空数据不改变空间
	s.resize(10, 'a');//将s中有效字符个数增加到10个,多出位置用'a'进行填充
	s.resize(15);//将s中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充
	s.resize(5);// 将s中有效字符个数缩小到5个
	s.reserve(100);//与resize相同只不过不对其他多出位置补充'\0'
}

注意:

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, char c)用字符c来填充多出的 元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大 小,如果是将元素个数减少,底层空间总大小不变。

4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于 string的底层空间总大小时,reserver不会改变容量大小。

深浅拷贝 

拷贝构造

#define _CRT_SECURE_NO_WARNINGS
#include<assert.h>
#include<iostream>
#include<string>
class string
{
public:
	string(const char* str)
		//:_str(new char[strlen(str)+1])//+1算\0
		:_size(strlen(str))
	{
		_str = (new char[strlen(str) + 1]);//+1算\0
		strcpy(_str, str);
	}
		string& operator=(const string & s)
		{
			//if (this != &s)
			//{
			//	delete[] this->_str;//把s1的空间给释放了
			//	//delete[] _str;
			//	_str = new char[strlen(s._str) + 1];
			//	//给s1开辟一个与s3空间相同的空间
			//	strcpy(_str, s._str);
			//	//将s3的内容赋值给s1
			//}
			if (this != &s)
			{
				char* tmp = new char[strlen(s._str) + 1];
				strcpy(tmp, s._str);
				delete[] _str;
				_str = tmp;
			}
			return *this;
		}
		~string()
		{
			delete[]_str;
		}

	private:
		char* _str;
		size_t _size;
		size_t _capacity;
};
void test_string1()
{
	string s1("hello world");
	string s2(s1);

	string s3("sort");
	s1 = s3;
}
int main()
{
	test_string1();
	return 0;
}

我们运行以上代码可以发现程序发生错误

这是什么原因造成的呢,我们画图来更直观的看一下。

 

 由上图我们可以发现,我们由于类中并没有自己生成拷贝构造函数,那么他们会调用类中默认生成的拷贝构造函数。

默认生成的拷贝构造函数说白了也就是让s2也使用s1的空间,说的直白一点就是只是把s1的名字给更改了一下,但是呢,我们如果去查看s2中的数据的时候其实并不会发生错误,但是我们要知道,类中会有默认生成的析构函数会对已经创建的变量进行释放,当我们释放空间时就会出错,因为s1和s2指向的是同一块空间,通过上图右边的监视信息我们也可以发现他们拥有一样的地址,一旦释放他们其中某一个,另一个再进行释放的时候就会报错!

那么我们如何解决这个问题呢,如图所示:

 我们可以给s2也申请一个空间,然后将s1的值拷贝给s2,那么他们在析构的时候就不会报错了。

做法就是:自己实现一个拷贝构造函数,在这个拷贝构造函数中我们为s2开辟空间。代码如下:

string(const string& s)//这里传的是对象的别名
		//由于默认的拷贝构造只能进行浅拷贝
		//所以我们这里需要自己实现拷贝构造函数
		:_str(new char[strlen(s._str) + 1])
	{
		strcpy(_str, s._str);
	}

赋值操作

class string
{
public:
	string(const char* str)
		//:_str(new char[strlen(str)+1])//+1算\0
		:_size(strlen(str))
	{
		_str = (new char[strlen(str) + 1]);//+1算\0
		strcpy(_str, str);
	}	
		~string()
		{
			delete[]_str;
		}

	private:
		char* _str;
		size_t _size;
		size_t _capacity;
};
void test_string1()
{
	string s1("hello world");
	string s3("sort");
	s1 = s3;
}
int main()
{
	test_string1();
	return 0;
}

 同样我们运行上述代码会发现报错了

 这里与拷贝构造的原理其实是一样的,我们在进行赋值操作的时候如果不写的话会自动调用默认生成的函数,默认生成的函数进行的同样是浅拷贝,如图所示:

 那么我们也可以根据拷贝构造函数来推断出,当进行析构的时候肯定会发生报错。

 跟拷贝构造函数一样我们需要自己实现一个赋值函数,那么应该如何实现呢?

我们可以发现,无论拷贝还是赋值,重中之重都是两个变量指向了 同一个空间然后析构的时候发生错误导致的,所以我们不妨再给s1开辟一个空间,然后将s3的内容给拷贝过去,然后再释放s1原本的空间,这样不就可以实现深拷贝了。如图所示:

代码如下:

string& operator=(const string& s)
{
	//if (this != &s)
	//{
	//	delete[] this->_str;//把s1的空间给释放了
	//	//delete[] _str;
	//	_str = new char[strlen(s._str) + 1];
	//	//给s1开辟一个与s3空间相同的空间
	//	strcpy(_str, s._str);
	//	//将s3的内容赋值给s1
	//}
	if (this != &s)
	{
		char* tmp = new char[strlen(s._str) + 1];
		strcpy(tmp, s._str);
		delete[] _str;
		_str = tmp;
	}
	return *this;
}

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

operator[] (重 点):

返回pos位置的字符,const string类对象调用。

begin+ end:

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

rbegin + rend:

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

范围for:

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

注意:

由于operator[]是const类型的,所以不能修改。

三种遍历方式与迭代器的使用

void test_string1()
{
	string s1("hello world");

	// 遍历+修改
	// 方式1:下标+[]
	for (size_t i = 0; i < s1.size(); i++)
	{
		s1[i] += 1;
	}
	for (size_t i = 0; i < s1.size(); i++)
	{
		cout << s1[i] << " ";
	}
	cout << endl;
	//方式2:迭代器
	string::iterator it = s1.begin();
	//const_iterator begin()const;
	while (it != s1.end())
	//while (it < s1.end());
	//可以这样用因为string底层是数组但是不建议这样用
	{
		*it -= 1;//类似于指针可以对解引用进行操作
		it++;
	}
	it = s1.begin();
	//类似于指针 it此时指向s1的第一个字符
	while (it != s1.end())
	{
		cout << *it << " ";//与指针相同是解引用
		it++;//it向后遍历
	}
	cout << endl;
	// 方式3:范围for
	// 范围for实质上就是花架子
	// 底层:在编译的时候自动被替换为迭代器
	//for(char&e : s1)
	for (auto& e : s1)
	//这里的auto可以根据s1的类型自动推
	//取出一个个s1中的数值赋值给e
	//这里e就是s1的拷贝
	//改变e就不会影响s1
	//所以应该加&使e是s1的别名
	{
		e -= 1;
	}
	for (auto e : s1)//将s1中的数值一个个赋值给e
	//自动往后迭代,自动判断结束
	{
		cout << e << " ";
	}
	cout << endl;
}

string类对象的修改操作

尾部插入

int main()
{
	string s1;
	s1.push_back('a');//在尾部插入单个字符
	s1.append("bcde");//在尾部插入一个字符串
	cout << s1 << endl;

	s1 += ' ';//在尾部插入单个字符
	s1 += "hello world";//在尾部插入一个字符串
	cout << s1 << endl;
	return 0;
}

取出文件名的后缀

void test_string4()
{
	string s("hello world");
	cout << s << endl;
	cout << s.c_str() << endl;//返回C格式字符串

	string file("test.txt");
	FILE* fout = fopen(file.c_str(), "w");//可读

	// 要求你取出文件的后缀名
	size_t pos = file.find('.');//记录'.'的位置
	if (pos != string::npos)//
	{
		//string suffix = file.substr(pos, file.size() - pos);
		string suffix = file.substr(pos);//第二个参数不写默认截断到最后
		cout << suffix << endl;
	}

	string file("test.txt.zip");
	FILE* fout = fopen(file.c_str(), "w");

	// 要求你取出文件的后缀名
	size_t pos = file.rfind('.');
	//由于可能有多个'.'所以我们使用rfind从后寻找最后一个'.'的位置
	if (pos != string::npos)
	{
		//string suffix = file.substr(pos, file.size() - pos);
		string suffix = file.substr(pos);
		cout << suffix << endl;
	}

	// http://www.cplusplus.com/reference/string/string/
	// https://translate.google.cn/?sl=zh-CN&tl=en&text=%E5%90%8E%E7%BC%80&op=translate
	string url("http://www.cplusplus.com/reference/string/string/find/");
	size_t pos1 = url.find(':');//同样去寻找':'的位置
	string protocol = url.substr(0, pos1 - 0);
	cout << protocol << endl;

	size_t pos2 = url.find('/', pos1 + 3);
	string domain = url.substr(pos1 + 3, pos2 - (pos1 + 3));
	cout << domain << endl;

	string uri = url.substr(pos2 + 1);
	cout << uri << endl;
}

注意:

1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般 情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。

2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。

补充:

getline:获取一行字符(区别于cin的是它可以获取空格以\0作为结束标志)

扩容问题

通过测试我们可以发现,在vs下容量以1.5倍进行扩容,然而Linux下以2倍进行扩容。

void TestPushBack()
{
	string s;
	s.reserve(1000);
	//申请至少能存储1000个字符的空间
	size_t sz = s.capacity();
	cout << "capacity changed: " << sz << '\n';
	//先打印一遍原始容量
	cout << "making s grow:\n";
	for (int i = 0; i < 100; ++i)
	{
		//s.push_back('c');
		s += 'c';
		if (sz != s.capacity())
		//只要sz!=s.capacity时说明容量已经增加了
		{
			sz = s.capacity();
			//记录新的容量
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

vs下:

 Linux下:

 头部插入与中间插入

void test_string5()
{
	string s("hello world");
	s += ' ';
	s += "!!!!";
	cout << endl;

	//头插 效率,O(N),尽量少用
	s.insert(0, 1, 'x');
	s.insert(s.begin(), 'y');
	s.insert(0,"test");
	cout << s << endl;
	//中间位置插入
	s.insert(4, "&&&&&");
	cout << s << endl;
}

删除

void test_string6()
{
	string s("hello world");
	//尽量少用头部和中间删除,因为要挪动数据,效率低
	//头插
	s.erase(0, 1);
	//尾插
	s.erase(s.size() - 1, 1);
	cout << s << endl;

	s.erase(3);
	cout << s << endl;
}

比较 

void test_string7()
{
	string s1("hello world");
	string s2("string");
	//比较的原则是acill码值
	cout << (s1 < s2) << endl;
	cout << ("hhhhh" < s2) << endl;
	cout << (s1 < string("hhhhh")) << endl;

}

字符串与整型之间的互相转换

void test_string8()
{
	int val = stoi("1234");
	//字符串转整型
	cout << val << endl;
	
	string str = to_string(3.1415);
	//整型转字符串
	cout << str << endl;

}

相关OJ题目

字符串相加

把字符串转换成整数_牛客题霸_牛客网

class Solution {
public:
    string addStrings(string num1, string num2) 
    {
        int next=0;
        int end1=num1.size()-1,end2=num2.size()-1;
        string str;
        while(end1>=0||end2>=0)
        {
            int x1=0;
            //如果这段字符结束那么自动补充为0
            if(end1>=0)
            {
                x1=num1[end1]-'0';
                end1--;
            }
            int x2=0;
            //如果这段字符结束那么自动补充为0
            if(end2>=0)
            {
                x2=num2[end2]-'0';
                end2--;
            }
            int newbit=x1+x2+next;
            //计算相加后的位
            if(newbit>9)
            {
                newbit-=10;
                next=1;
            }
            else
            {
                next=0;
            }
            str.insert(str.begin(),newbit+'0');
            
        }
        if(next==1)
            {
                str.insert(str.begin(),'1');
            }
        return str;
    }
};

把字符串转换为整数

把字符串转换成整数_牛客题霸_牛客网

bool isnum(char c)
{
    if(c>='1'&&c<='9')
    return true;
    else
    return false;
}
class Solution {
public:
    int StrToInt(string str) {
        int begin=0;
        int flag=0;
        if(str[0]=='+')
        {
            begin++;
        }
        else if(str[0] == '-' )
        {
            begin++;
            flag=1;
        }
        int size=str.size();
        int longint=0;
        for(int i=begin;i<str.size();i++)
        {
            if(!isnum(str[i]))
            {
                return 0;
            }
            else
            {
                if(longint==0)
                longint=str[i]-'0';
                else
                {
                    int nextbit=str[i]-'0';
                    longint=longint*10+nextbit;
                }
            }

        }
        if(flag==0)
        return longint;
        else
        return -longint;
    }

};

相关文章:

  • BGP综合实验
  • Day4——两两交换链表节点、删除链表第n个绩点、链表相交、环形链表||
  • YOLOv5实现佩戴安全帽检测和识别(含佩戴安全帽数据集+训练代码)
  • H.264 入门篇 - 10 (帧间预测 - 参考帧列表修改/重排)
  • 第三章Linux环境基础开发工具使用(yum+rzsz+vim+g++和gcc+gdb+make和Makefile+进度条+git)
  • 博客系统数据库设计与实现之修改(java)
  • django-设置X-Frame-Options响应头防止点击劫持攻击
  • PostgreSQL自增主键的用法以及在mybatis中的使用
  • app逆向(10)| APP的加固与脱壳
  • 解析IEC 61850通信规约
  • 了解Selenium,看这一篇够了
  • 你就想这样一辈子躺平,还是改变这个世界?
  • Xmake基础----自定义脚本介绍
  • Python 的Tkinter包系列之二:窗口菜单
  • 基于Vue3+Go本地视频管理与播放系统设计与实现
  • 【Amaple教程】5. 插件
  • 10个确保微服务与容器安全的最佳实践
  • Angular4 模板式表单用法以及验证
  • input实现文字超出省略号功能
  • JavaScript 是如何工作的:WebRTC 和对等网络的机制!
  • Mocha测试初探
  • MySQL Access denied for user 'root'@'localhost' 解决方法
  • MySQL常见的两种存储引擎:MyISAM与InnoDB的爱恨情仇
  • python3 使用 asyncio 代替线程
  • Shadow DOM 内部构造及如何构建独立组件
  • SpiderData 2019年2月25日 DApp数据排行榜
  • vagrant 添加本地 box 安装 laravel homestead
  • Xmanager 远程桌面 CentOS 7
  • 代理模式
  • 基于Javascript, Springboot的管理系统报表查询页面代码设计
  • 漂亮刷新控件-iOS
  • 前端设计模式
  • 思考 CSS 架构
  • 小程序button引导用户授权
  • 验证码识别技术——15分钟带你突破各种复杂不定长验证码
  • #stm32驱动外设模块总结w5500模块
  • #WEB前端(HTML属性)
  • (+4)2.2UML建模图
  • (10)STL算法之搜索(二) 二分查找
  • (4)通过调用hadoop的java api实现本地文件上传到hadoop文件系统上
  • (day 12)JavaScript学习笔记(数组3)
  • (HAL)STM32F103C6T8——软件模拟I2C驱动0.96寸OLED屏幕
  • (poj1.3.2)1791(构造法模拟)
  • (Redis使用系列) Springboot 在redis中使用BloomFilter布隆过滤器机制 六
  • (附源码)springboot工单管理系统 毕业设计 964158
  • (附源码)ssm航空客运订票系统 毕业设计 141612
  • (附源码)ssm学生管理系统 毕业设计 141543
  • (附源码)计算机毕业设计ssm基于B_S的汽车售后服务管理系统
  • (附源码)计算机毕业设计SSM疫情社区管理系统
  • .Net 6.0 处理跨域的方式
  • .NET Conf 2023 回顾 – 庆祝社区、创新和 .NET 8 的发布
  • .net core 实现redis分片_基于 Redis 的分布式任务调度框架 earth-frost
  • .NET Framework杂记
  • .net mvc 获取url中controller和action
  • .NET6实现破解Modbus poll点表配置文件