8.string库函数的用法以及string的模拟实现

1. 为什么学习string类?

  • C语言中的字符串

C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

2. 标准库中的string类

2.1 string类(了解)

string类的文档介绍

  1. 字符串是表示字符序列的类

  2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。

  3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信息,请参阅basic_string)。

  4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traitsallocator作为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;

image-20221214001714867

// std::string
//string是针对char类型的一种实例化,类似于下方的代码
typedef basic_string<char> string;   

// 动态增长字符数组
template<class T>
class basic_string
{
private:
	T* _str;
	size_t _size;
	size_t _capacity;
};

typedef basic_string<char> string;

2.2 string类的常用接口说明

1.string类对象的常见构造

(constructor):构造函数

	  default (1)	string();         // 重点学习;构造空的string类对象,即空字符串

         copy (2)   string (const string& str);  // 重点学习;拷贝构造函数

    substring (3)   string (const string& str, size_t pos, size_t len = npos);

from c-string (4)	string (const char* s);   // 重点学习;用C-string来构造string类对象

from sequence (5)	string (const char* s, size_t n);

         fill (6)	string (size_t n, char c);   // 重点学习;string类对象中包含n个字符c

       range (7)	template <class InputIterator>
                    string  (InputIterator first, InputIterator last);

//用法如下:
(1) empty string constructor (default constructor) 
    空字符串构造函数(默认构造函数)
    Constructs an empty string, with a length of zero characters.
    构造一个空字符串,长度为0个字符。
    
(2) copy constructor
    Constructs a copy of str.
    
(3) substring constructor  子字符串构造函数
    Copies the portion of str that begins at the character position pos and spans len characters (or until the end of str, if either str is too short or if len is string::npos).
    复制从字符位置pos开始并跨越len字符的部分(或者直到str结束,如果str太短或者len是string::npos)// string::npos
    // std::string::npos
    // static const size_t npos = -1;
    
(4) from c-string
    Copies the null-terminated character sequence (C-string) pointed by s.
    复制s指向的以空结束的字符序列(C-string)(5) from buffer
    Copies the first n characters from the array of characters pointed by s.
    从s指向的字符数组中复制前n个字符。
    
(6) fill constructor
    Fills the string with n consecutive copies of character c.
    用字符c的n个连续副本填充字符串。
  • 演示样例
#include <iostream>
#include <string>    // 使用string,必须包含头文件<string>
#include <vector>
#include <list>
using namespace std;  // string也在std的命名空间中,因此,使用时需要展开

void test_test1()
{
    // 不同的构造方式,则会调用不同的构造函数来构造对象
	string s1;    				// 对应这个构造函数string()来创建对象;
	string s2("西安市雁塔区");    // string (const char* s);来创建对象
    // 因为没有explicit的限制,所以会进行隐式类型的转换,先生成一个临时变量,再拷贝构造创建s3
	string s3 = "西安市雁塔区";  
	

    // string (size_t n, char c);
    // 用字符c的n个连续副本填充字符串。
	string s4(10, '*');   

	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;

    // 运算符重载
    // string& operator+= (const char* s);
	s2 += "瞪羚路";   

	cout << s2 << endl;

    // 拷贝构造;对应string (const string& str);
	string s5(s2); 
    // 拷贝构造
	string s6 = s2;   
	cout << s5 << s6 << endl;
    
	// 对应string (const char* s, size_t n);
    // 从s指向的字符数组中复制前n个字符。
	string s7("hello world", 5); 
	cout << s7 << endl;

    // 对应string(const string & str, size_t pos, size_t len = npos);
    //  复制从字符位置pos开始并跨越len字符的部分(或者直到str结束,如果str太短或者len是string::npos)。
	string s8(s7, 2, 3); 
	cout << s8 << endl;

	string s9(s7, 2, 30);
	cout << s9 << endl;

    //对应(const char* s, size_t n);
	string s10(s7, 2); 
	cout << s10 << endl;
}
/*
// 重载 operator[]的机制
char& operator[](size_t i)
{
  assert(i < _size);
	return _str[i];
}
*/

void test_string2()
{
	string s1("1234");
	// 遍历他

	// 1、下标 [];  重载了operator[]
	for (size_t i = 0; i < s1.size(); ++i)
	{
        // char& operator[] (size_t pos);  可以在string的手册中查看相关用法
		s1[i]++;   
	}
	//s1[10];  // 重载,越界访问是一定会报错的(因为重载函数内的机制)
	cout << s1 << endl;

    
	// 2、范围for
	for (auto& ch : s1)
	{
		ch--;
	}
	cout << s1 << endl;

    
	// 反转一下字符串
	size_t begin = 0, end = s1.size() - 1;
	while (begin < end)
	{
		swap(s1[begin++], s1[end--]);
	}
	cout << s1 << endl;

	// reverse(s1.begin(), s1.end());
	cout << s1 << endl;
    
    
    // 3、迭代器  -- 通用的访问形式(string形式的迭代器和vector和list的访问形式是相同的)
    // s1.begin()指向字符串中的第一个字符
	string::iterator it1 = s1.begin(); 
	while (it1 != s1.end())
	{
        // 迭代之后,it1指向下一个字符
		*it1 += 1;
		++it1;
	}
    
	it1 = s1.begin();
	while (it1 != s1.end())
	{
		cout << *it1 << " ";
		++it1;
	}
	cout << endl;

    
	vector<int> v;           // 需要包含头文件<vector>
	vector<int>::iterator vit = v.begin();
	while (vit != v.end())
	{
		cout << *vit << " ";
		vit++;
	}
	cout << endl;

    
	list<int> lt;             // 需要包含头文件<list>
	list<int>::iterator ltit = lt.begin();
	while (ltit != lt.end())
	{
		cout << *ltit << " ";
		ltit++;
	}
	cout << endl;
}
// 正向iterator begin();  反向reverse_iterator rbegin(); -- 可以遍历读写容器数据
// const正向:const_iterator begin() const;
// const反向:const_reverse_iterator rbegin() const;  
// -- const迭代器只能遍历,不能修改容器数据

// 被const修饰的对象,只可以用const迭代器来迭代
void Print(const string& s)   
{
	// 遍历读,不支持写
	string::const_iterator it = s.begin(); //此处的const修饰it
	//const string::iterator it = s.begin();  // 此处的const修饰it指向的内容
	while (it != s.end())
	{
		// *it += 1;
		cout << *it << " ";
		++it;
	}
	cout << endl;

    // 反向迭代器,具体用法查看string手册
	// string::const_reverse_iterator rit = s.rbegin();
	auto rit = s.rbegin();    // auto自动识别类型
	while (rit != s.rend())
	{
		cout << *rit << " ";
		++rit;               // ++rit是正方向,越来越靠近s.rbegin
	}
	cout << endl;
}
void test_string3()
{
    // 使用这个构造函数构造string (const char* s);
	string s1("1234");
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	//string::reverse_iterator rit = s1.rbegin();
	auto rit = s1.rbegin();
	while (rit != s1.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;

	Print(s1);
}


int main()
{	
	test_test1();

	return 0; 
}

2.string类对象的容量操作

size(重点)

  • 返回字符串有效字符长度

length

  • 以字节为单位返回字符串的长度。

capacity

  • 返回空间总大小

empty (重点)

  • 检测字符串释放为空串,是返回true,否则返回false

clear (重点)

  • 清空有效字符

reserve (重点)

  • 为字符串预留空间

resize (重点)

  • 将有效字符的个数该成n个,多出的空间用字符c填充
#include <iostream>
#include <string>
using namespace std;

void test_string4()
{
	string s("hello world");
    // 字符串的大小
	cout << s.size() << endl;
    // 字符串所占字节的大小
	cout << s.length() << endl;
    // string对象容量的大小
	cout << s.capacity() << endl;

    // 返回字符串最大可以有多大,根据当前系统
	cout << s.max_size() << endl;
	string ss;
	cout << ss.max_size() << endl;

	cout << s << endl;
	cout << s.capacity() << endl;
    // 清除字符串的内容,使其成为一个空字符串
	s.clear();

	cout << s << endl;
	cout << s.capacity() << endl;
}

// 打印结果
11
11
15
2147483647
2147483647
hello world
15

15
void TestPushBack()
{
	string s;
	size_t sz = s.capacity();
	cout << "capacity changed: " << sz << '\n';
	cout << s.size() << endl;
	cout << "making s grow:\n";
	for (int i = 0; i < 1000; ++i)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}
// 打印结果
capacity changed: 15
0
making s grow:
capacity changed: 31
capacity changed: 47
capacity changed: 70
capacity changed: 105
capacity changed: 157
capacity changed: 235
capacity changed: 352
capacity changed: 528
capacity changed: 792
capacity changed: 1188
    // 由上面的打印结果可以知道,扩容时string容量的变化
// 如果我们知道要插入多少数据,提前用reserve开好空间,避免不断扩容,提高效率
void TestPushBack()
{
	string s;
    // 请求将字符串容量调整为计划中的大小更改,最大长度为n个字符。
	s.reserve(1000);
    
	size_t sz = s.capacity();
	cout << "capacity changed: " << sz << '\n';
	cout << s.size() << endl;
	cout << "making s grow:\n";
	for (int i = 0; i < 1000; ++i)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}

/*
// 打印结果
capacity changed: 1007
0
making s grow:
*/

void test_string5()
{
	TestPushBack();
}  

void test_string6()
{
    // hello world一共是11个字符
	string s1("hello world"); 
    // 将字符串的大小调整为5,那么后6个字符就会被删除
	s1.resize(5);
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	cout << s1 << endl << endl;

    // void resize (size_t n);         // 没有指定插入的数据,则会默认插入'\0'
	// void resize (size_t n, char c); // 指定插入的数据,则插入c
    // 11<n<15  插入数据
	string s2("hello world");
	//s2.resize(15);    // 没有指定插入的数据,则会默认插入'\0'
	s2.resize(15, 'x');     
	cout << s2.size() << endl;
	cout << s2.capacity() << endl;
	cout << s2 << endl << endl;


    // n>15 扩容+插入数据
	string s3("hello world");
	s3.resize(20, 'x');
	cout << s3.size() << endl;
	cout << s3.capacity() << endl;
	cout << s3 << endl << endl;
}

/*
// 打印结果为
5
15
hello

15
15
hello worldxxxx

20
31
hello worldxxxxxxxxx
*/   
    
int main()
{
    test_string6();
    
    return 0;
}

3.string类对象的修改操作

push_back

append

operator+=

void test_string7()
{
	/*
	string s1("hello world");
	// push_back就只有这一种用法
	// 对应 void push_back (char c);
	s1.push_back(' ');   
	s1.push_back('!');
	// append有多种用法,可以自行查看string手册
	// 对应 string& append (const char* s);
	s1.append("hello world"); 
	cout << s1 << endl;

	string s2("!!!!!!!"); 
	// 对应 string& append (const string& str);
	s1.append(s2);            
	cout << s1 << endl;*/

	string s1("hello world");
	s1 += ' ';
	s1 += '!';             // 对应 string& operator+= (char c);
	s1 += "hello world";   // 对应 string& operator+= (const char* s);
	cout << s1 << endl;

	string s2("!!!!!!!");
	s1 += s2;              // 对应 string& operator+= (const string& str);
	cout << s1 << endl;
}
void test_string8()
{
	string s("hello world");
	s.insert(0, "qwy");  // 对应 string& insert (size_t pos, const char* s);
	cout << s << endl;
	s.insert(9, "qwy");
	cout << s << endl;

	s.erase(9, 3);       // 对应 string& erase (size_t pos = 0, size_t len = npos);
	cout << s << endl;

	s.erase(0, 3);
	cout << s << endl;

    // string& erase (size_t pos = 0, size_t len = npos);
	// s.erase(5, 30);
	s.erase(5);
	cout << s << endl;
}
// 打印结果
qwyhello world
qwyhello qwyworld
qwyhello world
hello world
hello
void test_string9()
{
	string s1("hello world hello world");
	string s2("hello world hello world");
	string s3(s2);
	string s4(s3);

    //  从s指向的字符数组中复制前n个字符。
    // string& assign (const char* s, size_t n);
	s1.assign("hello qwy", 5);
	cout << s1 << endl;

    // replace的效率是比较慢的
    // 对应string& replace (size_t pos, size_t len,const char* s);
    // pos 就是要替换的第一个字符的位置
    // len 要替换字符的长度
    // 赋值指向s的字符串到string对象
	s2.replace(6, 5, "qwy");  
	cout << s2 << endl;

	// 将' '替换成20%
    size_t find (const char* s, size_t pos = 0) const;
	size_t pos = s3.find(' ');
	while (pos != string::npos)
	{
		s3.replace(pos, 1, "20%");
		pos = s3.find(' ', pos+3);
	}
	cout << s3 << endl;

    // 对case1的优化
	string ret;
	// ret.reserve(s4.size());
	// ret.reserve(s4.size() + space_size*2); // space_size为空格的个数
	for (auto ch : s4)
	{
		if (ch != ' ')
		{
			ret += ch;
		}
		else
		{
			ret += "%20";
		}
	}
	cout << ret << endl;
}
void test_string10()
{
	string file("test.cpp");
    // 对应const char* c_str() const noexcept;
    // 调用成员函数c_str(),将string对象的字符串以c字符串的形式返回
	FILE* fout = fopen(file.c_str(), "r"); 
	assert(fout);
	
	char ch = fgetc(fout);
	while (ch != EOF)
	{
		cout << ch;
		ch = fgetc(fout);
	}
	fclose(fout);
}
void test_string11()
{
	// "Test.cpp"
	// "Test.cpp.tar"
	string file;
	cin >> file;
	// 要求取后缀
	size_t pos = file.rfind('.'); 
    // 对应 size_t rfind (const char* s, size_t pos = npos) const;
	if (pos != string::npos)
	{
        // 对应string substr (size_t pos = 0, size_t len = npos) const;
		// string suffix = file.substr(pos, file.size() - pos);
        
        // 不指定len,那么就会使用缺省值
		string suffix = file.substr(pos);  
		cout << suffix << endl;
	}
}
// bool operator== (const string& lhs, const string& rhs);
// bool operator== (const char*   lhs, const string& rhs);
// bool operator== (const string& lhs, const char*   rhs);
void test_string12()
{
	string s1("111111");
	const char* p2 = "22";

	s1 == p2;
	p2 == s1;
}

int main()   
{
    test_string12();
    
    return 0;
}

4.习题

仅仅反转字母

image-20240409221200050

// 解题思路
class Solution {
public:
    string reverseOnlyLetters(string s) 
    {
        // 字符串的首尾下标分别为begin和end
        size_t begin = 0, end = s.size()-1;

        // 利用快排的思想,交换左右两边的字母
        // int isalpha ( int c );  // 检查c是否为字母
        while(begin < end)
        {
            while(begin < end && !isalpha(s[begin])) // 重载operator[]
                ++begin;

            while(begin < end && !isalpha(s[end]))
                --end;

            swap(s[begin],s[end]);
            ++begin;
            --end;
        }

        return s;

    }
};
字符串相加

image-20240409221543663

// 解题思路
class Solution 
{
public:
    string addStrings(string num1, string num2)
    {
        // 解题思路,从最低位开始相加,如果和大于10,则进一位
        int end1 = num1.size()-1, end2 = num2.size()-1;
        
        // carry n.进位
        int carry = 0;  
        // 用来储存求出来的字符串的和
        string retstr;  

        // 当两个字符串都迭代结束时,相加才会结束
        while(end1 >=0 || end2 >= 0)
        {
            // 如果字符串还没有结束,则拿到end处的字符,否则赋值为0
            // 字符串中存储的是字符的ASCLL值,因此要减去字符'0',将其变为数字
            // 如:0的ASCLL值为48, 9的ASCLL值为57;因此57-48的值为9
            
            // 从尾部的两个数开始相加
            int val1 = end1 >= 0 ? num1[end1]-'0' : 0;
            int val2 = end2 >= 0 ? num2[end2]-'0' : 0;

            // 拿到最低位的两个数之后,我们再相加,还要加上进位
            int ret = val1 + val2 + carry;
            
            // 如果大于10,进位,并将其个位的值储存到要返回的string中
            carry = ret / 10;
            ret %= 10;
            
            // 对应 string& insert (size_t pos,  size_t n, char c);
            // 字符串中储存的是字符,而不是数字,因此要加上字符'0'
            // 进行头插
            retstr.insert(0, 1, '0' + ret); 
            
            --end1;    //处理完最低位后,减1,再处理前一位
            --end2;
        }

        // 最后一个进位,可能会超出字符串个数,因此要单独处理
        if(carry == 1)
        {
            // 进行头插
            retstr.insert(0, 1, '1');
        }

        return retstr;

    }
};
// 对上面解题思路的优化
class Solution 
{
public:
    string addStrings(string num1, string num2)
    {
        int end1 = num1.size()-1, end2 = num2.size()-1;
        int carry = 0;  
        string retstr; 
        
        // 提前开好空间,防止不断扩容,提高效率
        // 对应 void reserve (size_t n = 0);
        retstr.reserve(max(num1[end1], num2[end2])+1);

        while(end1 >=0 || end2 >= 0)
        { 
            int val1 = end1 >= 0 ? num1[end1]-'0' : 0;
            int val2 = end2 >= 0 ? num2[end2]-'0' : 0;
            int ret = val1 + val2 + carry;
            
            carry = ret / 10;
            ret %= 10;
            
            //使用尾插,效率更高,最后只需要逆置字符串即可
            retstr += ('0'+ret); 
            --end1;
            --end2;
        }

        if(carry == 1)
        {
            retstr += '1';
        }

        // 逆置字符串
        // 对应 void reverse (BidirectionalIterator first, BidirectionalIterator last);
        reverse(retstr.begin(), retstr.end());

        return retstr;
    }
};
字符串中的第一个唯一字符

image-20240409222617278

class Solution {
public:
    int firstUniqChar(string s) 
    {
        //创建一个数组来记录每个字母出现的次数
        // 一共有26个字母
        int count[26] = {0}; 

        // 遍历一遍string,记录字母的出现的个数
        for(char ch : s) //使用范围for来进行遍历
        {
            //字符使用ASCLL值进行存储的,因此减去'a',就可以得到相应的下标
            count[ch - 'a']++; 
        }

        // 再找出第一个不重复的字符
        for(size_t i = 0; i < s.size(); i++)
        {
            if(count[s[i] - 'a'] == 1)
                return i;
        }

        return -1;

    }
};
字符串最后一个单词的长度

image-20240409222810514

#include <iostream>
using namespace std;

int main() 
{
    string str;
    // cin >> str; // cin输入时,遇到换行,或者空格,系统默认为多项输入间隔
    // istream& getline (istream&  is, string& str); 默认换行结束提取
    getline(cin,str);

    // 反向查找
    size_t pos = str.rfind(' ');
    cout << str.size() - 1 - pos << endl;

    return 0;
    
}

3. string类的模拟实现

string的私有成员变量

char* _str;     // _str指向存放字符串的指针
size_t _size;   // 字符串的大小
size_t _capacity;  // 存放字符串空间的容量

string的迭代器

typedef char* iterator;

// 开始迭代器,返回一个迭代器指针,这个指针指向字符串的第一个字符
iterator begin()
{
    return _str;
}

// 结束迭代器,返回一个迭代器指针,这个指针指向字符串的最后一个字符
iterator end()
{
    return _str + _size;
}

string的构造函数

// 不需要传参的构造函数
/*	
string()
{
    _str = new char[1];
    _str[0] = '\0';
    _capacity = _size = 0;
}
*/



// 构造函数
// '\0'   // 字符\0, \0的ASCLL值为0,就相当于是空指针
// "\0"   // 字符串存储了一个字符'\0',且以\0结束
// " "     // 没有存储任何字符,以\0结束
//  也不可以使用nullptr来作为缺省参数,因为strlen读取空指针时会报错

// 使用全缺省,就不需要单独构建无参的构造函数
string(const char* str = "" )  
{
    _size = strlen(str);
    _capacity = _size;
    _str = new char[_capacity + 1];  // 多的一个空间是给\0的

    strcpy(_str, str);
}

string的插入函数

// 在指定位置插入字符
string& insert(size_t pos, char ch)
{
    assert(pos <= _size);

    // 在插入一个新的字符前,需要判断当前空间的容量够不够,如果不够,那么就需要进行扩容
    if (_size == _capacity)
    {
        size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
        reserve(newCapacity);
    }

    // 挪动数据(错误演示)
    /*
    int end = _size;       //当pos为0时,就会 --end就为负数
    
    // pos的类型为size_t,如果end为-1,则end会发生隐式类型转换,小范围往大范围提升
    // 因此需要将pos强转为int类型
    while (end >= (int)pos)  
    {
        _str[end + 1] = _str[end];
        --end;
    }
    */

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

    // 在pos位置进行插入
    _str[pos] = ch;
    ++_size;

    // 返回string对象
    return *this;
}



// 在指定位置插入字符串
string& insert(size_t pos, const char* str)
{
    // 插入字符串之前,先要判断当前存放字符串的空间,容量是否足够
    size_t len = strlen(str);
    if (_size + len > _capacity)
    {
        reserve(_size + len);
    }


    size_t end = _size + len;
    
    // 需要挪动的字符的个数为 _size-pos+1 (加1是因为也要挪动\0)
    // 又因为_size = end - len; 所以需要挪动的字符的个数为 end-pos-len+1 
    while (end > pos + len - 1) 
    {
        _str[end] = _str[end - len];
        --end;
    }

    // 拷贝len个字符,防止str的\0被拷贝,len是str的长度,但不包括\0
    // 如果拷贝了\0,则读取时到\0结束,这样就读取不到后续的字符
    // 从str拷贝len个字符到_str + pos
    strncpy(_str + pos, str, len); 
    _size += len;

    return *this;
}

string删除指定位置的字符串

// 删除pos位置后指定长度的字符
string& erase(size_t pos, size_t len = npos)
{
    assert(pos < _size);

    // 删除字符的数量 len+pos >= _size 也就是将剩余的字符全部删除
    if (len == npos || pos + len >= _size)
    {
        _str[pos] = '\0';
        _size = pos;
    }
    else
    {
        // 将_str + pos + len后的字符,拷贝到str + pos
        // 这样就会将 _str+pos 到 _str+pos+len 之间要删除的字符串给覆盖掉
        strcpy(_str + pos, _str + pos + len);
        _size -= len;
    }

    // 没有拷贝构造的情况下,一定要传引用返回,不然传值返回会调用系统默认的拷贝构造
    // 那样的拷贝就是浅拷贝,这样的话,空间不会重新申请,两个对象指向同一块空间
    // 会对同一块空间,调用两次析构函数,则编译器会报错
    return *this;   
}

string的查找函数

// 寻找字符,并返回字符的位置
size_t find(char ch, size_t pos = 0) const
{
    assert(pos < _size);
    while (pos < _size)
    {
        if (_str[pos] == ch)
        {
            return pos;
        }

        ++pos;
    }

    return npos;
}

string调整字符串大小的函数

// 调整储存数据个数的大小
void resize(size_t n, char ch = '\0')
{
    if (n > _size)
    {
        reserve(n);
        for (size_t i = _size; i < n; ++i)
        {
            _str[i] = ch;
        }

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

string调整存储容量

// 申请存储的空间
void reserve(size_t n)
{
    // 多一个空间来存储\0
    char* tmp = new char[n + 1]; 
    strcpy(tmp, _str);
    
    delete[] _str;              // 释放原空间
    _str = tmp;
    _capacity = n;
}

string尾插字符

// 尾插数据
void push_back(char ch)
{
    // 当空间不够时,进行扩容
    if (_size == _capacity)
    {
        size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
        reserve(newCapacity);
    }

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

string尾插字符串

// 尾插字符串
void append(const char* str)
{
    // 如果容量不够,先进行扩容
    size_t len = strlen(str);
    if (_size + len > _capacity)
    {
        reserve(_size + len);
    }

    // 将str按字节拷贝到_str + _size指向的空间中
    strcpy(_str + _size, str);
    _size += len;
}

string运算符重载

 // 重载operator+= ;(字符)
string& operator+=(char ch)
{
    push_back(ch);
    return *this;
}


// 重载operator+= ;(字符串)
string& operator+=(const char* str)
{
    append(str);
    return *this;
}

string的拷贝构造函数

// 自己写的交换
void swap(string& s)
{
    std::swap(_str, s._str);    // 调用库里面的交换
    std::swap(_size, s._size);
    std::swap(_capacity, s._capacity);
}


// 现代写法(拷贝构造函数)
// s2(s1)
// 必须对s2进行初始化,
// 如果没有初始化,s2._str就是野指针(里面存放的是一个随机值)
// 因此tmp与s2交换之后,tmp无法析构,编译器就会报错
string(const string& s)
    :_str(nullptr)
    , _size(0)
    , _capacity(0)
{
    // 临时创建一个string对象tmp,并使用s._str指针对这个对象进行构造
    // 注:string(const char* str = "" )  这是我们自己实现的构造函数
    // 注:s._str就是一个指向字符串的字符指针,类型为char*
    string tmp(s._str);
    
    //this->swap(tmp);
    swap(tmp);
}



// s2(s1)
// 拷贝构造传统写法
/*
string(const string& s)
{
    _str = new char[s._capacity + 1];
    _capacity = s._capacity;
    _size = s._size;

    strcpy(_str, s._str);
}
*/



// s1 = s3;(对象赋值传统写法)
/*
string& operator=(const string& s)
{
    if (this != &s)
    {
        char* tmp = new char[s._capacity + 1];
        strcpy(tmp, s._str);

        delete[] _str;
        _str = tmp;

        _size = s._size;
        _capacity = s._capacity;
    }

    return *this;
}
*/




// s1 = s3;(对象赋值的现代写法)
string& operator=(const string& s)
{
	if (this != &s)
	{
		// 使用我们自己写的构造函数(构造函数,并传s._str初始化)
		// string tmp(s._str);   
        
        // s就是s3的引用
        // 拷贝构造一个临时对象,再将s1和tmp进行交换
        // 注:使用的拷贝函数,是上述我们自己实现的拷贝函数string(const string& s)
		string tmp(s);       
		swap(tmp);
	}

	return *this;
}




// s1 = s3;(对象赋值的现代写法的进一步优化)
// 传值传参s就是s3的拷贝构造,因此只需要交换s1和s
string& operator=(string s)
{
    swap(s);
    return *this;
}

string的析构函数

// 析构函数
~string()
{
    delete[] _str;
    _str = nullptr;
    _size = _capacity = 0;
}

string的其他函数

// c形式的字符串
const char* c_str() const
{
    return _str;
}

// 存储数据的大小
size_t size() const
{
    return _size;
}

// 容量大小
size_t capacity() const
{
    return _capacity;
}


// 普通对象:可读可写;重载operator[]
char& operator[](size_t pos)
{
    assert(pos < _size);
    return _str[pos];
}

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

string的完整实现以及测试用例

// string.h
namespace qwy
{
	class string
	{
	public:
		// 迭代器
		typedef char* iterator;

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}


        // 不需要传参的构造函数
		/*	string()
			{
			_str = new char[1];
			_str[0] = '\0';
			_capacity = _size = 0;
			}
		*/

        
        
        // 构造函数
		// '\0'   // 字符\0, \0的ASCLL值为0,就相当于是空指针
		// "\0"   // 字符串存储了一个字符'\0',且以\0结束
		// ""     // 没有存储任何字符,以\0结束
		//  也不可以使用nullptr来作为缺省参数,因为strlen读取空指针时会报错
		string(const char* str = "" )  // 使用全缺省,就不需要单独构建无参的构造函数
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];  // 多的一个空间是开给\0的

			strcpy(_str, str);
		}



		// 在指定位置插入字符
		string& insert(size_t pos, char ch)
		{
			assert(pos <= _size);

			if (_size == _capacity)
			{
				size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newCapacity);
			}

			// 挪动数据
			/*int end = _size;       //当pos为0时,就会--end就为负数
			//pos的类型为size_t,如果end为-1,则end会发生隐式类型转换,小范围往大范围提升
			// 因此需要将pos强转为int类型
			while (end >= (int)pos)  
			{
			_str[end + 1] = _str[end];
			--end;
			}*/

			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)
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}

			/*	int end = _size;
				while (end >= (int)pos)
				{
				_str[end + len] = _str[end];
				--end;
				}*/

			size_t end = _size + len;   
			// 需要挪动的字符的个数为 end-pos-len+1(加1是因为也要挪动\0)
			while (end > pos + len - 1) 
			{
				_str[end] = _str[end - len];
				--end;
			}

			// 拷贝len的字符,防止str\0被拷贝,
			// 如果拷贝了\0,则读取时到\0结束,这样就读取不到后续的字符
			strncpy(_str + pos, str, len); 
			_size += len;

			return *this;
		}


        
		// 删除pos位置后指定长度的字符
		string& erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);

			// 删除字符的数量 len+pos >= _size 也就是将剩余的字符全部删除
			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				// 将_str + pos + len后的字符,拷贝到str + pos
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}

			// 没有拷贝构造的情况下,一定要传引用返回,不然传值返回会调用系统默认的拷贝构造
			// 那样的拷贝就是浅拷贝,这样的话,空间不会重新申请,两个对象指向同一块空间
			// 会对同一块空间,调用两次析构函数,则编译器会报错
			return *this;   
		}



        
		// 寻找字符,并返回字符的位置
		size_t find(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;
			}
		}



		// 调整储存数据个数的大小
		void resize(size_t n, char ch = '\0')
		{
			if (n > _size)
			{
				reserve(n);
				for (size_t i = _size; i < n; ++i)
				{
					_str[i] = ch;
				}

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


		// 清除数据
		void clear()
		{
			_size = 0;
			_str[0] = '\0';
		}



		// c形式的字符串
		const char* c_str() const
		{
			return _str;
		}

		// 存储数据的大小
		size_t size() const
		{
			return _size;
		}

        // 容量大小
		size_t capacity() const
		{
			return _capacity;
		}


		// 普通对象:可读可写;重载operator[]
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}

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

        
        
		// 申请存储的空间
		void reserve(size_t n)
		{
			char* tmp = new char[n + 1]; // 多一个空间来存储\0
			strcpy(tmp, _str);
			delete[] _str;              // 释放原空间
			_str = tmp;

			_capacity = n;
		}


        
        
		// 尾插数据
		void push_back(char ch)
		{
			// 当空间不够时,进行扩容
			if (_size == _capacity)
			{
				size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newCapacity);
			}

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

        
        // 重载operator+= ;(字符)
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
        
        
		// 重载operator+= ;(字符串)
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}



		// 自己写的交换
		void swap(string& s)
		{
			std::swap(_str, s._str);    // 调用库里面的交换
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}



		// 现代写法(拷贝构造函数)
		// s2(s1)
		// 必须对s2进行初始化,
		// 如果没有初始化,s2._str就是野指针(里面存放的是一个随机值)
		// 因此tmp与s2交换之后,tmp无法析构,编译器就会报错
		string(const string& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			string tmp(s._str); // 构造函数
			//this->swap(tmp);
			swap(tmp);
		}



		// s2(s1)
		// 拷贝构造传统写法
		/*string(const string& s)
		{
		_str = new char[s._capacity + 1];
		_capacity = s._capacity;
		_size = s._size;

		strcpy(_str, s._str);
		}*/



		// s1 = s3;(对象赋值传统写法)
		/*string& operator=(const string& s)
		{
			if (this != &s)
			{
				char* tmp = new char[s._capacity + 1];
				strcpy(tmp, s._str);

				delete[] _str;
				_str = tmp;

				_size = s._size;
				_capacity = s._capacity;
			}

			return *this;
		}*/




		// s1 = s3;(对象赋值的现代写法)
		//string& operator=(const string& s)
		//{
		//	if (this != &s)
		//	{
		//		//string tmp(s._str);   // 构造函数(构造函数,并传s._str初始化)
		//		string tmp(s);       // 拷贝构造
		//		swap(tmp);
		//	}

		//	return *this;
		//}




		// s1 = s3;(对象赋值的现代写法的进一步优化)
		// 传值传参s就是s3的拷贝构造,因此只需要交换s1和s
		string& operator=(string s)
		{
			swap(s);
			return *this;
		}





		// 析构函数
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}

	private:
		char* _str;
		size_t _size;
		size_t _capacity;

		//特例:const修饰的整数,可以直接给默认值来初始化(静态)
		const static size_t npos = -1;  

		/*
		const static size_t N = 10;
		int a[N];
		*/
		//const static double x;   // 其他类型的数,只能在类外面初始化(静态)
	};



	// 流插入
	ostream& operator<<(ostream& out, const string& s)
	{
		for (size_t i = 0; i < s.size(); ++i)
		{
			out << s[i];
		}

		return out;
	}




	// 流提取
	// 空格默认是多项输入的分隔符,因此对于cin,scanf是提取不到空格的
	// 如果想要拿到空格,就需要使用get()函数
	// std::istream::get
	istream& operator>>(istream& in, string& s)
	{
		s.clear();  // 防止对象中本来就有数据,因此需要先清除

		// 这种写法,如果字符串特别长,就需要不断扩容,会影响运行效率
		//char ch = in.get();
		//while (ch != ' ' && ch != '\n')  // 当遇到空格或者换行时结束
		//{
		//	s += ch;
		//	ch = in.get();
		//}



		// 对上面的算法进行了优化
		char buff[128] = { '\0' };
		size_t i = 0;
		char ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			if (i == 127)   // 因为buff数组要留一个空间来放置\0
			{
				// 满了
				s += buff;
				i = 0;
			}

			buff[i++] = ch;

			ch = in.get();
		}

		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}

		return in;
	}




	/*
	void func(const string& s)
	{
	for (size_t i = 0; i < s.size(); ++i)
	{
		cout << s[i] << " ";
	}
	cout << s.c_str() << endl;

	string::iterator it1 = s.begin();
	while (it1 != s.end())
	{
		(*it1)--;

		++it1;
	}
	cout << s.c_str() << endl;

	for (auto ch : s)      // 范围for的底层实现其实就是迭代器的底层实现
	{
		cout << ch << " ";
	}
	cout << endl;
	}
	*/



    // 测试用例
	void test_string1()
	{
		string s1("hello world");
		cout << s1.c_str() << endl;

		for (size_t i = 0; i < s1.size(); ++i)
		{
			s1[i]++;
		}
		cout << s1.c_str() << endl;

		string::iterator it1 = s1.begin();
		while (it1 != s1.end())
		{
			(*it1)--;

			++it1;
		}
		cout << s1.c_str() << endl;

		for (auto ch : s1)
		{
			cout << ch << " ";
		}
		cout << endl;
	}



	void test_string2()
	{
		string s1("hello");
		s1 += ' ';
		s1 += '!';
		s1 += '!';

		s1 += "world hello world";

		cout << s1.c_str() << endl;

		string s2;
		s2 += 'x';

		cout << s2.c_str() << endl;
	}



	void test_string3()
	{
		string s1("helloworld");
		cout << s1.c_str() << endl;

		s1.insert(5, ' ');
		cout << s1.c_str() << endl;

		s1.insert(0, 'x');
		cout << s1.c_str() << endl;

		string s2("helloworld");
		cout << s2.c_str() << endl;
		s2.insert(5, " + ");
		cout << s2.c_str() << endl;

		s2.insert(0, "hello ");
		cout << s2.c_str() << endl;

		s2.insert(0, "x");
		cout << s2.c_str() << endl;

		string s3;
		s3.insert(0, "");
		cout << s3.c_str() << endl;
	}



	void test_string4()
	{
		string s1("hello hello world");
		s1.erase(0, 6);
		cout << s1.c_str() << endl;

		s1.erase(5);
		cout << s1.c_str() << endl;
	}




	void test_string6()
	{
		string s1("hello world");
		s1.resize(5);
		cout << s1.size() << endl;
		cout << s1.capacity() << endl;
		cout << s1.c_str() << endl << endl;

		string s2("hello world");
		//s2.resize(15);
		s2.resize(15, 'x');
		cout << s2.size() << endl;
		cout << s2.capacity() << endl;
		cout << s2.c_str() << endl << endl;

		string s3("hello world");
		s3.resize(20, 'x');
		cout << s3.size() << endl;
		cout << s3.capacity() << endl;
		cout << s3.c_str() << endl << endl;
	}



	void test_string7()
	{
		// 流插入(编译器的实现方法)
		/*	
		std::string s1("hello world");
		cout << s1 << endl;
        cout << s1.c_str() << endl;

        s1.insert(5, 1, '\0');
        cout << s1.size() << endl;
        cout << s1.capacity() << endl;

        cout << s1 << endl;
        cout << s1.c_str() << endl;
        */


		// 流插入(我们自己实现的)
		string s1("hello world");
		cout << s1 << endl;
		cout << s1.c_str() << endl;

		s1.insert(5, '\0');
		cout << s1.size() << endl;  
		cout << s1.capacity() << endl;

		cout << s1 << endl;        // 对对象打印,是按照数据个数打印
		cout << s1.c_str() << endl;// 按照c字符串打印,是到\0结束

		//
        /*	char ch1, ch2;
			cin >> ch1 >> ch2;
		*/

		cin >> s1;
		cout << s1 << endl;


		string s2;
		cin >> s2;
		cout << s2 << endl;

		string s3;
		cin >> s3;
		cout << s3 << endl;
	}



	void test_string8()
	{
		string s1("hello world");
		string s2(s1);

		cout << s1 << endl;
		cout << s2 << endl;

		string s3("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
		s1 = s3;
		cout << s1 << endl;
		cout << s3 << endl;

		s1.swap(s2);  // 调用自己写的swap
		cout << s1 << endl;
		cout << s2 << endl;
		// 调用库的swap,这个代价太大,影响运行效率;
		// 因为模板需要实例化,也就是s1和s2都需要实例化
		swap(s1, s2); 
		cout << s1 << endl;
		cout << s2 << endl;
		

		// 有了模板之后,内置类型也有构造和析构(兼容模板)
		// int i(10);对内置类型进行构造 --> int i = 10;
		// int j = int(); // 创建一个匿名对象int(),再进行赋值,匿名对象初始化为0
	}

}

test.cpp

#include "string.h"

int main()
{
	qwy::test_string8();

	return 0;
}

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

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

相关文章

RTX RTOS操作实例分析之---邮箱(mailbox)

0 Preface/Foreword 1 邮箱&#xff08;mailbox&#xff09; 1.1 mailbox ID定义 static osMailQId app_mailbox NULL; 1.2 定义mailbox结构体变量 #define osMailQDef(name, queue_sz, type) \ static void *os_mail_p_##name[2]; \ const char mail_##name[] #name; \ con…

mysql双机热备

MySQL双机热备&#xff1a;保障数据库高可用性的关键技术 在当今信息化社会中&#xff0c;数据库作为企业信息系统的核心组成部分&#xff0c;其高可用性和数据安全性至关重要。MySQL作为广泛应用的开源关系型数据库管理系统&#xff0c;其双机热备技术成为保障数据库稳定运行…

4.9QT

完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示”登录成功“&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到其他界面 如果账号和密码不匹配&#xf…

苹果商店审核指南:确保Flutter应用顺利通过审核的关键步骤

引言 Flutter是一款由Google推出的跨平台移动应用开发框架&#xff0c;其强大的性能和流畅的用户体验使其备受开发者青睐。然而&#xff0c;开发一款应用只是第一步&#xff0c;将其成功上架到苹果商店才是实现商业目标的关键一步。本文将详细介绍如何使用Flutter将应用程序上…

数字时代电子账单邮件群发:简便、高效、环保

电子账单已经在许多行业得到广泛应用&#xff0c;通过邮件群发发送电子账单简便、高效、环保&#xff0c;以下是一些通常使用电子账单的行业&#xff1a; 1.银行和金融服务&#xff1a;银行、信用合作社、金融科技公司等机构通常通过电子账单向客户提供账户摘要、交易明细、利息…

Python-VBA函数之旅-bool函数

目录 1、bool函数 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、相关文章&#xff1a; 个人主页&#xff1a;非风V非雨-CSDN博客 bool函数(Boolean Function)用于将给定的值转换为布尔值(True或False)。常见的应用场景有&#xff1a; 1、条件判断&#xff1a;bool()…

每日一题 — 无重复字符的最长子串

解法一&#xff1a;暴力枚举 先固定一个left&#xff0c;让right向右遍历遇到重复的字符&#xff0c;让left加一然后right返回&#xff0c;重新遍历 解法二&#xff1a; 滑动窗口(在解法一的基础上进行优化) 还是先固定一个left在起始位置&#xff0c;让right从起始位置开始向…

使用docker制作Android镜像(实操可用)

一、安装包准备 1、准备jdk 下载地址&#xff1a;Java Downloads | Oracle 注意版本&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 我下载的jdk17&#xff0c;不然后面构建镜像报错&#xff0c;就是版本不对 2、准备安装的工具包 ttps://dev…

Java多线程实战-从零手搓一个简易线程池(四)线程池生命周期状态流转实现

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Java全栈-专栏 &#x1f3f7;️本系列源码仓库&#xff1a;多线程并发编程学习的多个代码片段(github) &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正…

Playwright安装和基本使用(ui/web自动化)

1.简介 Playwright是2021年微软开源的一个项目「playwright-python」。针对 Python 语言的纯自动化工具&#xff0c;它可以通过单个API自动执行 Chromium&#xff0c;Firefox 和 WebKit 浏览器&#xff0c;同时支持以无头模式、有头模式运行。 Playwright&#xff08;Git&…

【Machine Learning系列】带你快速学习十大机器学习算法

前言 机器学习算法是一类用于从数据中学习模式和规律的算法。这些算法可以通过训练样本的输入和输出来推断出模型的参数&#xff0c;然后用于预测新的未知数据。 文章目录 前言机器学习算法1. 线性回归算法 Linear Regression2. 支持向量机算法(Support Vector Machine,SVM)3. …

Centos7.9部署Harbor详细教程

1、前置准备 系统需要已经安装docker、docker-compose… 2、下载Harbor wget https://github.com/goharbor/harbor/releases/download/v2.10.1/harbor-online-installer-v2.10.1.tgztar xvf harbor-offline-installer-v2.10.1.tgzcd harbor3、修改配置文件 cp harbor.yml.t…

CSS滚动条样式修改

前言 目前我们可以通过 CSS伪类 来实现滚动条的样式修改&#xff0c;以下为修改滚动条样式用到的CSS伪类&#xff1a; ::-webkit-scrollbar — 整个滚动条 ::-webkit-scrollbar-button — 滚动条上的按钮 (上下箭头) ::-webkit-scrollbar-thumb — 滚动条上的滚动滑块 ::-web…

CUDA 12.4文档2 内核线程架构

本博客参考官方文档进行介绍&#xff0c;全网仅此一家进行中文翻译&#xff0c;走过路过不要错过。 官方网址&#xff1a;https://docs.nvidia.com/cuda/cuda-c-programming-guide/ 本文档分成多个博客进行介绍&#xff0c;在本人专栏中含有所有内容&#xff1a; https://bl…

网络学习学习笔记

NETEBASE学习笔记 一.VRP系统1.四种视图模式2.基础命令 二.TCP/IP1.五层模型 一.VRP系统 1.四种视图模式 (1)< Huawei > 用户视图 【查看运行状态】 (2)[Huawei] 系统视图 【配置设备的系统参数】 system-view /sys 进入系统视图 CtrlZ/return 直接返回用户视图 (3)[Hua…

AR远程空间标注Vuforia+WebRTC音视频通话和空间标注功能

AR远程空间标注VuforiaWebRTC音视频通话和空间标注功能 视频学习地址&#xff1a;https://www.bilibili.com/video/BV1ZT4y187mG/?vd_sourcefc4b6cdd80b58c93a280fd74c37aadbf

李沐23_LeNet——自学笔记

手写的数字识别 知名度最高的数据集&#xff1a;MNIST 1.训练数据&#xff1a;50000 2.测试数据&#xff1a;50000 3.图像大小&#xff1a;28✖28 4.10类 总结 1.LeNet是早期成功的神经网络 2.先使用卷积层来学习图片空间信息 3.使用全连接层来转换到类别空间 代码实现…

学习记录:bazel和cmake运行终端指令

Bazel和CMake都是用于构建软件项目的工具&#xff0c;但它们之间有一些重要的区别和特点&#xff1a; Bazel&#xff1a; Bazel是由Google开发的构建和测试工具&#xff0c;用于构建大规模的软件项目。它采用一种称为“基于规则”的构建系统&#xff0c;它利用构建规则和依赖关…

Android 属性动画及自定义3D旋转动画

Android 动画框架 其中包括&#xff0c;帧动画、视图动画&#xff08;补间动画&#xff09;、属性动画。 在Android3.0之前&#xff0c;视图动画一家独大&#xff0c;之后属性动画框架被推出。属性动画框架&#xff0c;基本可以实现所有的视图动画效果。 视图动画的效率较高…

第十届蓝桥杯大赛个人赛省赛(软件类) CC++ 研究生组-RSA解密

先把p&#xff0c;q求出来 #include<iostream> #include<cmath> using namespace std; typedef long long ll; int main(){ll n 1001733993063167141LL, sqr sqrt(n);for(ll i 2; i < sqr; i){if(n % i 0){printf("%lld ", i);if(i * i ! n) pri…