C++基础知识(七)之STL算法、智能指针、文件操作、C++异常、断言

news2025/3/4 12:24:57

二十一、STL算法

STL提供了很多处理容器的函数模板,它们的设计是相同的,有以下特点:

1)用迭代器表示需要处理数据的区间。

2)返回迭代器放置处理数据的结果(如果有结果)。

3)接受一个函数对象参数(结构体模板),用于处理数据(如果需要)。

1、函数对象

很多STL算法都使用函数对象,也叫函数符(functor),包括函数名、函数指针和仿函数。

函数符的概念:

1)生成器(generator):不用参数就可以调用的函数符。

2)一元函数(unary function):用一个参数可以调用的函数符。

3)二元函数(binary function):用两个参数可以调用的函数符。

改进的概念:

1)一元谓词(predicate):返回bool值的一元函数。

2)二元谓词(binary predicate):返回bool值的二元函数。

2、预定义的函数对象

STL定义了多个基本的函数符,用于支持STL的算法函数。

包含头文件:#include <functional>

3、算法函数

STL将算法函数分成四组:

1)非修改式序列操作:对区间中的每个元素进行操作,这些操作不修改容器的内容。

2)修改式序列操作:对区间中的每个元素进行操作,这些操作可以容器的内容(可以修改值,也可以修改排列顺序)。

3)排序和相关操作:包括多个排序函数和其它各种函数,如集合操作。

4)通用数字运算:包括将区间的内容累积、计算两个容器的内部乘积、计算小计、计算相邻对象差的函数。通常,这些都是数组的操作特性,因此vector是最有可能使用这些操作的容器。

前三组在头文件#include <algorithm>中,第四组专用于数值数据,在#include <numeric>中。

  1. all_of( ): 检查范围内的所有元素是否都满足给定的条件。如果所有元素都满足条件,则返回true;否则返回false

  2. any_of( ): 检查范围内是否至少有一个元素满足给定的条件。如果至少有一个元素满足条件,则返回true;否则返回false

  3. none_of( ): 检查范围内是否没有任何元素满足给定的条件。如果所有元素都不满足条件,则返回true;否则返回false

  4. for_each( ): 对范围内的每个元素执行指定的操作。这个函数通常用于遍历容器并对每个元素应用某个函数或操作。

  5. find( ): 在范围内查找等于给定值的第一个元素。如果找到,返回指向该元素的迭代器;否则返回范围的结束迭代器。

  6. find_if( ): 在范围内查找第一个满足给定条件的元素。如果找到,返回指向该元素的迭代器;否则返回范围的结束迭代器。

  7. find_if_not( ): 在范围内查找第一个不满足给定条件的元素。如果找到,返回指向该元素的迭代器;否则返回范围的结束迭代器。

  8. find_end( ): 在范围内查找另一个范围的最后一次出现的位置。返回指向找到的子范围第一个元素的迭代器,如果未找到则返回范围的结束迭代器。

  9. find_first_of( ): 在范围内查找属于另一个范围中的任意一个元素的位置。返回指向找到的第一个元素的迭代器,如果未找到则返回范围的结束迭代器。

  10. adjacent_find( ): 在范围内查找一对相邻的元素,这对元素相等或者满足给定的二元谓词。返回指向这对元素的第一个元素的迭代器,如果未找到则返回范围的结束迭代器。

  11. count( ): 计算范围内等于给定值的元素个数。

  12. count_if( ): 计算范围内满足给定条件的元素个数。

  13. mismatch( ): 比较两个范围,返回指向第一个不匹配元素的迭代器对。如果两个范围在某个点上不相等(或者一个范围比另一个短),则返回这对迭代器。如果所有元素都匹配,则返回两个范围的结束迭代器。

  14. equal( ): 检查两个范围是否相等。如果两个范围内的所有对应元素都相等,则返回true;否则返回false

  15. is_permutation( ): 检查两个范围是否包含相同数量的相同元素,但顺序可能不同。如果两个范围是彼此的排列,则返回true;否则返回false

  16. search( ): 在范围内查找另一个范围的第一次出现。返回指向找到的子范围第一个元素的迭代器,如果未找到则返回范围的结束迭代器。

  17. search_n( ): 在范围内查找等于给定值的连续n个元素的第一次出现。返回指向找到的序列第一个元素的迭代器,如果未找到则返回范围的结束迭代器。

二十二、智能指针

1、智能指针unique_ptr

unique_ptr独享它指向的对象,也就是说,同时只有一个unique_ptr指向同一个对象,当这个unique_ptr被销毁时,指向的对象也随即被销毁。

包含头文件:#include <memory>

template <typename T, typename D = default_delete<T>>
class unique_ptr
{
public:
	explicit unique_ptr(pointer p) noexcept;	// 不可用于转换函数。
	~unique_ptr() noexcept;    
	T& operator*() const;            // 重载*操作符。
	T* operator->() const noexcept;  // 重载->操作符。
	unique_ptr(const unique_ptr &) = delete;   // 禁用拷贝构造函数。
	unique_ptr& operator=(const unique_ptr &) = delete;  // 禁用赋值函数。
	unique_ptr(unique_ptr &&) noexcept;	  // 右值引用。
	unique_ptr& operator=(unique_ptr &&) noexcept;  // 右值引用。
	// ...
private:
	pointer ptr;  // 内置的指针。
};

第一个模板参数T:指针指向的数据类型。

第二个模板参数D:指定删除器,缺省用delete释放资源。

测试类AA的定义:

class AA
{
public:
	string m_name;
	AA() { cout << m_name << "调用构造函数AA()。\n"; }
	AA(const string & name) : m_name(name) { cout << "调用构造函数AA("<< m_name << ")。\n"; }
	~AA() { cout << m_name << "调用了析构函数~AA(" << m_name << ")。\n"; }
};

基本用法

1)初始化

方法一:

unique_ptr<AA> p0(new AA("西施"));     // 分配内存并初始化。

方法二:

unique_ptr<AA> p0 = make_unique<AA>("西施");  // C++14标准。
unique_ptr<int> pp1=make_unique<int>();     // 数据类型为int。
unique_ptr<AA> pp2 = make_unique<AA>();    // 数据类型为AA,默认构造函数。
unique_ptr<AA> pp3 = make_unique<AA>("西施");  // 数据类型为AA,一个参数的构造函数。
unique_ptr<AA> pp4 = make_unique<AA>("西施",8); // 数据类型为AA,两个参数的构造函数。

方法三(不推荐):

AA* p = new AA("西施");
unique_ptr<AA> p0(p);          // 用已存在的地址初始化。

2)使用方法

  • 智能指针重载了*和->操作符,可以像使用指针一样使用unique_ptr。

  • 不支持普通的拷贝和赋值。

 AA* p = new AA("西施");
 unique_ptr<AA> pu2 = p;        // 错误,不能把普通指针直接赋给智能指针。
 unique_ptr<AA> pu3 = new AA("西施"); // 错误,不能把普通指针直接赋给智能指针。
 unique_ptr<AA> pu2 = pu1;      // 错误,不能用其它unique_ptr拷贝构造。
 unique_ptr<AA> pu3;
 pu3 = pu1;               // 错误,不能用=对unique_ptr进行赋值。
  • 不要用同一个裸指针初始化多个unique_ptr对象。

  • get()方法返回裸指针。

  • 不要用unique_ptr管理不是new分配的内存。

3)用于函数的参数

  • 传引用(不能传值,因为unique_ptr没有拷贝构造函数)。

  • 裸指针。

4)不支持指针的运算(+、-、++、--)

更多技巧

1)将一个unique_ptr赋给另一个时,如果源unique_ptr是一个临时右值,编译器允许这样做;如果源unique_ptr将存在一段时间,编译器禁止这样做。一般用于函数的返回值。

unique_ptr<AA> p0;
p0 = unique_ptr<AA>(new AA ("西瓜"));

2)用nullptr给unique_ptr赋值将释放对象,空的unique_ptr==nullptr。

3)release()释放对原始指针的控制权,将unique_ptr置为空,返回裸指针。(可用于把unique_ptr传递给子函数,子函数将负责释放对象)

4)std::move()可以转移对原始指针的控制权。(可用于把unique_ptr传递给子函数,子函数形参也是unique_ptr)

5)reset()释放对象。

void reset(T * _ptr= (T *) nullptr);
pp.reset();        // 释放pp对象指向的资源对象。
pp.reset(nullptr);  // 释放pp对象指向的资源对象
pp.reset(new AA("bbb"));  // 释放pp指向的资源对象,同时指向新的对象。

6)swap()交换两个unique_ptr的控制权。

void swap(unique_ptr<T> &_Right);

7)unique_ptr也可象普通指针那样,当指向一个类继承体系的基类对象时,也具有多态性质,如同使用裸指针管理基类对象和派生类对象那样。

8)unique_ptr不是绝对安全,如果程序中调用exit()退出,全局的unique_ptr可以自动释放,但局部的unique_ptr无法释放。

9)unique_ptr提供了支持数组的具体化版本。

数组版本的unique_ptr,重载了操作符[],操作符[]返回的是引用,可以作为左值使用。

// unique_ptr<int[]> parr1(new int[3]);          // 不指定初始值。
unique_ptr<int[]> parr1(new int[3]{ 33,22,11 });  // 指定初始值。
cout << "parr1[0]=" << parr1[0] << endl;
cout << "parr1[1]=" << parr1[1] << endl;
cout << "parr1[2]=" << parr1[2] << endl;

unique_ptr<AA[]> parr2(new AA[3]{string("西施"), string("冰冰"), string("幂幂")});
cout << "parr2[0].m_name=" << parr2[0].m_name << endl;
cout << "parr2[1].m_name=" << parr2[1].m_name << endl;
cout << "parr2[2].m_name=" << parr2[2].m_name << endl;

示例1:

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

class AA
{
public:
	string m_name;
	AA() { cout << m_name << "调用构造函数AA()。\n"; }
	AA(const string & name) : m_name(name) { cout << "调用构造函数AA("<< m_name << ")。\n"; }
	~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
};

// 函数func1()需要一个指针,但不对这个指针负责。
void func1(const AA* a) {
	cout << a->m_name << endl;
}

// 函数func2()需要一个指针,并且会对这个指针负责。
void func2(AA* a) {
	cout << a->m_name << endl;
	delete a;
}

// 函数func3()需要一个unique_ptr,不会对这个unique_ptr负责。
void func3(const unique_ptr<AA> &a) {
	cout << a->m_name << endl;
}

// 函数func4()需要一个unique_ptr,并且会对这个unique_ptr负责。
void func4(unique_ptr<AA> a) {
	cout << a->m_name << endl;
}

int main()
{
	unique_ptr<AA> pu(new AA("西施"));

	cout << "开始调用函数。\n";
	//func1(pu.get());        // 函数func1()需要一个指针,但不对这个指针负责。
	//func2(pu.release());  // 函数func2()需要一个指针,并且会对这个指针负责。
	//func3(pu);                // 函数func3()需要一个unique_ptr,不会对这个unique_ptr负责。
	func4(move(pu));     // 函数func4()需要一个unique_ptr,并且会对这个unique_ptr负责。
	cout << "调用函数完成。\n";

	if (pu == nullptr) cout << "pu是空指针。\n";
}

调用构造函数AA(西施)。
开始调用函数。
西施
调用了析构函数~AA(西施)。
调用函数完成。
pu是空指针。

示例2:

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

class AA
{
public:
	string m_name;
	AA() { cout << m_name << "调用构造函数AA()。\n"; }
	AA(const string & name) : m_name(name) { cout << "调用构造函数AA("<< m_name << ")。\n"; }
	~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
};

int main()
{
	//AA* parr1 = new AA[2];   // 普通指针数组。
	AA* parr1 = new AA[2]{ string("西施"), string("冰冰") };
	//parr1[0].m_name = "西施1";
	//cout << "parr1[0].m_name=" << parr1[0].m_name << endl;
	//parr1[1].m_name = "西施2";
	//cout << "parr1[1].m_name=" << parr1[1].m_name << endl;
	//delete [] parr1;
	
	unique_ptr<AA[]> parr2(new AA[2]);   // unique_ptr数组。
	//unique_ptr<AA[]> parr2(new AA[2]{ string("西施"), string("冰冰") });
	parr2[0].m_name = "西施1";
	cout << "parr2[0].m_name=" << parr2[0].m_name << endl;
	parr2[1].m_name = "西施2";
	cout << "parr2[1].m_name=" << parr2[1].m_name << endl;
}

调用构造函数AA()。
调用构造函数AA()。
parr2[0].m_name=西施1
parr2[1].m_name=西施2
调用了析构函数~AA(西施2)。
调用了析构函数~AA(西施1)。

2、智能指针shared_ptr

shared_ptr共享它指向的对象,多个shared_ptr可以指向(关联)相同的对象,在内部采用计数机制来实现。

当新的shared_ptr与对象关联时,引用计数增加1。

当shared_ptr超出作用域时,引用计数减1。当引用计数变为0时,则表示没有任何shared_ptr与对象关联,则释放该对象。

基本用法

shared_ptr的构造函数也是explicit,但是,没有删除拷贝构造函数和赋值函数。

1)初始化

方法一:

shared_ptr<AA> p0(new AA("西施"));     // 分配内存并初始化。

方法二:

shared_ptr<AA> p0 = make_shared<AA>("西施");  // C++11标准,效率更高。
shared_ptr<int> pp1=make_shared<int>();         // 数据类型为int。
shared_ptr<AA> pp2 = make_shared<AA>();       // 数据类型为AA,默认构造函数。
shared_ptr<AA> pp3 = make_shared<AA>("西施");  // 数据类型为AA,一个参数的构造函数。
shared_ptr<AA> pp4 = make_shared<AA>("西施",8); // 数据类型为AA,两个参数的构造函数。

方法三:

AA* p = new AA("西施");
shared_ptr<AA> p0(p);                  // 用已存在的地址初始化。

方法四:

shared_ptr<AA> p0(new AA("西施")); 
shared_ptr<AA> p1(p0);                 // 用已存在的shared_ptr初始化,计数加1。
shared_ptr<AA> p1=p0;                 // 用已存在的shared_ptr初始化,计数加1。

2)使用方法

  • 智能指针重载了*和->操作符,可以像使用指针一样使用shared_ptr。

  • use_count()方法返回引用计数器的值。

  • unique()方法,如果use_count()为1,返回true,否则返回false。

  • shared_ptr支持赋值,左值的shared_ptr的计数器将减1,右值shared_ptr的计算器将加1。

  • get()方法返回裸指针。

  • 不要用同一个裸指针初始化多个shared_ptr。

  • 不要用shared_ptr管理不是new分配的内存。

3)用于函数的参数

与unique_ptr的原理相同。

4)不支持指针的运算(+、-、++、--)

更多细节

1)将一个unique_ptr赋给另一个时,如果源unique_ptr是一个临时右值,编译器允许这样做;如果源unique_ptr将存在一段时间,编译器禁止这样做。一般用于函数的返回值。

2)用nullptr给shared_ptr赋值将把计数减1,如果计数为0,将释放对象,空的shared_ptr==nullptr。

3)release()释放对原始指针的控制权,将unique_ptr置为空,返回裸指针。

4)std::move()可以转移对原始指针的控制权。还可以将unique_ptr转移成shared_ptr。

5)reset()改变与资源的关联关系。

pp.reset();     // 解除与资源的关系,资源的引用计数减1。
pp. reset(new AA("bbb"));  // 解除与资源的关系,资源的引用计数减1。关联新资源。

6)swap()交换两个shared_ptr的控制权。

void swap(shared_ptr<T> &_Right);

7)shared_ptr也可象普通指针那样,当指向一个类继承体系的基类对象时,也具有多态性质,如同使用裸指针管理基类对象和派生类对象那样。

8)shared_ptr不是绝对安全,如果程序中调用exit()退出,全局的shared_ptr可以自动释放,但局部的shared_ptr无法释放。

9)shared_ptr提供了支持数组的具体化版本。

数组版本的shared_ptr,重载了操作符[],操作符[]返回的是引用,可以作为左值使用。

10)shared_ptr的线程安全性:

  • shared_ptr的引用计数本身是线程安全(引用计数是原子操作)。

  • 多个线程同时读同一个shared_ptr对象是线程安全的。

  • 如果是多个线程对同一个shared_ptr对象进行读和写,则需要加锁。

  • 多线程读写shared_ptr所指向的同一个对象,不管是相同的shared_ptr对象,还是不同的shared_ptr对象,也需要加锁保护。

11)如果unique_ptr能解决问题,就不要用shared_ptr。unique_ptr的效率更高,占用的资源更少。

示例1:

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

class AA
{
public:
	string m_name;
	AA() { cout << m_name << "调用构造函数AA()。\n"; }
	AA(const string & name) : m_name(name) { cout << "调用构造函数AA("<< m_name << ")。\n"; }
	~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
};

int main()
{
	shared_ptr<AA> pa0(new AA("西施a"));     // 初始化资源西施a。
	shared_ptr<AA> pa1 = pa0;                       // 用已存在的shared_ptr拷贝构造,计数加1。
	shared_ptr<AA> pa2 = pa0;                       // 用已存在的shared_ptr拷贝构造,计数加1。
	cout << "pa0.use_count()=" << pa0.use_count() << endl;   // 值为3。

	shared_ptr<AA> pb0(new AA("西施b"));    // 初始化资源西施b。
	shared_ptr<AA> pb1 = pb0;                      // 用已存在的shared_ptr拷贝构造,计数加1。
	cout << "pb0.use_count()=" << pb0.use_count() << endl;   // 值为2。

	pb1 = pa1;      // 资源西施a的引用加1,资源西施b的引用减1。
	pb0 = pa1;      // 资源西施a的引用加1,资源西施b的引用成了0,将被释放。

	cout << "pa0.use_count()=" << pa0.use_count() << endl;   // 值为5。
	cout << "pb0.use_count()=" << pb0.use_count() << endl;   // 值为5。
}

调用构造函数AA(西施a)。
pa0.use_count()=3
调用构造函数AA(西施b)。
pb0.use_count()=2
调用了析构函数~AA(西施b)。
pa0.use_count()=5
pb0.use_count()=5
调用了析构函数~AA(西施a)。

3、智能指针的删除器

在默认情况下,智能指针过期的时候,用delete原始指针; 释放它管理的资源。

程序员可以自定义删除器,改变智能指针释放资源的行为。

删除器可以是全局函数、仿函数和Lambda表达式,形参为原始指针。

示例:

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

class AA
{
public:
	string m_name;
	AA() { cout << m_name << "调用构造函数AA()。\n"; }
	AA(const string & name) : m_name(name) { cout << "调用构造函数AA("<< m_name << ")。\n"; }
	~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
};

void deletefunc(AA* a) {    // 删除器,普通函数。
	cout << "自定义删除器(全局函数)。\n";
	delete a;
}

struct deleteclass               // 删除器,仿函数。
{
	void operator()(AA* a) {
		cout << "自定义删除器(仿函数)。\n";
		delete a;
	}
};

auto deleterlamb = [](AA* a) {   // 删除器,Lambda表达式。
	cout << "自定义删除器(Lambda)。\n";
	delete a;
};

int main()
{
	shared_ptr<AA> pa1(new AA("西施a"), deletefunc);
	//shared_ptr<AA> pa2(new AA("西施b"), deleteclass());
	//shared_ptr<AA> pa3(new AA("西施c"), deleterlamb);
	
	//unique_ptr<AA,decltype(deletefunc)*> pu1(new AA("西施1"), deletefunc);
    // unique_ptr<AA, void (*)(AA*)> pu0(new AA("西施1"), deletefunc);
	//unique_ptr<AA, deleteclass> pu2(new AA("西施2"), deleteclass());
	//unique_ptr<AA, decltype(deleterlamb)> pu3(new AA("西施3"), deleterlamb);
}

调用构造函数AA(西施a)。
自定义删除器(全局函数)。
调用了析构函数~AA(西施a)。

4、智能指针weak_ptr

shared_ptr存在的问题

shared_ptr内部维护了一个共享的引用计数器,多个shared_ptr可以指向同一个资源。

如果出现了循环引用的情况,引用计数永远无法归0,资源不会被释放。

示例:

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

class BB;

class AA
{
public:
	string m_name;
	AA() { cout << m_name << "调用构造函数AA()。\n"; }
	AA(const string & name) : m_name(name) { cout << "调用构造函数AA("<< m_name << ")。\n"; }
	~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
	shared_ptr<BB> m_p;
};

class BB
{
public:
	string m_name;
	BB() { cout << m_name << "调用构造函数BB()。\n"; }
	BB(const string& name) : m_name(name) { cout << "调用构造函数BB(" << m_name << ")。\n"; }
	~BB() { cout << "调用了析构函数~BB(" << m_name << ")。\n"; }
	shared_ptr<AA> m_p;
};

int main()
{
	shared_ptr<AA> pa = make_shared<AA>("西施a");
	shared_ptr<BB> pb = make_shared<BB>("西施b");
	
	pa-> m_p = pb;
	pb->m_p = pa;
}

调用构造函数AA(西施a)。

调用构造函数BB(西施b)。

weak_ptr是什么

weak_ptr 是为了配合shared_ptr而引入的,它指向一个由shared_ptr管理的资源但不影响资源的生命周期。也就是说,将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。

不论是否有weak_ptr指向,如果最后一个指向资源的shared_ptr被销毁,资源就会被释放。

weak_ptr更像是shared_ptr的助手而不是智能指针。

示例:

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

class BB;

class AA
{
public:
	string m_name;
	AA() { cout << m_name << "调用构造函数AA()。\n"; }
	AA(const string & name) : m_name(name) { cout << "调用构造函数AA("<< m_name << ")。\n"; }
	~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
	weak_ptr<BB> m_p;
};

class BB
{
public:
	string m_name;
	BB() { cout << m_name << "调用构造函数BB()。\n"; }
	BB(const string& name) : m_name(name) { cout << "调用构造函数BB(" << m_name << ")。\n"; }
	~BB() { cout << "调用了析构函数~BB(" << m_name << ")。\n"; }
	weak_ptr<AA> m_p;
};

int main()
{
	shared_ptr<AA> pa = make_shared<AA>("西施a");
	shared_ptr<BB> pb = make_shared<BB>("西施b");
	
	cout << "pa.use_count()=" << pa.use_count() << endl;
	cout << "pb.use_count()=" << pb.use_count() << endl;

	pa->m_p = pb;
	pb->m_p = pa;

	cout << "pa.use_count()=" << pa.use_count() << endl;
	cout << "pb.use_count()=" << pb.use_count() << endl;
}

调用构造函数AA(西施a)。
调用构造函数BB(西施b)。
pa.use_count()=1
pb.use_count()=1
pa.use_count()=1
pb.use_count()=1
调用了析构函数~BB(西施b)。
调用了析构函数~AA(西施a)。

如何使用weak_ptr

weak_ptr没有重载 ->和 *操作符,不能直接访问资源。

有以下成员函数:

1)operator=(); // 把shared_ptr或weak_ptr赋值给weak_ptr。

2)expired(); // 判断它指资源是否已过期(已经被销毁)。

3)lock(); // 返回shared_ptr,如果资源已过期,返回空的shared_ptr。

4)reset(); // 将当前weak_ptr指针置为空。

5)swap(); // 交换。

weak_ptr不控制对象的生命周期,但是,它知道对象是否还活着。

用lock()函数把它可以提升为shared_ptr,如果对象还活着,返回有效的shared_ptr,如果对象已经死了,提升会失败,返回一个空的shared_ptr。

提升的行为(lock())是线程安全的。

示例:

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

class BB;

class AA
{
public:
	string m_name;
	AA() { cout << m_name << "调用构造函数AA()。\n"; }
	AA(const string& name) : m_name(name) { cout << "调用构造函数AA(" << m_name << ")。\n"; }
	~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
	weak_ptr<BB> m_p;
};

class BB
{
public:
	string m_name;
	BB() { cout << m_name << "调用构造函数BB()。\n"; }
	BB(const string& name) : m_name(name) { cout << "调用构造函数BB(" << m_name << ")。\n"; }
	~BB() { cout << "调用了析构函数~BB(" << m_name << ")。\n"; }
	weak_ptr<AA> m_p;
};

int main()
{
	shared_ptr<AA> pa = make_shared<AA>("西施a");

	{
		shared_ptr<BB> pb = make_shared<BB>("西施b");

		pa->m_p = pb;
		pb->m_p = pa;

		shared_ptr<BB> pp = pa->m_p.lock();            // 把weak_ptr提升为shared_ptr。
		if (pp == nullptr)
			cout << "语句块内部:pa->m_p已过期。\n";
		else
			cout << "语句块内部:pp->m_name=" << pp->m_name << endl;
	}

	shared_ptr<BB> pp = pa->m_p.lock();            // 把weak_ptr提升为shared_ptr。
	if (pp == nullptr)
		cout << "语句块外部:pa->m_p已过期。\n";
	else
		cout << "语句块外部:pp->m_name=" << pp->m_name << endl;
}

调用构造函数AA(西施a)。
调用构造函数BB(西施b)。
语句块内部:pp->m_name=西施b
调用了析构函数~BB(西施b)。
语句块外部:pa->m_p已过期。
调用了析构函数~AA(西施a)。

二十三、C++文件操作

1、文件操作-写入文本文件

文本文件一般以行的形式组织数据。

包含头文件:#include <fstream>

类:ofstream(output file stream)

ofstream打开文件的模式(方式):

对于ofstream,不管用哪种模式打开文件,如果文件不存在,都会创建文件。

ios::out 缺省值:会截断文件内容。

ios::trunc 截断文件内容。(truncate)

ios::app 不截断文件内容,只在文件未尾追加文件。(append)

示例:

#include <iostream>
#include <fstream>  // ofstream类需要包含的头文件。
using  namespace std;

int main()
{
	// 文件名一般用全路径,书写的方法如下:
	//  1)"D:\data\txt\test.txt"       // 错误。
	//  2)R"(D:\data\txt\test.txt)"   // 原始字面量,C++11标准。
	//  3)"D:\\data\\txt\\test.txt"   // 转义字符。
	//  4)"D:/tata/txt/test.txt"        // 把斜线反着写。
	//  5)"/data/txt/test.txt"          //  Linux系统采用的方法。
	string filename = R"(D:\data\txt\test.txt)";
	//char    filename[] = R"(D:\data\txt\test.txt)";

	// 创建文件输出流对象,打开文件,如果文件不存在,则创建它。
	// ios::out     		缺省值:会截断文件内容。
	// ios::trunc  		截断文件内容。(truncate)
	// ios::app   			不截断文件内容,只在文件未尾追加文件。(append)
	//ofstream fout(filename);
	//ofstream fout(filename, ios::out);
	//ofstream fout(filename, ios::trunc);
	//ofstream fout(filename, ios::app);
	
	ofstream fout;
	fout.open(filename,ios::app);

	// 判断打开文件是否成功。
	// 失败的原因主要有:1)目录不存在;2)磁盘空间已满;3)没有权限,Linux平台下很常见。
	if (fout.is_open() == false)
	{
		cout << "打开文件" << filename << "失败。\n";  return 0;
	}

	// 向文件中写入数据。
	fout << "西施|19|极漂亮\n";
	fout << "冰冰|22|漂亮\n";
	fout << "幂幂|25|一般\n";

	fout.close();	   // 关闭文件,fout对象失效前会自动调用close()。

	cout << "操作文件完成。\n";
}

2、文件操作-读取文本文件

包含头文件:#include <fstream>

类:ifstream

ifstream打开文件的模式(方式):

对于ifstream,如果文件不存在,则打开文件失败。

ios::in 缺省值。

示例:

#include <iostream>
#include <fstream>  // ifstream类需要包含的头文件。
#include <string>     // getline()函数需要包含的头文件。
using  namespace std;

int main()
{
	// 文件名一般用全路径,书写的方法如下:
	//  1)"D:\data\txt\test.txt"       // 错误。
	//  2)R"(D:\data\txt\test.txt)"   // 原始字面量,C++11标准。
	//  3)"D:\\data\\txt\\test.txt"   // 转义字符。
	//  4)"D:/tata/txt/test.txt"        // 把斜线反着写。
	//  5)"/data/txt/test.txt"          //  Linux系统采用的方法。
	string filename = R"(D:\data\txt\test.txt)";
	//char    filename[] = R"(D:\data\txt\test.txt)";

	// 创建文件输入流对象,打开文件,如果文件不存在,则打开文件失败。。
	// ios::in     			缺省值。
	//ifstream fin(filename);
	//ifstream fin(filename, ios::in);
	
	ifstream fin;
	fin.open(filename,ios::in);

	// 判断打开文件是否成功。
	// 失败的原因主要有:1)目录不存在;2)文件不存在;3)没有权限,Linux平台下很常见。
	if (fin.is_open() == false)
	{
		cout << "打开文件" << filename << "失败。\n";  return 0;
	}

	 第一种方法。
	//string buffer;  // 用于存放从文件中读取的内容。
	 文本文件一般以行的方式组织数据。
	//while (getline(fin, buffer))
	//{
	//	cout << buffer << endl;
	//}

	 第二种方法。
	//char buffer[16];   // 存放从文件中读取的内容。
	 注意:如果采用ifstream.getline(),一定要保证缓冲区足够大。
	//while (fin.getline(buffer, 15))
	//{
	//	cout << buffer << endl;
	//}

	// 第三种方法。
	string buffer;
	while (fin >> buffer)
	{
		cout << buffer << endl;
	}

	fin.close();	   // 关闭文件,fin对象失效前会自动调用close()。

	cout << "操作文件完成。\n";
}

3、文件操作-写入二进制文件

二进制文件以数据块的形式组织数据,把内存中的数据直接写入文件。

包含头文件:#include <fstream>

类:ofstream(output file stream)

ofstream打开文件的模式(方式):

对于ofstream,不管用哪种模式打开文件,如果文件不存在,都会创建文件。

ios::out 缺省值:会截断文件内容。

ios::trunc 截断文件内容。(truncate)

ios::app 不截断文件内容,只在文件未尾追加文件。(append)

ios::binary 以二进制方式打开文件。

操作文本文件和二进制文件的一些细节:

1)在windows平台下,文本文件的换行标志是"\r\n"。

2)在linux平台下,文本文件的换行标志是"\n"。

3)在windows平台下,如果以文本方式打开文件,写入数据的时候,系统会将"\n"转换成"\r\n";读取数据的时候,系统会将"\r\n"转换成"\n"。 如果以二进制方式打开文件,写和读都不会进行转换。

4)在Linux平台下,以文本或二进制方式打开文件,系统不会做任何转换。

5)以文本方式读取文件的时候,遇到换行符停止,读入的内容中没有换行符;以二制方式读取文件的时候,遇到换行符不会停止,读入的内容中会包含换行符(换行符被视为数据)。

6)在实际开发中,从兼容和语义考虑,一般:

a)以文本模式打开文本文件,用行的方法操作它;

b)以二进制模式打开二进制文件,用数据块的方法操作它;

c)以二进制模式打开文本文件和二进制文件,用数据块的方法操作它,这种情况表示不关心数据的内容。(例如复制文件和传输文件)

d)不要以文本模式打开二进制文件,也不要用行的方法操作二进制文件,可能会破坏二进制数据文件的格式,也没有必要。(因为二进制文件中的某字节的取值可能是换行符,但它的意义并不是换行,可能是整数n个字节中的某个字节)

示例:

#include <iostream>
#include <fstream>  // ofstream类需要包含的头文件。
using  namespace std;

int main()
{
	// 文件名一般用全路径,书写的方法如下:
	//  1)"D:\data\bin\test.dat"       // 错误。
	//  2)R"(D:\data\bin\test.dat)"   // 原始字面量,C++11标准。
	//  3)"D:\\data\\bin\\test.dat"   // 转义字符。
	//  4)"D:/tata/bin/test.dat"        // 把斜线反着写。
	//  5)"/data/bin/test.dat"          //  Linux系统采用的方法。
	string filename = R"(D:\data\bin\test.dat)";
	//char    filename[] = R"(D:\data\bin\test.dat)";

	// 创建文件输出流对象,打开文件,如果文件不存在,则创建它。
	// ios::out     		缺省值:会截断文件内容。
	// ios::trunc  		截断文件内容。(truncate)
	// ios::app   			不截断文件内容,只在文件未尾追加文件。(append)
	// ios::binary   		以二进制方式打开文件。
	//ofstream fout(filename, ios::binary);
	//ofstream fout(filename, ios::out | ios::binary);
	//ofstream fout(filename, ios::trunc | ios::binary);
	//ofstream fout(filename, ios::app | ios::binary);

	ofstream fout;
	fout.open(filename, ios::app | ios::binary);

	// 判断打开文件是否成功。
	// 失败的原因主要有:1)目录不存在;2)磁盘空间已满;3)没有权限,Linux平台下很常见。
	if (fout.is_open() == false)
	{
		cout << "打开文件" << filename << "失败。\n";  return 0;
	}

	// 向文件中写入数据。
	struct st_girl {               // 超女结构体。
		char name[31];         // 姓名。
		int    no;                    // 编号。   
		char memo[301];      // 备注。
		double weight;         // 体重。
	}girl;
	girl = { "西施",3,"中国历史第一美女。" ,45.8 };
	fout.write((const char *)& girl, sizeof(st_girl));   // 写入第一块数据。
	girl = { "冰冰",8,"也是个大美女哦。",55.2};
	fout.write((const char*)&girl, sizeof(st_girl));     // 写入第二块数据。

	fout.close();	   // 关闭文件,fout对象失效前会自动调用close()。

	cout << "操作文件完成。\n";
}

4、文件操作-读取二进制文件

包含头文件:#include <fstream>

类:ifstream

ifstream打开文件的模式(方式):

对于ifstream,如果文件不存在,则打开文件失败。

ios::in 缺省值。

ios::binary 以二进制方式打开文件。

示例:

#include <iostream>
#include <fstream>  // ifstream类需要包含的头文件。
using  namespace std;

int main()
{
	// 文件名一般用全路径,书写的方法如下:
	//  1)"D:\data\bin\test.dat"       // 错误。
	//  2)R"(D:\data\bin\test.dat)"   // 原始字面量,C++11标准。
	//  3)"D:\\data\\bin\\test.dat"   // 转义字符。
	//  4)"D:/tata/bin/test.dat"        // 把斜线反着写。
	//  5)"/data/bin/test.dat"          //  Linux系统采用的方法。
	string filename = R"(D:\data\bin\test.dat)";
	//char    filename[] = R"(D:\data\bin\test.dat)";

	// 创建文件输入流对象,打开文件,如果文件不存在,则打开文件失败。。
	// ios::in     			缺省值。
	// ios::binary   		以二进制方式打开文件。
	//ifstream fin(filename , ios::binary);
	//ifstream fin(filename , ios::in | ios::binary);

	ifstream fin;
	fin.open(filename, ios::in | ios::binary);

	// 判断打开文件是否成功。
	// 失败的原因主要有:1)目录不存在;2)文件不存在;3)没有权限,Linux平台下很常见。
	if (fin.is_open() == false)
	{
		cout << "打开文件" << filename << "失败。\n";  return 0;
	}

	// 二进制文件以数据块(数据类型)的形式组织数据。
	struct st_girl {               // 超女结构体。
		char name[31];         // 姓名。
		int    no;                    // 编号。   
		char memo[301];      // 备注。
		double weight;         // 体重。
	}girl;
	while (fin.read((char*)&girl, sizeof(girl)))
	{
		cout << "name=" << girl.name << ",no=" << girl.no << 
			",memo=" << girl.memo << ",weight=" << girl.weight << endl;
	}

	fin.close();	   // 关闭文件,fin对象失效前会自动调用close()。

	cout << "操作文件完成。\n";
}

5、文件操作-随机存取

1)fstream类

fstream类既可以读文本/二进制文件,也可以写文本/二进制文件。

fstream类的缺省模式是ios::in | ios::out,如果文件不存在,则创建文件;但是,不会清空文件原有的内容。

普遍的做法是:

1)如果只想写入数据,用ofstream;如果只想读取数据,用ifstream;如果想写和读数据,用fstream,这种情况不多见。不同的类体现不同的语义。

2)在Linux平台下,文件的写和读有严格的权限控制。(需要的权限越少越好)

2)文件的位置指针

对文件进行读/写操作时,文件的位置指针指向当前文件读/写的位置。

很多资料用“文件读指针的位置”和“文件写指针的位置”,容易误导人。不管用哪个类操作文件,文件的位置指针只有一个。

1)获取文件位置指针

ofstream类的成员函数是tellp();ifstream类的成员函数是tellg();fstream类两个都有,效果相同。

std::streampos tellp();
std::streampos tellg();

2)移动文件位置指针

ofstream类的函数是seekp();ifstream类的函数是seekg();fstream类两个都有,效果相同。

方法一:

std::istream & seekg(std::streampos _Pos);  
fin.seekg(128);  // 把文件指针移到第128字节。
fin.seekp(128);  // 把文件指针移到第128字节。
fin.seekg(ios::beg) // 把文件指针移动文件的开始。
fin.seekp(ios::end) // 把文件指针移动文件的结尾。

方法二:

std::istream & seekg(std::streamoff _Off,std::ios::seekdir _Way);

在ios中定义的枚举类型:

enum seek_dir {beg, cur, end};  // beg-文件的起始位置;cur-文件的当前位置;end-文件的结尾位置。
fin.seekg(30, ios::beg);   // 从文件开始的位置往后移30字节。
fin.seekg(-5, ios::cur);   // 从当前位置往前移5字节。
fin.seekg( 8, ios::cur);   // 从当前位置往后移8字节。
fin.seekg(-10, ios::end);  // 从文件结尾的位置往前移10字节。

3)随机存取

随机存取是指直接移动文件的位置指针,在指定位置读取/写入数据。

示例:

#include <iostream>
#include <fstream>  // fstream类需要包含的头文件。
using  namespace std;

int main()
{
	string filename = R"(D:\data\txt\test.txt)";
	
	fstream fs;
	fs.open(filename, ios::in | ios::out);

	if (fs.is_open() == false)
	{
		cout << "打开文件" << filename << "失败。\n";  return 0;
	}
	
	fs.seekg(26);    // 把文件位置指针移动到第26字节处。

	fs << "我是一只傻傻的小菜鸟。\n"; 

	/*string buffer; 
	while (fs >> buffer)
	{
		cout << buffer << endl;
	}*/

	fs.close();	   // 关闭文件,fs对象失效前会自动调用close()。

	cout << "操作文件完成。\n";
}

6、文件操作-缓冲区及流状态

1)文件缓冲区

文件缓冲区(缓存)是系统预留的内存空间,用于存放输入或输出的数据。

根据输出和输入流,分为输出缓冲区和输入缓冲区。

注意,在C++中,每打开一个文件,系统就会为它分配缓冲区。不同的流,缓冲区是独立的

程序员不用关心输入缓冲区,只关心输出缓冲区就行了。

在缺省模式下,输出缓冲区中的数据满了才把数据写入磁盘,但是,这种模式不一定能满足业务的需求。

输出缓冲区的操作:

1)flush()成员函数

刷新缓冲区,把缓冲区中的内容写入磁盘文件。

2)endl

换行,然后刷新缓冲区。

3)unitbuf

fout << unitbuf;

设置fout输出流,在每次操作之后自动刷新缓冲区。

4)nounitbuf

fout << nounitbuf;

设置fout输出流,让fout回到缺省的缓冲方式。

2)流状态

流状态有三个:eofbit、badbit和failbit,取值:1-设置;或0-清除。

当三个流状成都为0时,表示一切顺利,good()成员函数返回true。

1)eofbit

当输入流操作到达文件未尾时,将设置eofbit。

eof()成员函数检查流是否设置了eofbit。

2)badbit

无法诊断的失败破坏流时,将设置badbit。(例如:对输入流进行写入;磁盘没有剩余空间)。

bad()成员函数检查流是否设置了badbit。

3)failbit

当输入流操作未能读取预期的字符时,将设置failbit(非致命错误,可挽回,一般是软件错误,例如:想读取一个整数,但内容是一个字符串;文件到了未尾)I/O失败也可能设置failbit。

fail()成员函数检查流是否设置了failbit。

4)clear()成员函数清理流状态。

5)setstate()成员函数重置流状态。

示例1:

#include <iostream>
#include <fstream>          // ofstream类需要包含的头文件。
#include <unistd.h>
using  namespace std;

int main()
{
  ofstream fout("/oracle/tmp/bbb.txt");   // 打开文件。
  fout << unitbuf;

  for (int ii = 0; ii < 1000; ii++)  // 循环1000次。
  {
    fout << "ii=" << ii << ",我是一只傻傻傻傻傻傻傻傻傻傻傻傻傻傻的鸟。\n";
    //fout.flush();      // 刷新缓冲区。
    usleep(100000);    // 睡眠十分之一秒。
  }

  fout.close();  // 关闭文件。
}

示例2:

#include <iostream>
#include <fstream>  // ifstream类需要包含的头文件。
#include <string>     // getline()函数需要包含的头文件。
using  namespace std;

int main()
{
	ifstream fin(R"(D:\data\txt\test.txt)", ios::in);

	if (fin.is_open() == false) {
		cout << "打开文件" << R"(D:\data\txt\test.txt)" << "失败。\n";  return 0;
	}

	string buffer;
	/*while (fin >> buffer) {
		cout << buffer << endl;
	}*/
	while (true) {
		fin >> buffer;
		cout << "eof()=" << fin.eof() << ",good() = " << fin.good() << ", bad() = " << fin.bad() << ", fail() = " << fin.fail() << endl;
		if (fin.eof() == true) break;
		
		cout << buffer << endl;
	}

	fin.close();	   // 关闭文件,fin对象失效前会自动调用close()。
}

二十四、C++异常、断言

1、C++异常

1)异常的语法

1)捕获全部的异常

try
{
    // 可能抛出异常的代码。
    // throw 异常对象;
}
catch (...)
{
    // 不管什么异常,都在这里统一处理。
}

2)捕获指定的异常

try
{
    // 可能抛出异常的代码。
    // throw 异常对象;
}
catch (exception1 e)
{
    // 发生exception1异常时的处理代码。
}
catch (exception2 e)
{
   // 发生exception2异常时的处理代码。
}

在try语句块中,如果没有发生异常,执行完try语句块中的代码后,将继续执行try语句块之后的代码;如果发生了异常,用throw抛出异常对象,异常对象的类型决定了应该匹配到哪个catch语句块,如果没有匹配到catch语句块,程序将调用abort()函数中止。

如果try语句块中用throw抛出异常对象,并且匹配到了catch语句块,执行完catch语句块中的代码后,将继续执行catch语句块之后的代码,不会回到try语句块中。

如果程序中的异常没有被捕获,程序将异常中止。

示例:

#include <iostream>
using namespace std;

int main(int argc, char* argv[])
{
    try
    {
        // 可能抛出异常的代码。
        int ii = 0;
        cout << "你是一只什么鸟?(1-傻傻鸟;2-小小鸟)";
        cin >> ii;

        if (ii==1)  throw "不好,有人说我是一只傻傻鸟。";            // throw抛出const char *类型的异常。
        if (ii==2)  throw ii;                                                             // throw抛出int类型的异常。
        if (ii==3)  throw string("不好,有人说我是一只傻傻鸟。"); // throw抛出string类型的异常。

        cout << "我不是一只傻傻鸟,哦耶。\n";
    }
    catch (int ii)
    {
        cout << "异常的类型是int=" << ii << endl;
    }
    catch (const char* ss)
    {
        cout << "异常的类型是const char *=" << ss << endl;
    }
    catch (string str)
    {
        cout << "异常的类型是string=" << str << endl;
    }
    //catch (...)  // 不管什么异常,都在这里处理。
    //{
    //    cout << "捕获到异常,具体没管是什么异常。\n";
    //}

    cout << "程序继续运行......\n";   // 执行完try ... catch ...后,将继续执行程序中其它的代码。
}

2)栈解旋

异常被抛出后,从进入try语句块开始,到异常被抛出之前,这期间在上构造的所有对象,都会被自动析构。析构的顺序与构造的顺序相反。这一过程称为栈的解旋。

也就是在执行throw前,在try执行期间构造的所有对象被自动析构后,才会进入catch匹配。

3)异常规范

C++98标准提出了异常规范,目的是为了让使用者知道函数可能会引发哪些异常。

void func1() throw(A, B, C);   // 表示该函数可能会抛出A、B、C类型的异常。
void func2() throw();      // 表示该函数不会抛出异常。
void func3();          // 该函数不符合C++98的异常规范。

C++11标准弃用了异常规范,使用新增的关键字noexcept指出函数不会引发异常。

void func4() noexcept;     // 该函数不会抛出异常。

在实际开发中,大部分程序员懒得在函数后面加noexcept,弃用异常已是共识,没必要多此一举。

关键字noexcept也可以用作运算符,判断表达试(操作数)是否可能引发异常;如果表达式可能引发异常,则返回false,否则返回true。

4)C++标准库异常

5)重点关注的异常

1)std::bad_alloc

如果内存不足,调用new会产生异常,导致程序中止;如果在new关键字后面加(std::nothrow)选项,则返回nullptr,不会产生异常。

示例:

#include <iostream>
using namespace std;

int main()
{
	try {
		// 如果分配内存失败,会抛出异常。
		//double* ptr = new double[100000000000];  
		// 如果分配内存失败,将返回nullptr,会抛出异常。
		double* ptr = new (std::nothrow) double[100000000000];  

		if (ptr == nullptr) cout << "ptr is null.\n";
	}
	catch (bad_alloc& e)
	{
		cout << "catch bad_alloc.\n";
	}
}

2)std::bad_cast

dynamic_cast可以用于引用,但是,C++没有与空指针对应的引用值,如果转换请求不正确,会出现std::bad_cast异常。

3)std::bad_typeid

假设有表达式typeid(*ptr),当ptr是空指针时,如果ptr是多态的类型,将引发std::bad_typeid异常。

2、C++断言

1)断言

断言(assertion)是一种常用的编程手段,用于排除程序中不应该出现的逻辑错误。

使用断言需要包含头文件<cassert>或<assert.h>,头文件中提供了带参数的宏assert,用于程序在运行时进行断言。

语法:assert(表达式);

断言就是判断(表达式)的值,如果为0(false),程序将调用abort()函数中止,如果为非0(true),程序继续执行。

断言可以提高程序的可读性,帮助程序员定位违反了某些前提条件的错误。

注意:

  • 断言用于处理程序中不应该发生的错误,而非逻辑上可能会发生的错误。

  • 不要把需要执行的代码放到断言的表达式中。

  • 断言的代码一般放在函数/成员函数的第一行,表达式多为函数的形参。

示例:

#include <iostream>
#include <cassert>              // 断言assert宏需要包含的头文件。
using  namespace std;

void  copydata(void *ptr1,void *ptr2)   // 把ptr2中的数据复制到ptr1中。
{
    assert(ptr1&&ptr2);  // 断言ptr1和ptr2都不会为空。

    cout << "继续执行复制数据的代码......\n";
}

int main()
{
    int ii=0,jj=0;
    
    copydata(&ii, &jj);  // 把ptr2中的数据复制到ptr1中。
}

2)C++11静态断言

assert宏是运行时断言,在程序运行的时候才能起作用。

C++11新增了静态断言static_assert,用于在编译时检查源代码。

使用静态断言不需要包含头文件。

语法:static_assert(常量表达式,提示信息);

注意:static_assert的第一个参数是常量表达式。而assert的表达式既可以是常量,也可以是变量。

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

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

相关文章

【Linux】线程概念与控制

线程概念与控制 一.Linux线程概念1.什么是线程&#xff1f;2.分页式存储管理1.虚拟地址和页表的由来2.物理内存管理3.页表4.页目录结构5.两级页表的地址转换6.缺页中断(异常) 3.线程的优点(面试题)4.线程的缺点5.线程异常6.线程用途 二.Linux进程VS线程1.进程和线程2.进程的多个…

电子电路中,正负双电源供电的需求原因

1. 允许信号双向摆动 - **交流信号的处理**&#xff1a;许多电路&#xff08;如音频放大器、运算放大器&#xff09;需要处理正负交替变化的交流信号&#xff08;例如声音信号、传感器输出&#xff09;。如果仅用单正电源&#xff08;如12V&#xff09;&#xff0c;信号的“负…

ROS环境搭建

ROS首次搭建环境 注&#xff1a;以下内容都是在已经安装好ros的情况下如何搭建workplace 一、创建工作空间二、创建ROS包三、注意 注&#xff1a;以下内容都是在已经安装好ros的情况下如何搭建workplace 如果没有安装好&#xff0c;建议鱼香ros一步到位:鱼香ROS 我也是装了好久…

java后端开发day26--常用API(一)

&#xff08;以下内容全部来自上述课程&#xff09; 1.Math 1.简单介绍 是一个帮助我们用于进行数学计算的工具类私有化构造方法&#xff0c;所有的方法都是静态的 2.常用方法 不要背&#xff0c;忘了就查文档。 3.练习题 1.判断一个数是否为质数&#xff08;优化版&am…

SpringBoot接口自动化测试实战:从OpenAPI到压力测试全解析

引言&#xff1a;接口测试的必要性 在微服务架构盛行的今天&#xff0c;SpringBoot项目的接口质量直接影响着系统稳定性。本文将分享如何通过自动化工具链实现接口的功能验证与性能压测&#xff0c;使用OpenAPI规范打通测试全流程&#xff0c;让您的接口质量保障体系更加完备。…

Python中文自然语言处理库SnowNLP

SnowNLP 介绍 SnowNLP 是一个基于 Python 的中文自然语言处理库&#xff0c;专为处理中文文本而设计。它受到 TextBlob 的启发&#xff0c;但与 TextBlob 不同的是&#xff0c;SnowNLP 没有使用 NLTK&#xff0c;所有的算法都是自己实现的&#xff0c;并且自带了一些训练好的字…

Linux-计算机网络.udp

1.收发函数: read&#xff08;&#xff09;/write () ///通用文件读写&#xff0c;可以操作套接字。 recv(,0) /send(,0) ///TCP 常用套机字读写 recvfrom()/sendto() ///UDP 常用套接字读写 ssize_t recv(int sockfd, void *buf, size_t len, …

【大厂AI实践】清华:清华古典诗歌自动生成系统“九歌”的算法

【大厂AI实践】清华&#xff1a;清华古典诗歌自动生成系统“九歌”的算法 &#x1f31f; 嗨&#xff0c;你好&#xff0c;我是 青松 &#xff01; &#x1f308; 自小刺头深草里&#xff0c;而今渐觉出蓬蒿。 文章目录 **01 自动作诗缘起****1. 诗歌自动写作** **02 九歌的模型…

Docker安装Postgres_16数据库

PostgreSQL简介 PostgreSQL 是一个功能强大、开源的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;以其可靠性、功能丰富性和可扩展性而闻名。它支持复杂的查询、事务完整性、并发控制以及多种数据类型和扩展功能&#xff0c;适用于各种规模的应用程序; 适用传…

VSCode 移除EmmyLua插件的红色波浪线提示

VSCode 中安装插件EmmyLua&#xff0c;然后打开lua文件的时候&#xff0c;如果lua代码引用了C#脚本的变量&#xff0c;经常出现 “undefined global variable: UnityEngineEmmyLua(undefined-global)” 的红色波浪线提示&#xff0c;这个提示看着比较烦人&#xff0c;我们可以通…

大模型巅峰对决:DeepSeek vs GPT-4/Claude/PaLM-2 全面对比与核心差异揭秘

文章目录 一、架构设计深度解剖1.1 核心架构对比图谱1.2 动态MoE架构实现架构差异分析表 二、训练策略全面对比2.1 训练数据工程对比2.2 分布式训练代码对比DeepSeek混合并行实现GPT-4 Megatron实现对比 2.3 关键训练参数对比 三、性能表现多维评测3.1 基准测试全景对比3.2 推理…

C语言基础知识02

格式化输入输出 函数名&#xff1a;printf&#xff08;&#xff09; 格式控制符&#xff1a;%c //把数据转换成字符型 cahr %d //把数据转换为有符号十进制整型 int short %ld // long %f //把数据转成单精度浮点型 flot %d //double %s …

Linux的进程观:简单性如何成就强大性(三)

1. 环境变量 1.1. 基本概念 环境变量(environment variables)⼀般是指在操作系统中⽤来指定操作系统运⾏环境的⼀些参数。 如&#xff1a;我们在编写C/C代码的时候&#xff0c;在链接的时候&#xff0c;从来不知道我们的所链接的动态静态库在哪⾥&#xff0c;但是照样可以链接…

element-ui infiniteScroll 组件源码分享

简单分享 infiniteScroll 组件源码&#xff0c;主要有以下四个方面&#xff1a; 1、infiniteScroll 页面结构。 2、infiniteScroll 组件属性。 3、组件内部的方法。 4、存在的问题。 一、infiniteScroll 页面结构&#xff1a; 二、页面属性。 2.1 infinite-scroll-disab…

vulnhub靶场之【digitalworld.local系列】的bravery靶机

前言 靶机&#xff1a;digitalworld.local-bravery&#xff0c;IP地址为192.168.10.8 攻击&#xff1a;kali&#xff0c;IP地址为192.168.10.6 kali采用VMware虚拟机&#xff0c;靶机采用virtualbox虚拟机&#xff0c;网卡都为桥接模式 这里官方给的有两种方式&#xff0c;…

SpringBoot 整合mongoDB并自定义连接池,实现多数据源配置

要想在同一个springboot项目中使用多个数据源&#xff0c;最主要是每个数据源都有自己的mongoTemplate和MongoDbFactory。mongoTemplate和MongoDbFactory是负责对数据源进行交互的并管理链接的。 spring提供了一个注解EnableMongoRepositories 用来注释在某些路径下的MongoRepo…

C++20 标准化有符号整数:迈向更可预测的整数运算

文章目录 一、背景&#xff1a;为什么需要标准化&#xff1f;二、2 的补码&#xff1a;原理与优势&#xff08;一&#xff09;2 的补码原理&#xff08;二&#xff09;2 的补码的优势 三、C20 的变化&#xff1a;明确 2 的补码四、如何利用这一特性优化代码&#xff08;一&…

npm ERR! code 128 npm ERR! An unknown git error occurred

【问题描述】 【问题解决】 管理员运行cmd&#xff08;右键window --> 选择终端管理员&#xff09; 执行命令 git config --global url.“https://”.insteadOf ssh://git cd 到项目目录 重新执行npm install 个人原因&#xff0c;这里执行npm install --registryhttps:…

泵吸式激光可燃气体监测仪:快速精准守护燃气管网安全

在城市化进程加速的今天&#xff0c;燃气泄漏、地下管网老化等问题时刻威胁着城市安全。如何实现精准、高效的可燃气体监测&#xff0c;守护“城市生命线”&#xff0c;成为新型基础设施建设的核心课题。泵吸式激光可燃气体监测仪&#xff0c;以创新科技赋能安全监测&#xff0…

Stiring-PDF:开源免费的PDF文件处理软件

Stiring-PDF是一款开源免费且比较好用的PDF文件处理工具。 Stiring-PDF官网网址为&#xff1a;https://www.stiringpdf.com/。Stiring-PDF是一款专业的PDF文件处理工具&#xff0c;支持Windows和macOS操作系统&#xff1b;提供丰富的PDF编辑和转换功能&#xff0c;适用于日常工…