C++笔记:C++中的重载

重载的概念

一.函数重载

代码演示例子:


#include<iostream>
using namespace std;

//函数名相同,在是每个函数的参数不相同
void output(int x) {
    printf("output int : %d\n", x);
    return ;
}

void output(long long x) {
    printf("output long long : %llX\n", x);
    return ;
}

void output(const char *s) {
    if (!s) {
        printf("output string : nullptr\n");
        return ;
    }
    printf("output string : %s\n", s);
    return ;
}

void output(int x, int y) {
    printf("output double int : %d, %d\n", x, y);
    return ;
}
//如果调用这个函数时,没有传入参数3,也就是c
//那么c的默认值就为123,并且程序不会编译不通过
void output(int a, char b, int c = 123) {
    printf("output a = %d, b = %c, c = %d\n", a, b, c);
    return ;
}

//如果这里没有第三个参数,那么就会造成编译报错
//因为没有办法通过返回值类型进行区分重载类型
//也就是没有办法区分和上面的double int进行区分
const char *output(int a, int b, char c) {
    printf("output a = %d, b = %d, c = %c\n", a, b, c);
    return "yes";
}


int main() {
    output(3);//这里匹配的是int类型的output
    //在数字后面加上LL 表示这个数字是longlong类型
    output(3LL);//这里匹配的是longlong类型的output
    output("hello world");//匹配参数为char *的output
    output(3, 4);//匹配参数为两个int类型的output
    output(3LL, 4);//近似匹配了两个int类型的output
    //下面这个output(NULL),他会匹配到int类型
    //NULL可以表示成0值
    //然后它也可以表示成一个地址,然后它又是0值的地址,地址有64位也就是8字节,那他也会匹配long long类型
    //然后还表示一个空地址,还会匹配到char *类型
    //output(NULL);
    //而在C++中 nullptr只表示空地址,这样他就会精确匹配对应的函数
    output(nullptr);
    output(12, 'p');
    cout << output(3, 4, 'c') << endl;
    return 0;
}

然后如果output(NULL)那行代码没有注释掉,那么就会出现,注释说明的情况,无法匹配造成歧义:

对于成员函数重载和普通函数的重载一样,我就不用代码进行演示了。

二.运算符重载

不能重载的运算符:

类外:

参考代码:

#include <iostream>
#include <cstdio>
using namespace std;

class Point {
public :
    Point() : x(0), y(0), output_width(0) {}
    Point(int x, int y) : x(x), y(y), output_width(0) {}
private :
    int x, y;
    int output_width;
    //利用友元进行解决私有成员属性的访问问题
    friend ostream &operator<<(ostream &out, const Point &p);
    friend int operator*(const Point &, const Point &);
    friend Point operator+(const Point &, int );
    friend double operator*(const Point &, double );
    friend double operator*(double , const Point &);
    friend Point &operator-(Point &, int x) ;
    friend Point &operator-(Point &, Point &) ;
    friend Point &operator-(Point &, const char *) ;
};
//ostream类型就像cout的对象的类型
//加上const为了同时支持const和非const限定的对象
ostream &operator<<(ostream &out, const Point &p) {
    out << "("  << p.x << ", " << p.y << ")";
    return out; 
}

int operator*(const Point &p1, const Point &p2) {
    return p1.x * p2.x + p1.y * p2.y;
}

Point operator+(const Point &p1, int x) {
    Point p(p1.x + x, p1.y + x);
    return p;
}

double operator*(const Point &p1, double x) {
    return p1.x * x + p1.y * x;
}

double operator*(double x, const Point &p1) {
    //这里他调用的就是上面运算符重载的方法
    return p1 * x;
}

Point &operator-(Point &p, int x) {
    p.output_width = x; 
    return p;
}

Point &operator-(Point &p1, Point &p2) {
    char str[100] = {0};
    snprintf(str, 99, "(%%%dd, %%%dd)", p1.output_width, p1.output_width);
    printf(str, p2.x, p2.y);
    return p1;
}

Point &operator-(Point &p, const char *s) {
    printf("%s", s);
    return p;
}

Point &operator^(Point &p1, const Point &p2) {
     
    return p1;
}

Point &operator^(Point &p1, int x) {

    return p1;
}

int main() {
    Point p1(5, 6), p2(3, 6), p3(6, 9), p4(10, 12);
    //cout他也不认识我们创建的Point类
    //如果要对cout进行对p1进行输出
    //那么我们就要对<<这个左移运算符进行重载
    cout << p1 << endl;
    //这里对运算符的重载可以不用实现和我一样的,可以通过自己的想象然后来实现
    //然后实现的结果和自己的想象的需要是一样的结果
    cout << p1 * p2 << endl;
    cout << p1 * 2.3 << endl;
    cout << 2.3 * p1 << endl;
    cout << p1 + 5 << endl;
    //实现效果
    //p1 - 6设置输出的位宽为6
    //- p2 输出p2的值,并且输入的位宽为p1的位宽
    //- "\n" 换行
    p1 - 6 - p2 - "\n";
    return 0;
}

练习:

        实现下面的代码:

    Point p1(5, 6), p2(3, 6), p3(6, 9), p4(-2, -4);
    cout << p1 << p2 << p3 << p4 << endl;
    //^运算符将每个类中的成员属性x,y输出到一个坐标轴中
    //^1时打印出这个坐标轴
    p1^p2^p3^p4^1;

参考代码:

#include <iostream>
#include <cstdio>
using namespace std;

#define MAX_N 20

class Point {
public :
    Point() : x(0), y(0) {}
    Point(int x, int y) : x(x), y(y) {}
    ~Point() {}
    static void init_x_y_axis() {
        for (int i = 0; i < MAX_N; i++) {
            Point::x_y_axis[i] = new int[MAX_N + 5];
        }
        return ;
    }
    static void set_x_y_axis(int x, int y) {
        if (x_y_axis[x + MAX_N / 2][y + MAX_N / 2]) return ;
        x_y_axis[x + MAX_N / 2][y + MAX_N / 2] = 1;
        return ;
    }
    void output() {
        cout << sizeof(Point::x_y_axis[0]) << endl;
        cout << sizeof(Point::x_y_axis) << endl;
    }

    friend Point &operator^(Point &, Point &);
    friend Point &operator^(Point &, int);
    friend ostream &operator<<(ostream &, const Point &);
private :
    int x, y;
    //创建一个类属性
    //用来当作坐标轴
    static int **x_y_axis;
};

int **Point::x_y_axis = new int*[MAX_N + 5];


Point &operator^(Point &p1, Point &p2) {
    //将每个对象的x,y输出到坐标轴中
    Point::set_x_y_axis(p1.x, p1.y);
    Point::set_x_y_axis(p2.x, p2.y);
    return p1;
}

Point &operator^(Point &p1, int num) {
    //我们要求的是^1才打印
    if (num != 1) return p1;
    for (int y = MAX_N / 2; y > -MAX_N / 2; y--) {
        for (int x = -MAX_N / 2; x < MAX_N / 2; x++) {
            !x && y && printf("%3d", y);
            if (!y) {
                printf("%3d", x);
            } else {
                if (Point::x_y_axis[x + (MAX_N / 2)][y + (MAX_N / 2)]) {
                    printf("%3c", '*');
                }
                else if (x) printf("%3c", ' ');
            }
        }
        putchar(10);
    }
    return p1;
}

ostream &operator<<(ostream &out, const Point &p1) {
    out << "p("  << p1.x << ", " << p1.y << ")" << endl;
    return out;
}

int main() {
    Point::init_x_y_axis();
    Point p1(5, 6), p2(3, 6), p3(6, 9), p4(-2, -4);
    cout << p1 << p2 << p3 << p4 << endl;
    //^运算符将每个类中的成员属性x,y输出到一个坐标轴中
    //^1时打印出这个坐标轴
    p1^p2^p3^p4^1;
    return 0;
}

实现效果:

        实现结果可以和我不一样,但是一定要实现你自己的想法,C++的语法就是非常的灵活并且也非常容易出错,所以这才是C++的难处.

        注意:学习C++是学习C++的设计模式。

类内:

    对于下面的运算符,只能再类内中重载,但是不是意思是只能重载这些运算符,对于类外可以重载的运算符,类内一样也可以重载

在说类内的运算符之前说一个知识点左值右值: 

左值右值

带入代码理解:

#include<iostream>
using namespace std;

#define LEFT_OR_RIGHT(expr) {\
    printf("expr : %s\n", #expr);\
    left_or_right(expr);\
    printf("\n");\
}

void left_or_right(int &x) {
    printf("left value : %d\n", x);
    return ;
}

void left_or_right(int &&x) {
    printf("right value : %d\n", x);
    return ;
}


int main() {
    int a = 123;
    //a可以通过单一变量a访问
    //那么他就是左值
    LEFT_OR_RIGHT(a);
    //a + 1是中间产生临时的一个值
    //那么他无法通过单一变量进行访问到
    //那他就是一个右值
    //因为在过了下面这行代码后,我们没有办法进行通过单一变量进行访问到它
    LEFT_OR_RIGHT(a + 1);
    //任何字面量的值都是右值
    LEFT_OR_RIGHT(123);
    //这里a++你带入进去的是a的值
    //然后带入后,a进行了a += 1
    //那么在这行代码之后你无法通过单一变量去访问到之前的a值
    //那么它就是右值
    LEFT_OR_RIGHT(a++);
    //++a带入的是 a += 1的值
    //在这行代码之后,它可以通过单一变量a去访问到这个值
    //那他就是左值
    LEFT_OR_RIGHT(++a);
    //a += 2同理它可以通过变量a去访问到这个值
    LEFT_OR_RIGHT(a += 2);
    return 0;
}

执行结果,和我代码注释推断的结果是一样的:

然后下一个版本:

#include<iostream>
using namespace std;

#define LEFT_OR_RIGHT(expr) {\
    printf("expr : %s\n", #expr);\
    left_or_right(expr);\
    printf("\n");\
}

void left_or_right(int &&, int);

void left_or_right(int &x, int flag = 1) {
    printf("left value : %d\n", x);
    if (flag) left_or_right(x, 0);
    return ;
}

void left_or_right(int &&x, int flag = 1) {
    printf("right value : %d\n", x);
    if (flag) left_or_right(x, 0);
    return ;
}


namespace test1 {
int main() {
    int a = 123;
    //a可以通过单一变量a访问
    //那么他就是左值
    LEFT_OR_RIGHT(a);
    //a + 1是中间产生临时的一个值
    //那么他无法通过单一变量进行访问到
    //那他就是一个右值
    //因为在过了下面这行代码后,我们没有办法进行通过单一变量进行访问到它
    LEFT_OR_RIGHT(a + 1);
    //任何字面量的值都是右值
    LEFT_OR_RIGHT(123);
    //这里a++你带入进去的是a的值
    //然后带入后,a进行了a += 1
    //那么在这行代码之后你无法通过单一变量去访问到之前的a值
    //那么它就是右值
    LEFT_OR_RIGHT(a++);
    //++a带入的是 a += 1的值
    //在这行代码之后,它可以通过单一变量a去访问到这个值
    //那他就是左值
    LEFT_OR_RIGHT(++a);
    //a += 2同理它可以通过变量a去访问到这个值
    LEFT_OR_RIGHT(a += 2);
    return 0;
}
}

int main() {
    //test1::main();
    left_or_right(123);
    return 0;
}

执行结果:

为什么呢,123先调用右值引用没有问题吧,然后现在flag为1,需要再次调用left_or_right()函数,然后呢现在他的参数为x而不是123,那么在执行完成调用函数这句代码之后,我还是可以通过x进行访问到这个值,在右值引用这个函数的作用域里面这个x是持久态,我调用完成这个函数我是可以进行访问到的,所以第二次调用就会调用左值引用。

那么问题来了,我想保持当前的右值引用如何操作呢:

现在去理解move函数就是把move函数里的参数强制转换为右值,如果你想了解的更深可以自己看看move函数的原型,以及具体如何操作的。  

if (flag) left_or_right(move(x), 0);

改完这行代码后执行结果:

还有一种方式:

forward<>()函数,forward 函数通常用于完美转发,用于将参数原封不动地传递给另一个函数,保持参数的值类别不变。

if (flag) left_or_right(forward<int &&>(x), 0);

forward在这句代码的作用就是将x作为右值引用作为传入参数,但是x他还是左值引用。

但是move是将x变为了右值引用。

移动构造 

那么说完左值右值,那么对于构造函数还有一种形式,叫做移动构造:

代码演示:

#include <iostream>
#include <cassert>
#include <ctime>
using namespace std;

class Array {
public :
    Array(int n) : n(n), arr(new int[n]) {
        cout << "array default constructor " << arr << endl;
    }
    Array(const Array &a) : n(a.n), arr(new int[n]) {
        cout << "copy array constructor " << arr << endl;
        for (int i = 0; i < n; i++) arr[i] = a[i];
        return ;
    }
    //再函数调用时,如果没有返回值优化
    //那么调用func()函数时,会先默认构造a,
    //默认构造返回值的隐匿对象,这里创建了新的空间
    //然后a拷贝给匿名对象
    //然后又将匿名拷贝给b对象,有创建了新空间
    //而移动构造,直接将第一次a创建的空间直接给匿名对象
    //匿名对象又通过移动构造将创建的空间给b对象,省去了中间创建新空间的步骤和释放空间的步骤
    Array(Array &&a) : n(a.n), arr(a.arr) {
        cout << "move constructor" << arr << endl;
        a.arr = nullptr;
        a.n = 0;
        return ;
    }
    //这里为什么要返回int &
    //因为你在访问该位置时,又可以能会将该位置进行赋值
    //所以需要返回int &
    int &operator[](int ind) const{
        assert(ind >= 0 && ind < n);
        return arr[ind];
    }
    
    void output(const char *frm) {
        for (int i = 0; i < n; i++) {
            printf(frm, i, arr[i]);
        }
    }
    ~Array() {
        if (arr) delete[] arr;
        cout << "array disconstructor " << arr << endl;
        return ;
    }
private :
    int n;
    int *arr;
};

Array func() {
    int n = 7;
    Array a(n);
    for (int i = 0; i < n; i++) {
        a[i] = i;
    }
    return a;
}

int main() {
    srand(time(0));
    int n = 10;
    printf("a = ");
    Array a(n);
    for (int i = 0; i < n; i++) {
        a[i] = rand() % 100;
    }
    a.output("a[%d] = %d\n");
    Array b = func();
    b[0] = 999;
    b.output("b[%d] = %d\n");
    //如果对于a对象不用了,想将a对象所有的东西给c对象
    //那么就可以调用移动构造,使用move函数将a对象传入时变为右值
    Array c(move(a));
    c.output("c[%d] = %d\n");
    return 0;
}

 类内的运算符重载代码演示:
 前后++代码演示:
#include<iostream>
using namespace std;

class Point {
public :
    Point(int x, int y) : x(x), y(y) {}
    Point(const Point &a) : x(a.x), y(a. y) {}
    //+重载, p1 + p2 就是 p1.x + p2.x, p1.y + p2.y
    //因为返回的是一个新的值,所以是右值,返回值类型就不是引用,也就是不是左值
    Point operator+(const Point &p) {//他这里是成员方法,比如它可以访问this指针
        Point ret(x + p.x, y + p.y);
        return ret;
    }
    //由于是前++,就不需要参数
    //前++返回的是左值,所以返回值类型也是左值
    //返回的对象也是本身
    Point &operator++() {
        cout << "++class" << endl;
        this->x += 1, this->y += 1;
        return *this;
    }
    //后++是一个右值,在演示左值和右值代码中有示例
    //那么返回的是一个新的值,所以返回值类型也是右值,而不是左值引用
    //参数列表中有参数,就是后++
    Point operator++(int) {
        cout << "class++" << endl;
        Point ret(*this);
        //代码设计逻辑和技巧
        ++(*this);
        return ret;
    }
    friend ostream &operator<<(ostream &out, const Point &p);
private :
    int x, y;
};

ostream &operator<<(ostream &out, const Point &p) {
    out << "(" << p.x << ", " << p.y << ")";
    return out;
}


int main() {
    Point a(1, 2), b(3, 4);
    cout << "a : " << a << endl;
    cout << "b : " << b << endl;
    cout << a + b << endl;
    //这样去调用运算符重载的函数和上行代码执行效果一样
    cout << a.operator+(b) << endl;
    cout << "++a : " << ++a << endl;
    cout << "b++ : "<< b++ << endl;
    cout << "b : "<< b << endl;
    return 0;
}

=赋值运算符重载代码演示:


#include<iostream>
using namespace std;

class A {
public :
    A() {
        cout << "default constructor " << this << endl;
    }
    A(const A &a) {
        cout << "copy constructor " << this << endl;
    }
    A(const A &&a) {
        cout << "move constructor " << this << endl;
    }
    //对于=重载
    A &operator=(const A &a) {
        //new关键字的原地构造
        //在this指针这块位置调用拷贝构造
        //this指针指向的就是下面的对象c
        new(this) A(a);
        cout << "operator= " << this << endl;
        return *this;
    }
    A &operator=(A &&a) {
        new(this) A(move(a));
        cout << "operator= " << this << endl;
        return *this;
    }
};


int main() {
    A a, c, d;
    A b = a;
    c = a;
    d = move(a);
    cout << "a = " << &a << endl;
    cout << "b = " << &b << endl;
    cout << "c = " << &c << endl;
    cout << "d = " << &d << endl;
    return 0;
}
[]、->、()运算符重载代码演示:

#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

class Array_Object {
public :
    int operator[](int ind) {
        return 2 * ind;
    }
};

class Function_Object {
public :
    int operator()(int x) {
        return 2 * x; 
    }
};

class Point {
public :
    Point(int x, int y) : x(x), y(y){
        printf("x = %d, y = %d\n", x, y);
    }
    int x, y;
};

class Point_Object {
public :
    Point_Object() : p(new Point(rand() % 100, rand() % 100)) {}
    Point *operator->() {
        return p; 
    }
    ~Point_Object() {
        delete p;
    }
private :
    Point *p;
};

int main() {
    srand(time(0));
    Array_Object arr;
    Function_Object fun;
    Point_Object p;
    cout << p->x << " " << p->y << endl;
    for (int i = 0; i < 10; i++) {
        cout << "arr[" << i << "]" << arr[i] << endl;
    }
    for (int i = 0; i < 10; i++) {
        cout << "fun(" << i << ")" << fun(i) << endl;
    }
    return 0;
}

 

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

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

相关文章

RTU遥测终端为城市排水安全保驾护航!

近年来&#xff0c;全球气候变迁与城市化进程不断加速&#xff0c;导致强降雨事件频发&#xff0c;道路低洼地带、下穿式立交桥和隧道等区域在暴雨中常易积水&#xff0c;严重阻碍了人民的出行&#xff0c;甚至危及生命与财产安全。而传统的排水管网管理方式已难以适应现代城市…

mybatis的使用技巧8——联合查询union和union all的区别和用法

在实际项目开发中&#xff0c;会经常联合查询结构相似的多张数据表&#xff0c;使用union关键字就只需要一次sql操作&#xff0c;而无需执行多次查询并通过代码逻辑合并处理&#xff0c;减少了大量繁琐的操作&#xff0c;最重要的是还能通过可选的all关键字筛选重复的数据。 1…

数据结构基础:链表操作入门

数据结构基础&#xff1a;链表操作入门 数据结构基础&#xff1a;链表操作入门链表的基本概念链表的基本操作输出链表插入节点删除节点查找值 完整的链表操作示例结语 数据结构基础&#xff1a;链表操作入门 在计算机科学中&#xff0c;数据结构是组织和存储数据的方式&#x…

海康Visionmaster-常见问题排查方法-启动失数

问题2&#xff1a;VM无法启动&#xff0c;报错&#xff1a;参数错误&#xff1b;  问题原因&#xff1a;客户电脑环境异常导致代理启动失败。  解决方法&#xff1a;安装运行时库&#xff0c;并测试代理能否正常启动,步骤如下&#xff1a; ① 尝试双击代理进程&#xff…

WPF 6 命令

命令 创建一个按钮&#xff0c;新建一个事件&#xff0c;按住F12 就可以添加业务代码 运行代码 此时希望UI与后台代码分离&#xff0c;互不影响 此时新建一个MainViewModel类&#xff0c;来保存业务代码 Icommand 是所有command的父类接口 新建一个command来实现这个接口…

OceanBase诊断调优 】—— 如何快速定位SQL问题

作者简介&#xff1a; 花名&#xff1a;洪波&#xff0c;OceanBase 数据库解决方案架构师&#xff0c;目前负责 OceanBase 数据库在各大型互联网公司及企事业单位的落地与技术指导&#xff0c;曾就职于互联网大厂和金融科技公司&#xff0c;主导过多项数据库升级、迁移、国产化…

蓝桥杯:日期问题(我的绝望题)

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;每日一练 &#x1f337;追光的人&#xff0c;终会万丈光芒 目录 前言&#xff1a; &#x1f337;1.问题描述&#xff1a; 1.问题描述&#xff1a; 2.输入格式&#xff1a; 3.输出格式&#…

HarmonyOS开发案例:【相机开发】

基本概念 相机是OpenHarmony多媒体进程提供的服务之一&#xff0c;提供了相机的录像、预览、拍照功能&#xff0c;支持多用户并发取流。 在进行应用的开发前&#xff0c;开发者应了解以下基本概念&#xff1a; 视频帧 视频流指的是将一系列图片数据按照固定时间间隔排列形成的…

探索设计模式的魅力:主从模式与AI大模型的结合-开启机器学习新纪元

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 ✨欢迎加入探索主从模式与AI大模型之旅✨ &#x1f31f;Hey, tech enthusiasts! 你是否还在追…

AI论文速读 |2024[TPAMI]【综述】自监督学习在时间序列分析的分类、进展与展望

题目&#xff1a; Self-Supervised Learning for Time Series Analysis: Taxonomy, Progress, and Prospects 作者&#xff1a;Kexin Zhang, Qingsong Wen(文青松), Chaoli Zhang, Rongyao Cai, Ming Jin(金明), Yong Liu(刘勇), James Zhang, Yuxuan Liang(梁宇轩), Guansong…

运维 kubernetes(k8s)基础学习

一、容器相关 1、发展历程&#xff1a;主机–虚拟机–容器 主机类似别墅的概念&#xff0c;一个地基上盖的房子只属于一个人家&#xff0c;很多房子会空出来&#xff0c;资源比较空闲浪费。 虚拟机类似楼房&#xff0c;一个地基上盖的楼房住着很多人家&#xff0c;相对主机模式…

【python程序打包教程】PyInstaller一键打包Python程序为独立可执行exe文件

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

springboot论文格式系统

采用springbootmysqlhtmlvue技术 详细运行视频地址B站运行效果视频地址 &#xff08;1&#xff09;系统具备用户管理功能&#xff0c;包括用户注册、登录、权限管理等功能。 &#xff08;2&#xff09;系统具备格式规范管理功能&#xff0c;包括格式规范创建、编辑、删除等操…

Unity系统学习笔记

文章目录 1.基础组件的认识1.0.组件继承关系图1.1.项目工程文件结构&#xff0c;各个文件夹都是做什么的&#xff1f;1.2.物体变化组件1.2.3.三维向量表示方向1.2.4.移动物体位置附录&#xff1a;使用变换组件实现物体WASD移动 1.3.游戏物体和组件的显示和禁用1.3.1.界面上的操…

面试八股——RabbitMQ

消息丢失问题 消息确认机制 生产者与MQ之间的消息确认&#xff1a; 当MQ成功接收消息后&#xff0c;会返回给生产者一个确认消息。如果在规定时间内生产者未收到确认消息&#xff0c;则任务消息发送失败。 MQ与消费者之间的消息确认&#xff1a; 当MQ成功接收消息后&#…

related_name和related_query_name属性

在Django模型继承中&#xff0c;假如在外键或多对多字段中使用了related_name属性或related_query_name属性&#xff0c;则必须为该字段提供一个独一无二的反向名字和查询名字。但是&#xff0c;这样在抽象基类中一般会引发问题&#xff0c;因为基类中的字段都被子类继承并且保…

Python网络爬虫-详解XPath匹配网页数据

前言 XPath&#xff0c;全称XML Path Language&#xff0c;即XML路径语言&#xff0c;它是一门在XML文档中查找信息的语言。XPath使用路径表达式来选取XML文档中的节点或节点集。这些节点是通过沿着路径&#xff08;path&#xff09;或者步&#xff08;steps&#xff09;来选取…

从0到1—POC编写基础篇(一)

POC编写基础篇 POC的概念 在网络安全领域中&#xff0c;POC的概念是指"Proof of Concept"&#xff0c;也被称为"攻击验证"。它是指安全研究人员或黑客用来证明某个漏洞、弱点或安全问题存在的实证或演示。 网络安全研究人员经常通过开发POC来展示一个漏洞的…

【Node.js】03 —— HTTP 模块探索

&#x1f31f;Node.js之HTTP模块探索✨ &#x1f31f;引言 在网络编程中&#xff0c;HTTP协议无处不在。在Node.js的世界里&#xff0c;我们可以通过内置的http模块来轻松创建HTTP服务器和客户端&#xff0c;实现数据的接收和发送。今天就让我们一起打开这扇门&#xff0c;探索…

SpringBoot + kotlin 协程小记

前言&#xff1a; Kotlin 协程是基于 Coroutine 实现的&#xff0c;其设计目的是简化异步编程。协程提供了一种方式&#xff0c;可以在一个线程上写起来像是在多个线程中执行。 协程的基本概念&#xff1a; 协程是轻量级的&#xff0c;不会创建新的线程。 协程会挂起当前的协…