【C++11】C++11类与模板语法的完善

目录

一,新的类功能

1-1,默认成员函数

1-2,强制生成关键字

二,可变参数模板

2-1,模板参数包

2-2,STL容器empalce的相关接口


一,新的类功能

1-1,默认成员函数

        C++11之前的类中有6个默认成员函数(默认成员函数就是我们不写编译器会生成一个默认的):1. 构造函数。2. 析构函数。3. 拷贝构造函数。4. 拷贝赋值重载。5. 取地址重载。6. const 取地址重载。这六个之中重要的是前4个,后两个取地址的用处不大。

        C++11新增了两个:移动构造函数移动赋值运算符重载。此两种默认成员函数与其它的默认成员函数有所不同,需说明以下三点。

        1,如果你没有自己实现移动构造函数,且没有自己实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,即浅拷贝。自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。

        2,如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,即浅拷贝。自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)

        3,如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值,这时需自己实现拷贝构造和拷贝赋值。因为当你自己编写移动构造或移动赋值后,说明你可能想要完全控制对象的拷贝行为,且有了移动语义通常也需要深拷贝,而默认的拷贝操作都是浅拷贝,所以为了确保对象的拷贝行为符合预期,有了移动语义后编译器不会默认生成拷贝构造和拷贝赋值。

        这里的代码演示需借助类结构演示,我们先简单设计String类,如下:

#include <cstring>
#include <iostream>
using namespace std;
namespace bit
{
    class string
    {
    public:
        //构造函数
        string(const char* str = "")
            :_size(strlen(str))
            , _capacity(_size)
        {
            cout << "string(char* str)" << endl;
            _str = new char[_capacity + 1];
            strcpy(_str, str);
        }
        //拷贝构造
        string(const string& s)
            :_str(nullptr)
        {
            cout << "string(const string& s) -- 深拷贝" << endl;
            string tmp(s._str);
            swap(tmp);
        }
        //赋值重载
        string& operator=(const string& s)
        {
            cout << "string& operator=(string s) -- 深拷贝" << endl;
            string tmp(s);
            swap(tmp);
            return *this;
        }
        //移动构造
        string(string&& s)
            :_str(nullptr)
            , _size(0)
            , _capacity(0)
        {
            cout << "string(string&& s) -- 移动语义" << endl;
            swap(s);
        }
        //移动赋值
        string& operator=(string&& s)
        {
            cout << "string& operator=(string&& s) -- 移动语义" << endl;
            swap(s);
            return *this;
        }
        //析构函数
        ~string()
        {
            delete[] _str;
            _str = nullptr;
        }
    private:
        void swap(string& s)
        {
            std::swap(_str, s._str);
            std::swap(_size, s._size);
            std::swap(_capacity, s._capacity);
        }
        char* _str;
        size_t _size;
        size_t _capacity; 
    };
}

        上面string类中自己实现了移动构造和移动赋值,编译器不会默认生成拷贝构造和拷贝赋值,若这里不自己实现拷贝构造和拷贝赋值,将不能实现有关拷贝构造和拷贝赋值的语句。下面来用以上类观察默认移动构造和默认移动赋值。

#include "String.h" //设计string的头文件
class Person
{
public:
    Person(const char* name = "", int age = 0)
        :_name(name) , _age(age)
    {    }
private:
    bit::string _name; 
    int _age;
};

/*自定义string类中实现了移动语义,不会生成默认拷贝构造和默认拷贝赋值,若自己不实现的话这里将会出错*/
int main()
{
    Person s1;
    cout << endl;
    //拷贝构造
    Person s2 = s1;
    cout << endl;
    
    //移动构造

    /*Preson满足默认移动构造的所有条件,它的自定义成员_name将会调用_name自己的移动构造,若没有移动构造将调用拷贝构造*/
    Person s3 = move(s1);  
    cout << endl;
    
    Person s4;
    //移动赋值

    /*与移动构造同理,若定义了_name的移动赋值将调用它移动赋值,若没有定义将调用它的拷贝赋值*/
    s4 = move(s2); 
    cout << endl;
    return 0;
}

        若在以上Person类中实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,将会输出不一样的结果。

class Person
{

    //.......
    ~Person() {    }  //在以上代码上加上了析构函数

    //.......
};

//main函数都一样

        这里可观察到默认移动语义比其它默认函数的条件更为苛刻,需满足两个条件:1,没有自己实现移动语义。2,没有自己实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。

        第一个条件好理解,第二个条件就不知所措了。其实这里之所以要这样设计还是跟析构函数 、拷贝构造、拷贝赋值重载三者的作用有关。系统默认生成的它们三个函数都是浅拷贝,当我们自己实现时说明需要深拷贝,这时说明我们需要控制对象的拷贝复制和移动行为,因此这里就不再默认生成,若有需要可自己实现。

1-2,强制生成关键字

强制生成默认函数的关键字default:

        C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。

#include "String.h" //头文件
class Person
{
public:
    Person(const char* name = "", int age = 0)
        :_name(name) , _age(age)
    {    }

    ~Person() {    }

    //上面自己编写了析构函数将不会有默认移动语义,这里强制生成默认移动语义
    Person(Person&& p) = default;
    Person& operator=(Person && p) = default;

    //上面强制生成了移动语义将不会有默认拷贝和默认赋值,这里强制生成默认拷贝和默认赋值
    Person(const Person& p) = default; 
    Person& operator=(const Person& p) = default;
private:
    bit::string _name; 
    int _age;
};
int main()
{
    //默认构造
    Person s1;
    cout << endl;

    //拷贝构造
    Person s2 = s1;
    cout << endl;

    //拷贝赋值
    s1 = s2;
    cout << endl;
    
    //移动构造
    Person s3 = move(s1); 
    cout << endl;
    
    //移动赋值
    Person s4;
    s4 = move(s1);
    cout << endl;

    return 0;
}

禁止生成默认函数的关键字delete:

        有些类有时我们可能不希望有个别的默认函数,如拷贝构造、拷贝赋值等。如果想要限制这些默认函数的生成,C++98的做法是自己只声明该函数,且将该函数设置成private(防止别人在类外实现),这样就可做到万无一失,只要调用就会报错。C++11新增关键字delete对此做出处理,只需在该函数声明加上 “=delete” 即可,该语法指示编译器不生成对应函数的默认版本。“=delete”修饰的函数为删除函数。

        下面以禁止Person类进行拷贝和赋值为例。

class Person
{
public:
    Person(const char* name = "", int age = 0)
        :_name(name), _age(age)
    {    }

    ~Person() {    };

    //C++11的做法,delete关键字直接解决
    Person(Person&& p) = delete;
    Person& operator=(Person && p) = delete;
private:
    //以下是C++98的做法,只声明不实现,且放到私有
    //Person(const Person& p);
    //Person& operator=(const Person & p);

    bit::string _name; 
    int _age;
};


二,可变参数模板

2-1,模板参数包

        C++11的新特性可变参数模板能够让我们创建可以接受任意数量和类型的模板包。模板参数包是将传入任意数量和类型的模板参数打包形成的一个整体。相比 C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变参数模版无疑是一个巨大的改进。可变参数模板的知识运用比较广泛,这里不纠结过于复杂的结构,掌握一些基础的可变参数模板特性就足够使用了。

        下面就是一个基本可变参数的函数模板,并尝试使用sizeof来输出参数个数。具体细节和说明如下:

/*Args是一个模板参数包,args是一个函数形参参数包。这里可对比template<class Args> void func(Args args);运用时将传入的模板参数打包,形成Args,然后通过Args参数包中的类型定义具有Args包中模板参数类型的函数形参,将其打包形成args函数参数包*/
template <class... Args> //模板参数包Args,具有0到N个类型的模板参数
void func(Args... args)    //函数参数包args,具有0到N个类型的函数参数
{
    cout << sizeof...(Args) << " ";   //输出模板参数包所包含的参数数量  
    cout << sizeof...(args) << endl; //输出函数参数包所包含的参数数量(它们实际上大小是一样的)
    /*注意: 无法使用sizeof(Args或args),因为都是参数包。参数包的整体使用前面要加上“...”说明*/
}

int main()
{
    func();  //无参形式
    func(1, 1, 2); //同类型参数形式
    func(1, 1.2, "bit", 'a');  //不同类型参数形式
    return 0;
}

        上面的参数Args前面有省略号,表示它是一个参数表,函数形参args同理。它们里面包含了0到N(N>=0)个模版参数,当函数实例化后,以上面func(1, 1.2, "bit", 'a');为例,编译器将会在编译时生成void func<int,double,const char*,char>(其它形式同理)。

        由于参数个数和类型不定,所以使用sizeof输出的是参数的个数。下面的问题是又该如何使用包里面的参数?我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。参数包中的个数可以使用sizeof获取,有些人可能会使用数组的形式使用,但C++11语法并不支持使用args[i]这样的方式获取可变参数。

递归函数方式展开参数包

        C++11使用递归函数方式展开参数包,具体形式类似于以下代码:

#include <iostream>
using namespace std;
//编译时递归返回条件,即递归终止函数
void _func() { cout << endl; }
//注意: 递归返回条件不能使用下面的,因为出现无参函数时将会出错,即出现func()的情况
//template <class T>
//void _func(const T& data) { cout << data << endl; }

/*编译时递归解析,依次拿到参数包args里面的参数传递给T类型的data,直到参数包args为空时调用递归终止函数_func()*/
template <class T, class... Args>
void _func(const T& data, Args ...args)
{
    cout << data << " ";
    _func(args...);  //递归遍历
}

//参数包args中有0-N个参数
template <class ...Args>
void func(Args ...args)
{
    _func(args...); //传入参数包
}


int main()
{
    func(); 
    func(1);
    func(1, 1, 1);
    func(1, 1.2, "bit", 'a'); 
    return 0;
}

        模板在编译阶段实例化,编译器实例化后将会把参数包展开,这里的递归过程可理解为参数的诼渐展开过程,以func(1, 1.2, 'a');为例,展开过程如下:

//编译阶段中,func(1, 1.2, 'a')在主函数传入模板被实例化后,将会推演生成以下类似的代码
void func(int val1, double ch, char s)
{
    _func(val1, ch, s);
}

//诼渐往下抽象递归解包参数,直到遍历到递归终止函数
void _func(const int& data, double ch, char s)
{
    cout << data << " ";
    _func(ch, s);  //往下调用void _func(const double& data, char s)
}

void _func(const double& data, char s)
{
    cout << data << " ";
    _func(s);  //往下调用void _func(const char& data)
}

void _func(const char& data)
{
    cout << data << " ";
    _func(); //递归终止函数
}

逗号表达式展开参数包

        逗号表达式的思想是通过展开表达式来依次拿到参数包里面的参数,与递归不同,系统调用时会生成N个表达式,然后通过这些表达式分别调用对应参数的模板函数,如下:

//模板函数

template <class T>
void _Func(T t)
{
    cout << t << " ";
}
//展开函数
template <class ...Args>
void Func(Args... args)
{
    int arr[] = { (_Func(args), 0)... }; /*使用逗号表达式(PrintArg(args), 0)...展开参数包args中参数,直到参数包为空*/

    cout << endl;
}


int main()
{
    Func(1); 
    Func(1, 'A');
    Func(1, 'A', std::string("sort"));
    return 0;
}

分析:

        运用时,{(_Func(args), 0)...}将会展开成{(_Func(arg1),0), (_Func(arg2), 0), (Func(arg3), 0), ... )},以上面Func(1, 'A')为例,这里会创建{(_Func(1),0), (_Func('A'), 0)},最终会创建一个元素值都为0的数组int arr[sizeof...(Args)]。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分_Func(args)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。虽然这个数组无用,但是若直接运用{(_Func(args), 0)...}表达式将不会自动展开,因为它没有类似数组的initializer_list展开,直接运用时系统报错。

        明白了上面原理后可将上面逗号表达式修改为以下式子,至于原因可根据上面的分析思考,若明白了这一原理后说明这方面已经理解。

//模板函数

template <class T>
int PrintArg(T t)
{
    cout << t << " ";
    return 0;
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{
    int arr[] = { PrintArg(args)... };
    cout << endl;
}

参数包的综合运用

        参数包“...”(三个点)的运用情况还有很多。“...”的位置不同,它所对应的情况也有所不同。这方面的逻辑调用分很多情况,但具体的运用理解思路对应的也就以下几种情况。

//样例一:

template <class ...Args> /*“...”放在参数包Args的左边,表示中间有N个参数打包成了Args,运用时将类型参数全部展开*/
void ShowList(Args... args) /*“...”表示参数包中有N个类型,为每个类型在Args中生成一个对应的函数参数,将其打包成args,运用时将形参和类型全部展开*/

运用时为了方便记忆,上面参数包“...”的定义都放在函数参数包和模板参数包的左边

//样例二

void _func() { cout << endl; }
template <class T, class... Args>
void _func(const T& data, Args ...args)
{
    _func(args...);  /*“...”放在“()”里面args的右边,表示具有N个类型,调用时只将传入的形参全部展开,如上面_func(val1, ch, s)递归展开*/
}

//样例三:

template <typename ...Args>
struct A
{
    T _data;
    template<class ...Args>
    A(Args&&... args): _data(forward<Args>(args)...) {} /*构造函数模板参数包的万能引用。“...”在参数包args括号“()”外面的右边,表示具有N个forward<Args>(args)式子,调用时会将其表达式展开,如同上面的逗号表达式*/
};

参数包的调用时,“...”要放在其后面,表示存在N个参数或式子,当“...”直接放在参数包名称的后面时,只是表示参数具有N个,调用时会将参数全部展开,如上面_func(val1, ch, s);当“...”放在表达式的后面时,表示表达式具有N个,调用时会将表达式全部展开,如上面逗号表达式的式子

        模板参数包灵活运用的基本理解如上,其它语法运用基本都是固定语法,如sizeof...(args)。若是出现这一固定搭配可自行查看专门文档,这里不再一一说明。 

        明白了这一原理后这里会发现一个潜在的问题,当传入的参数数量过多,即参数包过大,递归层数过多,系统会支撑不住这么大的开销,发生错误,实际应用中应注意。类似于以下代码:

template<size_t N>
void fun()
{
    cout << N << " ";
    fun<N - 1>();
}

template<>
void fun<1>()
{
    cout << 1 << endl;
}

int main()
{
    fun<15000>();  //传入参数数量过大,递归层数过多,系统支撑不下
    return 0;
}

2-2,STL容器empalce的相关接口

        C++中的emplace是一个常用的技术,主要用于在容器中就地构造元素,而不是先创建一个元素,然后将其复制到容器中。这种方法通常比先创建再复制(或移动)更高效,因为它减少了不必要的临时对象的创建和销毁。

   emplace通常在标准模板库(STL)的容器中看到,如vectorlistmapset等。每个这些容器都可能有一个或多个名为emplace_backemplace_frontemplace等的成员函数。下面我们以list容器中的emplace_backpush_back为例展示。

        其中emplace_back底层的接口是万能引用参数包,push_back底层的接口是const左值引用和右值引用。

示例:

        假设我们有一个vector<string>,并且我们想要添加一个新的字符串。使用传统的push_back插入方法类似于以下:

vector<string> v;

//使用v.push_back("bit");内部形式如同以下

string str = "bit"; //先构造出string的临时对象

v.push_back(str); //然后将此对象移动拷贝到v容器中

        若使用emplace我们可以直接在v中构造字符串,从而避免了不必要的复制(或移动)。

vector<string> vec;
vec.emplace_back("bit");  //直接在容器中构造新的string对象,没有创建临时的string对象

        这里不难发现,若是直接传递对象,emplace将不会做出任何改变。emplace的主要作用是为了减少不必要的拷贝,如同string str("bit")的优化,将拷贝+拷贝构造直接优化成拷贝构造,若是没有不必要的构造,emplace与其它功能相同的函数的使用没有任何区别。还有就是emplace不能使用"{}"(初始化列表)。主要是因为emplace函数不是通过列表初始化来构造对象的,而是使用直接初始化或拷贝初始化,因为多参数要进行隐式类型转换,若进行隐式类型转换就必须要构造出临时对象,与底层底层结构不匹配。当出现多参数时这里可直接使用“()”。

        下面我们使用上面模拟实现的string类来观察这一现象。

#include <list>
#include "String.h"
int main()
{
    list<bit::string> lt1;
    bit::string s1("xxxx");
    lt1.push_back(s1); //调用拷贝构造bit::string在容器中创建string对象
    cout << endl;
    lt1.push_back(move(s1)); //调用移动拷贝bit::string将资源转移到容器string对象中
    cout << "=============================================" << endl;
    //对象传递,没有临时对象的不必要拷贝,emplace_back的调用输出跟push_back一样
    bit::string s2("xxxx");
    lt1.emplace_back(s2);
    cout << endl;
    lt1.emplace_back(move(s2));
    cout << "=============================================" << endl;
    //先将"xxxx"构造成bit::string对象,然后拷贝到lt1容器中
    lt1.push_back("xxxx");
    cout << endl;
    //直接在lt1容器中构造数据是"xxxx"的bit::string对象
    lt1.emplace_back("xxxx");
    cout << "=============================================" << endl;
    list<pair<bit::string, bit::string>> lt2;
    //下面输出两个重复的信息是因为pair是两个指令
    pair<bit::string, bit::string> kv1("xxxx", "yyyy");
    lt2.push_back(kv1);
    lt2.push_back(move(kv1));
    cout << "=============================================" << endl;

    pair<bit::string, bit::string> kv2("xxxx", "yyyy");
    //对象的传递,即便存储类型是pair键值对也是同理,与push_back输出一样
    lt2.emplace_back(kv2);
    lt2.emplace_back(move(kv2));
    cout << "=============================================" << endl;
    lt2.emplace_back("xxxx", "yyyy");
    cout << endl;
    lt2.push_back({ "xxxx", "yyyy" });
    cout << "=============================================" << endl;
    return 0;
}

        通过以上可发现,由于emplace只接受非对象时的改进,而非对象在这方面的运用是构造+移动语义,emplace的改进只是省去了移动语义。移动语义只是移动资源,内部消耗很小,也就是说emplace的改善在实现有移动语义类的情况下效率没有多大提高,但是在没有实现有移动语义类的情况下效率将大大提升,如push_back内部是构造+拷贝构造,emplace_back内部是构造。

class Date
{
public:
    Date(int year, int month, int day)
        :_year(year), _month(month), _day(day)
    {
        cout << "Date(int year, int month, int day)" << endl;
    }
    Date(const Date& d) //拷贝构造,没有移动语义
        :_year(d._year), _month(d._month), _day(d._day)
    {
        cout << "Date(const Date& d)" << endl;
    }
private:
    int _year = 1;
    int _month = 1;
    int _day = 1;
};

int main()
{
    list<Date> lt1;
    lt1.push_back({ 2024,3,30 });
    cout << endl;
    lt1.emplace_back(2024, 3, 30);
    cout << endl << endl;

    //匿名对象。匿名对象也是对象,emplace与push的实现相同
    lt1.push_back(Date(2023, 1, 1));
    cout << endl;
    lt1.emplace_back(Date(2023, 1, 1));
    return 0;
}

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

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

相关文章

自拍欺骗成为流行的身份证件欺诈技术

据 Socure 称&#xff0c;文档图像叠加是 2023 年最流行的身份证件欺诈技术&#xff0c;在所有被拒绝的身份证件中&#xff0c;有 63% 发生这种情况。 自拍欺骗和冒充在与文件相关的身份欺诈中占主导地位 当用户拍摄照片或使用 ID 的屏幕截图图像&#xff08;而不是提供文档的…

【牛客】SQL201 查找薪水记录超过15条的员工号emp_no以及其对应的记录次数t

1、描述 有一个薪水表&#xff0c;salaries简况如下&#xff1a; 请你查找薪水记录超过15条的员工号emp_no以及其对应的记录次数t&#xff0c;以上例子输出如下&#xff1a; 2、题目建表 drop table if exists salaries ; CREATE TABLE salaries ( emp_no int(11) NOT N…

现代制造之Solidworks三维建模篇

现代制造 有现代技术支撑的制造业&#xff0c;即无论是制造还是服务行业&#xff0c;添了现代两个字不过是因为有了现代科学技术的支撑&#xff0c;如发达的通信方式&#xff0c;不断发展的互联网&#xff0c;信息化程度加强了&#xff0c;因此可以为这两个行业增加了不少优势…

【微服务】spring aop实现接口参数变更前后对比和日志记录

目录 一、前言 二、spring aop概述 2.1 什么是spring aop 2.2 spring aop特点 2.3 spring aop应用场景 三、spring aop处理通用日志场景 3.1 系统日志类型 3.2 微服务场景下通用日志记录解决方案 3.2.1 手动记录 3.2.2 异步队列es 3.2.3 使用过滤器或拦截器 3.2.4 使…

唤醒手腕 Go 语言 并发编程、Channel通道、Context 详细教程(更新中)

并发编程概述 ​ 一个进程可以包含多个线程&#xff0c;这些线程运行的一定是同一个程序&#xff08;进程程序&#xff09;&#xff0c;且都由当前进程中已经存在的线程通过系统调用的方式创建出来。进程是资源分配的基本单位&#xff0c;线程是调度运行的基本单位&#xff0c…

CSS---Emmet(二)

一、Emmet语法 Emmet语法是一种用于快速编写HTML和CSS的缩写技术。它允许开发者通过简洁的表达式快速生成复杂的代码结构&#xff0c;极大地提高了编码效率。使用Emmet&#xff0c;你只需要写出一些简短的缩写符号和操作符&#xff0c;然后通过快捷键&#xff08;通常是Tab键&…

[单机]完美国际_V155_GM工具_VM虚拟机

[端游] 完美国际单机版V155一键端PC电脑网络游戏完美世界幻海凌云家园 本教程仅限学习使用&#xff0c;禁止商用&#xff0c;一切后果与本人无关&#xff0c;此声明具有法律效应&#xff01;&#xff01;&#xff01;&#xff01; 教程是本人亲自搭建成功的&#xff0c;绝对是…

定时任务的几种实现方式

定时任务实现的几种方式&#xff1a; 1、JDK自带 &#xff08;1&#xff09;Timer&#xff1a;这是java自带的java.util.Timer类&#xff0c;这个类允许你调度一个java.util.TimerTask任务。使用这种方式可以让你的程序按照某一个频度执行&#xff0c;但不能在指定时间运行。…

kali linux2024.1版安装

1 基于 VMware 安装 Kali 系统 打开已经安装好的 VMware 程序&#xff0c;点击选项卡中的“主页”--》而后点击“创建新的虚拟机” 选择“典型(推荐)”&#xff0c;并点击“下一步” 客户机操作系统镜像选择&#xff1a;选择“稍后安装操作系统”&#xff0c;并点击“下一步”…

网安面经之SSRF漏洞

一、ssrf漏洞 1、ssrf原理&#xff1f;危害&#xff1f;修复&#xff08;防御&#xff09;&#xff1f; 原理&#xff1a;SSRF就是服务器端请求伪造漏洞、它是一种由攻击者构造&#xff0c;由服务端发起请求的一个网络攻击&#xff0c;一般用来在外网探测或攻击内网服务&…

使用Flask构建POST请求的Web应用

文章目录 准备工作创建路由处理POST请求创建表单页面运行应用结论 在Web开发中&#xff0c;处理POST请求是一项常见任务&#xff0c;特别是在构建表单提交、用户注册和数据提交等功能时。Flask是一个简单而强大的Python Web框架&#xff0c;它提供了方便的工具来处理HTTP请求&a…

WEB前端复习——CSS

CSS:层叠样式表 将显示样式与内容分开 基本语法&#xff1a; 选择器{ 规则; } ①标签选择器&#xff1a;以HTML标签名为选择 <style>p{color: red;} </style> <body><p>你好</p> </body> ②id选择器&#xff1a;一次性的 以#号定义 &l…

美颜画面调节SDK解决方案,打造专业级美颜画质

视频与图像的质量成为了企业展示自身形象、吸引用户注意力的关键&#xff0c;为了满足企业对高质量美颜画面调节的需求&#xff0c;美摄科技精心打造了一款面向企业的美颜画面调节SDK解决方案&#xff0c;该方案不仅集成了LUT和调色滤镜等先进技术&#xff0c;更在人脸场景画面…

luceda ipkiss教程 70:合并GDS版图

通过代码拼版&#xff1a; 所有代码如下&#xff1a; from si_fab import all as pdk from ipkiss3 import all as i3class Design1(i3.GDSCell):def _default_filename(self):return "Ring_Test.gds"def _default_name(self):return "Design1"class Des…

出现Duplicate key

解决&#xff1a; 第一种情况&#xff1a; 添加一个字段prjId &#xff0c;和数据库表映射时&#xff0c;映射的字段存在映射关系了。 将第二个 TableField中的prj_num改成prj_id 即可。 第二种情况&#xff1a; 转成map的形式时&#xff1a;key重复了&#xff0c;不知道把值赋…

计算机视觉——基于改进UNet图像增强算法实现

1. 引言 在低光照条件下进行成像非常具有挑战性&#xff0c;因为光子计数低且存在噪声。高ISO可以用来增加亮度&#xff0c;但它也会放大噪声。后处理&#xff0c;如缩放或直方图拉伸可以应用&#xff0c;但这并不能解决由于光子计数低导致的低信噪比&#xff08;SNR&#xff…

Swift 字符串和字符

字符串和字符 一、字符串字面量1、多行字符串字面量2、字符串字面量的特殊字符3、扩展字符串分隔符 二、初始化空字符串三、字符串可变性四、字符串是值类型五、使用字符六、连接字符串和字符七、字符串插值八、Unicode1、Unicode 标量2、可扩展的字形群集 九、计算字符数量十、…

搭建nacos集群

1.修改nacos/conf/application.properties 2.在数据库中执行nacos/conf/nacos-mysql.sql脚本 3.修改nacos/conf/cluster.conf文件 4.修改startup.sh文件模式为集群 5.启动服务 附&#xff1a;安装nginx 修改/usr/local/openresty/nginx/conf/nginx.confi文件 http{}中增加如下…

关于一致性,你该知道的事儿(上)

关于一致性&#xff0c;你该知道的事儿&#xff08;上&#xff09; 前言一、缓存一致性二、内存模型一致性三、事务一致性四、分布式事务一致性4.1 分布式系统的一些挑战4.2 关于副本的一些概念4.3 分布式事务之共识问题4. 3.1 PC(two-phase commit, 2PC)4.3.2 Raft 三、后记参…