【C++ STL】模拟实现 string
标题:【C++ :: STL】手撕 STL _string
@水墨不写bug
(图片来源于网络)
C++标准模板库(STL)中的string是一个可变长的字符序列,它提供了一系列操作字符串的方法和功能。
本篇文章,我们将模拟实现STL的string类的部分功能,以增强对STL的熟练度,了解STL容器的工作原理,积累项目经验,也为将来自主实现和改造容器奠定坚实的基础。
STL的string类是一个模板,而我们为了方便实现,以达到练习的目的,我们暂时先实现一个成员变量为(下图示)的string类。
char* _str;
size_t _size;//字符串长度,不加上\0
size_t _capacity;
C++ STL的string类提供了以下常用的成员函数和接口:
构造函数和赋值操作函数接口:
- 默认构造函数:创建一个空字符串。
- 带string参数的构造函数:将一个string对象复制到另一个string对象中。
- 带字符数组参数的构造函数:将字符数组转换为string对象。
- 带整数参数的构造函数:将整数转换为字符串。
- 赋值操作符:用另一个string对象、字符数组或字符来赋值。
访问字符串内容相关函数接口:
- at():返回指定位置的字符。
- operator[]:返回指定位置的字符。
- front():返回第一个字符。
- back():返回最后一个字符。
- c_str():返回一个以空字符结尾的字符数组。
修改字符串内容接口:
- insert():在指定位置插入字符、字符串或字符数组。
- erase():删除指定位置的字符。
- replace():替换指定位置的字符串或字符。
- append():在字符串末尾添加字符、字符串或字符数组。
- clear():清空字符串。
字符串操作接口:
- size() 或 length():返回字符串的长度。
- empty():判断字符串是否为空。
- find():查找指定字符串或字符的位置。
- substr():返回指定位置和长度的子字符串。
- compare():比较两个字符串
(具体用法在上一篇讲解:【Cpp::STL】标准模板库_ string详解)
(一)头文件
我们在C语言阶段实现声明和定义分离的时候,只是单一的把函数的定义放在.c(源)文件,把函数的声明,头文件的包含,宏定义等放在.h(头)文件。
但是,在C++,不仅要遵守以上的规则,由于类的出现,需要域作用限定符(::)来限定方位;由于成员的访问权限的出现,需要考虑访问权限的问题;此外不同类型的成员的定义的位置也有讲究,比如静态成员尽量不要直接定义在头文件中,因为这会引发 多次包含多文件 在链接时的 头文件内的对象的重定义问题。
本文根据STL标准模板库的功能,给出头文件,包括string类的定义,众多成员函数,部分非成员函数(流插入,流提取的重载),并在后半节详细讲解各个函数的实现思路。
#define _CRT_SECURE_NO_WARNINGS 1 #pragma once #include<iostream> #include<cstring> #include<cassert> using namespace std;namespace ddsm {class string {friend ostream& operator<<(ostream& out, const string& s1);public://迭代器typedef char* iterator;typedef const char* const_iterator;iterator begin();const_iterator begin() const;iterator end();const_iterator end() const;//传参构造,默认构造,给默认值为空串,巧妙string(const char* str = "");string(const string& s);//copy constructor//string& operator=(const string& s);传统写法string& operator=(const char* s);string& operator=(string s);//现代写法//析构~string();//C类型字符串const char* c_str() const;//保留void reserve(int n);string& push_back(const char ch);//尾插字符string& append(const char* str);//尾插字符串string& operator+=(char ch);string& operator+=(const char* str);string& insert(size_t pos, const char ch);string& insert(size_t pos, const char* str);//缺省值代表最一般的情况string& erase(size_t pos = 0,size_t len = npos);//找一个字符size_t find(const char ch, size_t pos = 0);//找一个子串size_t find(const char* str, size_t pos = 0);void swap(string& s);string substr(size_t pos = 0,size_t len = npos);string& clear();private:char* _str;size_t _size;//字符串长度,不加上\0size_t _capacity;//特例,const静态整形对象可声明定义和一,但是可能造成链接时的错误static size_t npos;};istream& operator>>(istream& in, string& s);};
(二)string类的功能实现
(1)默认成员函数
i,构造函数
我们知道,构造函数的作用是在对象实例化时初始化对象,对于string类对象,含有三个基本成员变量:
char* _str;size_t _size;//字符串长度,不加上\0size_t _capacity;经过分析,我们得知在构造函数内部,需要申请动态的堆区空间给_str;需要根据_str的长度变化来动态更新_size;同时根据申请的动态空间的长度来更新_capacity。
于是,我们理所当然的想到这样写构造函数:
string::string(const char* str = "") // 缺省参数为一个空字符串,如果不传参,空字符串就是一个单独的'\0':_size(strlen(str)),_capacity(strlen(str)) {_str = new char[_size + 1];strcpy(_str, str); }但是,这种简单易懂的写法也暴露出了弊端:多次无意义的重复调用strlen,这会造成额外的消耗。于是,为了减少strlen的调用次数,我们考虑这样修改:
string::string(const char* str):_size(strlen(str)),_capacity(_size) {_str = new char[_size + 1];strcpy(_str, str); }这样修改虽然解决了strlen重复无意义调用的问题,但是也带来了新的问题:
程序稳定性下降的问题:
¥¥我们知道:初始化列表的初始化顺序是成员函数在类中的声明顺序:按照此例:
char* _str;size_t _size;//字符串长度,不加上\0size_t _capacity;先初始化_size,再初始化_capacity;在这种背景下,如果代码有一些微小的改变,或许就会造成意想不到的问题。
如果改变成员变量的顺序,那么初始化列表就会按照不同的顺序初始化。具体来说,如果_capacity在_size之前,初始化列表就会先初始化_capacity:
char* _str;size_t _capacity;size_t _size;//字符串长度,不加上\0这时_size还没有初始化,是随机值,那么就造成了_capacity为随机值的问题。
解决这个问题其实很简单,将对_capacity的初始化放入函数体:
string::string(const char* str)//strlen较低效,调用一次用size记录返回值//size/capacity不包含\0,但是其需要存储:_size(strlen(str)) {_str = new char[_size + 1];_capacity = _size;strcpy(_str, str); }这样就确定了是先初始化_size,再初始化_capacity。¥¥
(将声明和定义分离,需要将缺省参数放在声明处,同时函数名之前需要加上域作用限定符,表示这个函数在你实现的string类里面声明过。)
ii,析构函数
析构函数的作用是:清理资源。
由于比较简单,这里直接给出实现:
//析构 string::~string() {if(_str)delete[] _str;_size = _capacity = 0;_str = nullptr; }(函数名之前需要加上域作用限定符,表示这个函数在你实现的string类里面声明过。)
iii,拷贝构造
拷贝构造,完成创建对象时的初始化。
一般情况下,我们会这样写:
//拷贝构造 string::string(const string& s) {char* tem = new char[s._capacity+1];//多开一个,存储'\0'strcpy(tem, s._str);delete[] _str;//销毁原空间_str = tem;_size = s._size;_capacity = s._capacity; }但是,其实有更简单的写法:
void string::swap(string& s) {//调用模板swap交换内置类型,损失不大std::swap(_str, s._str);std::swap(_capacity, s._capacity);std::swap(_size, s._size); } //拷贝构造的现代写法 string::string(const string& s):_str(nullptr) {string tem(s._str);swap(tem); }仔细分析,我们其实在无形之中让构造函数给我们“打工”了:
string tem(s._str);就是用拷贝对象的字符串来构造一个tem对象,而这个tem对象就是我们需要的,所以我们实现一个swap函数,将*this与tem完全交换,同时tem在出作用域时也会自动析构,同样也达到了拷贝构造的目的。
iv,赋值重载
赋值重载:实现对象之间的赋值。
我们一般会这样实现:
//赋值重载 string& string::operator=(const char* s) {int len = strlen(s);char* tem = new char[len + 1];strcpy(tem, s);delete[] _str;_str = tem;_size = _capacity = len;return *this; }但是,同样也有更简单的写法:
void string::swap(string& s) {//调用模板swap交换内置类型,损失不大std::swap(_str, s._str);std::swap(_capacity, s._capacity);std::swap(_size, s._size); }//赋值重载的现代写法 string& string::operator=(string tem) {//自动调用拷贝构造swap(tem);//出作用域自动完成析构return *this; }在无形之中,我们让拷贝构造为我们“打工”。
我们通过传值传参,拷贝构造一个临时对象tem,这个tem就是我们需要的,所以完全交换*this就得到了构造的对象,同时tem出作用域也会自动析构。
(2)迭代器
对于迭代器,本质上是一个指针,也可以是一个类(对指针的封装),在这里,我们不妨用指针来作为迭代器:
//声明: typedef char* iterator; typedef const char* const_iterator; iterator begin(); const_iterator begin() const; iterator end(); const_iterator end() const;//定义string::iterator string::begin(){return _str;}string::const_iterator string::begin() const{return _str;}string::iterator string::end(){return _str + _size;}string::const_iterator string::end() const{return _str + _size;}const迭代器用于const对象调用;普通迭代器用于普通迭代器调用。
普通迭代器可读可写,const迭代器只可读不可写。
(3)容量和长度
i.reserve()
改变string的容量,若要求值n大于现在的容量,则容量扩大到n;若要求值小于等于现有容量,则改变容量。
reserve对于size没有影响,不会改变string的内容。
实现如下:
//保留指定容量,容量只增不减 void string::reserve(int n) {//要求保留的大于现有容量,需要扩容if (n > _capacity){char* tem = new char[n + 1];// 申请新空间完毕,转移数据strcpy(tem, _str);delete[] _str;_str = tem;_capacity = n;//reserve不改变size} }ii,resize()
//resize()不改变capacity,可能改变sizevoid string::resize(int size,int ch)//size为设定值,_size为现有值{if (size < _size){_size = size;_str[size] = '\0';}else if (size > _size){if (size > _capacity){reserve(size);}int i = _size;while (i != size){_str[i++] = '\0';}_size = size;_str[_size] = '\0';}}如果设定值小于现有值,减小_size,相当于截断_str;
如果设定值等于现有值,不做处理;
如果设定值大于现有值,有三种情况:
size <_capacity: 不扩容,并在[ _size,size)之间补0;
size == _capacity: 不扩容,并在[ _size,size)之间补0;
size > _capzcity: 扩容,并在[ _size,size)之间补0;
(4)元素访问
i,operator[]
下标的随机访问:
//声明 char& operator[](size_t pos); const char& operator[](size_t pos) const;//定义 char& string::operator[](size_t pos) {assert(pos >= 0 && pos < _size);return _str[pos]; } const char& string::operator[](size_t pos) const {assert(pos >= 0 && pos < _size);return _str[pos]; }对于at,front,back可以复用operator[]来实现。
(5)修改方式
i,push_back()
实现尾插字符,实现如下:
//尾插字符,由于是一个一个插入,扩容不能太频繁,所以采用二倍扩容 string& string::push_back(const char ch) {if (_size == _capacity)//不一定需要扩容,若长度等于容量,再次插入需要扩容{int Newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(Newcapacity);}//扩容完毕,尾插字符_str[_size++] = ch;_str[_size] = '\0';return *this; }这里使用了一个扩容技巧,就是二倍扩容。
ii,append()
追加,这里简化为追加一段字符串。
//尾插字符串,直接reserve到指定长度字符串 string& string::append(const char* str) {int len = strlen(str);if (len + _size > _capacity){reserve(len + _size);//不改变size}//扩容完毕strcpy(_str + _size, str);_size += len;return *this; }首先要先保存原来的len,这样如果需要扩容,在扩容完毕之后,只需更新_size为原_size+=len即可。
否则,如果不保存len,在需要扩容的情况下,就会出现问题了:
##
()
##
iii,operator+=复用上两函数即可
尾插一个字符
string& string::operator+=(char ch) {push_back(ch);return *this; }尾插一个字符串
string& string::operator+=(const char* str) {append(str);return *this; }iv,insert()
在任意位置插入一个字符
//插入一个字符 //用push_back逻辑来扩容 string& string::insert(size_t pos, const char ch) {assert(pos >= 0 && pos <= _size);if (_size == _capacity){int Newcapacity = _capacity == 0 ? 4 : 2 * _capacity;reserve(Newcapacity);//不改变size}int end = _size+1;//细节问题,int与size_t参与比较,//int隐式类型转化为size_t//size_t(-1)会变成很大的整数while(end>pos){_str[end] = _str[end-1];--end;}_str[pos] = ch;_size += 1;return *this; }
在任意位置插入一个字符串//插入一个字符串 //用reserve逻辑扩容 string& string::insert(size_t pos, const char* str) {assert(pos >= 0 && pos <= _size);int len = strlen(str);if (len + _size > _capacity){reserve(len+_size);}int end = _size + len;while (end>pos+len-1){_str[end] = _str[end - len];--end;}memmove(_str + pos, str, len);_size += len;return *this; }v,erase()
在任意位置处删除长度为len的字符串:
string& string::erase(size_t pos, size_t len)//两种情况;删除部分string,pos之后全删 {assert(pos >= 0 && pos <= _size);if ((len == npos) ||(pos + len >= _size))//全删的情况{_str[pos] = '\0';_size = pos;}else//删除部分string{int end = pos + len;while (_str[end]!='\0'){_str[end - len] = _str[end];++end;}_str[end-len] = '\0';}return *this; }
(6)串操作
i,find()
找字符
size_t string::find(const char ch, size_t pos) {for (size_t i = pos; i < _size; ++i){if (_str[i] == ch){return i;}}return npos; }找字符串
用到了strstr():字符串匹配函数。
size_t string::find(const char* str, size_t pos) {char* ret = strstr(_str, str);return (size_t)(ret - _str); }ii,c_str()
返回C类型的字符串:
const char* string::c_str() const {return _str; }iii,substr()
得到字符串的子串:
string string::substr(size_t pos, size_t len){assert(pos >= 0 && pos <= _size);if ((len == npos) || (pos + len >= _size)){string sub(_str + pos);return sub;}else{ string sub;sub.reserve(len);for (size_t i = 0; i < len; ++i){sub._str[i] = _str[pos + i];}sub._str[len] = '\0';sub._size =sub._capacity = len;return sub;}}
(7)成员常量
//特例,const静态整形对象可声明定义和一,但是可能造成链接时的错误 const static size_t npos = -1;无符号整数size_t(-1)是一个很大的整数。
(8)流插入和流提取
i,operator<<()
ostream& operator<<(ostream& out, const string& s) {for (size_t i = 0; i < s._size; ++i){cout << s._str[i];}cout << endl;return out; }ii,operator>>()
cin的get()函数可以提取空白字符和‘\n’,这也是循环逻辑结束的条件。
//流提取改进,用buf临时数组,防止string频繁扩容 istream& operator>>(istream& in,string& s) {s.clear();char buff[128] = { 0 };char ch = in.get();int i = 0;while(ch != ' ' && ch != '\n'){buff[i++] = ch;ch = in.get();if (i == 127){buff[i] = '\0';s += buff;i = 0;}}buff[i] = '\0';if (i != 0){s += buff;}return in; }整体使用了用临时栈区数组的方式来减少扩容次数,提高效率。
完~
未经作者同意禁止转载
相关文章:
【C++ STL】模拟实现 string
标题:【C :: STL】手撕 STL _string 水墨不写bug (图片来源于网络) C标准模板库(STL)中的string是一个可变长的字符序列,它提供了一系列操作字符串的方法和功能。 本篇文章,我们将模拟实现STL的…...
js 选择一个音频文件,绘制音频的波形,从右向左逐渐前进。
选择一个音频文件,绘制波形,从右向左逐渐前进。 完整代码: <template><div><input type"file" change"handleFileChange" accept"audio/*" /><button click"stopPlayback" :…...
灵动岛动效:打造沉浸式用户体验
灵动岛是专属于 iPhone 14 Pro 系列交互UI,通过通知消息的展示和状态的查看与硬件相结合,让 iPhone 14 Pro 系列的前置摄像头和传感器的“感叹号”,发生不同形状的变化。这样做的好处是让虚拟软件和硬件的交互变得更为流畅,以便让…...
VSCode数据库插件
Visual Studio Code (VS Code) 是一个非常流行的源代码编辑器,它通过丰富的插件生态系统提供了大量的功能扩展。对于数据库操作,VS Code 提供了几种插件,其中“Database Client”系列插件是比较受欢迎的选择之一,它包括了对多种数…...
正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-25 多点电容触摸屏实验
前言: 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM(MX6U)裸机篇”视频的学习笔记,在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…...
B3726 [语言月赛202303] String Problem P
[语言月赛202303] String Problem P 题目描述 Farmer John 有 n n n 个字符串,第 i i i 个字符串为 s i s_i si。 现在,你需要支持如下 q q q 次操作: 1 x y i:把字符串 s x s_x sx 整体插入到字符串 s y s_y sy …...
htb-linux-3-shocker
nmap web渗透 由于只有80端口,只考虑目录扫描和静态文件提醒 为什么能能知道http://10.10.10.56/cgi-bin/user.sh? 因为百度的 curl访问该文件 shell flag root...
Elasticsearch - No mapping found for [field_name] in order to sort on
chax根据关键字Action, MD5,模糊索引202*.log查询 curl -u user:password -H "Content-Type: application/json" http://127.1:9200/202*.log/_search?pretty -XPOST -d {"query": {"bool": {"should": [{"bool"…...
Lua 元表(Metatable)深入解析
Lua 元表(Metatable)深入解析 Lua 是一种轻量级的编程语言,因其简洁性和强大的扩展能力而被广泛应用于游戏开发、脚本编写和其他领域。在 Lua 中,元表(Metatable)是一个非常重要的概念,它允许我…...
MySQL Show命令集
MySQL SHOW 命令 1、mysql shell 查看帮助show (rootlocalhost) [(none)]> \help show Name: SHOW Description: SHOW has many forms that provide information about databases, tables, columns, or status information about the server. This section describes thos…...
倩女幽魂搬砖攻略:云手机自动托管搬砖刷本选哪家云手机?
欢迎来到《倩女幽魂手游》的世界,一个充满江湖恩怨的世界。在这个游戏中,你将扮演各个门派中的不同职业,踏上一段属于你自己的江湖之路。本攻略将为你详细介绍如何利用多开挂机搬砖,快速提升自己的实力,成为江湖中的一…...
php7.3安装phalcon扩展
php7安装3.4版本的phalcon扩展 适用于Centos6.x和Centos7.x系统,php使用7.1版本,wlnmp一键包已支持该扩展 phalcon扩展包地址:https://github.com/phalcon/cphalcon (git clone 有可能连接不上) 1、安装所需依赖&a…...
IIoT(智能物联网)的现状、应用及安全
近年来,物联网(IoT)作为推动现代公司和智能城市发展的一个范式,已经取得了显著的发展。IoT使得分布式设备(如手机、平板电脑和计算机)能够感知并从外部环境传输数据,以服务于最终用户。IoT的概念…...
YOLOv8_obb的训练、验证、预测及导出[旋转目标检测实践篇]
1.旋转目标检测数据集划分和配置 从上面得到的images和labels数据还不能够直接训练,需要按照一定的比例划分训练集和验证集,并按照下面的结构来存放数据,划分代码如下所示,该部分内容和YOLOv8的训练、验证、预测及导出[目标检测实践篇]_yolov8训练测试验证-CSDN博客是重复的…...
C语言实战:贪吃蛇(万字详解)
💡目录 效果图 界面设计思路 1. 基本布局 2. 视觉元素 游戏机制设计 基本规则 游戏代码 前期准备 游戏代码详解 数据结构设计 宏定义 数据结构定义 函数原型(详见后文) 主函数代码 核心代码 Review 效果图 界面设计思路 1. 基…...
定时器更新界面,线程报错
项目场景: 在javafx框架下使用线程更新UI的时候,出现无法正常更新UI。 问题代码如下: package clock;import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Timer; import java.util.TimerTask;import javaf…...
未来AI大模型的发展趋势
大家好,我是小悟 未来AI大模型的发展趋势无疑将是多元化、高效化、普及化以及人性化。随着技术的飞速进步,AI大模型将在各个领域中展现出更加广泛和深入的应用,成为推动社会进步的重要力量。 多元化是AI大模型发展的重要方向。随着数据量的…...
【JavaScript函数详解】Day04
JavaScript函数详解 JavaScript 基础 - 第4天笔记函数声明和调用声明(定义)调用 参数形参和实参参数默认值 返回值函数补充细节作用域全局作用域局部作用域变量的访问原则 匿名函数函数表达式立即执行函数 逻辑中断小知识(转换为Boolean型&am…...
json和axion结合
目录 java中使用JSON对象 在pom.xml中导入依赖 使用 public static String toJSONString(Object object)把自定义对象变成JSON对象 json和axios综合案例 使用的过滤器 前端代码 响应和请求都是普通字符串 和 请求时普通字符串,响应是json字符串 响应的数据是…...
v1.2.70-FastJson的AutoType机制研究
v1.2.70-FastJson的AutoType机制研究 最近在对接Alexa亚马逊语音技能,Smart Home Skill Apis时,有一个配置的JSON字符串是这样的: { "capabilityResources": {"friendlyNames": [{"type": "asset",…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...
大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...
苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...
【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
抽象类和接口(全)
一、抽象类 1.概念:如果⼀个类中没有包含⾜够的信息来描绘⼀个具体的对象,这样的类就是抽象类。 像是没有实际⼯作的⽅法,我们可以把它设计成⼀个抽象⽅法,包含抽象⽅法的类我们称为抽象类。 2.语法 在Java中,⼀个类如果被 abs…...
