【C++】STL之string的使用和模拟实现

       初阶的C++语法和基本的类和对象我们已经学过了,下面我们会步入一段新的旅程。本章我们将初步了解STL(标准模板库),并且深入探讨其中一个非常重要的容器———string。

目录

(一)STL简介(了解即可)

(1)定义和版本分类

(2)STL六大组件

(3)STL的一些不足

(二)string的认识和使用

(1)string常用接口

1、 string类对象的常见构造

2、string类对象的容量操作  

3、string类对象的修改操作

4、string类的非成员函数

5、string类对象的访问及迭代器相关操作*

(三)string的模拟实现

 (1)迭代器的实现

(2)构造、拷贝构造和赋值重载的实现

(3)其他操作的实现


(一)STL简介(了解即可)

(1)定义和版本分类

定义:

STL(standard template libaray- 标准模板库 ) C++ 标准库的重要组成部分 ,不仅是一个可复用的组件库,而且
是一个包罗数据结构与算法的软件框架
版本分类:

  • 原始版本: Alexander StepanovMeng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使用。 HP 版本--所有STL实现版本的始祖。
  • P. J. 版本 :P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低, 符号命名比较怪异。
  • RW版本 :Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般。
  • SGI版本 :Silicon Graphics Computer SystemsInc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好, 可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。我们后面学习STL要阅读部分源代码, 要参考的就是这个版本。

(2)STL六大组件

STL包含着数据结构和算法,经过不断的发展,常用的有下面的六大组件:

(3)STL的一些不足

  • 1. STL库的更新太慢了。这个得严重吐槽,上一版靠谱是C++98,中间的C++03基本一些修订。C++11出 来已经相隔了13年,STL才进一步更新。
  • 2. STL现在都没有支持线程安全。并发环境下需要我们自己加锁。且锁的粒度是比较大的。
  • 3. STL极度的追求效率,导致内部比较复杂。比如类型萃取,迭代器萃取。
  • 4. STL的使用会有代码膨胀的问题,比如使用vector/vector/vector这样会生成多份代码,当然这是模板语 法本身导致的。

(二)string的认识和使用

经过上面的介绍,我们明白了string是STL库中容器的一个类,我们下面的学习就是逐一地学习STL的各个组件部分以及组件中的数据结构、算法等。

首先,我在这里先介绍一个好用的工具,有利于我们查询库,深入了解分类、实现、用法:

https://cplusplus.com/

下面步入string的学习:

(1)string常用接口

1、 string类对象的常见构造

我们进入上面的cplusplus网站查询string,就可以看到string的常见构造:

 这里主要给出了构造函数,析构函数和赋值重载。下面我们一一查看:

构造函数:

 这里给出了多种应用方法,构造,缺省构造,拷贝构造等,大家可以参照着使用。

赋值重载:

可以看出,我们可以赋一个字符串,也可以赋一个字符。

举个例子:

2、string类对象的容量操作  

我们延续上面的操作,查询知主要的容量操作如下:

 最主要的几个操作和用途如下:

函数名称
功能说明
size (重点)       返回字符串有效字符长度
length                 返回字符串有效字符长度
capacity             返回空间总大小
empty (重点) 检测字符串释放为空串,是返回 true ,否则返回 false
clear (重点)    清空有效字符
reserve (重点   为字符串预留空间 * *
resize (重点) 将有效字符的个数该成 n 个,多出的空间用字符 c 填充
初次认识可能还不熟,其实无需着重记忆,后续使用多了自然会记住!
使用样例:
#include <iostream>
using namespace std;

#include <string>


// 测试string容量相关的接口
// size/clear/resize
void Teststring1()
{
	// 注意:string类对象支持直接用cin和cout进行输入和输出
	string s("hello, zc!!!");
	cout << s.size() << endl;
	cout << s.length() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;

	// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
	s.clear();
	cout << s.size() << endl;
	cout << s.capacity() << endl;

	// 将s中有效字符个数增加到10个,多出位置用'a'进行填充
	// “aaaaaaaaaa”
	s.resize(10, 'a');
	cout << s.size() << endl;
	cout << s.capacity() << endl;

	// 将s中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充
	// "aaaaaaaaaa\0\0\0\0\0"
	// 注意此时s中有效字符个数已经增加到15个
	s.resize(15);
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;

	// 将s中有效字符个数缩小到5个
	s.resize(5);
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;
}

//====================================================================================
void Teststring2()
{
	string s;
	// 测试reserve是否会改变string中有效元素个数
	s.reserve(100);
	cout << s.size() << endl;
	cout << s.capacity() << endl;

	// 测试reserve参数小于string的底层空间大小时,是否会将空间缩小
	s.reserve(50);
	cout << s.size() << endl;
	cout << s.capacity() << endl;
}

// 利用reserve提高插入数据的效率,避免增容带来的开销
//====================================================================================
void TestPushBack()
{
	string s;
	size_t sz = s.capacity();
	cout << "making s grow:\n";
	for (int i = 0; i < 100; ++i)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

// 构建vector时,如果提前已经知道string中大概要放多少个元素,可以提前将string中空间设置好
void TestPushBackReserve()
{
	string s;
	s.reserve(100);
	size_t sz = s.capacity();

	cout << "making s grow:\n";
	for (int i = 0; i < 100; ++i)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

int main()
{
	return 0;
}

3、string类对象的修改操作

 样例代码:

// 测试string:
// 1. 插入(拼接)方式:push_back  append  operator+= 
// 2. 正向和反向查找:find() + rfind()
// 3. 截取子串:substr()
// 4. 删除:erase
void Teststring5()
{
	string str;
	str.push_back(' ');   // 在str后插入空格
	str.append("hello");  // 在str后追加一个字符"hello"
	str += 'z';           // 在str后追加一个字符'z'   
	str += "cc";          // 在str后追加一个字符串"cc"
	cout << str << endl;
	cout << str.c_str() << endl;   // 以C语言的方式打印字符串

	// 获取file的后缀
	string file("string.cpp");
	size_t pos = file.rfind('.');
	string suffix(file.substr(pos, file.size() - pos));
	cout << suffix << endl;

	// npos是string里面的一个静态成员变量
	// static const size_t npos = -1;

	// 取出url中的域名
	string url("http://www.cplusplus.com/reference/string/string/find/");
	cout << url << endl;
	size_t start = url.find("://");
	if (start == string::npos)
	{
		cout << "invalid url" << endl;
		return;
	}
	start += 3;
	size_t finish = url.find('/', start);
	string address = url.substr(start, finish - start);
	cout << address << endl;

	// 删除url的协议前缀
	pos = url.find("://");
	url.erase(0, pos + 3);
	cout << url << endl;
}

int main()
{
	return 0;
}

4、string类的非成员函数

这里就不着重强调了,平时中大家会遇到很多输出输入操作,无需进一步讲解。

5、string类对象的访问及迭代器相关操作*

 对对象的访问重点是[ ]的重载,他可以访问string中任意的字符,也可以进行历遍访问操作。

// string的遍历

void Teststring3()
{
	string s1("hello zc");
	const string s2("Hello zc");
	cout << s1 << " " << s2 << endl;
	cout << s1[0] << " " << s2[0] << endl;

	s1[0] = 'H';
	cout << s1 << endl;

	// s2[0] = 'h';   代码编译失败,因为const类型对象不能修改
}

void Teststring4()
{
	string s("hello Bit");
	// 3种遍历方式:
	// 需要注意的以下三种方式除了遍历string对象,还可以遍历是修改string中的字符,
	// 另外以下三种方式对于string而言,第一种使用最多
	// 1. for+operator[]
	for (size_t i = 0; i < s.size(); ++i)
		cout << s[i] << endl;

	
}

 我们重点讲解迭代器操作,因为迭代器对于任意一个容器都可以进行历遍访问!!!

比如后面的vector,list等等都可以进行迭代器进行历遍。

迭代器历遍:

首先我们定义一个迭代器对象,然后利用begin()初始化,以及end()来标识范围即可实现历遍。

这里大家可能认为还是上面的[ ]好用,为什么非要用迭代器?

简要回答一下:

迭代器可以实现对任意容器的访问,如后面的list链表也可以同样的操作进行访问,比较具有统一性(而且下面将会介绍范围for,更简便)。

// string的遍历


void Teststring4()
{
	
	// 2.迭代器
	string::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << endl;
		++it;
	}

	// string::reverse_iterator rit = s.rbegin();
	// C++11之后,直接使用auto定义迭代器,让编译器推到迭代器的类型
	auto rit = s.rbegin();
	while (rit != s.rend())
		cout << *rit << endl;


}

范围for:

范围for就像一个语法糖,历遍访问可以非常简易的实现,迭代器类型直接用auto代替,直接进行历遍访问(底层还是使用迭代器)。

操作如下:

// string的遍历

// 注意:string遍历时使用最多的还是for+下标 或者 范围for(C++11后才支持)
//


void Teststring4()
{
	string s("hello zc");


	// 3.范围for
	for (auto ch : s)
		cout << ch << endl;
}

(三)string的模拟实现

前言,我们模拟实现string首先要构想它是一个什么样的类,成员有什么?

通过上面的学习,我们可以初步定下他的三个类成员就是字符串,大小和容量,如下:

 (1)迭代器的实现

对于string类,他的迭代器就是一个char类型的指针,然后记录好begin和end的位置,就可以从头向后历遍访问了。

namespace ZC
{

	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;
		}
private:
		char* _str;
		size_t _capacity;
		size_t _size;

	};

(2)构造、拷贝构造和赋值重载的实现

我们上面查询过,构造、拷贝构造函数的版本有很多种:

这里着重要注意的深浅拷贝问题:

浅拷贝:

浅拷贝就是没有对st2另外开空间,然后直接把st1指向的空间赋给st2,这样一来,两者指向同一块空间,增删查改不仅会相互影响,而且对于同一块空间析构函数会调用两次倒置程序崩溃

 深拷贝:

新开辟一段空间,然后一一表示st1的元素拷贝到st2新开的空间上,这样的拷贝不会相互影响。

下面我们分别来看构造函数。拷贝构造和复制重载:

这三类都需要用到深拷贝!!!

//实现一个简单的string,只考虑资源管理深浅拷贝的问题
	//暂且不考虑增删查改

	//string需要考虑完善的增删查改和使用的string
	class string
	{
	public:
	

		//无参的构造函数
		/*string()
			:_size(0)
			,_capacity(0)
		{
			_str = new char[1];
			_str[0] = '\0';
		}*/

		//全缺省
		//1、"\0"这是有两个\0  
		//2、""这是有一个\0  
		//3、'\0'这里是把\0的assic码值0给了指针,实际是空指针
		string(const char* str = "")     //""是C语言默认常量字符串,后面有\0
			:_size(strlen(str))          //strlen()是不会判空指针的
			, _capacity(_size)

		{
			_str = new char[_capacity + 1];//给'\0'多开一个
			strcpy(_str, str);
		}

//·构造函数:
//·传统的写法:本分,老实,老老实实干活,该开空间开空间,该拷贝数据就自己拷贝数据
		//s2(s1); - 深拷贝
		//在类里面只要用string对象访问成员都是不受限制的
		//私有是限制在类外面使用对象去访问成员
		/*string(const string& s)
			:_size(strlen(s._str))
			, _capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, s._str);
		}*/

//·现代写法:剥削,要完成深拷贝,自己不想干活,安排别人干活,然后窃取劳动成果
		//要初始化一下,不然有可能释放野指针
		string(const string& s)
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{
			string tmp(s._str);
			swap(tmp);
		}


//赋值重载:
//传统写法:
		//s1 = s3,如果不传引用返回,用传值返的话会深拷贝,代价太大了
		//new失败了之后会抛异常,用try捕获
		//string& operator = (const string& s)
		//{
		//	if (this != &s)
		//	{
		//		//1、先释放:如果s1开空间失败了,之前的空间也被释放了
		//			/*delete[] _str;
		//			_str = new char[strlen(s._str) + 1];
		//			strcpy(_str, s._str);*/

		//		//2、先开空间:下面写法可以避免上述问题
		//		char* tmp = new char[s._capacity + 1];
		//		strcpy(tmp, s._str);
		//		delete[] _str;
		//		_str = tmp;
		//		_size = s._size;
		//		_capacity = s._capacity;
		//	}
		//	return *this;
		//}

//现代写法:
//现代方法一:
		/*string& operator=(const string& s)
		{
			if (this != &s)
			{
				string tmp(s._str);
				swap(tmp);
			}
			return *this;
		}*/

//现代方法二:-- 更简单,一行代码搞定,适用于所有深拷贝
		//s 就是 s1的深拷贝,先传参,传参就是拷贝构造
		string& operator=(string s)
		{
			swap(s);
			return *this;
}

——构造函数的现代写法:

  • 先实例一个string类型的tmp对象:
  • 直接根据传来的指针实例化一个所需一样的对象
  • 再将tmp对象的内容和所要拷贝构造的对象的成员变量进行交换
  • 在将这个拷贝函数结束之后,tmp对象的生命周期结束,自动调用其析构函数,释放掉空间

——赋值重载的现代写法:

  • 赋值函数中,形参是string类型的对象,调用函数是值传参
  • s对象已经是拷贝的对象了,直接将s对象和所需要拷贝的对象交换就好

注意:

  • 要被拷贝构造的对象中的成员变量为随机值,所以里面的str成员指针是随机值
  • 这个随机值换给tmp这个对象之后
  • tmp对象生命周期结束后,自动调用析构函数,对野指针进行释放,就会出错,程序崩溃
  • 所以拷贝构造一开始要在初始化类表中对要被拷贝的对象成员变量进行初始化
     

(3)其他操作的实现

主要是增删查改,判断大小等操作。

主要有一点大家可能难以理解:

 首先为什么要删掉原_str指针再赋值呢?

主要是为了防止_str后面的空间不够开辟的了,如果在原_str处开辟空间不够的话,系统会重新找新的足够大的空间,这样_str的位置就改变了,原来char* _str存放的位置就无法找到新开辟的空间了。

要先开所需要的空间再去拷贝之后再释放

因为如果先释放,当新空间空开失败了的时候

就会出现原来的空间也被释放了,原来的数据也找不到了

const char* c_str()
		{
			return _str;
		}

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

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

		const size_t size()const
		{
			return _size;
		}

		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 strcmp(_str, s._str) < 0;
		}

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

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

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

		//开空间
		void reserve(size_t n)
		{
			char* tmp = new char[n + 1];
			strcpy(tmp , _str);

			delete[]_str;
			_str = tmp;
			_capacity = n;

		}
		//开空间加初始化
		void resize(size_t n, char ch = '\0')
		{
			if (n < _size)
			{
				// 删除数据--保留前n个
				_size = n;
				_str[_size] = '\0';
			}
			else if (n > _size)
			{
				if (n > _capacity)
				{
					reserve(n);
				}

				size_t i = _size;
				while (i < n)
				{
					_str[i] = ch;
					++i;
				}

				_size = n;
				_str[_size] = '\0';
			}
		}


		void push_back(char ch)
		{
			if (_size + 1 > _capacity)
			{
				reserve(_capacity * 2);
			}
			
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}


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

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


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

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


		void insert(size_t pos, char ch)
		{
			assert(pos <= _size);
			if (_size + 1 > _capacity)
			{
				_capacity *= 2;
			}
			//区别end=_size   while(end>=pos)_str[end -1 ] = _str[end ];这里>=的=没法实现,因为无符号
			size_t end = _size+1;
			while (end > pos)
			{
				_str[end ] = _str[end - 1];
				end--;
			}

			_str[pos] = ch;
			++_size;
		}

		void insert(size_t pos, const char* str)
		{

			assert(pos <= _size);
			size_t len = strlen(str);
			if (_size + 1 > _capacity)
			{
				_capacity = _size + len;
			}
		
			size_t end = _size + len;
			while (end > pos+len-1)
			{
				_str[end] = _str[end - len];
				end--;
			}

			strncpy(_str + pos, str, len);
			_size += len;
		}

		void erase(size_t pos, size_t len = npos)
		{
			assert(pos <= _size);
			if (pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				while (_str[pos+len]!='\0')
				{
					_str[pos] = _str[pos + len];
					pos++;
				}

				_str[pos] = '\0';
				_size -= len;
			}
		}

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

		size_t find(char ch, size_t pos = 0)
		{
			assert(pos < _size);

			for (size_t i = pos; i < _size; ++i)
			{
				if (_str[i] == ch)
				{
					return i;
				}
			}

			return npos;
		}

		size_t find(const char* str, size_t pos = 0)
		{
			assert(pos < _size);

			// kmp
			char* p = strstr(_str + pos, str);
			if (p == nullptr)
			{
				return npos;
			}
			else
			{
				return p - _str;
			}
		}

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

相关链接——>>string的模拟实现

感谢您的阅读,祝您学业有成!

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

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

相关文章

Hashtable、HashMap、ConcurrentHashMap的区别

作者&#xff1a;爱塔居 专栏&#xff1a;JavaEE 作者简介&#xff1a;大三学生&#xff0c;希望和大家一起进步。 Hashtable和HashMap、ConcurrentHashMap 之间的区别? HashMap:线程不安全&#xff0c;key允许为null Hashtable:线程安全&#xff0c;使用synchronized锁Hashta…

2.4 特征工程

2.4 特征工程 李沐 B站:https://space.bilibili.com/1567748478/channel/collectiondetail?sid=28144 课程主页:https://c.d2l.ai/stanford-cs329p/ 1. 为什么需要特征工程: 特征工程 数据集进行特征提取,以使机器学习模型在对经过特征工程处理过的数据进行学习时可以更快…

(02)基础强化:面向对象,变量作用域,封装,继承,虚方法,可访问性

一、面向对象概念复习 1、什么是面向对象&#xff1f;OOP&#xff08;Object-Oriented Programming&#xff09; 一种分析问题的方式&#xff0c;增强了程序的可扩展性。 OOP面向对象编程 OOA面向对象分析 OOAD面向对象分析与设计&#xff08;…

Redis管道(pipeline)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言1、管道(pipeline)的基本概念2、管道实操3、小总结前言 在正式讲解Redis管道之前&#xff0c;先引入一个面试题&#xff1a; 如何优化频繁命令往返造成的性能瓶…

【Hello Linux】线程控制

作者&#xff1a;小萌新 专栏&#xff1a;Linux 作者简介&#xff1a;大二学生 希望能和大家一起进步&#xff01; 本篇博客简介&#xff1a;简单介绍linux中的线程控制 线程控制线程创建线程等待线程终止线程分离线程id和进程地址空间布局线程创建 我们可以通过下面pthread_c…

蓝桥杯基础14:BASIC-1试题 闰年判断

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 给定一个年份&#xff0c;判断这一年是不是闰年。 当以下情况之一满足时&#xff0c;这一年是闰年&#xff1a; 1. 年份…

Java面向对象 - 封装、继承和多态的综合练习(答案+知识点总结)第1关:封装、继承和多态进阶(一)+ 第2关:封装、继承和多态进阶(二)

目录 第1关&#xff1a;封装、继承和多态进阶&#xff08;一&#xff09; 报错总结 & 注意事项&#xff1a; 第2关&#xff1a;封装、继承和多态进阶&#xff08;二&#xff09; 源码&#xff1a; 报错总结 & 注意事项&#xff1a; 思维导图免费制作网站&#xf…

软考软件设计师下午试题一

数据流图基本图形元素 外部实体 外部系统是当前系统之外的系统 数据存储 在里面存储数据后还能取出来用 跟实体没有关系&#xff0c;他负责存储加工的数据或者提供数据给加工 加工 灰洞的解释比如输入需要两个才能得到输出的&#xff0c;但是他只输入了一个就是灰洞 数…

Matlab傅里叶级数展开(附结果图)

Matlab傅里叶级数展开&#xff08;附结果图&#xff09; 代码下载链接 代码下载链接 代码下载链接 如下图所示&#xff1a;

“唯一靶点”的华堂宁会成控糖爆品吗?

一上市&#xff0c;两次“断货”的货华堂宁有爆品那味儿了。 2022年10月28日华领医药-B&#xff08;02552.HK&#xff09;公告华堂宁&#xff08;多格列艾汀&#xff09;正式进入商业化&#xff0c;一周后各个渠道便进入到了断货和限售的状态。 对于一个不在传统九大降糖药品…

元宇宙与网络安全

元宇宙是一种虚拟现实空间&#xff0c;用户可以在计算机生成的环境中进行互动。元宇宙的应用范围很广&#xff0c;比如房地产&#xff0c;医疗&#xff0c;教育&#xff0c;军事&#xff0c;游戏等等。它提供了更具沉浸感的体验&#xff0c;更好地现实生活整合&#xff0c;以及…

组件、套件、 中间件、插件

组件、套件、 中间件、插件 组件 位于框架最底层&#xff0c;是由重复的代码提取出来合并而成。组件的本质&#xff0c;是一件产品&#xff0c;独立性很强&#xff0c;组件的核心&#xff0c;是复用&#xff0c;与其它功能又有强依赖关系。 模块 在中台产品和非中台产品中&…

C语言程序环境和预处理

文章目录程序的翻译环境和执行环境详解编译和链接翻译环境编译本身也分为几个阶段预处理编译汇编链接段表符号表的合并预处理详解预定义符号#define#define 定义标识符#define定义宏#define替换规则#和#### 的作用带副作用的宏参数宏和参数的对比宏和函数的一个对比命名约定#un…

FastestDet:比yolov-fastest更快!更强!全新设计的超实时Anchor-free目标检测算法

本篇文章转自于知乎——qiuqiuqiu,主要设计了一个新颖的轻量级网络! 代码地址:https://github.com/dog-qiuqiu/FastestDet 1 概述 FastestDet是设计用来接替yolo-fastest系列算法,相比于业界已有的轻量级目标检测算法如yolov5n, yolox-nano, nanoDet, pp-yolo-tiny, Fast…

CSS基础知识,必须掌握!!!

CSS基础知识Background&#xff08;背景&#xff09;CSS文本格式文本颜色文本对齐格式文本修饰文本缩进CSS中的字体字体样式字体大小CSS链接&#xff08;link&#xff09;CSS列表不同列表标项CSS列表项用图片作为标记CSS列表标记项位置CSS中表格&#xff08;table&#xff09;表…

Shell脚本之嵌套循环与中断跳出

1、双重循环 1.1 格式 #!/bin/bash for ((i9;i>1;i--)) do for ((j9;j>$i;j--)) do echo -n -e "$j$i$[$i*$j]\t" done echo done1.2 实例操作 2.1 格式 #!/bin/bash for ((a1;a<9;a)) dofor ((b9;b>a;b--))doecho -n " "donefor((c1;c<…

系统信息:uname,sysinfo,gethostname,sysconf

且欲近寻彭泽宰&#xff0c;陶然共醉菊花怀。 文章目录系统信息系统标识 unamesysinfo 函数gethostname 函数sysconf()函数系统信息 系统标识 uname 系统调用 uname()用于获取有关当前操作系统内核的名称和信息&#xff0c;函数原型如下所示&#xff08;可通过"man 2 un…

面向对象编程(基础)7:再谈方法(重载)

目录 7.1 方法的重载&#xff08;overload&#xff09; 7.1.1 概念及特点 7.1.2 示例 举例1&#xff1a; 举例2&#xff1a; 举例3&#xff1a;方法的重载和返回值类型无关 7.1.3 练习 **练习1&#xff1a;** 练习2&#xff1a;编写程序&#xff0c;定义三个重载方法并…

如何大批量扫描的发票进行ocr识别导出Excel表格和WPS表格

OCR技术&#xff1a;OCR&#xff08;Optical Character Recognition&#xff0c;光学字符识别&#xff09;是将数字图像中的文字识别成字符代码的技术&#xff0c;在发票识别中应用广泛。通过OCR技术&#xff0c;可以将图片发票上的信息识别出来&#xff0c;并导出到Excel表格中…

3年测试越来越迷茫... 技术跟不上接下来是不是要被淘汰了?

这两天和朋友聊到了软件测试的发展&#xff1a;这一行的变化确实蛮大&#xff0c;从开始最基础的功能测试&#xff0c;到现在自动化、性能、安全乃至于以后可能出现的大数据测试、AI测试岗位需求逐渐增多。我也在软件测试这行摸爬滚打有些日子了&#xff0c;正好有朋友问我&…