C++:string类(auto+范围for,typeid)

news2024/11/15 18:15:23

目录

前言

auto

typeid

范围for

使用方法

string类的模拟实现

默认构造函数

拷贝构造函数

swap

赋值重载

析构函数

迭代器iterator

begin和end

c_str

clear

size

capacity

[]运算符重载

push_back

reserve

append

+=运算符重载

insert

erase

find

npos

substr

比较运算符重载

<<运算符重载

>>运算符重载

完整代码

string.h

string.cpp


前言

string是一个字符串类

string文档:

string - C++ Reference

string类和数据结构中的顺序表非常相似,只要我们懂了顺序表那么string类也是手拿把掐了

C数据结构:顺序表-CSDN博客

为什么要有string类?和我们平时使用的 "内容"有什么区别?

string类有非常多的接口,比如size,substr等等,这些都可以直接的帮助我们完成所需对字符串的改变

在学习STL模板之前,我们还需要了解两个前置知识

auto

auto声明的变量必须由编译器在编译时期推导而得

也就是说编译器会帮我们自动推导类型,不需要我们自己写,具体示例看下面与typeid的结合使用

typeid

格式为typeid(变量).name()

typeid的作用是打印出变量的类型

int main()
{
	int a = 10;
	auto b = a;
	auto c = 'a';
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	return 0;
}

 

使用auto的注意事项

1. auto声明指针类型时,使用auto和auto*没有任何区别,但是auto声明引用类型时必须加&

2. 当同一行声明多个变量时,auto会根据第一个变量进行推导类型,所以后面的变量也必须要和第一个变量类型相同

3. auto不能作为函数的参数(自己定义编译器推导不出来),但是可以做为返回值(根据返回值推导)

4. auto不能直接用来声明数组

范围for

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会因为粗心犯错误

因此C++11中引入了基于范围的for循环,可以把它叫做范围for

范围for的作用是在数组和容器对象上进行遍历

使用方法

for循环后的括号由冒号“:”分为两个部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束

int main()
{
	int arr[] = { 0,1,2,3,4 };
	for (auto x : arr)
	{
		cout << x << " ";
	}
	cout << endl;
	return 0;
}

 

它不仅仅能在这个int数组里使用,它还能在其他各种容器使用(STL),因为这个机制所以我们不需要了解任何容器的底层即可对一个容器进行遍历和取值

先来看看string类的遍历

int main()
{
	std::string str = "123456";
	for (auto c : str)
	{
		cout << c;
	}
	cout << endl;

	return 0;
}

其实它的底层是这样的

int main()
{
	std::string str = "123456";
	std::string::iterator it;
	for (it = str.begin(); it != str.end(); it++)
	{
		cout << *it;
	}
	cout << endl;

	return 0;
}

 iterator是一个迭代器,在下面的模拟实现里可以把它看成就是一个char*的指针

每一个容器都有一个iterator迭代器,但它们的实现方式都不一样,不能把它看成简单的指针

所以在我们模拟实现的时候,如果想要让我们自己实现的string类支持范围for,首先我们需要先把begin,end,!=重载即可

具体实现看下面的模拟实现

string类的模拟实现

首先我们需要对它使用命名空间进行封装,与std中的string错开

其次我们需要三个类成员

namespace lyw
{
	class string
	{
	public:
        // ...
	private:
		char* _str = nullptr;
		int _size = 0;
		int _capacity = 0;
	};
}

_str用来存储字符串

_size表示字符串大小(不包括'\0')

_capacity表示容量(不包括'\0') 

这里的实现并不会把所有都实现,但会把一些相对重要的模拟实习了

默认构造函数

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

使用strcpy直接拷贝到新空间即可 

拷贝构造函数

string(const string& s)
{
	_str = new char[s._size];
	strcpy(_str, s._str);
	_size = s._size;
	_capacity = s._capacity;
}

和默认构造逻辑相同

还有另一种现代写法

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

直接利用拷贝构造函数构造一个和s一样的tmp,然后让我们的*this和tmp交换,这样最后不仅编译器会通过析构函数自动释放tmp,并且还达到了我们的目的

但是swap函数还是需要我们自己写的,不能用库里的

swap

void swap(string& s)
{
	std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}

直接利用库里的swap将内部成员交换即可

赋值重载

这个和拷贝构造函数的思路一致

string& operator=(string s)
{
	if (this != &s)
	{
		delete[] _str;
		_str = new char[s._size];
		strcpy(_str, s._str);
		_size = s._size;
		_capacity = s._capacity;
	}
	
	return *this;
}

 它也有现代写法

string& operator=(string s)
{
	if (this != &s)
		swap(s);
	return *this;
}

析构函数

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

析构函数只需要释放空间即可

迭代器iterator

迭代器分为普通迭代器和const迭代器,当然还有reverse迭代器

iterator
const_iterator
reverse_iterator
const_reverse_iterator

 这里着重实现iterator和const_iterator

typedef char* iterator;
typedef const char* const_iterator;

这里的iterator我们可以把它看成一个char*的指针,把这个指针当成迭代器使用

begin和end

iterator begin()
{
	return _str;
}

iterator end()
{
	return _str + _size;
}

const_iterator begin() const
{
	return _str;
}

const_iterator end() const
{
	return _str + _size;
}

 直接可以基于迭代器实现iterator版本的begin、end和const_iterator版本的begin、end

c_str

由于类内部成员变量是设置为了private的,所以如果需要使用类中的_str,可以使用该函数获取_str

const char* c_str() const
{
	return _str;
}

clear

将字符串清空

void clear()
{
	_str[0] = '\0';
	_size = 0;
}

size

size_t size() const
{
	return _size;
}

capacity

size_t capacity() const
{
	return _capacity;
}

[]运算符重载

char& operator[](size_t pos) const
{
	return _str[pos];
}

push_back

	void string::push_back(char ch)
	{
		_str[_size] = ch;
		_size++;
		_str[_size] = '\0';
	}

 

这里需要加上命名空间的原因是因为这个定义的地方和声明的地方不一样,类和函数声明是在.h头文件中,而实现是在.cpp文件中,所以需要加上域作用符

这个很简单,只需要直接在后面插入一个元素即可 

但好像是不是少了些什么?我们的空间从哪来?

这时候我们就需要扩容,因为刚开始的capacity是0,这时候我们可以选择写一个扩容逻辑,但我们可以直接写reserve函数(扩容逻辑),这样可以直接在push_back中使用

void string::push_back(char ch)
{
	if (_size == _capacity)
		reserve(_capacity == 0 ? 4 : 2 * _capacity);

	_str[_size] = ch;
	_size++;
	_str[_size] = '\0';
}

reserve

void string::reserve(size_t n)
{
	if (n > _capacity)		
    {
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;
		_capacity = n;
	}
}

n表示需要扩容的空间个数

当扩容的n大于capacity时,则会扩容,反之,由编译器自主决定(缩容或者不变)

这里每一种策略都会有对应的好处

例如缩容会比较节省空间,但是缩容的时间消耗会相对较多

若不变,则牺牲了空间,但不会消耗时间

所以两种策略各有优缺点

append

它的作用是在最后加上一段字符串

void string::append(const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
		reserve(_size + len > _capacity * 2 ? _size + len : _capacity * 2);

	strcpy(_str + _size, str);
	_size += len;
}

 只需要在_size的位置复用strcpy即可,不要忘记_size加上str本身的长度

+=运算符重载

+=有两个版本,一个是+=一个字符,一个是+=一个字符串

string& string::operator+=(char ch)
{
	push_back(ch);
	return *this;
}
string& string::operator+=(const char* str)
{
	append(str);
	return *this;
}

它们的功能和push_back,append一样,所以我们只需要复用即可 

insert

void string::insert(size_t pos, char ch)
	if (_size == _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位置空出来即可,把pos之后的数据挪动到后面,插入ch即可

要注意的是'\0'也要挪动

void string::insert(size_t pos, const char* str)
{
	int len = strlen(str);
	if (_size + len > _capacity)
		reserve(_size + len > _capacity * 2 ? _size + len : _capacity * 2);

	size_t end = _size + len;
	while (end > pos + len - 1)
	{
		_str[end] = _str[end - len];
		--end;
	}

	for (int i = 0; i < len; i++)
		_str[i + pos] = str[i];
	
	_size += len;
}

这是插入一个字符串的版本

和上面一样,但需要计算好需要挪动的数据个数和位置,唯一的区别就是最后需要多使用一个for循环来插入数据

要注意的是'\0'也要挪动

erase

void string::erase(size_t pos, size_t len)
{
	if (len >= _size - pos)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		for (int i = pos; i <= _size; i++)
			_str[i] = _str[i + len];

		_size -= len;
	}
}

 和顺序表思路一样,挪动数据覆盖掉要删除的节点即可,若需要删除长度超出了字符串的长度,则全部删除即可

find

作用是从pos位置开始在字符串中寻找ch字符

size_t string::find(char ch, size_t pos)
{
	for (size_t i = pos; i < _size; i++)
	{
		if (_str[i] == ch)
			return i;
	}

	return npos;
}

 若找到则返回该位置的下标,失败则返回npos

这里出现了一个npos,npos是什么?

npos

我们可以看到它的值是-1,但它的类型确是size_t无符号整型,并且由于它在二进制中的数字为   全1,所以它是一个无穷大

下面我们需要定义它

static const size_t npos;

 把它定义为了静态成员变量,那么为了定义它我们需要在类外面定义

const size_t string::npos = -1;

substr

该函数的作用是截取字符串中的子串,从pos位置开始,len长度的字符串

string string::substr(size_t pos, size_t len)
{
	if (len > _size - pos)
		len = _size - pos;

	string sub;
	sub.reserve(len);
	for (int i = 0; i < len; i++)
	{
		sub += _str[i + pos];
	}
	return sub;
}

这里定义了一个sub当作子串,并将pos位置开始后面的len个字符插入到sub中即可

比较运算符重载

bool string::operator<(const string& s)
{
	return strcmp(_str, s._str) < 0;
}

bool string::operator<=(const string& s)
{
	return (*this < s || *this == s);
}

bool string::operator>(const string& s)
{
	return !(*this <= s);
}

bool string::operator>=(const string& s)
{
	return !(*this < s);
}

bool string::operator==(const string& s)
{
	return strcmp(_str, s._str) == 0;
}

bool string::operator!=(const string& s)
{
	return !(*this == s);
}

在比较运算符重载这个家族中,我们只需要实现其中的两个函数就能够复用这两个函数将其他函数全部实现

 上面的示例就是手动实现了<和==,剩下的只需复用即可

复用看似是一种偷懒行为,实则不然

我们当我们的比较逻辑改变时(例如不再根据strcmp比较,而是根据字符串的长度比较)

若我们没有复用,那么我们需要将这全部函数都重新实现

但是我们如果复用了,我们只需要修改我们手动实现的那两个函数即可 

<<运算符重载

ostream& operator<<(ostream& out, const string& s)
{
	for (auto x : s)
	{
		out << x;
	}
	
	return out;
}

<<运算符重载我们是在类外面实现的,它在类内实现不出库中的效果

如果我们在类中实现那么函数参数的第一个位置一定是this指针,那么我们在使用时就必须要把string变量放在第一个才能使用

str << cout 这种写法不是很别扭吗? 

所以我们可以把它放在类外面实现,函数中的参数的位置就可以由我们自己决定了

这里的两个&是必须要有的,否则会报错

<<是输出函数,所以我们要用的是iostream中的ostream类(out)

这里只需要把out当成cout使用即可

>>运算符重载

istream& operator>>(istream& in, string& s)
{
	s.clear();

	const int N = 128;
	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;
		}
		ch = in.get();
	}
	if (i != 0)
	{
		buff[i] = '\0';
		s += buff;
	}
	return in;
}

和上面的<<一样,都是要放在类外面实现

否则会出现 str >> cin这种尴尬的写法

这里的细节就相对多一些

还是要有两个&

>>是输入函数,所以我们要用的是iostream中的istream类(in)

首先要确保s里面没有字符,所以先使用clear清空字符串

然后定义了一个buff作为缓冲区,先暂时将输入的字符

这里不能使用in >> ch,因为这样若是我们的字符串当中要有空格的时候,in会提取不到ch中,所以我们要用in中的get方法,它和in的本质区别就是它可以吸收空格,只有回车的时候才会停止

当缓冲区满时我们就可以将缓冲区的内容全部插入到s中,并重新将buff缓冲区置空

最后buff缓冲区很有可能没满但是有剩余数据在buff缓冲区中,所以循环结束后我们还要将buff中的内容全部插入到s中

这样就完成了

完整代码

string.h

#pragma once

#include<iostream>
using namespace std;

namespace lyw
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
			
		string(const char* str = "")
		{
			int len = strlen(str);
			_str = new char[len+1];
			strcpy(_str, str);
			_size = len;
			_capacity = _size;
		}

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

		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

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

		//string& operator=(string s)
		//{
		//	if (this != &s)
		//	{
		//		delete[] _str;
		//		_str = new char[s._size];
		//		strcpy(_str, s._str);
		//		_size = s._size;
		//		_capacity = s._capacity;
		//	}
		//	
		//	return *this;
		//}

		string& operator=(string s)
		{
			if (this != &s)
				swap(s);
			return *this;
		}

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

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		const_iterator begin() const
		{
			return _str;
		}

		const_iterator end() const
		{
			return _str + _size;
		}

		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) const
		{
			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);

		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 = nullptr;
		int _size = 0;
		int _capacity = 0;
		static const size_t npos;
	};

	ostream& operator<<(ostream& out, const string& s);
	istream& operator>>(istream& in, string& s);
}

string.cpp

#include"string.h"

namespace lyw
{
	const size_t string::npos = -1;

	void string::reserve(size_t n)
	{
		if (n > _capacity)
		{
			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 : 2 * _capacity);

		_str[_size] = ch;
		_size++;
		_str[_size] = '\0';
	}

	void string::append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
			reserve(_size + len > _capacity * 2 ? _size + len : _capacity * 2);

		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;
	}

	void string::insert(size_t pos, char ch) // 注意'\0'也要挪动
	{
		if (_size == _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++;
	}

	void string::insert(size_t pos, const char* str)
	{
		int len = strlen(str);
		if (_size + len > _capacity)
			reserve(_size + len > _capacity * 2 ? _size + len : _capacity * 2);

		size_t end = _size + len;
		while (end > pos + len - 1)
		{
			_str[end] = _str[end - len];
			--end;
		}

		for (int i = 0; i < len; i++)
			_str[i + pos] = str[i];
		
		_size += len;
	}

	void string::erase(size_t pos, size_t len)
	{
		if (len >= _size - pos)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			for (int i = pos; i <= _size; i++)
				_str[i] = _str[i + len];

			_size -= len;
		}
	}

	size_t string::find(char ch, size_t pos)
	{
		for (size_t i = pos; i < _size; i++)
		{
			if (_str[i] == ch)
				return i;
		}

		return npos;
	}

	size_t string::find(const char* str, size_t pos)
	{
		const char* tmp = strstr(_str + pos, str);
		if (tmp == nullptr)
			return npos;
		else
			return tmp - _str;
	}

	string string::substr(size_t pos, size_t len)
	{
		if (len > _size - pos)
			len = _size - pos;

		string sub;
		sub.reserve(len);
		for (int i = 0; i < len; i++)
		{
			sub += _str[i + pos];
		}
		return sub;
	}

	bool string::operator<(const string& s)
	{
		return strcmp(_str, s._str) < 0;
	}

	bool string::operator<=(const string& s)
	{
		return (*this < s || *this == s);
	}

	bool string::operator>(const string& s)
	{
		return !(*this <= s);
	}

	bool string::operator>=(const string& s)
	{
		return !(*this < s);
	}

	bool string::operator==(const string& s)
	{
		return strcmp(_str, s._str) == 0;
	}

	bool string::operator!=(const string& s)
	{
		return !(*this == s);
	}

	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto x : s)
		{
			out << x;
		}
		
		return out;
	}

	istream& operator>>(istream& in, string& s)
	{
		s.clear();

		const int N = 128	;
		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;
			}
			ch = in.get();
		}
		if (i != 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
}

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

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

相关文章

postgresql 宝塔 连接不上,prisma

不太熟悉pgsql; 配置搞了半天; 一直连不上远程数据库; 后台经过探索发现需要以下配置 1. 端口放行; 5422 (pgsql的端口) 2.编辑 pg_hba.conf 文件最后新增一条,这样可以外部使用postgres超级管理员账号 host all all 0.0.0.0/0 md5 3. pris…

数据结构复杂度

文章目录 一. 数据结构前言1.1 数据结构1.2 算法 二. 算法效率2.1 时间复杂度2.1.1 T(N)函数式2.1.2 大O的渐进表示法 一. 数据结构前言 1.1 数据结构 什么是数据结构呢&#xff1f;打开一个人的主页&#xff0c;有很多视频&#xff0c;这是数据&#xff08;杂乱无章&#xf…

了解k8s架构,搭建k8s集群

kubernetes 概述 Kubernetes 集群图例 安装控制节点 安装网络插件 安装 calico 安装计算节点 2、node 安装 查看集群状态 kubectl get nodes # 验证容器工作状态 [rootmaster ~]# kubectl -n kube-system get pods

【学习笔记】:Maven初级

一、Maven简介 1、为什么需要maven Maven是一个依赖管理工具,解决如下问题: 项目依赖jar包多jar包来源、版本问题jar包导入问题jar包之间的依赖Maven是一个构建工具: 脱离IDE环境的项目构建操作,需要专门的工具2、Maven介绍 https://maven.apache.org/what-is-maven.htm…

代码随想录算法训练营第44天|LeetCode 1143.最长公共子序列、1035.不相交的线、53. 最大子序和、392.判断子序列

1. LeetCode 1143.最长公共子序列 题目链接&#xff1a;https://leetcode.cn/problems/longest-common-subsequence/description/ 文章链接&#xff1a;https://programmercarl.com/1143.最长公共子序列.html 视频链接&#xff1a;https://www.bilibili.com/video/BV1ye4y1L7CQ…

苹果离线打包机配置和打包

1、虚拟机安装 macOS虚拟机安装全过程&#xff08;VMware&#xff09;-腾讯云开发者社区-腾讯云 给 windows 虚拟机装个 mac 雪之梦 1、安装苹果镜像 去网上下载&#xff0c;打包机的镜像要和自己mac电脑上的保持一致。 同时打包机的用户名也需要和自己的mac保持一致。 2、…

云原生专题-k8s基础系列-k8s-namespaces详解

获取所有的pod实例&#xff1a; k8s中&#xff0c;命名空间&#xff08;Namespace&#xff09;提供一种机制&#xff0c;将同一集群中的资源划分为相互隔离的组。同一命名空间内的资源名称要唯一&#xff0c;命名空间是用来隔离资源的&#xff0c;不隔离网络。 https://kubern…

Kafka 实战使用、单机搭建、集群搭建、Kraft集群搭建

文章目录 实验环境单机服务启动停止服务简单收发消息其他消费模式理解Kakfa的消息传递机制 集群服务为什么要使用集群部署Zookeeper集群部署Kafka集群理解服务端的Topic、Partition和Broker总结 Kraft集群相关概念 实验环境 准备三台虚拟机 三台机器均预装CentOS7 操作系统。…

探索Transformer中的多头注意力机制:如何利用GPU并发

什么是多头注意力机制&#xff1f; 首先&#xff0c;什么是多头注意力机制&#xff1f;简单来说&#xff0c;它是Transformer模型的核心组件之一。它通过并行计算多个注意力头&#xff08;attention heads&#xff09;&#xff0c;使模型能够从不同的表示子空间中捕捉不同的特…

Oracle服务器windows操作系统升级出现计算机名称改变导致数据库无法正常连接

1.数据库莫名奇妙无法正常连接&#xff0c;经排查是主机名称改变&#xff0c;导致oracle无法正常运行 如何查看ORACLE主机名称及路径&#xff1a;需要修改 listener 和 tnsnames的配置的主机名 2.修改tnsnames配置的主机名称&#xff0c;HOST主机名称 3.修改listener中的主机…

【案例36】Apache未指向新的openssl

客户发现apache报openssl相关漏洞&#xff0c;于是升级了操作系统的openssl组件。但再次漏扫发现相关版本依旧显示openssl的版本为&#xff1a;1.0.2k。怀疑升级的有问题。 问题分析 查看libssl.so.10指向的是/lib64.so.10 ldd mod_ssl.so libssl.so.10指向的是openssl1.0.2k…

【实际案例】服务器宕机情况分析及处理建议

了解银河麒麟操作系统更多全新产品&#xff0c;请点击访问麒麟软件产品专区&#xff1a;https://product.kylinos.cn 服务器环境以及配置 物理机/虚拟机/云/容器 物理机 外网/私有网络/无网络 私有网络 处理器&#xff1a; Kunpeng 920 内存&#xff1a; 4 TiB BIOS版…

【JVM基础18】——实践-Java内存泄漏排查思路?

目录 1- 引言&#xff1a;2- ⭐核心&#xff1a;2-1 排查思路2-2 步骤1&#xff1a;获取堆内存快照 dump2-3 步骤2、3&#xff1a;使用 VisualVM 打开 dump文件 3- 小结&#xff1a;3-1 Java内存泄漏排查思路&#xff1f; 1- 引言&#xff1a; 首先得明确哪里会产生内存泄漏的…

Solidworks API利用C# 实现物体的运动与碰撞检测

详情见github连接 SolidWorks-API-Collision-Detection Use SolidWorks API to MovePart and Collision Detection 利用solidworks的API来移动控件物体以及进行碰撞检测 visual studio 2022 利用Nuget 安装这些库 打开solidworks 可以看到有两个控件 部件运动 使用封装的函…

嵌入式初学-C语言-十七

#接嵌入式初学-C语言-十六# 函数的递归调用 含义&#xff1a; 在一个函数中直接或者间接调用了函数本身&#xff0c;称之为函数的递归调用 // 直接调用a()->a(); // 间接调用a()->b()->a();a()->b()->..->a();递归调用的本质&#xff1a; 本是是一种循环…

深入理解Spring的三级缓存机制

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

Ubuntu(20.04 LTS)更换镜像源

此换镜像源方法只适用x86_64架构的系统&#xff0c;其他架构的系统参考ubuntu-ports的方法 1、备份文件 sudo mv /etc/apt/sources.list /etc/apt/sources.list.bk2、创建新文件 sudo vi /etc/apt/sources.list根据自己系统版本选择下面对应的镜像源添加到新文件中&#xf…

智能指针--

智能指针简介 头文件&分类 智能指针都在memory中&#xff0c; 有auto_ptr, unique_ptr, shared_ptr, weak_ptr 智能指针发展史 C&#xff0b;&#xff0b;98就有智能指针了&#xff08;auto_ptr&#xff09; c&#xff0b;&#xff0b;11前&#xff0c;智能指针主要靠bo…

FPGA开发——在Quartus中实现对IP核的PLL调用

一、简介 PLL主要由鉴相器&#xff08;PD&#xff09;、环路滤波器&#xff08;LF&#xff09;和压控振荡器&#xff08;VCO&#xff09;三部分组成。鉴相器检测输入信号与VCO输出信号的相位差&#xff0c;并输出一个与相位差成正比的电压信号。该信号经过环路滤波器滤除高频噪…

esp32学习笔记

前言&#xff1a;学习视频链接放在最后&#xff0c;开发方式为esp32Arduino&#xff0c;使用型号为ESP32-WROOM-32&#xff0c;引脚功能分配图如下。 #esp32介绍 GPIO的引脚默认情况下&#xff0c;只能当做普通功能引脚使用&#xff0c;也就是只能输入&#xff0c;输出&#x…