【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-3,模板参数包的实际运用

        模板参数包通常与万能引用连用,实际运用中分两大模块:普通函数和构造函数。一般情况下的函数运用上面几个模块已说明的很清楚,这里重点说明构造函数方面的调用。

class Person 
{
public:
    Person(std::string name = "", int age = 0) 
        : name_(name)
        , age_(age)
    {}
    Person(const Person& it)
        : name_(it.name_)
        , age_(it.age_)
    {}
    std::string name_;
    int age_;
};
template<class T>
struct ListNode
{
    T _data;
    //const左值构造
    ListNode(const T& x = T()) 
        : _data(x) 
    {}
    //移动构造
    ListNode(T&& x)
        : _data(forward<T>(x))
    {}
    //万能引用构造
    template<class ...Args>
    ListNode(Args&&... args)
        : _data(forward<Args>(args)...) 
    {
        cout << sizeof...(args) << endl;
    }
};
int main()
{
    Person p1("Alice", 30);
    const Person p2("map", 40);
    /*调用万能引用构造,然后通过万能引用的构造调用Person的拷贝构造*/
    ListNode<Person> node1(p1); 
    /*调用const左值构造,然后通过ListNode的构造调用Person的拷贝构造*/
    ListNode<Person> node2(p2); 
    /*调用移动构造,然后通过ListNode的移动构造调用Person的拷贝构造*/
    ListNode<Person> node3(std::move(p1));
    /*调用万能构造,因为是多参,所以ListNode的构造相当于_data(forward<Args>("Bob"), forward<Args>(25)),然后再调用Person的构造*/
    ListNode<Person> node4("Bob", 25); 
    /*{ "Bob", 25 }初始化列表。由于C++初始化列表是一个整体,会构造成对象,所以这里要注意的是上面的万能引用构造和移动构造不能同时出现,因为两个构造都满足将{ "Bob", 25 }进行构造。*/

    /*万能引用会实例化成const char*和int,然后调用Person的构造函数构造_data(多参构造,将这里的“构造+拷贝构造”优化成构造)*/
    /*移动构造会实例化成ListNode(Person &&),先将{ "Bob", 25 }构造成Person对象,然后将Person对象拷贝赋值给_data*/
    /*最后重点说明下这里的("Bob", 25)与{ "Bob", 25 }意义不同,("Bob", 25)是两个整体,即"Bob"和25,{ "Bob", 25 }是一个整体,即Person。两者的调用实例化不同*/

    ListNode<Person> node5({ "Bob", 25 }); 
    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;
}

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

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

相关文章

Tomcat添加服务以及设置开机自启

下载地址连接 Index of /dist/tomcat&#x1f453; 注意点&#xff1a;不要出现中文路径 #环境变量 CATALINA_HOMED:\apache-tomcat-7.0.62 TOMCAT_HOMED:\apache-tomcat-7.0.62 JAVA_HOMED:\tool\jdk1.8.0_111 PATH%CATALINA_HOME%\bin;%CATALINA_HOME%\lib;%CATALINA_HOME%\…

对称加密介绍

一、什么是对称加密 对称密钥算法(Symmetric-key algorithm)&#xff0c;又称为对称加密、私钥加密、共享密钥加密&#xff0c;是密码学中的一类加密算法。 对称加密的特点是&#xff0c;在加密和解密时使用相同的密钥&#xff0c;或是使用两个可以简单地相互推算的密钥。 这…

超越传统游戏:生成式人工智能对游戏的变革性影响

人工智能&#xff08;AI&#xff09;在游戏中的应用 游戏产业是一个充满活力、不断发展的领域&#xff0c;人工智能&#xff08;AI&#xff09;的融入对其产生了重大影响。这一技术进步彻底改变了游戏的开发、玩法和体验方式。本文分析的重点是传统人工智能和生成式人工智能在游…

网络安全之弱口令与命令爆破(下篇)(技术进阶)

目录 一&#xff0c;什么是弱口令&#xff1f; 二&#xff0c;为什么会产生弱口令呢&#xff1f; 三&#xff0c;字典的生成 四&#xff0c;九头蛇&#xff08;hydra&#xff09;弱口令爆破工具 1&#xff0c;破解ssh登录密码 2&#xff0c;破解windows登录密码 3&#xf…

java项目之相亲网站的设计与实现源码(springboot+mysql+vue)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的相亲网站的设计与实现。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 相亲网站的设计与实…

Excel办公技巧之下拉菜单

在日常办工中&#xff0c;经常需在单元格中输入特定的值&#xff0c;此时我们可以使用下拉菜单解决&#xff0c;输入错误和错误值&#xff0c;可以一劳永逸的解决固定数据输入问题。 使用Excel下拉菜单时&#xff0c;它在数据输入和验证方面发挥着重要作用通过点击单元格的下拉…

权限及权限操作

1.命令行解释器 Linux将命令行解释器称为外壳程序shell 命令行解释器的作用就是将用户输入的指令转换为操作系统能够直接执行的指令。同时将操作系统的反馈转换为用户能看懂的反馈&#xff0c;解决了用户和操作系统沟通成本的问题。与此同时&#xff0c;命令行解释器还能够拦…

乡村旅游指标-最美乡村数、旅游示范县数、旅行社数、景区数、农家乐数(2007-2021年)

01、数据介绍 乡村旅游也是促进乡村经济发展的有效途径。通过发展乡村旅游&#xff0c;可以带动乡村相关产业的发展&#xff0c;提高乡村居民的收入&#xff0c;促进乡村的经济发展和社会进步。此外&#xff0c;乡村旅游还能促进城乡交流&#xff0c;推动城乡统筹发展。 数据…

rt-thread 挂载romfs与ramfs

参考&#xff1a; https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/tutorial/qemu-network/filesystems/filesystems?id%e4%bd%bf%e7%94%a8-romfs https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/tutor…

香港虚拟主机哪里可以试用?用于企业建站的

香港虚拟主机适合个人、企业建站&#xff0c;包括外贸企业网站、个人博客网站、中小企业官网等&#xff0c;那么作为新手不知道哪家香港虚拟主机好用的时候&#xff0c;该如何找到可以试用的香港虚拟主机呢&#xff1f; 香港虚拟主机也称作香港空间、香港虚拟空间&#xff0c;…

DS:时间复杂度和空间复杂度

欢迎各位来到 Harper.Lee 的学习世界&#xff01; 博主主页传送门&#xff1a;Harper.Lee的博客主页 想要一起进步的uu欢迎来后台找我哦&#xff01; 本片博客主要介绍的是数据结构中关于算法的时间复杂度和空间复杂度的概念。 一、算法 1.1 什么是算法&#xff1f; 算法(Alg…

QT--2

Qt界面设计 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent) {//窗口相关设置this->resize(680,520);this->setFixedSize(680,520);this->setWindowTitle("Tim");this->setWindowFla…

Linux进程控制——Linux进程终止

前言&#xff1a;前面了解完前面的Linux进程基础概念后&#xff0c;我们算是解决了Linux进程中的一大麻烦&#xff0c;现在我们准备更深入的了解Linux进程——Linux进程控制&#xff01; 我们主要介绍的Linux进程控制内容包括&#xff1a;进程终止&#xff0c;进程等待与替换&a…

【机器学习数据可视化-04】Pyecharts数据可视化宝典

一、引言 在大数据和信息爆炸的时代&#xff0c;数据可视化成为了信息传递和展示的关键手段。通过直观的图表和图形&#xff0c;我们能够更好地理解数据&#xff0c;挖掘其背后的信息。Pyecharts&#xff0c;作为一款基于Python的数据可视化库&#xff0c;凭借其丰富的图表类型…

数据结构-二叉树-红黑树

一、红黑树的概念 红黑树是一种二叉搜索树&#xff0c;但在每个节点上增加一个存储位表示节点的颜色&#xff0c;可以是Red或者BLACK&#xff0c;通过对任何一条从根到叶子的路径上各个节点着色方式的限制&#xff0c;红黑树确保没有一条路径会比其他路径长出两倍&#xff0c;…

Crossplane 实战:构建统一的云原生控制平面

1 什么是 Crossplane Crossplane 是一个开源的 Kubernetes 扩展&#xff0c;其核心目标是将 Kubernetes 转化为一个通用的控制平面&#xff0c;使其能够管理和编排分布于 Kubernetes 集群内外的各种资源。通过扩展 Kubernetes 的功能&#xff0c;Crossplane 对 Kubernetes 集群…

Go实现树莓派读取at24c02 eeprom读写数据

步骤 启用i2c 参考 Go实现树莓派读取bh1750光照强度 代码 package mainimport ("fmt""periph.io/x/conn/v3/i2c" )type AT24C02Device struct {dev *i2c.Dev }func NewAT24C02Device(addr uint16, bus i2c.BusCloser) (*AT24C02Device, error) {var (d…

利用github pages建立Serverless个人博客

利用github pages建立Serverless个人博客 概述 使用github pages&#xff0c;可以在github上部署静态网站。利用这个功能&#xff0c;可以很方便地实现个人博客的发布托管。 比如我的个人博客&#xff1a;Buttering’s Blog 对应代码仓库&#xff1a;buttering/EasyBlog: 自…

Ubuntu20.4部署Cuda12.4

准备Ubuntu20.4 VM 安装Cuda12.4 1.进入如下界面安装安装Cuda12.4版本&#xff1a; CUDA Toolkit 12.4 Update 1 Downloads | NVIDIA Developerhttps://developer.nvidia.com/cuda-downloads?target_osLinux&target_archx86_64&DistributionUbuntu&target_vers…

图像分割各种算子算法-可直接使用(Canny、Roberts、Sobel)

Canny算子&#xff1a; import numpy as np import cv2 as cv from matplotlib import pyplot as pltimg cv.imread("../test_1_1.png") edges cv.Canny(img, 100, 200)plt.subplot(121),plt.imshow(img,cmap gray) plt.title(Original Image), plt.xticks([]), …