当前位置: 首页 > news >正文

C++STL库中不可或缺的部分—string(模拟实现)

前文

大家好,本篇文章主要是讲解一下string一些常用接口的模拟实现
众所周知,在日常生活中,字符串无处不在,如''just do it'',''中国'',''一坤年''等,想要在计算机上将这些字符展现出来就需要用到string类,而对我们C++程序员来说能否模拟实现string是对我们基本功的一个重要考验
话不多说,下面就开始模拟实现。(文末有源代码,需要自取)

一,常用接口的实现

ps:为了和库里面的string区分开,所以我们新创了一个命名空间,名字为mjw,我们将在里面实现string。
本次模拟成员变量如下定义

1.1 构造函数

如图所示,上面是库中string构造函数的各个函数重载,其中比较常用的是的是(1)无参构造函数,(2)拷贝构造函数,(4)有参构造函数

1.1.1 有参/无参构造函数

由于无参构造函数其实就是传字符' ',所以我们将(1)(4)合到一起实现,(1)将作为(4)的缺省参数实现
在写代码时,我们需要注意两点:
1. strlen(str)计算的时'\0'前面的字符数量,所以在开空间时要加上'\0'的位置
2. 开空间要注意有可能开辟失败,所以我们先创建一个指针ptr开空间,成功后再将ptr赋值给_str
3.字符串的拷贝我们直接用strcpy实现,下面简单介绍一些strcpy的用法
如上图所示,strcpy的作用是将source中的内容拷贝到destination指向的空间
        //有参构造函数,无参利用缺省参数实现string(const char* str = ""):_size(strlen(str)){//由于strlen计算的是"/0"前面字符的数量,//所以实际空间要留出'/0'的位置,也就是要多开辟一个空间_capaicty = strlen(str)==0?3:strlen(str);char* ptr = new char[_capacity + 1];strcpy(ptr, str);_str = ptr;}

1.1.1 拷贝构造函数

拷贝构造函数的逻辑和构造函数类似,但是需要注意不要用默认拷贝构造函数,那样看起来是拷贝成功,实际上两个指针指向的是同一个空间。
这里就涉及到深浅拷贝的问题
浅拷贝就会造成如下问题:(用的是之前类和对象的图,原谅我偷懒啦)
因此如果一个类中涉及到资源管理那么其拷贝构造函数,赋值重载函数,析构函数都需要显示给出,都需要按照深拷贝的方式提供。

拷贝构造函数代码如下:

//拷贝构造函数string(const string& s):_size(s.size()){_capaicty = s._capacity;char* ptr = new char[_capacity + 1];strcpy(ptr, s._str);_str = ptr;}

1.2析构函数

将开辟的空间释放,然后将_str置空即可,一定要注意开辟和释放所用关键字要配对(new []/delete[])

代码如下:

        //析构函数~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}

1.3 []运算符重载

由于[]访问字符串比较方便,所以我们为了后续方便测试,我们将[]运算符重载放到第三个实现。
为了应对不同情况的权限问题,所以我们打算完成上面的两个函数重载,这里需要注意的点就是要保证pos值的合法性,也就是pos<=_size.

代码如下:

        //[]重载char& operator[](size_t size){assert(!(size > _size));return _str[size];}const char& operator[](size_t size) const//应对只用[]遍历,不修改的权限问题{assert(!(size > _size));return _str[size];}

1.4 返回_size/返回_str的地址/返回_capacity

三个个比较简短却又不能缺少的接口,没什么难度就不做赘述了。

代码如下:

        //返回sizesize_t size() const{return _size;}//返回_str地址const char* c_str(){return _str;}//返回capacitysize_t capacity() const{return _capacity;}

1.5赋值函数重载

如上图所示,如果是第三种情况两个长度相等,那么容量不用变;如果是第一种情况s1的长度小于s2,要将s1赋值给s2,直接拷贝即可,但是此时会有一个问题,那就是有大量空间浪费掉了;第二种情况,s1的长度大于s2,想要将s1赋值给s2,s2就要扩容,但是new不支持扩容,所以我们只能将s2原来空间释放,重新开辟一个和s1一样大的空间再将s1的内容拷贝过去
综上所述,我们为了满足每一种的情况,采取第二种的应对方法,就是将原来空间释放掉,重新开辟一个空间进行拷贝

代码如下:

//赋值string& operator=(const string& s){if (this != &s)//s1=s1的情况{//new开辟失败的时候,赋值没有实现,但s1却已经被破坏/*delete[] _str;_str = new char[s._capaicty + 1];_size = s._size;_capaicty = s._capaicty;strcpy(_str, s._str);*/char* ptr = new char[s._capaicty + 1];strcpy(ptr, s._str);delete[] _str;_str = ptr;_size = s._size;_capaicty = s._capaicty;}return *this;}

1.6 迭代器

迭代器(Iterator)是一个对象,它的工作是遍历并选择序列中的对象,它提供了一种访问一个容器(container)对象中的各个元素,而又不必暴露该对象内部细节的方法。
string的迭代器实现方式比较简单,用typedef就可以实现。

代码如下:

//迭代器typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}//const修饰的迭代器const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}
但是由于string中的[]更加方便,所以迭代器用的地方比较少,但是后面的list迭代器用处很大。

1.7 reserve(扩容)

扩容函数接口是我们后面模拟插入,尾插等必不可少的接口,虽然很重要但是实现还是比较简单的。

reserve接口的实现和赋值函数重载的实现一致,都是把原来的空间销毁,然后新开空间。

代码如下:

        //扩容,和赋值的思路类似void reserve(const size_t n){if (_capacity < n){//开n+1的空间,是要给'/0'留一个空间char* ptr = new char[n + 1];//防止开空间失败所以先用ptr接收,成功后在赋值给_strstrcpy(ptr, _str);delete[] _str;_str = ptr;_capacity = n;}}

1.8 insert(重点)

insert接口实现是string模拟中比较重要的一个点,后面的尾插可以复用这个,而且这一部分的细节比较多,需要多注意。

对于intsert部分,我们打算实现两个函数重载:
1.在pos位置插入字符串 2.在pos位置插入字符

1.8.1 insert(插入字符串)

insert:在指定的位置插入字符或者字符串
插入字符串的大体逻辑如下:
首先检查是否需要扩容,然后在将pos位置往后的字符往后挪len(要插入的字符串的长度)个位置,给要插入的字符串留出足够的位置,然后拷贝字符串
注意:最后的拷贝字符串可以手动拷贝,我们这里选择的是用库里的函数strncpy进行拷贝,相比与strcpy,strncpy的控制更加精准
strncpy简单介绍
函数的作用大致为从source中拷贝num个字符到destination中

代码如下:

//在pos的位置插入字符串sstring& insert(size_t pos, const string& s){assert(pos <= _size);//检查pos是否合法int len = s.size();//检查扩容if (_size + len > _capacity){reserve(_size + len);}size_t end = _size;//pos的数据及后面的数据向后挪len个位置while (end >= pos){_str[end + len] = _str[end];end--;}//插入字符串//strcpy(_str + pos, s._str);strncpy(_str + pos, s._str,len);_size += len;return *this;}
插入的基本功能差不多完成了,但是其中还有一个小bug不知道铁子们发现没有,那就是当pos为0时,循环会进入死循环
注意此时end为0,按照我们的逻辑来看,下一步为-1,就该跳出循环了。
实际上并不是我们想的那样,end变成-1,而变成了最大值,这是因为什么呢,
因为end和pos的类型都是size_t,而size_t实际上是unsignen int,因此当end为0进行--时就直接变成了最大值.
那么有没有避免这种情况的方法?
答案肯定是有的如:
1. 将end和pos的类型都变成int,但是这样就和库中的参数不同,有违我们模拟的初衷
ps:如果只改变end的类型,在比大小的时候仍会被强制转成size_t,当然也可以在比的时候把pos强制转出int,但是这样可能会导致数据失真。
2. 改变循环逻辑
如上所示,这样以来end的最小值不会再低于0,这样就不会因为是无符号整形,导致永远是正数,从而导致死循环。

改良后的代码:

//在pos的位置插入字符串sstring& insert(size_t pos, const string& s){assert(pos <= _size);//检查pos是否合法int len = s.size();//检查扩容if (_size + len > _capacity){reserve(_size + len);}size_t end = _size+len;//pos的数据及后面的数据向后挪len个位置/*while (end >= pos){_str[end + len] = _str[end];end--;}*/while (end > pos + len - 1){_str[end] = _str[end - len];end--;}//插入字符串//strcpy(_str + pos, s._str);strncpy(_str + pos, s._str,len);_size += len;return *this;}

1.8.2 insert(插入字符)

插入字符和插入字符串一样,其实就是把插入字符串中的len变成1就是插入字符。
//在pos的位置插入字符chstring& insert(size_t pos, const char ch){assert(pos <= _size);//检查pos是否合法//检查扩容if (_size + 1 > _capacity){reserve(_capacity * 2);//二倍扩容}size_t end = _size+1;while (end > pos){_str[end] = _str[end-1];end--;}_str[pos] = ch;_size++;return *this;}

1.9 erase

erase:在pos位置往后(包括pos)删除len个字符,当len>=_size时,默认pos后面的数据删完即可
erase情况分三种:len==npos,len>=_size,len<size.因为len类型为size_t,而npos值恒定为-1,所以前两种情况可以归为一种,就是len>=_size.

代码如下:

//erase,在pos位置往后(包括pos)删除n/npos个字符string& erase(size_t pos = 0, size_t len = npos){assert(pos <= _size);//检查pos是否合法if (len == npos || len >= _size){_str[pos] = '\0';_size = pos;}else{//将pos后面的数据都向前挪len个位置//1.手动挪//size_t cur = pos;//while (cur <= _size - len)//{//    _str[cur] = _str[cur + len];//    cur++;//}//2.strcpystrcpy(_str + pos, _str + pos + len);_size -= len;}

1.10 push_back(尾插字符)和append(尾插字符串)

1.10.1 push_back

实现方法:
1.检查扩容,然后直接插入
2.复用insert(插入字符)
//尾插字符void push_back(char ch){//1.检查扩容,然后直接插入//检查扩容//if (_size + 1 > _capacity)//{//    reserve(_capacity*2);//二倍扩容//}当前_size指向的是原字符串'\0'的位置,此时赋值'\0'会被覆盖所以需要在后面补上'\0'//_str[_size] = ch;//_size++;//_str[_size] = '\0';//2.复用insertinsert(_size, ch);}

1.10.2 append

我们要实现的是上面的第一个函数重载
实现方法:
1.检查扩容,然后用strcpy拷贝
2. 复用insert(插入字符串)
//尾插字符串void append(const string& s){//1.检查扩容,然后用strcpy拷贝//int len = s._size;检查扩容//if (_size + len > _capacity)//{//    reserve(_size + len);//按需扩容//}//strcpy(_str + _size, s._str);//_size += len;//2. 复用insert(插入字符串)insert(_size, s);}

1.11 +=操作符重载

我们要实现上图的第一个和第三个函数重载
实现方式:复用push_back(尾插字符)和append(尾插字符串)即可
//+=重载 复用尾插和尾插字符串//+=字符//1.字符string& operator+=(const char ch){push_back(ch);return *this;}//2.字符串string& operator+=(const string& s){append(s);return *this;}

1.12 resize

resize:重新规划_size的大小,注意不是_capacity的大小,而是元素的个数。
resize的实现分为以下情况:

代码实现:

void resize(size_t n, char ch = '\0'){if (n <= _size){_size = n;_str[_size] = '\0';}else{//判断扩容if (n > _capacity){reserve(n);}for(int i = _size; i < n; i++){_str[i] = ch;}_size = n;_str[_size] = '\0';}}

1.13 swap

写交换函数的时候尽量不要直接复用库里的swap函数,下面代码会解释。
//交换函数//swap(s1,s2);//和上面库中的交换函数比,类中的交换函数效率更高//因为库中函数需要调用三次构造函数构造s1,s2//而类中的交换函数,可以直接引用传参,不需要调用构造函数void swap(string& s){//用库中的swap函数,前面要加std//不然会优先调用当前类中的swap函数,参数不对会出错std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}

1.14 <<(流插入)和>>(流提取)重载

流插入流提取都不能作为成员函数实现,因为成员函数中*this永远是第一个参数,所以在成员函数中实现只能实现这样的效果:s1<<cout,所以我们一般是作为全局函数或者友元函数实现。

1.14.1 <<(流插入)

流插入我们采取一个范围for来实现
//流插入ostream& operator<<(ostream& out,string& s){for (auto ch : s){out << ch;}return out;}

1.14.2 >>(流提取)重载

在写流提取重载前,我们可以看看库中是如何运行的
观察上面程序我们发现,每次进行流提取,会将字符串的原数据删除,然后输入流提取的内容
ps:在写入字符时,要用istream中的get()函数,如果直接用>>,库中函数默认空格和'\n'会清除缓存,导致ch无法读取,从而无法停止循环,如下所示

因此需要用in.get()函数提取字符

代码如下:

//流提取istream& operator>>(istream& in,string& s){char ch = in.get();//直接流提取输入默认' '是单词的间隔s.erase();while (ch!=' '&&ch != '\n'){s += ch;ch = in.get();}return in;}

二,源码

#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
namespace mjw
{class string{public://迭代器typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}//const修饰的迭代器const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}//有参构造函数,无参利用缺省参数实现string(const char* str = ""):_size(strlen(str)){//由于strlen计算的是"/0"前面字符的数量,//所以实际空间要留出'/0'的位置,也就是要多开辟一个空间_capacity = strlen(str)==0?3:strlen(str);char* ptr = new char[_capacity + 1];strcpy(ptr, str);_str = ptr;}//拷贝构造函数string(const string& s):_size(s.size()){_capacity = s.capacity();char* ptr = new char[_capacity + 1];strcpy(ptr, s._str);_str = ptr;}//[]重载char& operator[](size_t size){assert(!(size > _size));return _str[size];}const char& operator[](size_t size) const//应对只用[]遍历,不修改的权限问题{assert(!(size > _size));return _str[size];}//返回sizesize_t size() const{return _size;}//返回_str地址const char* c_str(){return _str;}//返回capacitysize_t capacity() const{return _capacity;}//赋值string& operator=(const string& s){if (this != &s)//s1=s1的情况{//new开辟失败的时候,赋值没有实现,但s1却已经被破坏/*delete[] _str;_str = new char[s._capaicty + 1];_size = s._size;_capaicty = s._capaicty;strcpy(_str, s._str);*/char* ptr = new char[s.capacity() + 1];strcpy(ptr, s._str);delete[] _str;_str = ptr;_size = s._size;_capacity = s.capacity();}return *this;}//比较大小// 对于不修改成员变量的函数尽量用const修饰一下//<bool operator<(const string& s) const{return strcmp(_str, s._str) < 0;}//==bool operator==(const string& s) const{return strcmp(_str, s._str) == 0;}//>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);}//扩容,和赋值的思路类似void reserve(const size_t n){if (_capacity < n){//开n+1的空间,是要给'/0'留一个空间char* ptr = new char[n + 1];//防止开空间失败所以先用ptr接收,成功后在赋值给_strstrcpy(ptr, _str);delete[] _str;_str = ptr;_capacity = n;}}//尾插字符void push_back(const char ch){//1.检查扩容,然后直接插入//检查扩容//if (_size + 1 > _capacity)//{//    reserve(_capacity*2);//二倍扩容//}当前_size指向的是原字符串'\0'的位置,此时赋值'\0'会被覆盖所以需要在后面补上'\0'//_str[_size] = ch;//_size++;//_str[_size] = '\0';//2.复用insertinsert(_size, ch);}//尾插字符串void append(const string& s){//1.检查扩容,然后用strcpy拷贝//int len = s._size;检查扩容//if (_size + len > _capacity)//{//    reserve(_size + len);//按需扩容//}//strcpy(_str + _size, s._str);//_size += len;//2. 复用insert(插入字符串)insert(_size, s);}//+=重载 复用尾插和尾插字符串//+=字符//1.字符string& operator+=(const char ch){push_back(ch);return *this;}//2.字符串string& operator+=(const string& s){append(s);return *this;}//void resize(size_t n, char ch = '\0'){if (n <= _size){_size = n;_str[_size] = '\0';}else{//判断扩容if (n > _capacity){reserve(n);}for(int i = _size; i < n; i++){_str[i] = ch;}_size = n;_str[_size] = '\0';}}//insert//在pos的位置插入字符chstring& insert(size_t pos, const char ch){assert(pos <= _size);//检查pos是否合法//检查扩容if (_size + 1 > _capacity){reserve(_capacity * 2);//二倍扩容}size_t end = _size+1;while (end > pos){_str[end] = _str[end-1];end--;}_str[pos] = ch;_size++;return *this;}//在pos的位置插入字符串sstring& insert(size_t pos, const string& s){assert(pos <= _size);//检查pos是否合法int len = s.size();//检查扩容if (_size + len > _capacity){reserve(_size + len);}size_t end = _size+len;//pos的数据及后面的数据向后挪len个位置/*while (end >= pos){_str[end + len] = _str[end];end--;}*/while (end > pos + len - 1){_str[end] = _str[end - len];end--;}//插入字符串//strcpy(_str + pos, s._str);strncpy(_str + pos, s._str,len);_size += len;return *this;}//erase,在pos位置往后(包括pos)删除n/npos个字符string& erase(size_t pos = 0, size_t len = npos){assert(pos <= _size);//检查pos是否合法if (len == npos || len >= _size){_str[pos] = '\0';_size = pos;}else{//将pos后面的数据都向前挪len个位置//1.手动挪//size_t cur = pos;//while (cur <= _size - len)//{//    _str[cur] = _str[cur + len];//    cur++;//}//2.strcpystrcpy(_str + pos, _str + pos + len);_size -= len;}return *this;}//交换函数//swap(s1,s2);//和上面库中的交换函数比,类中的交换函数效率更高//因为库中函数需要调用三次构造函数构造s1,s2//而类中的交换函数,可以直接引用传参,不需要调用构造函数void swap(string& s){//用库中的swap函数,前面要加std//不然会优先调用当前类中的swap函数,参数不对会出错std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}//析构函数~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}private:char* _str;size_t _size;size_t _capacity;static size_t npos;//static const size_t npos;两个是一样的};size_t string::npos = -1;//流插入ostream& operator<<(ostream& out,string& s){for (auto ch : s){out << ch;}return out;}//流提取istream& operator>>(istream& in,string& s){char ch = in.get();//直接流提取输入默认' '是单词的间隔s.erase();while (ch!=' '&&ch != '\n'){s += ch;ch = in.get();}return in;}}

总结

以上就是我们模拟实现的接口,我们模拟实现string的目的不是造一个更好的轮子,而是更加深入的了解string的各个常用接口,希望能够对铁子们有所帮助。

相关文章:

C++STL库中不可或缺的部分—string(模拟实现)

前文大家好&#xff0c;本篇文章主要是讲解一下string一些常用接口的模拟实现。众所周知&#xff0c;在日常生活中&#xff0c;字符串无处不在&#xff0c;如just do it,中国,一坤年等&#xff0c;想要在计算机上将这些字符展现出来就需要用到string类&#xff0c;而对我们C程序…...

MySQL复合查询

文章目录基本查询回顾多表查询自连接子查询单行子查询多行子查询多列子查询在from子句中使用子查询合并查询unionunion all基本查询回顾 查询的员工部门表结构&#xff1a; mysql> show tables; ----------------- | Tables_in_scott | ----------------- | dept …...

PCIe 资料收集2

文章目录感官认识PCIe的存储空间PCIe 在 linux 下的驱动PCIe 验证1.PCIe 传递裸数据2.PCIe 转其他设备PCIe转其他总线RS232USB从用户空间理解PCIe感官认识 总线协议接口 视频介绍PCIe 视频介绍及PCIe文字介绍 PCIe上可以接各种控制器硬盘控制器硬盘声卡控制器音响咪头/耳机显…...

Linux网络编程(使用VScode远程登录ubuntu)

文章目录 前言一、SSH插件的安装1.SSH简单介绍2.SSH插件安装和配置步骤二、安装C/C++插件总结前言 本篇文章将带大家进行网络编程的准备工作,使用vscode进行远程登录ubuntu。为什么要使用vscode进行远程登录ubantu呢?因为有些小伙伴的电脑可能性能不够开启虚拟机后会导致电脑…...

如何提高项目估算精准度?关键看5大影响因子

如何让项目估算工作更加精准&#xff0c;我们需要重点关注5大调整因子。 1、功能点调整因子 首先需要对功能点因子进行调整&#xff0c;区分不同类型的系统特征值。 因为不同的系统&#xff0c;对项目开发的影响程度不同&#xff0c;一般我们把系统特征值分为14种类型&#xff…...

论文阅读笔记《Nctr: Neighborhood Consensus Transformer for Feature Matching》

核心思想 本文提出一种融合邻域一致性的Transfomer结构来实现特征点的匹配&#xff08;NCTR&#xff09;。整个的实现流程和思想与SuperGlue相似&#xff0c;改进点在于考虑到了邻域一致性。邻域一致性在许多的传统图像匹配和图匹配任务中都有应用&#xff0c;他基于一个很重要…...

上位机系统Ubuntu 20.04与下位机arduino UNO通讯

目录一、安装arduino IDE1.1安装方法1.1.1终端里命令下载&#xff08;不推荐&#xff09;1.1.2官网下载&#xff08;不推荐&#xff09;1.1.3论坛下载&#xff08;不推荐&#xff09;1.1.4系统应用商店&#xff08;推荐&#xff01;&#xff09;1.2配置项目文件位置1.3测试IDE功…...

hive面试题

1、什么是Hive Hive是基于Hadoop的一个数据仓库工具&#xff0c;可以将结构化的数据文件映射为一张数据库表&#xff0c;并提供类SQL查询功能&#xff08;HQL&#xff09; 2、Hive的意义&#xff08;最初研发的原因&#xff09; 避免了去写MapReduce&#xff0c;提供快速开发的…...

【CUDA】《CUDA编程:基础与实践》CUDA加速的关键因素

CUDA事件计时 CUDA提供了一种基于CUDA事件(CUDA event)的计时方式&#xff0c;可用来给一段CUDA代码(可能包含主机代码和设备代码)计时。 对计时器的封装&#xff1a; class CUDATimeCost { public:void start() {elapsed_time_ 0.0;// 初始化cudaEventcheckCudaRuntime(cud…...

数据结构【Golang实现】(四)——双向循环链表

目录0. 定义节点1. IsEmpty()2. Length()3. AddFromHead()4. AddFromTail()5. Insert()6. DeleteHead()7. DeleteTail()8. Remove()9. RemoveByValue()10. Contain()11. Traverse()0. 定义节点 type DLNode struct {Data anyPrev, Next *DLNode }// DoublyLoopLinkedLis…...

【Redis】高可用架构之哨兵模式 - Sentinel

Redis 高可用架构之哨兵模式 - Sentinel1. 前言2. Redis Sentinel 哨兵集群搭建2.1 一主两从2.2 三个哨兵3. Redis Sentinel 原理剖析3.1 什么哨兵模式3.2 哨兵机制的主要任务3.2.1 监控&#xff08;1&#xff09;每1s发送一次 PING 命令&#xff08;2&#xff09;PING 命令的回…...

图片的美白与美化

博主简介 博主是一名大二学生&#xff0c;主攻人工智能研究。感谢让我们在CSDN相遇&#xff0c;博主致力于在这里分享关于人工智能&#xff0c;c&#xff0c;Python&#xff0c;爬虫等方面知识的分享。 如果有需要的小伙伴可以关注博主&#xff0c;博主会继续更新的&#xff0c…...

面试官:关于CPU你了解多少?

CPU是如何执行程序的&#xff1f; 程序执行的基本过程 第一步&#xff0c;CPU 读取「程序计数器」的值&#xff0c;这个值是指令的内存地址&#xff0c;然后 CPU 的「控制单元」操作「地址总线」指定需要访问的内存地址&#xff0c;接着通知内存设备准备数据&#xff0c;数据准…...

UI自动化测试-Selenium的使用

文章目录 1. 环境搭建1.1 入门示例1.2 元素操作常用方法1.3 浏览器操作常用方法1.4 获取元素信息常用方法1.5 鼠标操作常用方法1.6 键盘操作常用方法1.7 下拉选择框操作2. 元素定位2.1 id定位2.2 name定位2.3 class_name定位2.4 tag_name定位2.5 link_text定位2.6 partail_link…...

嵌入式学习笔记——STM32的USART相关寄存器介绍及其配置

文章目录前言USART的相关寄存器介绍状态寄存器&#xff1a;USARTX->SR具体位代表的含义实际代码数据寄存器 USARTX->DR波特率寄存器 USARTX->BRR控制寄存器 (USART_CR)控制寄存器1&#xff08;USART_CR1&#xff09;控制寄存器2&#xff08;USART_CR2&#xff09;GPIO…...

Android setContentView流程分析(一)

对于做Android App的小伙伴来说setContentView这个方法再熟悉不过了&#xff0c;那么有多少小伙伴知道它的调用到底做了多少事情呢&#xff1f;下面就让我们来看看它背后的故事吧&#xff1f; setContentView()方法将分为两节来讲&#xff1a;   第一节&#xff1a;如何获取De…...

doris数据库操作数字遇到的问题

关于doris数据库Apache Doris 是一个基于 MPP 架构的高性能、实时的分析型数据库&#xff0c;以极速易用的特点被人们所熟知&#xff0c;仅需亚秒级响应时间即可返回海量数据下的查询结果&#xff0c;不仅可以支持高并发的点查询场景&#xff0c;也能支持高吞吐的复杂分析场景。…...

3.13文件的IO操作

一.文件1.定义文件一般指的是存储在硬盘上的普通文件形如:txt.jpg.mp4,rar等这些文件在计算机中,文件可能是一个广义的概念,不仅可以包含普通文件,还可以包含目录(也就是文件夹.把目录称为目录文件)在操作系统中,还会用文件来描述一些其他的硬件设备或者软件资源比如网卡,显示器…...

ffmpeg使用

1 下载FFmpeg安装 官网地址&#xff1a;https://www.ffmpeg.org/download.html#build-windows 进入网址&#xff0c;点击下面红框部分 点击下面范围进行下载&#xff0c;下载速度有点慢&#xff0c;等等吧&#xff01; 下载成功后&#xff0c;解压后&#xff0c;复制bin的路…...

spark中的并行度(分区数)/分区器如何确定

源头RDD有自己的分区计算逻辑&#xff0c;一般没有分区器,并行度是根据分区算法自动计算的&#xff0c;RDD的compute函数中记录了数据如何而来&#xff0c;如何分区的hadoopRDD&#xff0c;根据XxxinputFormat.getInputSplits()来决定&#xff0c;比如默认的TextInputFormat将文…...

00后女生“云摆摊”两周赚1.5万,实体店转战线上真的能赚钱吗?

最近&#xff0c;山东临沂的00后女生利用小程序在线上“云摆摊”卖水果&#xff0c;两周赚1.5万&#xff0c;引发网友热议。不少人发出质疑的声音&#xff1a;年轻人不要有稳定的工作不做&#xff0c;去摆摊&#xff1b;网上开店成本低&#xff0c;开实体店结果就难说了&#x…...

华为OD机试题 - 最优资源分配(JavaScript)| 机考必刷

更多题库,搜索引擎搜 梦想橡皮擦华为OD 👑👑👑 更多华为OD题库,搜 梦想橡皮擦 华为OD 👑👑👑 更多华为机考题库,搜 梦想橡皮擦华为OD 👑👑👑 华为OD机试题 最近更新的博客使用说明本篇题解:最优资源分配题目输入输出描述备注示例一输入输出说明示例二输入…...

利用python判断字符串是否为回文

1 问题 如何用python判断字符串是否为回文。 2 方法 用两个变量left&#xff0c;right模仿指针&#xff08;一个指向第一个字符&#xff0c;一个指向最后一个字符&#xff09;&#xff0c;每比对成功一次&#xff0c;left向右移动一位&#xff0c;right向左移动一位&#xff0c…...

GDB 调用之ptype、set variable

今天在公司的时候&#xff0c;排查一个问题&#xff0c;创建l3 lif 失败&#xff0c;查看各种日志发现是用key去创建的 lif失败了&#xff0c;日志里指示key为空&#xff0c;导致的创建失败。原因为一个结构体比基线的多了一些东西&#xff0c;导致版本不对&#xff0c;既而计算…...

并发编程---阻塞队列(五)

阻塞队列一 阻塞队列1.1.阻塞队列概念1.2.阻塞队列API案例1.2.1. ArrayBlockingQueue1.2.1.1.抛出异常1.2.1.2.返回布尔1.2.1.3.阻塞1.2.1.4.超时1.2.2.SynchronousQueue二 阻塞队列应用---生产者消费者2.1.传统模式案例代码结果案例问题---防止虚假唤醒2.2.⽣产者消费者防⽌虚…...

本科课程【计算机组成原理】实验1 - 输出ABCD程序的生成

大家好,我是【1+1=王】, 热爱java的计算机(人工智能)渣硕研究生在读。 如果你也对java、人工智能等技术感兴趣,欢迎关注,抱团交流进大厂!!! Good better best, never let it rest, until good is better, and better best. 近期会把自己本科阶段的一些课程设计、实验报…...

Java并发编程(2) —— 线程创建的方式与原理

一、Java线程创建的三种方式 1. 继承Thread类并重写run()方法 ///方法一&#xff1a;使用匿名内部类重写Thread的run()方法Thread t1 new Thread() {Overridepublic void run() {try {sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}log.debug("…...

你写的js性能有多差你知道吗 | js性能优化

性能的计算⽅式 确认⾃⼰需要关注的指标 常⻅的指标有&#xff1a; ⻚⾯总加载时间 load⾸屏时间⽩屏时间 代码 尝试⽤⼀个指令, 挂载在重要元素上, 当此元素inserted就上报 各个属性所代表的含义 connectStart, connectEnd 分别代表TCP建⽴连接和连接成功的时间节点。如果浏…...

线程的状态、状态之间的相互转换

目录 一、线程的状态 1. NEW 2. TERMINATED 3. RUNNABLE 4. TIMED_WAITING 5. BLOCKED 6. WAITING 二、线程状态转换 1. 线程状态转换简图 一、线程的状态 线程的状态一共有 6 种&#xff1a; NEW&#xff1a;安排了工作&#xff0c;还未开始行动&#xff08;调用 st…...

Java8使用Lambda表达式(流式)快速实现List转map 、分组、过滤等操作

利用java8新特性&#xff0c;可以用简洁高效的代码来实现一些数据处理。1 数据准备1.1 定义1个Fruit对象package com.wkf.workrecord.work;import org.junit.Test;import java.math.BigDecimal; import java.util.ArrayList; import java.util.List;/*** author wuKeFan* date …...

做律师咨询网站/山东疫情最新情况

简介 自.NET 4.5发布已经过了差不多1年了。但是随着最近微软大多数的发布&#xff0c;与.NET开发者交流的问题显示&#xff0c;开发者仅知道一到两个特性&#xff0c;其他的特性仅仅停留在MSDN并以简单的文档形式存在着。 比如说&#xff0c;当你问一个.NET开发者.NET框架内核中…...

如何提升做网站的效率/杭州网站推广与优化

Object.freezed() 冻结  检查函数 Object.isFrozen(obj) Object.seal() 密封 检查函数 Object.isSealed(obj) Object.preventExtensions()扩展 检查函数 Object.isExtensible(obj) 共同点&#xff1a; 都不能添加新的属性&#xff08;有一个例外就是属性是对象的时候&…...

葡萄酒公司网站建设/如何免费制作网站

Mysql的事务和锁相信大家都很熟悉&#xff0c;其实redis也是有的&#xff0c;只是因为redis的事务比较鸡肋很少被人谈起&#xff0c;至于为什么鸡肋下面我们就会见到。同时redis的分布式锁还是非常值得我们了解一下的。 文章目录什么是事务事务基本操作错误处理锁分布式锁死锁什…...

18末年年禁止观看网站/seo网站优化培训多少价格

地址 abs(x) 返回一个数的绝对值。实参可以是整数或浮点数。如果实参是一个复数&#xff0c;返回它的模。all(iterable) 如果 iterable 的所有元素为真&#xff08;或迭代器为空&#xff09;&#xff0c;返回 True 。def all(iterable):for element in iterable:if not ele…...

汉川建设局网站/营销顾问公司

先看看网上的方法是怎么样的&#xff1a; implementation (com.android.support:support-fragment:28.0.0){exclude group: "com.android.support",module: versionedparcelable}是我盲区&#xff0c;上述对我无效&#xff0c;没能解决&#xff0c;当场哭了 我们来看…...

怎么做赌钱网站代理/杭州关键词优化测试

消息队列 消息队列技术是分布式应用间交换信息的一种技术。消息队列可驻留在内存或磁盘上, 队列存储消息直到它们被应用程序读走。通过消息队列&#xff0c;应用程序可独立地执行--它们不需要知道彼此的位置、或在继续执行前不需要等待接收程序接收此消息。在分布式计算环境中&…...