String(C++)

文章目录

  • 前言
  • 文档介绍
  • 经典题目讲解
    • HJ1 字符串最后一个单词的长度
  • 模拟实现
    • 框架
    • 构造函数
    • 析构函数
    • 迭代器
    • c_str()
    • 赋值
    • size()
    • capacity()
    • reserve
    • empty()
    • [ ]访问
    • front/back
    • push_back
    • append
    • operator+=
    • insert一个字符
    • insert一个字符串
    • erase
    • swap
    • find一个字符
    • find一个字符串
    • substr()
    • clear()
    • 拷贝
    • 赋值
    • operator<<
    • operator>>
    • 完整代码
  • string总体介绍
    • stoi/stod
    • to_string
    • Class instantiations
  • vs和g++下string结构的
  • 总结


前言

在本篇文章中,我们将会学习到string相关的内容,并且对部分容器进行模拟实现,了解底层原理。
vs下ctrl+f可进行搜索

文档介绍

string

  1. 字符串是表示字符序列的类
  2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作
    单字节字符字符串的设计特性。
  3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信
    息,请参阅basic_string)。
  4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits
    和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。
  5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个
    类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。

string本质就是char的字符数组
在这里插入图片描述

三个成员函数:构造,析构,赋值

在这里插入图片描述

我们主要看一下这一个

在这里插入图片描述
npos是const静态成员变量,为-1
在这里插入图片描述

但是这个-1是size_t的,也就是无符号整形,这样算下来就是整形的最大值。

元素访问

在这里插入图片描述
我们string遍历可以通过下标+【】进行访问,但是其他容器不一定适合。
这个仅仅适合部分容器,要求底层有一定的连续物理空间。对于树,链表,迭代器才是主流。
迭代器的区间一般都是左闭右开的
at如果出错会抛异常,【】会直接报错。

迭代器

在这里插入图片描述
我们这里主要看一下这个一点
在这里插入图片描述,
一个是const的一个是非const的,这里会走参数匹配。
这里是const_iterator。保证迭代器指向的内容不会被修改。
不是const iterator,对于这样,保证这个迭代器不会被修改,那就不能进行加加减减操作了。
一般不会直接定义const对象,多用于传参。

我们某些情况下也可以用auto代替,但是会降低代码可读性。

容量相关接口

在这里插入图片描述
capacity:容量
对于vs编译器是1.5倍扩容。对于g++,是2倍扩容。这里为什么不一样呢??
C++只说明了不同编译器,不同平台要实现相应的接口,供用户使用。但是没有具体说明如何实现。

reserve:开空间,影响容量
一般用于知道空间大小,一下子开好,避免大量扩容。
在vs下也不是n传级就开多少空间,他是按照扩容机制实现的。
g++是n是多少,加开多少。

对于reserve缩容情况!!!
vs,不会进行缩容。
g++,会进行缩容,但是这个缩容也是有限度的,不会影响数据。(如果n比size小,最小缩到size)

resize:开空间+初始化,影响数据和容量
在这里插入图片描述
如果resize>capacity,扩容+尾插,不传参数c,默认为/0
如果resize在size和capacity之间,尾插
如果resize<size,capacity不变,size变为resize大小。

clear:清空数据
clear只会清空数据,不改变底层大小

修改

在这里插入图片描述
注意:push_back只会尾插一个字符
我们最常用的就是+=

assign是赋值,我们一般不使用这个。
replace是替换,效率很低,能不用就不用。

字符串操作

在这里插入图片描述
c_str:返回c格式字符串,与data相同
下面这段代码不会编译通过。

int main()
{
	string filename("main.cpp");
	FILE* fout = fopen(filename, "r");
	char ch = fgetc(fout);
	while (ch != EOF)
	{
		cout << ch;
		ch = fgetc(fout);
	}
	return  0;
}

fopen的参数是这样的
在这里插入图片描述
我们需要将string转换成c格式的字符串进行传参/

int main()
{
	string filename("main.cpp");
	FILE* fout = fopen(filename.c_str(), "r");
	char ch = fgetc(fout);
	while (ch != EOF)
	{
		cout << ch;
		ch = fgetc(fout);
	}
	return  0;
}

substr:取子串,注意参数

在这里插入图片描述
find_first_of:查找这里面的任意一个

非成员函数

在这里插入图片描述
operator+
尽量少用,因为传值返回,导致深拷贝效率低

relational operators
大小比较

getline
获取一行字符串
我们在用scanf和cin输入多个值的时候,用空格或者换行进行分割
有些时候我们需要接收到空格,这时候就可以用getline,这个是遇到换行结束。

经典题目讲解

HJ1 字符串最后一个单词的长度

HJ1 字符串最后一个单词的长度
我们如果直接用cin输入,是错误的。
cin是以空格和换行为结束标志的,我们读取不到换行。
可以考虑用getline,这个是遇到换行才结束。

#include <iostream>
#include <string>
using namespace std;

int main() 
{
    string s;
    getline(cin,s);
   // cin>>s;
    auto rit=s.rfind(' ');
    if(rit!=-1)
    {
        cout<< s.size()-rit-1<<endl;
    }
    else {
        cout<< s.size()<<endl;
    }
   return 0;
}

模拟实现

框架

class string
{
    public:

	private:
		char* _ptr;
		size_t _size;
		size_t _capacity;
		//特殊处理,只有整形才可以
		static const size_t npos = -1;
};

这个特殊处理可以这样用
const static int N=10;
int a[N];

构造函数

我们看一下这样的构造函数是否正确

string(const char* ptr)
	:_ptr(ptr)
	, _size(strlen(ptr))
	, _capacity(strlen(ptr))
{

}

这样子是不正确的,我们并不是直接把地址赋值给他,而是重新开一块同样大的空间,把数据拷贝过去。

string(const char* ptr)
     //多开一个,放‘\0’
	:_ptr(new char [strlen(ptr)]+1 )
	, _size(strlen(ptr))
	 //容量不关心‘\0’,只关心有效空间
	, _capacity(strlen(ptr))
{
     //数据拷贝
     strcpy(_ptr,ptr);
}

但是这样还是有一点小问题,strlen时间复杂度为O(N),
在这里我们需要计算三次。

我们可以直接给_size,通过_size进行传参,那麽我们再进行初始化列表的时候就必须按照声明顺序执行。

那我们如何解决呢??
初始化列表在这种场景下又不是必须的,我们可以不用初始化列表,就用正常的构造函数。

我么还需要处理传递参数为空的情况。

string()
{
	_size = strlen(ptr);
	_ptr =nullptr;
	_capacity = _size;
	strcpy(_ptr, ptr);
}

我们将_ptr初始化为空是否可行呢??
是不可以,如果我们直接这个string进行打印,就会出现空指针的解引用操作。

我们可以给一个空串。所以完整的构造应该是这样的

string(const char* ptr = "")
{
	_size = strlen(ptr);
	_ptr = new char[_size + 1];
	_capacity = _size;
	strcpy(_ptr, ptr);
}

析构函数

这个很简单,将开辟的资源释放就可以。

~string()
{
	delete[] _ptr;
	_ptr = nullptr;
	_capacity = _size = 0;
}

delete对空机型特殊处理,delete nullptr不会报错。

迭代器

我们的迭代器区间是左闭右开的
我们需要实现普通迭代器和const迭代器

typedef char* iterator;
typedef const char* const_iterator;

iterator begin()
{
	return _ptr;
}
const_iterator begin()const 
{
	return _ptr;
}

iterator end()
{
	return _ptr + _size;
}
const_iterator end()const
{
	return _ptr + _size;
}

范围for就是傻瓜式的替换,底层还是迭代器

c_str()

将一个string转换成一个char*使用

const char* c_str()const
{
	return _ptr;
}

赋值

string operator=(string& str)
{
	if (this != &str)
	{
		_size = str._size;
		_capacity = str._capacity;
		delete[]_ptr;
		_ptr = new char[_size + 1];
		strcpy(_ptr, str._ptr);

	}
	return *this;
}

size()

计算数据个数

size_t size()const
{
	return _size;
}

capacity()

计算容量大小

size_t capacity()const
{
	return _capacity;
}

reserve

开空间,n是多少我们就开多少。
我们这里不会进行缩容

void reserve(size_t n=0)
{
	if (n > capacity())
	{
		char* tmp = new char[n + 1];
		strcpy(tmp, _ptr);
		delete _ptr;
		_capacity = n;
		_ptr = tmp;
	}
}

empty()

判断是否为空

bool empty()const
{
	return _size == 0;
}

[ ]访问

我们的【】范围需要实现两份
const与非const 版本

char& operator[](size_t pos)
{
	assert(pos >= 0 && pos < _size);
	return  _ptr[pos];
}
const char& operator[](size_t pos)const
{
	assert(pos >= 0 && pos < _size);
	return  _ptr[pos];
}

front/back

访问头和尾

char& back()
{
	return _ptr[_size - 1];
}
char& front()
{
	return _ptr[0];
}
const char& back()const
{
	return _ptr[_size - 1];
}
const char& front()const 
{
	return _ptr[0];
}

push_back

尾插一个字符

void push_back(char c)
{
     //是否扩容
	if (_size == _capacity)
	{
		int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(newcapacity);
	}
	_ptr[_size] = c;
	_size++;
	_ptr[_size] = '\0';
}

append

尾插一个字符串

void append(const string& str)
{
	if (_capacity < str._size+_size)
	{
		reserve(str._size + _size);
	}
	strcpy(_ptr+_size, str._ptr);
	_size += str._size;
	_ptr[_size] = '\0';
}

operator+=

我们在实际使用中,最常用的就是+=

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

}

insert一个字符


string& insert(size_t pos, const char& ch)
{
	assert(pos >= 0 && pos <= _size);
	//扩容
	if (_size == _capacity)
	{
		int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(newcapacity);
	}

	int end = _size;
	while (end >= pos)
	{
		if (end == 1)
		{
			int i = 0;
		}
		_ptr[end+1] = _ptr[end];
		end--;
	}
	_ptr[pos] = ch;
	_size++;
	return *this;
}

我们看一下这段代码
正常情况下是没有问题的,但是如果在0位置插入就会出问题

我们调试看一下

在这里插入图片描述
继续下一步

在这里插入图片描述

我们发现end=-1,pos=0,end>=pos居然是真的。
那这是为什么呢??
在这里插入图片描述
我们可以看到二者的类型是不一样的,这里会把end提升为unsigned_int进行比较。
-1如果是unsigned_int,就是整形的最大数,肯定大于0.

解决1
将pos强制转化成int类型,按照int进行比较

在这里插入图片描述

解决2
专门处理等于情况,不让等于发生。
我们上面是把end位置的值挪动到end+1位置,最后才可能end走到与pos相同的位置,二者可能相等。
但是我们如果把end-1的位置挪动到end位置就不会发生上面的情况了。

string& insert(size_t pos, const char& ch)
{
	assert(pos >= 0 && pos <= _size);
	//扩容
	if (_size == _capacity)
	{
		int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(newcapacity);
	}


	size_t end = _size + 1;
	while (end > pos)
	{
		if (end == 1)
		{
			int i = 0;
		}
		_ptr[end] = _ptr[end - 1];
		end--;
	}
	_ptr[pos] = ch;
	_size++;
	return *this;
}

insert一个字符串

string& insert(size_t pos, const char* str)
{
	assert(pos >= 0 && pos <= _size);

	//判断扩容
	int n = strlen(str);
	if (n + _size > _capacity)
	{
		reserve(n + _size);
	}
	//挪动数据
	int end = _size;
	while (end >= (int)pos)
	{
		_ptr[end+n] = _ptr[end ];
		end--;
	}
	strncpy(_ptr + pos, str, n);
	_size += n;
	return *this;
}

erase

从pos位置开始删除len个字符。

string& erase(size_t pos=0, size_t len=npos)
{
	if (len == npos || len + pos >= _size)
	{
		//删除直接给'\0'
		_ptr[pos] = '\0';
		_size = pos;
	}
	else
	{
		//数据挪动
		strcpy(_ptr + pos, _ptr + pos + len);
		_size -= len;
	}
	return *this;
}

if (len == npos || len + pos >= _size)
如果我们变成len+pos>=_size,不写前面的条件,很可能会越界。

swap

我们会发现库里面有三个swap
在这里插入图片描述
这个调用是为了减少string的拷贝。

在这里插入图片描述
这个与下面相比,如果我们是swap(s1,s2);
我们会优点调用这个,这个就是根据第一个实现的。
模板的匹配。
在这里插入图片描述
这个是所有STL库的交换函数

我们string的交换只需要交换三个变量的内容就饿可以,但是需要调用库里的,否则就会产生死递归。

void swap(string& str)
{
	std::swap(_ptr, str. _ptr);
	std::swap(_size, str._size);
	std::swap(_capacity, str._capacity);
}

find一个字符

我们直接进行遍历就可以。

size_t find(const char ch, size_t pos=0)const
{
	size_t i = 0;
	for (i = pos; i < _size; i++)
	{
		if (_ptr[i] == ch)
		{
			return i;
		}
	}
	return npos;
}

find一个字符串

借助strstr实现


size_t find(const char* ptr, size_t pos = 0)const
{
	//strstr 查找字串
	char* tmp = strstr(_ptr+pos, ptr);
	if (tmp == nullptr)
	{
		return npos;
	}
	else
	{
		//返回位置
		return tmp-_ptr;
	}
}

substr()

取string一部分

string substr(size_t pos = 0, size_t len = npos)
{
	size_t end = pos + len;
	if (len == npos || pos + len > _size)
	{
		end = _size;
	}
	string tmp;
	reserve(_capacity + 1);
	for (size_t i = pos; i< end; i++)
	{
		tmp += _ptr[i];
	}
	return tmp;
}

clear()

清空数据

void clear()
{
	_size = 0;
	_ptr[0] = '\0';
}

拷贝

我们的string有资源,所以我们需要深拷贝。

默认的拷贝完成浅拷贝,完不成我们的任务。

在这里插入图片描述
String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝

开一块同样大的空间,把值拷贝过去。

string(const string  ptr)
{
	_ptr = new char[ptr._size + 1];
	_size = ptr._size;
	_capacity = ptr._capacity;
	strcpy(_ptr, ptr._ptr);
}

上面写的是传统写法。

我们看一种现代写法

void swap(string& str)
{
	std::swap(_ptr, str. _ptr);
	std::swap(_size, str._size);
	std::swap(_capacity, str._capacity);
}
string(const string& ptr)
{
	string tmp(ptr._ptr);
	swap(tmp);
}

为什么这样就可以呢??
我们画图看一下:

s1,s2各自的空间
tmp是根据s1拷贝出来的.
在这里插入图片描述

我们看一下swap之后的效果

s2指向tmp的空间,这不就是我们想要的吗!!
tmp指向s2的空间。
在这里插入图片描述

赋值

赋值按照传统写法

string operator=(string& str)
{
	if (this != &str)
	{
		_size = str._size;
		_capacity = str._capacity;
		delete[]_ptr;
		_ptr = new char[_size + 1];
		strcpy(_ptr, str._ptr);

	}
	return *this;
}

现代写法


string operator=(string s)
{
	swap(s);
	return *this;
}

这里千万不能用引用

我们这里不需要进行判断,因为s已经拷贝出来了。
在这里插入图片描述

swap之后,s出了作用域要进行销毁,就自动把原来s3的空间进行释放了。

operator<<

<<不是必须是友元,我们用友元是为了可以拿到私有成员。
我们string直接对每个字符进行遍历及就可以了

ostream& operator<<(ostream& out, const string& s)
{
	for (auto ch : s)
	{
		out << ch;
	}

	return out;
}

operator>>

这个运算符>>输入多个值,默认空格和换行进行分割。
.>>是一个个字符的拿。
但是我们用这个>>,默认是拿不到换行个空格的。

我们可以用get获取。
为了避免多次扩容,我们可以引入一个buff字符数组来缓解这个问题。

流提取会覆盖这个变量已有的内容,我们再进行输入之前需要先清空。

istream& operator>>(istream& in, string& s)
{
	s.clear();
	char buff[128];
	char ch = in.get();
	int i = 0;
	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;
		if (i == 127)
		{
			buff[i] = '\0';
			s += buff;
			i = 0;
		}

		ch = in.get();
	}

	if (i > 0)
	{
		buff[i] = '\0';
		s += buff;
	}

	return in;
}

完整代码

namespace peng
{

	class string
	{
	public:
		string(const char* ptr = "")
		{
			_size = strlen(ptr);
			_ptr = new char[_size + 1];
			_capacity = _size;
			//
			strcpy(_ptr, ptr);
		}

		string(const string& str, size_t pos, size_t len = npos)
		{
			size_t n = strlen(str._ptr);
			if (len == npos || pos + len > n)
			{
				_size = n - pos;
				_capacity = n - pos;
				_ptr = new char[_size + 1];
				strcpy(_ptr, str._ptr + pos);
			}
			_size = len;
			_capacity = len;
			_ptr = new char[len + 1];
			strncpy(_ptr, str._ptr + pos + 1, len);
			_ptr[_size] = '\0';
		}

		string(size_t n, char c = '\0')
		{
			_size = n;
			_capacity = n;
			_ptr = new char[n + 1];
			for (size_t i = 0; i < n; i++)
			{
				_ptr[i] = 'c';
			}
			_ptr[_size] = '\0';
		}

		~string()
		{
			delete[] _ptr;
			_ptr = nullptr;
			_capacity = _size = 0;
		}
		const char* c_str()const
		{
			return _ptr;
		}


		//迭代器,左闭右开
		typedef char* iterator;
		typedef const char* const_iterator;

		iterator begin()
		{
			return _ptr;
		}
		const_iterator begin()const
		{
			return _ptr;
		}

		iterator end()
		{
			return _ptr + _size;
		}
		const_iterator end()const
		{
			return _ptr + _size;
		}


		size_t size()const
		{
			return _size;
		}

		size_t capacity()const
		{
			return _capacity;
		}

		//开空间,n是多少开多少
		//不进行缩容
		void reserve(size_t n = 0)
		{
			if (n > capacity())
			{

				char* tmp = new char[n + 1];
				strcpy(tmp, _ptr);
				delete _ptr;
				_capacity = n;
				_ptr = tmp;
			}
		}

		bool empty()const
		{
			return _size == 0;
		}


		char& operator[](size_t pos)
		{
			assert(pos >= 0 && pos < _size);
			return  _ptr[pos];
		}
		const char& operator[](size_t pos)const
		{
			assert(pos >= 0 && pos < _size);
			return  _ptr[pos];
		}
		char& back()
		{
			return _ptr[_size - 1];
		}
		char& front()
		{
			return _ptr[0];
		}
		const char& back()const
		{
			return _ptr[_size - 1];
		}
		const char& front()const
		{
			return _ptr[0];
		}

		void push_back(char c)
		{
			if (_size == _capacity)
			{
				int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}
			_ptr[_size] = c;
			_size++;
			_ptr[_size] = '\0';
		}

		void append(const string& str)
		{
			if (_capacity < str._size + _size)
			{
				reserve(str._size + _size);
			}
			strcpy(_ptr + _size, str._ptr);
			_size += str._size;
			_ptr[_size] = '\0';
		}

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

		}

		string& insert(size_t pos, const char* str)
		{
			assert(pos >= 0 && pos <= _size);

			//判断扩容
			int n = strlen(str);
			if (n + _size > _capacity)
			{
				reserve(n + _size);
			}
			//挪动数据
			int end = _size;
			while (end >= (int)pos)
			{
				_ptr[end + n] = _ptr[end];
				end--;
			}
			strncpy(_ptr + pos, str, n);
			_size += n;
			return *this;
		}
		string& insert(size_t pos, const char& ch)
		{
			assert(pos >= 0 && pos <= _size);
			//扩容
			if (_size == _capacity)
			{
				int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}
			size_t end = _size + 1;
			while (end > pos)
			{
				_ptr[end] = _ptr[end - 1];
				end--;
			}
			_ptr[pos] = ch;
			_size++;
			return *this;
		}
		string& erase(size_t pos = 0, size_t len = npos)
		{
			if (len == npos || len + pos >= _size)
			{
				//删除直接给'\0'
				_ptr[pos] = '\0';
				_size = pos;
			}
			else
			{
				//数据挪动
				strcpy(_ptr + pos, _ptr + pos + len);
				_size -= len;
			}
			return *this;
		}

		size_t find(const char ch, size_t pos = 0)const
		{
			size_t i = 0;
			for (i = pos; i < _size; i++)
			{
				if (_ptr[i] == ch)
				{
					return i;
				}
			}
			return npos;
		}
		size_t find(const char* ptr, size_t pos = 0)const
		{
			//strstr 查找字串
			char* tmp = strstr(_ptr + pos, ptr);
			if (tmp == nullptr)
			{
				return npos;
			}
			else
			{
				//返回位置
				return tmp - _ptr;
			}
		}
		//取string一部分
		string substr(size_t pos = 0, size_t len = npos)
		{
			size_t end = pos + len;
			if (len == npos || pos + len > _size)
			{
				end = _size;
			}
			string tmp;
			reserve(_capacity + 1);
			for (size_t i = pos; i < end; i++)
			{
				tmp += _ptr[i];
			}
			return tmp;
		}

		void swap(string& str)
		{
			std::swap(_ptr, str._ptr);
			std::swap(_size, str._size);
			std::swap(_capacity, str._capacity);
		}



		//拷贝赋值
		//s1(s2)
		//string(const string  ptr)
		//{
		//	_ptr = new char[ptr._size + 1];
		//	_size = ptr._size;
		//	_capacity = ptr._capacity;
		//	strcpy(_ptr, ptr._ptr);
		//}


		//s1=s2;
		//s1.operator(s2);
		//string operator=(string& str)
		//{
		//	if (this != &str)
		//	{
		//		_size = str._size;
		//		_capacity = str._capacity;
		//		delete[]_ptr;
		//		_ptr = new char[_size + 1];
		//		strcpy(_ptr, str._ptr);

		//	}
		//	return *this;
		//}

		string(const string& ptr)
		{
			string tmp(ptr._ptr);
			swap(tmp);
		}

		string operator=(string s)
		{
			swap(s);
			return *this;
		}
		void clear()
		{
			_size = 0;
			_ptr[0] = '\0';
		}
	private:
		char* _ptr = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;
		//特殊处理
		static const size_t npos = -1;
	};

	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			out << ch;
		}

		return out;
	}

	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		char buff[128];
		char ch = in.get();
		int i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 127)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}

			ch = in.get();
		}

		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}

		return in;
	}

}

string总体介绍

stoi/stod

在这里插入图片描述
这几个函数可以把字符串转成整形或者浮点型

to_string

将整形或者浮点数转化成string

在这里插入图片描述

int main()
{
	string s1("1111");
	int m = stoi(s1);
	cout << m << endl;
	cout << typeid(m).name() << endl;

	int n = 22222;
	string s2 = to_string(n);
	cout << typeid(s2).name() << endl;
	cout << s2 << endl;

	return 0;
}

在这里插入图片描述

Class instantiations

在这里插入图片描述

这些是编码表。
我们最熟悉的就是ASCII码,每个值都一一对应。

但是对于我们的汉字,需要用多个字节表示一个中文汉字,一般常见的汉字都是用两个字节表示。

随着历史的发展,产生了万国码

在这里插入图片描述

在这里插入图片描述
每一种又对应各自的编码方式。

在我们中国,自己设立一一套规范,称为GBK
在这里插入图片描述

string就是用到UTF-8的格式
在这里插入图片描述

vs和g++下string结构的

VS

下面代码的结果是什么???

int main()
{
	string s1;
	cout << sizeof(s1) <<endl;

	string s2("12345");
	cout << sizeof(s2) << endl;

	string s3("1234sssssssssssssssssssssssssssssssssss5");
	cout << sizeof(s3) << endl;
	return 0;
}

在这里插入图片描述
结果是28,为什呢??

在这里插入图片描述
我们看到库里还开了一个16字节大小的字符数组。

我们可以认为底层是这样的
在这里插入图片描述
 当字符串长度小于16,使用内部固定的字符数组来存放
 当字符串长度大于等于16,从堆上开辟空间

我们可以认为是这样

在这里插入图片描述

G++

但是如果我们在g++下测试,打印8。
一个指针的大小,g++下默认指针是8个字节。

g++下是如何通过一个char*指针解决的呢??
浅拷贝问题:
1.析构两次
2.一个修改会影响另一个

g++解决浅拷贝:引用计数,写时拷贝

引用计数:用来记录资源使用者的个数。
在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1;
当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源。
如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。

具体是这样实现的

在这里插入图片描述

总结

以上就是今天要讲的内容,本文仅仅详细介绍了 。希望对大家的学习有所帮助,仅供参考 如有错误请大佬指点我会尽快去改正 欢迎大家来评论~~ 😘 😘 😘

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

超级会员卡积分收银系统源码 带完整的安装代码包以及搭建部署教程

系统概述 超级会员卡积分收银系统源码是一款专为商业运营打造的综合性软件解决方案。它集成了会员卡管理、积分管理、收银管理等多种功能&#xff0c;旨在为企业提供高效、便捷、准确的运营管理工具。 该系统源码采用先进的技术架构&#xff0c;具有良好的稳定性和扩展性&…

Python火焰锋动力学和浅水表面波浪偏微分方程

&#x1f3af;要点 &#x1f3af;流图可视化正弦余弦矢量场 | &#x1f3af;解空间变化边界条件二维拉普拉斯方程 | &#x1f3af;解圆柱坐标系标量场 | &#x1f3af;解一维泊松方程 | &#x1f3af;解二维扩散方程 | &#x1f3af;解火焰锋的动力学偏微分方程 | &#x1f3a…

自动备份SQL Server数据库,试试这4种方法!

各种规模的企业都使用 SQL 数据库来存储数据。因此&#xff0c;备份 SQL Server 数据库对于确保数据安全并在发生灾难时可恢复至关重要。对于 SQL 数据库备份&#xff0c;有多种可行的方法&#xff0c;对于特定组织来说&#xff0c;方法将取决于其具体需求。 SQL Server 备份的…

Vue69-路由基本使用

一、需求 二、开发步骤 2-1、路由的安装 vue-router3才能在vue2中使用&#xff01;现在默认是vue-router4版本&#xff0c;要在vue3中使用&#xff01;所以&#xff0c;安装的时候要指定版本。 2-2、在main.js中引入和使用路由 2-3、创建router文件夹 一般在vue中用了vue-ro…

vb.net c#一键编绎引用DLL如何做?编绎成独立EXE

.net c#一键编绎引用如何做&#xff1f; 3个工程有依懒关系 ClassLibrary1&#xff0c;ClassLibrary2&#xff0c;MainProject &#xff08;主工程&#xff09; ClassLibrary2依赖ClassLibrary1&#xff0c;MainProject依赖前2个 如何实现一键按顺序编绎&#xff0c;自动添加前…

【中学教资科目二】03中学教学

03中学教学 第一节 教学的任务1.1 教学的任务 第二节 教学过程2.1 教学过程的本质2.2 教学过程的基本规律 第三节 教学原则3.1 因材施教原则 第四节 教学方法4.1 我国中学常用的教学方法4.2 国外常用的教学方法 第五节 教学组织形式、教学工作基本环节和教学评价5.1 现代其他教…

SpringSecurity实战入门——认证

项目代码 gson/spring-security-demo 简介 Spring Security 是 Spring 家族中的一个安全管理框架。相比与另外一个安全框架Shiro,它提供了更丰富的功能,社区资源也比Shiro丰富。 一般来说中大型的项目都是使用SpringSecurity来做安全框架。小项目有Shiro的比较多,因为相比…

容器之对齐构件

代码&#xff1a; #include <gtk-2.0/gtk/gtk.h> #include <glib-2.0/glib.h> #include <gtk-2.0/gdk/gdkkeysyms.h> #include <stdio.h>int main(int argc, char *argv[]) {gtk_init(&argc, &argv);GtkWidget *window;window gtk_window_ne…

同城跑腿小程序的崛起与用户体验革新

随着移动互联网的飞速发展&#xff0c;人们的生活方式正在发生深刻的变化。在这个快节奏的时代&#xff0c;时间成为了最宝贵的资源。在这样的背景下&#xff0c;同城跑腿小程序应运而生&#xff0c;以其高效、便捷的服务特性&#xff0c;迅速赢得了广大用户的青睐。本文将探讨…

java基于ssm+jsp KTV点歌系统

1管理员功能模块 管理员登录&#xff0c;通过填写注册时输入的用户名、密码进行登录&#xff0c;如图1所示。 图1管理员登录界面图 管理员登录进入KTV点歌系统可以查看个人中心、用户管理、歌曲库管理、歌曲类型管理、点歌信息管理等信息。 修改密码&#xff0c;在修改密码页…

ES6(ECMAScript 6.0) 新特性

1 ES6 基本介绍 &#xff08;1&#xff09;ECMAScript 6.0(简称 ES6)是 JavaScript 语言的下一代标准&#xff0c; 2015 年 6 月发布。 &#xff08;2&#xff09;ES6 设计目标&#xff1a;达到 JavaScript 语言可以用来编写复杂的大型程序&#xff0c;成为企业级开发语言 &…

00 - matlab m_map地学绘图工具安装及简单使用教程

00 - matlab m_map地学绘图工具安装及简单使用教程 0. 引言1. m_map工具的获取及配置过程2. 绘图示例3. 结语 0. 引言 m_map是MATLAB中的一个绘图工具包&#xff0c;用于绘制地图和地理数据。它提供了一系列函数&#xff0c;可以用来绘制地理投影、添加地理特征、绘制等值线图等…

Springboot开发Webservice服务端和客户端

环境说明 Java JDK 1.8、Spring boot 2.1.6、Apache CXF 3.1.6 POM依赖 <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.1.6</version&…

可变分区管理 分区分配算法

First Fit Algorithm Best Fit Algorithm FFA&#xff1a;按照分区编号找到第一个能装下进程的起始地址填入第二个表 此时 原表中将起始地址进程大小 分区大小-进程大小 如此继续 BFA&#xff1a;按分区大小排序 从小到大 找到第一个能装下的 剩余步骤和FFA一样 装满了可以直…

【图书推荐】《Linux C与C++一线开发实践(第2版)》

本书重点 主要讲解开发环境搭建、C/C语言基础、文件编程、多进程、Web编程、多线程、库、TCP/IP协议、套接字、并发聊天服务器项目实战。适合选择Linux系统开发方向的开发人员&#xff0c;打下Linux开发的牢固基础。 Linux开发方向比较固定&#xff0c;知识和技能更新没有Web…

【八股系列】为什么组件中的 data 必须是一个函数,然后 return 一个对象,而 new Vue 实例里,data 可以直接是一个对象?

&#x1f389; 博客主页&#xff1a;【剑九 六千里-CSDN博客】 &#x1f3a8; 上一篇文章&#xff1a;【点击一个按钮&#xff0c;浏览器会做些什么事情【呈现效果时流程】&#xff1f;(js)】 &#x1f3a0; 系列专栏&#xff1a;【面试题-八股系列】 &#x1f496; 感谢大家点…

【ARMv8/v9 GIC 系列 4.3 -- GIC 中断控制系统寄存器 ICC_SRE_ELn 使用介绍】

文章目录 GIC 中断控制系统寄存器 ICC_SRE_ELn寄存器位域介绍Interrupt BypassBypass IRQBypass FIQBypass 配置GIC 中断控制系统寄存器 ICC_SRE_ELn ICC_SRE_EL3是中断控制器系统寄存器(Interrupt Controller System Register),用于控制在异常级别3(EL3)下,对GIC CPU接口…

CCAA质量管理【学习笔记】​​ 备考知识点笔记(三)质量管理方法与常见工具

第二部分 质量管理领域专业知识 《质量管理体系基础考试大纲》中规定的考试内容&#xff1a; 3.2 质量管理领域专业知识 a) 了解质量管理方法与工具相关知识&#xff0c;包括&#xff1a; 质量管理方法与工具的内涵与作用、发展历程与应用现状、分类与选择常用的应用软件…

SpringCloud 基于Nacos和Eureka 实现双注册双订阅

一、使用场景/原因 过渡期迁移: 当系统从一个服务注册中心迁移到另一个时&#xff0c;例如从 Eureka 迁移到 Nacos&#xff0c;可以在过渡期内同时使用两个注册中心&#xff0c;确保服务平稳迁移&#xff0c;逐步过渡&#xff0c;避免一次性切换带来的风险。 兼容性考虑: 不同的…

京东云 AX1800 Pro 路由器 亚瑟 R2242 u-boot 和 OpenWrt 刷机教程

上周末陪媳妇儿逛街的时候无意中看到了一篇 OpenWrt 的公众号文章&#xff1a;百元京东亚瑟wifi6路由器真香&#xff0c;支持刷OpenWrt系统。 然后&#xff0c;就开启了将近一周的苦逼刷机过程&#xff0c;因为最近工作忙只能利用晚上的时间来搞&#xff0c;刷机用了一天多搞定…