【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.rhkb.cn/news/261120.html

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

相关文章

linux下开发,stm32和arduino,我该何去何从?

linux下开发&#xff0c;stm32和arduino&#xff0c;我该何去何从&#xff1f; 在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「stm3的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共…

网站常见的反爬手段及反反爬思路

摘要:介绍常见的反爬手段和反反爬思路&#xff0c;内容详细具体&#xff0c;明晰解释每一步&#xff0c;非常适合小白和初学者学习&#xff01;&#xff01;&#xff01; 目录 一、明确几个概念 二、常见的反爬手段及反反爬思路 1、检测user-agent 2、ip 访问频率的限制 …

OpenCV笔记2:鼠标事件实现绘制直线、矩阵、曲线

OpenCV 鼠标事件 创建窗口设置窗口大小鼠标事件监听 判断事件更新起始点和终点绘制线显示图片 打开背景图 """ 鼠标事件 down up move """ import cv2 import numpy as npWINNAME DRAWBOARD st_point (-1, -1) end_point (-1, -1)def draw…

C#,洗牌问题(Card Shuffle Problem)的算法与源代码

1 洗牌问题&#xff08;Card Shuffle Problem&#xff09; 洗牌问题&#xff08;Card Shuffle Problem&#xff09;的基本描述 你有 100 张牌&#xff0c;从 1 到 100。 你把它们分成 k 堆&#xff0c;然后按顺序收集回来。 例如&#xff0c;如果您将它们分成 4 堆&#xff0…

从源代码安装 rocSOLVER 并 调试 rocSOLVER 在 Ubuntu 22.04 平台

0, 下载并编译 rocBLAS 的调试版本 sudo apt install python3.10-venv sudo apt install libmsgpack-dev sudo pip install joblibgit clone --recursive https://github.com/ROCm/rocBLAS.git $ cd rocBLAS/ $ ./install.sh -i -g构建时间也不短 1&#xff0c;下载并编译 roc…

linux 防火墙

防火墙分类 按保护范围划分 主机防火墙&#xff1a;服务服务为当前一台主机 网络防火墙&#xff1a;服务服务为防火墙一侧的局域网 按实现方式分类划分 硬件防火墙&#xff1a;在专用硬件级别实现部分功能的防火墙&#xff1b;另一部分基于软件的实现 如&#xff1a;华为&#…

ping 8.8.8.8和ping www.baidu.com都OK,但是打不开网页

ping 8.8.8.8和ping www.baidu.com都OK&#xff0c;但是打不开网页 打开设置 -> 网络 找到IPV4, DNS栏输入 8.8.8.8 , apply 设置里界面变成这样 然后网页就能加载了

vue 非父子通信-event bus 事件总线

1.作用 非父子组件之间&#xff0c;进行简易消息传递。(复杂场景→ Vuex) 2.步骤 创建一个都能访问的事件总线 &#xff08;空Vue实例&#xff09; import Vue from vue const Bus new Vue() export default Bus A组件&#xff08;接受方&#xff09;&#xff0c;监听Bus的…

04 动力云客之登录后获取用户信息+JWT存进Redis+Filter验证Token + token续期

1. 登录后获取用户信息 非常好实现. 只要新建一个controller, 并调用SS提供的Authentication对象即可 package com.sunsplanter.controller;RestController public class UserController {GetMapping(value "api/login/info")public R loginInfo(Authentication a…

Spring Boot项目中TaskDecorator的应用实践

一、前言 TaskDecorator是一个执行回调方法的装饰器&#xff0c;主要应用于传递上下文&#xff0c;或者提供任务的监控/统计信息&#xff0c;可以用于处理子线程与主线程间数据传递的问题。 二、开发示例 1.自定义TaskDecorator import org.springframework.core.task.Task…

【rust】7、命令行程序实战:std::env、clap 库命令行解析、anyhow 错误库、indicatif 进度条库

文章目录 一、解析命令行参数1.1 简单参数1.2 数据类型解析-手动解析1.3 用 clap 库解析1.4 收尾 二、实现 grep 命令行2.1 读取文件&#xff0c;过滤关键字2.2 错误处理2.2.1 Result 类型2.2.2 UNwraping2.2.3 不需要 panic2.2.4 ? 问号符号2.2.5 提供错误上下文-自定义 Cust…

java导出动态下拉框excel模板

1.原始模板 2.导出模板,下拉框为数据库中得到动态数据 public void downloadTemplate(HttpServletResponse response) throws IOException {// 所有部门List<String, String> departments expertManageMapper.selectAllDepartment();//所有职位List<String, String&g…

打码半年,开源一款自定义大屏设计软件!

hi&#xff0c;大家好&#xff0c;我是Tduck马马。 最近我们开源了一款大屏软件-TReport&#xff0c;与大家分享。 TReport是一款基于Vue3技术栈的数据可视化系统&#xff0c;支持静态、动态api等数据源&#xff1b;可用于数据可视化分析、报表分析、海报设计使用。 提供自定…

定制你的【Spring Boot Starter】,加速开发效率

摘要&#xff1a; 本文将介绍如何创建一个自定义的 Spring Boot Starter&#xff0c;让您可以封装常用功能和配置&#xff0c;并在多个 Spring Boot 项目中共享和重用。 1. 简介 Spring Boot Starter 是 Spring Boot 框架中的一种特殊的依赖项&#xff0c;它用于快速启动和配置…

计算机网络-广域通信网

1.广域网概念和分类 什么是广域网&#xff1f; 广域网是指长距离跨地区的各种局域网、计算机、终端互联在一起&#xff0c;组成一个资源共享的通信网络。 广域网分为传统广域网和现代广域网。 传 统 广 域 网公共交换电话网PSTN公共数据网X.25帧中继网FR综合业务数据网ISDN…

Linux 内存top命令详解

通过top命令可以监控当前机器的内存实时使用情况&#xff0c;该命令的参数解释如下&#xff1a; 第一行 15:30:14 —— 当前系统时间 up 1167 days, 5:02 —— 系统已经运行的时长&#xff0c;格式为时:分 1 users ——当前有1个用户登录系统 load average: 0.00, 0.01, 0.05…

时间获取,文件属性和权限的获取——C语言——day06

今天主要内容是时间获取以及文件属性和权限的获取 时间获取 1.time 1.time time_t time(time_t *tloc); 功能:返回1970-1-1到现在的秒数&#xff08;格林威治时间&#xff09; 参数:tloc:存放秒数空间首地址 返回值:成功返回秒数失败返回-12.localtime 2.localtimestruct t…

开发一款招聘小程序需要具备哪些功能?

随着时代的发展&#xff0c;找工作的方式也在不断变得简单&#xff0c;去劳务市场、人才市场的方式早就已经过时了&#xff0c;现在大多数年轻人都是直接通过手机来找工作。图片 找工作类的平台不但能扩大企业的招聘渠道&#xff0c;还能节省招聘的成本&#xff0c;方便求职者进…

Linux-时间接口-005

学习重点&#xff1a; 1.函数接口 2.【ls-l】命令的实现1【time】 1.1函数原型 【time_t time(time_t *tloc);】1.2函数功能 返回1970-1-1到现在的秒数&#xff08;格林威治时间&#xff09;1.3函数参数 1.3.1【tloc】 存放秒数空间首地址 存放的秒数&#xff1a;如果【t…