【C++】C++中的继承

目录

介绍:

一,继承的访问权限

二,基类和派生类对象赋值转换

三,继承中的作用域

四,派生类的默认成员函数

1,构造函数

2,析构函数

3,拷贝构造和赋值运算符

五,继承中的友元与静态成员

1,继承与友元

2,继承与静态成员

六,复杂的菱形继承及菱形虚拟继承

七,继承与组合
​​​​​​​


介绍:

        继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。

        这里被复用的类叫做基类或父类,复用后产生新的类叫做派生类或子类。

普通继承:

class Person
{
public:
    void Print()
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;
    }
protected:
    string _name = "peter"; 
    int _age = 18;  
};
class Student : public Person //子类Student继承父类Person的成员(即成员函数+成员变量),继承后都会变成子类的一部分,通过调试窗口即可见
{
protected:
    int _stuid; 
};
class Teacher : public Person  //同理,继承父类Person
{
protected:
    int _jobid; 
};
int main()
{
    Student s;  
    Teacher t;
    //由于继承后将会成为子类的一部分,这里可看作子类中的成员
    s.Print(); 
    t.Print();
    cout << sizeof(Student) << endl << sizeof(Teacher) << endl; //发现占用的内存变大,因为继承,子类中包含父类,内存变大
    return 0;
}

模板继承:

//模板继承
template <class T>
class B : public A<T>
{
    .......
};

        以上继承中,Person是父类(基类)。Student和Teacher是子类(派生类)。我们以Student为例,如下:


一,继承的访问权限

        下面,我们谈谈这里的继承方式中的访问限定符和访问权限的问题。先来观察基类访问权限与派生类的关系。

        当基类的访问权限为private类型时,除非在自己类中运用自己的成员函数可进行访问,派生类无论怎样都访问不了。

        当基类的访问权限为protected类型时,在派生类中可进行访问,但在类外不可进行访问。也就是说在类外时如同private权限。        

        当基类的访问权限为public类型时,无论在派生类中,还是在类外,都允许直接访问。

        继承方式中的权限限制的就是基类中的权限。基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符与继承方式),其中public > protected > private。即如下:

        当继承方式是public时,基类的private是private,public是public,protected是protected。

        当继承方式是protected,基类的private是private,public是protected,protected是protected

        当继承方式是private时,继承基类的所有权限都为private。

#include <iostream>
using namespace std;
class Person
{
public:
    void Print()
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;
    }
protected:
    string _name = "peter"; 
    int _age = 18;  
};
class Student : public Person
{
public:
    void func() //继承方式是public权限,访问时与基类Person中默认取最小权限 
    {
        cout << _name << endl;
        cout << _age << endl;
        Print();
    }
protected:
    int _stuid; 
};
int main()
{
    Student s;
    s.func();  //没有私有权限,都可被派生类访问
    return 0;
}

        需说明一点,于class而言,若继承方式的权限不写,默认为私有private;于struct而言,若继承方式的权限不写,默认为公有public,跟默认成员权限一样。

        补:在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。


二,基类和派生类对象赋值转换

        派生类对象可以赋值给基类的对象 / 基类的指针 / 基类的引用,但基类对象不能赋值给派生类对象。因为子类对象(派生类)中都有父类(基类)成员,所以,子类对象可以赋值给父类对象,但是子类的成员父类不一定拥有,所以父类不能赋值给子类。

#include <iostream>
using namespace std;
class Person  //父类(基类)
{
public:
    void Print()
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;
    }
protected:
    string _name = "peter"; 
    int _age = 18;  
};
class Student : public Person  //子类(派生类)
{
public:
    void func() 
    {
        cout << _name << endl;
        cout << _age << endl;
        Print();
    }
protected:
    int _stuid; 
};
int main()
{
    Student s;
    Person p = s;    //子类对象s赋值父类的对象p
    Person* pp = &s; //子类s的地址赋值给父类的指针pp
    Person& rp = s;  //子类对象s赋值给父类的引用rp
    
    //s = p; //报错,父类对象不能赋值给子类对象
    return 0;
}

        注意,这里重点说明一下,不同类型赋值会产生临时变量,赋予的数据也是临时变量,但这里的子类赋值于父类是不会产生临时变量。这里的子类对象赋值给父类对象如同切割一样,将子类中所包含父类的成员切割下来给父类对象,即子类比父类所占用的空间大。如下图:

        父类(基类)向子类(派生类)的转换在一定条件下也可进行,但这方面设计到一定的东西,这里先不做过多研究,后文会详细说明。


三,继承中的作用域

        1. 在继承体系中基类和派生类都有独立的作用域。

        2. 由于子类和父类都有独立的作用域,所以子类和父类中可以有同名成员。当在子类中访问同名成员时,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。若想访问父类中的同名成员时,在子类成员函数中使用 基类::基类成员 显示访问基类成员(成员:成员函数和普通成员)。 

        3. 需要注意的是隐藏关系是对于继承关系中子类与父类中同名成员的关系,只要重名即构成隐藏。如果是成员函数的隐藏,只需要函数名相同就构成隐藏,返回值和参数可以不同。隐藏的本质是子类隐藏父类的同名成员。

        4. 注意在实际中在继承体系里面最好不要定义同名的成员。

//A和B的fun函数是隐藏关系
//注意: 这里的fun不是重载关系,重载关系是在同一作用域下的关系,这里的基类与派生类是两个不同的作用域

//对于基类和派生类而言,只要满足成员名相同即构成隐藏关系,所以,成员如果是函数,函数名相同就构成隐藏关系
class A
{
public:
    void fun()
    {
        cout << "func()" << endl;
    }
};
class B : public A
{
public:
    void fun(int i)
    {
        A::fun();
        cout << "func(int i)->" << i << endl;
    }
};

int main()
{
    B b;
    b.fun(1);
    //b.fun();//调用错误,因为父类的fun被隐藏了,这里调用的是派生类中的fun
    b.A::fun(); //调用基类A的fun,需指定作用域
    return 0;
}


四,派生类的默认成员函数

        派生类一共存在6个默认构造函数,这里我们只需要了解四个即可,即构造函数、析构函数、拷贝构造函数、赋值运算符重载。在继承关系中,父类的这些函数都是通过子类进行调用的。

1,构造函数

        对于构造函数,由于构造函数的初始化列表是按照初始化对象的声明顺序进行初始化,而子类是先继承父类,所以这里先对父类初始化,即先调用父类的构造函数,然后再对子类进行初始化,即后调用子类的构造函数。如以下:

#include <iostream>
using namespace std;

class Person
{
public:
    Person(const char* name = "peter") //普通构造函数
        : _name(name)
    {   }
protected:
    string _name;
};
class Student : public Person
{
public:
    Student(const char* name, int num)
        //:_name(name) 错误,这里不允许这样对父类成员初始化,必须调用父类的构造函数对父类成员初始化
        : Person(name) //调用父类构造函数,若不写,这里会自动先调用父类的匹配构造函数,然后再调用子类的构造函数
        //这里要注意,若不显示调用父类的构造函数,当没有与之默认匹配的构造函数时将会报错

        , _num(num)
    {   }
protected:
    int _num;
};
int main()
{
    Student s("兔子", 1); 
    return 0;
}

        父类的构造函数可直接手动调用,也可让系统自动调用吗,但这里建议手动调用,因为当自动调用时,若不存在匹配的构造函数时将会出错。如下:

#include <iostream>
using namespace std;
class Person
{
public:
    Person(const char* name) //这里的构造函数默认情况下不能调用,因为不匹配
        : _name(name)
    {        }
protected:
    string _name;
};
class Student : public Person
{
public:
    Student(const char* name, int num)  //报错,因为不存在默认的构造函数
        : _num(num)
    {        }
protected:
    int _num;
};
int main()
{
    Student s("兔子", 1);
    return 0;
}

2,析构函数

        析构函数系统默认会先调用子类的析构函数,然后调用父类的析构函数,这样做是为了避免安全隐患。因为子类包含父类,若父类动态指向一块内存空间时,若先析构父类时,此空间已经被释放,子类析构时会再次析构此空间。

        众所周知,构造函数不能手动调用,但是析构函数可以,所以说这里我们可强行先调用父类的析构函数。但是要注意的是,子类的析构函数与父类的析构函数构成隐藏关系(由于多态的原因,析构函数被特殊处理),不能在子类的析构函数中直接显示调用。这里跟隐藏关系调用的逻辑一样,需指名作用域。

        这里不建议手动调用析构函数,这里的规则跟析构函数的规则一样,系统会自动调用,也就是说手动析构完之后系统也会再次调用。

#include <iostream>
using namespace std;
class Person
{
public:
    Person(const char* name = "peter") 
        : _name(name)
    {
        cout << "Person()" << endl;
    }
    ~Person()
    {
        cout << "~Person()" << endl;
    }
protected:
    string _name;
};
class Student : public Person
{
public:
    Student(const char* name, int num)
        : Person(name)
        , _num(num)
    {
        cout << "Student()" << endl;
    }
    ~Student() 
    {
        Person::~Person();//若不显示调用父类的析构函数,会默认先调用本类(子类)的析构函数,然后再调用父类Person的析构函数
        cout << "~Student()" << endl; 
    }
protected:
    int _num;
};
int main()
{
    Student s("兔子", 1); 
    return 0;//发现系统结束时输出了两次父类析构函数中的内容
}

3,拷贝构造和赋值运算符

        调用父类的赋值运算符时需注意指定作用域,而拷贝构造是将子类对象拷贝构造给父类,如同子类赋值父类般,进行切割。

#include <iostream>
using namespace std;
class Person
{
public:
    Person(const char* name = "peter") 
        : _name(name)
    {
        cout << "Person()" << endl;
    }

    Person(const Person& p) 
        : _name(p._name)
    {
        cout << "Person(const Person& p)" << endl;
    }
    Person& operator=(const Person& p)
    {
        cout << "Person operator=(const Person& p)" << endl;
        if (this != &p)
            _name = p._name;
        return *this;
    }
protected:
    string _name; 
};
class Student : public Person
{
public:
    Student(const char* name, int num)
        : Person(name)
        , _num(num)
    {
        cout << "Student()" << endl;
    }

    Student(const Student& s)
        : Person(s) //这里的拷贝构造也是将子类所包含父类的成员切割给父类,如同将子类对象赋值给父类对象般。赋值运算符同理
        , _num(s._num)
    {
        cout << "Student(const Student& s)" << endl;
    }

    Student& operator = (const Student& s)
    {
        cout << "Student& operator= (const Student& s)" << endl;
        if (this != &s)
        {
            Person::operator=(s); //注意这里要指定作用域,因为是隐藏关系
            _num = s._num;
        }
        return *this;
    }
protected:
    int _num; 
};
int main()
{
    Student s("兔子", 1);
    Student s1(s);
    s1 = s; 
    return 0;
}

总:这里的所有知识点可理解为跟单独的类一样,父类看作是子类的成员,父类的所有基本功能都要靠子类所对应的基本功能来实现。


五,继承中的友元与静态成员

1,继承与友元

        这里这里友元关系不能继承,即基类的友元不能被子类继承,也就是说基类友元不能访问子类私有和保护成员(当权限公有时有没有友元都一样,直接可被访问)。要想访问子类成员,这里必须也在子类中声明。

#include <iostream>
using namespace std;
class Student; //声明Student类,因为下面Display要使用
class Person
{
public:
    friend void Display(const Person& p, const Student& s);
    string _name = "张三";
};

class Student : public Person
{
    //friend void Display(const Person& p, const Student& s);  这里在子类中也声明友元,可正常运行
//public:  权限是公有,在类外可直接访问,不需要友元
protected: //权限为私有或保护,由于友元不能被继承,所以不可访问数据
    int _stuNum = 5; 
};
void Display(const Person& p, const Student& s)
{
    cout << p._name << endl;
    cout << s._stuNum << endl;  //权限公有运行正常。权限私有或保护不可访问,运行错误
}
int main()
{
    Person p;
    Student s;
    Display(p, s);
    return 0;
}

2,继承与静态成员

        静态成员存储在静态区,不在类中,也就说当基类存在静态成员时,子类不会将其直接继承下来复制一份,而是跟基类一样,直接调用静态区里的静态成员,即整个继承体系里面只有一个这样的静态成员。无论基类有多少个子类,都只有一个static成员实例。 

#include <iostream>
using namespace std;
class Person
{
public:
    Person() { ++_count; }
protected:
    string _name; 
public:
    static int _count; 
};

int Person::_count = 0;

class Student : public Person
{
protected:
    int _stuNum; 
};

int main()
{
    Person p;
    Student s;
    cout << Person::_count << endl;  //输出2
    cout << Student::_count << endl;  //输出2
}


六,复杂的菱形继承及菱形虚拟继承

单继承:一个子类只有一个直接父类时称这个继承关系为单继承。以上所有的实例都为单继承。

多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承。

菱形继承:菱形继承是多继承的一种特殊情况。

        菱形继承存在很多问题,结构比较复杂。我们先来观察以下代码样例。

#include <iostream>
using namespace std;
class Person
{
public:
    string _name;
};

class Student : public Person
{
protected:
    int _num; 
};

class Teacher : public Person
{
protected:
    int _id;
};

class Assistant : public Student, public Teacher
{
protected:
    string _majorCourse; 
};

int main()
{
    Assistant a;
    //a._name = "pter";//这样会有二义性无法明确知道访问的是哪一个
    //以下为正确写法,需跟一般继承一样,指名作用域
    //这样虽暂时解决了二义性,但没有解决本质问题。有时我们不需要存储两份,这样会造成空间的浪费,也就是数据冗余问题无法解决

    a.Student::_name = "张三";
    a.Person::_name = "李四";
    return 0;
}

        菱形继承的问题:从上面的对象成员模型构造可以看出,菱形继承有数据冗余和二义性的问题。 在Assistant的对象中Person成员会有两份。存储结构如下图:

        虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和 Teacher的继承Person时使用虚拟继承即可解决问题。

        虚拟继承的使用方法是在继承前加上关键字virtual,此时所有加上关键字virtual的类将会特殊处理。此时可以理解它们所继承的父类共用一块空间。具体使用方法如下:

#include <iostream>
using namespace std;
class Person
{
public:
    string _name;
};

class Student : virtual public Person //增加关键字virtual,即虚继承
{
protected:
    int _num; 
};

class Teacher : virtual public Person //同理,增加虚继承
{
protected:
    int _id;
};

class Assistant : public Student, public Teacher //加上此位置后,与此类同名成员会跟以上情况一样,被特殊处理。
{
public:
    string _majorCourse; 
    string _name;
};

int main()
{
    Assistant a;
    a.Student::_name = "张三";  //在Student、Teacher类中,所继承的父类成员做了特殊处理,这里基类名称为_name的成员为"张三",即a.Person::_name = "张三"
    a.Person::_name = "李四";  //同理,都被处理成"李四"

    a._name = "小张"; //只有Assistant类的_name被处理成小张,因为此类没有增加虚继承
    //注意:若Assistant类里没有_name,这里直接a._name会默认基类中的成员

    return 0;
}

       下面我们通过内部来分别观察使用虚拟继承和不使用虚拟继承的情况。代码样例如下:

#include <iostream>
using namespace std;
class A
{
public:
    int _a;
};

class B : public A  //普通继承

//class B : virtual public A  //虚拟继承
{
public:
    int _b;
};

class C : public A //普通继承

//class C : virtual public A  //虚拟继承
{
public:
    int _c;
};

class D : public B, public C  //普通菱形继承
{
public:
    int _d;
};
int main()
{
    D d;
    d.B::_a = 1;
    d.C::_a = 2;
    d._b = 3;
    d._c = 4;
    d._d = 5;
    return 0;
}

        下图是菱形继承的内存对象成员模型:这里可以看到数据冗余 。

        当我们使用虚拟存储时,内部结构如下:

        上图是菱形虚拟继承的内存对象成员模型:这里可以分析出D对象中将A放到的了对象组成的最下面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。虚基表指针本身的地址加上偏移量可以找到A的地址,进而找到A。注意,内存中按照十六进制存放偏移量。原理如下图:

        通过以上逻辑图,我们再观察以下代码有关虚继承的问题。 

#include <iostream>
using namespace std;
class A
{
public:
    int _a;
​​​​​​​};

class B : virtual public A  
{
public:
    int _b;
};

class C : virtual public A  
{
public:
    int _c;
};

class D : public B, public C  //先继承B,后继承C,即B在存储前面,C在储存后面
{
public:
    int _d;
};
int main()
{
    D d;
    d.B::_a = 1;
    d.C::_a = 2;
    d._b = 3;
    d._c = 4;
    d._d = 5;
    cout << d._a << endl;  //输出2

    D dd;  //调试后发现dd里面的虚基表指针指向的地址跟d一样,但虚基表指针本身的地址不一样,即A中的数据不一样
    cout << dd._a << endl;  //随机值

    B b;  //调试后发现b里面的虚基表指针指向的地址与d和dd都不一样
    B bb; //只与b里面的虚基表指针指向的地址一样,但虚基表指针本身的地址不一样,即A中的数据不一样
    
    C c; //调试后发现c里面的虚基表指针指向的地址跟b、d、dd都不一样
    C cc; //只与c里面的虚基表指针指向的地址一样,但虚基表指针本身的地址不一样,即A中的数据不一样
    return 0;
}

        有些人可能会有疑问,为什么D中B和C部分要去找属于自己的A,而不是直接指向A?不妨先想想,在虚继承中,若存在子类赋值给父类的情况,这里就出现很多复杂情况了,这里只需了解即可。在整个C++体系中,除了输入流istream和输出流ostream运用了虚拟继承,其它很少使用虚拟继承。

        最后,说一下,继承是C++中的缺陷之一。有了多继承,就存在菱形继承。菱形继承由于有数据冗余和二义性的问题,所以就有了菱形虚拟继承,到了这里底层实现就很复杂。所以一般不建议设计出多继承,若设计出多继承切记一定不要设计出菱形继承。否则在复杂度及性能上都有问题。


七,继承与组合

        继承和组合都是类的复用,不同的是,继承可以说每个派生类对象都是一个基类对象。而组合是一种包含关系,即假设B组合了A,每个B对象中都有一个A对象。

class A
{
public:
    int _a;
};
//组合
class B
{
private:
    A _a;
};

        通过以上实例可发现组合为低耦合(即两个模块关系不大),继承是高耦合(即两个模块关系大)。

        耦合性平常也叫做可维护性。若代码的可维护性高,在以后对代码的调整或更新的时候会很方便,比如在此项目上继续添加东西或迭代更新。若代码的可维护性低,这很不利于更新或填补bug。因此,我们需要低耦合,即可以用组合,就用组合。若实在不行才考虑继承。

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

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

相关文章

GitCode配置ssh

下载SSH windows设置里选“应用” 选“可选功能” 添加功能 安装这个 坐等安装&#xff0c;安装好后可以关闭设置。 运行 打开cmd 执行如下指令&#xff0c;启动SSH服务。 net start sshd设置开机自启动 把OpenSSH服务添加到Windows自启动服务中&#xff0c;可避免每…

java中实体pojo对于布尔类型属性命名尽量别以is开头,否则 fastjson可能会导致属性读取不到

假如我们有一个场景&#xff0c;就是需要将一个对象以字符串的形式&#xff0c;也就是jsonString存到一个地方&#xff0c;比如mysql&#xff0c;或者redis的String结构。现在有一个实体&#xff0c;我们自己创建的&#xff0c;叫做CusPojo.java 有两个属性是布尔类型的&#x…

STM32引脚重定义问题

最近在搞资源管理&#xff0c;发现有些引脚不能用 比如这个PE引脚。我想用他输出PWM&#xff0c;但是不能用&#xff0c;我也重定义了&#xff0c;还是不能用。回去翻看了技术手册。 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //重映射引脚功能&#xff0c;需…

NoSQL 数据库管理工具,搭载强大支持:Redis、Memcached、SSDB、LevelDB、RocksDB,为您的数据存储提供无与伦比的灵活性与性能!

NoSQL 数据库管理工具&#xff0c;搭载强大支持&#xff1a;Redis、Memcached、SSDB、LevelDB、RocksDB&#xff0c;为您的数据存储提供无与伦比的灵活性与性能&#xff01; 【官网地址】&#xff1a;http://www.redisant.cn/nosql 介绍 直观的用户界面 从单一应用程序中同…

String 必知必会底层逻辑

String 是不可变的 String 类中使用 final 关键字修饰字符数组来保存字符串 public final class String implements java.io.Serializable, Comparable<String>, CharSequence {private final char value[];//... }final关键字的作用&#xff1a; 不可变性&#xff1a…

openai DALL-E 3 论文 提升图像生成的关键:更好的图像描述

摘要 我们展示了通过训练高度描述性的生成图像标题&#xff0c;可以显着改善文本到图像模型的提示跟随能力。 现有的文本到图像模型在跟随详细的图像描述方面存在困难&#xff0c;经常忽略单词或混淆提示的含义。 我们假设这个问题源于训练数据集中存在嘈杂和不准确的图像标…

人工智能|深度学习——基于对抗网络的室内定位系统

代码下载&#xff1a; 基于CSI的工业互联网深度学习定位.zip资源-CSDN文库 摘要 室内定位技术是工业互联网相关技术的关键一环。该技术旨在解决于室外定位且取得良好效果的GPS由于建筑物阻挡无法应用于室内的问题。实现室内定位技术&#xff0c;能够在真实工业场景下实时追踪和…

w29pikachu-ssrf实例

实验环境 php&#xff1a;7.3.4nts apache&#xff1a;2.4.39 浏览器&#xff1a;谷歌实验步骤 ssrf&#xff08;curl&#xff09; 打开ssrf(curl) 点击文字&#xff0c;跳转404页面&#xff0c;从反馈信息来看是找不到对应的页面。 查看源码&#xff0c;发现有个RD变量影…

【Flink网络通讯(一)】Flink RPC框架的整体设计

文章目录 1. Akka基本概念与Actor模型2. Akka相关demo2.1. 创建Akka系统2.2. 根据path获取Actor并与之通讯 3. Flink RPC框架与Akka的关系4.运行时RPC整体架构设计5. RpcEndpoint的设计与实现 我们从整体的角度看一下Flink RPC通信框架的设计与实现&#xff0c;了解其底层Akka通…

利用nbsp设置空格

想要实现上面效果&#xff0c;一开始直接<el-col :span"8" >{{ item.name }} </el-col> 或者<el-col :span"8" >{{ item.name }}</el-col>或者<el-col :span"8" >{{ item.name }}</el-col> 都无…

深入浅出JVM(三)之HotSpot虚拟机类加载机制

HotSpot虚拟机类加载机制 类的生命周期 什么叫做类加载? 类加载的定义: JVM把描述类的数据从Class文件加载到内存,并对数据进行校验,解析和初始化,最终变成可以被JVM直接使用的Java类型(因为可以动态产生,这里的Class文件并不是具体存在磁盘中的文件,而是二进制数据流) 一个…

善于利用GPT确实可以解决许多难题

当我设计一个导出Word文档的功能时&#xff0c;我面临了一个挑战。在技术选型时&#xff0c;我选择了poi-tl这个模板引擎&#xff0c;因为在网上看到了很多关于它的推荐。poi-tl可以根据模板快速导出Word文档。虽然之前没有做过类似的功能&#xff0c;而且项目中也没有用过&…

开年喜报!Walrus成功入选CNCF云原生全景图

近日&#xff0c;数澈软件 Seal &#xff08;以下简称“Seal”&#xff09;旗下开源应用管理平台 Walrus 成功入选云原生计算基金会全景图&#xff08;CNCF Landscape&#xff09;并收录至 “App Definition and Development - Application Definition & Image Build”板块…

Encoder-decoder 与Decoder-only 模型之间的使用区别

承接上文&#xff1a;Transformer Encoder-Decoer 结构回顾 笔者以huggingface T5 transformer 对encoder-decoder 模型进行了简单的回顾。 由于笔者最近使用decoder-only模型时发现&#xff0c;其使用细节和encoder-decoder有着非常大的区别&#xff1b;而huggingface的借口为…

热阻基础理论 --NMOS温度评估

热阻基础理论 器件 温度差 功率 * 热阻 MOS应用实例 1.假如MOS管悬挂或者外壳贴到散热器上&#xff0c;就意味着用CASE到空气的散热热阻会很大&#xff0c; 如下图中的20。 2. 假如MOS管金属面焊接到PCB上&#xff0c;就意味着用CASE到空气的散热热阻会很校&#xff0c; 如…

计算机设计大赛 深度学习人脸表情识别算法 - opencv python 机器视觉

文章目录 0 前言1 技术介绍1.1 技术概括1.2 目前表情识别实现技术 2 实现效果3 深度学习表情识别实现过程3.1 网络架构3.2 数据3.3 实现流程3.4 部分实现代码 4 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习人脸表情识别系…

vmware的ubuntu虚拟机因空间满无法启动

正在虚拟机编译android源代码&#xff0c;没注意空间不足&#xff0c;结果回来发现了 Assuming drive cache: write through 的问题&#xff0c;经查是空间不足的原因 按照这个教程&#xff0c;清除出来部分空间&#xff0c;才能进去系统&#xff0c;并且对系统空间做下优化 …

为什么运维要转行

为什么运维要转行 粉丝提问&#xff1a; 在各种APP里经常看到&#xff0c;趁年轻赶紧远离运维&#xff0c;为什么&#xff1f; 互联网老兵是这样回答的&#xff1a; 运维有很多分类&#xff0c;有干实施运维的&#xff0c;有干交付运维的&#xff0c;也有自动化运维&#xf…

07 Redis之持久化(RDB+AOF)

4 Redis持久化 Redis 是一个内存数据库&#xff0c;然而内存中的数据是不持久的&#xff0c;若主机宕机或 Redis 关机重启&#xff0c;则内存中的数据全部丢失。 当然&#xff0c;这是不允许的。Redis 具有持久化功能&#xff0c;其会按照设置以快照或操作日志的形式将数据持…

Stable Diffusion WebUI 界面介绍

本文收录于《AI绘画从入门到精通》专栏&#xff0c;专栏总目录&#xff1a;点这里。 大家好&#xff0c;我是水滴~~ 本文主要对 Stable Diffusion WebUI 的界面进行简单的介绍&#xff0c;让你对该 WebUI 有个大致的了解&#xff0c;为后面的深入学习打下一个基础。主要内容包…