万字string类总结

news2024/12/25 1:20:46


目录

一、string类的介绍

二、string类的常用接口

1、构造函数

2. string类对象的容量操作

3. string类对象的访问及遍历操作

4. string类对象的修改操作 (重点)

5. string类非成员函数

6. vs和g++下string结构的说明

三、string类的模拟

1. 浅拷贝问题

2. 深拷贝

3. string类常用库函数的实现

4、写时拷贝(了解)


一、string类的介绍

1. 字符串是表示字符序列的类
2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。
3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信息,请参阅basic_string)。
4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。
5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。 
总结:
1. string是表示字符串的字符串类
2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
3. string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator>string;
4. 不能操作多字节或者变长字符的序列。
注:在使用string类时,必须包含#include头文件以及using namespace std;
详细文章可以参考:string - C++ Reference (cplusplus.com)


二、string类的常用接口

1、构造函数

函数功能
string( )构造一个空的string类对象,即空字符串
string(const char* s)用字符串来构造一个string类对象
string(size_t n, char c)
string 类对象中包含 n 个字符 c
string(const string&s) 
拷贝构造函数

代码:

#include <iostream>
#include <string>
using namespace std;

int main()
{
    string s1;
    string s2("hello qingshan");
    string s3(3, 'x');
    string s4(s3);
    return 0;
}

2. string类对象的容量操作

函数功能
size返回字符串有效长度
capacity返回空间总大小
lenth返回字符串有效长度
empty
检测字符串是否为空串,是返回true,否则返回false
clear清空有效字符
reverse为字符串预留空间
resize
将有效字符的个数该成n个,多出的空间用字符c填充

size lenth

其实效果相同,只是由于某些原因创造出了一些多余的接口。 

capacity

string底层原理是一个字符数组,capacity代表的就是这个字符数组的容量大小。

int main()
{
    string s1;
    string s2("hello qingshan");
    cout << s2.size() << " " << s2.length() << endl;
    cout << s2.capacity() << endl;

    return 0;
}

clear

就是将string中的字符全部清空。

 可以看到clear之后s2变成了空。

reverse

一般是开更大的空间,值得说的是,一般都是异地扩容,将string类对象的容量扩容到指定个数(或者更大)。如果想要缩容的话,是不支持的。

resize

分为三种,重置的大小大于容量,那么就需要扩容,然后将扩容后的空间用字符c填充;如果小于容量但是大于有效字符长度,那么就直接用字符填充;如果小于有效字符长度,那么就直接缩减长度。(具体后文实现的时候会详细叙述)

注意:resize的第二个参数是将有效空间的字符都变成第二个参数中的字符。

int main()
{
    string s1;
    string s2("hello qingshan");
    cout << s2.size() << " " << s2.length() << endl;
    cout << s2.capacity() << endl;
    s2.clear();
    s2.reserve(20);
    s2.resize(20, 'q');
    cout << s2.capacity() << endl;
    cout << s2 << endl;
    return 0;
}

 这里编译器是按照2倍数扩容了。

  

3. string类对象的访问及遍历操作

operator[ ]

 返回pos位置的字符

代码:

int main()
{
    string s1;
    string s2("hello qingshan");
    cout << s2[1] << endl;
    return 0;
}

   

begin+ end

begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器

一般来说迭代器可能指针,但有时候也可能不是。

int main()
{
    string s1;
    string s2("hello qingshan");
    string::iterator it = s2.begin();
    while (it != s2.end())
    {
        cout << *it << " ";
        it++;
    }
    cout << endl;

    return 0;
}

 迭代器就是遍历的另一种方式

  

rbegin + rend

begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
代码:
int main()
{
    string s1;
    string s2("hello qingshan");
    string::reverse_iterator it = s2.rbegin();
    while (it != s2.rend())
    {
        cout << *it << " ";
        it++;
    }
    cout << endl;

    return 0;
}

 这其实调用的是反向迭代器,所以遍历的时候也是反向遍历。

  

范围for

C++11支持更简洁的范围for的新遍历方式
代码:
int main()
{
    string s1;
    string s2("hello qingshan");
    for (auto ch : s2)
    {
        cout << ch << " ";
    }
    cout << endl;
    return 0;
}

ch每次取一个s1中的元素。

其实,范围for看着很神奇,底层用的也是迭代器。

 

4. string类对象的修改操作 (重点)

push_back

尾部插入字符

 
 

append

在尾部追加

 可以看到用法有很多,最常用的其实就是追加字符和追加字符串。

代码:

int main()
{
    string s1;
    string s2("hello qingshan");
    s2.append("!");
    s2.append("hahaha");
    cout << s2 << endl;
    return 0;
}

   

opeartor +=

在字符串后追加

 追加分为三种:1、字符。 2、字符数组。3、字符串。

  

c_str

返回c格式字符串

也就是返回底层的那个指向字符数组的字符指针

int main()
{
    string s1;
    string s2("hello qingshan");
 
    cout << s2.c_str() << endl;
    return 0;
}

   

find函数+ npos

find是查找函数,npos是string类型中的值为-1的 size_t类型的数,size_t其实是无符号整型。

 查找一般默认是从0位置开始。找到了返回该位置,没找到返回npos。

int main()
{
    string s1;
    string s2("hello qingshan");
    cout << s2.find("n") << endl;

    return 0;
}

   

rfind

反向查找所要找的内容。
 
int main()
{
    string s1;
    string s2("hello qingshan");
    cout << s2.rfind("n") << endl;

    return 0;
}

 这里返回的pos还是按照正向顺序来的,并不是反向从0开始数。

  

substr

在str中从pos位置开始,截取n个字符,然后将其返回
int main()
{
    string s1;
    string s2("hello qingshan");
    size_t pos = s2.rfind("q");
    cout << s2.substr(pos, 8) << endl;

    return 0;
}

   

5. string类非成员函数

operator+

尽量少用,因为传值返回,导致深拷贝效率低
  
 

operator>> 

>>重载之后我们就可以直接通过cin向string类对象中输入数据了,但是不能有空格,空格会自动截断。

operator<< 

<<重载之后就可以直接通过cout输出string类对象的内容了。

getline 

获取一行内容

 第一个参数必须是输入流,第二个才是填string类对象。

代码:

int main()
{
    string s1;
    string s2("hello qingshan");
    getline(cin, s1);
    cout << s1 << endl;

    return 0;
}

  

relational operators

也就是string类对象的比较函数

代码:

#include <iostream>
#include <vector>

int main ()
{
  std::string foo = "alpha";
  std::string bar = "beta";

  if (foo==bar) std::cout << "foo and bar are equal\n";
  if (foo!=bar) std::cout << "foo and bar are not equal\n";
  if (foo< bar) std::cout << "foo is less than bar\n";
  if (foo> bar) std::cout << "foo is greater than bar\n";
  if (foo<=bar) std::cout << "foo is less than or equal to bar\n";
  if (foo>=bar) std::cout << "foo is greater than or equal to bar\n";

  return 0;
}

6. vs和g++下string结构的说明

注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。
· vs下string的结构
string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字
符串的存储空间:
当字符串长度小于16时,使用内部固定的字符数组来存放
当字符串长度大于等于16时,从堆上开辟空间
union _Bxty
{ // storage for small buffer or pointer to larger one
 value_type _Buf[_BUF_SIZE];
 pointer _Ptr;
 char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;
这种设计也是有一定道理的,大多数情况下字符串的长度都小于 16 ,那 string 对象创建好之后,内
部已经有了 16 个字符数组的固定空间,不需要通过堆创建,效率高。
其次:还有 一个 size_t 字段保存字符串长度,一个 size_t 字段保存从堆上开辟空间总的容量
最后:还 有一个指针 做一些其他事情。
故总共占 16+4+4+4=28 个字节。

· g++下string的结构

G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指
针将来指向一块堆空间,内部包含了如下字段:
        ` 空间总大小
        ` 字符串有效长度
        ` 引用计数
struct _Rep_base
{
 size_type _M_length;
 size_type _M_capacity;
 _Atomic_word _M_refcount;
};

三、string类的模拟

1. 浅拷贝问题

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。

  

我们看下面这段代码:

class String
{
public:
    String(const char* str = "")
    {
        if (nullptr == str)
        {
            assert(false);
            return;
        }
        _str = new char[strlen(str) + 1];
        strcpy(_str, str);
    }
    ~String()
    {
        if (_str)
        {
            delete[] _str;
            _str = nullptr;
        }
    }
private:
    char* _str;
};
// 测试
void TestString()
{
    String s1("hello bit!!!");
    String s2(s1);
}

说明:上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。

  

2. 深拷贝

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情况都是按照深拷贝方式提供。
//s2(s1)
string(const string& s)
{
	_str = new char[s._capacity + 1];
	_capacity = s._capacity;
	_size = s._size;

	strcpy(_str, s._str);
}

其实也就是调用拷贝构造函数的时候给新创的对象开辟一块空间。

  

3. string类常用库函数的实现

先确定string类私有的成员变量为

_str指的是开辟的字符数组, _size代表有效字符个数,_capacity代表字符数组中的容量。npos则是无符号整型的最大值。为什么static的的成员可以在类内定义呢?这是因为C++标准规定了const类型的成员可以在类内给一个初始值。

构造函数 、拷贝构造、析构函数

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

	strcpy(_str, str);
}

string(const string& s)
{
	_str = new char[s._capacity + 1];
	_capacity = s._capacity;
	_size = s._size;

	strcpy(_str, s._str);
}

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

}

获取size和capacity

size_t size() const
{
	return _size;
}

size_t capacity()const
{
	return _capacity;
}

  

获取c_str

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

获取第n个元素(重载 [ ])

这里有可能是const类型的对象和普通的对象调用,而普通对象需要对数据进行修改,所以这里将函数重载一下。

//普通对象
char& operator[](size_t pos)
{
	assert(pos < _size);
	return _str[pos];
}

//const对象
const char& operator[](size_t pos) const
{
	assert(pos < _size);
	return _str[pos];
}

resize和reserve的实现

因为reserve只做扩容操作,所以我们需要判断传入的开辟大小是否大于本身拥有的空间容量

而resize就需要分情况了,如果是是扩容,就先用reserve开辟空间,然后默认填入 '\0' 到开辟的空间中。如果是缩小有效字符长度,直接在该位置填入 '\0' 即可。

void reserve(size_t n)
{
	//c++中扩容只能重新开辟空间拷贝过去
	if (n > _capacity)
	{
		char* temp = new char[n + 1];

		//拷贝过来再删除
		strcpy(temp, _str);
		delete[] _str;
		_str = temp;
		_capacity = n;
	}
			
}

void resize(size_t n, char ch = '\0')
{
	if (n > _size)
	{
		reserve(n);
		for (int i = _size; i > _size; --i)
		{
			_str[i] = ch;
		}

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

迭代器

迭代器一般情况下都是指针,但不全都是。在string类中就是一个char*类型,只不过被我们用typedef包装成了iterator。

begin() 返回字符数组的开头,end() 返回字符数组的尾部。

typedef char* iterator;

iterator begin()
{
	return _str;
}

iterator end()
{
	return _str + _size;
}

   

push_back、append、+=

尾插的话还是经典的先检查再插入数据。

append需要先算好扩容后的空间大小然后扩容,最后strncpy把需要追加的字符串追加到原有的字符串后面就行了。重载+=号服用前面两个函数就够了。

void push_back(char ch)
{
	if (_size == _capacity)
	{
		int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(newcapacity);
		_capacity = newcapacity;
	}
	_str[_size] = ch;
	++_size;

	//结尾加上\0
	_str[_size] = '\0';
}

void append(const char* str)
{
	size_t len = strlen(str);
	//如果扩容了两倍也有可能超出
	if (_size + len > _capacity)
	{
		reserve(_size + len + 1);
	}
	strncpy(_str + _size, str, len);
	_size += len;

}

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

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

 

insert的实现

有两种情况:插入字符和插入字符串。

插入字符需要先检查扩容,然后挪动数据,这里需要注意边界问题。如果我们是把end位置的数据挪动到end+1的位置,那么到0的时候再--,因为这里的pos是一个无符号整形,0就会变成npos,陷入死循环。

解决方法:1、可以把pos强制类型转换为int类型。

// 挪动数据
int end = _size;
while (end >= (int)pos)
{
    _str[end + 1] = _str[end];
	--end;
}

2、循环到1的时候就停下来,那么我们就可以看成是end位置来获取前一个位置的值。

//挪动数据
size_t end = _size + 1;
while (end > pos)
{
	_str[end] = _str[end - 1];
	--end;
}

插入字符串也是同理,因为字符串是有长度的,可能是len个,我们这里也需要关注边界问题,循环里end+len之后还要-1,因为需要排除最后一个。

代码:

string& insert(size_t pos, char ch)
{
	assert(pos < _size);
	//检查容量
	if (_size == _capacity)
	{
		int newcapacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(newcapacity);
		_capacity = newcapacity;
	}
	//挪动数据
	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		--end;
	}
	_str[pos] = ch;
	++_size;
			
	return *this;
}

string& insert(size_t pos, const char* str)
{
	assert(pos < _size);
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}
	size_t end = _size + len ;
	while (end > pos + len - 1)
	{
		_str[end] = _str[end - len];
		--end;
	}
	while(len  > 0)
	{ 
		_str[pos -1] = str[len-1];
		len --;
	}
	return *this;
}

 

erase

先断言一下pos位置是否在有效范围内。

接下里我们需要判断的是删除的元素个数是否超出了有效字符个数。

当删除的元素大于剩余有效字符长度,那么就可以直接把该位置的元素改为 '\0' ,然后修改_size的数据就行了。

当删除的元素小于剩余有效字符长度,用strcpy将后面的元素拷贝到前面来就行了,因为strcpy是会把 ‘\0’ 也一并拷贝过来的,所以使用起来非常方便。

string& erase(size_t pos, size_t len)
{
	assert(pos < _size);

	if (len == npos || _size - pos <= len)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		strcpy(_str + pos, _str + pos + len);
		_size -= len;
	}

	return *this;
}

 

find实现

查找分为:查找字符和查找字符串。

查找字符串这里偷个懒,用strstr函数直接查找一下。

因为strstr返回的是指针,所以我们用if判断一下,如果返回的是空指针,那么就返回npos,如果不为空,那么返回找到的ptr - _str 就是位置了。

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

	while (pos < _size)
	{
		if (_str[pos] == ch)
		{
			return pos;
		}
		++pos;
	}
	return npos;
}

size_t find(const char* str, size_t pos = 0)const
{
	assert(pos < _size);
	const char* ptr = strstr(_str + pos, str);
	if (ptr == nullptr)
	{
		return npos;
	}
	else
	{
		return ptr - _str;
	}
}

 

clear、operator<<、operator>>

clear函数作用是清空有效字符。

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

因为操作符第二个参数才是string类对象,所以需要在类外定义。

operator<<就是一个循环输出所有字符。

operator>>由于我们不知道输入多少内容,开少了需要频繁扩容,开大了浪费空间。

我们可以设置一个有128给元素的数组buff,用get函数来获取输入的字符,每次填入一个字符到数组中,每次满了128就将数组添加到string类对象的字符数组中,并将buff数组重置。最后一次如果不满128个是不会追加到string类对象的字符数组中的,我们手动追加一下。因为每次输入的东西都是要覆盖前一次的,所以我们每次输入前都需要清空一下原来的数据,所以我们调用一下clear函数。

ostream& operator<<(ostream& out, const string& s)
{
	for (int i = 0; i < s.size(); ++i)
	{
		out << s[i];
	}

	return out;
}

istream& operator>>(istream& in, string& s)
{
	//先清理s
	s.clear();
		
	char buff[128] = { '\0' };
	size_t i = 0;
	char ch = in.get();
	while (ch != ' ' && ch != '\n')
	{
		//满了
		if (i == 127)
		{
			s += buff;
			i = 0;
		}

		buff[i++] = ch;
		ch = in.get();
	}

	//剩下不满一组的
	if (i > 0)
	{
		buff[i] = '\0';
		s += buff;

	}
	return in;
}

 

4、写时拷贝(了解)

写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。

参考文章:

C++ STL string的Copy-On-Write技术 | 酷 壳 - CoolShell
C++的std::string的“读时也拷贝”技术! | 酷 壳 - CoolShell

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

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

相关文章

c++智能指针(raii)

目录 1.智能指针的作用 2.智能指针带来的问题与挑战 3.三种不同的智能指针 4.auto_ptr 5.unique_ptr 6.shared_ptr 7.weak_ptr&#xff1b;相互引用 8.总结 1.智能指针的作用 以c的异常处理为例看看throw catch用法。有时&#xff0c;一个用new开出的空间用完还没delete…

[附源码]java毕业设计壹家吃货店网站

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【C语言】学数据结构前必学的结构体struct详细

佛祖说&#xff0c;他可以满足程序猿一个愿望。程序猿许愿有生之年写出一个没有bug的程序&#xff0c;然后他得到了永生。 目录 1、结构体的声明与定义 1.1结构体是什么&#xff1f; 1.2为什么要有结构&#xff1f; 1.3结构体的声明 1.4结构体成员类型 1.5结构体变量定义…

由CPU高负载引发内核探索之旅

导语&#xff1a;STGW&#xff08;腾讯云CLB&#xff09;在腾讯云和自研业务中承担多种网络协议接入、请求加速、流量转发等功能&#xff0c;有着业务数量庞大、接入形式多样、流量规模巨大的特点&#xff0c;给产研团队带来了各种挑战&#xff0c;经常要深入剖析各种疑难杂症。…

Win7纯净版系统镜像64位介绍

Win7系统是一款非常经典的系统&#xff0c;这里想为大家介绍的是Win7纯净版系统镜像64位&#xff0c;主要特点就是非常稳定&#xff0c;运行流畅、占用CPU和内存都非常少。系统非常纯净&#xff0c;使用此系统&#xff0c;可让你的电脑焕然一新&#xff01; 一、系统稳定 1、采…

科普读书会丨《被讨厌的勇气》:愤怒不是目的,是一种工具

Hello&#xff0c; 这里是壹脑云读书圈&#xff0c;我是领读人小美~ 《被讨厌的勇气》读书会目前已经进行了两期&#xff0c;成员们也共同探讨了其中第一夜和第二夜的内容。每个人都有被情绪困扰的时候&#xff0c;而阿德勒心理学告诉我们&#xff0c;即使是负面情绪也不可怕…

WebRTC 服务器搭建篇

First off All 服务器环境&#xff1a;采用的阿里云国内服务器&#xff0c;系统&#xff1a; Ubuntu 16.04 64位 。 各个服务所需要的编译环境图&#xff1a; 各个服务器对应所需编译平台 1.第一步&#xff0c;先更新下命令行工具&#xff0c;工欲善其身必先利其器&#xff…

推荐一款图表功能强大的可视化报表工具

企业信息化建设&#xff0c;大量的数据需要经过分析才能挖掘价值。因此数据的价值越来越受到大家的重视&#xff0c;大数据分析工具逐渐成为企业运营必不可少的辅助工具。俗话说工人要想做好事&#xff0c;首先要磨利工具&#xff0c;拥有一个好用的大数据分析工具尤为重要&…

numpy生成0和1数组方法、从已有数组生成新数组方法、生成固定范围内数组、生成随机数组、绘制指定均值和标准差正态分布图、均匀分布图绘制

一、生成0和1数组 np.ones(shape, dtype)&#xff1a;shape为要生成的数组的维度&#xff0c;dtype为数组内元素类型np.ones_like(a, dtype)&#xff1a;生成与a同维度的数组np.zeros(shape, dtype)np.zeros_like(a, dtype) 代码如下 one np.ones([3,4]) one --------------…

机器学习——支持向量机与集成学习

支持向量机与集成学习 文章目录支持向量机与集成学习支持向量机的基本原理线性可分支持向量常用核函数集成学习概述集成学习的两种方式集成学习的基本类型弱学习其合成方式AdaBoost算法训练过程简例一类按监督学习方式对数据进行二元分类的广义线性分类器 文章目录支持向量机与…

免费不限时长的语音转文字软件——Word365

适用场景 想将语音转化成文字。 这里的语音可以是实时输入&#xff0c;也可以是已有音、视频转换成文字。 后者的操作比前者多一步操作。 1.实时语音转文字 可以直接打开Word365&#xff0c;【开始】选项卡中的【听写】功能。 打开前修改一下设置&#xff0c;语言可以根据需…

nmap之nse脚本简单学习

nmap之nse脚本简单学习 环境&#xff1a;centos8 nmap安装 yum -y install nmap -- 版本 [rootqingchen /]# nmap -version Nmap version 7.70 ( https://nmap.org )脚本入门学习 cd /usr/share/nmap [rootqingchen nmap]# ls nmap.dtd nmap-mac-prefixes nmap-os-db …

300dpi等于多少分辨率?如何给图片修改分辨率大小?

​图片是我们在生活中经常需要接触使用到的东西&#xff0c;无论是工作中还是生活中都离不开图片&#xff0c;在使用图片时我们会接触到“图片分辨率”、“dpi”这个概念&#xff0c;那么到底什么是图片分辨率&#xff1f;300DPI等于多少分辨率&#xff1f;如何给图片修改分辨率…

Lidar和IMU(INS)外参标定----常用开源项目总结

写在前面&#xff1a;博主主要关注的是自动驾驶中Lidar和RTK组合导航设备的标定&#xff0c;大部分的开源项目都把其转化为Lidar和IMU的标定过程。 1. ETH的lidar_align (Github)A simple method for finding the extrinsic calibration between a 3D lidar and a 6-dof pose …

推特精准客户开发手册

你要在巷子里营造出热闹的气氛&#xff0c;人为把热度炒起来&#xff0c;虚假的繁荣是做给别人看的&#xff0c;是用来吸引别人而不是说你自己沉迷于此&#xff0c;而“虚假的繁荣”又是个怎么的虚法呢&#xff1f;它需要外界看起来是真的。 可是问题来了&#xff0c;我们都知…

NTP时钟系统为制造业信息化产业提供守时保障

随着科学技术的发展&#xff0c;工业信息化高速迈进&#xff0c;高精度的同步时钟系统显得尤为重要。利用网络同步时钟系统技术对各个设备之间进行时间统一&#xff0c;对制造业和信息化产业提高产能&#xff0c;让生产力更高效提供守时保障。NTP时钟系统是基于网络时间协议而衍…

你问我答 | 解决关于入托的8个疑问

很多新手家长对于送孩子入托有很多顾虑&#xff0c;这次我们通过“你问我答”让家长更了解托班的意义。 Q&#xff1a;不好好吃饭的小宝宝&#xff0c;适合入托吗&#xff1f; A&#xff1a;适合。吃饭是孩子生活能力培养的重要部分&#xff0c;大部分孩子在入托前&#xff0c…

C. Binary String(思维+贪心)

Problem - 1680C - Codeforces 给你一个由字符0和/或1组成的字符串s。 你必须从字符串的开头去除几个&#xff08;可能是零&#xff09;字符&#xff0c;然后从字符串的结尾去除几个&#xff08;可能是零&#xff09;字符。移除后&#xff0c;字符串可能会变成空的。删除的代价…

【跟学C++】C++STL标准模板库——算法详细整理(中)(Study18)

文章目录1、简介2、STL算法分类及常用函数2.1、变序算法(一)2.2.1 初始化算法(2个)2.2.2 修改算法(2个)2.2.3 复制算法(6个)2.2.4 删除算法(6个)3、总结 【说明】 大家好&#xff0c;本专栏主要是跟学C内容&#xff0c;自己学习了这位博主【 AI菌】的【C21天养成计划】&#x…

大学生静态HTML网页设计--公司官网首页

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | 公司官网网站 | 企业官网 | 酒店官网 | 等网站的设计与制 HTML期末大学生网页设计作业&#xff0c;Web大学生网页 HTML&#xff1a;结构 CSS&#xf…