shared_from_this
使用分析
- 场景
- 类的成员函数需要获取指向自身的shared_ptr的时候
- 类成员函数传递shared_ptr给其他函数或者对象的时候,目的是为了管理对象生命周期
- 使用方法
- 首先类必须继承 std::enable_shared_from_this<T>
- 必须使用 shared_from_this 获取指向自身的shared_ptr
cpp
#include<iostream>
#include<memory>
using namespace std;
class MyClass :public std::enable_shared_from_this<MyClass>
{
public:
void show()
{
cout << "MyClass : show 运行" << endl;
}
//返回一个shared_ptr指向自身
std::shared_ptr<MyClass> getSharedPtr()
{
return shared_from_this();
}
//获取shared_Ptr然后调用它
void process()
{
std::shared_ptr<MyClass> ptr = shared_from_this();
Funcaion(ptr);
}
void Funcaion(std::shared_ptr<MyClass>ptr)
{
cout << "函数内部传入的shared_ptr指针使用次数:" << ptr.use_count() << endl;
ptr->show();
}
};
int main()
{
//创建一个指向Myclass的share_ptr指针
std::shared_ptr<MyClass> obj = std::make_shared<MyClass>();
//调用类内成员函数,获取shared_ptr自身
std::shared_ptr<MyClass> selfptr = obj->getSharedPtr();
//验证shared_ptr的使用次数
cout << "shared_ptr use_count:" << selfptr.use_count() << endl;
//cout << obj.use_count() << endl;
//两个指针都可以调用类内成员函数验证
obj->process();
selfptr->process();
}
左值和右值
**左值:**表达式的结果是一个持久对象,也就是该值可以取地址(内存区域是确定的)。左值通常出现在赋值操作符左侧的值
**右值:**表达式的结果是一个临时值,不可以取地址,通常是赋值操作符右侧的数值
**左值引用:**利用&表示引用的类型,可以绑定到左值
右值引用:&&表示的引用类型,目的是操控临时对象,减少拷贝临时对象而产生性能消耗
移动语义: 也就是不用复制对象 ,直接将资源进行移动即可,减少性能消耗
cpp
#include <iostream>
#include <vector>
std::vector<int> createVector() {
std::vector<int> temp = {1, 2, 3};
return temp; // 返回的是一个右值
}
int main() {
std::vector<int> vec = createVector(); // 这里使用移动语义
for(int i : vec) {
std::cout << i << " ";
}
return 0;
}
左值转换成右值场景分析
- 当左值出现在赋值运算符右侧的时候,会自动将其转化为右值
- 当左值作为算法的数时,会自动将其转换为右值
- 左值作为参数的时候,传递给一个按值传递的函数时候,会自动转换为右值
注意:转换过程中,左值本身是没有消失的,而是生成了一个右值,表示左值所指向的信息(后面的代码会验证)
cpp
#include <iostream>
int main() {
int a = 10; // a 是一个左值
int b = a; // 这里发生了左值到右值的转换,a 的值(10)被赋给 b
std::cout << "a: " << a << std::endl; // a 是左值
std::cout << "b: " << b << std::endl; // b 是左值,打印出来的值是 10
int* p = &a; // 获取 a 的地址
std::cout << "Address of a: " << p << std::endl;
return 0;
}
引用折叠
**作用:**复杂引用类型中,确定最终的引用类型是什么
规则总结
T& &
变为T&
T& &&
变为T&
T&& &
变为T&
T&& &&
变为T&& (特点:只有双&&时,最终才是&&,剩下情况都是&)
事例分析(完美转发)功能:将一个参数完整的转发给另一个函数,也就是说不改变值的类型,原本是左值就还是左值,右值还是右值。(具体理解看函数实现吧)
**具体实现:**结合引用折叠规则,必须传入左值,当T被推导的时候,就是 & &&最终得到的也就是 &
cpp
#include <iostream>
#include <utility> // for std::forward
// 接受左值引用
void process(int& x) {
std::cout << "左值引用: " << x << std::endl;
}
// 接受右值引用
void process(int&& x) {
std::cout << "右值引用: " << x << std::endl;
}
// 用于完美转发
template <typename T>
void forwarder(T&& arg) {
process(std::forward<T>(arg));
}
int main() {
int a = 10;
forwarder(a); // 调用 process(int&) - 传递左值
forwarder(20); // 调用 process(int&&) - 传递右值
forwarder(std::move(a)); // 调用 process(int&&) - 传递右值
return 0;
}
std::move
- 使用方法
- 将一个对象的所有权从一个地方转移到另一个地方,本质是移动而不是复制资源
- 实现原理 (结合函数原型理解)
- 模板参数类型是&&万能引用,所以可以根据传入的参数类型,T可以是任意的成为左值引用或者右值引用
- remove_reference是类型萃取,主要是用于去除T的引用,无论是左值引用还是右值引用最终都变成原型
- static_cast:将参数显式的转换为右值引用,也就是说即使传递的是一个左值,move最终也会将其变成一个右值
C++指针
指针和引用的区别
- 指针是一个存储地址的变量,可以为空,同样也可以重新指向不同的对象,也可以进行指针算术运算
- 引用则是一个对象的别名,必须在声明的时候初始化,不可以重新绑定,更不可以为空
- 使用场景不同
- 指针常用在需要动态分配内存或者需要表示未初始化状态的时候
- 引用则是需要传递函数参数,避免大量复制数据,或者函数返回的时候,避免造成内存消耗
重要的区别如下
- 函数指针
- 参数说明(参考下面函数指针定义一同理解)
- **return_type:**函数返回的类型
- **pointer_name:**指针变量名称(可以理解为就是函数名)
- **parameter_list:**函数的参数列表
- 具体使用参考下面的类型
函数指针数组
cpp
#include <iostream>
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int multiply(int a, int b) {
return a * b;
}
int main() {
// 创建一个函数指针数组
int (*operations[3])(int, int) = { add, subtract, multiply };
int a = 10, b = 5;
for (int i = 0; i < 3; ++i) {
std::cout << "Result: " << operations[i](a, b) << std::endl;
}
return 0;
}
可以使用typedef简化函数指针的声明和使用
cpp
typedef int (*Operation)(int, int);
int add(int a, int b) {
return a + b;
}
int multiply(int a, int b) {
return a * b;
}
int main() {
Operation op;
op = add;
std::cout << "Add: " << op(5, 3) << std::endl;
op = multiply;
std::cout << "Multiply: " << op(5, 3) << std::endl;
return 0;
}
函数的三种传递方式(值传递、引用传递、指针传递)
值传递
- 含义: 在使用值传递的时候,函数收到的参数是一个拷贝后的数据,函数内部无论对形参做什么操作都不会影响原始变量的数值
- 特点
- 首先是不会改变原始变量的数值
- 适用于传输比较小的数据类型
cpp
void modifyValue(int x) {
x = 20; // 仅修改了局部变量 x 的值
}
int main() {
int a = 10;
modifyValue(a);
std::cout << "a: " << a << std::endl; // 输出 10,a 未被修改
return 0;
}
引用传递
- 含义: 函数接收的是变量引用,如果对该引用进行修改会影响到之前的原始变量
- 特点
- 在传递数据的时候,不需要复制对象,从而可以减少性能消耗
cpp
void modifyValue(int& x) {
x = 20; // 修改了原始变量的值
}
int main() {
int a = 10;
modifyValue(a);
std::cout << "a: " << a << std::endl; // 输出 20,a 被修改
return 0;
}
指针传递
- 含义:函数中接受的参数是一个地址,函数内可以通过对该地址解应用,从而修改原始变量的数值
- 特点
- 使用之前需要进行指针安全性检查 ,防止空指针
- 可以改变原始变量的数值
cpp
void modifyValue(int* x) {
if (x != nullptr) {
*x = 20; // 通过指针修改原始变量的值
}
}
int main() {
int a = 10;
modifyValue(&a);
std::cout << "a: " << a << std::endl; // 输出 20,a 被修改
return 0;
}
迭代器
迭代器的含义与作用
**含义:**STL库中提供的一种访问遍历容器中数据的通用方法
作用
- **遍历容器:**提供一种统一的方法去遍历各种容器,不需要关注容器的底层实现细节
- **访问元素:**通过迭代器可以访问容器中的元素,堆目标元素进行读取和修改
- **插入和删除:**特定位置的元素插入和删除操作
迭代器类型**输入迭代器:**只读访问,单向移动,用于一次性读取输入
**输出迭代器:**写访问,用于输出操作
前向迭代器:读写访问,但是只支持单向移动
**双向迭代器:**读写访问,支持双向移动
野指针和悬空指针
野指针
- 含义:未被释放的指针 或者已经被释放了内存但是没有被设置为NULL的指针
- 未初始化的指针: 指针在声明的时候,如果没有初始化,那么指针指向的位置是不可控的,所以会造成野指针
- 内存释放但是指针没有置为NULL: 因此该指针还是指向那片被释放的内存,但是该内存已经被释放,所以成为了野指针
- 野指针的危害 :
- 访问未定义的内存,有可能引起程序崩溃
- 无法预料指针的行为,可能指向重要的内存区域,从而导致数据破坏
悬空指针
- 含义:指针指向已经被释放或者不再有效的内存位置的指针
- 产生原因
- **内存释放后继续使用:**该指针指向的内存已经被释放了,但是该指针仍然时被继续使用的
- **局部变量的作用域:**指向局部变量的指针在该局部变量作用域结束后继续使用(结合事例理解)
- 悬空指针的危害:
- 悬空指针会导致对无效内存的访问,这样会导致程序崩溃或者未定义的行为
- 也有可能会覆盖其他数据,破坏数据的完整性
四种强制类型转换
static_cast:相同类型之间进行转换
- 特点
- 编译时会进行类型检查
- 运行时不会进行类型检查,向下转换时需要注意(结合事例理解向下转换)
dynamic_cast :多态中进行类型转换,比如基类到派生类
- 特点
- 运行是会进行类型检查 ,会确保转换的安全性
- 只有当基类有虚函数的时候才会有效(注意)
- 转换失败时,会返回空指针或者抛出异常
const_cast :主要用于移除const或者volatile属性 ,或者是为非const变量添加这些属性
- 特点
- 只可以改变const和volatile属性,不可以用于其他类型的转换
- 不会改变底层的数据结构
reinterpret_cast: 用于任何类型的转化 ,该转换不会进行任何检查,使用的时候需要注意转换的安全性和正确性
- 特点
- 限制少,可以用于任何类型的转换
- 没有类型检查,不保证其安全性,可能会存在未定义的行为
类型萃取
- **含义:**利用模版的特定,根据传入的不同参数,判断其不同的类型,主要是用于STL中的一种技术(比较复杂,该处不深究)
- 应用场景
- **泛型编程:**根据类型特性进行不同的处理,从而提高代码的通用性和灵活性
- **类型安全检查:**编译期间进行类型检查,从而避免运行时的错误,从而提高程序的安全性
- 基础类型萃取
std::is_integral<T>
:判断类型T是否为整型。std::is_floating_point<T>
:判断类型T是否为浮点型。std::is_pointer<T>
:判断类型T是否为指针类型(根据下面事例进一步理解)
结构体相等判断方法分析
**结构体逐个成员进行比较:**粗暴直接的比较每一个成员是否相等
cpp
struct MyStruct {
int a;
float b;
std::string c;
};
bool areEqual(const MyStruct& lhs, const MyStruct& rhs) {
return lhs.a == rhs.a && lhs.b == rhs.b && lhs.c == rhs.c;
}
**重载比较运算符:**利用运算符重载,从而判断两个结构体是否相等
cpp
struct MyStruct {
int a;
float b;
std::string c;
bool operator==(const MyStruct& other) const {
return a == other.a && b == other.b && c == other.c;
}
};
使用标准库函数memcmp,只适用于简单的数据类型,结构体中如果出现指针等就不可以
cpp
#include <cstring>
struct MyStruct {
int a;
float b;
};
bool areEqual(const MyStruct& lhs, const MyStruct& rhs) {
return std::memcmp(&lhs, &rhs, sizeof(MyStruct)) == 0;
}
拓展,直接使用Boost库可以实现两个结构体比较
cpp
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/equal_to.hpp>
struct MyStruct {
int a;
float b;
std::string c;
};
BOOST_FUSION_ADAPT_STRUCT(MyStruct, a, b, c)
bool areEqual(const MyStruct& lhs, const MyStruct& rhs) {
return boost::fusion::equal_to(lhs, rhs);
}
C++常用的四种模板简述
函数模板: 定义一个通用的函数,函数可以根据指定的类型来推导
类模板:定义一个类的通用版本,类的类型参数可以在实例化的时候再进行指定
别名模板:给模板起一个别名,让模版更加方便的使用
变量模板:用于定义可以接受任何类型参数的常量,很少用
switch语句的case分支是否可以直接定义变量分析
- 结果 :不可以,会导致异常编译错误
- 原因:因为switch语句中所有的case标签共享同一个作用域,不声明其作用域会造成命名冲突
- 解决: 在case后面加一对{},来创建一个新的作用域就可以了
cpp
#include <iostream>
void example(int n) {
switch (n) {
case 1:
int a = 10; // 定义变量 a
std::cout << "Case 1: " << a << std::endl;
break;
case 2:
int b = 20; // 这里也定义了变量 b
std::cout << "Case 2: " << b << std::endl;
break;
default:
std::cout << "Default case" << std::endl;
}
}
int main() {
example(1);
example(2);
return 0;
}
cpp
#include <iostream>
void example(int n) {
switch (n) {
case 1: {
int a = 10; // 在新的作用域中定义变量 a
std::cout << "Case 1: " << a << std::endl;
break;
}
case 2: {
int b = 20; // 在新的作用域中定义变量 b
std::cout << "Case 2: " << b << std::endl;
break;
}
default:
std::cout << "Default case" << std::endl;
}
}
int main() {
example(1);
example(2);
return 0;
}
可变参数模版
- 含义:C++11的一种新模板特性,允许定义任意数量的参数
- 使用:具体参考代码事例理解
函数模版中可变参数模板的使用事例
cpp
#include <iostream>
// 基础情况:不接受任何参数时终止递归
void print() {
std::cout << std::endl;
}
// 可变参数模板函数
template <typename T, typename... Args>
void print(T first, Args... args) {
std::cout << first << " ";
print(args...); // 递归调用
}
int main() {
print(1, 2, 3.5, "hello", 'A');
return 0;
cpp
#include <iostream>
// 基础情况:没有参数时返回0
int sum() {
return 0;
}
// 可变参数模板函数
template <typename T, typename... Args>
int sum(T first, Args... args) {
return first + sum(args...); // 递归调用
}
int main() {
std::cout << sum(1, 2, 3, 4, 5) << std::endl; // 输出: 15
return 0;
}
类模板的可变参数
cpp
#include <iostream>
#include <string>
// 基础情况:空的元组
template <typename... Values>
class Tuple;
// 可变参数模板类
template <typename Head, typename... Tail>
class Tuple<Head, Tail...> : private Tuple<Tail...> {
public:
Tuple(Head head, Tail... tail) : Tuple<Tail...>(tail...), head_(head) {}
Head head() const { return head_; }
const Tuple<Tail...>& tail() const { return *this; }
private:
Head head_;
};
// 终止递归的空元组特化
template <>
class Tuple<> {};
int main() {
Tuple<int, double, std::string> t(42, 3.14, "hello");
std::cout << t.head() << std::endl; // 输出: 42
std::cout << t.tail().head() << std::endl; // 输出: 3.14
std::cout << t.tail().tail().head() << std::endl; // 输出: hello
return 0;
}