c++(三)

1. STL

1.1. 迭代器

  • 迭代器是访问容器中元素的通用方法。如果使用迭代器,不同的容器,访问元素的方法是相同的;
  • 迭代器支持的基本操作:赋值(=)、解引用(*)、比较(==和!=)、从左向右遍历(++)
  • 一般情况下,迭代器是指针和移动指针的方法。

1.1.1. 正向迭代器

只能使用++运算符从左向右遍历容器,每次沿容器向右移动一个元素。

容器名<元素类型>::iterator 迭代器名;        // 正向迭代器。
容器名<元素类型>::const_iterator 迭代器名;  // 常量正向迭代器。

// 区别在于通过非常量迭代器还能修改其指向的元素

1.1.2. 双向迭代器

具备正向迭代器的功能,还可以反向(从右到左)遍历容器(也是用++),不管是正向还是反向遍历,都可以用--让迭代器后退一个元素。

容器名<元素类型>:: reverse_iterator 迭代器名;        // 反向迭代器。
容器名<元素类型>:: const_reverse_iterator 迭代器名;  // 常反向迭代器。

1.1.3. 随机访问迭代器

具备双向迭代器的功能,还支持以下操作:

  • 用于比较两个迭代器相对位置的关系运算(<、<=、>、>=);
  • 迭代器和一个整数值的加减法运算(+、+=、-、-=);
  • 支持下标运算(iter[n])。

容器

对应的迭代器类型

array

随机访问迭代器

vector

随机访问迭代器

deque

随机访问迭代器

list

双向迭代器

set / multiset

双向迭代器

map / multimap

双向迭代器

forward_list

前向迭代器

unordered_map / unordered_multimap

前向迭代器

unordered_set / unordered_multiset

前向迭代器

stack

不支持迭代器

queue

不支持迭代器

1.1.4. 迭代器失效

erase方法和insert方法都会导致迭代器失效

  • 当容器调用erase方法后,当前位置到容器末尾元素的所有的迭代器全部失效了;
  • 当容器调用insert方法后,当前位置到容器末尾元素的所有的迭代器全部失效了

1.2. 容器

1.2.1. unordered_map

特性操作

  • size_t bucket_count(); // 返回容器桶的数量,空容器有8个桶。
  • float load_factor(); // 返回容器当前的装填因子,load_factor() = size() / bucket_count()。
  • float max_load_factor(); // 返回容器的最大装填因子,达到该值后,容器将扩充,缺省为1。
  • void max_load_factor (float z ); // 设置容器的最大装填因子。
#include<iostream>
#include<unordered_map>
#include<map>

using namespace std;

template<class K, class V>
//取别名
using umap = unordered_map<K, V>;


int main()
{
	//umap<int, string> m;
	//cout << m.bucket_count() << endl;		// 输出默认桶的数量

	//size_t itmp = m.bucket_count();

	//for (int i = 0; i < 200000; i++)
	//{
	//	char name[50];
	//	sprintf_s(name, "西施%d", i);		// 将数据格式化输出到字符串
	//	m.emplace(i, name);					// 向map中插入元素
	//}
	//if (itmp != m.bucket_count())
	//{
	//	cout << m.bucket_count() << endl;
	//	itmp = m.bucket_count();
	//}

	umap<int, string> m1;
	cout << "最大装填因子:" << m1.max_bucket_count() << endl;
	m1.insert({ { 1, "西施1" }, { 2, "西施2" }, { 3, "西施3" }, { 4, "西施4" } });
	cout << "当前桶数:"	<<  m1.bucket_count() << endl;
	cout << "当前装填因子:" << m1.load_factor() << endl;

	m1.insert({ { 5, "西施5" }, { 6, "西施6" }, { 7, "西施7" }, { 8, "西施8" } });
	cout << "当前桶数:" << m1.bucket_count() << endl;
	cout << "当前装填因子:" << m1.load_factor() << endl;

	m1.emplace(9, "西瓜");
	cout << "当前桶数:" << m1.bucket_count() << endl;
	cout << "当前装填因子:" << m1.load_factor() << endl;
}
  • iterator begin(size_t n); // 返回第n个桶中第一个元素的迭代器。
  • iterator end(size_t n); // 返回第n个桶中最后一个元素尾后的迭代器。
#include<iostream>
#include<string>
#include<unordered_map>
#include<map>

using namespace std;

template<class K, class V>
//取别名
using umap = unordered_map<K, V>;


int main()
{
	umap<int, string> m1;
	m1.max_load_factor(5);
	m1.insert({ { 1, "西施1" }, { 2, "西施2" }, { 3, "西施3" }, { 4, "西施4" } });
	m1.insert({ { 5, "西施5" }, { 6, "西施6" }, { 7, "西施7" }, { 8, "西施8" } });
	m1.insert({ { 9, "西施9" }, { 10, "西施10" }, { 11, "西施11" }, { 12, "西施12" } });
	m1.insert({ { 13, "西施13" }, { 14, "西施14" }, { 15, "西施15" }, { 16, "西施16" } });
	m1.emplace(17, "西瓜");
	// 遍历整个容器的方式1
	for (auto& item : m1)
	{
		cout << item.first << "," << item.second << " ";
	}
	// 遍历整个容器的方式2
	for (auto it = m1.begin(); it != m1.end(); it++)
	{
		cout << it->first << "," << it->second << " ";
	}
	cout << endl;

	// 遍历桶中的元素
	for (int i = 0; i < m1.bucket_count(); i++)
	{
		cout << "桶" << i << ": ";
		for (auto it = m1.begin(i); it != m1.end(i); it++)
		{
			cout << it->first << "," << it->second << " ";
		}
		cout << endl;
	}
}
  • void reserve(size_t n); // 将容器设置为至少n个桶。
  • void rehash(size_t n); // 将桶的数量调整为>=n。如果n大于当前容器的桶数,该方法会将容器重新哈希;如果n的值小于当前容器的桶数,该方法可能没有任何作用。
  • size_t bucket_size(size_t n); // 返回第n个桶中元素的个数,0 <= n < bucket_count()。
  • size_t bucket(K &key); // 返回值为key的元素对应的桶的编号。

1.3. 算法

1.3.1. for_each

模拟实现for_each算法的功能。特别是要掌握仿函数的用法。

//
//  main.cpp
//  foreach_demo
//
//  Created by apple on 2023/10/5.
//

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

// 模板函数
template<typename T>
void show(const T& t)
{
    cout << "亲爱的 " << t << " 号,我是一只傻傻鸟" << endl;
}

// 仿函数
template<class T>
class CGirl
{
public:
    CGirl(){};
    void operator()(const T& t)
    {
        cout << "亲爱的 " << t << " 号,我是一只傻傻鸟" << endl;

    }
};
// 自定义的foreach函数,用于遍历容器,并对每个元素执行操作
/**
 T2 func:func是一个函数指针,函数指针的类型是T2
 */
template<typename T1, typename T2>
void foreach(const T1& start, const T1& end, T2 func)
{
    for(auto it=start; it!=end;it++)
    {
        func(*it);
    }
}

int main(int argc, const char * argv[]) {

    // 存放int的vector容器
    vector<int> v = {3,4,23,2,1};
    foreach(v.begin(),  v.end(), show<int>);
    cout << endl;
    // 显式地创建对象
    CGirl<int> c;
    foreach(v.begin(),  v.end(), c);
    // 匿名对象的括号不能省略
    foreach(v.begin(),  v.end(), CGirl<int>());
    return 0;
}

1.3.2. find_if

模拟实现find_if算法的功能

//
//  main.cpp
//  findif_demo
//
//  Created by apple on 2023/10/5.
//

#include <iostream>
#include <vector>
#include <list>

using namespace std;

// 使用模板函数
template<typename T>
bool show(const T& t, const T& no)
{
    return t == no;
}

// 使用仿函数
template<class T>
class CGirl
{
public:
    T m_no;
    CGirl(T no):m_no(no){};
    bool operator()(const T& t)
    {
        return t == m_no;
    }
};

// 自定义find_if函数,使用模板函数,返回的是一个迭代器
template<typename T1, typename T2, typename T3>
T1 findif(const T1& start, const T1& end, T2 pfunc, T3 no)
{
    for(auto it = start; it!=end;it++)
    {
        if (pfunc(*it, no))
        {
            return it;
        }
    }
    return end;
}

// 自定义find_if函数,使用仿函数,返回的是一个迭代器
template<typename T1, typename T2>
T1 findif(const T1& start, const T1& end, T2 pfunc)
{
    for(auto it = start; it!=end;it++)
    {
        if (pfunc(*it))
        {
            return it;
        }
    }
    return end;
}

int main(int argc, const char * argv[]) {
    vector<int> v1 {1,2,3,4,5,7};
    list<string> v2 {"05", "07", "08"};
    auto t1 = findif(v2.begin(), v2.end(), show<string>, "03");
    auto t2 = findif(v2.begin(), v2.end(), CGirl<string>("03"));
    if(t2 == v2.end())
    {
        cout << "查找失败" << endl;
    }
    else{
        cout << "查找成功:" << *t2 << endl;
    }
    
    return 0;
}

2. 对象优化

2.1. 对象构造时的对象优化

不同方式构造对象,其背后都调用了哪些方法呢?

#include <iostream>

using namespace std;

class Test
{
public:
	Test(int a=10) :ma(a)
	{
		cout << "Test(int)" << endl;
	}
	~Test()
	{
		cout << "~Test()" << endl;
	}
	Test(const Test& t)
	{
		cout << "Test(const Test&)" << endl;
		ma = t.ma;
	}
	Test& operator=(const Test& t)
	{
		cout << "operator=" << endl;
		ma = t.ma;
		return *this;
	}
private:
	int ma;
};
#if 0

int main()
{
	Test t1;
	Test t2(t1);
	Test t3 = t1;
	// 等价于Test t4(20)
	Test t4 = Test(20);	 // c++编译器对对象构造做了优化,这里的临时对象不产生了,直接构造新对象。不会打印Test(const Test&),只会打印Test(int)

	t4 = Test(40);		// 这里就必须要生成临时对象了,因为这行代码调用的是赋值函数,需要将临时对象当做参数传递给operator=函数

	cout << "-------------------" << endl;
	// 指针指向对象的优化
	//Test* p = &Test(40); // 不能用一个指针去保留一个临时对象的地址,这样会造成野指针

	Test&&ri = Test(40); // 这样是可以的。Test(40)是一个临时对象,是一个右值,需要使用右值引用
}

#endif // 1

上面的代码中,Test t4 = Test(20)这行代码,按照正常的构造对象方式是,Test(20)生成一个临时对象(即调用构造方法);然后将这个临时对象拷贝构造给t4(即调用拷贝构造方法)。但是因为Test(20)生成的是一个临时对象,出了这行代码之后就被销毁了。因此编译器在这里就做了优化,不再产生这个临时对象,而是直接构造t4新对象。因此Test t4 = Test(20)等价于Test t4(20)

如果想要看到编译器未做优化的打印结果,可以在 Linux 系统下设置set(CMAKE_CXX_FLAGS -fno-elide-constructors)

添加带右值引用参数的拷贝构造函数和赋值函数

#include <iostream>
using namespace std;

class CMyString
{
public:
	// 构造函数
	CMyString(const char* p = nullptr)
	{
		if (p != nullptr)
		{
			mptr = new char[strlen(p) + 1];		// 开辟空间并初始化
			strcpy(mptr, p);  //  将p空间中的内容拷贝到mptr
		}
		// 防止mptr是一个空指针
		else {
			mptr = new char[1];
			*mptr = '\0';
		}
		cout << "CMyString()" << endl;
	}
	// 析构函数
	~CMyString()
	{
		delete[]mptr;
		mptr = nullptr;		// 标准写法,释放空间后再将指针指向空,因为释放空间后,指针还保留着地址
		cout << "~CMyString()" << endl;
	}
	// 拷贝构造函数
	CMyString(const CMyString& str) // 传入的是一个左值对象,即有地址或者有名字的对象
	{
		mptr = new char[strlen(str.mptr) + 1];
		strcpy(mptr, str.mptr);
		cout << "CMyString(const CMyString&)" << endl;
	}
	// 拷贝构造函数
	CMyString(CMyString&& str) // 传入的是一个右值对象,即没有地址或者没有名字的对象
	{
		mptr = str.mptr;
		str.mptr = nullptr;		// 要置为空,不然两个指针都指向同一个对象,在对象析构的时候会报错
		cout << "CMyString(CMyString&&)" << endl;
	}
    // 赋值构造函数
	CMyString& operator=(CMyString&& str)
	{
		cout << "String& operator=(String&&)" << endl;
		// 防止自赋值
		if (this == &str)
		{
			return *this;
		}
		delete[] mptr; // 先把原先的内存释放掉
		mptr = str.mptr;
		str.mptr = nullptr;

		return *this;
	}
	const char* c_str() const { return mptr; }
private:
	char* mptr;
	friend CMyString operator+ (const CMyString& str1, const CMyString& str2);
	friend ostream& operator<<(ostream& out, const CMyString& str);
};

CMyString operator+ (const CMyString& str1, const CMyString& str2)
{
    cout << "operator+" << endl;
	CMyString tempstr;		//CMyString()
	tempstr.mptr = new char[strlen(str1.mptr) + strlen(str2.mptr) + 1];
	strcpy(tempstr.mptr, str1.mptr);
	strcat(tempstr.mptr, str2.mptr);
	return tempstr;	
}

ostream& operator<<(ostream& out, const CMyString& str)
{
	out << str.mptr;
	return out;
}
#if 1
int main()
{
	CMyString str1 = "aaaaaaaaaaaaaaaaa";		//CMyString()
	CMyString str2 = "bbbbbbbbbbbbbb";			//CMyString()
	CMyString str3 = str1 + str2;				//operator+,CMyString(),CMyString(CMyString&&)
	cout << str3 << endl;

	cout << "--------------------" << endl;
}
#endif

打印结果如下:

CMyString()
CMyString()
operator+
CMyString()
CMyString(CMyString&&)	// 直接使用临时对象tempstr拷贝构造str3对象
~CMyString()						// 析构掉临时对象tempstr
aaaaaaaaaaaaaaaaabbbbbbbbbbbbbb
--------------------
~CMyString()
~CMyString()
~CMyString()

2.2. 函数调用时的对象优化

正常代码

#include <iostream>

using namespace std;

class Test
{
public:
	Test(int a = 10) :ma(a)
	{
		cout << "Test(int)" << endl;
	}
	~Test()
	{
		cout << "~Test()" << endl;
	}
	Test(const Test& t)
	{
		cout << "Test(const Test&)" << endl;
		ma = t.ma;
	}
	Test& operator=(const Test& t)
	{
		cout << "operator=" << endl;
		ma = t.ma;
		return *this;
	}
	int getData()const { return ma; }
private:
	int ma;
};

Test getObject(Test t)
{
	int val = t.getData();
	Test tmp(val);
	return tmp;
}

#if 1
int main()
{
	Test t1;
	Test t2;
	t2 = getObject(t1);
}
#endif 

上面代码中,执行过程如下:

  • Test t1; 会调用构造方法,构造t1对象。即打印Test(int)
  • Test t2; 会调用构造方法,构造t2对象。即打印Test(int)
  • t2 = getObject(t1); 调用getObject函数,会将t1传递给形参t,这里有一个拷贝构造,即打印Test(const Test&)。然后在getObject函数中,会创建tmp对象,即打印Test(int)。然后return tmp,为了将临时对象带出getObject函数,会调用拷贝构造函数在main函数的栈上创建一个临时对象(tmp1),即打印Test(const Test&)。出了getObject函数后,tmp对象就会被销毁,即打印~Test()。形参t也会被析构,即打印~Test()。然后将临时对象(tmp1)赋值给t2对象,即打印operator=。之后销毁临时对象(tmp1),即打印~Test()
  • 最后依次销毁对象t2和t1。即打印~Test(),~Test()
Test(int)
Test(int)
Test(const Test&)
Test(int)
Test(const Test&)
~Test()
~Test()
operator=
~Test()
~Test()
~Test()

优化代码

#include <iostream>

using namespace std;

class Test
{
public:
	Test(int a = 10) :ma(a)
	{
		cout << "Test(int)" << endl;
	}
	~Test()
	{
		cout << "~Test()" << endl;
	}
	Test(const Test& t)
	{
		cout << "Test(const Test&)" << endl;
		ma = t.ma;
	}
	Test& operator=(const Test& t)
	{
		cout << "operator=" << endl;
		ma = t.ma;
		return *this;
	}
	int getData()const { return ma; }
private:
	int ma;
};

Test getObject(Test &t)
{
	int val = t.getData();
	return Test(val);
}

#if 1
int main()
{
	Test t1;
	Test t2 = getObject(t1);
}
#endif 

优化后的代码,执行过程如下:

  • Test t1;会调用构造方法,构造t1对象。即打印Test(int)
  • Test t2 = getObject(t1);由于getObject函数的形参接收的是Test对象的引用,因此这里就不会再拷贝对象了。进入getObject函数内,return Test(val)这行代码,这里编译器做了优化,不再产生临时对象 Test(val),而是直接构造t2对象,即打印Test(int)
  • 最后依次销毁对象t2和t1。即打印~Test(),~Test()

打印结果如下:

Test(int)
Test(int)
~Test()
~Test()

2.3. 移动语义与完美转发

右值引用变量本身还是一个左值。如T &&val,val本身还是一个左值,因为它也有地址。

std::move(),将左值强转成右值

std::forward<T>,类型完美转发,能够识别左值和右值类型

以下是一个vector容器存放自定义对象的例子

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

class CMyString1
{
public:
	CMyString1(const char* p = nullptr)
	{
		if (p != nullptr)
		{
			mptr = new char[strlen(p) + 1];
			strcpy(mptr, p);
		}
		else
		{
			mptr = new char[1];
			*mptr = '\0';
		}
		cout << "CMyString()" << endl;
	}
	~CMyString1()
	{
		delete[] mptr;
		mptr = nullptr;
		cout << "~CMyString()" << endl;
	}

	CMyString1(const CMyString1& str)
	{
		mptr = new char[strlen(str.mptr) + 1];
		strcpy(mptr, str.mptr);
		cout << "CMyString(const CMyString&)" << endl;
	}

	CMyString1(CMyString1&& str)
	{
		mptr = str.mptr;
		str.mptr = nullptr;
		cout << "CMyString(CMyString&&)" << endl;
	}

	CMyString1& operator=(CMyString1&& str)
	{
		cout << "String& operator=(String&&)" << endl;
		if (this == &str)
			return *this;

		delete[] mptr;

		mptr = str.mptr;
		str.mptr = nullptr;

		return *this;
	}
	const char* c_str() const { return mptr; }

private:
	char* mptr;
	friend CMyString1 operator+(const CMyString1& lhs,
		const CMyString1& rhs);
	friend ostream& operator<<(ostream& out, const CMyString1& str);
};

CMyString1 GetString(CMyString1& str)
{
	const char* pstr = str.c_str();
	CMyString1 tmpStr(pstr);
	cout << "--------------------" << endl;
	return tmpStr;
}

CMyString1 operator+(const CMyString1& lhs,
	const CMyString1& rhs)
{
	cout << "--------------------" << endl;
	// char* ptmp = new char[strlen(lhs.mptr) + strlen(rhs.mptr) + 1];
	CMyString1 tmpStr;
	tmpStr.mptr = new char[strlen(lhs.mptr) + strlen(rhs.mptr) + 1];
	strcpy(tmpStr.mptr, lhs.mptr);
	strcat(tmpStr.mptr, rhs.mptr);


	return tmpStr;
}

ostream& operator<<(ostream& out, const CMyString1& str)
{
	out << str.mptr;
	return out;
}

// 容器的空间配置器allocator做四件事情  内存开辟/内存释放  对象构造/对象析构
template<typename T>
struct Allocator
{
	T* allocate(size_t size) // 负责内存开辟
	{
		return (T*)malloc(sizeof(T) * size);
	}
	void deallocate(void* p) // 负责内存释放
	{
		free(p);
	}

	//void construct(T* p, const T& val) // 负责对象构造
	//{
	//	new (p) T(val); // 定位new
	//}
	//void construct(T* p, T&& val) // 负责对象构造
	//{
	//	new (p) T(std::move(val)); // 定位new
	//}

	template<typename Ty>
	void construct(T* p, Ty&& val)
	{
		new (p) T(std::forward<Ty>(val));
	}

	void destroy(T* p) // 负责对象析构
	{
		p->~T();
	}
};

/*
容器底层内存开辟,内存释放,对象构造和析构,都通过allocator空间配置器来实现
*/
template<typename T, typename Alloc = Allocator<T>>
class vector
{
public:
	vector(int size = 10)
	{
		// 需要把内存开辟和对象构造分开处理
		// _first = new T[size];
		_first = _allocator.allocate(size);
		_last = _first;
		_end = _first + size;
	}
	~vector()
	{
		// 析构容器有效的元素,然后释放_first指针指向的堆内存
		// delete[] _first;
		for (T* p = _first; p != _last; ++p) // 把有效的对象析构从_first到_last
		{
			_allocator.destroy(p);
		}
		_allocator.deallocate(_first); // 释放内存
		_first = _end = _last = nullptr;
	}
	vector(const vector<T>& rhs)
	{
		int size = rhs._end - rhs._first;
		//_first = new T[size];
		_first = _allocator.allocate(size);
		int len = rhs._last - rhs._first;
		for (int i = 0; i < len; ++i)
		{
			//_first[i] = rhs._first[i];
			_allocator.construct(_first + i, rhs._first[i]);
		}
		_last = _first + len;
		_end = _first + size;
	}
	vector<T>& operator = (const vector<T>& rhs)
	{
		if (this == &rhs)
		{
			return *this;
		}

		//delete[] _first;
		for (T* p = _first; p != _last; ++p) // 把有效的对象析构从_first到_last
		{
			_allocator.destroy(p);
		}
		_allocator.deallocate(_first);

		int size = rhs._end - rhs._first;
		//_first = new T[size];
		_first = _allocator.allocate(size);
		int len = rhs._last - rhs._first;
		for (int i = 0; i < len; ++i)
		{
			//_first[i] = rhs._first[i];
			_allocator.construct(_first + i, rhs._first[i]);
		}
		_last = _first + len;
		_end = _first + size;
		return *this;
	}
	//void push_back(const T& val)
	//{
	//	if (full())
	//		expand();
	//	//*_last++ = val;  _last指针指向的内存构造一个值为val的对象
	//	_allocator.construct(_last, val);
	//	_last++;
	//}
	//void push_back(T&& val)
	//{
	//	if (full())
	//		expand();
	//	_allocator.construct(_last, std::move(val));
	//	_last++;
	//}
	template<typename Ty> // 函数模板的类型推演+引用折叠
	void push_back(Ty&& val) // 引用折叠,& + && = &, && + && = &&
	{
		if (full())
			expand();

		// move:移动语义,得到右值类型
		// forward:类型完美转发,能够识别左值和右值类型
		_allocator.construct(_last, std::forward<Ty>(val));
		_last++;
	}
	void pop_back()
	{
		if (empty())
			return;
		//--_last;   不仅要把_last指针--,还需要析构删除的元素
		--_last;
		_allocator.destroy(_last);
	}
	T back() const
	{
		return *(_last - 1);
	}
	bool full() const { return _last == _end; }
	bool empty() const { return _first == _last; }
	int size() const { return _last - _first; }
private:
	T* _first;
	T* _last;
	T* _end;
	Alloc _allocator;
	void expand()
	{
		int size = _end - _first;
		//T* ptmp = new T[2 * size];
		T* ptmp = _allocator.allocate(2 * size);
		for (int i = 0; i < size; i++)
		{
			_allocator.construct(ptmp + i, _first[i]);
			//ptmp[i] = _first[i];
		}
		//delete[] _first;
		for (T* p = _first; p != _last; ++p)
		{
			_allocator.destroy(p);
		}
		_allocator.deallocate(_first);
		_first = ptmp;
		_last = _first + size;
		_end = _first + 2 * size;
	}
};

class Test
{
public:
	Test(int a = 10) : ma(a) { cout << "Test()" << endl; }
	~Test() { cout << "~Test()" << endl; }
	Test(const Test& t)
	{
		ma = t.ma;
		cout << "Test(const Test& t)" << endl;
	}
public:
	int ma;
};

#if 1
int main()
{
	CMyString1 str1 = "aaa";
	vector<CMyString1> vec;

	cout << "------------------" << endl;
	vec.push_back(str1);
	vec.push_back(CMyString1("bbbb"));
	cout << "------------------" << endl;

	return 0;
}
#endif

3. 智能指针

3.1. 不带引用计数的智能指针

3.1.1. unique_ptr

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

unique_ptr是一个模板类,里面有一个内置的指针来指向对象。

使用unique_str需要包含头文件:#include <memory>

3.1.1.1. move
#include <iostream>
#include <string>
#include <memory>

int main() {
    std::unique_ptr<int> p1(new int(10));
    std::unique_ptr<int> p2 = std::move(p1);
    std::cout << "p2: " << *p2 << std::endl;
    // std::cout << "p1: " << *p1 << std::endl; // error
    return 0;
} 

// p2: 10
3.1.1.2. release
#include <iostream>
#include <string>
#include <memory>

int main()
{
    std::unique_ptr<int> auto_pointer(new int);
    int * manual_pointer;
    *auto_pointer = 10;
    // 转移该对象到另一个指针那里去
    manual_pointer = auto_pointer.release();
    // (auto_pointer is now empty)
    std::cout << "manual_pointer points to " << *manual_pointer << '\n';
    std::cout << "auto_pointer points to " << (auto_pointer ? "not null" : "nullptr") << '\n';
    delete manual_pointer;
    return 0;
}

// manual_pointer points to 10
// auto_pointer points to nullptr
3.1.1.3. swap
#include <iostream>
#include <string>
#include <memory>

int main() {
    std::unique_ptr<int> p1(new int(10));
    std::unique_ptr<int> p2(new int(20));
    p1.swap(p2);
    std::cout << "p1: " << *p1 << std::endl;
    std::cout << "p2: " << *p2 << std::endl;
    return 0;
}

// p1: 20
// p2: 10
3.1.1.4. reset
#include <iostream>
#include <string>
#include <memory>

using namespace std;

class AA
{
public:
    string m_name;
    AA(){cout << "调用AA的无参构造函数" << endl;}
    AA(string name):m_name(name){cout << "调用AA的有参构造函数" << endl;}
    ~AA(){cout << "调用AA的析构函数" << endl;}
};

int  main()
{
    // 方法1
    unique_ptr<AA>p0(new AA());
    // 方法2
    unique_ptr<AA> p1 = unique_ptr<AA>(new AA("西施"));
    // 释放对象
    p1.reset();

    cout << "auto_pointer points to " << (p1 ? "not null" : "nullptr") << '\n';
}

//调用AA的无参构造函数
//调用AA的有参构造函数
//调用AA的析构函数
//auto_pointer points to nullptr
//调用AA的析构函数

3.2. 带引用计数的智能指针

3.2.1. shared_ptr

4. 内存划分

4.1. 代码区

  • 存放函数体的二进制代码,由操作系统进行管理;
  • 代码区是共享的,共享的目的是对频繁被执行的程序,只需要在内存中有一份代码即可;
  • 代码区是只读的,防止程序意外修改了它的指令;
  • 属于程序运行前的区域

4.2. 全局/静态区

  • 存放全局变量、静态变量
  • 内存空间由操作系统释放;
  • 属于程序运行前的区域

4.3. 文字常量区

  • 存放字符串常量
  • 常量区是只读

4.4. 栈区

  • 编译器自动分配和释放,存放函数的参数值局部变量等;
  • 特别注意:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放

4.5. 堆区

  • 程序员分配和释放(释放内存使用delete关键字),若程序员不释放,程序结束后由操作系统回收;
  • 在c++中主要利用new关键字在堆区开辟内存
#include<iostream>
#include<string>

using namespace std;

// 在堆区创建一个变量
void test()
{
	int* i = new	 int(10);
	cout << *i << endl;
    // 释放new分配的内存空间,不能连续多次释放同一块内存空间
    delete i;
    // 在释放掉内存后,手动设置指针指向空,后续再继续对空指针释放内存是安全的
    i = nullptr;
}

// 在堆区创建一个数组
void test1()
{
	int* arr = new	 int[10];
    // 给数组赋值
	for (int i = 0; i < 10; i++)
	{
		arr[i] = i + 10;
	}
    // 打印数组
	for (int i = 0; i < 10; i++)
	{
		cout << arr[i] << endl;
	}
    delete[] arr;
    arr = nullptr;
}
int main()
{
	test();
	test1();
}
#include <iostream>
using namespace std;

int num1;
int* ptr1;

int main(int argc, const char* argv[]) {
	int num2;
	char* ptr2;
	double scores[]{ 3.1,3.2 };
	char str1[10] = "我是谁";
	const char* str2  = "我是谁"; 
	static int num3(10);
	ptr1 = new int(3);
	ptr2 = new char[10];
	cout  << "num1的地址:\t" << &num1 << endl;
	cout << "scores的地址:\t" << scores << endl;
	printf("str1的地址:\t%p\n", str1);
	cout << "&str1的地址:\t" << &str1 << endl;
	printf("str2的地址:\t%p\n", str2);
	cout << "&str2的地址:\t" << &str2 << endl;
	cout << "num2的地址:\t" << &num2 << endl;
	cout << "num3的地址:\t" << &num3 <<endl;
	cout << "ptr1的地址:\t" << ptr1 << endl;
	printf("ptr2的地址:\t%p\n", ptr2);
	return 0;
}
num1的地址:    00007FF6220EF9E0		全局区
scores的地址:  000000DCBD4FF888		栈区
str1的地址:    000000DCBD4FF8B8		栈区
&str1的地址:   000000DCBD4FF8B8		栈区
str2的地址:    00007FF6220EBD0C		文字常量区
&str2的地址:   000000DCBD4FF8E8		栈区
num2的地址:    000000DCBD4FF844		栈区
num3的地址:    00007FF6220EF010		全局区
ptr1的地址:    000002AFC90623F0		堆区
ptr2的地址:    000002AFC90638E0		堆区

5. 代码编译原理

5.1. 预编译

以#开头的命令(除开 pragma lib、pragma link 等发生在链接阶段的指令)

预编译命令为: g++ -E 需要预处理的cpp文件 -o 处理后需要保存的文件名

(base) apple@appledeMacBook-Pro lesson_1 % ls
CMakelists.txt  add.cpp         add.h           main.cpp
(base) apple@appledeMacBook-Pro lesson_1 % g++ -E add.cpp -o add.i

5.2. 编译

将 c++文件编译成对应平台下的汇编指令。编译过程中,符号是不生成虚拟地址的。

编译命令为:g++ -E 原始的cpp文件 -o 处理后需要保存的文件名 或者 g++ -E 预处理之后的文件 -o 处理后需要保存的文件名

(base) apple@appledeMacBook-Pro lesson_1 % g++ -E add.cpp -o add.i
(base) apple@appledeMacBook-Pro lesson_1 % g++ -S add.i -o add.s

或者
(base) apple@appledeMacBook-Pro lesson_1 % g++ -S add.cpp -o add.s

5.3. 汇编

将汇编指令转换成机器码,也就是计算机硬件可以直接执行的指令。这一步产生的文件叫做目标文件(main.o),是二进制格式。

汇编命令为:g++ -c 原始的cpp文件 -o 处理后需要保存的文件名 或者 g++ -c 编译之后的文件 -o 处理后需要保存的文件名

.o 文件的头文件中包含了程序的入口地址。

.o 文件的格式组成如下:

5.4. 链接

链接命令为:g++ 所有的app文件 -o 输出的文件名 或者 g++ 所有的汇编之后的文件 -o 输出的文件名

5.4.1. 文件段合并

所有.o 文件中的.text 段进行合并;所有.o 文件中的.data 段进行合并;所有.o 文件中的.bss 段进行合并;......

所有对符号的引用都要找到该符号定义的地方,如上面的 main.o 文件中的 gdata 符号和 _Z3sumii 符号,要去其他文件中找到定义的地方,找不到的话就链接失败。且也不能重复定义,不然也要报错。

5.4.2. 符号重定向

给所有的符号分配虚拟地址,并将.o 文件中的符号地址进行修改

最后生成可执行文件,可执行文件与.o 文件都是由各种段组成的,只是可执行文件多了一个 program headers 段。

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

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

相关文章

ros笔记01--初次体验ros2

ros笔记01--初次体验ros2 介绍安装ros2测试验证ros2说明 介绍 机器人操作系统(ROS)是一组用于构建机器人应用程序的软件库和工具。从驱动程序和最先进的算法到强大的开发者工具&#xff0c;ROS拥有我们下一个机器人项目所需的开源工具。 当前ros已经应用到各类机器人项目开发中…

【IVI】CarService启动-Android13

【IVI】CarService启动-Android13 1、CarServiceImpl启动概述2、简要时序图 1、CarServiceImpl启动概述 【IVI】CarService启动&#xff1a; CarServiceHelperService中绑定CarServiceICarImpl初始化各种服务 packages/services/Car/README.md 2、简要时序图

Linux——passwd文件,grep,cut

/etc/passwd文件含义 作用 - 记录用户账户信息&#xff1a;共分为7段&#xff0c;使用冒号分割 含义 - 文件内容意义&#xff1a;账户名&#xff1a;密码代号x&#xff1a;UID&#xff1a;GID&#xff1a;注释&#xff1a;家目录&#xff1a;SHELL - 第7列/sbin/nologin&#x…

昇思25天学习打卡营第7天之二 | 模型保存与加载

1. 保存与加载 在训练网络模型的过程中&#xff0c;实际上我们希望保存中间和最后的结果&#xff0c;用于微调&#xff08;fine-tune&#xff09;和后续的模型推理与部署&#xff0c;本章节我们将介绍如何保存与加载模型。 1.1 导入依赖 # 导入numpy库&#xff0c;并将其重命…

【C语言】--分支和循环(1)

&#x1f37f;个人主页: 起名字真南 &#x1f9c7;个人专栏:【数据结构初阶】 【C语言】 目录 前言1 if 语句1.1 if1.2 else1.3 嵌套if1.4 悬空else 前言 C语言是结构化的程序设计语言&#xff0c;这里的结构指的是顺序结构、选择结构、循环结构。 我们可以用if、switch实现分支…

51单片机第6步_stdlib.h库函数

本章重点学习stdlib.h库函数。 #include <REG51.h> //包含头文件REG51.h,使能51内部寄存器; #include <stdlib.h> //float atof (char *s1); //参数s1字符串可包含正负号,小数点或E(e)来表示指数部分,如123.456或123e-2; //若首字符是非数据字符,或为正负号…

力扣每日一题 6/30 记忆化搜索/动态规划

博客主页&#xff1a;誓则盟约系列专栏&#xff1a;IT竞赛 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 494.目标和【中等】 题目&#xff1a; 给你一个非负整数数组 nums 和一个…

VMware中的三种虚拟网络模式

虚拟机网络模式 1 主机网络环境2 VMware中的三种虚拟网络模式2.1 桥接模式NAT模式仅主机模式网络模式选择1 VMware虚拟网络配置2 虚拟机选择网络模式3 Windows主机网络配置 配置静态IP 虚拟机联网方式为桥接模式&#xff0c;这种模式下&#xff0c;虚拟机通过主机的物理网卡&am…

mysql8.0-学习

文章目录 mysql8.0基础知识-学习安装mysql_8.0登录mysql8.0的体系结构与管理体系结构图连接mysqlmysql8.0的 “新姿势” mysql的日常管理用户安全权限练习查看用户的权限回收:revoke角色 mysql的多种连接方式socket显示系统中当前运行的所有线程 tcp/ip客户端工具基于SSL的安全…

2024最新boss直聘岗位数据爬虫,并进行可视化分析

前言 近年来,随着互联网的发展和就业市场的变化,数据科学与爬虫技术在招聘信息分析中的应用变得越来越重要。通过对招聘信息的爬取和可视化分析,我们可以更好地了解当前的就业市场动态、职位需求和薪资水平,从而为求职者和招聘企业提供有价值的数据支持。本文将介绍如何使…

Linux系统编程--进程间通信

目录 1. 介绍 1.1 进程间通信的目的 1.2 进程间通信的分类 2. 管道 2.1 什么是管道 2.2 匿名管道 2.2.1 接口 2.2.2 步骤--以父子进程通信为例 2.2.3 站在文件描述符角度-深度理解 2.2.4 管道代码 2.2.5 读写特征 2.2.6 管道特征 2.3 命名管道 2.3.1 接口 2.3.2…

【驱动篇】龙芯LS2K0300之i2c设备驱动

实验背景 由于官方内核i2c的BSP有问题&#xff08;怀疑是设备树这块&#xff09;&#xff0c;本次实验将不通过设备树来驱动aht20&#xff08;i2c&#xff09;模块&#xff0c;大致的操作过程如下&#xff1a; 模块连接&#xff0c;查看aht20设备地址编写device驱动&#xff…

K8S之网络深度剖析(一)(持续更新ing)

K8S之网络深度剖析 一 、关于K8S的网络模型 在K8s的世界上,IP是以Pod为单位进行分配的。一个Pod内部的所有容器共享一个网络堆栈(相当于一个网络命名空间,它们的IP地址、网络设备、配置等都是共享的)。按照这个网络原则抽象出来的为每个Pod都设置一个IP地址的模型也被称作为I…

忍法:声音克隆之术

前言&#xff1a; 最近因为一直在给肚子里面的宝宝做故事胎教&#xff0c;每天&#xff08;其实是看自己心情抽空讲下故事&#xff09;都要给宝宝讲故事&#xff0c;心想反正宝宝也看不见我&#xff0c;只听我的声音&#xff0c;干脆偷个懒&#xff0c;克隆自己的声音&#xf…

信息学奥赛初赛天天练-40-CSP-J2021基础题-组合数学-缩倍法、平均分组、2进制转10进制、面向过程/面向对象语言应用

PDF文档公众号回复关键字:20240630 2021 CSP-J 选择题 单项选择题&#xff08;共15题&#xff0c;每题2分&#xff0c;共计30分&#xff1a;每题有且仅有一个正确选项&#xff09; 1.以下不属于面向对象程序设计语言是( ) A. C B. Python C. Java D. C 2.以下奖项与计…

R包的4种安装方式及常见问题解决方法

R包的4种安装方式及常见问题解决方法 R包的四种安装方式1. install.packages()2. 从Bioconductor安装3. 从本地源码安装4. 从github安装 常见问题的解决1. 版本问题2. 网络/镜像问题3.缺少Rtools R包的四种安装方式 1. install.packages() 对于R自带的包的安装一般都可以通过…

HarmonyOS--路由管理--组件导航 (Navigation)

文档中心 什么是组件导航 (Navigation) &#xff1f; 1、Navigation是路由容器组件&#xff0c;一般作为首页的根容器&#xff0c;包括单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式 2、Navigation组件适用于模块内和跨模块的路由切换&#xff0c;一次开发&#xff0…

实现点击按钮导出页面pdf

在Vue 3 Vite项目中&#xff0c;你可以使用html2canvas和jspdf库来实现将页面某部分导出为PDF文档的功能。以下是一个简单的实现方式&#xff1a; 1.安装html2canvas和jspdf&#xff1a; pnpm install html2canvas jspdf 2.在Vue组件中使用这些库来实现导出功能&#xff1a;…

网线直连电脑可以上网,网线连tplink路由器上不了网

家里wifi网络连不上好几天了&#xff0c;用网线直连电脑可以上网&#xff0c;但网线连tplink路由器wan口上不了网&#xff0c;无Internet连接&#xff0c;网线连lan口可以电脑上网&#xff0c;手机上不了。 后来发现网线的主路由用的192.168.0.1&#xff0c;我的路由器wan口自…

在node环境使用MySQL

什么是Sequelize? Sequelize是一个基于Promise的NodeJS ORM模块 什么是ORM? ORM(Object-Relational-Mapping)是对象关系映射 对象关系映射可以把JS中的类和对象&#xff0c;和数据库中的表和数据进行关系映射。映射之后我们就可以直接通过类和对象来操作数据表和数据了, 就…