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 段。