C++ STL-string类底层实现

 摘要:

本文实现了一个简易的string类,主要包含以下功能:

1. 默认成员函数:构造函数(默认/参数化)、拷贝构造、赋值重载和析构函数,采用深拷贝避免内存问题;

2. 迭代器支持:通过char*实现begin()/end()迭代器;

3. 容量操作:size()/capacity()获取大小容量,reserve()/resize()调整空间;

4. 字符串修改:push_back()、append()、insert()、erase()等操作;

5. 访问操作:重载operator[]和c_str()方法;

6. 实用功能:find()查找、substr()子串;

7. 运算符重载:关系运算符、流操作符<<和>>。类内部使用动态分配的char数组存储字符串,通过_size和_capacity管理空间。实现时特别注意了深拷贝、边界检查和内存管理,基本模拟了标准string类的核心功能。

目录

 摘要:

实现框架

一、默认成员函数

1.默认构造函数

2.构造函数

3.拷贝构造函数(重点)

4.赋值运算符重载函数

5.析构函数

二、迭代器相关函数

begin和end

三、容量和大小相关函数

size和capacity

reserve和resize

四、与修改字符串相关的函数

push_back

append

operator+=

insert 

erase

clear

五、访问字符串相关函数

operator[ ]

c_str

小知识点- npos

find函数

substr函数

六、关系运算符重载函数

>、>=、<、<=、==、!=

>>和<<运算符的重载


实现框架

#include<iostream>
#include<assert.h>
using namespace std;namespace lzg
{class string{public://typedef char* iterator;using iterator = char*;using const_iterator = const char*;//一、默认成员函数string(const char* str = "");       //默认构造string(const string& s);            //拷贝构造string& operator=(const string& s); //赋值重载~string();                          //析构函数//二、迭代器相关函数iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}//三、容量和大小相关函数void reserve(size_t n);size_t size() const{return _size;}size_t capacity() const{return _capacity;}void resize(size_t n, char ch = '\0');//四、与修改字符串相关的函数void push_back(char ch);void append(const char* str);string& operator+=(char ch);string& operator+=(const char* str);void insert(size_t pos, char ch);void insert(size_t pos, const char* str);void erase(size_t pos, size_t len = npos);void clear(){_str[0] = '\0';_size = 0;}//五、访问字符串相关函数char& operator[](size_t i){assert(i < _size);return _str[i];}const char& operator[](size_t i) const{assert(i < _size);return _str[i];}const char* c_str() const{return _str;}size_t find(char ch, size_t pos = 0);size_t find(const char* str, size_t pos = 0);string substr(size_t pos, size_t len = npos);private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;public:static const size_t npos;};//六、关系运算符重载函数bool operator== (const string& lhs, const string& rhs);bool operator!= (const string& lhs, const string& rhs);bool operator> (const string& lhs, const string& rhs);bool operator< (const string& lhs, const string& rhs);bool operator>= (const string& lhs, const string& rhs);bool operator<= (const string& lhs, const string& rhs);ostream& operator<<(ostream& os, const string& str);istream& operator>>(istream& is, string& str);
}

一、默认成员函数

1.默认构造函数

string():_str(new char[1] {'\0'}),_size(0),_capacity(0)
{}

为_str开辟1字节空间来存放'\0'_capacity不记录\0的大小,这样调用c_str()就不会报错

2.构造函数

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

注意在private中的声明顺序,这里初始化列表只走_size(大小为str的长度),让_str和_capacity在函数体内初始化(这样保证了不会出错,比如三者都在初始化列表的话,会先初始化_str这样有风险),同样的,_str开辟空间时,为'\0'多开辟1字节

构造函数有一个默认构造就行了,写1个就行,把1注释掉,并把2改为全缺省

string(const char* str="")//不要写成" "这是非空:_size(strlen(str))
{_capacity = _size;_str = new char[_size + 1];strcpy(_str, str);
}

3.拷贝构造函数(重点)

在模拟实现拷贝构造函数前,我们应该首先了解深浅拷贝:

我们不写拷贝构造,编译器默认生成的拷贝构造是值拷贝也叫浅拷贝

浅拷贝:拷贝出来的目标对象的指针和源对象的指针指向的内存空间是同一块空间。其中一个对象的改动会对另一个对象造成影响。
深拷贝:深拷贝是指源对象与拷贝对象互相独立。其中任何一个对象的改动不会对另外一个对象造成影响。

很明显,我们并不希望拷贝出来的两个对象之间存在相互影响,因此,我们这里需要用到深拷贝。

	//s2(s1)             s1string(const string& s){_str = new char[s._capacity + 1];strcpy(_str, s._str);_capacity = s._capacity;_size = s._size;}

这里还有一个现代(偷懒)写法

string(const string& s){string tmp(s._str);swap(tmp);}

现代写法与传统写法的思想不同:先构造一个tmp对象,然后再将tmp对象与拷贝(s)对象的数据交换即可。

4.赋值运算符重载函数

与拷贝构造函数类似,赋值运算符重载函数的模拟实现也涉及深浅拷贝问题,我们同样需要采用深拷贝

再强调一下赋值和拷贝的区别,赋值是两个已创建的对象之间完成赋值操作,拷贝是指一个已创建的对象调用拷贝构造生成一个新的对象

// s1 = s3                      s3
string& operator=(const string& s)
{if (this != &s)//避免自己给自己赋值{delete[] _str;  //释放掉s1的旧空间,开辟一个和s3容量的新空间_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}return *this;
}

同样的赋值也有现代写法

// 现代写法// s1 = s3string& operator=(string s){swap(s);return *this;}

赋值运算符重载函数的现代写法是通过采用“值传递”接收右值的方法,让编译器自动调用拷贝构造函数,然后我们再将拷贝出来的对象与左值进行交换即可

5.析构函数

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

二、迭代器相关函数

string类中的迭代器实际上就是字符指针,只是给字符指针起了一个别名叫iterator而已。

typedef char* iterator;

typedef const char* const_iterator;

begin和end

iterator begin()
{return _str; //返回字符串中第一个字符的地址
}const_iterator begin()const
{return _str; //返回字符串中第一个字符的const地址
}iterator end()
{return _str + _size; //返回字符串中最后一个字符的后一个字符的地址
}const_iterator end()const
{return _str + _size; //返回字符串中最后一个字符的后一个字符的const地址
}

其实范围for的底层就是调用了begin()和end()迭代器,当你模拟实现了迭代器,范围for自然就能使用

string s("hello world!!!");
//编译器将其替换为迭代器形式
for (auto e : s)
{cout << e << " ";
}
cout << endl;

注:自己写的begin()和end()函数名必须是这样的,不能有任何大小写否则范围for就报错

三、容量和大小相关函数

size和capacity

因为string类的成员变量是私有的,我们并不能直接对其进行访问,所以string类设置了size和capacity这两个成员函数,用于获取string对象的大小和容量。
size函数用于获取字符串当前的有效长度(不包括’\0’)。

size_t size() const
{return _size;
}

capacity函数用于获取字符串当前的容量。(不包括’\0’)。

size_t capacity() const
{return _capacity;
}

reserve和resize

reserve和resize这两个函数的执行规则一定要区分清楚。
reserve规则:
 1、当n大于对象当前的capacity时,将capacity扩大到n或大于n。
 2、当n小于对象当前的capacity时,什么也不做。

void reserve(size_t n)
{if (n > _capacity){char* tmp = new char[n + 1];//多开一个空间用于存放'\0'strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}
}

resize规则:

1、当n大于当前的size时,将size扩大到n,扩大的字符为ch,若ch未给出,则默认为’\0’。
2、当n小于当前的size时,将size缩小到n。

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

四、与修改字符串相关的函数

push_back

void push_back(char c)
{if (_size == _capacity){reserve(_capacity == 0 ? 4 : 2 * _capacity);}_str[_size] = c;    // 在结尾位置写入新字符(覆盖原'\0')_size++;            // 增加长度_str[_size] = '\0'; // 在新结尾补终止符
}

实现push_back还可以直接复用下面即将实现的insert函数。

//尾插字符
void push_back(char ch)
{insert(_size, ch); //在字符串末尾插入字符ch
}

append

//尾插字符串
void append(const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){size_t newCapacity = 2 * _capacity;// 扩2倍不够,则需要多少扩多少if (newCapacity < _size + len)newCapacity = _size + len;reserve(newCapacity);}//避免尾插字符串时如果字符串大于二倍_capacity时的多次扩容strcpy(_str + _size, str);_size += len;
}

operator+=

+=运算符的重载是为了实现字符串与字符、字符串与字符串之间能够直接使用+=运算符进行尾插。
+=运算符实现字符串与字符之间的尾插直接调用push_back函数即可。

//+=运算符重载
string& operator+=(char ch)
{push_back(ch); //尾插字符串return *this; //返回左值(支持连续+=)
}

+=运算符实现字符串与字符串之间的尾插直接调用append函数即可。

//+=运算符重载
string& operator+=(const char* str)
{append(str); //尾插字符串return *this; //返回左值(支持连续+=)
}

insert 

insert函数的作用是在字符串的任意位置插入字符或是字符串。

void insert(size_t pos, char ch);

void insert(size_t pos,const char* str);

void insert(size_t pos, char ch)
{if (_size == _capacity){reserve(_capacity == 0 ? 4 : 2 * _capacity);}size_t end = _size;while (end >=(int)pos){_str[end + 1] = _str[end];end--;}_str[pos] = ch;_size++;
}

insert函数插入字符串时也是类似思路

void insert(size_t pos, const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){size_t newCapacity = 2 * _capacity;// 扩2倍不够,则需要多少扩多少if (newCapacity < _size + len)newCapacity = _size + len;reserve(newCapacity);}size_t end = _size + len;while (end > pos + len - 1){_str[end] = _str[end - len];--end;}for (size_t i = 0; i < len; i++){_str[pos + i] = str[i];}_size += len;
}

erase

erase函数的作用是删除字符串任意位置开始的n个字符。

void erase(size_t pos, size_t len)
{assert(pos < _size);if (len >= _size - pos){_str[pos] = '\0';_size = pos;}else{   //从后往前挪size_t end = pos + len;while (end <= _size){_str[end-len] = _str[end];//覆盖pos及后len长度的值,完成删除end++;}_size -= len;}
}

clear

clear函数用于将对象中存储的字符串置空,实现时直接将对象的_size置空,然后在字符串后面放上’\0’即可

//清空字符串
void clear()
{_size = 0; //size置空_str[_size] = '\0'; //字符串后面放上'\0'
}

swap

有三个swap,有两个是string里的swap函数,另外一个是算法库的swap函数

std::swap
template <class T> void swap (T& a, T& b);

std::string::swap

void swap (string& str);

void swap (string& x,sring& y);

算法库中的swap也能交换自定义类型,但代价非常大

我们自己实现swap完全不用这么复杂,直接把s1、s2指针及数据交换一下就行了

//交换两个对象的数据
void swap(string& s)
{//调用库里的swap::swap(_str, s._str); //交换两个对象的C字符串::swap(_size, s._size); //交换两个对象的大小::swap(_capacity, s._capacity); //交换两个对象的容量
}

还有一个swap存在的意义是什么呢?

void swap(string& s1,string& s2)
{s1.swap(s2);
}

这样在类外面调用swap函数时就不会调到算法库的swap函数了(模板实例化会走上面的swap函数)

五、访问字符串相关函数

operator[ ]

[ ]运算符的重载是为了让string对象能像C字符串一样,通过[ ] +下标的方式获取字符串对应位置的字符。

//[]运算符重载(可读可写)
char& operator[](size_t i)
{assert(i < _size); //检测下标的合法性return _str[i]; //返回对应字符
}

在某些场景下,我们可能只能用[ ] +下标的方式读取字符而不能对其进行修改。例如,对一个const的string类对象进行[ ] +下标的操作,我们只能读取所得到的字符,而不能对其进行修改。所以我们需要再重载一个[ ] 运算符,用于只读操作。

//[]运算符重载(可读可写)
const  char& operator[](size_t i)const
{assert(i < _size); //检测下标的合法性return _str[i]; //返回对应字符
}

c_str

c_str函数用于获取对象C类型的字符串

//返回C类型的字符串
const char* c_str()const
{return _str;
}

小知识点- npos

在 C++ 标准库中,​npos​ 是一个特殊的静态常量成员,主要用于表示“无效位置”或“未找到”的状态

注:只有整形才能在声明中定义

static const size_t npos=-1;

 ​核心用途

(1) ​查找函数失败时的返回值

std::string str = "Hello";
size_t pos = str.find('x');  // 查找不存在字符
if (pos == std::string::npos) {  // 必须用 npos 检查std::cout << "Not found!";
}

(2) ​表示“直到字符串末尾”​

std::string sub = str.substr(2, std::string::npos);  // 从索引2到末尾

特性

  • 无符号值​:由于是 size_t类型,避免直接与 -1比较,而应使用 npos。•

  • 足够大​:其值(如 18446744073709551615)保证不会与任何有效索引冲突。

find函数

find函数是用于在字符串中查找一个字符或是字符串

find函数:正向查找即从字符串开头开始向后查找

1、正向查找第一个匹配的字符。

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;}}//找不到返回nposreturn npos;
}

2、正向查找第一个匹配的字符串。

这里用到了一个strstr函数

char * strstr (char * str1, const char * str2 );

返回指向 str1 中第一次出现的 str2 的指针,如果 str2 不是 str1 的一部分,则返回空指针。

size_t find(const char* str, size_t pos = 0)
{assert(pos < _size);const char* ptr = strstr(_str, str);if (ptr)//如果找到了{return ptr - _str;  //下标}else //为空{return npos;}
}

substr函数

substr函数用来取字符串中的子字符串,位置和长度由自己决定

默认从首字符取,默认取的长度是取到尾

string substr (size_t pos = 0, size_t len = npos) const;

调用拷贝构造,返回pos位后len长度的字符串

string substr(size_t pos, size_t len)
{assert(pos < _size);//len长度足够大就直接取到尾if (len > _size - pos){len = _size - pos;}string sub;sub.reserve(len);for (size_t i = pos;i < len; i++){sub += _str[pos + i];}return sub;
}

六、关系运算符重载函数

>、>=、<、<=、==、!=

关系运算符有 >、>=、<、<=、==、!= 这六个,但是对于C++中任意一个类的关系运算符重载,我们均只需重载其中的两个,剩下的四个关系运算符可以通过复用已经重载好了的两个关系运算符来实现。

例如,对于string类,我们可以选择只重载 < 和 == 这两个关系运算符。

注:重载在类外面,命名域例:lzg里。这样操作数就不用限定死是string类的

lhs​:代表 ​Left-Hand ​Side (左侧操作数/左侧值)

rhs​:代表 ​Right-Hand ​Side (右侧操作数/右侧值)

//==运算符重载
bool operator== (const string& lhs, const string& rhs)
{return strcmp(lhs.c_str(), rhs.c_str()) == 0;
}//<运算符重载
bool operator< (const string& lhs, const string& rhs)
{return strcmp(lhs.c_str(), rhs.c_str()) < 0;
}

剩下的四个关系运算符的重载,就可以通过复用这两个已经重载好了的关系运算符来实现了。

//<=运算符重载
bool operator<= (const string& lhs, const string& rhs)
{return lhs < rhs || lhs == rhs;
}//!=运算符重载
bool operator!= (const string& lhs, const string& rhs)
{return !(lhs == rhs);
}//>运算符重载
bool operator> (const string& lhs, const string& rhs)
{return !(lhs <= rhs);
}//>=运算符重载
bool operator>= (const string& lhs, const string& rhs)
{return !(lhs < rhs);
}

>>和<<运算符的重载

>>运算符的重载

ostream& operator<<(ostream& os, const string& str)
{for (size_t i = 0; i < str.size(); i++){os << str[i];}return os;
}

<<运算符的重载

istream& operator>>(istream& is, string& str)
{str.clear();char ch;//is >> ch;ch = is.get();while (ch != ' ' && ch != '\n'){str += ch;ch = is.get();}return is;
}

参考链接

C++string接口

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

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

相关文章

【LeetCode每日一题】

每日一题3. 无重复字符的最长子串题目总体思路代码1.两数之和题目总体思路代码15. 三数之和题目总体思路代码2025.8.153. 无重复字符的最长子串 题目 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长 子串 的长度。 示例 1: 输入: s “abcabcbb” 输出: 3…

sharding-jdbc读写分离配置

一主两从&#xff0c;爆红是正常的&#xff0c;不知为啥 spring:shardingsphere:datasource:names: ds_master,ds_s1,ds_s2ds_master:type: com.zaxxer.hikari.HikariDataSourcedriverClassName: com.mysql.jdbc.DriverjdbcUrl: jdbc:mysql://192.168.135.100:3306/gmall_produ…

【大模型核心技术】Dify 入门教程

文章目录一、Dify 是什么二、安装与部署2.1 云端 SaaS 版&#xff08;快速入门&#xff09;2.2 私有化部署&#xff08;企业级方案&#xff09;三、界面导航与核心模块3.1 控制台概览3.2 核心功能模块详解3.2.1 知识库&#xff08;RAG 引擎&#xff09;3.2.2 工作流编排3.2.3 模…

homebrew 1

文章目录brew(1) – macOS&#xff08;或 Linux&#xff09;上缺失的包管理器概要描述术语表基本命令install *formula*uninstall *formula*listsearch \[*text*|/*text*/]命令alias \[--edit] \[*alias*|*alias**command*]analytics \[*subcommand*]autoremove \[--dry-run]bu…

设计索引的原则有哪些?

MySQL 索引设计的核心原则是 在查询性能与存储成本之间取得平衡。以下是经过实践验证的 10 大设计原则及具体实现策略&#xff1a;一、基础原则原则说明示例/反例1. 高频查询优先为 WHERE、JOIN、ORDER BY、GROUP BY 频繁出现的列建索引✅ SELECT * FROM orders WHERE user_id1…

使用影刀RPA实现快递信息抓取

最近公司项目有个需求&#xff0c;要求抓取快递单号快递信息&#xff0c;比如签收地点、签收日期等。该项目对应的快递查询网站是一个国外的网站&#xff0c;他们有专门的快递平台可以用于查询。该平台提供了快递接口进行查询&#xff0c;但需要付费。同时也提供了免费的查询窗…

蚁剑--安装、使用

用途限制声明&#xff0c;本文仅用于网络安全技术研究、教育与知识分享。文中涉及的渗透测试方法与工具&#xff0c;严禁用于未经授权的网络攻击、数据窃取或任何违法活动。任何因不当使用本文内容导致的法律后果&#xff0c;作者及发布平台不承担任何责任。渗透测试涉及复杂技…

Varjo XR虚拟现实军用车辆驾驶与操作培训

Patria基于混合现实的模拟器提供了根据现代车辆乘员需求定制的培训&#xff0c;与传统显示设置相比&#xff0c;全新的模拟解决方案具有更好的沉浸感和更小的物理空间需求。Patria是芬兰领先的国防、安全和航空解决方案提供商。提供尖端技术和全面的培训系统&#xff0c;以支持…

Java 10 新特性及具体应用

目录 1. 局部变量类型推断&#xff08;JEP 286&#xff09; 2. 不可修改集合&#xff08;JEP 269&#xff09; 3. 并行全垃圾回收&#xff08;JEP 307&#xff09; 4. 应用类数据共享&#xff08;JEP 310&#xff09; 5. 线程局部管控&#xff08;JEP 312&#xff09; 总结…

【力扣 Hot100】刷题日记

D8 全排列(非回溯法) 全排列原题链接 在刷leetcode的时候&#xff0c;看到这道题目并没法使用像STL的next_permutation方法&#xff0c;感叹C便利的同时&#xff0c;又惋惜Java并没有类似的API&#xff0c;那我们只能从原理入手了&#xff0c;仿写此算法。 其实回溯法更应该…

JetPack系列教程(七):Palette——让你的APP色彩“飞”起来!

JetPack系列教程&#xff08;七&#xff09;&#xff1a;Palette——让你的APP色彩“飞”起来&#xff01; 各位开发小伙伴们&#xff0c;还在为APP的配色发愁吗&#xff1f;别担心&#xff0c;今天咱们就来聊聊JetPack家族里的“色彩魔法师”——Palette&#xff01;这个神奇的…

力扣hot100 | 矩阵 | 73. 矩阵置零、54. 螺旋矩阵、48. 旋转图像、240. 搜索二维矩阵 II

73. 矩阵置零 力扣题目链接 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,1,1],[1,0,1],[1,1,1]] 输出&#xff1a;[[1,0,1],[0,0,0],[1,0,1]]…

ARC与eARC是什么?主要用在哪?

在家庭影音设备不断升级的今天&#xff0c;人们对音视频体验的要求越来越高。无论是追剧、玩游戏还是观看电影大片&#xff0c;很多用户不再满足于电视自带的扬声器&#xff0c;而是希望借助回音壁、功放或家庭影院系统&#xff0c;获得更加震撼的沉浸式声音体验。一、ARC是什么…

解锁JavaScript性能优化:从理论到实战

文章目录 前言 一、常见性能瓶颈剖析 二、实战案例与优化方案 (一)DOM 操作优化案例​ (二)事件绑定优化案例​ (三)循环与递归优化案例​ (四)内存管理优化案例​ 三、性能优化工具介绍 总结 前言 性能优化的重要性 在当今数字化时代,Web 应用已成为人们生活和工作…

结构化记忆、知识图谱与动态遗忘机制在医疗AI中的应用探析(上)

往期相关内容推荐: 基于Python的多元医疗知识图谱构建与应用研究(上)

XSS攻击:从原理入门到实战精通详解

一、XSS攻击基础概念1.1 什么是XSS攻击 XSS&#xff08;Cross-Site Scripting&#xff0c;跨站脚本攻击&#xff09;是一种将恶意脚本注入到可信网站中的攻击手段。当用户访问被注入恶意代码的页面时&#xff0c;浏览器会执行这些代码&#xff0c;导致&#xff1a;用户会话被劫…

Leetcode 14 java

今天复习一下以前做过的题目&#xff0c;感觉是忘光了。 160. 相交链表 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数…

用 FreeMarker 动态构造 SQL 实现数据透视分析

在 ERP、BI 等系统中&#xff0c;数据透视分析&#xff08;Pivot Analysis&#xff09;是非常常见的需求&#xff1a;用户希望按任意维度&#xff08;如门店、时间、商品分类等&#xff09;进行分组统计&#xff0c;同时选择不同的指标&#xff08;如 GMV、订单数、客单价等&am…

13.深度学习——Minst手写数字识别

第一部分——起手式 import torch from torchvision import datasets, transforms import torch.nn as nn import torch.nn.functional as F import torch.optim as optimuse_cuda torch.cuda.is_available()if use_cuda:device torch.device("cuda") else: device…

【JAVA高级】实现word转pdf 实现,源码概述。深坑总结

之前的需求做好后,需求,客户突发奇想。要将生成的word转为pdf! 因为不想让下载文档的人改动文档。 【JAVA】实现word添加标签实现系统自动填入字段-CSDN博客 事实上这个需求难度较高,并不是直接转换就行的 word文档当中的很多东西都需要处理 public static byte[] gener…