06-C++ 类和对象-多态

类与对象

多态

1. 简介

一个事物的多种形态,简称多态。

  • 物的多态

    同一个人在不同人面前,角色不同

    如:

    1. 在父母面前
    2. 在对象面前
    3. 在朋友面前
    4. 在同事面前
  • 事的多态

    同一种事情,在不同情况下展现不同

    如:

    1. 吃饭

      • 中国人 筷子 熟食
      • 美国人 刀叉 7分熟
      • 印度人 手 咖喱饭
    2. 睡觉

      • 中国人 床上
      • 日本人 地上

      平躺

      侧卧

      趴着

2. 上行与下行

2.1 上行

子类 转 父类

语法:

父类名 *父类对象指针 = 子类对象指针;
或
父类名& 父类对象名 = 子类对象;

注意:

无风险,无需强转。

2.2 下行

父类 转 子类

语法:

子类名 *子类对象指针 = (子类名 *)父类对象指针;
或
子类名& 子类对象 = (子类名&) 父类对象;

注意:

有风险,需强转。

2.3 示例
#include <iostream>
using namespace std;

class Anim{
public:
    int a;
    Anim(){}
    Anim(int a):a(a){}
};

class Dog:public Anim{
public:
    int d;
    Dog(){}
    Dog(int a, int d):Anim(a),d(d){}
};
class Cat:public Anim{
public:
    int c;
};

int main(int argc, char *argv[])
{
    //上行
    //将子类对象的地址赋值给父类对象的引用或指针
    Dog d1(10, 100);
    Anim& a1 = d1;
    //赋值给指针,记得取地址,不然报错
    Anim* a2 = &d1;
    cout << "d1.d=" << d1.d << endl;    //d1.d=100
    cout << "a1.a=" << a1.a << endl;    //a1.a=10
    //子类转父类后, 父类对象不能使用子类所特有的属性
//    cout << "a1.d=" << a1.d << endl;      //报错

    //下行
    Dog d2 = (Dog &)a1;
    Dog *d3 = (Dog *)a2;
    cout << "d2.d=" << d2.d << endl;    //d2.d=100
    //父类转换子类后,子类可以使用父类的
    cout << "d2.a=" << d2.a << endl;    //d2.a=10
    //下行父转子
    Cat& c1 = (Cat &)a1;
    cout << "c1.a=" << c1.a << endl;    //c1.a=10
    //d1的内存中没有c,所以c1.c 的值为 d1中d的值
    cout << "c1.c=" << c1.c << endl;    //c1.c=100

    return 0;
}

在这里插入图片描述

分析:

  • Dog继承了 Anim,所以d1对象中 有a 也有 d;
  • 父转子,c1也指向 d1(可能会报错,编译器优化可能也不报错)

3. 重写

继承关系中,返回值类型相同,函数名形同,形参列表相同,函数体不同 。

重载

同一作用域下,函数名相同,形参列表不同

重定义

继承关系中,函数名相同即可。一旦重定义后,子类会将父类的函数覆盖掉

4. c++多态分类

多态分为:

  • 物的多态(上行、下行)

  • 事的多态(静态多态,动态多态)

4.1 静态多态(早绑定,静态联编)

概念: 在编译阶段 就确定函数的入口地址
又名: 静态联编,早绑定
如: 函数重载,运算符重载,重定义等

函数在代码区,函数名就是这个函数在代码区的地址,这个地址就是这个函数的地址

4.2 动态多态(晚绑定,动态联编)

概念: 在运行阶段确定程序入口地址
又名: 动态联编,晚绑定
如: 虚函数,重写

5. 引入

要求:设计一个函数,根据传入的对象调用重写的方式。

5.1 示例

需求:

小明开了一个宠物医院,可以给狗看病,可以给猫看病
    可以给猪看病

    张女士带着他家的狗旺财去找小明给狗看病
    李女士带着他家的猫布丁去找小明给猫看病
    王先生带着他家的猪佩奇去找小明给猪看病
    
分析:
    对象
        小明
        张女士
        李女士
        王先生
        旺财
        布丁
        佩奇
    类
        动物类
            属性:
               姓名
               
            狗类
            猫类
            猪类
            人类
                属性:
                    动物
                get与set
                宠物医生类
                    看病

代码:

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

class Anim{
private:
    char name[50];
public:
    Anim(){}
    Anim(char *name)
    {
        strcpy(this->name,name);
    }
    char* getName()
    {
        return name;
    }
    void setName(char *name)
    {
        strcpy(this->name,name);
    }
    void call()
    {
        cout << "动物叫" << endl;
    }
};
class Dog:public Anim
{
public:
    Dog(){}
    Dog(char *name):Anim(name){}
    //重写父类call方法
    void call()
    {
        cout << this->getName() << ":汪汪汪" << endl;
    }
};
class Cat:public Anim
{
public:
    Cat(){}
    Cat(char *name):Anim(name){}
    //重写父类call方法
    void call()
    {
        cout << this->getName() << ":喵喵喵" << endl;
    }
};
class Pig:public Anim
{
public:
    Pig(){}
    Pig(char *name):Anim(name){}
    //重写父类call方法
    void call()
    {
        cout << this->getName() << ":哼哼哼" << endl;
    }
};
class Person:public Anim
{
private:
    Anim* anim;
public:
    Person(){
        anim = NULL;
    }
    Person(char *name):Anim(name){}
    Person(char *name,Anim *anim):Anim(name),anim(anim){}
    ~Person()
    {
        if(anim != NULL)
        {
            delete anim;
            anim = NULL;
        }
    }
    void setAnim(Anim* anim){
        this->anim = anim;
    }
    Anim* getAnim()
    {
        return anim;
    }
    //重写父类call方法
    void call()
    {
        cout << this->getName() << ":哇哇哇" << endl;
    }
};
class AnimDoctor:public Person{
public:
    AnimDoctor(){}
    AnimDoctor(char *name):Person(name)
    {

    }
    AnimDoctor(char *name,Anim* anim):Person(name,anim)
    {

    }
    void cb(Anim* anim)
    {
        cout << this->getName() << "给" << anim->getName() << "看病" << endl;
        
        anim->call();
    }
};
int main(int argc, char *argv[])
{
    Dog * dog = new Dog("旺财");
    Cat * cat = new Cat("布丁");
    Pig * pig = new Pig("佩奇");

    Person *p1 = new Person("张女士",dog);
    Person *p2 = new Person("李女士",cat);
    Person *p3 = new Person("王先生",pig);

    AnimDoctor * doctor = new AnimDoctor("小明");

    doctor->cb(p1->getAnim());
    return 0;
}

//小明给旺财看病
//动物叫
//小明给布丁看病
//动物叫
//小明给佩奇看病
//动物叫
5.2 问题

父类有个函数 void call(){},要求是子类继承并重写 父类 该方法,每个子类 动物在看病时,都会叫 “狗:汪汪汪,猫:喵喵喵…” ;

但是,此时现状是,每次打印出来的都是 父类 void call(){} 函数中 的 “动物叫”(子传父 上行之后调用的依旧是父类的call函数),而我们需要的是 子传父之后,调用的是每个子类特有的方法。

所以需要引入 虚函数

6. 虚函数

概念:virtual 修饰的成员函数,就是虚函数

语法:

virtual 返回值类型 函数名(形参列表)
{
	函数体
}

注意:

  • 子类在继承父类时,会生成 虚函数指针

对比如下:以上边动物看病为例

  • 不是虚函数

在这里插入图片描述

在这里插入图片描述

  • 虚函数:下面第2幅图可以看出,子类继承了父类的 name 和 虚函数指针 vfptr ,此时指针指向的是 子类自己的 函数 Dog::call

在这里插入图片描述

在这里插入图片描述

特点:

子类转换为父类 后:

  • 使用该父类调用 使用virtual修饰的函数,调用的是子类重写后的函数

  • 使用该父类调用普通函数,调用的是父类的该函数

6.1 上边示例修改
#include <iostream>
using namespace std;
#include <cstring>
class Anim{
private:
    char name[50];
public:
    Anim(){}
    Anim(char *name)
    {
        strcpy(this->name,name);
    }
    char* getName()
    {
        return name;
    }
    void setName(char *name)
    {
        strcpy(this->name,name);
    }
    virtual void call()
    {
        cout << "动物叫" << endl;
    }
};
class Dog:public Anim
{
public:
    Dog(){}
    Dog(char *name):Anim(name){}
    virtual void call()
    {
        cout << this->getName() << ":汪汪汪" << endl;
    }
};
class Cat:public Anim
{
public:
    Cat(){}
    Cat(char *name):Anim(name){}
    void call()
    {
        cout << this->getName() << ":喵喵喵" << endl;
    }
};
class Pig:public Anim
{
public:
    Pig(){}
    Pig(char *name):Anim(name){}
    void call()
    {
        cout << this->getName() << ":哼哼哼" << endl;
    }
};
class Person:public Anim
{
private:
    Anim* anim;
public:
    Person(){
        anim = NULL;
    }
    Person(char *name):Anim(name){}
    Person(char *name,Anim *anim):Anim(name),anim(anim){}
    ~Person()
    {
        if(anim != NULL)
        {
            delete anim;
            anim = NULL;
        }
    }
    void setAnim(Anim* anim){
        this->anim = anim;
    }
    Anim* getAnim()
    {
        return anim;
    }
    void call()
    {
        cout << this->getName() << ":哇哇哇" << endl;
    }
};
class AnimDoctor:public Person{
public:
    AnimDoctor(){}
    AnimDoctor(char *name):Person(name)
    {

    }
    AnimDoctor(char *name,Anim* anim):Person(name,anim)
    {

    }
    void cb(Anim* anim)
    {
        cout << this->getName() << "给" << anim->getName() << "看病" << endl;
        anim->call();
    }
};
int main(int argc, char *argv[])
{
    Dog * dog = new Dog("旺财");
    Cat * cat = new Cat("布丁");
    Pig * pig = new Pig("佩奇");

    Person *p1 = new Person("张女士",dog);
    Person *p2 = new Person("李女士",cat);
    Person *p3 = new Person("王先生",pig);

    AnimDoctor * doctor = new AnimDoctor("小明");

    doctor->cb(p1->getAnim());
    doctor->cb(p2->getAnim());
    doctor->cb(p3->getAnim());
    /*
        当子类转换为父类后
        使用该父类调用使用virtual修饰的函数,调用的是子类重写后的函数
        使用该父类调用普通函数,调用的是父类的该函数
    */
    // 虚函数所在的类,依据可以直接创建对象
    Anim a;
    return 0;
}
//小明给旺财看病
//旺财:汪汪汪
//小明给布丁看病
//布丁:喵喵喵
//小明给佩奇看病
//佩奇:哼哼哼
6.2 动态绑定的条件(重要)
有继承,子类重写父类的虚函数,父类指针或引用指向子类空间(上行)。父类指针或引用才能调用子类重写的虚函数。

错误演示:
    B b;
    //此时会调用父类的拷贝构造,会产生一个新的父类对象,该 父类对象a 与 子类对象b 是两个独立空间
    //所以此时使用a对象调用test01依据会执行父类的test01函数
    A a = b; //拷贝构造
    a.test01();
6.3 动态绑定原理(机制)(重要)
  • 父类有虚函数,产生的 虚函数指针 指向 虚函数表,表中记录的是 父类的虚函数地址
  • 如果子类继承 父类,那么子类会继承父类的虚函数指针以及虚函数表。
  • 如果子类 重写父类的虚函数,会将将虚函数表纪录的入口地址修改成子类重写的函数入口地址。
  • 这时 父类指针指向子类空间,父类指针调用虚函数就 间接 调用子类重写的虚函数。

7. 纯虚函数

概念:父类的虚函数没有函数体

语法:

virtual 返回值类型 函数名(形参列表) = 0;

注意:

  • 纯虚函数所在的类不能 直接 创建对象,这种类被称为抽象类
  • 子类继承与抽象类,要么重写父类提供的所有纯虚函数,要么自己也是抽象类

示例:

#include <iostream>
#include <cstring>
using namespace std;
//纯虚函数所在的类称为抽象类
/*特点:
 *  1,抽象类不能直接创建对象
 *  2,子类继承与抽象类,要么重写所有纯虚函数,要么自己也是抽象类
 */
class Anim{
private:
    char name[50];
public:
    Anim(){}
    Anim(char *name)
    {
        strcpy(this->name,name);
    }
    char* getName()
    {
        return name;
    }
    void setName(char *name)
    {
        strcpy(this->name,name);
    }
    //纯虚函数
    virtual void call() = 0;
    virtual void sleep() = 0;
};
class Dog:public Anim{
public:
    Dog(){}
    Dog(char *name):Anim(name){}
    void call(){
        cout << "汪汪汪" << endl;
    }
};
class Cat:public Anim{
public:
    Cat(){}
    Cat(char *name):Anim(name){}
    void call(){
        cout << "喵喵喵" << endl;
    }
    void sleep(){
        cout << "在猫窝睡" << endl;
    }
};
int main(int argc, char *argv[])
{
    //有纯虚函数所在的类为抽象类,抽象类不能直接创建对象
    //Anim anim
    //子类继承与抽象类,要么重写所有纯虚函数,要么自己也是抽象类
    //Dog类只重写了call纯虚函数,但是没有重写sleep纯虚函数
    //所以Dog类也是抽象类,所以不能直接创建对象
    //Dog dog;
    //Cat类重写了Anim类所有的纯虚函数,所以Cat类不是抽象类
    Cat cat;

    Anim& anim = cat;
    cout << "Hello World!" << endl;
    return 0;
}

8. 虚析构造

8.1 问题引入
#include <iostream>

using namespace std;
class Anim{
public:
    Anim()
    {
        cout << "父类构造函数" << endl;
    }
    
    ~Anim(){
         cout << "父类析构函数" << endl;
    }
};

class Dog:public Anim{
public:
    Dog()
    {
        cout << "子类构造函数" << endl;
    }
    ~Dog()
    {
        cout << "子类析构函数" << endl;
    }
};
int main(int argc, char *argv[])
{
    Dog *dog = new Dog();
    Anim *anim = dog;
    delete anim;
    return 0;
}
//父类构造函数
//子类构造函数
//父类析构函数

问题:没有调用 子类析构函数

8.2 解决方案

将父类的析构函数 设置成 虚析构

虚析构函数是为了解决:基类的指针指向派生类对象,并用基类的指针删除派生类对象。

语法:

virtual ~析构函数()
{

}

示例:

#include <iostream>

using namespace std;
class Anim{
public:
    Anim()
    {
        cout << "父类构造函数" << endl;
    }
    //虚析构
    virtual ~Anim(){
         cout << "父类析构函数" << endl;
    }
};

class Dog:public Anim{
public:
    Dog()
    {
        cout << "子类构造函数" << endl;
    }
    ~Dog()
    {
        cout << "子类析构函数" << endl;
    }
};
int main(int argc, char *argv[])
{
    Dog *dog = new Dog();
    Anim *anim = dog;
    delete anim;
    return 0;
}
//父类构造函数
//子类构造函数
//子类析构函数
//父类析构函数

9. 纯虚析构(了解)

效果等同于 虚析构

语法:

virtual 析构函数名() = 0;

注意:需要在类外实现析构函数

示例:

#include <iostream>

using namespace std;
//class Anim{
//public:
//    Anim()
//    {
//        cout << "父类构造函数" << endl;
//    }
//    //虚析构
//    virtual ~Anim(){
//         cout << "父类析构函数" << endl;
//    }
//};
class Anim{
public:
    Anim()
    {
        cout << "父类构造函数" << endl;
    }
    //纯虚析构
    //类中定义
    virtual ~Anim() = 0;
};
//类外实现
Anim::~Anim()
{
    cout << "父类析构函数" << endl;
}
class Dog:public Anim{
public:
    Dog()
    {
        cout << "子类构造函数" << endl;
    }
    ~Dog()
    {
        cout << "子类析构函数" << endl;
    }
};
int main(int argc, char *argv[])
{
    Dog *dog = new Dog();
    Anim *anim = dog;
    delete anim;
    return 0;
}
virtual 析构函数名() = 0;

注意:需要在类外实现析构函数

示例:

#include <iostream>

using namespace std;
//class Anim{
//public:
//    Anim()
//    {
//        cout << "父类构造函数" << endl;
//    }
//    //虚析构
//    virtual ~Anim(){
//         cout << "父类析构函数" << endl;
//    }
//};
class Anim{
public:
    Anim()
    {
        cout << "父类构造函数" << endl;
    }
    //纯虚析构
    //类中定义
    virtual ~Anim() = 0;
};
//类外实现
Anim::~Anim()
{
    cout << "父类析构函数" << endl;
}
class Dog:public Anim{
public:
    Dog()
    {
        cout << "子类构造函数" << endl;
    }
    ~Dog()
    {
        cout << "子类析构函数" << endl;
    }
};
int main(int argc, char *argv[])
{
    Dog *dog = new Dog();
    Anim *anim = dog;
    delete anim;
    return 0;
}

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

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

相关文章

NXP实战笔记(一):基于RTD-SDK新建一个S32DS工程

目录 1、概述 2、操作步骤 2.1、新建Application工程 2.2、命名工程、选择芯片型号、选择编译器GCC版本 2.3、配置基本参数 3、文件描述 3.1、文件结构描述 3.2、编译之后 4、下载调试 1、概述 安装了S32DS之后&#xff0c;导入SDK插件&#xff0c;这个步骤不赘述&…

世界经济论坛制定了五项指导原则,实现跨OT环境的网络安全。

内容概述&#xff1a; 世界经济论坛在其题为“解锁工业环境中的网络弹性&#xff1a;五项原则”的报告中列出&#xff1a;原则一&#xff1a;执行全面风险管理OT 环境、原则二&#xff1a;确保OT工程师和安装操作员对OT网络安全负责、原则三&#xff1a;与高层组织领导、战略规…

传感器原理与应用复习--光电式与半导体式传感器

文章目录 上一篇光电传感器光电器件 光纤传感器光纤传感器的工作原理及组成 半导体传感器下一篇 上一篇 光电传感器 光电器件 每个光子的能量为 E h v E hv Ehv h为普朗克常数 6.626 ∗ 1 0 − 34 ( J / s ) 6.626 * 10^{-34}(J/s) 6.626∗10−34(J/s) ν \nu ν 为光…

yolov5 主要流程

1.介绍 本文包含了有关yolov5目标检测的基本流程&#xff0c;包括模型训练与模型部署&#xff0c;旨在帮助小伙伴们建立系统的认知&#x1f496;&#x1f496; YOLO是 "You only look once "的首字母缩写&#xff0c;是一个开源软件工具&#xff0c;它具有实时检测…

【Java开发岗面试】八股文—Java框架(Spring+SpringMVC+MyBatis+SpringBoot)

声明&#xff1a; 背景&#xff1a;本人为24届双非硕校招生&#xff0c;已经完整经历了一次秋招&#xff0c;拿到了三个offer。本专题旨在分享自己的一些Java开发岗面试经验&#xff08;主要是校招&#xff09;&#xff0c;包括我自己总结的八股文、算法、项目介绍、HR面和面试…

IC入门必备!数字IC中后端设计实现全流程解析(1.3万字长文)

吾爱IC社区自2018年2月份开始在公众号上开始分享数字IC后端设计实现相关基础理论和实战项目经验&#xff0c;累计输出文字超1000万字。全部是小编一个个字敲出来的&#xff0c;绝对没有复制粘贴的情况&#xff0c;此处小编自己得给自己鼓鼓掌鼓励下自己。人生不要给自己设限&am…

web前端开发html/css求职简介/个人简介小白网页设计

效果图展示&#xff1a; html界面展示&#xff1a; html/css代码&#xff1a; <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns"http://www.w3.…

scanf函数返回值被忽略

心怀希望的前进 前言 最近在复习c语言&#xff0c;发现了许多之前不了解的知识&#xff0c;今天想来与大家分享一下scanf返回值值被忽略的问题。 很多人应该都在vs中见到过&#xff0c;我们先说原因&#xff0c;再说改进方法 原因&#xff1a; scanf函数在读取数据时不会检…

华为无线ac双链路冷备和热备配置案例

所谓的冷备和热备&#xff0c;冷备就是不用vrrp和hsb协议同步ap和用户信息&#xff0c;主的断了等七十五秒后&#xff0c;备的capwap和ap连接上去。 双链路冷备不用vrrp和hsb 双链路热备份只用hsb同步ap和用户信息&#xff0c;不用vrrp&#xff0c;两个ac可以不用在同一个二层…

新年好,恭喜发财,喜气临门

2024&#xff0c;尽人事&#xff0c;听天命&#xff01; 身体健康。 尽力工作&#xff0c;身体健康优先。 减肥&#xff0c;适当运动。 休养生息。 技术人上班用脑子&#xff0c;下班就不要用脑子了。 放松脑细胞。 日子一天一天过&#xff0c;钱慢慢挣。 挣钱不在多少…

flask之文件管理系统-项目 JRP上线啦!!! ---修订版,兼容Windows和Linux系统

上一章的版本https://blog.csdn.net/weixin_44517278/article/details/135275066&#xff0c;在Windows下debug完成无异常后&#xff0c;上传到我的树莓下开始正式服役 由于开发环境是Windows&#xff0c;使用环境是Linux&#xff0c;导致最后没能成功运行起来 这个版本是今天去…

个人财务管理软件Money Pro mac功能特点

Money Pro mac是一款专为Mac用户设计的个人财务管理软件&#xff0c;具有全面的账户管理、智能的预算规划、强大的投资分析、丰富的报表和图表、安全的数据保护以及易于使用的界面设计等特点。 Money Pro mac功能和特点 全面的账户管理&#xff1a;支持多种账户类型&#xff0…

Threejs项目实战之四:实现地图雷达效果

目录 最终效果代码实现创建项目DigitalMapView.vue的核心代码 最终效果 最近事情比较多&#xff0c;今晚难得有空&#xff0c;就抽空完成了一个使用Threejs实现地图雷达扫描效果的程序&#xff0c;下面说下代码实现的原理及核心代码&#xff0c;老规矩&#xff0c;先看下效果图…

【模拟电路】常见电子元器件

一、常见电子元器件 二、电阻器 三、电容器 四、电感器 五、电容电感组成LRC振荡电路 六、保险丝、熔断器 七、锂电池 八、接插件 九、蜂鸣器 立创商城_一站式电子元器件采购自营商城-嘉立创电子商城 华秋商城(原"华强芯城")官网_电子元器件采购网_自营现货电子元器…

Jupyter Notebook又一地理数据可视化扩展!

本次分享一个Jupyter Notebook地理数据可视化扩展&#xff1a;pyl7vp pyl7vpPythonl7vp&#xff0c;如其名&#xff0c;是l7vp在Python3方向的封装&#xff0c;l7vp是蚂蚁集团AntV数据可视化团队开发的地理空间智能应用研发开源平台。 通过pyl7vp可在Jupyter Notebook中轻松完…

大模型系列:OpenAI使用技巧_使用文本向量化Embeding进行分类

文章目录 使用文本向量化Embeding进行分类使用文本向量进行零样本Zero Shot分类 使用文本向量化Embeding进行分类 有许多方法可以对文本进行分类。本笔记本分享了使用嵌入进行文本分类的示例。对于许多文本分类任务&#xff0c;我们已经看到微调模型比嵌入效果更好。请参见微调…

命令模式-举例

开关和电灯之间并不存在直接耦合关系&#xff0c;在命令模式中&#xff0c;发送者与接收者之间引入了新的命令对象&#xff0c;将发送者的请求封装在命令对象中&#xff0c;再通过命令对象来调用接收者的方法。 命令模式的主要缺点如下&#xff1a; 使用命令模式可能会导致某…

Android 13 - Media框架(27)- ACodec(五)

前面几节我们了解了OMXNodeInstance是如何处理setPortMode、allocateBuffer、useBuffer的&#xff0c;这一节我们再回到ACodec&#xff0c;来看看 ACodec start 的其他部分。 我们首先来回顾一下&#xff0c;ACodec start 的状态切换以及处理的事务&#xff0c;我们用一张不太准…

sudo: /usr/bin/sudo must be owned by uid 0 and have the setuid bit set问题解决方案

sudo: /usr/bin/sudo must be owned by uid 0 and have the setuid bit set问题解决方案 当我们使用sudo su切换权限时提示错误&#xff1a; sudo: /usr/bin/sudo must be owned by uid 0 and have the setuid bit set该错误出现原因&#xff1a;是因为/usr/bin/sudo的权限被…

GPT4All : 便捷易用的本地智能问答推理软件(乱记)

安装与使用 去官网 https://gpt4all.io/index.html下载可执行文件。 打开应用即可看到是否共享数据的选项&#xff1a; 然后自动进入模型下载界面 测试 内存占用 缺点&#xff1a;在我本地的轻薄本上运行时&#xff0c;风扇会有轻微噪声&#xff0c;关闭软件很久都没停止。…