目录

1.构造函数

2.析构函数

3.扩容

   1.reserve(扩容不初始化)

   2.resize(扩容加初始化)

4.push_back

5.append

6. += 运算符重载

  1.+=一个字符

  2.+=一个字符串

7 []运算符重载

8.find

1.找一个字符 

2.找一个字符串

9.insert

 1.插入一个字符

 2.插入一个字符串

9.erase

10.substr

11.运算符重载比较大小

         1.<

        2.==

        3.<=

        4.>

        5.>=

        6.!=

12.拷贝构造

13.赋值 =

14.<<

15.>>

16.迭代器

完整代码实现


      首先我们先定义头文件String.h,和命名空间_string防止和库中的string发生名字冲突

#include <iostream>
#include <cassert>
#include <string.h>
using namespace std;namespace _string {class string {public:static size_t npos;protected:char*  _str;size_t _size;size_t _capacity;}size_t string::npos = -1;
}

        其是我们的string类里面就是一个顺序表, 所以我们会定义一个数组_str,_size为实际字符所占的大小,_capacity为容量,如果达到了最大的容量我们就是_size == _capacity 我们就要进行扩容处理.

1.构造函数

string(const char* str = ""):_str(new char[strlen(str) + 1]),_size(strlen(str)),_capacity(strlen(str)){memcpy(_str, str, _size + 1);}

        这里我们为什么要多开一个空间strlen(str) + 1呢?因为我们这里字符串后面会有'\0',我们要多开一个位置给'\0',但是为什么_size和_capacity为什么不用+1呢?因为我们的'\0' 不算大小,所以我们_size和_capacity不需要给多一个空间.

        这里需要注意的是,我们开好空间的时候不要忘记去把str的内容拷贝的_str中,这里拷贝_size + 1是因为有'\0'也是需要拷贝的.

2.析构函数

~string() {if (_str) {delete[] _str;_size = _capacity = 0;}
}

        这里析构函数我认为,如果_str为空才需要释放空间,不为空不需要判断,所以我们这里加上一个判断,不为空的时候才可以去释放空间。

3.扩容

          1.reserve(扩容不初始化)

void reserve(size_t n) {if (n > _capacity) {char* tmp = new char[n + 1];memcpy(tmp, _str, _size + 1);delete[] _str;_str = tmp;_capacity = n;}
}

   扩容的时候需要进行判断,如果n大于容量扩容才有意义,如果小于就没有意义了

        2.resize(扩容加初始化)

void resize(size_t n , char ch = '\0') {if (n < _size) {_size = n;_str[_size] = '\0';}else {reserve(n);while (_size != n) {_str[_size++] = '\0';}_str[_size] = '\0';}
}

        resize如果给的n容量会继续缩小,所以当n < _size的时候我们直接把n位置变为'\0'就可以了

如果这里的n > _ size,就会出现两种情况,一种是  _capacity > n > _size 还有一种是 n > _capacity

因为我们写了扩容reserve,里面有判断,所以我们之间继续复用就可以了,最后再完成数据的拷贝就可以了.

4.push_back

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

5.append

void append(const char* str) {size_t len = strlen(str);if (_size + len > _capacity) {reserve(_size + len);}memcpy(_str+_size,str,len + 1);_size += len;
}

        push_back和append都一样,使用时应该要检查容量是否足够,不够的时候再扩容,最后完成数据的拷贝就可以了.

6. += 运算符重载

        1.+=一个字符

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

        2.+=一个字符串

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

      我们上面实现了push_back()和append()所以这里我们实现复用就可以了.

7 []运算符重载

//只读
const char operator[](size_t pos) const {assert(pos < _size);return _str[pos];
}//读写
char operator[](size_t pos) {assert(pos < _size);return _str[pos];
}

        我们在进行重载的时候应该注意,pos的范围不能超过_size的大小.

8.find

1.找一个字符 

            找一个字符返回当前位置

//默认从0位置开始size_t find(char ch, size_t pos = 0) {assert(pos < _size);for (size_t i = pos; i < _size; i++) {if (_str[i] == ch) {return i;}}return npos;
}

2.找一个字符串

        找一个字符串返回第一个字符出现的位置.

size_t find(const char* str, size_t pos) {assert(pos < _size);char* ptr = strstr(_str, str);if (ptr) {return ptr - _str;}else {return npos;}
}

        什么两个如果都没有找到的话,会返回npos,npos是string中静态变量,为无符号数的最大值.

9.insert

        1.插入一个字符

void insert(size_t pos , char ch) {assert(pos < _size);if (_size == _capacity) {reserve(_capacity == 0 ? 4 : _capacity*2);}//'\0'也要一起移动size_t end = _size;while (end >= pos &&  end != npos) {_str[end + 1] = _str[end];end--;}_str[pos] = ch;_size++;
}

        2.插入一个字符串

void insert(size_t pos, const char* str) {size_t len = strlen(str);if (_size + len > _capacity) {reserve(_size + len);}	//移动数据size_t end = _size;while (end >= pos && end != npos) {_str[end + len] = _str[end];end--;}//拷贝数据for (size_t i = 0; i < len; i++) {_str[pos + i] = str[i];}_size += len;
}

        我们两个函数都需要注意的是移动数据的时候这里是end--,如果没有npos != end,pos的位置是0,会造成死循环的问题,这是为什么?

        没有npos != end,如果pos的位置是0,那么循环的结束条件应该是end < 0,但是这里的end是无符号数,当end到达-1时相当于正数的最大值,不可能小于0. 所以我们这里应该要加上npos != end;

9.erase


void erase(size_t pos = 0 , size_t len = npos) {// '\0'也要移动所以可以等于 _size;assert(pos <= _size);if (pos + len >= _size || len == npos) {_str[_size] = '\0';_str[pos] = '\0';_size = pos;}else {size_t end = pos + len;while (end <= _size) {_str[pos++] = _str[end++];}_size -= len;}
}

        从pos位置开始删除,长度为len的字符. 如果pos + len >= _size 或者说 len说民删除完后面的字符, 直接把pos换成'\0'就可以了.

        如果pos + len < _size 就移动数据,为什么end <= _size ? 因为'\0'也要被移动.

10.substr

string substr(size_t pos = 0,size_t len = npos) {assert(pos < _size);//调整len的大小if (pos + len >= _size || len == npos) {len = _size - pos;}//预留好空间string tmp;tmp.reserve(len);for (size_t i = 0; i < len; i++){tmp += _str[pos + i];}tmp += '\0';return tmp;
}

        不要忘记最后一个位置要加上'\0'

11.运算符重载比较大小

         1.<

        我们要注意有三种特殊的情况

hello helloo true
hello hell false
hello hello false

        我们给两个长度len1,len2从0开始遍历碰到第一个小于就返回true,大于返回false,要是等于就继续len1和len2都要加加.

        如果出了循环,我们看上面三种情况,只有len2 < str._size && len1 == _size 才能返回true

其他都是false,所以我们直接返回len2 < str._size && len1 == _size就可以了

bool operator<(const string& str) const {size_t len1 = 0, len2 = 0;while (len1 < _size && len2 < str._size) {if (_str[len1] < str._str[len2]) {return true;}else if (_str[len1] > str._str[len2]) {return false;}else {len1++;len2++;}}return len1 == _size && len2 < str._size;
}

        2.==

bool operator==(const string& str) const {if (_size != str._size) {return false;}else {size_t len1 = 0, len2 = 0;while (len1 < _size && len2 < str._size) {if (_str[len1] < str._str[len2]) {return false;}else if (_str[len1] > str._str[len2]) {return false;}else {len1++;len2++;}}return true;}
}

       如果两个字符串的长度不相等直接返回false就可以了,长度如果相等有不相等的两个字符也是直接返回false,如果能出循环直接返回true;

        我们上面实现了< 和 = 我们直接复用就可以了

        3.<=

bool operator<=(const string& str) const {return *this < str || *this == str;
}

        4.>

bool operator>(const string& str) const {return !(*this <= str);
}

        5.>=

bool operator>=(const string& str) const {return !(*this < str);
}

        6.!=

bool operator!=(const string& str) const {return !(*this == str);
}

12.拷贝构造

        类里面默认生成的拷贝构造为浅拷贝,如果我们直接用默认生成的赋值,会造成析构函数释放同一块空间两次的问题编译器会就行报错,所以我们需要实现深拷贝.

string(const string& str) {//要多给一个位置给'\0'char* _str = new char[str._capacity + 1];_size = str._size;_capacity = str._capacity;//'\0' 也要进行拷贝所以拷贝_size + 1memcpy(_str,str._str,str._size + 1);
}

13.赋值 =

        和上面的拷贝构造一样,必须要实现深拷贝否则编译器会报错.

void swap(string& str) {std::swap(_str, str._str);std::swap(_size, str._size);std::swap(_capacity, str._capacity);
}//str为临时变量出了作用域就会销毁
//交换后不用就行处理
string& operator=(string str) {swap(str);return *this;
}

        这里有很多人不明白赋值这里参数为什么不传引用?这里的string没有引用是一个临时变量,处理作用域就会销毁.所以我们把*this 的值和str的值进行交换不会有任何的问题

        这里的<< 和 >> 我们一般不喜欢写在类里面,如果写在类里面,第一个参数默认是this指针,那么我们调用的时候就需要反过来写,不符合我们的习惯.

str >> cin   

str << cout 

14.<<

//按照_size的大小来进行打印
//不是碰到'\0'就停止
ostream& operator<<(ostream& _cout, const string& str) {for (size_t i = 0; i < str.size(); i++){_cout << str[i];}return _cout;
}

        这里需要注意的是打印的是按照_size去打印,不是碰到'\0'就停下.

15.>>

//每次输入之前都要清空
istream& operator>>(istream& _cin, string& str) {	//清空str.clear();char ch;//给一buff数组来减小拷贝来提高效率int i = 0;char buff[128] = { 0 };//读取前置空格ch = _cin.get();while (ch == '\n' || ch == ' ')ch = _cin.get();while (ch != '\n' && ch != ' ') {buff[i++] = ch;if (i == 127) {//最后一个位置给'\0'buff[i] = '\0';str += buff;i = 0;}ch = _cin.get();}//如果i不等于0说明里面还有字符if (i != 0) {buff[i] = '\0';str += buff;}return _cin;
}

16.迭代器

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

完整代码实现

namespace _String {class string {friend ostream& operator<<(ostream& _cout, const string& str);friend istream& operator>>(istream& _cin, string& str);public:typedef char* iterator;typedef const char* const_iterator;iterator begin() {return _str;}iterator end() {return _str + _size;}const_iterator begin() const {return _str;}const_iterator end() const {return _str + _size;}string(const char* str = ""):_str(new char[strlen(str) + 1]), _size(strlen(str)), _capacity(strlen(str)){memcpy(_str, str, _size + 1);}~string() {if (_str) {delete[] _str;_size = _capacity = 0;}}string(const string& str) {//要多给一个位置给'\0'char* _str = new char[str._capacity + 1];_size = str._size;_capacity = str._capacity;//'\0' 也要进行拷贝所以拷贝_size + 1memcpy(_str,str._str,str._size + 1);}void swap(string& str) {std::swap(_str, str._str);std::swap(_size, str._size);std::swap(_capacity, str._capacity);}//str为临时变量出了作用域就会销毁//交换后不用就行处理string& operator=(string str) {swap(str);return *this;}//只读const char operator[](size_t pos) const {assert(pos < _size);return _str[pos];}//读写char operator[](size_t pos) {assert(pos < _size);return _str[pos];}// hello helloo true// hello hell false;// hello hello falsebool operator<(const string& str) const {size_t len1 = 0, len2 = 0;while (len1 < _size && len2 < str._size) {if (_str[len1] < str._str[len2]) {return true;}else if (_str[len1] > str._str[len2]) {return false;}else {len1++;len2++;}}return len1 == _size && len2 < str._size;}bool operator==(const string& str) const {if (_size != str._size) {return false;}else {size_t len1 = 0, len2 = 0;while (len1 < _size && len2 < str._size) {if (_str[len1] < str._str[len2]) {return false;}else if (_str[len1] > str._str[len2]) {return false;}else {len1++;len2++;}}return true;}}bool operator<=(const string& str) const {return *this < str || *this == str;}bool operator>(const string& str) const {return !(*this <= str);}bool operator>=(const string& str) const {return !(*this < str);}bool operator!=(const string& str) const {return !(*this == str);}void reserve(size_t n) {if (n > _capacity) {char* tmp = new char[n + 1];memcpy(tmp, _str, _size + 1);delete[] _str;_str = tmp;_capacity = n;}}void resize(size_t n, char ch = '\0') {if (n < _size) {_str[_size] = '\0';_size = n;_str[_size] = '\0';}else {//会自己判断不用加reserve(n);while (_size != n) {_str[_size++] = '\0';}_str[_size] = '\0';}}void push_back(char ch) {if (_size == _capacity) {reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size++] = ch;_str[_size] = '\0';}void insert(size_t pos , char ch) {assert(pos <= _size);if (_size == _capacity) {reserve(_capacity == 0 ? 4 : _capacity*2);}//'\0'也要一起移动size_t end = _size;while (end >= pos &&  end != npos) {_str[end + 1] = _str[end];end--;}_str[pos] = ch;_size++;}void insert(size_t pos, const char* str) {assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity) {reserve(_size + len);}//移动数据size_t end = _size;while (end >= pos && end != npos) {_str[end + len] = _str[end];end--;}//拷贝数据for (size_t i = 0; i < len; i++) {_str[pos + i] = str[i];}_size += len;}void erase(size_t pos = 0 , size_t len = npos) {// '\0'也要移动所以可以等于 _size;assert(pos <= _size);if (pos + len >= _size || len == npos) {_str[_size] = '\0';_str[pos] = '\0';_size = pos;}else {size_t end = pos + len;while (end <= _size) {_str[pos++] = _str[end++];}_size -= len;}}//如果len == npos 说明从pos位置开始截完全部string substr(size_t pos = 0,size_t len = npos) {assert(pos < _size);//调整len的大小if (pos + len >= _size || len == npos) {len = _size - pos;}//预留好空间string tmp;tmp.reserve(len);for (size_t i = 0; i < len; i++){tmp += _str[pos + i];}tmp += '\0';return tmp;}//默认从0位置开始size_t find(char ch, size_t pos = 0) {assert(pos < _size);for (size_t i = pos; i < _size; i++) {if (_str[i] == ch) {return i;}}return npos;}//默认0位置开始size_t find(const char* str, size_t pos = 0) {assert(pos < _size);char* ptr = strstr(_str, str);if (ptr) {return ptr - _str;}else {return npos;}}void append(const char* str) {size_t len = strlen(str);if (_size + len > _capacity) {reserve(_size + len);}//'\0'也要就行拷贝memcpy(_str + _size, str, len + 1);_size += len;}string& operator+=(char ch) {push_back(ch);return *this;}string& operator+=(const char* str) {append(str);return *this;}const char* c_str() const {return _str;}size_t size() const {return _size;}void clear() {_str[0] = '\0';_size = 0;}static size_t npos;protected:char* _str;size_t _size;size_t _capacity;};size_t string::npos = -1;//按照_size的大小来进行打印//不是碰到'\0'就停止ostream& operator<<(ostream& _cout, const string& str) {for (size_t i = 0; i < str.size(); i++){_cout << str[i];}return _cout;}//每次输入之前都要清空istream& operator>>(istream& _cin, string& str) {//清空str.clear();char ch;//给一buff数组来减小拷贝来提高效率int i = 0;char buff[128] = { 0 };//读取前置空格ch = _cin.get();while (ch == '\n' || ch == ' ')ch = _cin.get();while (ch != '\n' && ch != ' ') {buff[i++] = ch;if (i == 127) {buff[i] = '\0';str += buff;i = 0;}ch = _cin.get();}//如果i不等于0说明里面还有字符if (i != 0) {buff[i] = '\0';str += buff;}return _cin;}
}

        如果有错误,欢迎各位大佬指正.

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

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

相关文章

学习笔记(24): 机器学习之数据预处理Pandas和转换成张量格式[2]

学习笔记(24): 机器学习之数据预处理Pandas和转换成张量格式[2] 学习机器学习&#xff0c;需要学习如何预处理原始数据&#xff0c;这里用到pandas&#xff0c;将原始数据转换为张量格式的数据。 学习笔记(23): 机器学习之数据预处理Pandas和转换成张量格式[1]-CSDN博客 下面…

LeetCode 2297. 跳跃游戏 VIII(中等)

题目描述 给定一个长度为 n 的下标从 0 开始的整数数组 nums。初始位置为下标 0。当 i < j 时&#xff0c;你可以从下标 i 跳转到下标 j: 对于在 i < k < j 范围内的所有下标 k 有 nums[i] < nums[j] 和 nums[k] < nums[i] , 或者对于在 i < k < j 范围…

【前端】缓存相关

本知识页参考&#xff1a;https://zhuanlan.zhihu.com/p/586060532 1. 概述 1.1 应用场景 静态资源 场景&#xff1a;图片、CSS、JS 文件等静态资源实现&#xff1a;使用 HTTP 缓存控制头&#xff0c;或者利用 CDN 进行边缘缓存 数据缓存 场景&#xff1a;请求的返回结果实现…

猎板硬金镀层厚度:高频通信领域的性能分水岭

在 5G 基站、毫米波雷达等高频场景中&#xff0c;硬金镀层厚度的选择直接决定了 PCB 的信号完整性与长期可靠性。猎板硬金工艺&#xff1a; 1.8μm 金层搭配罗杰斯 4350B 基材的解决方案&#xff0c;在 10GHz 频段实现插入损耗&#xff1c;0.15dB/cm&#xff0c;较常规工艺降低…

第35次CCF计算机软件能力认证-5-木板切割

原题链接&#xff1a; TUOJ 我自己写的35分正确但严重超时的代码 #include <bits/stdc.h> using namespace std; int main() {int n, m, k;cin >> n >> m >> k;vector<unordered_map<int, int>> mp(2);int y;for (int i 1; i < n; …

【蓝桥杯】包子凑数

包子凑数 题目描述 小明几乎每天早晨都会在一家包子铺吃早餐。他发现这家包子铺有 NN 种蒸笼&#xff0c;其中第 ii 种蒸笼恰好能放 AiAi​ 个包子。每种蒸笼都有非常多笼&#xff0c;可以认为是无限笼。 每当有顾客想买 XX 个包子&#xff0c;卖包子的大叔就会迅速选出若干…

pikachu通关教程-目录遍历漏洞(../../)

目录遍历漏洞也可以叫做信息泄露漏洞、非授权文件包含漏洞等. 原理:目录遍历漏洞的原理比较简单&#xff0c;就是程序在实现上没有充分过滤用户输入的../之类的目录跳转符&#xff0c;导致恶意用户可以通过提交目录跳转来遍历服务器上的任意文件。 这里的目录跳转符可以是../…

[概率论基本概念4]什么是无偏估计

关键词&#xff1a;Unbiased Estimation 一、说明 对于无偏和有偏估计&#xff0c;需要了解其叙事背景&#xff0c;是指整体和抽样的关系&#xff0c;也就是说整体的叙事是从理论角度的&#xff0c;而估计器原理是从实践角度说事&#xff1b;为了表明概率理论&#xff08;不可…

面试题——计算机网络:HTTP和HTTPS的区别?

HTTP&#xff08;HyperText Transfer Protocol&#xff09;&#xff1a;作为互联网上应用最广泛的网络通信协议&#xff0c;HTTP是基于TCP/IP协议族的应用层协议。它采用标准的请求-响应模式进行通信&#xff0c;通过简洁的报文格式&#xff08;包含请求行、请求头、请求体等&a…

uni-app学习笔记十九--pages.json全局样式globalStyle设置

pages.json 页面路由 pages.json 文件用来对 uni-app 进行全局配置&#xff0c;决定页面文件的路径、窗口样式、原生的导航栏、底部的原生tabbar 等。 导航栏高度为 44px (不含状态栏)&#xff0c;tabBar 高度为 50px (不含安全区)。 它类似微信小程序中app.json的页面管理部…

SQL思路解析:窗口滑动的应用

目录 &#x1f3af; 问题目标 第一步&#xff1a;从数据中我们能直接得到什么&#xff1f; 第二步&#xff1a;我们想要的“7天窗口”长什么样&#xff1f; 第三步&#xff1a;SQL 怎么表达“某一天的前六天”&#xff1f; &#x1f50d;JOIN 比窗口函数更灵活 第四步&am…

解决MyBatis参数绑定中参数名不一致导致的错误问题

前言 作为一名Java开发者&#xff0c;我在实际项目中曾多次遇到MyBatis参数绑定的问题。其中最常见的一种情况是&#xff1a;在Mapper接口中定义的参数名与XML映射文件中的占位符名称不一致&#xff0c;导致运行时抛出Parameter xxx not found类异常。这类问题看似简单&#x…

黑马程序员TypeScript课程笔记—类型兼容性篇

类型兼容性的说明 因为传入的时候只有一个参数 对象之间的类型兼容性 接口之间的类型兼容性 函数之间的类型兼容性&#xff08;函数参数个数&#xff09; 和对象的兼容性正好相反 函数之间的类型兼容性&#xff08;函数参数类型&#xff09; 函数参数的兼容性就不要从接口角度…

智能电视的操作系统可能具备哪些优势

丰富的应用资源&#xff1a; 操作系统内置了应用商店&#xff0c;提供了丰富的应用资源&#xff0c;涵盖视频、游戏、教育等多个领域&#xff0c;满足不同用户的多样化需求。用户可以轻松下载并安装所需的应用&#xff0c;享受更多元化的娱乐和学习体验。 流畅的操作体验&…

Xget 正式发布:您的高性能、安全下载加速工具!

您可以通过 star 我固定的 GitHub 存储库来支持我&#xff0c;谢谢&#xff01;以下是我的一些 GitHub 存储库&#xff0c;很有可能对您有用&#xff1a; tzst Xget Prompt Library 原文 URL&#xff1a;https://blog.xi-xu.me/2025/06/02/xget-launch-high-performance-sec…

精美的软件下载页面HTML源码:现代UI与动画效果的完美结合

精美的软件下载页面HTML源码&#xff1a;现代UI与动画效果的完美结合 在数字化产品推广中&#xff0c;一个设计精良的下载页面不仅能提升品牌专业度&#xff0c;还能显著提高用户转化率。本文介绍的精美软件下载页面HTML源码&#xff0c;通过现代化UI设计与丰富的动画效果&…

麒麟v10+信创x86处理器离线搭建k8s集群完整过程

前言 最近为某客户搭建内网的信创环境下的x8s集群&#xff0c;走了一些弯路&#xff0c;客户提供的环境完全与互联网分离&#xff0c;通过yum、apt这些直接拉依赖就别想了&#xff0c;用的操作系统和cpu都是国产版本&#xff0c;好在仍然是x86的&#xff0c;不是其他架构&…

Pycharm的使用技巧总结

目录 一、高效便捷的快捷键 二、界面汉化处理 1.设置 2.插件 3.汉化插件安装 三、修改字体大小、颜色 1.选择文件-设置 2.选择编辑器-配色方案-python 3.修改注释行颜色 4.修改编辑器字体颜色 一、高效便捷的快捷键 序号快捷键功能场景效果1Ctrl /快速注释/取消注释…

安全编码规范与标准:对比与分析及应用案例

在软件开发领域&#xff0c;尤其是涉及安全关键系统的开发中&#xff0c;遵循编码规范和标准是确保软件质量和安全性的重要手段。除了CERT C、CERT Java和MISRA外&#xff0c;还有其他多个与安全相关的编码规范和标准&#xff0c;以下是一些主要标准的对比说明&#xff1a; 一…

FFmpeg学习笔记

1. 播放器的架构 2. 播放器的渲染流程 3. ffmpeg下载与安装 3.0 查看PC是否已经安装了ffmpeg ffmpeg 3.1 下载 wget https://ffmpeg.org/releases/ffmpeg-7.0.tar.gz 3.2 解压 tar zxvf ffmpeg-7.0.tar.gz && cd ./ffmpeg-7.0 3.3 查看配置文件 ./configure …