【C++ • STL】探究string的源码

news2024/11/26 21:43:41

文章目录

  • 一、深浅拷贝
  • 二、传统版写法的string类(简单)
  • 三、string类的模拟实现
  • 四、现代版写法的string类
  • 五、总结


ヾ(๑╹◡╹)ノ" 人总要为过去的懒惰而付出代价ヾ(๑╹◡╹)ノ"


一、深浅拷贝

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。
浅拷贝:(1)析构两次,造成程序崩溃(2)一个对象修改影响另外一个

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。

编译器默认生成的拷贝构造,是浅拷贝,会是两个对象指向同一块空间,当程序结束的时候,那么两个对象都会进行销毁,那么一块空间就会进行多次释放,从而引起崩溃。

深拷贝:给每一个对象分配资源,保证多个对象之间不会因为共享资源而导致多次释放造成程序崩溃。

二、传统版写法的string类(简单)

#pragma once
#include <iostream>
using namespace std;
#include <assert.h>

namespace yyqx//为了与库里面的string进行区分
{
	//仅仅实现一个简单的string,仅仅考虑资源管理深浅拷贝问题
	class string
	{
	public:
		//构造函数
		string(const char* str)
			:_str(new char[strlen(str) + 1])//这里的+1,是为了'\0'开辟空间
		{
			strcpy(_str, str);//拷贝的时候'\0'也拷贝了
		}

		//拷贝构造(深拷贝)
		//s2(s1)
		string(const string& s)
			:_str(new char[strlen(s._str) + 1])
		{
			strcpy(_str, s._str);
		}

		//赋值,也会有深浅拷贝的问题
		string& operator=(const string& s)
		{
			if (this != &s)//避免自己给自己赋值,会导致值被释放,就会变成随机值
			{
				//delete[] _str;//首先进行释放
				//_str = new char[strlen(s._str) + 1];//C++的new是不需要检查是否开辟空间
				会抛异常
				//strcpy(_str, s._str);

				//为了避免开辟空间失败,而本来的空间也被我们释放,可以先开启空间,
				//进行拷贝,然后再释放
				char* tmp = new char[strlen(s._str) + 1];
				strcpy(tmp, s._str);
				delete[] _str;
				_str = tmp;
			}
			return *this;
		}


		//析构函数
		~string()
		{
			if (_str)
			{
				delete[] _str;
			}
		}
		
		//目的为了输出字符串
		const char* c_str() const
		{
			return _str;
		}//返回c格式的字符串

		//重载[]
		char& operator[](size_t pos)
		{
			assert(pos < strlen(_str));//注意这里的范围
			return _str[pos];
		}

		size_t size()
		{
			return strlen(_str);
		}
	private:
		char* _str;
	};
}

赋值运算符重载也会有深浅拷贝的问题。赋值,对象本身是有值的【拷贝的时候,如果空间小,就会不够,空间大,就会造成资源浪费】

三、string类的模拟实现

string的增删查改以及使用string【传统】
基本框架

#pragma once
#include <iostream>
using namespace std;
#include <assert.h>

namespace yyqx//为了与库里面的string进行区分
{
	class string
	{
	public:

构造函数+析构函数
写法1

//构造函数
		string(const char* str)
			:_size(strlen(str))
			,_capacity(_size)
		{
			_str = new char[strlen(str) + 1];//这里的+1,是为了'\0'开辟空间
			strcpy(_str, str);//拷贝的时候'\0'也拷贝了
		}

		string()//注意,这里不是给的空,而是给了一个空的字符串//标准库里的就是给了一个""
			:_size(0)
			,_capacity(0)
		{
			_str = new char[1];
			_str[0] = '\0';
		}
  • 构造函数:初始化列表,初始化的顺序并不是初始化列表的顺序,而是成员变量在类中的声明次序。
  • 构造函数:注意默认的构造函数【编译器自动生成、缺省、函数重载】,默认的构造函数这里选择写一个同名函数,注意这里并不是给一个空指针,而是给了一个空字符串。
    写法2:(最优写法)
		string(const char* str = "")//这里默认值不能给nullptr,strlen以及拷贝strcpy会崩溃
			:_size(strlen(str))
			,_capacity(_size)
		{
			_str = new char[strlen(str) + 1];//这里的+1,是为了'\0'开辟空间
			strcpy(_str, str);//拷贝的时候'\0'也拷贝了
		}
		
		//析构函数
		~string()
		{
			if (_str)
			{
				delete[] _str;
				_str = nullptr;//好习惯
				_size = 0;
				_capacity = 0;
			}
		}
  • 缺省值这不能给nullptr,strlen以及拷贝strcpy时程序会崩溃
  • 注意初始化列表
  • strcpy注意,拷贝的时候’\0’也拷贝了
  • new开空间的时候,一定要多开一个给’\0’

拷贝构造+赋值重载函数+其他

		//拷贝构造(深拷贝)
		//s2(s1)
		string(const string& s)
			:_size(strlen(s._str))
			,_capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, s._str);
		}

		//赋值,也会有深浅拷贝的问题
		string& operator=(const string& s)
		{
			if (this != &s)//避免自己给自己赋值,会导致值被释放,就会变成随机值
			{
				char* tmp = new char[s._capacity + 1];
				strcpy(tmp, s._str);
				delete[] _str;
				_str = tmp;
				_size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}
		//目的为了输出字符串
		const char* c_str() const
		{
			return _str;
		}//返回c格式的字符串
		
		char& operator[](size_t pos)//这里仅仅可以传入对象,不能传入const对象,如果是const对象,就会报错
		{
			assert(pos < _size);//注意这里的范围
			return _str[pos];
		}
		const char& operator[](size_t pos) const//这里就可以传入const对象
		{
			assert(pos < _size);
			return _str[pos];
		}

		//这里的const修饰的是this指针指向的对象const string s;
		size_t size() const//写const,普通对象以及const对象都可以调用,如果不加const对象就不可以调用
		{
			return _size;
		}
		size_t capacity() const//写const,普通对象以及const对象都可以调用
		{
			return _capacity;
		}

添加

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

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

		void reverse(size_t n)//一个扩容的作用
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;//注意这里的释放不是free
				_str = tmp;
				_capacity = n;
			}
		}

		void resize(size_t n, char ch = '\0')
		{
			if (n < _size)
			{
				_size = n;
				_str[_size] = '\0';
			}
			else
			{
				if (n > _capacity)
				{
					reverse(n);
				}
				for (size_t i = _size; i < n; i++)
				{
					_str[i] = ch;
				}
				_size = n;
				_str[_size] = '\0';
			}
		}

		void push_back(char ch)
		{
			if (_size == _capacity)
			{
				reverse(_capacity == 0 ? 4 : _capacity * 2);//如果是一个空字符串,就会导致并没有扩容,
				//扩容要注意刚开始没有容量的情况下
			}
			_str[_size] = ch;
			_size++;
			_str[_size] = '\0';//注意\0,容易遗漏
		}

		//append插入的字符个数是未知的,扩容二倍也不一定足够
		void append(const char* str)
		{
			size_t len = _size + strlen(str);
			if (len > _capacity)
			{
				reverse(len);
			}
			strcpy(_str + _size, str);
			_size = len;
		}//但是我们一般用+=
  • 判断容量是否满,如果 _size= _ capacity,容量扩2倍,new一个新容量的空间,释放旧空间,最后指针指向新的空间。
  • append (append插入的字符个数是未知的,扩容二倍也不一定足够:解决办法:reverse预留空间【一个扩容的作用】)
  • reverse 为string预留空间,避免多次扩容(提高效率)
  • resize用处:扩空间+初始化;删除数据保留前n个
    插入
string& insert(size_t pos, char ch)
		{
			assert(pos <= _size);//这里的=_size相当于尾插
			//注意,这里容易忘记,size_t就已经大于等于0了,所以在这里我们主要保证pos是小于_size即可
			if (_size == _capacity)
			{
				reverse(_capacity == 0 ? 4 : 2 * _capacity);
			}
			//不可以用strcpy,这里不可以是同一块地址,对导致内容不是我们想要的
			//最后一个未知的字符移到_size然后就是倒数第二位移动,从后向前移动
			size_t end = _size + 1;
			//注意这里如果end=_size,当头插的时候,进入循环end会变成-1,因为是size_t所以又会进入循环,导致错误
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				--end;
			}
			_str[pos] = ch;
			_size++;
			return *this;
		}

		//插入\0,用c_str(遇到\0停止打印)打印显示在屏幕的字符串长度会减小或者不变,但是_size会变大
		//用范围for或者迭代器可以打印出来

		string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reverse(_size + len);
			}
			size_t end = _size + len;
			while (end > pos + len - 1)//这里注意
			{
				_str[end] = _str[end - len];
				--end;
			}
			strncpy(_str + pos, str, len);//防止为了遇见\0就不拷贝了(strcpy遇见\0就不拷贝了)
			_size += len;
			return *this;
		}

插入字符:

  • 不可以用strcpy,在字符进行向后移的时候,不可以是同一块地址,对导致内容不是我们想要的,最后一个未知的字符移到_size然后就是倒数第二位移动,从后向前移动
  • end=_size,当头插的时候,进入循环end会变成-1,因为是size_t,又是大于0所以又会进入循环,导致代码错误

插入字符串:

  • 防止为了遇见\0就不拷贝了,所以用的是strncpy(strcpy遇见\0就不拷贝了)

删除

//删除
		string& erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			//删除的数据大于等于_size
			if (len == npos || pos + len >= npos)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				size_t begin = pos + len;
				while (begin <= _size)
				{
					_str[begin - len] = _str[begin];
					++begin;
				}
				_size -= len;
			}
			return *this;
		}

注意:npos类中静态成员的初始化,必须在类外,类和对象(下)本篇文章中有详细说明。【const在定义的时候必须初始化,但是静态成员的变量初始化又在外面】

查找

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

		size_t find(const char* str, size_t pos = 0)
		{
			const char* p = strstr(_str + pos, str);
			if (p == nullptr)
			{
				return npos;
			}
			else
			{
				return p - _str;
			}
			
		}
		
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}
	private:
		char* _str;
		size_t _size;//有效字符的个数
		size_t _capacity;//存储有效字符的空间大小
		const static size_t npos;//正确的写法是在类外进行初始化
		//const static size_t npos = -1;//这种写法也可以,但是违背了正确的写法,要注意
	};
	const size_t string::npos = -1;
  • strstr返回的是指针,没有找到返回空指针。

流插入和流提取

/流插入和流提取
	//在类外
	//不可以用c_str(),因为遇见\0会停止
	//'\0'是不可以见字符,不会显示

	//流插入
	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			out << ch;
		}
		return out;
	}
	//流提取,字符从面板提取到s
	istream& operator>>(istream& in, string& s)
	{
			s.clear();
		//要把对象里面的字符清理掉,否则当对象不是空的时候,会导致字符直接加到已有对象的后面。
		//但是我们想要的是,对象是我们输入的字符串
	
		//第一种思路(缺点:频繁的+=,字符串过大,会导致频发的扩容,影响效率)
		/*char ch;
		ch = in.get();
		if (ch != ' ' && ch != '\n')
		{
			s += ch;
			ch = in.get();
		}
		return in*/

		//第二种思路(这种思路比较优,无论大小都可以避免频繁扩容)
		char ch;
		ch = in.get();
		char buff[128] = { '\0' };
		size_t i = 0;
		if (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 127)
			{
				s += buff;
				memset(buff, '\0', 128);
				i = 0;
			}
			ch = in.get();
		}
		s += buff;
		return in;
	}
  • '\0’是不可以见字符,不会显示
  • clear()要把对象里面的字符清理掉,否则当对象不是空的时候,会导致字符直接加到已有对象的后面。但是我们想要的是,对象是我们输入的字符串

运算符重载

	//运算符重载
	//比较大小
	//全局函数.可以类比日期类
	bool operator<(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) < 0;
	}

	bool operator==(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) == 0;
	}

	bool operator<=(const string& s1, const string& s2)
	{
		return s1 < s2 || s1 == s2;
	}

	bool operator>(const string& s1, const string& s2)
	{
		return !(s1 <= s2);
	}

	bool operator>=(const string& s1, const string& s2)
	{
		return s1 > s2 || s1 == s2;
	}

	bool operator!=(const string& s1, const string& s2)
	{
		return !(s1 == s2);
	}
}//这个是yyqx的大括号

这里是在全局变量,没有在类里面,是在类外

迭代器
string类private里面:

public:
		//迭代器
		typedef char* iterator;
		typedef const char* const_iterator;

		const_iterator begin() const
		{
			return _str;
		}

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

		iterator end() 
		{
			return _str + _size;
		}

四、现代版写法的string类

拷贝构造和赋值的现代写法

		//拷贝构造(深拷贝)
		//s2(s1)//现代写法,剥削行为,要完成深拷贝,
		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		string(const string& s)
			:_str(nullptr)
			,_size(0)
			,_capacity(0)//这里要进行初始化,否则交换后,局部变量的销毁(随机值销毁,不可以)
		{
			//构造一个对象tmp,tmp里所有的东西this想要。this和tmp
			string tmp(s._str);//局部变量,出了作用域会销毁
			swap(tmp);
			//tmp出了作用域会销毁
		}

		//赋值,也会有深浅拷贝的问题
		//现代写法
		//第一种
		//string& operator=(const string& s)
		//{
		//	if (this != &s)//避免自己给自己赋值,会导致值被释放,就会变成随机值
		//	{
		//		string tmp(s._str);
		//		swap(tmp);//把tmp给this,出了作用域把this给tmp的值进行销毁
		//	}
		//	return *this;
		//}

		//第二种
		string& operator=(string s)//传值传参,拷贝构造,拷贝的值给this,并不会导致s的实参发生变化
		{
			swap(s);
			return *this;
		}
		//掌握现代写法

补充知识点
遍历方式中有一个是范围for(范围for的底层实现是迭代器,如果没有迭代器的程序,代码会进行报错)

代码展示:

	yyqx::string s("hello 12345");
	for (auto ch : s)
	{
		cout << ch << " ";
	}
	cout << endl;

在c语言中,我们用atoi。
在这里插入图片描述
string中的两个常用函数


五、总结

以上就是今天要讲的内容,本文详细的介绍了浅拷贝、浅拷贝和string的模拟实现。本文以及一文带你走进string详细的介绍了string的相关知识,希望给友友们带来帮助!

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

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

相关文章

数据结构与算法之HashBitMap

一&#xff1a;引入 1.Hash扩容算法在多线程情况有什么问题&#xff1f; 2.如何在3亿个整数&#xff08;0~2亿&#xff09;中判断某一个数是否存在&#xff1f;内存限制500M&#xff0c;一台机器。 分治&#xff1a; 布隆过滤器&#xff1a;神器 Redis Hash: 开3亿个空间&#…

[Go 夜读 第 148 期] Excelize 构建 WebAssembly 版本跨语言支持实践

Excelize 是 Go 语言编写的用于操作电子表格文档的基础库&#xff0c;支持 XLAM / XLSM / XLSX / XLTM / XLTX 等多种文档格式&#xff0c;高度兼容带有样式、图片 (表)、透视表、切片器等复杂组件的文档&#xff0c;并提供流式读写支持&#xff0c;用于处理包含大规模数据的工…

【项目管理】--敏捷开发管理之Scrum

目录 一、前言二、what---敏捷开发是什么2.1、敏捷开发宣言2.2、敏捷开发原则2.3、一句话概述敏捷开发三、why---为什么会有敏捷开发3.1、传统开发模式和敏捷开发模式对比四、how---敏捷开发怎么实践到项目团队4.1、what---Scrum是什么4.2、what---Scrum有哪些内容(1)、Scrum之…

【C语言练习】DOS黑框框通讯录(使用结构体、动态内存管理联系人信息,函数指针等)

文章目录 1. contacts.h 头文件、函数/常量/结构体声明2. test.c 主界面菜单打印、菜单功能选项选择3. contacts.c 函数实现4. 使用结构体、动态内存&#xff0c;函数指针实现时的注意点5. 运行演示 1. contacts.h 头文件、函数/常量/结构体声明 #pragma once#include <std…

浏览器原生JavaScript离线文字转语音TTS播放,支持Windows自带TTS语音和移动端(安卓、IOS)

前言 JS已经可以实现语音合成(文字转语音)和语音识别(语音转文字),各个浏览器支持列表如下所示: 语音识别支持列表: 因此,浏览器上面使用语音合成非常简单。 页面效果示例: 实现功能 1、支持速度,音调设置 2、支持下拉选择语音模板 3、文字转语音 代码实现 …

自学视觉SLAM(1)

引言 小编研究生的研究方向是视觉SLAM&#xff0c;目前在自学&#xff0c;已经学了Linux系统的基本操作&#xff0c;vim编辑器以及高翔老师的一些视屏。本篇文章为初学笔记。 文章目录 引言1 熟悉 Linux1.1 如何在 Ubuntu 中安装软件&#xff08;命令⾏界⾯&#xff09;&#x…

算法竞赛备赛之动态规划训练提升,DP基础掌握

1.背包问题 1.1.01背包问题 01背包问题是在M件物品中选择若干件放在空间为W的背包中&#xff0c;每件物品的体积为W1&#xff0c;W2至Wn&#xff0c;价值为P1&#xff0c;P2至Pn&#xff0c;01背包的约束条件是给定几种物品&#xff0c;每种物品有且只有一个&#xff0c;并且…

(4) OpenCV图像处理kNN近邻算法-识别数字0和1

我们的目标是构建一个可以读取手写数字的应用程序。为此,我们需要创建一个手写数字的图片文件 “ digits.png ”,图像的像素为:( 320*40 ), 其中包含 32个手写数字(每个数字16个),每个数字都是20x20的图像,如下图。 因此,我们的第 1 步是将图像分割成 32 个不…

华为OD机试 - 第k个排列 - 全排列递归(Java 2023 B卷 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#…

C语言刷题篇(备赛中......)--------( 篇章1 )

前言: 博主报名了一个小比赛&#xff0c;用来写一些基础的C语言题目&#xff0c;正好借此巩固一下基础 希望这些题对你们也有帮助 猫猫跟你说:该学习了 目录 前言: 题目&#xff08; 1 &#xff09; 解题思路 题目&#xff08; 2 &#xff09; 解题思路 题目&#xff…

Linux虚拟机磁盘空间不足怎么办

我是一个目录 1. 在虚拟机客户端扩充磁盘空间2. 检查磁盘空间3. 扩展空间而无需重启虚拟机4. 创建新磁盘分区5. 创建物理卷6. 扩展逻辑卷 对大多数系统管理员来说&#xff0c;扩充 Linux 服务器的磁盘空间是日常的工作之一&#xff0c;下面会通过使用 Linux 命令&#xff0c;在…

Text-to-SQL小白入门(七)PanGu-Coder2论文——RRTF

论文概述 学习这个RRTF之前&#xff0c;可以先学习一下RLHF。 顺带一提&#xff1a;eosphoros-ai组织「DB-GPT开发者」最新有个新项目Awesome-Text2SQL&#xff1a;GitHub - eosphoros-ai/Awesome-Text2SQL: Curated tutorials and resources for Large Language Models, Text2…

ps 去除图标背景色

百度安全验证 https://baijiahao.baidu.com/s?id1766678909759797922&wfrspider&forpc 使用魔术橡皮擦工具&#xff0c;超级简单 最后&#xff0c;保存为PNG格式&#xff0c;因为PNG格式支持透明背景。

大二毕设.3-网盘系统-文件模块讲解

目录 模块功能介绍 抽象文件存储引擎顶级接口 高性能单文件上传-sendfile零拷贝 为什么要分片上传 文件存储引擎模块讲解 文件模块具体实现讲解 项目演示博客 模块功能介绍 文件列表查询 聚簇索引和非聚簇索引回表查询最左前缀原则覆盖索引创建文件夹文件重命名文件删除 …

jvs-rules(规则引擎)和jvs智能bi(自助式数据分析)9.22更新内容

规则引擎更新功能 新增: 1.新增节点匹配筛选 用于做多个条件的数据筛选&#xff0c;以便将符合条件的数据传递给下一个节点进行处理&#xff0c;通常用于实现复杂的查询逻辑。 2.复合变量节点新增判断条件选项说明 用户可以根据自己的需求&#xff0c;为复合变量节点添加不…

全球化、国际化、本地化和翻译:GILT 的方法

为促进公司走向全球市场&#xff0c;您需要与客户沟通&#xff0c;并成功传达您的信息。您可能会认为&#xff0c;您需要的只是一支强大的翻译团队。但是&#xff0c;翻译并不像看起来那么简单。 将公司信息翻译成另一种语言&#xff0c;需要了解一些细微差别和文化背景。要想在…

合合信息、上海大学、华南理工大学发布业内首个古彝文编码“大字典” ,为古文字打造“身份证”

“乌蒙山连着山外山&#xff0c;月光洒向了响水滩。”近期在各大短视频平台爆火的《奢香夫人》你听过吗&#xff1f;奢香夫人是一位彝族“巾帼英雄”&#xff0c;这首同名歌曲早在2009年便已发布&#xff0c;如今突然“翻红”&#xff0c;不仅体现了大众对于少数民族文化高涨的…

竞赛 基于生成对抗网络的照片上色动态算法设计与实现 - 深度学习 opencv python

文章目录 1 前言1 课题背景2 GAN(生成对抗网络)2.1 简介2.2 基本原理 3 DeOldify 框架4 First Order Motion Model5 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于生成对抗网络的照片上色动态算法设计与实现 该项目较为新颖&am…

计算糖果(牛客)

一、题目 计算糖果_牛客题霸_牛客网 二、代码 #include <iostream> using namespace std;int main() {int x, y, m, n;cin >> x >> y >> m >> n;//A-Bx B-Cy ABm BCnif ((x > 30 || x < -30) && (y > 30 || y < -30) &am…

【Vue】模板语法,插值、指令、过滤器、计算属性及监听属性(内含面试题及毕设等实用案例)上篇

一、引言 1、什么是模板语法&#xff1f; Vue 的模板语法是一种用于在 HTML 中声明式地渲染 Vue 组件的语法。它基于 HTML&#xff0c;并通过特定的模板语法扩展了 HTML。Vue 使用了一种称为 “Mustache” 语法的模板插值来绑定数据到 HTML 元素上。 在 Vue 的模板语法中&…