本文使用gcc 8.3.1版本编译。
nullptr:用来替代NULL,区别空指针、0。nullptr的类型为nullptr_t。
#include <iostream>
void foo(char *)
{
std::cout << "char *" << std::endl;
}
void foo(int)
{
std::cout << "int" << std::endl;
}
int main(int argc, char const *argv[])
{
//foo(NULL);
foo(nullptr); //nullptr表示空指针,本质还是0,但是具备了类型
return 0;
}
constptr:显式的生命函数或对象构造函数在编译器会成为常量表达式。
#include <iostream>
#define LEN 10
//时期:
//1.编译器 cpp--编译器--> exe
//2.运行期
//常量表达式
constexpr int len_foo()
{
return 5;
}
int main(int argc, char const *argv[])
{
//局部变量,在栈上开辟空间
char arr_1[10];
char arr_2[LEN];
//int len = 5;
const int len = 5;
char arr_3[len]; //编译器在运行期才能确定该变量的大小,从而分配空间
//char arr_3[len + 5]; //非法
//常量表达式
char arr_5[len_foo() + 5];
return 0;
}
iterator:迭代器,为容器提供统一的遍历方式。
auto:自动类型推导。
#include <iostream>
#include <vector>
int main(int argc, char const *argv[])
{
//普通的数组:一旦申请,不能再扩增
int ary[5] = {1, 2, 3, 4, 5};
//int *pAry = new int[5];
//容器:动态数组,不用指定其大小,会根据数组当前的使用情况进行动态的扩容
std::vector<int> v;
//插入数据
std::cout << "初始capacity:" << v.capacity() << std::endl;
for (int i = 0; i < 10; ++i)
{
v.push_back(i);
std::cout << "第 " << i + 1 << " push_back后capacity:" << v.capacity() << std::endl;
}
//遍历
/*
for (int i = 0; i < v.size(), ++i)
{
std::cout << v[i] << " ";
}
*/
//使用迭代器的方式遍历
//迭代器的好处:统一的遍历方式。vector/list/string...
std::vector<int>::iterator it; //迭代器,模板类中的内部类
for (it = v.begin(); it != v.end(); ++it)
{
std::cout << *it << " ";
}
std::cout << std::endl;
//auto类型推导关键字
for (auto it2 = v.begin(); it2 != v.end(); ++it2)
{
std::cout << *it2 << " ";
}
std::cout << std::endl;
for (auto i : v)
{
std::cout << i << " ";
}
std::cout << std::endl;
return 0;
}
decltype:为了解决 auto 关键字只能对变量进行类型推导的缺陷而出现的,decltype可以用来计算某个表达式的类型。
template <typename T, typename U>
auto add(T x, U y)
{
return x + y;
}
int main(int argc, char const *argv[])
{
int x = 1;
float y = 1.12f;
auto r = add<int, float>(x, y);
if (std::is_same<decltype(r), int>::value)
{
std::cout << "decltype(x+y) = int" << std::endl;
}
else if (std::is_same<decltype(r), float>::value)
{
std::cout << "decltype(x+y) = float" << std::endl;
}
return 0;
}
委托构造:Base(int i) : Base,同一个类中一个构造函数调用另一个构造函数(仅能委托一个)。
#include <iostream>
class Base
{
public:
Base()
{
value1 = 1;
}
Base(float f)
{
value3 = f;
}
Base(int i) : Base() //委托Base()构造函数
{
value2 = i;
}
Base(int i, float f) : Base(f)
{
//委托Base(float)构造函数
value2 = i;
}
//不能委托多个构造函数
/*Base(int i, float f) : Bae Base(f)
{
value2 = i;
}*/
int value1;
int value2;
float value3;
};
int main(int argc, char const *argv[])
{
Base b1(2);
std::cout << b1.value1 << " " << b1.value2 << std::endl;
Base b2(5, 5.4f);
std::cout << b2.value1 << " " << b2.value2 << " " << b2.value3 << std::endl;
return 0;
}
继承构造:using Base::Base; 派生类委托使用基类的构造函数。
#include <iostream>
class Base
{
public:
Base()
{
value1 = 1;
}
Base(int i) : Base() //委托Base()构造函数
{
value2 = i;
}
int value1;
int value2;
};
//继承构造
class SubClass : public Base
{
public:
/*SubClass(int v)
{
value2 = v;
}*/
//直接使用基类的构造
using Base::Base;
};
int main(int argc, char const *argv[])
{
SubClass s1(2);
std::cout << s1.value1 << " " << s1.value2 << std::endl;
return 0;
}
override:显示声明派生类重写基类的虚函数。
#include <iostream>
class Base
{
public:
virtual void foo()
{
std::cout << "基类foo()" << std::endl;
}
};
class SubClass : public Base
{
public:
void foo() override //override显式说明重写基类的虚函数
{
std::cout << "派生类重写基类foo()" << std::endl;
}
};
int main(int argc, char const *argv[])
{
SubClass s;
s.foo();
return 0;
}
final:防止类被继续继承以及终止虚函数继续重载。final声明的虚函数不能被重写,final声明的类不能被继承。
default:希望编译器产生默认构造。
delete:不让编译器产生默认构造。
#include <iostream>
class Base
{
public:
Base() = default; //希望编译器产生默认构造
//Base() = delete; //不让编译器产生默认构造
Base(int n)
{}
//virtual void foo() final //final声明的虚函数不能被重写
virtual void foo()
{
std::cout << "基类foo()" << std::endl;
}
};
//final声明的类不能被继承
//class SubClass final : public Base
class SubClass : public Base
{
public:
void foo() override //override显式说明重写基类的虚函数
{
std::cout << "派生类重写基类foo()" << std::endl;
}
};
/*class SubSubClass : public SubClass
{
};*/
int main(int argc, char const *argv[])
{
SubClass s;
s.foo();
Base b;
return 0;
}
const_cast:移除变量的const或volatile限定符。调用了一个参数不是const的函数,而我们要传进去的实际参数确实const的,但是我们知道这个函数是不会对参数做修改的。
#include <iostream>
#include <string>
class Test
{
public:
Test() : value(2)
{}
//常成员函数
void foo(int n) const
{
//value = n; //错误
const_cast<Test* const>(this)->value = n; //const Test* const this ----> Test* const this
}
int value;
};
int main(int argc, char const *argv[])
{
//const_cast只针对指针,引用,this指针
const int n = 5;
//int* p = &n; //错误
const int* const_p = &n;
int* mod_p = const_cast<int*>(const_p);
*mod_p = 10;
std::cout << "&n = " << &n << " n = " << n << std::endl;
std::cout << "const_p = " << const_p << " *const_p = " << *const_p << std::endl;
std::cout << "mod_p = " << mod_p << " *mod_p = " << *mod_p << std::endl;
Test t;
std::cout << t.value << std::endl;
t.foo(10);
std::cout << t.value << std::endl;
return 0;
}
输出结果:
&n = 0x7ffc3de5500c n = 5
const_p = 0x7ffc3de5500c *const_p = 10
mod_p = 0x7ffc3de5500c *mod_p = 10
2
10
static_cast:基本等价于隐式转换的一种类型转换运算符,可使用于需要明确隐式转换的地方。
#include <iostream>
//基类与派生类之间的转换
class Father
{
public:
Father()
{
value = 5;
}
virtual void foo()
{
std::cout << "Father::foo()" << std::endl;
}
int value;
};
class Son : public Father
{
public:
virtual void foo() override
{
std::cout << "Son::foo()" << std::endl;
}
};
int main(int argc, char const *argv[])
{
int n = 5;
float f = 10.1f;
//f = n; //本质上,发生了隐式转换
//static_cast:低风险的转换
//1.整型与浮点型
n = static_cast<int>(f);
std::cout << n << std::endl;
//2.字符与整型
char ch = 'a';
n = static_cast<int>(ch);
std::cout << n << std::endl;
//3.void*指针的转换
void* p = nullptr;
int* pN = static_cast<int*>(p);
//4.转换运算符的方式
//无法转换,高风险的转换:
//1.整型与指针类型转换
int kk;
//pN = kk;
//pN = static_cast<char*>(kk); //编译不通过
//基类与派生类之间的转换
Father* pFather = nullptr;
Son* pSon = nullptr;
//基类转派生类(不安全)
//pSon = pFather; //编译不通过
pSon = static_cast<Son*>(pFather); //编译不通过。仍然是不安全的,没有提供运行时的检测
//派生类转基类(安全)
pFather = pSon;
pFather = static_cast<Father*>(pSon);
return 0;
}
dynamic_cast:用于具有虚函数的基类与派生类之间的指针或引用的转换(一般用于向下转换)。
#include <iostream>
//基类与派生类之间的转换
class Father
{
public:
Father()
{
value = 5;
}
virtual void foo()
{
std::cout << "Father::foo()" << std::endl;
}
int value;
};
class Son : public Father
{
public:
Son()
{
value2 = 10;
}
virtual void foo() override
{
std::cout << "Son::foo()" << std::endl;
}
int value2;
};
int main(int argc, char const *argv[])
{
Father f;
Son s;
Father* pFather = &f;
Son* pSon = &s;
//向下转换:基类转派生类,不安全
//用dynamic_cast能够检测出这种转换是不安全的(依赖RTTI)
//在运行时刻检测转换是否安全
//有额外的开销,一般而言只有在向下转换时才必须使用
pSon = dynamic_cast<Son*>(pFather);
pSon->value2 = 123;
//向上转换:派生类转基类
pFather = static_cast<Father*>(pSon);
return 0;
}
reinterpret_cast:强制转换。
#include <iostream>
//基类与派生类之间的转换
class Father
{
public:
Father()
{
value = 5;
}
virtual void foo()
{
std::cout << "Father::foo()" << std::endl;
}
int value;
};
class Son : public Father
{
public:
Son()
{
value2 = 10;
}
virtual void foo() override
{
std::cout << "Son::foo()" << std::endl;
}
int value2;
};
int main(int argc, char const *argv[])
{
int n = 1;
//int* p = (int*)n;
//用于各种高危险的转换方式
//1.整型指针
int* p = reinterpret_cast<int*>(n);
//2.各种类型的指针转换
char* pCh = reinterpret_cast<char*>(p);
//3.基类,派生类指针的转换
Father f;
Son s;
Father* pFather = &f;
Son* pSon = &s;
pSon = reinterpret_cast<Son*>(pFather);
return 0;
}
lambda:匿名函数。
#include <iostream>
int foo(int a, int b)
{
return a + b;
}
void test01()
{
//int c = foo(1, 2);
//lambda表达式就是匿名函数(没有名字的函数)
//[]捕获列表 ()参数列表 ->返回值
int c = [](int a, int b) -> int
{
//函数主体
return a + b;
}(1, 2);
std::cout << c << std::endl;
auto f = [](int a, int b) -> int
{
return a + b;
};
c = f(10, 20);
std::cout << c << std::endl;
}
//嵌套用法
void test02()
{
//函数式编程 多线程,并发
int c = [](int n)
{
return [n](int x)
{
return n + x;
}(1);
}(2);
std::cout << c << std::endl;
auto f = [](int n)
{
return [n](int x)
{
return n + x;
};
};
c = f(10)(20);
std::cout << c << std::endl;
}
int main(int argc, char const *argv[])
{
//test01();
test02();
return 0;
}
lambda中mutable用法:
#include <iostream>
int main(int argc, char const *argv[])
{
int t = 10;
//Lambda 表达式内部函数体在默认情况下是不能够使用函数体外部的变量的, 这时候捕获列表可以起到传递外部数据的作用
//按值捕捉
auto f = [t]() mutable
{
return ++t;
};
auto f2 = [t]() mutable
{
return ++t;
};
std::cout << f() << std::endl; //11
std::cout << f2() << std::endl; //11
std::cout << f() << std::endl; //12
std::cout << f2() << std::endl; //12
std::cout << f() << std::endl; //13
std::cout << f2() << std::endl; //13
std::cout << t << std::endl; //10
return 0;
}
lambda捕获方式:
#include <iostream>
#include <vector>
#include <algorithm>
void test01()
{
int t = 10;
//1.按值捕获,捕获的是声明匿名函数时,捕获列表参数的值
auto f = [t]()
{
std::cout << t << std::endl; //10
};
t = 11;
f();
}
void test02()
{
//2.按引用捕获
int t = 10;
auto f2 = [&t]()
{
std::cout << t << std::endl; //12
t = 13;
};
t = 12;
f2();
std::cout << t << std::endl; //13
}
void test03()
{
//隐式捕获
int a = 1;
int b = 2;
int c = 3;
int d = 4;
//按值捕获所有的变量
[=]()
{
std::cout << a << " + " << b << std::endl;
}();
//按引用捕获所有的变量
[&]()
{
std::cout << ++a << " + " << ++b << std::endl;
}();
//按照值和引用的方式捕获变量
[a, b, &c, &d]()
{
std::cout << a << " + " << b << std::endl;
std::cout << ++c << " + " << ++d << std::endl;
}();
}
//应用
void test04()
{
std::vector<int> v = {1, 2, 3, 4, 5};
/* for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it)
{
if (*it % 2 == 0)
{
std::cout << *it << "是偶数" << std::endl;
}
else
{
std::cout << *it << "是奇数" << std::endl;
}
}*/
//高阶写法
for_each(v.begin(), v.end(), [&](int n)
{
if (n % 2 == 0)
{
std::cout << n << "是偶数" << std::endl;
}
else
{
std::cout << n << "是奇数" << std::endl;
}
});
}
int main(int argc, char const *argv[])
{
//test01();
//test02();
//test03();
test04();
return 0;
}
function:一种通用、多态的函数封装, 它的实例可以对任何可以调用的目标实体进行存储、复制和调用操作, 它也是对 C++ 中现有的可调用实体的一种类型安全的包裹(相对来说,函数指针的调用不是类型安全的), 换句话说,就是函数的容器。当我们有了函数的容器之后便能够更加方便的将函数、函数指针作为对象进行处理
bind:用来绑定函数调用的参数的。
#include <iostream>
#include <functional>
class Test
{
public:
Test() = default;
int MyTest(int n)
{
std::cout << n << std::endl;
return n;
}
int operator()(int n)
{
std::cout << n << std::endl;
return n;
}
};
int test(int n)
{
std::cout << n << std::endl;
return n;
}
void print(int a, int b, int c)
{
std::cout << a << " " << b << " " << c << std::endl;
}
int main(int argc, char const *argv[])
{
//函数对象包装器
//为了函数提供了一种容器(封装)
//test(1);
//支持4中函数的封装
//1.普通函数
std::function<int(int)> f = test;
f(123);
//2.匿名函数
std::function<int(int)> f2 = [](int n) -> int
{
std::cout << n << std::endl;
return n;
};
f2(456);
//3.类的成员函数
std::function<int(Test*, int)> f3 = &Test::MyTest;
Test t;
f3(&t,789);
//4.仿函数(重载了()运算符的函数)
t(111);
std::function<int(Test*, int)> f4 = &Test::operator();
f4(&t, 222);
//bind机制
auto a = std::bind(print, 1, 2, 3);
a();
auto b = std::bind(print, std::placeholders::_1, std::placeholders::_2, 3);
b(100, 200);
return 0;
}
T &&:右值引用的声明让这个临时值的生命周期得以延长、只要变量还活着,那么将亡值将继续存活。
std::move:将左值参数无条件的转换为右值。
#include <iostream>
void reference(std::string& str)
{
std::cout << "左值" << std::endl;
}
void reference(std::string&& str)
{
std::cout << "右值" << std::endl;
}
int main(int argc, char const *argv[])
{
std::string lv1 = "string"; //lv1是一个左值
//std::string&& r1 = lv1; //非法,右值引用不能引用左值
std::string&& rv1 = std::move(lv1); //合法,std::move可以将左值转换为右值
std::cout << rv1 << std::endl;
const std::string& lv2 = lv1 + lv1; //合法,常量左值引用能够延长临时变量的生命周期
//lv2 += "Tese"; //非法,常量引用无法修改
std::cout << lv2 << std::endl;
std::string&& rv2 = lv1 + lv2; //合法,右值引用延长临时对象生命周期
rv2 += "Test"; //合法,非常量引用能够修改临时变量
std::cout << rv2 << std::endl;
reference(rv2); //输出左值。rv2虽然引用了一个右值,但由于它是一个引用,所以rv2依然是一个左值。
// int &a = std::move(1); // 不合法,非常量左引用无法引用右值
const int &b = std::move(1); // 合法, 常量左引用允许引用右值
return 0;
}
移动语义:传统的 C++ 没有区分『移动』和『拷贝』的概念,造成了大量的数据拷贝,浪费时间和空间。
#include <iostream>
class A
{
public:
//默认构造
A() : pointer(new int(1))
{
std::cout << "默认构造" << pointer << std::endl;
}
//拷贝构造
A(A& a) : pointer(new int(*a.pointer))
{
std::cout << "拷贝构造" << pointer << std::endl;
}
//移动构造:避免先复制,再析构,浪费时间和空间
A(A&& a) : pointer(a.pointer)
{
a.pointer == nullptr;
std::cout << "移动构造" << pointer << std::endl;
}
int* pointer;
};
//防止编译器优化
A return_rvalue(bool test)
{
A a,b;
if (test)
return a; //等价于static_cast<A&&>(a);
else
return b; //等价于static_cast<A&&>(b);
}
int main(int argc, char const *argv[])
{
/*函数返回后,产生一个将亡值,被 A 的移动构造(A(A&&))引用,从而延长生命周期,并将这个右值中的指针拿到,保存到了 obj 中,而将亡值的指针被设置为 nullptr,防止了这块内存区域被销毁。*/
A obj = return_rvalue(false);
std::cout << "obj.pointer: " << obj.pointer << " *obj.pointer: " << *obj.pointer << std::endl;
return 0;
}
std::array:容器大小固定的线性容器(std::vector是自动扩容)。
#include <iostream>
#include <vector>
#include <array>
#include <algorithm>
void test01()
{
std::vector<int> v;
std::cout << "size:" << v.size() << std::endl; // 输出 0
std::cout << "capacity:" << v.capacity() << std::endl; // 输出 0
// 如下可看出 std::vector 的存储是自动管理的,按需自动扩张
// 但是如果空间不足,需要重新分配更多内存,而重分配内存通常是性能上有开销的操作
v.push_back(1);
v.push_back(2);
v.push_back(3);
std::cout << "size:" << v.size() << std::endl; // 输出 3
std::cout << "capacity:" << v.capacity() << std::endl; // 输出 4
// 这里的自动扩张逻辑与 Golang 的 slice 很像
v.push_back(4);
v.push_back(5);
std::cout << "size:" << v.size() << std::endl; // 输出 5
std::cout << "capacity:" << v.capacity() << std::endl; // 输出 8
//如下可以看出vector虽然清空了容器,但是被清空元素的内存并没有归还
v.clear();
std::cout << "size:" << v.size() << std::endl; // 输出 0
std::cout << "capacity:" << v.capacity() << std::endl; // 输出 8
//额外内存可通过skrink_to_fit()调用返会给系统
v.shrink_to_fit();
std::cout << "size:" << v.size() << std::endl; // 输出 0
std::cout << "capacity:" << v.capacity() << std::endl; // 输出 0
}
void test02()
{
//数组大小必须是常量表达式
//int len = 4; //非法
constexpr int len = 4;
std::array<int, len> arr = {1, 2, 3, 4};
if (!arr.empty())
{
std::cout << "size:" << arr.size() << std::endl;
}
//用lambda表达式排序
std::sort(arr.begin(), arr.end(), [](int a, int b) {
return b < a;
});
for (auto& i : arr)
{
std::cout << i << " ";
}
std::cout << std::endl;
}
int main(int argc, char const *argv[])
{
//test01();
test02();
return 0;
}
std::forward_list:使用方法和 std::list 基本类似。需要知道的是,和 std::list 的双向链表的实现不同,std::forward_list 使用单向链表进行实现, 提供了 O(1) 复杂度的元素插入,不支持快速随机访问(这也是链表的特点), 也是标准库容器中唯一一个不提供 size() 方法的容器。当不需要双向迭代时,具有比 std::list 更高的空间利用率。
元组:来存放不同类型的数据。std::make_tuple构造元组;std::get获取元组某个位置的值;std::tie元组拆包;std::tuple_cat元组合并。
#include <iostream>
#include <tuple>
auto get_student(int id)
{
//返回类型被推断为std::tuple<double, char, std::string>
if (id == 0)
return std::make_tuple(3.8, 'A', "张三");
else if (id == 1)
return std::make_tuple(2.9, 'B', "李四");
else if (id == 2)
return std::make_tuple(1.7, 'C', "王五");
return std::make_tuple(0.0, 'D', "null");
// 如果只写 0 会出现推断错误, 编译失败
}
void test01()
{
std::tuple<double, int, std::string> student = get_student(0);
std::cout << "ID: 0, GPA: " << std::get<0>(student) << ",成绩: " \
<< std::get<1>(student) << ",姓名: " << std::get<2>(student) << std::endl;
//元组进行拆包
double gpa;
char grade;
std::string name;
std::tie(gpa, grade, name) = get_student(1);
std::cout << "ID: 0, GPA: " << gpa << ",成绩: " << grade << ",姓名: " << name << std::endl;
}
void test02()
{
//added by c++14
std::tuple<std::string, double, double, int> t = std::make_tuple("ABC", 3.14, 2.15, 8);
std::cout << std::get<std::string>(t) << std::endl;
//std::cout << std::get<double>(t) << std::endl; //非法
std::cout << std::get<int>(t) << std::endl;
auto new_tuple = std::tuple_cat(get_student(1), t);
}
int main(int argc, char const *argv[])
{
test01();
test02();
return 0;
}
std::unique_ptr:独占的智能指针,不可复制。
#include <iostream>
#include <memory>
#include <vector>
using std::cout;
using std::endl;
void foo_constuct()
{
//这样构造是可以的
std::unique_ptr<int> p(new int(3));
//空构造
std::unique_ptr<int> p4;
//下面三种写法会报错
// std::unique_ptr<int> p2 = p; //需要拷贝构造
// std::unique_ptr<int> p3(p); //需要拷贝构造
// p4 = p; //需要=运算符重载
}
void foo_reset()
{
//释放
int *pNew = new int(3);
int *p = new int(5);
{
std::unique_ptr<int> uptr(pNew);
uptr.reset(p);
}
}
void foo_move()
{
std::unique_ptr<int> uptr = std::make_unique<int>(4); //C++14添加
std::unique_ptr<int> uptr2 = std::move(uptr); //剪切操作
//cout << *uptr << endl; //非法,std::unique_ptr是独占的智能指针,不可复制
std::unique_ptr<int> uptr3;
uptr3 = std::move(uptr2);
}
void foo_ary()
{
std::vector<std::unique_ptr<int>> Vec;
std::unique_ptr<int> uptr = std::make_unique<int>(3);
//Vec.push_back(uptr); //非法,需要使用std::move操作
Vec.push_back(std::move(uptr));
//cout << *uptr << endl; //非法,std::unique_ptr是独占的智能指针,不可复制
}
int main(int argc, char const *argv[])
{
foo_constuct();
foo_move();
foo_ary();
return 0;
}
std::shared_ptr:带引用计数的智能指针。std::make_shared();sptr.use_count()。
#include <iostream>
#include <memory>
using std::cout;
using std::endl;
void foo_constuct()
{
std::shared_ptr<int> sptr(new int(4));
std::shared_ptr<int> sptr2(sptr);
std::shared_ptr<int> sptr3 = sptr2;
std::shared_ptr<int> sptr4 = std::make_shared<int>(5);
}
void foo(std::shared_ptr<int> p)
{
(*p)++;
}
void test01()
{
std::shared_ptr<int> sptr1 = std::make_shared<int>(10);
foo(sptr1);
cout << *sptr1 << endl; //11
auto sptr2 = sptr1; //引用加1
auto sptr3 = sptr1; //引用加1
int *p = sptr1.get(); //这样不会增加引用计数
cout << "sptr1.use_count: " << sptr1.use_count() << endl;
cout << "sptr2.use_count: " << sptr2.use_count() << endl;
cout << "sptr3.use_count: " << sptr3.use_count() << endl;
}
void test02()
{
//如果用同一个指针去初始化两个shared_ptr时,则引用计数仍然会出错
int *p = new int(3);
{
//重复释放
std::shared_ptr<int> sptr(p);
{
std::shared_ptr<int> sptr2(p);
}
}
}
int main(int argc, char const *argv[])
{
foo_constuct();
test01();
//test02();
return 0;
}
weak_ptr:弱指针。std::weak_ptr 没有* 运算符和-> 运算符,所以不能够对资源进行操作,它的唯一作用就是用于检查std::shared_ptr 是否存在。因此,实际上在使用弱指针时,不能通过弱指针,直接访问内部指针的数据,而应该是先判断该弱指针所观察的强指针是否存在(调用expired()函数), 如果存在,那么则使用lock()函数来获取一个新的shared_ptr来使用对应的内部指针。
#include <iostream>
#include <memory>
using std::cout;
using std::endl;
class Son;
class Parent;
class Son
{
public:
Son()
{}
~Son()
{}
void Set(std::shared_ptr<Parent> parent)
{
m_pParent = parent;
}
std::shared_ptr<Parent> m_pParent;
};
class Parent
{
public:
Parent()
{}
~Parent()
{}
void Set(std::shared_ptr<Son> pSon)
{
m_pSon = pSon;
}
std::shared_ptr<Son> m_pSon;
};
void testShared()
{
Son *pSon = new Son();
Parent *pParent = new Parent();
{
std::shared_ptr<Parent> shared_Parent(pParent);
std::shared_ptr<Son> shared_Son(pSon);
shared_Parent->Set(shared_Son);
shared_Son->Set(shared_Parent);
cout << "shared_Parent.use_count: " << shared_Parent.use_count() << endl; //shared_Parent.use_count: 2
cout << "shared_Son.use_count: " << shared_Son.use_count() << endl; //shared_Son.use_count: 2
}
//通过gdb调试,在这个位置可以看到shared_Parent.use_count和shared_Son.use_count均为1,两个对象均未被销毁
/*最后两者的引用计数均为1,原因是出了块作用域之后,两个shared_parent和shared_son均会析构,
在这两个智能指针的内部,均会先去判断对应的内部指针是否-1是否为0,显然这里相互引用的情况下,
引用计数初值为2,减1后值为1,所以两个指针均不会被释放。这里,其实只需要一个释放了,
另外一个也能跟着释放,可以采用弱指针,即人为的迫使其中一个引用计数为1,从而打破闭环。
这里只需要将上例子中的任意一个强指针改为弱指针即可。
原因是,弱指针的引用不会增加原来的引用计数,那么就使得引用不再是闭环,所以在出作用域之后,全部得到释放。*/
}
void foo_weak()
{
/*std::weak_ptr 没有* 运算符和-> 运算符,所以不能够对资源进行操作,它的唯一作用就是
用于检查std::shared_ptr 是否存在。*/
/*因此,实际上在使用弱指针时,不能通过弱指针,直接访问内部指针的数据,
而应该是先判断该弱指针所观察的强指针是否存在(调用expired()函数),
如果存在,那么则使用lock()函数来获取一个新的shared_ptr来使用对应的内部指针*/
auto sptr = std::make_shared<int>(3);
auto sptr2 = sptr;
std::weak_ptr<int> wptr = sptr;
cout << "sptr.use_count: " << sptr.use_count() << endl; //ptr.use_count: 2
if (!wptr.expired())
{
std::shared_ptr<int> ptr3 = wptr.lock();
cout << "sptr.use_count: " << sptr.use_count() << endl; //ptr.use_count: 3
}
}
int main(int argc, char const *argv[])
{
testShared();
foo_weak();
return 0;
}