C++ ——string的模拟实现

目录

前言

浅记

1. reserve(扩容) 

2. push_back(尾插) 

3. iterator(迭代器)

4. append(尾插一个字符串)

5. insert 

5.1 按pos位插入一个字符

5.2 按pos位插入一个字符串

6. erase 

7. find

7.1 查找字符

7.2 查找字符串

8. substr 

 代码汇总

string.h

string.cpp


前言

        C++ —— 关于string类-CSDN博客icon-default.png?t=O83Ahttps://blog.csdn.net/hedhjd/article/details/142023625?spm=1001.2014.3001.5501


浅记

char* _str;字符串存储空间首地址指针
size_t _size; 当前存储的有效数据个数
size_t _capacity;可用容量

*
_str:指向字符串存放的空间的指针
_size:当前存储的有效数据个数 ,指向最后一个字符的下一个位置
_capacity:代表当前可存储的最大容量
nops:此值设置为 -1,无符号整型转换就是42亿,且此值为const和静态参数具有全局效应,某些函数设置其为缺省参数

 


1. reserve(扩容) 

void string::reserve(size_t n)
{//如果字符串存储的数据大于容量if (n > _capacity){//cout << "reserve:" << n << endl;//申请新空间赋给临时变量tmp  + 1是位\0申请的空间char* tmp = new char[n + 1];//把数据拷贝到临时变量里strcpy(tmp, _str);//释放旧空间delete[] _str;//指向新空间_str = tmp;//字符串存储的数据等于容量_capacity = n;}
}


2. push_back(尾插) 

//尾插
void string::push_back(char ch)
{//如果当前的有效数据个数和最大容量相等 if (_size == _capacity){//那么扩容reserve(_capacity == 0 ? 4 : _capacity * 2);}//因为size指向最后一个字符的下一个位置,所以直接插入_str[_size] = ch;++_size;//最后要加一个\0,不然会乱码_str[_size] = '\0';
}

先判断是否还有剩余空间如果满了就进行扩容操作。扩容后在_size位置放上我们要插入的字符,然后++_size,最后在_size的后一个位置补上'\0',防止乱码


3. iterator(迭代器)

const size_t string::npos = -1;string::iterator string::begin()
{return _str;
}string::iterator string::end()
{return _str + _size;
}string::const_iterator string::begin() const
{return _str;
}string::const_iterator string::end() const
{return _str + _size;
}

 


4. append(尾插一个字符串)

//尾插一个字符串//_str:指向字符串存放的空间的指针//_size:当前存储的有效数据个数, 指向最后一个字符的下一个位置void string::append(const char* str){//先计算要插入的字符长度,将值赋给临时变量lensize_t len = strlen(str); //如果当前存储的有效数据个数 加上 要插入的字符 大于 容量if (_size + len > _capacity){// 大于2倍,需要多少开多少,小于2倍按2倍扩reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);}//将目标地址到\0的位置中间的值拷贝过去strcpy(_str + _size, str);//尾插_size += len;}

如果尾插后的字符串所需空间大于现有空间就扩容,然后使用strcpy函数将需要尾插的字符串从_str + _size的位置开始拷贝,最后插入len即可


5. insert 

5.1 按pos位插入一个字符

//按pos位插入一个字符
void string::insert(size_t pos, char ch)
{assert(pos <= _size);//如果当前的有效数据个数和最大容量相等 if (_size == _capacity){//那么扩容reserve(_capacity == 0 ? 4 : _capacity * 2);}// 挪动数据//_size + 1:\0的后一位//把end指向\0的后一位size_t end = _size + 1;//循环将pos之后的数据都向后挪动一位while (end > pos){_str[end] = _str[end - 1];--end;}//将字符ch放入pos位_str[pos] = ch;++_size;
}

先判断是否需要扩容,然后定义一个end指向'\0'的下一个位置,然后将pos之后的数据都向后挪动一位,最后将字符ch放入pos位

5.2 按pos位插入一个字符串

//按pos位插入一个字符串
void string::insert(size_t pos, const char* str)
{assert(pos <= _size);//先计算要插入的字符长度,将值赋给临时变量lensize_t len = strlen(str);//如果当前存储的有效数据个数 加上 要插入的字符 大于 容量if (_size + len > _capacity){// 大于2倍,需要多少开多少,小于2倍按2倍扩reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);}//定义一个end指向_size + len的位置上size_t end = _size + len;//通过循环把pos后的len个数据向后挪动len个位置while (end > pos + len - 1){_str[end] = _str[end - len];--end;}// 这里不能用 strcpy 会把 '\0' 拷贝过来//使用memcpy函数进行拷贝的原因是memcpy函数不会自动补上'\0'// 而strcpy函数会在拷贝后自动补上'\0’,//把str里的len个数据拷贝到_str + pos里memcpy(_str + pos, str, len);//插入字符串_size += len;
}

 先计算要插入的字符长度,将值赋给临时变量len,判断是否需要扩容,然后定义一个end指向_size + len 的位置。通过循环来控制将pos后的len个数据向后挪动len个位置,然后使用memcpy函数把str里的len个数据拷贝到_str + pos里


6. erase 

//从pos位置开始删除长度为的len的字符串
//_str:指向字符串存放的空间的指针
//_size:当前存储的有效数据个数, 指向最后一个字符的下一个位置
void string::erase(size_t pos, size_t len)
{//判断是否会越界assert(pos < _size);//如果要删除的字符  大于等于  当前存储的有效数据个数 减去 当前指向数据的位置if (len >= _size - pos){//直接将pos位置置为\0_str[pos] = '\0';_size = pos;}//如果要删除的字符  小于  当前存储的有效数据个数减去当前指向数据的位置else{//将_str + pos + len(要保留的数据)位置的字符串直接拷贝到_str + pos(要删除的数据)位置直接覆盖掉strcpy(_str + pos, _str + pos + len);_size -= len;}
}

 先进行判断len是否大于等于pos后面的元素个数,如果大于等于的话,就将pos位及其之后的元素全部删除,直接将pos位置置为'\0',然后将有效数据个数置为pos

如果len小于pos后面的元素个数的话,就将_str + pos + len位置的字符串直接拷贝到_str + pos位置,直接将要删除的那len个元素覆盖,最后有效数据更新即可


7. find

7.1 查找字符

//查找字符//查找对应的字符,返回对应的下标size_t string::find(char ch, size_t pos){assert(pos < _size);for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;}


7.2 查找字符串

strstr:在一个字符串中查找另一个字符串 ,返回子串在原串里第一个出现的位置
str1:原串       str2:子串
const char * strstr ( const char * str1, const char * str2 );


size_t string::find(const char* str, size_t pos)
{assert(pos < _size);//通过strstr函数来将str作为子串找到它的地址const char* ptr = strstr(_str + pos, str);//匹配失败if (ptr == nullptr){return npos;}else//匹配成功{//ptr的地址减去_str的地址就是我们要找的字符串的起始位置的下标return ptr - _str;}
}

通过strstr函数来将str作为子串找到它的地址,然后sub的地址减去_str的地址就是我们要找的字符串的起始位置的下标


8. substr 

//从pos位置开始截取长度为len的字符再构造一个子串,子串返回
string string::substr(size_t pos, size_t len)
{assert(pos < _size);// 如果len大于剩余字符长度if (len > _size - pos){//把len更新成为一个有效的长度,有多少长度取多少空间len = _size - pos;}//构造一个子串substring sub;//先给sub预留len个空间sub.reserve(len);//for循环遍历要截取的字符for (size_t i = 0; i < len; i++){//将从pos位置开始长度为len的字符尾插到sub中sub += _str[pos + i];}return sub;
}

先判断len是否大于剩余字符的长度,如果len大于剩余字符的长度就把len更新成为一个有效的长度,有多少长度取多少空间,然后再构造一个子串sub,先给sub预留len个空间,然后通过遍历将从pos位置开始长度为len的字符尾插到sub中最后直接返回sub 


 代码汇总

string.h

 

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once#include<iostream>
#include<string>
#include<assert.h>
using namespace std;namespace bit
{class string{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():_str(new char[1]{'\0'}),_size(0),_capacity(0){}*/// 短小频繁调用的函数,可以直接定义到类里面,默认是inlinestring(const char* str = ""){_size = strlen(str);// _capacity不包含\0_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);}// 深拷贝问题// // s2(s1)string(const string& s){_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}// s2 = s1// s1 = s1string& operator=(const string& s){if (this != &s){delete[] _str;_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}return *this;}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}const char* c_str() const{return _str;}//将字符串的内容清空void clear(){_str[0] = '\0';_size = 0;}size_t size() const{return _size;}size_t capacity() const{return _capacity;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}void reserve(size_t n);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);size_t find(char ch, size_t pos = 0);size_t find(const char* str, size_t pos = 0);string substr(size_t pos = 0, size_t len = npos);private: //设置私有,不允许随便访问底层数据char* _str;//字符串存储空间首地址指针size_t _size; //当前存储的有效数据个数size_t _capacity;//可用容量//static const size_t npos = -1;static const size_t npos;/*_str:指向字符串存放的空间的指针_size:当前存储的有效数据个数 ,指向最后一个字符的下一个位置_capacity:代表当前可存储的最大容量nops:此值设置为 -1,无符号整型转换就是42亿,且此值为const和静态参数具有全局效应,某些函数设置其为缺省参数*/};bool operator<(const string& s1, const string& s2);bool operator<=(const string& s1, const string& s2);bool operator>(const string& s1, const string& s2);bool operator>=(const string& s1, const string& s2);bool operator==(const string& s1, const string& s2);bool operator!=(const string& s1, const string& s2);ostream& operator<<(ostream& out, const string& s);istream& operator>>(istream& in, string& s);
}


string.cpp

 

#include"string.h"//char* _str;字符串存储空间首地址指针
//size_t _size; 当前存储的有效数据个数
//size_t _capacity;可用容量
//static const size_t npos = -1;
/*_str:指向字符串存放的空间的指针
_size:当前存储的有效数据个数 ,指向最后一个字符的下一个位置
_capacity:代表当前可存储的最大容量
nops:此值设置为 -1,无符号整型转换就是42亿,且此值为const和静态参数具有全局效应,某些函数设置其为缺省参数*/namespace bit
{const size_t string::npos = -1;//扩容void string::reserve(size_t n){//如果字符串存储的数据大于容量if (n > _capacity){//cout << "reserve:" << n << endl;//申请新空间赋给临时变量tmp  + 1是位\0申请的空间char* tmp = new char[n + 1];//把数据拷贝到临时变量里strcpy(tmp, _str);//释放旧空间delete[] _str;//指向新空间_str = tmp;//字符串存储的数据等于容量_capacity = n;}}//尾插void string::push_back(char ch){//如果当前的有效数据个数和最大容量相等 if (_size == _capacity){//那么扩容reserve(_capacity == 0 ? 4 : _capacity * 2);}//因为size指向最后一个字符的下一个位置,所以直接插入_str[_size] = ch;++_size;//最后要加一个\0,不然会乱码_str[_size] = '\0';}//尾插一个字符串//_str:指向字符串存放的空间的指针//_size:当前存储的有效数据个数, 指向最后一个字符的下一个位置void string::append(const char* str){//先计算要插入的字符长度,将值赋给临时变量lensize_t len = strlen(str);//如果当前存储的有效数据个数 加上 要插入的字符 大于 容量if (_size + len > _capacity){// 大于2倍,需要多少开多少,小于2倍按2倍扩reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);}//将目标地址到\0的位置中间的值拷贝过去strcpy(_str + _size, str);//尾插_size += len;}//尾插一个字符string& string:: operator+=(char ch){push_back(ch);return *this;}//尾插一个字符串string& string::operator+=(const char* str){append(str);return *this;}//按pos位插入一个字符void string::insert(size_t pos, char ch){assert(pos <= _size);//如果当前的有效数据个数和最大容量相等 if (_size == _capacity){//那么扩容reserve(_capacity == 0 ? 4 : _capacity * 2);}// 挪动数据//_size + 1:\0的后一位//把end指向\0的后一位size_t end = _size + 1;//循环将pos之后的数据都向后挪动一位while (end > pos){_str[end] = _str[end - 1];--end;}//将字符ch放入pos位_str[pos] = ch;++_size;}//按pos位插入一个字符串void string::insert(size_t pos, const char* str){assert(pos <= _size);//先计算要插入的字符长度,将值赋给临时变量lensize_t len = strlen(str);//如果当前存储的有效数据个数 加上 要插入的字符 大于 容量if (_size + len > _capacity){// 大于2倍,需要多少开多少,小于2倍按2倍扩reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);}//定义一个end指向_size + len的位置上size_t end = _size + len;//通过循环把pos后的len个数据向后挪动len个位置while (end > pos + len - 1){_str[end] = _str[end - len];--end;}// 这里不能用 strcpy 会把 '\0' 拷贝过来//使用memcpy函数进行拷贝的原因是memcpy函数不会自动补上'\0'// 而strcpy函数会在拷贝后自动补上'\0’,//把str里的len个数据拷贝到_str + pos里memcpy(_str + pos, str, len);//插入字符串_size += len;}//从pos位置开始删除长度为的len的字符串//_str:指向字符串存放的空间的指针//_size:当前存储的有效数据个数, 指向最后一个字符的下一个位置void string::erase(size_t pos, size_t len){//判断是否会越界assert(pos < _size);//如果要删除的字符  大于等于  当前存储的有效数据个数 减去 当前指向数据的位置if (len >= _size - pos){//直接将pos位置置为\0_str[pos] = '\0';_size = pos;}//如果要删除的字符  小于  当前存储的有效数据个数减去当前指向数据的位置else{//将_str + pos + len(要保留的数据)位置的字符串直接拷贝到_str + pos(要删除的数据)位置直接覆盖掉strcpy(_str + pos, _str + pos + len);_size -= len;}}//查找字符//查找对应的字符,返回对应的下标size_t string::find(char ch, size_t pos){assert(pos < _size);for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;}//查找字符串//strstr:在一个字符串中查找另一个字符串 // 返回子串在原串里第一个出现的位置//str1:原串       str2:子串//const char * strstr ( const char * str1, const char * str2 );size_t string::find(const char* str, size_t pos){assert(pos < _size);//通过strstr函数来将str作为子串找到它的地址const char* ptr = strstr(_str + pos, str);//匹配失败if (ptr == nullptr){return npos;}else//匹配成功{//ptr的地址减去_str的地址就是我们要找的字符串的起始位置的下标return ptr - _str;}}//从pos位置开始截取长度为len的字符再构造一个子串,子串返回string string::substr(size_t pos, size_t len){assert(pos < _size);// 如果len大于剩余字符长度if (len > _size - pos){//把len更新成为一个有效的长度,有多少长度取多少空间len = _size - pos;}//构造一个子串substring sub;//先给sub预留len个空间sub.reserve(len);//for循环遍历要截取的字符for (size_t i = 0; i < len; i++){//将从pos位置开始长度为len的字符尾插到sub中sub += _str[pos + i];}return sub;}//string比较大小按照ascii码比//strcmp:如果第一个数大于/小于/等于第二个数,那么返回>0/<0/0//小于bool operator<(const string& s1, const string& s2){return strcmp(s1.c_str(), s2.c_str()) < 0;}//小于等于bool operator<=(const string& s1, const string& s2){return s1 < s2 || s1 == s2;}//大于bool operator>(const string& s1, const string& s2){return !(s1 <= s2);}//大于等于bool operator>=(const string& s1, const string& s2){return !(s1 < s2);}//等于bool operator==(const string& s1, const string& s2){return strcmp(s1.c_str(), s2.c_str()) == 0;}//不等于bool operator!=(const string& s1, const string& s2){return !(s1 == s2);}//流插入
ostream& operator<<(ostream& out, const string& s)
{for (auto ch : s){out << ch;}return out;
}//流提取
istream& operator>>(istream& in, string& s)
{s.clear();const int N = 256;char buff[N];int i = 0;char ch;//in >> ch;ch = in.get();while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == N - 1){buff[i] = '\0';s += buff;i = 0;}//in >> ch;ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;
}


感谢观看~

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

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

相关文章

C++第五十一弹---IO流实战:高效文件读写与格式化输出

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 目录 1. C语言的输入与输出 2. 流是什么 3. CIO流 3.1 C标准IO流 3.2 C文件IO流 3.2.1 以写方式打开文件 3.2.1 以读方式打开文件 4 stringstre…

flask框架

Flask 1 flask简介 我们之所以在浏览器中输入localhost:8080然后就可以把webapps下面的项目文件以浏览器的方式打开&#xff0c;功臣在与tomcat。python语言写的项目&#xff0c;转换为web&#xff0c;Flask框架 轻量级web应用框架。 环境准备&#xff1a; pip install fl…

【C语言】内存函数详细讲解

文章目录 前言strerror的声明和使用字符串分类函数字符转换函数内存拷贝函数&#xff08;memcpy)memcpy的声明和使用memcpy函数的模拟实现 内存拷贝函数&#xff08;memmove&#xff09;memmove的声明和使用memmove模拟实现 内存比较函数&#xff08;memcmp&#xff09;memcmp的…

UDP_SOCKET编程实现

文章目录 socket编程接口认识struct sockaddr类 编写一个server服务Client代码查看启动结果代码修正1.获取内核分配给客户端的信息2.修正不匹配ip不能访问的问题 不同机器之间的通信利用xftp将udp_client传给wsl的ubuntu机器进行演示现在模拟在windows下的udp_client代码: 对方…

时序预测 | MATLAB实现BKA-XGBoost(黑翅鸢优化算法优化极限梯度提升树)时间序列预测

时序预测 | MATLAB实现BKA-XGBoost(黑翅鸢优化算法优化极限梯度提升树)时间序列预测 目录 时序预测 | MATLAB实现BKA-XGBoost(黑翅鸢优化算法优化极限梯度提升树)时间序列预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 Matlab实现BKA-XGBoost时间序列预测&a…

C++从入门到起飞之——继承下篇(万字详解) 全方位剖析!

&#x1f308;个人主页&#xff1a;秋风起&#xff0c;再归来~&#x1f525;系列专栏&#xff1a;C从入门到起飞 &#x1f516;克心守己&#xff0c;律己则安 目录 1、派⽣类的默认成员函数 1.1 四个常⻅默认成员函数 1.2 实现⼀个不能被继承的类 ​编辑 2. 继承与友…

力扣题解2390

大家好&#xff0c;欢迎来到无限大的频道。 今日继续给大家带来力扣题解。 题目描述​&#xff08;中等&#xff09;&#xff1a; 从字符串中移除星号 给你一个包含若干星号 * 的字符串 s 。 在一步操作中&#xff0c;你可以&#xff1a; 选中 s 中的一个星号。 移除星号…

力扣刷题(6)

两数之和 II - 输入有序数组 两数之和 II - 输入有序数组-力扣 思路&#xff1a; 因为该数组是非递减顺序排列&#xff0c;因此可以设两个左右下标当左右下标的数相加大于target时&#xff0c;则表示右下标的数字过大&#xff0c;因此将右下标 - -当左右下标的数相加小于targ…

HashMap线程不安全|Hashtable|ConcurrentHashMap

文章目录 常见集合线程安全性HashMap为什么线程不安全&#xff1f;怎么保证HashMap线程安全 HashtableConcurrentHashMap 引入细粒度锁代码中分析总结 小结 常见集合线程安全性 ArrayList、LinkedList、TreeSet、HashSet、HashMap、TreeMap等都是线程不安全的。 HashTable是线…

C语言:数组

1. 数组的概念 数组是⼀组相同类型元素的集合&#xff1b;从这个概念中我们就可以发现2个有价值的信息&#xff1a; • 数组中存放的是1个或者多个数据&#xff0c;但是数组元素个数不能为0。 • 数组中存放的多个数据&#xff0c;类型是相同的。 数组分为⼀维数组和多维数组…

【C语言必学知识点七】你知道在动态内存管理中存在的内存泄露问题吗?遇到内存泄露时应该如何处理?今天跟你好好介绍一下如何正确使用calloc与realloc!!!

动态内存管理——动态函数&#xff08;calloc、realloc&#xff09;的使用 导读一、calloc函数1.1 函数介绍1.2 calloc的使用1.3 calloc与malloc 二、realloc函数2.1 函数介绍2.2 realloc的使用2.3 realloc的空间分配2.3.1 空间分配成功——地址的改变2.3.2 空间分配失败——内…

【在Linux世界中追寻伟大的One Piece】数据链路层

目录 1 -> 数据链路层 2 -> 对比理解“数据链路层”和“网络层” 3 -> 以太网 3.1 -> 以太网的帧格式 4 -> 认识MAC地址 4.1 -> 对比理解MAC地址和IP地址 5 -> 认识MTU 5.1 -> MTU对IP协议的影响 5.2 -> MTU对UDP协议的影响 5.3 -> MT…

UE5源码Windows编译、运行

官方文档 Welcome To Unreal Engine 5 Early Access Learn what to expect from the UE5 Early Access program. 链接如下&#xff1a;https://docs.unrealengine.com/5.0/en-US/Welcome/#gettingue5earlyaccessfromgithub Step 0&#xff1a;找到UE5源码 直接先上链接 https…

【Qt】Qml界面中嵌入C++ Widget窗口

1. 目的 qml做出的界面漂亮&#xff0c;但是执行效率低&#xff0c;一直想找一个方法实现qml中嵌入c界面。现在从网上找到一个方法&#xff0c;简单试了一下貌似可行&#xff0c;分享一下。 2. 显示效果 3. 代码 3.1 工程结构 3.2 pro文件 需要添加widgets > QT quick …

店群合一模式下的社区团购新发展——结合链动 2+1 模式、AI 智能名片与 S2B2C 商城小程序源码

摘要&#xff1a;本文探讨了店群合一的社区团购平台在当今商业环境中的重要性和优势。通过分析店群合一模式如何将互联网社群与线下终端紧密结合&#xff0c;阐述了链动 21 模式、AI 智能名片和 S2B2C 商城小程序源码在这一模式中的应用价值。这些创新元素的结合为社区团购带来…

设计模式重新整理

系统整理 河北王校长的 贯穿设计模式 和 王争的设计模式之美&#xff0c;希望能形成肌肉记忆 文章目录 为什么需要掌握设计模式1. 六大原则介绍1. 单一职责原则2. 开闭原则3. 里式替换原则4. 依赖倒置原则5. 接口隔离原则6. 迪米特法则 分类 单例模式适配器模式封装有缺陷的接口…

【可测试性实践】C++ 单元测试代码覆盖率统计入门

引言 最近在调研C工程怎么做单元测试和代码覆盖率统计&#xff0c;由于我们工程有使用Boost库&#xff0c;尝试使用Boost.Test来实现单元测试并通过Gcov和Lcov来生成代码覆盖率报告。本文记录完整的搭建测试Demo&#xff0c;希望能带来一定参考。 常用C单测框架对比 特性Goo…

嵌入式通信原理—SPI总线通信原理与应用

文章目录 SPI 简介基本原理工作模式特点 SPI寻址方式1. 片选&#xff08;Chip Select, CS&#xff09;2. 多从设备通信3. 菊花链&#xff08;Daisy-Chain&#xff09;模式4. 地址寄存器&#xff08;应用层&#xff09; SPI通信过程时钟信号生成&#xff08;SCLK&#xff09;数据…

9.15javaweb项目总结

1.贴吧界面算是完成了基本的 能通过url打开多个贴吧信息的界面了&#xff0c;界面水平不是很高&#xff0c;界面还有待提升&#xff0c;然后该界面的功能点还差点有点远&#xff0c;完成度不是很高。 2.解决了关注的功能问题 要考虑的地方有点多&#xff0c;最简单的就是点击…

Axure RP 9最新安装程序及汉化包下载(支持Win、Mac版,附下载安装教程)

数月前Axure RP官方已经发布了Axure RP 9的消息&#xff0c;并计划在今年夏天发布beta版本。新版Axure RP 9将是该工具向前迈出的重要一步&#xff0c;其中包括一系列广泛的改进&#xff1a;全面的UI修改&#xff0c;新的设计和文档功能以及前所未有的内部优化。我们已经彻底重…