【C++】string学习 — 手搓string类项目

在这里插入图片描述

手搓string项目

  • 1 string类介绍
  • 2 功能描述
  • 3 代码实现
    • 3.0 基础框架
    • 3.1 构造函数 和 析构函数
    • 3.2 流操作符重载 和 尾插扩容
    • 3.4 运算符重载
    • 3.5 实用功能
    • 3.6 迭代器模拟
  • 总结
  • Thanks♪(・ω・)ノ谢谢阅读!!!
  • 下一篇文章见!!!

1 string类介绍

C++ 的 string 类是 C++ 标准库中提供的一个用于处理字符串的类。它在 C++ 的历史中扮演了重要的角色,为字符串处理提供了更加方便、高效的方法。

在 C++ 的早期版本中,字符串处理并不是一个简单的事情。在 C++ 的最初版本中,字符串被处理为 char* 类型的指针,这使得字符串处理变得非常复杂,容易出错。例如,简单的字符串连接操作都需要手动管理内存,这无疑增加了编程的难度。
为了解决这个问题,C++98 引入了 头文件,其中包含了 string 类。这个类的引入,可以说是一场革命,因为它提供了一个安全、方便、可移植的字符串处理方式。也为以后STL的出现埋下了伏笔…

在现实生活中,string也有着大量的应用:

  1. 社交媒体: 当你在社交媒体上发布状态或评论时,你输入的文字内容会存储在一个 string 变量中。例如,你可能会写一条消息 like “I had a great day at the park!”,这条消息就是存储在一个 string 变量中的。
  2. 电子邮件: 当你写一封电子邮件时,正文内容、主题行和收件人地址等都可能是 string 类型的。例如,你可能会写一封主题为 “Meeting Invitation” 的邮件,内容为 “Dear John, please join us for a meeting at 10am tomorrow.”,这些内容都是以 string 形式存储的。
  3. 购物车: 在在线购物时,你的购物车中商品的名称、价格和数量等信息通常会存储在 string 类型的变量中。例如,你的购物车中可能有 “T-shirt”、" Jeans" 和 “Shoes” 等商品,这些商品名称都是以 string 形式存储的。
  4. 等等等…

这里我们不管他的底层:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator>string;
我们通过类与对象的相关知识来尝试完成 string项目!我们将通过先描述,在落地的原则开始,只有明白了功能模块,才能流畅的写出string。

2 功能描述

首先我们必须明白我们需要什么功能,所以我们可以熟悉一下官方的常用接口:string使用手册
当然,从实际出发不失为一种更好的选择,想象一下使用场景:

  1. 第一,因为本质是字符串,所以我们需要成员函数 char* _str,并且要做到很好控制的话还需要 数据大小size_t _size 和容量 size_t capacity。
  2. 第二,构造函数,析构函数必须要有的,而且构造函数需要支持多种构造方法(常量字符串,拷贝构造,空类构造)。
  3. 第三,我们一定要支持输入字符串来构造string类和输出string,这就需要做到<< >>的重载了。
  4. 第亖,要想实现输入>>的重载,就要辅助实现push_back尾插函数,实现了尾插那 +=的操作重载也就完成了。
  5. 第五,我们还需要通过对比大小的一系列操作符(== <= >= > < !=)的重载。
  6. 第六,根据字符串输入的特性,为了我们可以从一行中正确读取数据,我们还需getline函数来实现功能。
  7. 第七,回到最基础的功能增删查改,所以我们可以增加指定位置插入,指定位置删除,查找字符串等功能。
  8. 第八,对于C++新增特性迭代器,我们也可以用指针模拟实现一下。
  9. 第九,既然支持了迭代器,那最原始的小标操作也要支持一下。

以上就是对一个字符串类可能需要的功能的全面总结,通过实现这些功能,我们可以创建出一个既实用又灵活的字符串操作工具
接下来,我们将根据之前列出的功能需求,逐步实施我们的字符串模拟项目。在编写代码的过程中,我们必须保持细心和谨慎,这样可以避免后期出现不必要的调试困扰。

3 代码实现

在实现这个项目的过程中,我们需要注意以下几点:

  1. 保持代码的清晰和可读性(重中之重):在编写代码时,要注意命名规范、代码结构和注释,使得其他人能够轻松理解我们的代码。

  2. 模块化设计:将代码分为多个模块,每个模块负责一个特定的功能。这样可以降低代码的复杂度,也便于后期的维护和扩展。

  3. 充分测试一定一定!!!):在代码实现完成后,要进行充分的测试,确保每个功能的正确性和稳定性。我们可以使用单元测试和集成测试来验证代码的质量。

  4. 优化性能:在保证功能实现的基础上,尽量优化代码的性能。我们可以关注一些常见的性能瓶颈,如内存分配、字符串拼接等,并寻求优化的方法

总之,在实现这个项目的过程中,我们要注重代码的质量、可读性和可维护性。只有这样,我们才能构建出一个高效、稳定且易于扩展的字符串模拟类。接下来,让我们开始编写代码吧!

3.0 基础框架

我们先根据功能写一下功能框架,方便书写:

#pragma once#include<iostream>
#include<string.h>
#include<assert.h>using namespace std;namespace bit {class string {public://默认结尾static const int npos;//构造函数string() :{}string(const char* str = "")	{}string(const string& s = "") {}//析构函数~string() {}//取等操作string& operator=(string s) {}//迭代器模拟char* begin() {}char* end() {}//逆转迭代器char* rbegin() {}char* rend() {}//交换void swap(string& s) {}//从pos位置开始搜索寻找ch第一次出现的位置size_t find(const char ch, size_t pos = 0) {}//从pos位置开始搜索寻找 字符串s 第一次出现的位置size_t find(const char* s, size_t pos = 0) {}//在pos位置插入字符void insert(const char ch, size_t pos = 0) {}//在pos位置插入字符串void insert(const char* s, size_t pos = 0) {}//返回成员变量size_t size() {}size_t capacity() {}//扩容操作void reserve(size_t n){}//重置数据大小void resize(size_t n , char ch = '\0') {}//在pos位置后消除 n 个元素void erase(size_t pos, size_t n = npos) {}//尾插void push_back(const char* s) {}void push_back(char s) {}//清空void clear() {}//+=操作符重载void operator+= (const char* s) {}void operator+= (char s) {}//运算符重载friend ostream& operator<< (ostream& out, const string& str);bool operator==(const string& s) {}bool operator>(const string& s) {}bool operator>=(const string& s) {}bool operator<=(const string& s) {}bool operator<(const string& s) {}bool operator!=(const string& s) {}
//私有成员函数private:char* _str;size_t _size;size_t _capacity;};//流操作符重载ostream& operator<< (ostream& out, const bit::string& str) {}istream& operator>> (istream& in,  bit::string& str) {}//获取一行istream& getline(istream& in, string& s) {}//结尾赋值const int string::npos = -1;}

框架写好,我们就可以开始逐个实现,一定注意其中的逻辑,不要刻意去实现一个功能,要联系其他功能,看看是否存在联系,进而通过调用函数简便我们的实现过程。

3.1 构造函数 和 析构函数

构造函数我们使用全却省,无参数默认,拷贝构造三个:这里注意初始化列表的使用
因为涉及了指针操作,所以必要的初始化是十分需要的

//空串
string() :
_str(new char[1]),
_size(0),
_capacity(0)
{_str[0] = '\0';
}
//常量字符串构造
string(const char* str = ""):_str(new char[strlen(str) + 1]),_size(strlen(str)),_capacity(strlen(str) + 1)
{
//调用函数简单完成strcpy(_str, str);
}
//拷贝构造
//这里使用到了 = 重载,所以它测试可以等到实现操作符重载之后在实现。
string(const string& s = "") :_str(new char[s._capacity + 1]),_size(0),_capacity(0)
{strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;
}

析构函数就简单的多:正常释放空间即可

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

再来增加一些获取私有变量的函数:

//返回成员变量size_t size() {return _size;}size_t capacity() {return _capacity;}

3.2 流操作符重载 和 尾插扩容

接下来我们实现一下流操作符,方便我们可以快速进行一下测试。
对于流操作我们应该写在全局,这就可以正常的传入参数,不然就会报错哦。

//简单打印即可,注意设置友元哦
ostream& operator<< (ostream& out, const bit::string& str) {out << str._str;return out;
}
//这里是优化版本,可以避免频繁开空间,优化性能
istream& operator>> (istream& in,  bit::string& str) {str.clear();char* buff = new char[128];char ch;ch = in.get();int count = 0;//先存入中间数组再存入string中while (ch != '\n' && ch != ' ') {buff[count++] = ch;ch = in.get();if (count >= 127) {buff[127] = '\0';str.push_back(buff);count = 0;}}buff[count] = '\0';str.push_back(buff);return in;
}

这里我们发现我们需要实现一下尾插操作才好进行流输入操作。看,这样一步一步我们就可以完成所需功能。
尾插 push_back

	//插入字符串
void push_back(const char* s) {//先扩容!!!while (_size + strlen(s) >= _capacity) {reserve(_capacity == 0 ? 4 : 2 * _capacity);}//然后依次读入即可for (size_t i = 0; i < strlen(s); i++) {_str[_size++] = s[i];}_str[_size] = '\0';}//插入单个字符
void push_back(char s) {while (_size + 1 >= _capacity) {reserve(_capacity == 0 ? 4 : 2 * _capacity);}_str[_size++] = s;_str[_size] = '\0';
}

可以想到,要实现尾插,扩容是必不可少的!!
再补充一个更改数据大小的函数。
扩容 reserve

		void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}//重置数据大小void resize(size_t n , char ch = '\0') {if ( n <= _size ) {_str[n] = '\0';_size = n;}else {reserve(n);for (size_t i = _size; i < n; i++){_str[i] = ch;}_str[n] = '\0';_size = n;}}

这样我们 就初步完成了局部的可执行程序。接下来我们进行第一次测试来看是否能够成功运行:

void test_string1(){bit::string s1("123 456");bit::string s2("");cout << "\n-------流输出测试--------\n";cout << s1 << endl;cout << s1.size() << ' ' << s1.capacity();s1.push_back("abcdefg");cout << "\n-------尾插测试--------\n";cout << s1 << endl;cout << s1.size() << ' ' << s1.capacity();cout << "\n-------流输入测试--------\n";cin >> s2;cout << s2 << endl;cout << s2.size() << ' ' << s2.capacity();
}

来看效果:
在这里插入图片描述

这样我们初步就完成了我们基础功能。接下来我们继续实现!

3.4 运算符重载

这部分比较简单:
实现两个之后,就可以来回使用完成六个函数的书写!这是运算逻辑的体现,让我们的代码变得简洁明朗,减少冗杂,增加代码的可读性。

bool operator==(const string& s) {if (strcmp(_str, s._str) == 0) return true;else return false;}bool operator>(const string& s) {if (strcmp(_str, s._str) > 0) return true;else return false;}bool operator>=(const string& s) {return _str == s._str || _str > s._str;}bool operator<=(const string& s) {return !(_str > s._str);}bool operator<(const string& s) {return !(_str >= s._str);}bool operator!=(const string& s) {return !(_str == s._str);}string& operator=(string s) {//现代写法	//swap(tmp);char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;return *this;}//交换void swap(string& s) {std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}

然后我们来完成十分常用的+=操作,不用多说,这个底层逻辑和push_back是一致的,所以在底层直接调用即可,这样也变向保证了代码的鲁棒性,只需对一个功能做出维护,既可以扩展出其他接口。
注意这里面 的 = 重载,现代写法更加简单 只需一步 swap即可。这十分巧妙,通过调用不同函数就帮助我们改善了代码的复杂性。

//提供两个重载,让其使用体验更好
void operator+= (const char* s) {push_back(s);}
void operator+= (char ch) {push_back(ch);}

然后,再来测试一下,保证我们的功能可以正常使用,一定一定要测试哦!测试十分重要,千万不能忽视!!!小心驶得万年船!

void test_string2(){bit::string s1("123 456");bit::string s2("123");cout<< "-----------比较测试--------------\n" ;cout << (s2 == s1) << endl;cout << (s2 < s1) << endl;cout << (s2 > s1) << endl;cout << (s2 <= s1) << endl;cout << (s2 >= s1) << endl;cout << (s2 != s1) << endl;cout<< "-----------+=测试--------------\n" ;bit::string s3("123456789");s3 += "abc";char ch = '1';s3 += ch;cout << s3;}

我们进行了每个操作符的测试和+= 单个字符 与字符串的测试。正常通过测试,返回的0 1 值符合我们的要求。
在这里插入图片描述

3.5 实用功能

上述我们已经实现了基本的功能,接下来我们要加入一些比较实用的功能,比如查找,指定位置插入,指定位置删除,获取一行的字符。这些函数大大加强了string 的可操作性,让string更加使用,与普通的 char 类型拉开差距!
注意我们都要提供两种重载,保证单个字符和字符串都可以正常进行操作

//指定位置删除
void erase(size_t pos, size_t n = npos) {assert(pos < _size);if (n == npos || n >= _size - pos) {_str[pos] = '\0';_size = pos;}else {strcpy(_str + pos, _str + pos + n);_size -= n;}}//在pos位置插入字符串void insert(const char* s, size_t pos = 0) {assert(pos < _size);int len = strlen(s);//保证容量足够不然会发生报错哦while (_size + len >= _capacity){reserve(_capacity == 0 ? 4 : 2 * _capacity);}//挪动数据size_t end = _size + len;while (end > pos + len - 1 ) {_str[end] = _str[end - len];end--;}//拷贝到指定位置,不要拷贝‘\0’strncpy(_str + pos, s, len);_str[_size + strlen(s)] = '\0';_size += strlen(s);}//在pos位置插入字符void insert(const char ch, size_t pos = 0) {assert(pos < _size);while (_size +1 >= _capacity){reserve(_capacity == 0 ? 4 : 2 * _capacity);}size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;++_size;}//从pos位置开始搜索寻找ch第一次出现的位置size_t find(const 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;}//从pos位置开始搜索寻找 字符串s 第一次出现的位置size_t find(const char* s, size_t pos = 0) {assert(pos < _size);const char* p = strstr(_str, s);if (p) return p - _str;else return npos;}

写入了这么多功能,快来进行测试一波!

void test_string3() {bit::string s1("123 456 abc");cout << "---------find测试-------------\n";cout << s1.find('1') << endl;cout << s1.find("457") << endl;cout << "---------insert测试-------------\n";bit::string s2("123");s1.insert("jkl465798", 0);cout << s1 << endl;cout << "---------resize测试-------------\n";cout << s2 << endl;cout << s2.capacity() << endl;s1.resize(4);cout << s2 << endl;cout << s2.size() << ' ' << s2.capacity() << endl;s1.resize(10, 'c');cout << s2 << endl;cout << s2.size() << ' ' << s2.capacity() << endl;cout << "---------erase测试-------------\n";s2.erase(2, 5);cout << s2 << endl;cout << s2.size() << ' ' << s2.capacity() << endl;}

在这里插入图片描述
我们成功完成了功能的扩展,实现了增删查改的重要部分,这下子我们的string就完成了绝大部分,接下来在补充上迭代器就更好了。

3.6 迭代器模拟

C++中的迭代器是用于访问容器元素的一种抽象指针。迭代器具有五个基本特性:

  1. 迭代器类型:迭代器是一个实现了迭代器协议的对象,它有一个类型,该类型指示它所指向的元素的类型。例如,在std::string中,迭代器类型是std::string::iterator
  2. 解引用:迭代器可以解引用,这意味着可以通过解引用迭代器来访问它所指向的元素。在std::string中,解引用迭代器可以访问字符串中的字符。
  3. 箭头操作符:大多数迭代器都支持箭头操作符->,用于访问迭代器所指向对象的成员。在std::string中,箭头操作符可以用于访问字符串中字符的成员函数,如std::string::iterator>std::string::value_type::operatorchar()
  4. 增加和减少:迭代器可以通过增加(++)和减少(–)操作符来遍历容器。在std::string中,增加迭代器会移动到下一个字符,减少迭代器会移动到前一个字符。
  5. 比较:迭代器可以比较,以确定它们是否指向同一个元素或是否在容器中相邻。在std::string中,两个迭代器可以通过比较操作符(==、!=)来比较它们是否相等,或者通过比较操作符(<、<=、>、>=)来比较它们的相对位置。

所以我们可以简单通过指针来模拟实现一下,让其可以初步使用即可。

		//迭代器模拟char* begin() {return _str;}char* end() {return _str + _size;}char* rbegin() {return _str + _size;}char* rend() {return _str;}

来进行测试一下:

void test_string4(){bit::string s1("123456789");for (auto ch : s1) {cout << ch << ' ';}reverse(s1.begin(), s1.end());cout << endl;for (auto ch : s1) {cout << ch << ' ';}}

在这里插入图片描述
这下也间接证明了基于范围的for循环是以迭代器为底层的。

总结

这篇文章我们模拟实现了string类,这对于我来说是一次不小的挑战,相信我会继续进步的!!!
送给我一句话:
肆无忌惮的放任自己,这样得来的自由,终将在现实中轰然倒塌。而自律赢来的,是你对现实的自主感,是真正的自由。
自律让我更加强大!

Thanks♪(・ω・)ノ谢谢阅读!!!

下一篇文章见!!!

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

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

相关文章

LeetCode:206.反转链表

206. 反转链表 解题过程 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, ListNode next) { this.val val; this.next next; …

如何使用 Langchain、Ollama 和 Streamlit 构建 RAG

一、先决条件&#xff1a;您需要了解什么 在深入讨论技术细节之前&#xff0c;我们先概述一下先决条件。Python 的基础知识至关重要&#xff0c;因为它是我们将使用的主要语言。熟悉机器学习和自然语言处理的基本概念将帮助您更轻松地掌握这些概念。此外&#xff0c;对 Langch…

蓝桥杯练习系统(算法训练)ALGO-973 唯一的傻子

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 腿铮找2255有点事&#xff0c;但2255太丑了&#xff0c;所以腿铮不知道他的长相。正愁不知道到如何找他的时候&#xff0c;…

【开发环境】Ubuntu 18.04 搭建 QT编译环境详细步骤 【亲测有效】

目录 1 查看Ubuntu系统中Qt版本 2 下载Ubuntu系统Qt版本安装包 3 Qt安装 3.1 Qt 安装步骤 3.2 安装qt发现Ubuntu空间不足&#xff0c;怎么去扩容呢&#xff1f; 3.2.1 硬盘操作步骤&#xff08;需要关闭虚拟机进行操作&#xff09; 3.2.2 Ubuntu命令操作&#xff1a;安装…

云计算项目七:jump-server安装部署

jump-server安装部署 配置清单 jumpserver概述 Jumpserver是一款开源的堡垒机&#xff0c;可使系统的管理员和开发人员安全的连接到企业内部服务器上执行操作&#xff0c;并且支持大部分操作系统&#xff0c;是一款非常安全的远程连接工具 常见支持的系统 CentOS, RedHat, …

基于springboot实现酒店客房管理系统项目【项目源码+论文说明】

基于springboot实现酒店客房管理平台系统演示 摘 要 随着人们的物质水平的提高&#xff0c;旅游业和酒店业发展的速度越来越快。近年来&#xff0c;市面上酒店的数量和规模都在不断增加&#xff0c;如何提高酒店的管理效率和服务质量成为了一个重要的问题。伴随着信息技术的发…

内网渗透-跨域环境渗透-1

目录 smbclient工具 mimikatz工具 Kerbers协议 NTLM认证 hash传递攻击&#xff08;PTH攻击&#xff09; 黄金票据攻击 白银票据 MS14-068 smbclient工具 在linux里面连接远程windows共享目录&#xff0c;可以使用这个工具 ​ 第一种连接方式&#xff1a;smbclient -L 目…

HarmonyOS 非线性容器特性及使用场景

非线性容器实现能快速查找的数据结构&#xff0c;其底层通过 hash 或者红黑树实现&#xff0c;包括 HashMap、HashSet、TreeMap、TreeSet、LightWeightMap、LightWeightSet、PlainArray 七种。非线性容器中的 key 及 value 的类型均满足 ECMA 标准。 HashMap HashMap 可用来存…

JWT概述

JSON Web Token&#xff08;缩写 JWT&#xff09;是目前最流行的跨域认证解决方案&#xff0c;本文介绍它的原理和用法。 一、跨域认证的问题 互联网服务离不开用户认证。一般流程是下面这样。 1、用户向服务器发送用户名和密码。 2、服务器验证通过后&#xff0c;在当前对话…

学习Java的第八天

本节我们重点研究对象和类的概念。 对象&#xff08;Object&#xff09;是一个应用系统中的用来描述客观事物的实体&#xff0c;是有特定属性和行为&#xff08;方法&#xff09;的基本运行单位。是类的一个特殊状态下的实例。对象可以是一个实体、一个名词、一个可以想象为有…

记事小本本

记事小本本 实现效果 相关代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</titl…

Qt Creator常见问题解决方法

Qt Creator源文件重命名的正确方法 光改文件名是不够的&#xff0c;还要在.pro文件中的SOURCES中把名字改成之后的。 中文乱码&#xff08;字符集设置&#xff09; 菜单栏-工具-选项-设置为utf-8

MongoDB性能最佳实践:硬件和操作系统配置

欢迎阅读有关MongoDB性能最佳实践的系列博文。在往期文章中&#xff0c;我们已经讨论过查询模式和性能分析、事务和读/写关注等实现大规模性能的关键考虑因素。在本篇文章中&#xff0c;我们将讨论硬件和操作系统配置。 如果您在阿里云上部署MongoDB&#xff0c;那么阿里云会为…

移动端tabBar的固定问题

tabBar原本是 position: fixed的布局&#xff0c;会导致元素脱标&#xff0c;不占位&#xff0c;上面的内容就会被覆盖 .layout {height: 100%;display: flex;flex-direction: column;.main{flex: 1;overflow: hidden; // 隐藏二级路由中超出main高度的内容}.van-tabbar {posi…

运维自动化之ansible

pxe 一键安装操作系统 操作系统只是提供一个平台 lnmp 需要多软件协同完成的一个简单项目 服务器正常运行 日常运维 巡检 服务器上的软件正常运行 zabbix 普罗米修斯 系统调优&#xff0c;架构调优 云计算核心职能 搭建平台架构 日常运营保障 性能效率优化 相关工具 代…

lucky-canvas实现老虎机、九宫格和大转盘抽奖

lucky-canvas是一款开源免费的基于 jscanvas 的前端插件&#xff0c;UI精美&#xff0c;功能强大&#xff0c;使用起来比较方便。 lucky-canvas官网https://100px.net/ 一、使用 注意&#xff1a;下例是vue中的应用&#xff0c;具体还有js和uniapp中的应用&#xff0c;详细查…

部署zabbix6.0.27 执行 make install 报错

CentOS7 部署 zabbix6.0.27 执行 make install 报错 报错信息 [rootlocalhost zabbix-6.0.27]# make install /usr/bin/ld: warning: libssl.so.3, needed by /usr/local/mysql/lib/libmysqlclient.so, not found (try using -rpath or -rpath-link) /usr/bin/ld: warning: l…

EasyExcel导出自定义表格

谈到新技术&#xff0c;每个人都会有点恐惧&#xff0c;怕处理不好。确实&#xff0c;第一次使用新技术会遇到很多坑&#xff0c;这次使用 EasyExcel 这个新技术去做 excel 导出&#xff0c;还要给表格加样式&#xff0c;遇到不同的版本问题&#xff0c;遇到颜色加错了地方&…

web开发——前端html、css、JavaScript学习总结(持续更新中.......)

目录模版 1 html:结构标签/属性文本标记: mark文本设置:删除线del / 下划线ins/ 加粗b / 强调的文本em / 重要的文本 strong超链接: a联系信息: addressdiv 定义文档中的分区或节: div行元素:spanhtml结构: main / section / articlenav表格:table html中各种标签/属性的英文扩…

记一次寻找js来文件上传

edu教育证书站之路 0x01 信息收集 通过fofa&#xff0c;子域名收集等相关工具搜索域名 定位到站点&#xff1a;htps://xx..edu.cn/x/xx/ 0x02 寻找接口 通过f12寻找相关的js&#xff0c;发现有其他的页面 0x03 拼接路径 https://xx.xx.edu.cn/xx/xx/repairResgister 之后未授权…