string类的详细模拟实现

string类的模拟实现

文章目录

  • string类的模拟实现
    • 前言
    • 1. 类的框架设计
    • 2. 构造函数与析构函数
    • 3. 拷贝构造与重载赋值运算符函数
    • 4. 运算符重载
    • 5. 成员函数
    • 6. 迭代器的实现
    • 7. 非成员函数
    • 8. 单元测试
    • 总结

前言

​ 在现代编程中,字符串处理是每个程序员都会遇到的基本任务。C++标准库提供了强大的std::string类,但了解其背后的原理对于深入理解语言特性至关重要。本文将介绍如何从零开始实现一个简单的String类,这不仅能够帮助我们更好地理解C++,还能够提升我们解决问题的能力。


1. 类的框架设计

​ 首先需要确定成员变量有哪些,结合“先描述,再组织”的方针,要对String类进行描述,那么就必须先确定成员变量,下一步才有资格谈论功能实现(函数声明与定义)。这里我们需要实现的string本质上是一个可实现自动扩容的字符数组!所以成员变量采用char*声明的字符数组+字符串长度大小+字符数组容量,通过字符数组实现存储,字符串长度和字符数组容量共同作为控制扩容逻辑的参考值。

​ 当然,在确定成员变量后接着就需要定夺功能实现了,首先必须要有的是构造、析构函数,拷贝构造、重载复制运算符函数(这两者拷贝函数有时需要显式delete避免调用),特定的运算符重载,基本功能函数等。直接一次性声明出所有的类内成员变量与函数是不现实的,很难免会出现遗漏功能或逻辑不自洽。所以一般都是一边完善类的声明,一边实现函数定义,下面我便于后续描述直接给出了本文涉及的所有函数功能声明。

(注:仅挑选实现了std::string类中部分高使用率的函数)

class String
{
public:
	// 基础构造函数
	String(const char* str = "");
	// 拷贝构造函数
	String(const String& s);
	// 赋值运算符函数
	String& operator=(const String& s);
	// 析构函数
	~String();


	// 重载运算符
	friend std::ostream& operator<<(std::ostream& out, const String& s);
	friend std::istream& operator>>(std::istream& in, const String& s);
	bool operator==(const String& s);
	char operator[](size_t index)const;
	char& operator[](size_t index);
	String operator+(const String& s)const;
	String operator+(const char c)const;
	String operator+(const char* str)const;
	String& operator+=(const String& s);
	String& operator+=(const char c);
	String& operator+=(const char* str);


	// 成员函数
	size_t size()const;
	size_t capacity()const;
	const char* c_str()const;
	void reserve(size_t n);
	void resize(size_t n, char c = '\0');
	void push_back(const char c);
	void append(const char* str);
	void insert(size_t pos, const char c, size_t n = 1);
	void insert(size_t pos, const char* str);
	void erase(size_t pos, size_t n = npos);
	void swap(String& s);
	size_t find(const String& s, size_t pos = 0)const;
	size_t find(const char* str, size_t pos = 0)const;
	size_t find(const char c, size_t pos = 0, size_t n = 0)const;
	String substr(size_t pos = 0, size_t len = npos)const;


	// (仿)迭代器
	typedef char* iterator;
	typedef const char* const_iterator;
	iterator begin();
	iterator end();
	const_iterator cbegin();
	const_iterator cend();


private:
	char* m_str;
	size_t m_size;
	size_t m_capacity;
private:
	static const int npos;
};

在实现具体函数功能前需要声明:

任何类内非静态函数,其参数列表第一个参数永远是类自身的self指针!(例如这里实现String类,即为:String* self)

​ 这是后面函数实现中有时显式无参却能操作类内对象、仅有一个参数却能实现两对象之间的对比亦或其他操作的理论基础!

2. 构造函数与析构函数

String::String(const char* str):m_size(strlen(str))		// 注意缺省参数在声明和定义处仅出现一次
{
	m_capacity = m_size;
	m_str = new char[m_capacity + 1];	// 多一个字节存放'\0'
	strcpy(m_str, str);
}

String::~String()
{
	delete[] m_str;
	m_str = nullptr;
	m_size = m_capacity = 0;
}

注意这里的构造函数使用了全缺省参数实现,通过这种方法可实现通过一个函数兼任无参构造与单参数构造函数。定义内容上,在String类实例化对象时实现堆区创建指针数组,以便后续对其扩容。

注意:函数参数使用缺省值实现时,缺省值定义的出现在声明和定义中仅能出现一次!

析构函数中需要注意delete的对象是数组,所以需要相应使用delete[] 数组名的方式对数组申请的堆区空间进行释放。

3. 拷贝构造与重载赋值运算符函数

String::String(const String& s)
{
	m_size = s.m_size;
	m_capacity = s.m_capacity;
	m_str = new char[m_capacity + 1];
	strcpy(m_str, s.m_str);
}
String& String::operator=(const String& s)
{
	char* tmp = new char[s.m_capacity];
	strcpy(tmp, s.m_str);

	delete[] this->m_str;
	this->m_str = tmp;
	this->m_size = s.m_size;
	this->m_capacity = s.m_capacity;
	return *this;
}

实现类的拷贝功能类型函数时,特别需要注意深浅拷贝的问题,很好的辨别方法是观察成员变量是否有是在堆区创建的,如果有,一般都需要避免浅拷贝的问题。在复制给新对象时,需要对该成员变量在堆区新开辟空间,接着才能进行堆空间之间内容的值拷贝。

对于拷贝赋值运算符而言,返回值一般以 self& 形式存在,很大一部分原因在于通过这种方法可以实现链式调用,例如:

String s1("haha");
String s2;
String s3;

s3 = s2 = s1;

由于赋值运算符的运算逻辑是自右向左的,所以仅需一条语句就可将s1的值拷贝给s2、s3对象。

4. 运算符重载

  • 流插入运算符和流提取运算符
std::ostream& operator<<(std::ostream& out, const String& s)
{
	out << s.m_str;
	return out;
}

std::istream& operator>>(std::istream& in, String& s)
{
	char ch;
	ch = in.get();
	int i = 0;
	char buff[128]{ 0 };
	while (ch != '\n' && ch != ' ')
	{
		buff[i++] = ch;

		if (i == 127)
		{
			buff[i] = '\0';
			s += buff;
			i = 0;
		}
		ch = in.get();
	}
	if (i != 127)
	{
		buff[i] = '\0';
		s += buff;
	}
	return in;
}

当我们使用流提取(<<)时,由于不会导致String对象的成员变量(字符数组)内数组大小发生改变,并不会发生扩容,所以可以采用简单的直接提取字符数组进行输出(out << s.m_str)

然而使用流插入(>>)时, 很难免传入的新字符串大小超过原字符数组的容量承载极限,有可能导致扩容,所以我们需要对插入的数据进行分批从缓冲区获取,分批插入字符数组,如此一来当我们调用插入之类的函数时就可以一边插入一边扩容,这里采用了 += 运算符实现插入操作,当然也可以使用 insertpush_back之类的函数实现。当然,后面我们会给出这类插入函数的实现。

  • 双等号运算符
bool String::operator==(const String& s)
{
	return strcmp(m_str, s.m_str) == 0;
}

这里采用了C语言库中的strcmp函数,通过对两个String对象的字符数组进行对比判断两对象是否等价。

  • 下标运算符
char String::operator[](size_t index)const
{
	assert(index < m_size);
	return m_str[index];
}
char& String::operator[](size_t index)
{
	assert(index < m_size);
	return m_str[index];
}

提供了重载实现:

  • 如果const String对象调用[]运算符,那么返回的 char 类型不会因其改变影响对象内的字符数组内值的变化。
  • 如果String对象(non_const)调用时,返回指定下标位置的引用,外部对其进行修改时可以引起字符数组内指定位置值同步发生改变
  • 加号运算符
String String::operator+(const String& s)const
{
	String tmp;
	size_t capacity_sum = capacity() + s.capacity();
	tmp.reserve(capacity_sum);	// 开空间
	// 字符串拷贝
	strcpy(tmp.m_str, m_str);
	strcat(tmp.m_str, s.m_str);

	tmp.m_size = size() + s.size();
	return tmp;
}
String String::operator+(const char c)const
{
	String tmp;		// 初始化和定义
	tmp.reserve(capacity() + 1);
	size_t size = this->size();
	// 赋值
	strcpy(tmp.m_str, m_str);
	tmp.m_str[size] = c;
	tmp.m_str[size + 1] = '\0';

	tmp.m_size = size + 1;
	return tmp;
}
String String::operator+(const char* str)const
{
	assert(str);

	String tmp;
	size_t size_1 = size();
	size_t size_2 = strlen(str);
	tmp.reserve(size_1 + size_2 + 1);
	// 赋值
	strcpy(tmp.m_str, c_str());
	strcat(tmp.m_str, str);

	return tmp;
}

可以看到重载了三种加号运算符,分别实现了追加String对象、char字符、char*类型字符串,从内部实现来看,无一不是调用reserve()函数实现扩容,具体的reserve()函数定义将会在后面给出。不过可以确定的是,+运算符也属于插入类型的运算符函数,不可避免的存在扩容的问题,所以模型都属于扩容+赋值

  • 加等运算符
String& String::operator+=(const String& s)
{
	this->append(s.m_str);
	return *this;
}
String& String::operator+=(const char c)
{
	this->push_back(c);
	return *this;
}
String& String::operator+=(const char* str)
{
	this->append(str);
	return *this;
}

由于operator+=的功能和append()函数基本相同,所以采用代码复用的方式实现,后面给出 append() 函数的具体实现。

5. 成员函数

  • sizecapacity
size_t String::size()const
{
	return m_size;
}

size_t String::capacity()const
{
	return m_capacity;
}

出于对数据的保护,使用接口对外提供访问。

  • c_str
const char* String::c_str()const
{
	return m_str;
}

便于外部直接访问字符数组,尤其是外部C语言函数调用时,不接受String类型参数,只能通过 c_str() 函数进行传递,注意返回值类型为 const char* ,可以避免外部对指针的操作引起内部数组变化。

  • reserveresize
void String::reserve(size_t n)		// 设定容量,不影响size值
{
	if (n > m_size)
	{
		char* cmp = new char[n + 1];
		strcpy(cmp, m_str);
		delete[] m_str;

		m_str = cmp;
		m_capacity = n;
	}
}
void String::resize(size_t n, char c)
{
	if (n <= m_size)
	{
		m_str[n] = c;
		m_size = n;
	}
	else
	{
		if (n >= m_capacity)	// 考虑扩容
		{
			reserve(n);
		}

		for (size_t i = m_size; i < n; i++)	// 填充值
		{
			m_str[i] = c;
		}
		m_str[n] = '\0';
		m_size = n;
	}
}

reserve():控制容量

resize(): 控制size大小,可能影响容量(需要扩容)

  • push_backappend
void String::push_back(const char c)
{
	if (m_size == m_capacity)	// 考虑扩容
	{
		m_capacity = (m_capacity == 0) ? (4) : (m_capacity * 2);
		reserve(m_capacity);
	}

	m_str[m_size++] = c;
	m_str[m_size] = '\0';
}

void String::append(const char* str)
{
	assert(str);

	const size_t size = strlen(str);
	if (size >= m_capacity - m_size)	// 考虑扩容
	{
		m_capacity = size + m_size + 1;
		reserve(m_capacity);
	}
	// 拷贝
	strcpy(m_str + this->size(), str);		// 从中间部分开始拷贝
	m_size += size;
}

都属于引起字符数组size增大的函数,存在扩容的可能,所以内部对size与capacity值进行判断,如有需要先扩容再插入。

  • inserterase
void String::insert(size_t pos, const char c, size_t n)
{
	assert(pos <= m_size);

	while(n >= m_capacity - m_size)		// 避免n过大内存分配不够
	{
		m_capacity = (m_capacity == 0) ? (n+1) : (m_capacity * 2);
		reserve(m_capacity);
	}
	
	size_t i = m_size + n;
	for (; i >= n + pos && i != 0; --i)	// i!=0避免溢出 内置if语句拖慢运行时间
	{
		m_str[i] = m_str[i - n];
	}
	if (i == 1 && pos == 0)	// 处理pos=0(或i=0)的情况
	{
		m_str[n] = m_str[n - 1];
	}
	//m_str[m_size + n] = '\0';	// 注意结尾的'\0' (如果上面for循环定义i = m_size + n - 1时)

	for (size_t j = pos; j < pos + n; ++j)
	{
		m_str[j] = c;
	}
	m_size += n;	// 切记更新size值
}
void String::insert(size_t pos, const char* str)
{
	assert(str && pos <= m_size);

	size_t length = strlen(str);
	while (length >= m_capacity - m_size)
	{
		m_capacity = (m_capacity == 0) ? (4) : (m_capacity * 2);
		reserve(m_capacity);
	}

	// 字符串右移
	size_t i = m_size + length;
	for (; i >= length + pos && i != 0; --i)	// i!=0避免溢出 内置if语句拖慢运行时间
	{
		m_str[i] = m_str[i - length];
	}
	if (i == 1 && pos == 0)	// 处理pos=0(或i=0)的情况
	{
		m_str[length] = m_str[length - 1];
	}

	for (size_t j = pos, i = 0; j < pos + length; ++j, ++i)
	{
		m_str[j] = str[i];
	}
	m_size += length;
}

void String::erase(size_t pos, size_t n)
{
	assert(pos < m_size);

	if (n == npos || n >= m_size - pos)
	{
		m_str[pos] = '\0';
		m_size = pos;
	}
	else
	{
		for (size_t i = pos; i <= m_size; ++i)		//i <= pos + n连带复制'\0'
		{
			m_str[i] = m_str[i + n];
		}
		m_size -= n;
	}
}

提供了两个重载的 Insert() 函数,可实现指定位置插入n个char值或在指定位置插入字符串(const char*)。而 erase() 函数也实现了从pos位置向后删除n个字符的功能。两者都采用了缺省值参数,如不指定就插入1个字符,而 erase() 函数的n如果未指定代表极大的值,相当于删除pos值以后的所有字符。

  • swapfind
void String::swap(String& s)
{
	std::swap(m_str, s.m_str);
	std::swap(m_size, s.m_size);
	std::swap(m_capacity, s.m_capacity);
}

size_t String::find(const String& s, size_t pos)const
{
	assert(pos < m_size);

	const char* sub_str = strstr(m_str + pos, s.m_str);
	if (sub_str != nullptr)
	{
		return sub_str - m_str;
	}
	return npos;
}
size_t String::find(const char* str, size_t pos)const
{
	assert(pos < m_size && str != nullptr);

	const char* sub_str = strstr(m_str + pos, str);
	if (sub_str != nullptr)
	{
		return sub_str - m_str;
	}
	return npos;
}
size_t String::find(const char c, size_t pos, size_t n)const
{
	assert(pos < m_size && n <= m_size - pos);

	for (size_t i = pos; i <= m_size - n; ++i)
	{
		if (m_str[i] == c)
		{
			size_t j = 0;
			for (; j < n; ++j, ++i)
			{
				if (m_str[i] != c)
				{
					break;
				}
			}
			if (j == n) { return i - n; }
		}
	}
	return npos;
}

使用最简单朴素的方法实现字符串查找。

  • substr
String String::substr(size_t pos, size_t len)const
{
	assert(pos < m_size);

	String tmp;
	if (len > m_size - pos)		// 取到字符串结尾
	{
		for (size_t i = pos; i <= m_size; ++i)
		{
			tmp.push_back(m_str[i]);
		}
	}
	else
	{
		for (size_t i = pos; i < pos + len; ++i)
		{
			tmp.push_back(m_str[i]);
		}
		tmp.push_back('\0');
	}
	return tmp;
}

实现字符串切割,如果 len 值过大呢?我们将其视为取pos 往后全部字符串的功能要求,所以需要设置一个极大的值赋给 len,我们在类内定义一个静态成员变量(size_t npos = -1),由于 size_t 属于无符号整形,所以 -1 代表的值极大,一般字符串都不会超过这个长度。

6. 迭代器的实现

  • beginend
char* String::begin()
{
	return m_str;
}
char* String::end()
{
	return m_str + m_size;
}

这里需要C语言的基础,指针变量+整数代表指针后移整数个指向的变量位置。

  • cbegincend
const char* String::cbegin()
{
	return m_str;
}
const char* String::cend()
{
	return m_str + m_size;
}

上面提供两种迭代器的原因是外部利用迭代器访问时,如果是 const 对象就调用 “cbegin(), cend()” 函数,反之调用 “begin(), end()” 函数。返回非const迭代器可以实现外部改变类内字符数组的值。

7. 非成员函数

  • getlineswap
istream& getline(istream& in, String& s, char delim = '\n')	// 空格不作为读取终止符
{
	char ch = in.get();
	int i = 0;
	char buff[128]{ 0 };
	while (ch != delim)
	{
		buff[i++] = ch;
		if (i == 127)
		{
			buff[127] = '\0';
			s += buff;
			i = 0;		// 重置buff
		}
		ch = in.get();	// 循环读取
	}
	if (i != 127)
	{
		buff[i] = '\0';
		s += buff;
	}
	return in;
}

void swap(String& s1, String& s2)
{
	s1.swap(s2);
}

8. 单元测试

void test1()
{
	const String s1("xxxxx");
	String s2(s1);
	String s3 = s1 + s2;
	String s4 = s1 + 'w';
	String s5 = s1 + "hahaha";
	cout << s3 << endl;
	cout << s4 << endl;
	cout << s5 << endl;
	// 测试扩容
	String ss1;
	ss1.push_back('s');
	ss1.push_back('s');
	ss1.push_back('s');
	ss1.push_back('s');
	ss1.push_back('s');
	cout << ss1 << endl;
}

运行结果:

在这里插入图片描述

void test2()
{
	String s1;
	s1.append("hahahaha");
	cout << s1 << endl;
	s1.insert(0, 'q', 50);
	cout << s1 << endl;

	String s2 = s1;
	s2.insert(0, "wwwwwwwwww");
	cout << s2 << endl;

	s2.erase(0, 10);
	cout << s2 << endl;
}

运行结果:

在这里插入图片描述

void test3()
{
	String s1("wewewewewe");
	String s2("hhhhh");
	s1 += s2;
	s1 += '6';
	s1 += "vocal";
	cout << s1 << endl;
}

运行结果:

在这里插入图片描述

void test4()
{
	String s1;
	String s2;
	cin >> s1 >> s2;
	cout << s1 << endl;
	cout << s2 << endl;
	String s3;
	getline(cin, s3);
	cout << s3 << endl;
}

运行结果:

在这里插入图片描述

void test5()
{
	String s1("hahaha");
	String s2("jiarenmen");
	s1.swap(s2);
	cout << "s1:" << s1 << '\t' << "s2:" << s2 << endl;
	swap(s1, s2);
	cout << "s1:" << s1 << '\t' << "s2:" << s2 << endl;
}

运行结果:

在这里插入图片描述

void test6()
{
	String s("hawrwcrwwwwcrw");
	size_t found = s.find('w', 0, 4);
	cout << found << endl;
	found = s.find("wwcrw");
	cout << found << endl;
	String s2("wc");
	found = s.find(s2);
	cout << found << endl;
}

运行结果:

在这里插入图片描述

void test7()
{
	String s("hawwwcrwwwwcrw");
	String s2 = s.substr(2);
	cout << s2 << endl;
	String s3 = s.substr(s.find("cr"), 4);
	cout << s3 << endl;
}

运行结果:

在这里插入图片描述


总结

​ 通过实现自己的String类,我们不仅加深了对C++内存管理、对象生命周期和运算符重载的理解,还提高了我们的编程技能和问题解决能力。虽然std::string类在功能和性能上可能更加成熟,但自己动手实现一个String类无疑是一次宝贵的学习经历。在未来的编程实践中,我们可以继续探索更多的优化方法和高级特性,使我们的String类更加健壮和高效。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Web核心简介

简介 web&#xff1a;全球广域网&#xff0c;也称万维网(www)&#xff0c;能够通过浏览器访问的网站 JavaWeb&#xff1a;是用Java技术来解决相关web互联网领域的技术栈 JavaWeb技术栈 B/S架构&#xff1a;Browser/Server&#xff0c;浏览器/服务器架构模式&#xff0c;它的…

精神暴力的来源与解药

导致人生病的&#xff0c;不仅是病毒或细菌&#xff0c;也有精神暴力。与病毒破坏物理肌体、摧毁生命不同&#xff0c;精神暴力是让人们在过度的自我狂热中燃尽自我、而毁灭自身的。 21世纪以来&#xff0c;精神方面的疾病越来越多&#xff0c;为什么这样呢&#xff1f;大的背景…

【C语言进阶篇】动态内存管理

【C语言进阶篇】动态内存管理 &#x1f308;个人主页&#xff1a;开敲 &#x1f525;所属专栏&#xff1a;C语言 &#x1f33c;文章目录&#x1f33c; 1. 为什么要有动态内存分配 2.动态内存开辟和释放函数 2.1 动态内存释放函数 2.1.1 free函数 2.2 动态内存开辟函数 2.2.1 …

HTML元素语义化补充之css函数(三)

文章目录 CSS中的函数css函数–varcss函数–calccss函数–blurcss函数–gradientlinear-gradient的使用 CSS中的函数 ◼ 在前面我们有使用过很多个CSS函数: 比如rgb/rgba/translate/rotate/scale等; CSS函数通常可以帮助我们更加灵活的来编写样式的值&#xff1b; ◼ 下面有几…

自动驾驶轨迹规划之时空语义走廊(一)

欢迎大家关注我的B站&#xff1a; 偷吃薯片的Zheng同学的个人空间-偷吃薯片的Zheng同学个人主页-哔哩哔哩视频 (bilibili.com) 目录 1.摘要 2.系统架构 3.MPDM 4.时空语义走廊 ​4.1 种子生成 4.2 具有语义边界的cube inflation ​4.3 立方体松弛 本文解析了丁文超老师…

【机器学习300问】47、如何计算AUC?

一、AUC是什么&#xff1f; &#xff08;1&#xff09;文绉绉的定义 AUCArea Under the Curve中文直译叫“曲线下面积”&#xff0c;AUC名字里面的Curve曲线指的就是ROC曲线&#xff0c;关于ROC曲线的相关知识我已经在之前的文章中详细说过了&#xff0c;有需要的友友可以点击…

阿里云服务器(Ubuntu22)上的MySQL8数据库下载,安装和远程连接

最近阿里云centos主机到期了改为使用Ubuntu操作系统&#xff0c;在上面安装mysql并远程连接出现了一系列问题并解决。 之前在centos系统上下载mysql8的教程如下&#xff1a; 阿里云服务器&#xff08;centos7&#xff09;上的MySQL8数据库下载&#xff0c;安装和远程连接 主机操…

10、chrome拓展程序的实现

一、拓展程序的实现 拓展程序项目的构成 和前端项目一样&#xff0c;拓展程序也是有Html、CSS、JS文件实现的&#xff0c;现在看来它就是一个静态的前端页面。但是不同的是&#xff0c;拓展程序中还需要额外的一个清单文件&#xff0c;就是manifest.json&#xff0c;清单文件可…

Fabric Measurement

Fabric Measurement 布料测量

stm32平衡车

目录 一.所需材料 二.PID算法&#xff08;简单说明&#xff09; 直立环 速度环 串级PID 三.使用到的外设 1.定时器输出比较-PWM 2.定时器编码器模式 3.编码器读取速度 4.电机驱动函数 5.外部中断 四、小车 调试 一.所需材料 1.陀螺仪MPU6050--读取三轴的加速度…

基于springboot+vue的旅游推荐系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

自定义序列化

3.2.2.自定义序列化 RedisTemplate可以接收任意Object作为值写入Redis&#xff1a; 只不过写入前会把Object序列化为字节形式&#xff0c;默认是采用JDK序列化&#xff0c;得到的结果是这样的&#xff1a; 缺点&#xff1a; 可读性差内存占用较大 我们可以自定义RedisTempla…

Python 从0开始 一步步基于Django创建项目(3)使用Admin site管理数据模型

本文内容建立在《Python 从0开始 一步步基于Django创建项目&#xff08;2&#xff09;创建应用程序&数据模型》的基础上。 Django提供的admin site&#xff0c;使得网站管理员&#xff0c;能够轻松管理网站的数据模型。 本文首先创建‘管理员账户’&#xff0c;即超级用户…

springboot296基于个性化定制的智慧校园管理系统设计与开发

智慧校园管理系统的设计与实现 摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统校园综合信息管理难度大&…

C++夯实基础

C在线学习笔记 第一阶段&#xff1a;基础 一、环境配置 1.1.第一个程序&#xff08;基本格式&#xff09; ​ #include <iosteam> using namespace std;int main(){cout<<"hello world"<<endl;system("pause"); }​ 模板 #include…

【比较函数坑点】D. Li Hua and Tree

题目 #include <bits/stdc.h> using namespace std; #define int long long #define pb push_back #define fi first #define se second #define lson p << 1 #define rson p << 1 | 1 const int maxn 1e5 5, inf 1e18, maxm 4e4 5; const int mod 1e9…

Qt creator构建DLL库

文章目录 一、构建DLL库二、隐式调用DLL库 一、构建DLL库 Qt creator创建DLL项目。 实现功能函数。 运行代码&#xff0c;debug目录下会有.dll和.lib文件。 二、隐式调用DLL库 QT新建控制台项目。将.lib文件和与之关联的头文件赋值到项目文件夹。 3. 添加头文件和外部依赖库…

创建vue3项目并集成cesium插件运行

创建vue3项目并集成cesium插件 一、vue项目创建 1、前期准备 node.js&npm或yarn 本地开发环境已经安装好。 参考安装 2、安装vue-cli&#xff0c;要求3以上版本 #先查看是否已经安装 vue -V#安装 npm install -g vue/cli4.5.17 示例&#xff1a;Idea工具 页面 Termin…

HE切片+qupath识别TIL和成纤维细胞文献(三阴性乳腺癌)

An Open-Source, Automated Tumor-Infiltrating Lymphocyte Algorithm for Prognosis in Triple-Negative Breast Cancer An Open-Source, Automated Tumor-Infiltrating Lymphocyte Algorithm for Prognosis in Triple-Negative Breast Cancer - PubMed (nih.gov) 试验设计 …

Matplotlib中英文使用不同字体的最优解

中英文使用不同字体&#xff0c;我们需要解决两个需求&#xff1a; 第一个需求&#xff1a;要用中文字体显示中文&#xff0c;不能全部都是框框。第二个需求&#xff1a;横纵坐标的数字用英文字体显示&#xff0c;英文用英语字体显示。 方法很简单&#xff0c;只需要添加一行…