6.11运算符重载

news2024/11/17 15:29:51

友元

友元的设置是因为这样就可以访问类中的private成员

设置某一个类或者是函数为友元的,用friend关键字声明友元

友元的三种形式:普通函数、成员函数、友元类

普通函数:在类内部声明然后再类外部定义。 

class Point{
public:
    friend float distance(const Point & rhs, const Point & rhs1);
private:
    int _ix;
    int _iy;
};

float distance(const Point & rhs, const Point & rhs1){
    return (rhs._ix - rhs1._ix );
}

成员函数:另一个类的成员函数访问该类的私有成员

Point设置为前向声明,这样line中可以知道有Point,但是不知有_ix,所以说将函数声明和实现拆开,实现放在这两个对象的后面。

【注意】一个friend只能声明一个友元函数,所以可以将friend放在前面

当需要申明的友元函数非常多的时候每次都要在这个类中设置友元,同时又在原类中声明在类下面进行实现很麻烦,所以说这样直接将一个类申明为另外一个类的友元类

//友元类的实现就是
class Point;
class Line{
函数声明;
};
class Point{
friend class Line;
};
函数定义;

友元的特点

友元:使得使用类内部的成员使用更加灵活。

但是破坏了类的封装性。

友元是单向的,如果说A类是B类的友元类,A可以访问B的私有成员。

class B{

        friend class A;

};

友元不具备传递性。

友元不能被继承。

运算符重载

这样的话就可以和自定义类型进行一样的操作,非常的方便。

不能重载的运算符:有5个

可以进行重载的运算符有42个

【了解】sizeof int 可以这样写

1234点运算符和sizeof是不能重载的

运算符重载的规则以及形式:

1. 操作数据的类型必须要有自定义类型或者枚举类型(如果都是int那么1+1 = 0乱套了)

2. 优先级和结合性固定不变的

3.操作符的个数是保持不变的,运算符重载的时候不能设置默认参数,这样的话操作数的个数就发生了变化

4. &&与||不再具有短路求值特性,不能造一个根本就不存在的运算符

运算符重载的形式有三种:

  1. 采用友元函数的重载形式
  2. 采用普通函数的重载形式
  3. 采用成员函数的重载形式

友元函数实现的重载形式 

这样就可以借助于friend关系来访问私有元素,比第二种更加安全,只能在这个函数才能访问私有元素,不像是通过类中外开放接口。

//友元的方式实现运算符重载
class Complex{
    //...
    friend Complex operator+(const Complex & lhs, const Complex & rhs);
    //...
};

//友元函数中的普通函数形式实现重载
Complex operator+(const Complex & lhs, const Complex & rhs){
    //...
}

void test0(){
    Complex cx(1,2);
    Complex cx2(3,4);
    Complex cx3 = cx + cx2; //看上去和内置类型的计算一样了
    //Complex cx3 = operator(cx,cx2);   //本质上是调用了operator+函数
}

普通的函数实现的重载形式

对于私有成员的保护不够,因为在外面直接使用getReal和getImage就可以获取私有元素

class Complex {
public:
	//...
	double getReal() const { return _real; }
	double getImage() const { return _image; }
	//...
};

Complex operator+(const Complex & lhs, const Complex & rhs)
{
	return Complex(lhs.getReal() + rhs.getReal(),
			lhs.getImage() + rhs.getImage());
}

void test0()
{
	Complex c1(1, 2), c2(3, 4);
	Complex c3 = c1 + c2;//ok
}

还可以通过成员函数来实现

class Complex{
public:
	//...
	Complex operator+(const Complex & rhs)
	{
		return Complex(_real + rhs._real, _image + rhs._image);
	}
};

【注意】在类中实现的时候就需要注意只是需要传递一个对象就可以,因为本身还有一个this指针指向的对象。

像加号这一类不会修改操作数的值的运算符,倾向于采用友元函数的方式重载。参数中会标记const,这样就不能对于函数中的数

p3 = p1 + p2
//如果是成员函数
p3 = p1.operator+(p2);

//友元函数
p3 = operator+(p1,p2);

进行修改。如果是定义在类内部,那样的话就可以直接访问并且修改私有成员的内容。

【注意】友元函数和成员函数的表面都是+,本质上有区别

+=运算符

对于需要修改最终的对象的时候倾向于采用成员函数的方式实现。

重载形式的选择(重点)

  • 不会修改操作数的值的运算符,倾向于采用友元函数的方式重载
  • 会修改操作数的值的运算符,倾向于采用成员函数(有this提供就不写该对象)的方式重载

赋值=、下标[ ]、调用()、成员访问->、成员指针访问->* 运算符必须是成员函数形式重载

考虑函数的返回值,尽量和内置类型对象保持一致

【了解】在类内部只有=赋值运算符函数是类是默认提供的,其他的赋值运算符函数是不会默认提供的

++运算符重载

难点在于有前置++和后置++之分,借助于函数参数区别虽然不需要参数但是在后置++的参数中写int就可以

前置++是返回修改以后的对象,使用引用。参数是无参的。

后置++是返回修改修改以前的副本,返回副本。参数设置为int,只是为了区分。

//前置++的形式
    Complex& operator++(){
        cout << "Complex & operator++()" << endl;
        ++_real;
        ++_image;
        return *this;
    }

    //后置++的形式
    //参数列表中要多加一个int
    //与前置形式进行区分
    Complex operator++(int){
        cout << "Complex operator++(int)" << endl;
        Complex tmp(*this);
        ++_real;
        ++_image;
        return tmp;
    }

[]运算符

class CharArray{
public:
    CharArray(const char * pstr)
    : _capacity(strlen(pstr) + 1)
    , _data(new char[_capacity]())
    {
        strcpy(_data,pstr);
    }

    ~CharArray(){
        if(_data){
            delete [] _data;
            _data = nullptr;
        }
    }

    //"hello"来创建
    //capacity = 6
    //下标只能取到 4
    char & operator[](size_t idx){
        if(idx < _capacity - 1){
            return _data[idx];
        }else{
            cout << "out of range" << endl;
            static char nullchar = '\0';
            return nullchar;
        }
    }

    void print() const{
        cout << _data << endl;
    }
private:
    size_t _capacity;
    char * _data;
};

【注意】前面的const是不能修改指向,后面的那个const是不能修改其中的内容,但是如果是存的是一个指针的话,那么是不能修改这个指针指向,但是其中的内容也是可以进行改动的,如果不想让内容后序改动只能初始化的话,那么就在private那个指针的前面加上const

【注意】只需要提供一个idx就可以,因为会有this指针已经指向这个对象了

输入输出流重载

输出是一个cout(ostream对象),因为cout(cin)是不能进行复制的所以使用引用,同理形参的位置也是使用引用

cout和cin不能复制是因为在内核代码中对于拷贝构造函数是删除的。

成员函数因为隐含的this指针是在第一个,所以只能写成很奇怪的方式

p1 << cout;

所以说只能是使用友元函数的方式

class Point {
public:
	//...
	friend ostream & operator<<(ostream & os, const Point & rhs);

private:
	int _x;
	int _y;
};

ostream & operator<<(ostream & os, const Point & rhs)
{
	os << "(" << rhs._x << "," << rhs._y << ")";
	return os;
}

void test0(){
    Point pt(1,2);
    cout << pt << endl; //本质形式: operator<<(cout,pt) << endl;
}

输入流

仍然是用友元函数的方式实现

cin的返回对象仍然是cin本身,需要修改对象本身传入一个引用

class Complex {
public:
	//...
	friend istream & operator>>(istream & is, Complex & rhs);
private:
	int _real;
	int _image;
};

istream & operator>>(istream & is, Complex & rhs)
{
	is >> rhs._real;
	is >> rhs._image;
	return is;
}

成员访问运算符(->这个可以重载,但是.不可以重载)

本节的例子感觉有点奇特,要求明白意思。

class Data
{
public:
    Data(){}
    ~Data(){}

    int getData() const{ return _data; }
private:
    int _data = 10;
};

class MiddleLayer
{
public:
    MiddleLayer(Data * p)
    : _pdata(p)
    {}

    ~MiddleLayer(){
        if(_pdata){
            delete _pdata;
            _pdata = nullptr;
        }
    }
private:
    Data * _pdata;
};

 要求使用MiddleLayer ml;来实现Data * _pdata的功能。理由是因为M中只有一个指针Data * _pdata,要求ml对象实现完成指针的所有功能。

为什么需要进行封装:将一个栈上的指针封装成一个对象的时候,因为栈上的对象被销毁的时候,会主动调用析构函数,可以回收堆上的空间。

Data * p = new Data();
p->getData();
(*p).getData();
delete p;
p = nullptr;

//要求可以实现
ml->getData();
(*ml).getData();
ml = nullptr;

如果使用下面的方式进行初始化的时候,不能进行delete pdata操作,会出现double free问题。因为如果使用了delete pdata;但是M中的指针依然是指向这个位置,当M销毁的销毁的时候调用析构函数清理堆上空间的时候就出问题了。

如果是让pdata单独管理一块堆上的空间的话,就不会出现问题。

重载类型没得选,只能成员函数形式(特定规定->)。

返回类型是Data 类型的指针。

在getData()前面会自动添加->指针,即使是已经对于->指针进行了重载。

智能指针的最大的效果就是自动回收。

要返回一个引用,这样的话不是对于副本进行操作,而是对于本身。 

Data & operator *(){
    return *_pdata;
}

 额外再加一层:

Data *p = new Data();
MiddleLayer ml(p);
ThirdLayer tl(ml);

上述这种会出现tl管理栈上对象的效果,这样的话当tl(最后被调用)先销毁的时候销毁了栈上的空间,当程序结束的时候程序销毁栈上空间的时候出现问题。

想法就是将这个栈上的空间放到堆上去,同时借鉴于避免浅拷贝的问题所以都是使用匿名

ThirdLayer tl(new MiddleLayer(new data));

在构造的时候先构造thL然后Ml然后是data构造函数。

析构函数的调用也是同样的顺序。

这个地方在thl对象的->返回的是ml对象,ml的又会调用->获取到的data *pdata然后就是正常的->

【注意】->只能是写一次,下面的*可以一次次的调用,也可以一次调用

【理解】这个地方需要注意->和*函数和返回值是一样的,因为都是需要返回一个middleLayer对象,然后借助于middleLayer对象的运算符重载进行处理。

//*可以多次调用

cout << (*(*tl)).getData() << endl;

//*的实现直接一步到位的使用

*tl.getData();

//一步到位的实现

//方式一:借助于跨层的实现,*pml得到ml对象,*ml得到data
Data & operator*(){
    return *(*pml);
}

//方式二:借助于友元函数的实现,因为ml中的_pdata是一个私有元素
//所以在ml类中设置TL为友元类,这样的话就可以在tl中访问_pdata指针
Data & operator*(){
    return *(*pml)._pdata;
}

//在函数的中调用方式都是这样
cout << (*tl).getData() << endl;

//但是两者的本质是不同的
//方式一:借助于跨层重载
cout << (tl.operator*()).operator*().getData() << endl;

//方式二:借助于友元类
cout << tl.operator*().getData() << endl;

可调用实体

普通函数,函数指针,成员函数指针,类中的成员函数,函数对象都被称为可调用实体。

函数对象

重载了函数调用运算符的类的对象称为函数对象,可以像对象一样使用,也可以像函数一样使用

背景需求:希望对象像函数一样被调用。查看函数被调用多少次,如果是直接定义全局变量不安全,如果是定义局部静态变量出了大括号就不能随便访问了。所以说一个想法是将这个计数器放在类中的私有区域中,通过一个接口在访问访问次数。

所以说:函数对象可以携带函数的状态(函数被调用的次数)。

对于运算符()进行重载

class FunctionObject{
public:
    void operator()(){
        cout << "FunctionObject operator()()" << endl;
        ++ _count;
    }

    int operator()(int x, int y){
        cout <<"operator()(int,int)" << endl;
        ++ _count;
        return x + y;
    }
    
    int _count = 0;//携带状态
};

void test0(){
    FunctionObject fo;
  
    cout << fo() << endl;
    cout << fo.operator()() << endl;//本质

    cout << fo(5,6) << endl;
    cout << fo.operator()(5,6) << endl;//本质

    cout << "fo._count:" << fo._count << endl;//记录这个函数对象被调用的次数
}

函数指针

void print(int x){
    cout << "print:" << x << endl;
}

void display(int x){
    cout << "display:" << x << endl;
}

int main(void){
    //省略形式
    void (*p)(int) = print;
    p(4);
    p = display;
    p(9);
    
    void (*)(int) p = print;//error
    
    //完整形式
    void (*p2)(int) = &print;
    (*p2)(4);
    p2 = &display;
    (*p2)(9);
}

p和p2可以抽象出一个函数指针类型void(*)(int) —— 逻辑类型,不能在代码中直接以这种形式写出

使用typedef定义:

Function类的“对象”可以这样使用,这个类的“对象”都是特定类型的函数指针,只能指向一种函数(这种函数的类型在定义函数指针类时就决定了)

typedef void(*Function)(int);

    Function f;
    f = print;
    f(19);
    f = display;
    f(27);

成员函数指针

定义一个成员函数信息,确定属于哪一个类,函数返回类型,函数信息

非静态类的函数

typedef void (*Function)(int); //定义函数指针类型

typedef void (FFF::*MemberFunction)(int); //定义成员函数指针类型

.*是成员指针运算符的一种形式

//只能这么写,定义时需要使用完整形式即&即取地址
//还需要指明类的命名,因为在成员函数在存储的时候也会有类的标记
void (FFF::*p)(int) = &FFF::print;
FFF ff;
(ff.*p)(4);

//使用的时候进行解引用
//通过对象调用
//需要解引用,和定义配套的使用。同时加上*也不会从这个类中找相应的函数
//又优先级比较高所以加()
//.*是配套的成员指针运算符一种形式

->*成员指针运算符的一种形式

FFF * fp = new FFF();

(fp->*mf)(65);//通过指针调用成员函数指针

函数指针的意义:就是将函数视为一个变量存在,也可以将函数指针作为参数传给别的函数。

成员函数也是同样的效果,就是记得加上类名。

【注意】注意一个问题,一个空指针也可以调用得到结果

现象:空指针没有指向有效的对象。对于不涉及数据成员的成员函数,不需要实际的对象上下文,因此就算是空指针也可以调用成功。对于涉及数据成员的成员函数,空指针无法提供有效的对象上下文,因此导致错误。

原因:因为在查找的时候就直接从程序代码区就可以找到相应的类名对应的成员函数,不会用到这个对象,但是当访问对象中的成员的时候,需要借助于这个对象指针。

类型转换函数

以前又int向double这种转换,现在讨论自定义类对象向系统类型转换,以及转换回来。

以前见到过类似的隐式转换

因为默认参数:Point p = 1;

string str = "hello";将const char *转换为string类型

class Point{
public:
//将返回类型写在中间
 operator int(){
     cout << "operator int()" << endl;
     return _ix + _iy;
 }
	//...
};

自定义类型转换内置类型

Point pt(3,4);

pt = 1;

一般是先进行隐式转换,发现1可以通过默认参数构造成一个point,然后再调用到赋值运算符函数

如果将默认构造函数的隐式转换关掉,那么就出错。

这个时候可以修改赋值运算符函数传入一个int数据得到一个point对象

num = pt使用类型转换函数,不然只能去int去修改int的函数。

自定义类型向自定义类型进行转换

多种方式

1.可以在一个类中实现类型转换

class Complex
{
 //...
 operator Point(){
     cout << "operator Complex()" << endl;
     return Point(_real,_image);
 }
};

2.也可以进行隐式转换

注意前向声明(Point可以知道又com),声明为friend(com中可以访问),以后后来的定义

3.也可以使用赋值运算符修改的方式

如果当前2条内容都存在的时候那么会使用类型转换函数,而不是使用隐式转换函数,不要系统自己隐式转换了。类型转换的优先级高于隐式转换。

如果当这些3条内容都存在的时候那么会使用赋值运算符函数,因为赋值直接就可以使用,不用再进行类型转换。

string类型的+=

其实const函数和非const函数其中的内容是一样的,但是必须要再定义一遍,因为const只能使用const函数。

const函数的形式

const char & operator[](String &s) const{}前面的const 用来限制不能修改这一片区域的内容,后面的这个const是用来限制只是被const对象来使用。

输入流的实现

耍流氓做法一

//TODO这个地方也就是耍流氓,只能动态扩容,勉强用vector实现
istream &operator>>(std::istream &is, String &s){
    string word;
    is >> word;
    if(s._pstr){
        delete [] s._pstr;
        s._pstr = new char[word.size() + 1]();
        strcpy(s._pstr, word.c_str());
    }
    return is;
}

耍流氓做法二

//TODO这个地方也就是耍流氓,只能动态扩容,勉强用vector实现
istream &operator>>(std::istream &is, String &s){
    vector<char> vec;
    char ch;
    while((ch = is.get()) != '\n'){
        vec.push_back(ch);
    }
    s._pstr = new char[vec.size() + 1]();
    strncpy(s._pstr, &vec[0], vec.size());
    return is;
    /*
    string word;
    is >> word;
    if(s._pstr){
        delete [] s._pstr;
        s._pstr = new char[word.size() + 1]();
        strcpy(s._pstr, word.c_str());
    }
    return is;
    */
}

string的函数重载

//string.hpp
#include <iostream>
using namespace std;

class String 
{
public:
	String();
	String(const char *);
	String(const String &);
	~String();
	String &operator=(const String &);
	String &operator=(const char *);

	String &operator+=(const String &);
	String &operator+=(const char *);
	
	char &operator[](std::size_t index);
	const char &operator[](std::size_t index) const;
	
	std::size_t size() const;
	const char* c_str() const;
	
	friend bool operator==(const String &, const String &);
	friend bool operator!=(const String &, const String &);
	
	friend bool operator<(const String &, const String &);
	friend bool operator>(const String &, const String &);
	friend bool operator<=(const String &, const String &);
	friend bool operator>=(const String &, const String &);
	
	friend std::ostream &operator<<(std::ostream &os, const String &s);
	friend std::istream &operator>>(std::istream &is, String &s);

private:
	char * _pstr;
};

String operator+(const String &, const String &);
String operator+(const String &, const char *);
String operator+(const char *, const String &);
//string.cc
#include "string.hpp"
#include <string.h>

String::String()
:_pstr(new char[1]()) 
{}
String::String(const char * pstr)
:_pstr(new char[strlen(pstr) + 1]())
{
    strcpy(_pstr, pstr);
}
String::String(const String &pstr)
:_pstr(new char[pstr.size() + 1]())
{
    strcpy(_pstr, pstr.c_str());
}
String::~String(){
    if(_pstr){
        delete [] _pstr;
        _pstr = nullptr;
    }
}
String & String::operator=(const String &pstr){
    if(&pstr != this){
        //REM 判断的是指针!!
        if(_pstr) delete [] _pstr;
        _pstr = new char[strlen(pstr._pstr) + 1]();
        strcpy(_pstr, pstr._pstr);
    }
    return *this;
}

String &String::operator=(const char *pstr){
    if(_pstr) delete [] _pstr;
    _pstr = new char[strlen(pstr) + 1]();
    strcpy(_pstr, pstr);
    return *this;
}

String & String::operator+=(const String & rhs){
    //这样的话了解底层空间的分配问题
    //程序块结束的时候会回收指针但是不会回收堆上的空间
    char *temp = new char[strlen(_pstr) + strlen(rhs._pstr)+ 1]();
    strcpy(temp, _pstr);
    strcat(temp, rhs._pstr);
    delete [] _pstr;
    _pstr = temp;

    return *this;
    /*
    char *temp = new char[strlen(_pstr) + 1]();
    strcpy(temp, _pstr);
    delete [] _pstr;
    if(this != &rhs) _pstr = new char[strlen(temp) + strlen(rhs._pstr) + 1]();
    else _pstr = new char[strlen(temp) + strlen(temp) + 1]();
    strcpy(_pstr, temp);
    if(this != &rhs) strcat(_pstr, rhs._pstr);
    else strcpy(_pstr, temp);
    delete [] temp;
    return *this;
    */
}

String &String::operator+=(const char * pstr){
    char *temp = new char[strlen(_pstr) + 1]();
    strcpy(temp, _pstr);
    delete [] _pstr;
    _pstr = new char[strlen(temp) + strlen(pstr) + 1]();
    strcpy(_pstr, temp);
    strcat(_pstr, pstr);
    delete [] temp;
    return *this;
}

char &String::operator[](std::size_t index){
    return _pstr[index];    
}

const char & String::operator[](std::size_t index) const{
    return _pstr[index];
}

size_t String::size() const{
    return strlen(_pstr);
}

const char* String::c_str() const{
    return _pstr;
}
	
bool operator==(const String &lhs, const String &rhs){
    return !strcmp(lhs._pstr, rhs._pstr);
}

bool operator!=(const String &lhs, const String &rhs){
    return strcmp(lhs._pstr, rhs._pstr);
}

bool operator<(const String & lhs, const String & rhs){
    for(size_t idx = 0; idx < lhs.size(); idx ++){
        //只有相等才能继续往后比较
        if((lhs._pstr[idx] - rhs._pstr[idx] < 0 )){
            return true;
        }else if(lhs._pstr[idx] - rhs._pstr[idx] > 0 ){
            return false;
        }
    }
    if(rhs.size() > lhs.size()) return true;
    return false;
}

bool operator>(const String &lhs, const String &rhs){
    if(lhs == rhs) return false;
    if(lhs < rhs) return false;
    return true;
}
bool operator<=(const String &lhs, const String &rhs){
    if(lhs > rhs) return false;
    return true;
}
bool operator>=(const String &lhs, const String &rhs){
    if(lhs < rhs) return false;
    return true;
}
ostream &operator<<(std::ostream &os, const String &s){
    if(s._pstr) os << s._pstr;
    //REM 需要尽量避免万一是空指针
    return os;
}
//TODO
istream &operator>>(std::istream &is, String &s){
    string word;
    is >> word;
    if(s._pstr){
        delete [] s._pstr;
        s._pstr = new char[word.size() + 1]();
        strcpy(s._pstr, word.c_str());
    }
    return is;
}

String operator+(const String &lhs, const String &rhs){
    String temp = lhs;
    temp += rhs;
    return temp;
}
String operator+(const String &lhs, const char *s){
    String temp(s);
    String res = lhs + temp;
    return res;
}
String operator+(const char *s, const String &rhs){
    String temp(s);
    temp += rhs;
    return temp;
}
//stringTest.cc
#include "string.hpp"
#include <iostream>

void test(){
    String s1("h");
    String s2(s1);
    cout << "s1: " << s1 << endl;
    cout << "s2: " << s2 << endl;

    cout << endl << endl;
    s2 += s1;
    cout << "+= s2: " << s2 << endl;
    s2 += "u";
    cout << "+= s2: " << s2 << endl;
    cout << endl << endl;

    s2[0] = 'u';
    cout << "[] s2[0]: " << s2 << endl;
    cout << "s2.size(): " << s2.size() << endl;
    cout << "s2.c_str(): " << s2.c_str() << endl;

    cout << endl << endl;
    //REM 闇€瑕佸姞鎷彿
    cout << "s1 == s2: " << (s1==s2) << endl;
    cout << "s1 != s2: " << (s1!=s2) << endl;

    cout << "s1 > s2 " << (s1 > s2) << endl;
    cout << "s1 < s2 " << (s1 < s2) << endl;

    cout << "s1 >= s2 " << (s1 >= s2) << endl;
    cout << "s1 <= s2 " << (s1 <= s2) << endl;

    String s3;
    cin >> s3;
    cout <<"s3: " <<  s3;
    String s4;
    cin >> s4;

    cout << "s3 + s4: " << s3 + s4 << endl;
    cout << "s3 + h" << s3 + "h" << endl;
    cout << "h + s3" << "h" + s3 << endl;
}

void test1(){
    String s1 = "h";
    s1 += s1;
    cout << s1 << endl;
}
int main()
{
    test1();
    return 0;
}

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

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

相关文章

Linux2(文件类型分类 基本命令2 重定向)

目录 一、文件类型分类 二、基本命令2 1. find 帮助查询 2. stat 查看文件的信息 3. wc 统计文本 4. 查看文本内容 4.1 cat 4.2 more 4.3 less 4.4 head 4.5 tail 5. cal 显示日历 6. date 显示时间 7. du 文件大小 8. ln 链接 软链接 硬链接 区别 9. histo…

6.8日志系统

当做大型项目的时候&#xff0c;出了bug可能需要借助于日志检查&#xff0c;小项目一般是打断点。 服务器是一直在运行的&#xff0c;不能停止&#xff0c;可以借助于日志检查错误。 日志分为两种&#xff1a;业务级别的日志&#xff08;供用户分析业务过程&#xff09;&…

基于springboot实现教学资料管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现教学资料管理系统演示 摘要 使用旧方法对教学资料管理系统的信息进行系统化管理已经不再让人们信赖了&#xff0c;把现在的网络信息技术运用在教学资料管理系统的管理上面可以解决许多信息管理上面的难题&#xff0c;比如处理数据时间很长&#xff0c;数据存…

【ARM Coresight Debug 系列 -- ARMv8/v9 Watchpoint 软件实现地址监控详细介绍】

请阅读【嵌入式开发学习必备专栏 】 文章目录 ARMv8/v9 Watchpoint exceptionsWatchpoint 配置信息读取Execution conditionsWatchpoint data address comparisonsSize of the data accessWatchpoint 软件配置流程Watchpoint Type 使用介绍WT, Bit [20]: Watchpoint TypeLBN, B…

《一头扎进》系列之Python+Selenium框架实战篇23- 价值好几K的框架,呵!这个框架有点意思啊!!!

宏哥微信粉丝群&#xff1a;https://bbs.csdn.net/topics/618423372 有兴趣的可以扫码加入 1.简介 前面文章&#xff0c;我们实现了框架的一部分功能&#xff0c;包括日志类和浏览器引擎类的封装&#xff0c;今天我们继续封装一个基类和介绍如何实现POM。关于基类&#xff0c;…

JAVA小知识17:数组,从0基础到掌握

数组&#xff0c;无论在哪种编程语言当中都是最基础&#xff0c;最广泛使用的一种线性表数据结构&#xff0c;这篇文章将从多个角度来从浅入深的讲述数组。 本文讲述了数组的概念&#xff0c;定义&#xff0c;初始化方法以及如何遍历数组&#xff0c;如何赋值&#xff0c;关于数…

基于WPF技术的换热站智能监控系统03--实现左侧加载动画

1、左侧布局规划 左侧分5行&#xff0c;每行的高度通过height属性来指定&#xff0c;1.2*表示占1.2倍的宽度 2、创建用户控件 在WPF中想要进行个性化处理&#xff0c;主要可以通过三个方面来实现&#xff1a;控件模板&#xff08;控件模板、数据模板、数据容器模板&#xff09…

EDEX-UI这个终端模拟器

eDEX-UI 是一款开源、免费、跨平台的全屏终端模拟器和系统监视器&#xff0c;外观和操作界面极其科幻&#xff0c;灵感来自电影《创战纪》的会议室特效场景。作者倾注了大量心血&#xff0c;使得它不仅拥有酷炫的操作界面&#xff0c;还具备清晰爽脆的音效。 优点&#xff1a; …

【车载AI音视频电脑】4路AHD 130万像素双卡车载录像机

产品主要特点&#xff1a; -支持4路实时高清AHD 720P录像 -SD卡记录数据&#xff08;可支持2张大容量SD卡,最大支持单张256G&#xff09; -支持GPS全球定位, 可选模块 -支持WIFI高速自动下载功能, 可选模块 -内置3/4G模块&#xff0c;实时预览和远程管理&#xff0c; 可选…

打造你的专属扭蛋机:淘宝扭蛋机小程序搭建全攻略

想要在互联网娱乐领域大展拳脚吗&#xff1f;淘宝扭蛋机小程序或许是你的不二选择。本文将为你提供详细的搭建教程&#xff0c;帮助你轻松打造属于自己的扭蛋机小程序。 一、了解扭蛋机小程序的基本原理 在开始搭建之前&#xff0c;我们需要了解扭蛋机小程序的基本原理。扭蛋机…

收藏一些毕业论文技术路线图

*信息来源&#xff1a;xhs 立青Jill 原文链接https://mp.weixin.qq.com/s?__bizMzUyNzczMTI4Mg&mid2247693272&idx3&snf6c8513eaee894c5158dc5c3620bf93c&chksmfa76ace5cd0125f3169b2782c137f6308c6d201d3a845db1be8b397758a1f11e3719524e601b&token18515…

图片转Excel表格:提升数据处理效率的利器

在日常工作和生活中&#xff0c;我们经常遇到各种数据和信息以图片的形式存在。有时&#xff0c;这些数据图片中包含了重要的表格信息&#xff0c;例如财务报告、统计数据或调研结果。为了对这些数据进行进一步的分析和处理&#xff0c;我们需要将其转换为可编辑的电子表格格式…

Android WebSocket长连接的实现

一、为什么需要 WebSocket 初次接触 WebSocket 的人&#xff0c;都会问同样的问题&#xff1a;我们已经有了 HTTP 协议&#xff0c;为什么还需要另一个协议&#xff1f;它能带来什么好处&#xff1f; 答案很简单&#xff0c;因为 HTTP 协议有一个缺陷&#xff1a;通信只能由客…

程序员职业素养

程序员应该具备的职业素养 一、专业精神1.1、专业精神在程序员职业生涯中的重要性1.2、追求技术的过程1.3、专业精神对团队和项目的影响1.4、专业精神在个人职业发展中的意义 二、沟通能力2.1 沟通能力在程序员职业生涯中的重要性2.2 沟通能力的要素2.2.1. 有效的口头和书面表达…

UPS负载箱的使用注意事项有哪些?

UPS负载箱是用于模拟电网中各种负载的设备&#xff0c;广泛应用于电力系统、通信系统、数据中心等领域。为了保证UPS负载箱的正常运行和使用安全&#xff0c;在使用过程中需要注意以下几点&#xff1a; 1. 选择合适的负载箱&#xff1a;根据实际需求选择合适的负载箱&#xff0…

DevExpress WPF中文教程:Grid - 如何完成列和编辑器配置(设计时)?

DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress WPF能创建有着强大互动功能的XAML基础应用程序&#xff0c;这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案。 无论是Office办公软件…

数据密集型企业是如何选择替代FTP传输文件的系统的?

数据密集型企业是指其发展和运行高度依赖于数据、算法和算力的闭环优化体系的企业。这类企业拥有规模化知识创造者、更广泛的智能工具以及更丰裕的数据要素资源。 毋庸置疑&#xff0c;数据对于数据密集型企业来说是最关键、最核心的资产&#xff0c;但数据密集型企业同样也面临…

航天科技集团与SPACEX公司的思考与分析

近期&#xff0c;中国航天科技集团正式发文与SPACEX对标的认识结果&#xff0c;包括发展理念上、科研生产模式上、关键核心技术上、质量效率效益上存在明显差距与不足。真诚的态度&#xff0c;赢得了社会上的广泛关注和积极评价。真心为老东家能够保持这份清醒而高兴。 从对标管…

WPF学习(1)--类与类的继承

在面向对象编程中&#xff0c;继承是一种机制&#xff0c;允许一个类&#xff08;称为子类或派生类&#xff09;从另一个类&#xff08;称为父类或基类&#xff09;继承属性和方法。继承使我们能够创建一个通用类&#xff0c;然后根据需要扩展或修改它以创建更具体的类。以下是…

关于pip的15个使用小技巧

认识pip 众所周知&#xff0c;pip可以对python的第三方库进行安装、更新、卸载等操作&#xff0c;十分方便。 pip的全称&#xff1a;package installer for python&#xff0c;也就是Python包管理工具。 可能有些人用了很久pip&#xff0c;但还不清楚包管理工具是个啥。 我…