一、引言

        在之前的文章中,我们一同学习了有关类和对象、模板、动态内存管理的相关知识,那么接下来一段时间我们将要趁热打铁,一起来手撕C++库中最重要的一个库----STL中的一些容器,在手撕它们之前,我将先介绍一下对应的容器,在这个过程中,我们将加深之前学习过的相关知识。

二、string的前置内容

        1、string的介绍

        事实上string并不是STL中的一个容器,这是由于C++的发展历史的原因,但是随着语言的发展,string与STL中的容器的风格以及使用方法已经有着很高的耦合度了,所以我将它放在STL这里一起学习。

        string是一个字符串类,可以借助其提供的接口进行字符串的各种增删查改操作,它的底层其实就是一个存储字符数组的顺序表。

·        2、string中的常用接口

        事实上,string所提供的接口是有很多冗余的,大概有100多个接口,在实现部分都只涉及到一些常用的接口,通过这些常用的接口已经可以处理平常所能遇到的几乎所有情况了,如果需要了解所有接口或者对于以下接口的使用还不太熟悉的话,可以调转到以下网站了解:cplusplus.com - The C++ Resources Networkhttps://legacy.cplusplus.com/三、手撕一个string类

        1、string类的成员变量及整体的框架

        为了与库中的string区分,我们将在自己的命名空间中进行定义相关变量,这个命名空间的名字是无所谓的,为了方便起见,我将直接展开std命名空间,但是在真实场景下,不直接展开会更好一些 

        同时在这里说明一下,我们将在写一个函数之前先将库中的函数头放在前面,以其为导引,同时在这里简单的说明一下该函数的作用

        string类是一个字符类型的顺序表,所以需要一个内置的字符类型的指针来管理字符串中的内容,对于一个顺序表,需要记录它的长度,同时我们将动态开辟空间,所以记录此时string中的空间是多少也是非常重要的:
        

        涉及到类似顺序表的结构,_size、_capacity都是必要的,这个在以后也经常用到,同时在命名时,在成员变量之前加一个‘_’可以区分成员变量与外部变量

        2、类的构造函数与析构函数

        库中的函数头:
        

        以上是库中所提供的所有的构造函数,我们只实现图中圈出的两个,还有几个是拷贝构造函数,在后面会实现,同时由于析构函数名一定并且没有返回值,不传参,这里就不展示了

        一般来说构造函数我们更倾向于在初始化列表进行初始化,但是由于要开空间,同时对于一个内置类型来说,在函数体内赋值和在初始化列表进行初始化的效率差别不是很大,所以我都采用函数体内赋值:
        

       可以看到,在上面我们只实现了一个构造函数但是由于它是带缺省值的,如果不传参默认会构造一个空串,所以与库中的函数是配合的,需要注意的是,在开空间时一定要多开一个空间,这个位置是留给‘\0’的

        3、类中留给类外读取信息的几个简单函数

        库中的函数头:

                (1).size函数:返回此时字符串中的元素个数
                

                (2).c_str函数:返回一个c风格的字符串

                

                这两个函数实现起来是很简单的,就不做过多的介绍了:
                

        4、[]操作符重载

        库中的函数头:
        

        []操作符重载的作用是让我们实现的类类型可以像正常的数组一样使用[]访问下标的方式来访问字符串中指定位置的内容,使用起来很方便,同时代码的可读性变高

        可以看到,该操作重载有两个函数重载,这两个函数重载都是很有必要的,当this指针不被const修饰时,函数提供给非const类型使用,可读可写;当this指针被const修饰时,函数提供给const类型使用,只读不写:
        

        可以看到,该函数的实现也是非常的方便的,并且两个重载的内容也一模一样,这时候我们可以使用模板来简化代码,但由于这是很简单的两个重载,所以没有必要,在以后STL中其它部分,我们会大量的使用模板的相关知识

        5、完成迭代器的封装

        库中的函数头:
                (1).begin

                

                (2).end

                

        迭代器是STL的相关容器中的一个重要工具,常被用来遍历容器,它是类中定义的一个类型,使用起来和指针非常相似,在类中定义类型有两种方式,封装内部类和typedef,很明显使用指针就可以直接遍历一个字符类型的数组,所以string类的迭代器我们可以直接使用typedef封装字符指针的方式

        了解迭代器之后,我们还需要了解begin和end是留给用户使用迭代器的两个接口,begin返回指向首元素的迭代器,end返回指向末尾元素下一个位置的迭代器,它们也都进行了重载,同样分为只读不写和可读可写,在之前已经详细介绍过,这里就不多介绍了:
        

        6、一个特殊的常量

        库中的介绍:
        

        在库中定义了一个静态成员变量npos,它的值是-1,但由于它是size_t类型的,所以实质上是一个非常大的正整数,我们默认一个自负床不会有这么长,所以在库中的函数将npos这一静态成员变量当作无穷大来使用:
        

        在这里需要注意:类中的静态成员变量一定要记得在类外进行定义,虽然const修饰的静态成员变量是可以i直接在类内声明时直接定义的,但为了统一性,这里我还是在类外进行了定义

       7、拷贝构造函数

        库中的函数头:
        

        拷贝构造函数的作用在这里就不多介绍了,它在之后的函数返回一个string类型、传参时传一个string类型、赋值运算符重载时都会用到:
        

        8、开空间函数

        库中的函数头:
        reserve:开指定大小的空间,将原数据进行拷贝,剩余部分不做处理

        

        

        9、插入相关函数-----append

        库中的函数头:
        

        append函数是用于在字符串末尾插入的函数,可以看到库中提供了很多的函数重载,接下来我们只会实现以上圈出的几个,同时我们将借助缺省值简化代码:
               

        纠正一下:不小心忘记进行:_size += n\_size += sublen啦,这是在写完之后测试的时候发现的,所以一定要边写边测试啊!!!

        10、交换函数

        库中的函数头:
        

        swap函数顾名思义就是交换两个string类对象,需要注意的是我们将完成深拷贝,也就是说要重新开空间,而不能简单的交换两个类对象的成员变量,这时候我们可以借助算法库中提供的swap函数,它可以帮助我们完成深拷贝,事实上我们可以直接进行深拷贝,但是借助算法库实现会更简单:

        

        需要注意的是:在这里我们需要指定标准命名空间

        11、赋值运算符重载

        库中的函数头:
        

        库中海还有该函数的其它重载,但其实都可以通过该函数完成剩余操作,所以只实现这一个,同时,由于我们可以借助一种更简单的方式来完成该函数的实现,所以接下来我对该函数的实现时,函数头与库中的略有差异,但是对于用户的使用是相同的:
        

        对以上代码做一下解释:我们的函数实参传给形参会进行临时拷贝,这是会调用我们之前实现过的拷贝构造函数进行深拷贝,再交换*this与str,*this就变成了str,str是形参,出作用域销毁,调用析构函数,不会造成内存泄漏,*this出函数还存在,进行传引用返回

        12、其它运算符重载

        由于接下来都是正常的运算符重载,功能也是显而易见的,所以在这里就不提供库中的函数头了,直接进行相关的实现:
        

//其它运算符重载//+=运算符重载
string& operator+=(const string& str)
{return append(str);
}
string& operator+=(const char* s)
{return append(s);
}
string& operator+=(char c)
{return append(1, c);
}
//+运算符重载
string operator+(const string& str)
{string strtmp(*this);return (strtmp += str);
}
string operator+(const char* s)
{string strtmp(*this);return (strtmp += s);
}
string operator+(char c)
{string strtmp(*this);return (strtmp += c);
}
//<运算符重载
bool operator < (const string& s) const
{int minlen = _size;if (s._size < _size){minlen = s._size;}for (int i = 0; i < minlen; i++){if (_str[i] != s._str[i]){return _str[i] < s._str[i];}}return _size < s._size;
}
//==运算符重载
bool operator==(const string& s) const
{if (_size != s._size) return false;for (int i = 0; i < _size; i++){if (_str[i] != s._str[i]){return false;}}return true;
}
//<=运算符重载
bool operator<=(const string& s) const
{return ((*this < s) || (*this == s));
}
//>运算符重载
bool operator>(const string& s) const
{return (!(*this < s) && !(*this == s));
}
//>=运算符重载
bool operator>=(const string& s) const
{return ((*this > s) || (*this == s));
}
//!=运算符重载
bool operator!=(const string& s) const
{return !(*this == s);
}

        以上就是所有可能用到的运算符重载了,由于很长,所以我将代码直接放在上面,可以看到,由于这些逻辑运算符有天然的互斥性,所以我们可以很好的进行代码复用,大大提高了代码的质量

        13、插入相关函数-----push_back

        库中的函数头:
        

        该函数的作用是在字符串末尾插入一个字符,无返回值:
        

        在这个位置我们可以直接进行代码复用

        14、插入相关函数-----insert

        库中的函数头:
        

        库中对于插入函数也是提供了非常多的重载,我们只实现图中圈出的几个,同时我们会使用缺省值的方式简化代码:
         

//插入函数
string& insert(size_t pos, const string& str, size_t subpos = 0, size_t sublen = npos)
{assert(pos < _size);assert(subpos < str._size);if (subpos + sublen >= str._size) sublen = str._size - subpos;if (_capacity < _size + sublen + 1){reserve(_size + sublen + 1 + 0.5 * sublen);}int end = _size , next = end + sublen;while (next > pos){_str[next--] = _str[end--];}for (int i = 0; i < sublen; i++){_str[pos + i] = str._str[i];}_size += sublen;return *this;
}
string& insert(size_t pos, const char* s, size_t n = npos)
{string strtmp(s);return insert(pos, s, 0, n);
}
string& insert(size_t pos, size_t n, char c)
{assert(pos < _size);if (_capacity < _size + n + 1){reserve(_size + n + 1 + 0.5 * n);}int end = _size, next = end + n;while (next > pos){_str[next] = _str[end];}for (int i = 0; i < n; i++){_str[pos + n] = c;}_size += n;return *this;
}

        可以看到,在上面我们也进行了一定程度的代码复用,所以代码复用是非常重要的,可以大大提高代码质量

        15、删除函数

        库中的函数头:
        

        erase函数的作用是从pos位置开始,删除len长度的字符:
        

        16、查找相关函数

        库中的函数头:
        

        库中对于查找函数也是实现了多个函数重载,在这里我们只实现上面圈出的两个,其他的通过借助这两个函数也是都可以完成的:

        

        在上面的实现中我们借助了C语言常用的几个函数,简化了代码

        17、返回子串的函数

        库中的函数头:
        

        该函数的作用是返回从pos位置开始len长度的子串:

        

        18、清理函数

        库中的函数头:

        

        该函数可以清空字符串中的数据:
        

四、最终的string类

        终于,我们一起写完了一个简陋版的string类,需要注意的是在这个过程中我们更关注的是了解string的使用、加深对于类和对象等知识的理解和提升我们的代码能力,并不是写出一个更好的string类,最终完成的string类如下:
        

namespace bea
{class string{public://相关函数//构造函数string(const char* s = ""){int n = strlen(s);_str = new char[n + 1];memcpy(_str, s, n + 1);_size = n;_capacity = n;}//size函数size_t size() const{return _size;}//c_str函数const char* c_str() const{return _str;}//[]操作符重载// // 普通类型char& operator[](size_t pos){assert(pos < _size);return _str[pos];}//const类型const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}//迭代器相关接口//先进行类型的封装typedef char* iterator;typedef const char* const_iterator;//begin函数iterator begin(){return _str;}const_iterator begin() const{return _str;}//end函数iterator end(){return _str + _size;}const_iterator end() const{return _str + _size;}string(const string& str){_str = new char[str._capacity + 1];memcpy(_str, str._str, _size + 1);_size = str._size;_capacity = str._capacity;}//开空间函数void reserve(size_t n = 0){if (n > _capacity){char* strtmp = new char[n + 1];memcpy(strtmp, _str, _size + 1);delete[] _str;_str = strtmp;_capacity = n;}}//尾接函数//尾接C风格字符串//接入s字符串的前n个字符string& append(const char* s, size_t n = npos){size_t len = strlen(s);if (n > len) n = len;if (_capacity < _size + n + 1){reserve(_size + n + 1 + 0.5 * n);}for (int i = 0; i < n; i++){_str[_size + i] = s[i];}_str[_size + n + 1] = '\0';_size += n;return *this;}//尾接一个string//接入str的subpos开始sublen长度的子串string& append(const string& str, size_t subpos = 0, size_t sublen = npos){assert(subpos < str._size);if (subpos + sublen >= str._size) sublen = _size - subpos;if (_capacity < _size + sublen + 1){reserve(_size + sublen + 1 + 0.5 * sublen);}for (int i = 0; i < sublen; i++){_str[_size + i] = str._str[i];}_str[_size + sublen + 1] = '\0';_size += sublen;return *this;}//尾接n个chstring& append(size_t n, char ch){if (_capacity < _size + n + 1){reserve(_size + n + n * 0.5);}for (int i = 0; i < n; i++){_str[_size + i] = ch;}_str[_size + n + 1] = '\0';_size += n;return *this;}//交换函数void swap(string& str){std::swap(_str, str._str);std::swap(_size, str._size);std::swap(_capacity, str._capacity);}//赋值运算符重载string& operator=(string str){swap(str);return *this;}//其它运算符重载//+=运算符重载string& operator+=(const string& str){return append(str);}string& operator+=(const char* s){return append(s);}string& operator+=(char c){return append(1, c);}//+运算符重载string operator+(const string& str){string strtmp(*this);return (strtmp += str);}string operator+(const char* s){string strtmp(*this);return (strtmp += s);}string operator+(char c){string strtmp(*this);return (strtmp += c);}//<运算符重载bool operator < (const string& s) const{int minlen = _size;if (s._size < _size){minlen = s._size;}for (int i = 0; i < minlen; i++){if (_str[i] != s._str[i]){return _str[i] < s._str[i];}}return _size < s._size;}//==运算符重载bool operator==(const string& s) const{if (_size != s._size) return false;for (int i = 0; i < _size; i++){if (_str[i] != s._str[i]){return false;}}return true;}//<=运算符重载bool operator<=(const string& s) const{return ((*this < s) || (*this == s));}//>运算符重载bool operator>(const string& s) const{return (!(*this < s) && !(*this == s));}//>=运算符重载bool operator>=(const string& s) const{return ((*this > s) || (*this == s));}//!=运算符重载bool operator!=(const string& s) const{return !(*this == s);}//push_back函数void push_back(char c){*this += c;}//插入函数string& insert(size_t pos, const string& str, size_t subpos = 0, size_t sublen = npos){assert(pos < _size);assert(subpos < str._size);if (subpos + sublen >= str._size) sublen = str._size - subpos;if (_capacity < _size + sublen + 1){reserve(_size + sublen + 1 + 0.5 * sublen);}int end = _size , next = end + sublen;while (next > pos){_str[next--] = _str[end--];}for (int i = 0; i < sublen; i++){_str[pos + i] = str._str[i];}_str[pos + sublen + 1] = '\0';_size += sublen;return *this;}string& insert(size_t pos, const char* s, size_t n = npos){string strtmp(s);return insert(pos, s, 0, n);}string& insert(size_t pos, size_t n, char c){assert(pos < _size);if (_capacity < _size + n + 1){reserve(_size + n + 1 + 0.5 * n);}int end = _size, next = end + n;while (next > pos){_str[next] = _str[end];}for (int i = 0; i < n; i++){_str[pos + n] = c;}_str[pos + n + 1] = '\0';_size += n;return *this;}//删除函数string& erase(size_t pos = 0, size_t len = npos){assert(pos < _size);if (pos + len >= _size){_str[pos] = '\0';_size -= len;return *this;}int end = pos + len, front = pos;while (front < _size){_str[front++] = _str[end++];}_size -= len;_str[pos + len + 1] = '\0';return *this;}//找字符函数size_t find(char ch, size_t pos = 0){assert(pos < _size);for (int i = pos; i < _size; i++){if (_str[i] == ch) return i;}return npos;}//找字符串size_t find(const char* str, size_t pos = 0){assert(pos < _size);const char* ptr = strstr(_str + pos, str);if (ptr){return ptr - _str;}else{return npos;}}//子串函数string substr(size_t pos = 0, size_t len = npos){if (pos == 0 && len >= _size) return *this;if (pos + len >= _size) len = _size - pos;string tmp;for (int i = pos; i < pos + len; i++) tmp.push_back(_str[i]);return tmp;}//清除函数void clear(){_str[0] = '\0';_size = 0;}//析构函数~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str;size_t _size;size_t _capacity;static const size_t npos;};const size_t string::npos = -1;
}

五、结语

        以上就是本期关于手撕string类的所有内容了,感谢大家的阅读,欢迎各位于晏、亦菲和我一起交流、学习、进步!!!        

        

        
        
        

      

                

             
                        

        
        

                                

                

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

低版本GUI配置SAProuter

1、注意配置SAProuter时&#xff0c;必须添加后面的/H/ 如&#xff1a;/H/sap.sapzx.cn/H/ 2、或者有时需要配置service文件&#xff08;C:\WINDOWS \system32\drivers\etc\service&#xff09; sapmsEP1 3600/tcp

springBoot中自定义一个validation注解,实现指定枚举值校验

缘由 在后台写接口的时候&#xff0c;经常会出现dto某个属性是映射到一个枚举的情况。有时候还会出现只能映射到枚举类中部分枚举值的情况。以前都是在service里面自行判断&#xff0c;很多地方代码冗余&#xff0c;所以就想着弄一个自定义的validation注解来实现。 例如下面某…

MySQL数据库中篇

#作者&#xff1a;允砸儿 #日期&#xff1a;乙巳青蛇年 四月初九 笔者继续带朋友们了解mysql数据库中篇的内容。多了不说&#xff0c;少了不唠&#xff0c;咱们直接就开写。 书接上回笔者在上篇中介绍了什么是数据库和数据库的一些基础的概念&#xff0c;以及mysql数据库的…

AI如何重塑DDoS防护行业?六大变革与未来展望

随着AI技术的深度渗透&#xff0c;DDoS防护行业正经历一场从“规则驱动”到“智能驱动”的范式革命。传统依赖静态阈值和人工规则的防御模式已难以应对新型攻击&#xff0c;而AI的引入不仅提升了检测精度&#xff0c;更重构了防护体系的底层逻辑。以下是AI带来的六大核心变革及…

五一作业-day04

文章目录 1. **ps -ef是显示当前系统进程的命令,统计下当前系统一共有多少进程**2. **last命令用于显示所用用户最近1次登录情况,awk可以取出某一列,现在要取出last命令第1列并去重统计次数**3. **secure日志是用户的登录日志,过滤出secure日志中的Failed password的次数(用课堂…

抽奖系统(基于Tkinter)

一、抽奖规则及使用方法 抽奖规则&#xff1a; 从1-138个号码中随机抽奖&#xff0c;共进行n轮抽奖&#xff0c;每个号码仅有一次中奖机会&#xff0c;即已中奖的号码不会再次中奖。 使用方法&#xff1a; 要求开始抽奖后屏幕上随机滚动显示中奖号码&#xff0c;点击“STOP”之…

window 系统 使用ollama + docker + deepseek R1+ Dify 搭建本地个人助手

1. 下载ollama &#xff0c;官网 下载地址&#xff1a;Download Ollama on macOS&#xff0c;选择 Window 下载完成后&#xff0c;可在终端 使用 ollama --version 2. 下载 本地大模型&#xff0c;这里下载deepseek r1 7b 3.下载Embed模型 Embed模型 是文本工具向量化的核心工…

【学习笔记】 强化学习:实用方法论

作者选择了由 Ian Goodfellow、Yoshua Bengio 和 Aaron Courville 三位大佬撰写的《Deep Learning》(人工智能领域的经典教程&#xff0c;深度学习领域研究生必读教材),开始深度学习领域学习&#xff0c;深入全面的理解深度学习的理论知识。 之前的文章参考下面的链接&#xf…

益鑫通汽车连接器可替代Molex,JST

# 探秘优质车规连接器 在汽车向新能源和智能化发展的进程中&#xff0c;车规连接器对汽车电子系统的稳定运行至关重要。有企业凭借技术与创新&#xff0c;在该领域表现出色。其车规连接器类型多样&#xff0c;能满足汽车不同系统连接需求。 一款2.54Pitch线对板连接器&#xff…

【WPF】将Bitmap图像转换为BitmapImage,并给Image控件显示图像

1.C#将Bitmap图像转换为BitmapImage&#xff0c;并给Image控件显示图像后台实现 public void InitImage(Bitmap bitmap){try{// 将Bitmap转换为WPF的BitmapImageBitmapImage bitmapImage;using (MemoryStream memory new MemoryStream()){bitmap.Save(memory, System.Drawing.…

Python从入门到高手8.2节-元组的常用操作符

目录 ​8.2.1 元组的常用操作符 8.2.2 []操作符: 索引访问元组 8.2.3 [:]操作符&#xff1a;元组的切片 8.2.4 操作符&#xff1a;元组的加法 8.2.5 *操作符&#xff1a;元组的乘法 8.2.6 元组的关系运算 8.2.7 in操作符&#xff1a;查找元素 8.2.8 五一她玩了个狗吃…

Vue3源码学习4-effect中为什么使用WeakMap,Set?

文章目录 前言1. 精细化依赖追踪2. 高效的依赖收集与触发3. 自动内存管理&#xff0c;防止内存泄漏4. 支持复杂场景 前言 在 mini vue - effect 实现中 设计&#xff08;WeakMap → Map → Set → effect函数&#xff09;有以下几个重要原因&#xff1a; 1. 精细化依赖追踪 W…

TinyML 边缘智能:在资源受限 MCU 上部署 AI

前言 在物联网(IoT)和智能边缘计算的时代浪潮下,TinyML(微型机器学习)正以前所未有的速度改变着我们与设备交互的方式。它将 AI 推理能力放在资源极度受限的 MCU(微控制器)上,兼顾实时性、低功耗和数据隐私,成为智能家居、可穿戴设备、工业检测等场景的核心技术。尽管…

技术白皮书:Oracle GoldenGate 优势

本文为技术白皮书Oracle GoldenGate 优势的翻译及阅读笔记。以下注释中GoldenGate为OGG。 副标题为&#xff1a;Oracle 数据库的变更数据捕获 (CDC) 技术比较。版本为July, 2021, Version 2.1。 Oracle GoldenGate 被客户和分析师公认为功能最齐全、性能最高、最值得信赖的数…

Android控件VideoView用法

一 控件UI <VideoViewandroid:id="@+id/videoView"android:layout_width="match_parent"android:layout_height="match_parent"android:scaleType="fitCenter" /> 二 配置 <?xml version="1.0" encoding="u…

React 第三十六节 Router 中 useParams 的具体使用及详细介绍

一、useParams 的基本用法 用途&#xff1a;用于在组件中获取当前 URL 的动态路由参数&#xff08;如 /user/:id 中的 id&#xff09;。 import { Routes, Route, useParams } from react-router-dom;// 定义路由 function App() {return (<Routes><Route path"…

C++战胜白蚁 2024年信息素养大赛复赛 C++小学/初中组 算法创意实践挑战赛 真题详细解析

目录 C++战胜白蚁 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、运行结果 五、考点分析 六、 推荐资料 1、C++资料 2、Scratch资料 3、Python资料 C++战胜白蚁 2024年信息素养大赛 C++复赛真题 一、题目要求 1、编程实现 小明因为很长…

Linux网络编程 day4

inet_pton&#xff1a;IP 字符串 → 网络字节序地址 ntohl&#xff1a;网络字节序 → 主机字节序 TCP状态转换图(重点) 可以通过下面这行代码查看目前网络状态 netstat -apn | grep client 1、主动发起请求端 close-->SYN-->SYN_SENT-->接收ACK、SYN-->SYN_SEN…

基于springboot+vue的个人财务管理系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat12开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;Maven3.3.9 系统展示 用户信息管理 账…

ffmpeg 元数据-avformatcontext字段 AVDictionary *metadata;

ffmpeg 元数据 1. 解释什么是ffmpeg元数据 ffmpeg元数据是指与音视频文件相关的附加信息&#xff0c;这些信息不直接影响音视频内容的播放&#xff0c;但提供了关于文件内容、创作者、版权、播放参数等的有用信息。元数据在音视频文件的处理、管理和共享中起着重要作用。 2.…