施磊老师高级c++(一)

对象被优化后, 才是高效的c++编程

文章目录

  • 对象被优化后, 才是高效的c++编程
    • 1.对象使用背后调用了哪些方法
    • 2.函数调用过程中对象背后调用方法
    • 3.总结三条对象优化的规则
    • 4.CMyString的代码问题
    • 5.添加带右值引用参数的拷贝构造和赋值函数
    • 6.String类在vector上的应用--面试题
    • 7.move移动语义和forword类型完美转发
      • move移动语义的作用
        • 代码:
        • **问题: **
        • 解决办法:
        • 最终代码:
      • forward完美转发内容
        • 引用折叠
        • push_back模板 -- 直接替代左右值引用push_back
        • forward完美转发
        • 带有完美转发的 完整代码
      • 总结

注意, 这节讲的, 有很多 和现代编译器的结果不同, 注意RVO的默认开启

1.对象使用背后调用了哪些方法

  1. 代码实例:

    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <functional>
    using namespace std;
    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&)" << endl;}Test& operator=(const Test& t){cout << "operator=" << endl;ma = t.ma;return *this;}
    private:int ma;
    };int main()
    {Test t1;Test t2(t1); // 拷贝构造Test t3 = t1; //拷贝构造//c++编译器对于对象构造的优化: 用临时对象生成新对象时,临时对象就不产生了, 直接构造新对象就行了Test t4 = Test(20);  // Test(20)临时对象,生存周期: 所在语句  //等价于Test t5(20);cout << "------------" << endl;t4 = t2;  // 赋值, 不是构造//operator=,  需要传参, //这两是 显示生成临时对象t4 = Test(30);  // 赋值, 这个临时对象必须生成, t4 = (Test)20; //int->Test(int), 编译器发现要转的类型里有int , 就可以编译器隐式转换, // 隐式生成临时对象t4 = 30; //int->Test(int)cout << "------------" << endl;Test* p = &Test(50);  // 临时对象, 出了这个语句就没了, 析构了. 地址也就没了const Test& ref = Test(50);  // 引用临时对象, 需要常引用, vs版本高, 检查严格// 引用是可以的,  引用虽然和指针很像, 但实际是  起别名, 离开这个语句, 临时对象不会析构//引用在C++中是一个别名(alias),它本质上是对某个对象的另一个名字。//临时对象变为了 引用变量, 声明周期是整个函数里return 0;
    }
    
  2. 结论:指针指向临时变量是不安全的, 但是 引用 是安全的

  3. 代码示例: 各种情况

    #include <iostream>
    #include <vector>
    #include <algorithm>
    #include <functional>
    using namespace std;class Test {
    public:Test(int a=5, int b=5) : ma(a), mb(b) {cout << "Test(int, int)" << endl;}~Test() {cout << "~Test()" << endl;}Test(const Test& t) : ma(t.ma), mb(t.mb) {cout << "Test(const Test&)" << endl;}Test& operator=(const Test& t) {cout << "operator=" << endl;ma = t.ma;mb = t.mb;return *this;}
    private:int ma;int mb;
    };Test t1(10, 10);  // 1. 全局变量先构造, 普通构造int main() {Test t2(20, 20);   // 3. 普通构造Test t3 = t2; // 4. 拷贝构造static Test t4 = Test(30, 30);  // 5. 静态局部变量, 程序运行到这里才 构造  临时对象被优化, 仅t4的普通构造    等价于 static Test t4(30, 30)t2 = Test(40, 40);  // 6. 显示生成临时对象 先临时对象普通构造, 再operator= 析构t2 = (Test)(50, 50); // 7. 隐式生成临时对象 先临时对象普通构造, 再operator= 析构t2 = 60;  //8. 隐式生成临时对象 先临时对象普通构造, 再operator= 析构Test* p1 = new Test(70, 70); // 9.指针, new了, 非临时对象, 普通构造 , 不析构, 没有deleteTest* p2 = new Test[2];  // 10. 数组, 两次普通构造 , 不析构, 没有delete//Test* p3 = &Test(80, 80);  // 11. 指针, 普通构造 , 但是出了这句 就析构了  成为 悬空指针 会被警告, 无法编译执行const Test& p4 = Test(90, 90); //12. 引用, 普通构造, 出了这句不析构, 出了函数析构delete p1;  // 13. 析构 p1delete[] p2; // 14. 析构 p2,两次析构
    }               // 15. 12析构, 4析构, 3析构, 5析构, 静态局部变量在数据段, 程序结束才析构, 2析构, 1析构Test t5(100, 100); // 2. 全局变量先构造, 普通构造
    

2.函数调用过程中对象背后调用方法

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;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&)" << endl;}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;  //不能返回 局部的或者临时对象的指针/*static Test tmp(val);return &tmp;*/
}int main() {Test t1;        // 1. 调用默认构造函数Test t2;        // 2. 调用默认构造函数t2 = GetObject(t1);  // 3. 调用拷贝构造函数(参数传递)// 4. 调用默认构造函数(创建 tmp)// 5. 调用拷贝构造函数(返回 tmp) 会返回一个临时对象, 这个不会被优化哈// 6. 析构 tmp// 7. 析构参数 t// 8. 调用赋值运算符(将返回值赋给 t2)// 9. 析构临时对象return 0;// 10. 析构 t2// 11. 析构 t1
}Test()
Test()
Test(const Test&)
Test()
~Test()
operator=
~Test()
~Test()
~Test()

注意: 以上标注只是NRVO情况, 现在的编译器默认开启RVO----自己补充 会变为9个

//5和9  由于现在大多数默认启用了RVO, 将不会存在  RVO 在 C++17 之后变成“强制优化”
// RVO(Return Value Optimization,返回值优化)是 C++ 编译器的一种优化技术,用于减少临时对象的创建,提升程序性能。它的核心思想是 避免拷贝构造,直接在目标位置构造对象。// RVO优化后
/*
Test GetObject(Test t) {int val = t.GetData();return Test(val);  // 直接构造在 t2 的内存位置上
}*/

3.总结三条对象优化的规则

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;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&)" << endl;}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) {  // 仅换为引用, 就减少了 t的拷贝构造和析构  int val = t.GetData();// Test tmp(val);//return tmp;  return Test(val);  // 返回临时对象, 会直接在main函数 构造临时对象   //这个在现代编译器无变化, RVO会默认开启//临时对象 优化 比较复杂, 先浅理解}int main() {Test t1;        Test t2 = GetObject(t1);       //而如果用临时对象 拷贝构造 新对象, 那么临时对象就不产生了, 直接构造新对象  减少为 4个, 直接调用构造, 也不需要 operator=return 0;}
  1. 函数参数传递过程中, 对象 优先 按 引用传递, 不要 值传递, 会减少很多 构造析构

    //未开启RVO
    Test()
    Test()
    Test(const Test&) // t拷贝构造
    Test()  
    Test(const Test&)  
    ~Test() 
    ~Test() // t析构
    operator=  
    ~Test() 
    ~Test()  
    ~Test()  //变为Test()
    Test() 
    Test()  
    Test(const Test&)  
    ~Test() 
    operator=  
    ~Test()  
    ~Test()  
    ~Test()
    
  2. 当函数 返回对象的 时候, 应该返回一个 临时对象, 而不要返回 一个定义过得 对象—这个在现代编译器无变化, RVO会默认开启

    Test()
    Test() 
    Test()  
    Test(const Test&)  // 返回值的tmp 拷贝构造 给main里的 临时对象
    ~Test()  // 析构返回值的这个 tmp
    operator=  
    ~Test()  
    ~Test()  
    ~Test() //变为Test()
    Test()
    Test()  // 返回临时对象, 会直接在main函数 构造临时对象
    operator=   
    ~Test()
    ~Test()
    ~Test()
    
  3. 当函数返回一个对象时,优先使用初始化方式接收返回值,而不是赋值方式。
    使用临时对象 直接 初始化 对象, 将会舍弃临时对象, 直接 使用默认构造

    Test()
    Test()
    Test() // 返回临时对象, 会直接在main函数 构造临时对象
    operator=   // 临时对象 operator= 赋值
    ~Test() // 析构临时对象
    ~Test()
    ~Test()//变为
    Test()
    Test()  // 直接默认构造 t2, 不要临时对象了
    ~Test()
    ~Test()
    

4.CMyString的代码问题

String类中间 会有大量的 new和 delete, 如果不做 对象优化, 效率会非常低!!!

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

重点最后两个函数

#include <iostream>
#include <cstring>
using namespace std;
class String
{
public:// 构造函数String(const char* p = nullptr){if (p != nullptr){_pstr = new char[strlen(p) + 1];cout << "new" << endl;strcpy(_pstr, p);}else{_pstr = new char[1];cout << "new" << endl;*_pstr = '\0';}cout << "String()" << endl;}// 析构函数~String(){delete[] _pstr;cout << "delete" << endl;_pstr = nullptr;cout << "~String()" << endl;}// 拷贝构造函数String(const String& other){_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);cout << "String(const String)" << endl;}// 赋值运算符重载String& operator=(const String& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);}cout << "operator=" << endl;return *this;}加法运算符重载--未优化, 多了一次new,delete//String operator+(const String& other) const//{//    char* newtmp = new char[strlen(_pstr) + strlen(other._pstr) + 1];//    strcpy(newtmp, _pstr);//    strcat(newtmp, other._pstr);//    String newString(newtmp);//    delete[]newtmp;//    return newString;//}// 加法运算符重载--小优化后String operator+(const String& other) const{String newString;newString._pstr = new char[strlen(_pstr) + strlen(other._pstr) + 1];cout << "new" << endl;strcpy(newString._pstr, _pstr);strcat(newString._pstr, other._pstr);return newString;}// 比较运算符重载bool operator>(const String& other) const{return strcmp(_pstr, other._pstr) > 0;}bool operator<(const String& other) const{return strcmp(_pstr, other._pstr) < 0;}bool operator==(const String& other) const{return strcmp(_pstr, other._pstr) == 0;}// 长度方法size_t length() const{return strlen(_pstr);}// 下标运算符重载char& operator[](size_t index){return _pstr[index];}const char& operator[](size_t index) const{return _pstr[index];}// 输出运算符重载friend std::ostream& operator<<(std::ostream& os, const String& str){os << str._pstr;return os;}// 输入运算符重载friend std::istream& operator>>(std::istream& is, String& str){char buffer[1024];is >> buffer;str = String(buffer);return is;  // 支持链式操作  例如: cin>>a>>b,第一次cin>>a返回cin, 即后面的变为 cin>>b}const char* c_str()const { return _pstr; }private:char* _pstr;
};String GetString(String& str)
{const char* pstr = str.c_str();String tmpstr(pstr);return tmpstr;   // 不开启RVO时, main函数这里会拷贝构造临时对象//return String(pstr);   
}int main()
{String str1 ="aaaaaaaaaa";String str2;str2 = GetString(str1);cout << str2.c_str() << endl;return 0;
}
  1. 在String类中, 开启RVO, 并不使用operator= 的构造,new,delete,析构 顺序

    new
    String()
    new
    String()
    new
    String()
    delete
    new
    operator=
    delete
    ~String()
    aaaaaaaaaa
    delete
    ~String()
    delete
    ~String()
    
  2. 回顾右值引用: ---- 右值是 没有名字(临时量)或者没内存
    普通引用只能引用左值, 且
    常引用 可以 引用 右值, 但不能修改

    右值引用 引用右值, 且能改变值

        int a = 10;int& b = a;cout << b << endl;//int&& c = a; // 这是不行的int&& c = 20;  // c可以改变临时量值c = 40;int &f = c;cout << c << endl;int tmp = 20;int& d = tmp;   d = 30;cout << d << endl;const int& e = 20;  // 常引用可以 引用右值, 但不能修改 值//e = 10;cout << e << endl;
    
  3. 带右值引用参数的拷贝构造-------- 实际代码里 没用–RVO使得没有 拷贝构造了

    String(String&& other)   // 换成右值引用   {//临时对象进入_pstr = other._pstr;  // 直接指向同一个资源other._pstr = nullptr;cout << "String(String&&)" << endl;}
  4. 带右值引用参数的operator= -------- 这个在代码里是 实际有效的 — 减少一次 new

    String& operator=(String&& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = other._pstr;  // 直接指向同一个资源other._pstr = nullptr;}return *this;}
    
  5. 总体代码:

    #include <iostream>
    #include <cstring>
    using namespace std;
    class String
    {
    public:// 构造函数String(const char* p = nullptr){if (p != nullptr){_pstr = new char[strlen(p) + 1];cout << "new" << endl;strcpy(_pstr, p);}else{_pstr = new char[1];cout << "new" << endl;*_pstr = '\0';}cout << "String()" << endl;}// 析构函数~String(){delete[] _pstr;cout << "delete" << endl;_pstr = nullptr;cout << "~String()" << endl;}// 左值引用拷贝构造函数String(const String& other){_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);cout << "String(const String)" << endl;}//右值引用拷贝构造String(String&& other)   // 换成右值引用   {//临时对象进入_pstr = other._pstr;  // 直接指向同一个资源other._pstr = nullptr;cout << "String(String&&)" << endl;}// 左值引用赋值运算符重载String& operator=(const String& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);}cout << "operator=" << endl;return *this;}//右值引用赋值运算符重载String& operator=(String&& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = other._pstr;  // 直接指向同一个资源other._pstr = nullptr;}cout << "operator=&&" << endl;return *this;}加法运算符重载--未优化, 多了一次new,delete//String operator+(const String& other) const//{//    char* newtmp = new char[strlen(_pstr) + strlen(other._pstr) + 1];//    strcpy(newtmp, _pstr);//    strcat(newtmp, other._pstr);//    String newString(newtmp);//    delete[]newtmp;//    return newString;//}// 加法运算符重载--小优化后String operator+(const String& other) const{String newString;newString._pstr = new char[strlen(_pstr) + strlen(other._pstr) + 1];cout << "new" << endl;strcpy(newString._pstr, _pstr);strcat(newString._pstr, other._pstr);return newString;}// 比较运算符重载bool operator>(const String& other) const{return strcmp(_pstr, other._pstr) > 0;}bool operator<(const String& other) const{return strcmp(_pstr, other._pstr) < 0;}bool operator==(const String& other) const{return strcmp(_pstr, other._pstr) == 0;}// 长度方法size_t length() const{return strlen(_pstr);}// 下标运算符重载char& operator[](size_t index){return _pstr[index];}const char& operator[](size_t index) const{return _pstr[index];}// 输出运算符重载friend std::ostream& operator<<(std::ostream& os, const String& str){os << str._pstr;return os;}// 输入运算符重载friend std::istream& operator>>(std::istream& is, String& str){char buffer[1024];is >> buffer;str = String(buffer);return is;  // 支持链式操作  例如: cin>>a>>b,第一次cin>>a返回cin, 即后面的变为 cin>>b}const char* c_str()const { return _pstr; }private:char* _pstr;
    };String GetString(String& str)
    {const char* pstr = str.c_str();String tmpstr(pstr);return tmpstr;   // 不开启RVO时, main函数这里会拷贝构造临时对象//return String(pstr);   
    }int main()
    {String str1 ("aaaaaaaaaa");String str2;str2 = GetString(str1);cout << str2.c_str() << endl;return 0;
    }
    
    new
    String()
    new
    String()
    new
    String()
    delete
    operator=&&
    delete
    ~String()
    aaaaaaaaaa
    delete
    ~String()
    delete
    ~String()
    

6.String类在vector上的应用–面试题

#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
#include <iostream>
#include <cstring>
using namespace std;
class String
{
public:// 构造函数String(const char* p = nullptr){if (p != nullptr){_pstr = new char[strlen(p) + 1];cout << "new" << endl;strcpy(_pstr, p);}else{_pstr = new char[1];cout << "new" << endl;*_pstr = '\0';}cout << "String()" << endl;}// 析构函数~String(){delete[] _pstr;cout << "delete" << endl;_pstr = nullptr;cout << "~String()" << endl;}// 左值引用拷贝构造函数String(const String& other){_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);cout << "String(const String)" << endl;}//右值引用拷贝构造String(String&& other)   // 换成右值引用   {//临时对象进入_pstr = other._pstr;  // 直接指向同一个资源other._pstr = nullptr;cout << "String(String&&)" << endl;}// 左值引用赋值运算符重载String& operator=(const String& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);}cout << "operator=" << endl;return *this;}//右值引用赋值运算符重载String& operator=(String&& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = other._pstr;  // 直接指向同一个资源other._pstr = nullptr;}cout << "operator=&&" << endl;return *this;}加法运算符重载--未优化, 多了一次new,delete//String operator+(const String& other) const//{//    char* newtmp = new char[strlen(_pstr) + strlen(other._pstr) + 1];//    strcpy(newtmp, _pstr);//    strcat(newtmp, other._pstr);//    String newString(newtmp);//    delete[]newtmp;//    return newString;//}// 加法运算符重载--小优化后String operator+(const String& other) const{String newString;newString._pstr = new char[strlen(_pstr) + strlen(other._pstr) + 1];cout << "new" << endl;strcpy(newString._pstr, _pstr);strcat(newString._pstr, other._pstr);return newString;}// 比较运算符重载bool operator>(const String& other) const{return strcmp(_pstr, other._pstr) > 0;}bool operator<(const String& other) const{return strcmp(_pstr, other._pstr) < 0;}bool operator==(const String& other) const{return strcmp(_pstr, other._pstr) == 0;}// 长度方法size_t length() const{return strlen(_pstr);}// 下标运算符重载char& operator[](size_t index){return _pstr[index];}const char& operator[](size_t index) const{return _pstr[index];}// 输出运算符重载friend std::ostream& operator<<(std::ostream& os, const String& str){os << str._pstr;return os;}// 输入运算符重载friend std::istream& operator>>(std::istream& is, String& str){char buffer[1024];is >> buffer;str = String(buffer);return is;  // 支持链式操作  例如: cin>>a>>b,第一次cin>>a返回cin, 即后面的变为 cin>>b}const char* c_str()const { return _pstr; }private:char* _pstr;
};String GetString(String& str)
{const char* pstr = str.c_str();String tmpstr(pstr);return tmpstr;   // 不开启RVO时, main函数这里会拷贝构造临时对象//return String(pstr);   
}int main()
{String str1 = "aaaaaaaaaa";vector<String> vec;vec.reserve(10);vec.push_back(str1);  //vec.push_back(String("bbbb"));return 0;
}

面试题: 问 两次push_back 分别是 使用了什么拷贝构造?!!!
一次左值引用拷贝, 一次右值引用拷贝
右值引用相比 左值引用, 更高效

new
String()
new
String(const String)
new
String()
String(String&&)
delete
~String()
delete
~String()
delete
~String()
delete
~String()

7.move移动语义和forword类型完美转发

move移动语义的作用

代码:

上一节代码里, 加上 很早之前写的 Vector类: 添加一个push_back的右值引用

#include <iostream>
#include <cstring>
using namespace std;#include <iostream>
using namespace std;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/*作用是在一块已经分配好的内存上构造一个对象,而不是通过 new 运算符动态分配内存。p 是一个指针,指向一块预先分配好的内存。T(val) 表示调用类型 T 的构造函数,并传递参数 val。new (p) T(val) 的意思是在 p 指向的内存地址上构造一个 T 类型的对象,并调用构造函数 T(val)。*/}void destroy(T* p) // 对象析构{p->~T(); //~T()代表T类型的析构函数}
};template<typename T, typename Alloc = Allocator<T>>  //Alloc默认是Allocator<T>
class Vector
{
private:T* _first;//数组起始,与数组名T* _last;//数组最后位置的下一个T* _end;//空间的后面位置Alloc _allocator;//定义容器空间配置对象void expand() //二倍扩容{int size = _last - _first;//T* ptmp = new T[2 * size];T* ptmp = _allocator.allocate(2 * size);for (int i = 0; i < size; i++){//ptmp[i] = _first[i];_allocator.construct(ptmp + i, _first[i]);}//delete[]_first;for (T* p = _first; p != _last; ++p){_allocator.destroy(p); //析构_first指针指向的数组的有效元素}_first = ptmp;_last = _first + size;_end = _first + 2 * size;}public:Vector(int size = 10){//_first = new T[size];// 只开辟内存_first = _allocator.allocate(size);_last = _first;_end = _first + size;}~Vector(){//delete[]_first;// 析构有效的元素并释放内存for (T* p = _first; p != _last; ++p){_allocator.destroy(p); //析构_first指针指向的数组的有效元素}_allocator.deallocate(_first); //释放堆上的数组内存_first = _last = _end = nullptr;}Vector(const Vector<T>& src){int size = src._end - src._first;//_first = new T[size];_first = _allocator.allocate(size);int len = src._last - src._first;for (int i = 0; i < len; i++){//_first[i] = src._first[i];_allocator.construct(_first + i, src._first[i]);}_last = _first + len;_end = _first + size;}Vector<T>& operator=(const Vector<T>& src){if (this == &src){return *this;}//delete[]_first;for (T* p = _first; p != _last; ++p){_allocator.destroy(p); //析构_first指针指向的数组的有效元素}int size = src._end - src._first;//_first = new T[size];_first = _allocator.allocate(size);int len = src._last - src._first;for (int i = 0; i < len; i++){//_first[i] = src._first[i];_allocator.construct(_first + i, src._first[i]);}_last = _first + len;_end = _first + size;return *this;}//添加右值引用push_backvoid push_back(T&& val) //向容器末尾添加元素{if (full()){expand();}_allocator.construct(_last, val);_last++;}void push_back(const T& val) //向容器末尾添加元素{if (full()){expand();}//*_last++ = val;_allocator.construct(_last, val);_last++;}void pop_back() //向容器末尾删除元素{if (empty()){return;}--_last;_allocator.destroy(_last);}bool full()const{return _last == _end;}bool empty()const{return _last == _first;}T back()const // 返回末尾元素{return *(_last - 1);// *(--_last)错误的, 本函数const方法, 不能修改成员变量, _last-1是偏移量, --会改变last值}
};class String
{
public:// 构造函数String(const char* p = nullptr){if (p != nullptr){_pstr = new char[strlen(p) + 1];cout << "new" << endl;strcpy(_pstr, p);}else{_pstr = new char[1];cout << "new" << endl;*_pstr = '\0';}cout << "String()" << endl;}// 析构函数~String(){delete[] _pstr;cout << "delete" << endl;_pstr = nullptr;cout << "~String()" << endl;}// 左值引用拷贝构造函数String(const String& other){_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);cout << "String(const String)" << endl;}//右值引用拷贝构造String(String&& other)   // 换成右值引用   {//临时对象进入_pstr = other._pstr;  // 直接指向同一个资源other._pstr = nullptr;cout << "String(String&&)" << endl;}// 左值引用赋值运算符重载String& operator=(const String& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);}cout << "operator=" << endl;return *this;}//右值引用赋值运算符重载String& operator=(String&& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = other._pstr;  // 直接指向同一个资源other._pstr = nullptr;}cout << "operator=&&" << endl;return *this;}加法运算符重载--未优化, 多了一次new,delete//String operator+(const String& other) const//{//    char* newtmp = new char[strlen(_pstr) + strlen(other._pstr) + 1];//    strcpy(newtmp, _pstr);//    strcat(newtmp, other._pstr);//    String newString(newtmp);//    delete[]newtmp;//    return newString;//}// 加法运算符重载--小优化后String operator+(const String& other) const{String newString;newString._pstr = new char[strlen(_pstr) + strlen(other._pstr) + 1];cout << "new" << endl;strcpy(newString._pstr, _pstr);strcat(newString._pstr, other._pstr);return newString;}// 比较运算符重载bool operator>(const String& other) const{return strcmp(_pstr, other._pstr) > 0;}bool operator<(const String& other) const{return strcmp(_pstr, other._pstr) < 0;}bool operator==(const String& other) const{return strcmp(_pstr, other._pstr) == 0;}// 长度方法size_t length() const{return strlen(_pstr);}// 下标运算符重载char& operator[](size_t index){return _pstr[index];}const char& operator[](size_t index) const{return _pstr[index];}// 输出运算符重载friend std::ostream& operator<<(std::ostream& os, const String& str){os << str._pstr;return os;}// 输入运算符重载friend std::istream& operator>>(std::istream& is, String& str){char buffer[1024];is >> buffer;str = String(buffer);return is;  // 支持链式操作  例如: cin>>a>>b,第一次cin>>a返回cin, 即后面的变为 cin>>b}const char* c_str()const { return _pstr; }private:char* _pstr;
};String GetString(String& str)
{const char* pstr = str.c_str();String tmpstr(pstr);return tmpstr;   // 不开启RVO时, main函数这里会拷贝构造临时对象//return String(pstr);   
}int main()
{String str1 = "aaaaaaaaaa";Vector<String> vec;vec.push_back(str1);  //vec.push_back(String("bbbb"));return 0;
}
new
String()
new
String(const String)
new
String()
new
String(const String)
delete
~String()
delete
~String()
delete
~String()
delete
~String()
**问题: **

发现没有使用 到 右值引用

//添加右值引用push_backvoid push_back(const T&& val) //向容器末尾添加元素{if (full()){expand();}_allocator.construct(_last, val);_last++;}

是因为, 这里面 给 _allocator.construct()的val 还是左值引用

解决办法:
  1. move移动语义, 强转成 右值引用类型

    _allocator.construct(_last, val);
    //变为
    _allocator.construct(_last, move(val));   //std::move
  2. 添加 construct 右值引用

    //右值引用 construct  里面的 也需要 move val
    void  construct(T* p, T&& val) //对象构造
    {new (p) T(move(val)); 
    }
    
最终代码:

----- 注意. 右值引用不加const

#include <iostream>
#include <cstring>
using namespace std;#include <iostream>
using namespace std;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/*作用是在一块已经分配好的内存上构造一个对象,而不是通过 new 运算符动态分配内存。p 是一个指针,指向一块预先分配好的内存。T(val) 表示调用类型 T 的构造函数,并传递参数 val。new (p) T(val) 的意思是在 p 指向的内存地址上构造一个 T 类型的对象,并调用构造函数 T(val)。*/}//右值引用 construct  里面的 也需要 move valvoid  construct(T* p, T&& val) //对象构造{new (p) T(move(val)); }void destroy(T* p) // 对象析构{p->~T(); //~T()代表T类型的析构函数}
};template<typename T, typename Alloc = Allocator<T>>  //Alloc默认是Allocator<T>
class Vector
{
private:T* _first;//数组起始,与数组名T* _last;//数组最后位置的下一个T* _end;//空间的后面位置Alloc _allocator;//定义容器空间配置对象void expand() //二倍扩容{int size = _last - _first;//T* ptmp = new T[2 * size];T* ptmp = _allocator.allocate(2 * size);for (int i = 0; i < size; i++){//ptmp[i] = _first[i];_allocator.construct(ptmp + i, _first[i]);}//delete[]_first;for (T* p = _first; p != _last; ++p){_allocator.destroy(p); //析构_first指针指向的数组的有效元素}_first = ptmp;_last = _first + size;_end = _first + 2 * size;}public:Vector(int size = 10){//_first = new T[size];// 只开辟内存_first = _allocator.allocate(size);_last = _first;_end = _first + size;}~Vector(){//delete[]_first;// 析构有效的元素并释放内存for (T* p = _first; p != _last; ++p){_allocator.destroy(p); //析构_first指针指向的数组的有效元素}_allocator.deallocate(_first); //释放堆上的数组内存_first = _last = _end = nullptr;}Vector(const Vector<T>& src){int size = src._end - src._first;//_first = new T[size];_first = _allocator.allocate(size);int len = src._last - src._first;for (int i = 0; i < len; i++){//_first[i] = src._first[i];_allocator.construct(_first + i, src._first[i]);}_last = _first + len;_end = _first + size;}Vector<T>& operator=(const Vector<T>& src){if (this == &src){return *this;}//delete[]_first;for (T* p = _first; p != _last; ++p){_allocator.destroy(p); //析构_first指针指向的数组的有效元素}int size = src._end - src._first;//_first = new T[size];_first = _allocator.allocate(size);int len = src._last - src._first;for (int i = 0; i < len; i++){//_first[i] = src._first[i];_allocator.construct(_first + i, src._first[i]);}_last = _first + len;_end = _first + size;return *this;}//添加右值引用push_backvoid push_back(T&& val) //向容器末尾添加元素{if (full()){expand();}_allocator.construct(_last, move(val));_last++;}void push_back(const T& val) //向容器末尾添加元素{if (full()){expand();}//*_last++ = val;_allocator.construct(_last, val);_last++;}void pop_back() //向容器末尾删除元素{if (empty()){return;}--_last;_allocator.destroy(_last);}bool full()const{return _last == _end;}bool empty()const{return _last == _first;}T back()const // 返回末尾元素{return *(_last - 1);// *(--_last)错误的, 本函数const方法, 不能修改成员变量, _last-1是偏移量, --会改变last值}
};class String
{
public:// 构造函数String(const char* p = nullptr){if (p != nullptr){_pstr = new char[strlen(p) + 1];cout << "new" << endl;strcpy(_pstr, p);}else{_pstr = new char[1];cout << "new" << endl;*_pstr = '\0';}cout << "String()" << endl;}// 析构函数~String(){delete[] _pstr;cout << "delete" << endl;_pstr = nullptr;cout << "~String()" << endl;}// 左值引用拷贝构造函数String(const String& other){_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);cout << "String(const String)" << endl;}//右值引用拷贝构造String(String&& other)   // 换成右值引用   {//临时对象进入_pstr = other._pstr;  // 直接指向同一个资源other._pstr = nullptr;cout << "String(String&&)" << endl;}// 左值引用赋值运算符重载String& operator=(const String& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);}cout << "operator=" << endl;return *this;}//右值引用赋值运算符重载String& operator=(String&& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = other._pstr;  // 直接指向同一个资源other._pstr = nullptr;}cout << "operator=&&" << endl;return *this;}加法运算符重载--未优化, 多了一次new,delete//String operator+(const String& other) const//{//    char* newtmp = new char[strlen(_pstr) + strlen(other._pstr) + 1];//    strcpy(newtmp, _pstr);//    strcat(newtmp, other._pstr);//    String newString(newtmp);//    delete[]newtmp;//    return newString;//}// 加法运算符重载--小优化后String operator+(const String& other) const{String newString;newString._pstr = new char[strlen(_pstr) + strlen(other._pstr) + 1];cout << "new" << endl;strcpy(newString._pstr, _pstr);strcat(newString._pstr, other._pstr);return newString;}// 比较运算符重载bool operator>(const String& other) const{return strcmp(_pstr, other._pstr) > 0;}bool operator<(const String& other) const{return strcmp(_pstr, other._pstr) < 0;}bool operator==(const String& other) const{return strcmp(_pstr, other._pstr) == 0;}// 长度方法size_t length() const{return strlen(_pstr);}// 下标运算符重载char& operator[](size_t index){return _pstr[index];}const char& operator[](size_t index) const{return _pstr[index];}// 输出运算符重载friend std::ostream& operator<<(std::ostream& os, const String& str){os << str._pstr;return os;}// 输入运算符重载friend std::istream& operator>>(std::istream& is, String& str){char buffer[1024];is >> buffer;str = String(buffer);return is;  // 支持链式操作  例如: cin>>a>>b,第一次cin>>a返回cin, 即后面的变为 cin>>b}const char* c_str()const { return _pstr; }private:char* _pstr;
};String GetString(String& str)
{const char* pstr = str.c_str();String tmpstr(pstr);return tmpstr;   // 不开启RVO时, main函数这里会拷贝构造临时对象//return String(pstr);   
}int main()
{String str1 = "aaaaaaaaaa";Vector<String> vec;vec.push_back(str1);  //vec.push_back(String("bbbb"));return 0;
}
new
String()
new
String(const String)
new
String()
String(String&&)
delete
~String()
delete
~String()
delete
~String()
delete
~String()

forward完美转发内容

引用折叠

引用折叠(Reference Collapsing)是 C++11 引入的一个规则,用于处理模板和类型推导中涉及引用的复杂情况。它是实现完美转发(Perfect Forwarding)和通用引用(Universal Reference)的基础。

template <typename T>
void foo(T&& arg) {// T 的类型和 arg 的类型由引用折叠规则决定
}
  • 如果传递一个左值(如 int x; foo(x);),T 推导为 int&arg 的类型为 int& &&,折叠为 int&
  • 如果传递一个右值(如 foo(42);),T 推导为 intarg 的类型为 int&&
push_back模板 – 直接替代左右值引用push_back
 template<typename Ty>  // 函数模板的类型推演 + 引用折叠void push_back(Ty&& val) {if (full()){expand();}_allocator.construct(_last, val);   // 这里还有问题, val传进去会被认为是左值_last++;}
forward完美转发

完美转发(Perfect Forwarding)是 C++11 引入的一项重要特性,用于在函数模板中保留参数的值类别(左值或右值),并将其原封不动地传递给其他函数。完美转发的核心目标是避免不必要的拷贝和移动操作,同时正确处理左值和右值。

完美转发通过以下两个特性实现:

  1. 通用引用(Universal Reference)

    • 使用 T&& 作为参数类型,可以根据传入参数的值类别推导出正确的类型(左值引用或右值引用)。
  2. std::forward

    • std::forward 是一个工具函数,用于保留参数的值类别。
    • 如果参数是左值,std::forward 返回左值引用。
    • 如果参数是右值,std::forward 返回右值引用。
  3. 最终,push_back 修改为

    template<typename Ty>  // 函数模板的类型推演 + 引用折叠
    void push_back(Ty&& val) 
    {if (full()){expand();}_allocator.construct(_last, forward<Ty>(val)); // std::forword(val) 类型的完美转发_last++;
    }
带有完美转发的 完整代码

construct 也可以使用模板, 不写两次

#include <iostream>
#include <cstring>
using namespace std;#include <iostream>
using namespace std;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
//        /*
//        作用是在一块已经分配好的内存上构造一个对象,而不是通过 new 运算符动态分配内存。
//
//p 是一个指针,指向一块预先分配好的内存。
//
//T(val) 表示调用类型 T 的构造函数,并传递参数 val。
//
//new (p) T(val) 的意思是在 p 指向的内存地址上构造一个 T 类型的对象,并调用构造函数 T(val)。
//
//        */
//    }
//    右值引用 construct  里面的 也需要 move val
//    //void  construct(T* p, T&& val) //对象构造
//    //{
//    //    new (p) T(move(val)); 
//    //}//模板, 引用折叠template<typename Ty>void  construct(T* p, Ty&& val) //对象构造{new (p) T(forward<Ty>(val));  // 注意Ty和 T}void destroy(T* p) // 对象析构{p->~T(); //~T()代表T类型的析构函数}
};template<typename T, typename Alloc = Allocator<T>>  //Alloc默认是Allocator<T>
class Vector
{
private:T* _first;//数组起始,与数组名T* _last;//数组最后位置的下一个T* _end;//空间的后面位置Alloc _allocator;//定义容器空间配置对象void expand() //二倍扩容{int size = _last - _first;//T* ptmp = new T[2 * size];T* ptmp = _allocator.allocate(2 * size);for (int i = 0; i < size; i++){//ptmp[i] = _first[i];_allocator.construct(ptmp + i, _first[i]);}//delete[]_first;for (T* p = _first; p != _last; ++p){_allocator.destroy(p); //析构_first指针指向的数组的有效元素}_first = ptmp;_last = _first + size;_end = _first + 2 * size;}public:Vector(int size = 10){//_first = new T[size];// 只开辟内存_first = _allocator.allocate(size);_last = _first;_end = _first + size;}~Vector(){//delete[]_first;// 析构有效的元素并释放内存for (T* p = _first; p != _last; ++p){_allocator.destroy(p); //析构_first指针指向的数组的有效元素}_allocator.deallocate(_first); //释放堆上的数组内存_first = _last = _end = nullptr;}Vector(const Vector<T>& src){int size = src._end - src._first;//_first = new T[size];_first = _allocator.allocate(size);int len = src._last - src._first;for (int i = 0; i < len; i++){//_first[i] = src._first[i];_allocator.construct(_first + i, src._first[i]);}_last = _first + len;_end = _first + size;}Vector<T>& operator=(const Vector<T>& src){if (this == &src){return *this;}//delete[]_first;for (T* p = _first; p != _last; ++p){_allocator.destroy(p); //析构_first指针指向的数组的有效元素}int size = src._end - src._first;//_first = new T[size];_first = _allocator.allocate(size);int len = src._last - src._first;for (int i = 0; i < len; i++){//_first[i] = src._first[i];_allocator.construct(_first + i, src._first[i]);}_last = _first + len;_end = _first + size;return *this;}template<typename Ty>  // 函数模板的类型推演 + 引用折叠void push_back(Ty&& val) {if (full()){expand();}_allocator.construct(_last, forward<Ty>(val)); // std::forword(val) 类型的完美转发_last++;}添加右值引用push_back//void push_back(T&& val) //向容器末尾添加元素//{//    if (full())//    {//        expand();//    }//    _allocator.construct(_last, move(val));//    _last++;//}//void push_back(const T& val) //向容器末尾添加元素//{//    if (full())//    {//        expand();//    }//    //*_last++ = val;//    _allocator.construct(_last, val);//    _last++;//}void pop_back() //向容器末尾删除元素{if (empty()){return;}--_last;_allocator.destroy(_last);}bool full()const{return _last == _end;}bool empty()const{return _last == _first;}T back()const // 返回末尾元素{return *(_last - 1);// *(--_last)错误的, 本函数const方法, 不能修改成员变量, _last-1是偏移量, --会改变last值}
};class String
{
public:// 构造函数String(const char* p = nullptr){if (p != nullptr){_pstr = new char[strlen(p) + 1];cout << "new" << endl;strcpy(_pstr, p);}else{_pstr = new char[1];cout << "new" << endl;*_pstr = '\0';}cout << "String()" << endl;}// 析构函数~String(){delete[] _pstr;cout << "delete" << endl;_pstr = nullptr;cout << "~String()" << endl;}// 左值引用拷贝构造函数String(const String& other){_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);cout << "String(const String)" << endl;}//右值引用拷贝构造String(String&& other)   // 换成右值引用   {//临时对象进入_pstr = other._pstr;  // 直接指向同一个资源other._pstr = nullptr;cout << "String(String&&)" << endl;}// 左值引用赋值运算符重载String& operator=(const String& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);}cout << "operator=" << endl;return *this;}//右值引用赋值运算符重载String& operator=(String&& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = other._pstr;  // 直接指向同一个资源other._pstr = nullptr;}cout << "operator=&&" << endl;return *this;}加法运算符重载--未优化, 多了一次new,delete//String operator+(const String& other) const//{//    char* newtmp = new char[strlen(_pstr) + strlen(other._pstr) + 1];//    strcpy(newtmp, _pstr);//    strcat(newtmp, other._pstr);//    String newString(newtmp);//    delete[]newtmp;//    return newString;//}// 加法运算符重载--小优化后String operator+(const String& other) const{String newString;newString._pstr = new char[strlen(_pstr) + strlen(other._pstr) + 1];cout << "new" << endl;strcpy(newString._pstr, _pstr);strcat(newString._pstr, other._pstr);return newString;}// 比较运算符重载bool operator>(const String& other) const{return strcmp(_pstr, other._pstr) > 0;}bool operator<(const String& other) const{return strcmp(_pstr, other._pstr) < 0;}bool operator==(const String& other) const{return strcmp(_pstr, other._pstr) == 0;}// 长度方法size_t length() const{return strlen(_pstr);}// 下标运算符重载char& operator[](size_t index){return _pstr[index];}const char& operator[](size_t index) const{return _pstr[index];}// 输出运算符重载friend std::ostream& operator<<(std::ostream& os, const String& str){os << str._pstr;return os;}// 输入运算符重载friend std::istream& operator>>(std::istream& is, String& str){char buffer[1024];is >> buffer;str = String(buffer);return is;  // 支持链式操作  例如: cin>>a>>b,第一次cin>>a返回cin, 即后面的变为 cin>>b}const char* c_str()const { return _pstr; }private:char* _pstr;
};String GetString(String& str)
{const char* pstr = str.c_str();String tmpstr(pstr);return tmpstr;   // 不开启RVO时, main函数这里会拷贝构造临时对象//return String(pstr);   
}int main()
{String str1 = "aaaaaaaaaa";Vector<String> vec;vec.push_back(str1);  //vec.push_back(String("bbbb"));return 0;
}

总结

move 是为了得到 右值类型
forward 是为了 能够正确识别 左值和右值类型—一般配合模板使用,很方便,不需要写那么多重载

模板中的 引用折叠 很好的解决了 重载麻烦的问题

很灵活的!!!

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

糊涂人寄信

1.糊涂人寄信 - 蓝桥云课 糊涂人寄信 题目描述 有一个糊涂人&#xff0c;写了 n 封信和 n 个信封&#xff0c;到了邮寄的时候&#xff0c;把所有的信都装错了信封。求装错信封可能的种类数。 输入描述 有多行读入&#xff0c;每行输入一个正整数 n&#xff0c;表示一种情况…

华为ISC+战略规划项目数字化转型驱动的智慧供应链革新(169页PPT)(文末有下载方式)

资料解读&#xff1a;华为ISC战略规划项目数字化转型驱动的智慧供应链革新 详细资料请看本解读文章的最后内容。 华为的ISC战略规划项目是其供应链数字化转型的核心&#xff0c;旨在通过智慧供应链的革新&#xff0c;提升企业的竞争力和运营效率。本文将从多个维度详细解读这…

深度学习框架PyTorch——从入门到精通(5)自动微分

使用torch.autograd自动微分 张量、函数和计算图计算梯度禁用梯度追踪关于计算图的更多信息张量梯度和雅可比乘积 在训练神经网络时&#xff0c;最常用的算法是反向传播。在该算法中&#xff0c;参数&#xff08;模型权重&#xff09;根据损失函数的梯度相对于给定参数进行调整…

Mobile-Agent-V:通过视频引导的多智体协作学习移动设备操作

25年2月来自北京交大和阿里巴巴公司的论文“Mobile-Agent-V: Learning Mobile Device Operation Through Video-Guided Multi-Agent Collaboration”。 移动设备使用量的快速增长&#xff0c;迫切需要改进自动化以实现无缝任务管理。然而&#xff0c;因缺乏操作知识&#xff0…

单片机开发资源分析的实战——以STM32F103C8T6为例子的单片机资源分析

目录 第一点&#xff1a;为什么叫STM32F103C8T6 从资源手册拿到我们的对STM32F103C8T6的资源描述 第二件事情&#xff0c;关心我们的GPIO引脚输出 第三件事情&#xff1a;去找对应外设的说明部分 前言 本文章隶属于项目&#xff1a; Charliechen114514/BetterATK: This is…

《基于Spring Boot+Vue的智慧养老系统的设计与实现》开题报告

个人主页:@大数据蟒行探索者 一、研究背景及国内外研究现状 1.研究背景 根据1982年老龄问题世界大会联合国制定的标准,如果一个国家中超过65岁的老人占全国总人口的7%以上,或者超过60岁的老人占全国总人口的10%以上,那么这个国家将被定义为“老龄化社会”[1]。 随着国…

微软OneNote无法同步解决方案

目录 前言原因UWP特性 解决方案C***h注册表 参考链接 前言 假设有多台Windows电脑&#xff0c;最方便且免费的多设备笔记同步方案就是微软自家的OneNote&#xff0c;使用OneDrive自带的5G云存储。 但是在国内大陆的OneNote&#xff0c;经常会出现无法同步、同步失败&#xff1…

硬件设计抽象级别详解:门级、RTL级、行为级与HLS

硬件设计抽象级别详解&#xff1a;门级、RTL级、行为级与HLS 引言 在数字系统设计领域&#xff0c;硬件描述语言(HDL)提供了多种抽象级别来描述电路功能和结构。从最底层的门级描述到高层的行为级描述&#xff0c;每一种抽象级别都有其特定的用途和优势。理解这些不同级别以及…

WPF程序使用AutoUpdate实现自动更新

AutoUpdate.NET使用 一、AutoUpdater.NET 简介 AutoUpdater.NET 是一个开源库&#xff0c;支持从各种源&#xff08;如GitHub、FTP、HTTP服务器等&#xff09;下载并安装更新。它提供了灵活的配置选项&#xff0c;允许开发者根据需求定制更新检查逻辑和用户体验。 二、安装 …

Qwen2-Audio:通义千问音频大模型技术解读

引言:从llm到mlm(audio) 大型语言模型(LLM)的发展日新月异,它们在文本理解、生成、推理等方面展现出惊人的能力。然而,交互模态不仅仅依赖于文字,语音、语调、环境音等听觉信息同样承载着丰富的内容。阿里巴巴通义千问团队,推出了 Qwen-Audio 系列模型,这里我们一起…

问题 | ACOS(X) 与 ACOSD(X)的区别

github&#xff1a;https://github.com/MichaelBeechan CSDN&#xff1a;https://blog.csdn.net/u011344545 [TOC](ACOS(X) 与 ACOSD(X)的区别) ACOSD(X) 是反余弦函数&#xff0c;结果以角度形式表示。ACOS(X) 用于计算 X 中每个元素的反余弦值。当 X 为复数时&#xff0c;结…

两款软件助力图片视频去水印及图像编辑

今天给大家分享两款呼声很高的软件&#xff0c;它们都能处理图片和视频去水印相关的问题。其中一款软件在去水印的同时&#xff0c;图像编辑功能也十分出色&#xff1b;另一款软件专注于图片和视频去水印&#xff0c;去除效果好且支持批量处理。下面就来详细了解一下。 Remover…

Hessian矩阵详解与应用

前言 本文隶属于专栏《机器学习数学通关指南》&#xff0c;该专栏为笔者原创&#xff0c;引用请注明来源&#xff0c;不足和错误之处请在评论区帮忙指出&#xff0c;谢谢&#xff01; 本专栏目录结构和参考文献请见《机器学习数学通关指南》 ima 知识库 知识库广场搜索&#…

【软件系统架构】单体架构

一、引言 在软件开发的漫长历程中&#xff0c;架构的选择一直是至关重要的决策。单体架构作为一种经典的架构模式&#xff0c;曾经在许多项目中发挥着不可替代的作用。虽然如今微服务等架构逐渐流行&#xff0c;但理解单体架构对于深入掌握软件架构体系仍然有着重要意义。 二、…

[C++初阶] :从C到C++

目录 C发展史&#xff0c;C语言的特性C新增关键字namespace关键字C语言的命名缺陷&#xff08;重定义现象&#xff09;域与指定访问操作符 “::”命名空间域详解namespace std C的输入与输出函数重载什么是重载,重载的几种常见形态重载的作用注意不构成重载的情况 缺省参数1.全…

[快乐学坊management_1] With Cursor | Mysql设计 | 服务接口设计与开发

目录 数据库设计流程 三张表 测试 接口设计 部门管理接口文档 1. 查询所有部门 2. 新增部门 ⭕3. 根据ID查询部门 4. 修改部门 5. 删除部门 &#xff08;部门分页条件查询&#xff09; 错误响应示例 接口设计规范 服务端开发 接口开发 数据库设计流程 01 明确业…

实用插件推荐 -------- 一个可以将任意语言(python、C/C++、go、java等)的程序转换为汇编语言的小插件

链接为&#xff1a; Compiler Explorer 界面&#xff1a; 参考自&#xff1a;如何获取虚函数表及内存分析_com的虚函数表怎么寻找-CSDN博客

vue学习八

十七 组件通信方式 1 props 父传子 //父组件 <script setup>//book来源省略import Subview1 from ./Subview1.vue;function updatebook(updatetimes){book.value.updatetimes updatetimes} </script> <template><Subview1 :book"book" :upd…

51单片机的寻址方式(完整)

目录 一、立即数寻址 二、直接寻址 三、寄存器寻址 四、寄存器间接寻址 五、变址寻址 六、位寻址 七、指令寻址 &#xff08;一&#xff09;绝对寻址 &#xff08;二&#xff09;相对寻址 在 51 单片机中&#xff0c;寻址方式是指在执行指令时&#xff0c;CPU 寻找操作…

每日一题:动态规划

如题&#xff08;基础题&#xff09;&#xff1a; 经典的爬楼梯问题&#xff0c;先从递归想起&#xff1b; class Solution { public:int climbStairs(int n) {if(n1)return 1;if(n2)return 2;return climbStairs(n-1)climbStairs(n-2);} }; 之后可以想办法&#xff08;如哈希…