【第三节】类的构造和析构函数

目录

一、数据成员的初始化

二、构造函数

2.1 什么是构造函数

2.2 构造函数的注意事项

三、析构函数

四、带参数的构造函数

五、缺省构造函数

六、构造函数初始化列表

七、拷贝构造函数和调用规则

八、深拷贝和浅拷贝

九、总结


一、数据成员的初始化

        定义普通变量,在定义时,一般都会给定一个初始值。

#include <iostream>
using namespace std;

int g_Num= 100;  //全局变量
int main() {
    int nNumA = 30;//局部变量
    int *pNum=new int[20];//堆
    memset(pNum, 0, 20*sizeof(int));
    cout<<g_Num << nNumA;
    return 0;
}

        在以上的例子中,我们给局部变量和全局变量,堆内都定义了初始值。但是假如我们使用了更为复杂的类,用类定义对象该怎么办呢?如下所示:

//定义一个办公桌类
class CDesk {

private:
    int m_high;
    int m_width;
    int m_length;
    int m_weight;
};

//全局对象
CDesk g_objDesk; 

int main() {
    // 局部对象
    CDesk objDesk;
    CDesk* pobjDesk = new CDesk;//堆对象
    delete pobjDesk;
    return 0;
}

上面的都是没有初始值的,或许有人可能想到下面的方法。

class CDesk { //定义一个办公桌类
public:
    //自己添加一个函数,能够给各个成员赋值。
    void SetValue() {
        m_high = 0;
        m_width = 0;
        m_length = 0;
        m_weight = 0;
    }

private:
    int m_high;
    int m_width;
    int m_length;
    int m_weight;
};

CDesk g_objDesk; //全局对象

int main() {
    CDesk objDesk;  //局部对象
    objDesk.SetValue();
    objDesk.SetValue();
    CDesk* pobjDesk = new CDesk;//堆对象
    pobjDesk->SetValue();
    delete pobjDesk;
    return 0;
}

这样就显得非常的麻烦。有没有更好的方法呢?有的,就是使用下面学习的构造函数。

二、构造函数

2.1 什么是构造函数

        构造函数是一种独特的成员函数,其名称与类名完全一致,且不具备返回值类型,因此无法为其定义返回类型,亦不可使用void。在常规情况下,构造函数应被声明为公有函数。然而,将其声明为私有函数则有其特殊目的,例如在单例模式中使用。当创建类类型的新对象时,系统会自动调用相应的构造函数。

代码示例:

class CDesk { //定义一个办公桌类
public:
    CDesk() {
        cout << "我是构造函数"<<endl;
    }

private:
    int m_high;
    int m_width;
    int m_length;
    int m_weight;
};

CDesk g_objDesk; //全局对象

int main() {
    CDesk objDesk;  //局部对象
    CDesk* pobjDesk = new CDesk;//堆对象
    delete pobjDesk;
    return 0;
}

运行结果:

        可以看到并没有人主动调用构造函数,但是这个函数自动被调用了。注意:全局对象局部对象,堆中的对象,只有有对象被创建了,都会自动调用构造函数。

2.2 构造函数的注意事项

问题一:构造函数仅用于初始化吗?
答案是否定的。构造函数是C++语言提供的一种机制,它在对象创建时自动被调用。在构造函数内部,可以编写任何代码,甚至可以模拟一个贪吃蛇游戏。然而,构造函数之所以具备这种自动调用的特性,主要是为了便于程序开发者进行对象的初始化工作。

问题二:构造函数能否在类外部定义?
确实可以。在类外部定义构造函数时,需要在函数名前加上“类名::”。构造函数没有返回类型,且不能被声明为虚函数。

示例代码:

class CDesk { //定义一个办公桌类
public:
    CDesk();

private:
    int m_high;
    int m_width;
    int m_length;
    int m_weight;
};

CDesk::CDesk() {//类外构造函数
    m_high = 0;
    m_width = 0;
    m_length = 0;
    m_weight = 0;
}

CDesk g_objDesk; //全局对象

int main() {
    CDesk objDesk;  //局部对象
    CDesk* pobjDesk = new CDesk;//堆对象
    delete pobjDesk;
    return 0;
}

问题三:main函数是第一个被调用的函数吗?
下面有了一个反例,全局对象的构造函数先于 main 函数执行。

class CDesk { //定义一个办公桌类
public:
    CDesk();

private:
    int m_high;
    int m_width;
    int m_length;
    int m_weight;
};

CDesk::CDesk() {//类外构造函数
    cout << "我是构造函数" << endl;
    m_high = 0;
    m_width = 0;
    m_length = 0;
    m_weight = 0;
}

CDesk g_objDesk; //全局对象

int main() {
    cout << "我是 main函数" << endl;
    CDesk objDesk;  //局部对象
    CDesk* pobjDesk = new CDesk;//堆对象
    delete pobjDesk;
    return 0;
}

运行截图:

问题四:如果一个类的成员是另一个类的对象,那么哪个构造函数会先被调用?
在对象创建过程中,如果一个类的数据成员是另一个类的对象,那么在调用构造函数时,首先会自动调用该数据成员对象的构造函数,随后再执行本类对象的构造函数。以下程序中的ClassRoom类(代表班级)包含学生类和老师类对象作为其成员,该程序展示了构造函数的调用顺序。

代码示例:

#include <iostream>
using namespace std;
class CStudent {
public:
    CStudent() {
        cout << "学生类构造函数" << endl;
    }
};

class CTeacher {
public:
    CTeacher() {
        cout<< "老师类构造函数" << endl;
    }
};

class ClassRoom {//假设这个班级中有3名学生一个老师
public:
    ClassRoom() {
        cout << "班级类构造函数" << endl;
    }
private:
    CStudent m_arrStr[3];
    CTeacher m_objTea;
};

int main() {
    ClassRoom objClass;
    return 0;
}

运行结果:

可以看出,对象数据成员的构造函数也是按照定义顺序构建的。

问题五:new操作符与构造函数之间有何关联?
new操作符不仅负责分配内存,还会自动调用相应的构造函数。这一点在上文的示例中已得到体现。值得注意的是,虽然malloc函数同样能够分配堆内存,但它无法识别对象类型,因此不会调用构造函数。

问题六:构造函数能否带有参数?
确实可以。构造函数可以包含参数,并且一个类可以定义多个构造函数,通过参数的不同来实现函数重载。

三、析构函数

        除了构造函数之外,C++还引入了析构函数。当对象生命周期结束时,析构函数会自动被调用。它通常用于回收对象内部申请的资源,例如,如果对象中有一个指针指向堆内存,可以在析构函数中释放这部分内存。

析构函数的特点如下:
A. 没有返回类型;
B. 不接受任何参数;
C. 不应随意调用,尽管在特定情况下可以调用;
D. 不能进行重载(相比之下,构造函数可以有参数并支持重载);
E. 析构函数的功能与构造函数相对应,因此其名称是在构造函数名前加上逻辑非运算符“~”。

        当对象的生命周期结束时,例如在函数体内定义的局部对象,随着函数调用的结束,这些局部对象将被释放,此时会调用析构函数。

以下情况需要使用析构函数:
A. 如果构造函数打开了文件,在使用完毕后,需要通过析构函数关闭文件;
B. 如果从堆中分配了动态内存,在对象销毁前,必须通过析构函数释放这部分内存。

代码示例:

#include <iostream>
using namespace std;

class CTest {
public:
    CTest() {//构造函数
        m_szName = new char[20];
    }
    ~CTest() {//析构函数
        delete[] m_szName;
    }
private:
    char* m_szName;
};

int main() {
    CTest objClass;
    return 0;
}

        该类定义的构造函数在对象之外分配一段堆内存空间,撤销时,由析构函数收回堆内存。注意,析构函数以调用构造函数相反的顺序被调用,请看如下示例:

#include <iostream>
using namespace std;

class CMonitor {
public:
    CMonitor() { cout << "构造 显示器.\n"; }
    ~CMonitor() { cout << "析构 显示器.\n"; }
};

class CKeyboard {
public:
    CKeyboard() { cout<<"构造 键盘.\n"; }
    ~CKeyboard() { cout<<"析构 键盘.\n"; }
};

class CComputer {
public:
    CComputer() { cout << "构造 电脑.\n"; }
    ~CComputer() { cout << "析构 电脑.\n"; }

protected:
    CMonitor m_objMonitor;//数据成员是类对象
    CKeyboard m_objKeyboard;//数据成员是类对象
};

int main() {
    CComputer* com = new CComputer();//显式调用无参构造
    delete com;
    return 0;
}

四、带参数的构造函数

        不带参数的构造函数不能完全满足初始化的要求,因为这样创建的类对象具有相同的初始化值,如果需要对类对象按不同特征初始化不同的值,应采用带参数的构造函数。如下面程序所示:

class CLocation {
public:
    CLocation(int nNumA, int nNumB) {
        m_X = nNumA;
        m_Y = nNumB;
    }
    int getx() {
        return m_X;
    }
    int gety() {
        return m_Y;
    }
private:
    int m_X, m_Y;//数据成员
};

一个类可以拥有多个构造函数构成重载,这样可以多样化的对对象进行初始化,请看如下示例代码:

#include <iostream>
using namespace std;

class CLocation {
public:
    CLocation(int nNumA, int nNumB) {
        cout << "2个参数的构造函数" << endl;
        m_X = nNumA;
        m_Y = nNumB;
    }


    CLocation(int nNumA) {
        cout << "1个参数的构造函数" << endl;
        m_X = nNumA;
        m_Y = nNumA * 2;
    }


    int getx() {
        return m_X;
    }
    int gety() {
        return m_Y;
    }
private:
    int m_X, m_Y;//数据成员
};

int main() {
    CLocation objA(1, 2);
    CLocation objB(3);
    return 0;
}

可以看出构造函数可以构成重载,根据初始化时传递的参数自动选择构造函数调用。

五、缺省构造函数

        C++语言规定,每个类都必须拥有至少一个构造函数。没有构造函数,就无法创建该类的任何对象。如果开发者没有为类定义构造函数,C++会提供一个默认的构造函数。这个默认构造函数是一个不带参数的构造函数,其作用仅限于创建对象,而不执行任何初始化操作。

        一旦类中定义了任何构造函数,C++将不再提供默认构造函数。如果需要无参数的构造函数,开发者必须自行定义。类似于变量的定义,当使用默认构造函数创建对象时,如果创建的是全局对象或静态对象,其成员数据将被初始化为0;而对于局部对象,其成员数据在创建时将是无意义的随机值。

        本节第一个例子中,创建的全局对象、静态对象及局部对象都是由编译器提供的缺省构造函数自动创建的,仅对成员数据分配了内存空间,未做初始化工作。一个类如果什么都没有则被称之为空类,一个空类的大小为1个字节,且编译器会为其隐式产生6个成员,假设有一个空类class Empty,则编译器会为其产生以下几个成员:

Empty();  //默认构造函数
Empty( const Empty&); //默认拷贝构造函数
~Empty(); //默认析构函数
Empty& operator=(constEmpty&); //默认赋值运算符
Empty* operator&(); //取址运算符
const Empty*operator&()const; //取址运算符const

六、构造函数初始化列表

        之前在构造函数中给变量初始值的时候,实际上都算不上初始化,在构造函数初始化列表中初始化才算的上是初始化,那么什么是构造函数初始化列表呢?

示例代码:

#include <iostream>
using namespace std;

class CLocation {
public:
    CLocation(int nNumA, int nNumB) :m_X(nNumA), m_Y(nNumB) {
        cout << "2个参数的构造函数" << endl;
        m_X = nNumA;
        m_Y = nNumB;
    }


    CLocation(int nNumA) : m_X(nNumA), m_Y(nNumA * 2) {
        cout << "1个参数的构造函数" << endl;
        m_X = nNumA;
        m_Y = nNumA * 2;
    }

    int getx() {
        return m_X;
    }
    int gety() {
        return m_Y;
    }
private:
    int m_X, m_Y;//数据成员
};

int main() {
    CLocation objA(1, 2); 
    CLocation objB(3);
}

        如以上代码所示,在构造函数声明的后面,加上一个冒号,冒号后面就是初始化列表在初始化列表中,你可以为成员变量初始化。初始化时可以使用一个表达式。表达式可以有形参,也可以没有形参。

实际上,构造函数的执行过程可以细分为两个阶段:

  1. 初始化阶段:在这一阶段,内存分配的同时会直接填充数据。

  2. 普通计算阶段:这一阶段涉及构造函数体内的程序代码,任何在此阶段的初始化操作实际上是赋值操作。我们的初始化列表位于第一阶段,而在构造函数体内的赋值则属于第二阶段,此时不能称为初始化,而应视为赋值。

区分这两个阶段的重要性在于:

  1. const类型的成员必须在初始化列表中进行初始化,因为它们在定义后不能被修改。

  2. 引用类型的成员同样必须在初始化列表中初始化,因为引用一旦定义就必须立即绑定到一个对象。

  3. 如果类的成员是另一个类的对象,且该对象所属的类没有提供默认构造函数,那么这个对象成员也必须在构造函数的初始化列表中进行初始化。

这些成员必须在定义时立即初始化,因此它们需要在初始化列表中处理。

以下示例代码是const与引用的初始化问题:

#include <iostream>
using namespace std;

class Object {
public:
    Object(int num = 0) :m_num(num), m_cNum(num), m_refNum(m_num) {
        //m_cNum=100;//常量不能在这里初始化
        //m_refNum = m_num;引用也不能再这里初始化
        cout << "Object"<< m_num << "..." <<endl;
    }


    ~Object(){
        cout << "~Object " << m_num << "..." << endl;
    }

    void DisplaycNum(){
        cout << "cNum=" << m_cNum << endl;
    }

private:
    int m_num;
    const int m_cNum;
    int& m_refNum;
};


int main(void) {
    Object obj1(10);
    Object obj2(20);
    obj1.DisplaycNum();
    obj2.DisplaycNum();
    return 0;
}

以下代码是有参构造的对象成员的初始化问题

#include <iostream>
using namespace std;

class Object {
public:
    Object(int num = 0) :m_num(num) {
        cout << "Object"<< m_num << "..." <<endl;
    }


    ~Object(){
        cout << "~Object " << m_num << "..." << endl;
    }


private:
    int m_num;
};

class Container {
public:
    Container(int objl = 0, int obj2 = 0) {
        cout << "Container" << "..." << endl;
    }

    Container() {
        cout << "~Container ..." << endl;
    }

private:
    Object m_objl;
    Object m_obj2;
};


int main(){
    Container c(10, 20);
    return 0;
}

        在Container类的构造函数中,你声明了两个Object类型的成员变量m_objlm_obj2,但是在构造函数的初始化列表中并没有对它们进行初始化。在C++中,如果一个类包含了其他类的成员变量,那么这些成员变量必须在构造函数的初始化列表中进行初始化。

代码改成如下:

#include <iostream>
using namespace std;

class Object {
public:
    Object(int num = 0) : m_num(num) {
        cout << "Object" << m_num << "..." << endl;
    }

    ~Object() {
        cout << "~Object " << m_num << "..." << endl;
    }

private:
    int m_num;
};

class Container {
public:
    Container(int obj1 = 0, int obj2 = 0) : m_obj1(obj1), m_obj2(obj2) {
        cout << "Container" << "..." << endl;
    }

private:
    Object m_obj1;
    Object m_obj2;
};

int main() {
    Container c(10, 20);
    return 0;
}

根据上述实验结果,我们得出以下结论:

  1. 建议将所有初始化操作,包括普通数据成员和对象数据成员的初始化,放置在构造函数的初始化列表中进行。

  2. 对于那些没有默认构造函数的对象成员,其初始化必须且只能在构造函数的初始化列表中完成。一旦进入构造函数体内,再进行初始化则为时已晚。

  3. 这一原则同样适用于const型成员和引用型成员,它们都必须在初始化列表中进行初始化。

  4. 如果一个有参构造函数的所有参数都提供了默认值,那么它也可以被视为具有默认构造函数的功能。

  5. 对象成员的构造顺序是由它们在类中的定义顺序决定的,与初始化列表中的顺序无关。

七、拷贝构造函数和调用规则

        在C++中,提供了用一个对象值创建并初始化另一个对象的方法,完成该功能的是拷贝构造函数(也叫复制构造函数)。
拷贝构造函数的格式如下:
<类名>::<拷贝构造函数名>(<类名>&<引用名>)
{<函数体>}
例如:
CLocation::CLocation(CLocation &obj)
{   }

注意:
其中<拷贝构造函数名>与该类名相同
如果一个类中没有定义拷贝构造函数,则系统自动生成一个缺省拷贝构造函数,其功能是将已知对象的所有数据成员的值拷贝给对应对象的数据成员

拷贝构造函数的特点:
拷贝构造函数名字与类同名,没有返回类型
拷贝构造函数只有一个形参数,该参数是该类的对象的引用

代码示例:

#include <iostream>
using namespace std;

class CLocation{
public:
    //拷贝构造
    CLocation(CLocation& obj):m_X(obj.m_X),m_Y(obj.m_Y) {}
    //普通带参构造
    CLocation(int nX, int nY):m_X(nX), m_Y(nY){ }
private:
    int m_X, m_Y;
    };

int main() {
    CLocation objA(1, 2);
    CLocation objB(objA);//此时调用的是拷贝构造函数
    return 0;
}

拷贝构造函数不仅用于通过已知对象的值创建同类型的新对象,还承担着另外两个关键角色:

A. 当对象作为函数参数传递时,系统会自动调用拷贝构造函数,以实现将对象的值复制给形式参数对象。

B. 当函数的返回值是对象时,系统会自动调用拷贝构造函数,创建一个临时对象来存储返回的对象值,然后将这个临时对象的值赋给接收函数返回值的目标对象。

拷贝构造函数调用示例:

#include <iostream>
using namespace std;

class CLocation{
public:
    //拷贝构造
    CLocation(CLocation& obj):m_X(obj.m_X),m_Y(obj.m_Y) {
        cout << "拷贝构造\n";
    }
    //普通带参构造
    CLocation(int nX, int nY):m_X(nX), m_Y(nY){ 
        cout << "普通构造\n"; 
    }
private:
    int m_X, m_Y;
};

CLocation& fun_A(CLocation obj) { return obj; }
CLocation& fun_B(CLocation& obj) { return obj; }

int main() {
    CLocation objA(3, 4);
    CLocation objB(objA);
    CLocation objC = fun_A(objB);
    CLocation& objD = fun_B(objB);
    return 0;
}

运行结果:

        程序输出结果说明程序中出现了三次调用拷贝构造函数:
1 以objA为蓝本创建objB
CLocation objB(objA);
2 实参objB对象被拷贝到形参
CLocation objC = fun_A(objB);
3函数返回时,调用拷贝构造函数,用对象obj创建一个临时对象保存obj的数据,在主函数中临时对象被释放前,将它的内容赋值到对象 objC中
return obj:
同时,可以发现,返回引用或者参数是引用,可以避免拷贝构造函数的调用。

八、深拷贝和浅拷贝

        如果不实现拷贝构造的话,系统也会帮助我们实现一个,功能就是挨个元素拷贝,这似乎和我们上面实现的功能是一致的。那么我们什么情况下需要自己实现拷贝呢?看下面的例子

#include <iostream>
#include <string.h>
using namespace std;

class String {
public:
    String(const char* str = "");
    ~String();

    void Display(); 

private:
    char* m_str;
};


String::String(const char* str) {
    int len = strlen(str) + 1; 
    m_str = new char[len];
    memset(m_str, 0, len);
    strcpy_s(m_str, len, str);
}

String::~String(){
    delete[] m_str;
}

void String::Display() {
    cout << m_str << endl;
}

int main(){
    String s1("AAA");
    s1.Display();
    //系统提供的默认拷贝构造函数实施的是浅拷贝s2.m_str = s1.m_str;
    String s2(s1);//调用拷贝构造函数
    return 0;
}

        上述程序由于默认提供的拷贝构造函数仅仅是赋值,导致S1和S2的m_str 指向同一个位置,两个对象的字符串,有一个改变另一个也会改变。
另外两个对象的析构函数释放的是同一块位置,这也必然会出错,造成程序崩溃。这种拷贝称之为浅拷贝函数。

接下来我们自己实现一个拷贝构造函数

#include <iostream>
#include <cstring>

using namespace std;

class String {
public:
    String(const char* str = "");
    String(const String& other); //自己实现拷贝构造函数
    ~String();

    void Display();

private:
    char* m_str;
};

String::String(const char* str) {
    int len = strlen(str) + 1;
    m_str = new char[len];
    strcpy_s(m_str, len, str);
}

String::String(const String& other) {
    int len = strlen(other.m_str) + 1;
    m_str = new char[len];
    strcpy_s(m_str, len, other.m_str);
}

String::~String() {
    delete[] m_str;
}

void String::Display() {
    cout << m_str << endl;
}

int main() {
    String s1("AAA");
    s1.Display();
    String s2(s1); // 调用拷贝构造函数
    s2.Display();
    return 0;
}

        在这个例子中,在 s2中又申请出了一块空间,将s1中的信息拷贝了进去。
这样,s1和s2就完全的没有关系了,把这种形式称之为深拷贝。可以看出,当类中有指针指向堆的时候,自己实现一个拷贝构造函数做成深拷贝是十分有必要的。

九、总结

        构造函数是一种特殊的成员函数,专门用于创建类的对象。它在对象创建时被调用,负责为对象分配内存空间,初始化其数据成员,并执行其他必要的资源请求操作。与之相对的是析构函数,它在对象生命周期结束时被调用,用于撤销对象并回收其占用的资源。构造函数和析构函数功能互补,通常成对出现。每个类的对象都必须通过构造函数来诞生,一个类可以定义一个或多个构造函数。编译器根据对象构造函数声明中形参的类型和数量与创建对象时提供的实参进行匹配,以确定应使用哪个构造函数,这一过程类似于普通重载函数的选择机制。当创建包含对象成员的类对象时,需要对这些对象成员进行初始化,此时会调用对象成员的构造函数。

        拷贝构造函数用于通过一个已存在的对象来创建一个新的对象,这种情况在对象作为参数传递或作为返回值返回时频繁发生。

        当类中未显式定义构造函数和析构函数时,编译器会提供默认的构造函数和析构函数。默认构造函数在创建对象时仅分配数据成员的存储空间,但不进行初始化;而默认析构函数在对象撤销时自动调用,负责回收资源。

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

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

相关文章

java智慧工厂制造生产管理MES系统saas模式Java+ idea+ uniapp全套MES系统源码,多端展示

java智慧工厂制造生产管理MES系统saas模式Java idea uniapp全套MES系统源码&#xff0c;多端展示 MES 系统源码&#xff08;生产制造执行系统&#xff09;能够帮助企业实现全生产过程的可视化&#xff0c;数据分析智能化、构建高效智能工厂&#xff0c;MES系统通过控制指令、人…

大气污染溯源算法及其技术实现

污染溯源基础概念知识 大气污染溯源是指识别并追踪污染物的来源及其传输过程&#xff0c;以确定造成大气污染的根本原因和污染物传播路径的技术和方法。这对于制定有效的控制和减轻污染策略至关重要。大气污染的溯源主要涉及以下几个方面&#xff1a; 污染源识别&#xff1a;…

Facebook开户 | 如何检查公共主页的状态

想要了解你的Facebook公共主页的状态吗&#xff1f; Facebook公共主页是让广告主与粉丝互动、传播信息的绝佳平台&#xff0c;但是大家知道如何检查并维护自己的主页状态吗&#xff1f;别担心&#xff0c;Facebook提供了一系列简单易用的工具来帮助大家实现这一目标。 *Page Q…

RedHat9网络配置设计

目录 一、实验目的 二、实验过程 1、配置新网络接口 2、多网卡配置网络 3、网络接口的绑定&#xff0c;进行远程访问 4、配置网络接口的组合 一、实验目的 本次实验的目的是使用nmcli命令工具配置网络&#xff0c;ens160配置多个网卡&#xff0c;进行网络接口的绑定与组合…

一文搞懂Java8 Lambda表达式、方法引用

Lambda表达式介绍 Java 8的一个大亮点是引入Lambda表达式&#xff0c;使用它设计的代码会更加简洁。通过Lambda表达式&#xff0c;可以替代我们以前经常写的匿名内部类来实现接口。Lambda表达式本质是一个匿名函数&#xff1b; 体验Lambda表达式 我们通过一个小例子来体验下L…

单元测试框架Pytest的基本操作

Pytest基本操作 1. 详解1.1 命名规则:1.2 自定义查找规则:1.3 3种运行方式1.4 执行顺序2. 断言2.1 定义2.2 断言的规则3. mark3.1 mark的作用3.2 mark的标记方式3.3 注册标签名3.4 skip跳过标记4. pytest的参数化5. pytest的夹具(fixture测试夹具)5.1. 作用5.2. 夹具应用场…

Java网络编程:UDP通信篇

目录 UDP协议 Java中的UDP通信 DatagramSocket DatagramPacket UDP客户端-服务端代码实现 UDP协议 对于UDP协议&#xff0c;这里简单做一下介绍&#xff1a; 在TCP/IP协议簇中&#xff0c;用户数据报协议&#xff08;UDP&#xff09;是传输层的一个主要协议之一&#xf…

LeetCode hot100-57-G

17. 电话号码的字母组合 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。不会&#xff0c;放IDEA里执行了一下大概理解了流程 …

《Ai企业知识库》-rasa-初步使用

根据上面的环境准备之后&#xff1a; 《Ai企业知识库》-模型实践-rasa开源学习框架-搭建简易机器人-环境准备(针对windows)-02-CSDN博客 基础的使用&#xff1a; rasa项目初始化&#xff1a; rasa init 首先进入目标文件夹 在dos窗口&#xff08;目标文件夹下&#xff09…

10Django项目--用户管理系统--改

对应视频链接点击直达 10Django项目--用户管理系统--改 对应视频链接点击直达改a&#xff0c;本质b&#xff0c;修改(更新) 页面相关a&#xff0c;index页面新增操作按钮b&#xff0c;修改(更新)页面c&#xff0c;路由和函数 OVER&#xff0c;不会有人不会吧不会的加Q139400651…

pdf只要其中一页 pdf只要第一页怎么办 pdf只要前几页怎么弄

在现代办公环境中&#xff0c;PDF文件已经成为我们日常工作中不可或缺的一部分。然而&#xff0c;有时我们可能只需要PDF文件中的某一页&#xff0c;而不是整个文件。这时&#xff0c;我们该如何操作才能只获取所需的那一页呢&#xff1f;本文将详细操作方法&#xff0c;帮助大…

全面盘点多模态融合算法及应用场景

关注作者&#xff0c;分享AI全维度知识。作者拥有10年互联网服务架构、AI产品研发经验、团队管理经验&#xff0c;同济本复旦硕博&#xff0c;复旦机器人智能实验室成员&#xff0c;阿里云认证的资深架构师&#xff0c;项目管理专业人士&#xff0c;上亿营收AI产品研发负责人 多…

工控一体机10.1寸显示器电容触摸屏(YA1308101JK)产品规格说明书

如果您对工控一体机有任何疑问或需求&#xff0c;或者对如何集成工控一体机到您的业务感兴趣&#xff0c;可移步控芯捷科技。 一、硬件功能介绍 YA1308101JK产品介绍&#xff1a; YA1308101JK搭载 Android10 主流操作系统&#xff0c;具有系统版本更高、占用内存更低、运行效率…

LeetCode583:两个字符串的删除操作

题目描述 给定两个单词 word1 和 word2 &#xff0c;返回使得 word1 和 word2 相同所需的最小步数。 每步 可以删除任意一个字符串中的一个字符。 代码 解法1 /*dp[i][j]&#xff1a;以i-1为结尾的wrod1中有以j-1为尾的word2的个数为了让word1和word2相同&#xff0c;最少操作…

什么是老板和工程师都喜欢的FMEA?——FMEA软件

免费试用FMEA软件-免费版-SunFMEA 在企业管理与工程技术领域&#xff0c;FMEA&#xff08;潜在失效模式与效应分析&#xff09;早已不仅仅是一个概念或工具&#xff0c;它更是一种思维方式和团队协作的精髓。那么&#xff0c;究竟什么才是老板和工程师都喜欢的FMEA呢&#xff…

学习笔记——动态路由协议——OSPF(OSPF网络类型2)

2、OSPF网络类型 常见链路层协议对应的默认网络类型 网络类型 描述 常见链路层协议 Hello报文间隔 报文类型 有无DR、BDR选举 P2P 点对点网络 HDLC、PPP、LAPB 10s 以组播方式发送OSPF报文 有 P2MP 点对多点网络 无 30s 以组播方式发送Hello报文&#xff0c;单…

YOLOv10最详细全面讲解2- 目标检测-环境搭建、训练自己的数据集

YOLOv10没想到出来的如此之快&#xff0c;作为一名YOLO的爱好者&#xff0c;以YOLOv5和YOLOv8的经验&#xff0c;打算出一套从数据集装备->环境配置->训练->验证->目标追踪全系列教程。请大家多多点赞和收藏&#xff01;&#xff01;&#xff01; 系列文章&#xf…

基于h5和大数据的游戏数据型网站-计算机毕业设计源码30844

摘 要 在目前的形势下&#xff0c;科技力量已成为我国的主要竞争力。而在科学技术领域&#xff0c;计算机的使用逐渐达到成熟&#xff0c;无论是从国家到企业再到家庭&#xff0c;计算机都发挥着其不可替代的作用&#xff0c;可以说计算机的可用领域遍及生活、工作的各个方面。…

TPM是如何平衡设备维护与生产需求的?

在当今快节奏的生产环境中&#xff0c;设备维护与生产需求之间的平衡成为了企业持续发展的关键所在。TPM&#xff08;全面生产维护&#xff09;作为一种先进的生产管理理念&#xff0c;为企业提供了实现这一平衡的有效路径。具体如深圳天行健精益管理咨询公司下文所述&#xff…

四川古力未来科技抖音小店畅享多重好处

在当今数字化浪潮席卷之下&#xff0c;四川古力未来科技抖音小店以其独特的魅力&#xff0c;正逐渐成为消费者们的新宠。作为融合了先进科技与便捷购物体验的创新平台&#xff0c;它不仅能够满足消费者的多样化需求&#xff0c;更在提升购物体验、优化服务流程等方面展现出了显…