惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
博客园_首页
H
Help Net Security
T
Tailwind CSS Blog
S
SegmentFault 最新的问题
GbyAI
GbyAI
Scott Helme
Scott Helme
D
Docker
Hacker News: Ask HN
Hacker News: Ask HN
P
Privacy & Cybersecurity Law Blog
Jina AI
Jina AI
雷峰网
雷峰网
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Spread Privacy
Spread Privacy
G
GRAHAM CLULEY
C
Cisco Blogs
The Hacker News
The Hacker News
F
Full Disclosure
Y
Y Combinator Blog
Blog — PlanetScale
Blog — PlanetScale
Recent Announcements
Recent Announcements
G
Google Developers Blog
量子位
K
Kaspersky official blog
Cisco Talos Blog
Cisco Talos Blog
The Cloudflare Blog
A
About on SuperTechFans
C
Cybersecurity and Infrastructure Security Agency CISA
Last Week in AI
Last Week in AI
博客园 - 三生石上(FineUI控件)
Microsoft Security Blog
Microsoft Security Blog
Martin Fowler
Martin Fowler
T
Tenable Blog
P
Palo Alto Networks Blog
H
Heimdal Security Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
W
WeLiveSecurity
Schneier on Security
Schneier on Security
The Register - Security
The Register - Security
F
Fortinet All Blogs
Stack Overflow Blog
Stack Overflow Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
T
The Blog of Author Tim Ferriss
N
News and Events Feed by Topic
Hugging Face - Blog
Hugging Face - Blog
小众软件
小众软件
V
V2EX
爱范儿
爱范儿

博客园 - GerJCS

《编程指北》动手写 C++ shared_ptr 章节开始(没钱买书,靠无尽追问豆包,反复思考质疑,一路拓展出很多东西,包括手写了个内存池,因为始终感觉没什么东西都是枯燥的概念对比,用Linux实践malloc又看不到RES回落,于是不知不觉摸索到了内存池) 《编程指北》动手写 C++ shared_ptr 章节开始(没钱买书,靠无尽追问豆包,反复思考质疑,一路拓展出很多东西,包括手写了个内存池,因为始终感觉没什么东西都是枯燥的概念对比,用Linux实践malloc又看不到RES回落,于是不知不觉摸索到了内存池) 续啃《编程指北 C++》智能指针(牵扯无穷无尽的其他知识) 续啃:编程指北 C++ (从 RAII开始的,RAII这一小节,学了将近两个月,RAII 的内容早在之前就主动追问豆包搞懂了,这节主要是自己无尽追问探索出很多其他知识,后来发现其实堪比精啃 CSAPP & APUE 等圣书) 编程指北的 C++ 续:啃操作系统 项目:从零开始做一个HTTP服务器(准备篇 —— 无尽弯路错路) C++:继续上一篇文章学CGI编程:乱码问题、误入歧途学了CGI编程(其实只是安装劝退因祸得福)、通过知乎直答及时更改学习方向、找到方向(花了2天) C++:网页网站、互联网、服务器、(web)服务器、(http)服务器、超文本、大型数据中心、云服务器、访问网页发生的流程、URL、DNS、域名、网址链接、IP地址、路由器、CGI架构图、万维网、tomcat、servlet、apache、学C++意义、动态静态请求、脚本、编码、Editplus、乱码 C++:多线程、进程、std::thread、互斥锁、reference_wrapper()、lambda()、cv.wait()、ref、条件变量、原子操作 、看书的意义(内存管理seq和宽松这需要看看)、线程局部存储、死锁、线程间通信(future/promise)、execution库并行 C++:#define等宏预处理器、中断信号处理 C++:命名空间、模版 C++:异常处理、二维数组/三维数组/对象的动态内存分配与delete释放 C++:数据抽象、数据封装、接口(之前做测试的时候听他们说接口始终不理解)、文件和流 C++:结构体、vector、队列、链表、哈希表、映射map、简述回顾O(logn)复杂度、哈希表和桶排序里的“桶”、set C++ 基本的输入输出、缓冲机制、cerr引发的超长折磨、同步问题 C++:数组、字符串、指针和引用、时间戳、覆盖静态存储区问题 C++:三角函数、随机数、数论线性同余(待研究)、math库函数、配置C++20、C++20的number、GCC版本和C++版本区别、M_PI、条件编译、头文件和命名空间和库函数区别、控制台/终端/命令行/cmd/bash lambda后续,实在受不了了,太痛苦了,全网找不到答案,详见最后,搜“诡异”,一个lambda整整搞了6天 菜鸟教程:运算符、指针和引用、(从刷算法题到现在目前为止最难啃的)Lambda、第一次主动了解学习new。对我来说这篇博客写的异常痛苦,不亚于刷过的最难算法题,最异常痛苦的是豆包的回答是错的,亏我还追着问了他整整3天关于lambda的事 菜鸟教程:存储类 菜鸟教程:修饰符、静态非静态、构造函数、类相关的杂七杂八的知识
C++:基本之前都深入问过豆包了,没啥新东西:类和对象、get()set()、公有私有保护、作用域解析运算符::、继承、函数重载、多态(从这开始不帖回答了,只放豆包回答链接)、虚函数
GerJCS · 2025-02-13 · via 博客园 - GerJCS

开始面向对象

菜鸟教程:C++ 类 & 对象  ~ C++ 多态

访问数据成员

精简代码

#include <iostream>
using namespace std;
class Box
{
   public:
      double length;   // 长度
      double breadth;  // 宽度
      double height;   // 高度
//      // 成员函数声明
      double get(void);
      void set( double len, double bre, double hei );
};
// 成员函数定义
double Box::get(void)
{
    return length * breadth * height;
}

void Box::set( double len, double bre, double hei)
{
    length = len;
    breadth = bre;
    height = hei;
}
int main()
{
   Box Box1;        // 声明 Box1,类型为 Box
   Box Box3;        // 声明 Box3,类型为 Box
   double volume = 0.0;     // 用于存储体积

   Box1.height = 5.0;
   Box1.length = 6.0;
   Box1.breadth = 7.0;

   volume = Box1.height * Box1.length * Box1.breadth;
   cout << volume <<endl;

   Box3.set(16.0, 8.0, 12.0);
   volume = Box3.get();
   cout << volume <<endl;
}

首先关于get()和set():

1、Box::get() 里的 get 不是固定的,可以叫 gett 等任意合法标识符,只要遵循命名规则;这里可以理解为一种虽然不是什么固定写法,可以随便起名字但为了方便导致大家都用get

2、其他成员函数也可以这么弄,即类内声明、类外使用作用域解析运算符 :: 定义

3、成员变量不能用这种方式,成员变量是在类内定义描述对象属性,而不是像成员函数那样类外通过 :: 定义。

成员函数定义 Box::get() 虽然不在类 Box 里直接写函数体,但通过作用域解析运算符 :: 表明它是类 Box 的成员函数,类里只是声明,函数体可在类外定义

Box1 和 Box3 本质都是 Box 类的对象,只是初始化方式不同,Box1 直接对成员变量赋值,Box3 通过调用 set 成员函数来设置成员变量的值。

注意到Box类里先对get成员函数声明了, 然后外面进行了定义:

内部声明后,定义写在内外都可以

而如果内部不声明,定义只能写在内部,不可以写在外部

注意到没写private还是public:

get 和 set 函数虽然没显式写 public 或 private ,但它们是公有的。原因如下:

  • 类成员访问控制规则:在 C++ 的类定义中,若不指定访问控制修饰符,成员的访问权限由上一个指定的访问控制修饰符决定。你代码里 public: 已经出现,后续未指定访问控制修饰符的成员函数(像 get 和 set 函数的声明)就都遵循此 public 权限,也就是这些函数能在类外被调用
class ExampleClass {
    int privateVar; // 未指定,默认 private
    void privateFunc() {} // 未指定,默认 private
public:
    int publicVar;
    int publichaha;  //未指定,由上一个public来定
};

由于类没显示指定访问权限,则默认是private的,再结合没指定也由上一个指定的访问权限规则,得知上述代码 

—————————————————— C++ 继承 ——————————————————

东西好多,他妈的

首先关于:::

:: 叫作用域解析运算符:主要用于指定要访问的标识符(如变量、函数、类等)所在的作用域,几个场景:

当在类外定义成员函数时,左侧是类名,右侧是成员函数名。

class MyClass {
public:
    void myFunction();
};

void MyClass::myFunction() {
    // 函数体
}

在使用命名空间中的标识符时,左侧是命名空间名,右侧是该命名空间内的标识符

namespace MyNamespace {
    int myVariable = 10;
}

int main() {
    int value = MyNamespace::myVariable;
    return 0;
}

MyNamespace::myVariable 表示访问 MyNamespace 命名空间里的 myVariable 变量

使用 :: 前面不写内容,表示全局作用域。例如当局部变量和全局变量同名时,可通过这种方式访问全局变量

#include <iostream>
int globalVar = 20;
int main() {
    int globalVar = 10;
    std::cout << "Local variable: " << globalVar << std::endl;
    std::cout << "Global variable: " << ::globalVar << std::endl;
}
class Animal {
public:
    void makeSound() {
        std::cout << "Some generic animal sound." << std::endl;
    }
};

class Dog : public Animal {
public:
    void makeSound() {
        std::cout << "Woof!" << std::endl;
    }

    void callBaseSound() {
        // 调用基类的 makeSound 函数
        Animal::makeSound();
    }
};

:是继承

// 基类
class Animal {
    // eat() 函数
    // sleep() 函数
};

// 派生类
class Dog : public Animal {
    // bark() 函数
};

初学者可以简单理解为

::是从左到右一次缩小范围

则是右边的是主(基类),派生是儿子(左边)

关于访问修饰符

菜鸟教程里的class Rectangle: public Shape继承了基类的全部成员函数和成员变量

问豆包更多:

一般情况下,若继承方式为公有(public)、保护(protected)继承,派生类会包含基类的全部成员(包括成员变量和成员函数)

但对于私有(private)继承也包含基类全部成员,只是访问权限有变化。不过,构造函数、析构函数和赋值运算符重载函数不会被继承,而是会根据自身情况重新定义。

  • 公有继承(public:基类的公有成员和保护成员在派生类中的访问权限不变,私有成员在派生类中不可直接访问。
  • 保护继承(protected:基类的公有成员和保护成员在派生类中变为保护成员,私有成员在派生类中不可直接访问。
  • 私有继承(private:基类的公有成员和保护成员在派生类中变为私有成员,私有成员在派生类中不可直接访问。

但说了等于没说,看具体例子

#include <iostream>

class Base {
public:
    int publicMember;
protected:
    int protectedMember;
private:
    int privateMember;
};

// 公有继承
class PublicDerived : public Base {
public:
    void accessMembers() {
        publicMember = 1; // 可访问
        protectedMember = 2; // 可访问
        // privateMember = 3; // 不可访问
    }
};

// 保护继承
class ProtectedDerived : protected Base {
public:
    void accessMembers() {
        publicMember = 1; // 可访问
        protectedMember = 2; // 可访问
        // privateMember = 3; // 不可访问
    }
};

// 私有继承
class PrivateDerived : private Base {
public:
    void accessMembers() {
        publicMember = 1; // 可访问
        protectedMember = 2; // 可访问
        // privateMember = 3; // 不可访问
    }
};

int main() {
    PublicDerived publicDerived;
    publicDerived.publicMember = 1; // 可访问

    ProtectedDerived protectedDerived;
    // protectedDerived.publicMember = 1; // 不可访问,已变为保护成员

    PrivateDerived privateDerived;
    // privateDerived.publicMember = 1; // 不可访问,已变为私有成员
}
  1. 公有继承能在派生类里访问基类 protectedMember,在类外main不行,因 protected 成员只能类内或派生类用。
  2. 保护继承在派生类的成员函数里可访问 publicMember 和 protectedMember,示例中 accessMembers 函数就能访问,在保护继承中,基类的publicMemberprotectedMember在派生类中都变为了保护成员。而保护成员只能在类内或者其派生类内访问,在main函数(类外部)是无法直接访问的。不过,你可以在派生类中定义公有的访问函数来间接访问这些成员
    #include <iostream>
    
    class Base {
    public:
        int publicMember;
    protected:
        int protectedMember;
    private:
        int privateMember;
    };
    
    // 保护继承
    class ProtectedDerived : protected Base {
    public:
        // 提供公有接口来访问基类的 publicMember
        void setPublicMember(int value) {
            publicMember = value;
        }
        int getPublicMember() {
            return publicMember;
        }
    
        // 提供公有接口来访问基类的 protectedMember
        void setProtectedMember(int value) {
            protectedMember = value;
        }
        int getProtectedMember() {
            return protectedMember;
        }
    };
    
    int main() {
        ProtectedDerived protectedDerived;
        // 通过派生类提供的公有接口设置和获取成员值
        protectedDerived.setPublicMember(10);
        std::cout << "publicMember: " << protectedDerived.getPublicMember() << std::endl;
    
        protectedDerived.setProtectedMember(20);
        std::cout << "protectedMember: " << protectedDerived.getProtectedMember() << std::endl;
    
    }
  3. 私有继承同理,在派生类的成员函数里可访问 publicMember 和 protectedMember,像 accessMembers 函数。
  4. 基类 privateMember 只能在基类自身成员函数里用,派生类和类外都没法直接用。想在main里访问也要定义公有的访问函数来间接访问这些成员
    #include <iostream>
    
    class Base {
    public:
        int publicMember;
    protected:
        int protectedMember;
    };
    
    // 私有继承
    class PrivateDerived : private Base {
    public:
        // 定义公有接口来访问基类的 publicMember
        void setPublicMember(int val) {
            publicMember = val;
        }
        int getPublicMember() {
            return publicMember;
        }
        // 定义公有接口来访问基类的 protectedMember
        void setProtectedMember(int val) {
            protectedMember = val;
        }
        int getProtectedMember() {
            return protectedMember;
        }
    };
    int main() {
        PrivateDerived privateObj;
        privateObj.setPublicMember(10);
        std::cout << "publicMember: " << privateObj.getPublicMember() << std::endl;
        privateObj.setProtectedMember(20);
        std::cout << "protectedMember: " << privateObj.getProtectedMember() << std::endl;
    }

这种基本问题,没啥深度难度的问豆包就可以了

总结:

1、基类构造函数不继承,派生类一般要在构造函数里调用基类构造函数,可按需决定是否额外写逻辑;

2、公有继承可以在基类内和继承类内访问基类的公有和保护的成员变量和函数,私有成员变量和函数无法直接在派生类内和main内访问,想访问要弄个公有函数,而main里可以访问公有函数和变量,想访问保护类型变量和函数也要弄个共有函数 且公有继承,main 里能访问基类公有成员;保护和私有继承,若不写公有函数,main 里无法访问基类原公有和保护成员 。 保护继承在派生类写公有访问,然后可以在基类和派生类内访问公有和保护成员函数和变量,而main里只能访问公有成员变量和函数,对于保护和私有成员变量和函数想在main和派生类里访问都需要弄个函数 私有继承在派生类内要写个公有函数来访问基类里的公有和保护变量和函数,而访问基类里的私有变量和函数或者mian里访问都要弄个公有函数来访问公有、保护、私有成员变量和函数都要写个公有访问函数

3、

Q:

代码中派生类里为何还要写个public?然后void accessMembers?

A:

是因为外界(如 main 函数)就能调用它,若不写 public 的 accessMember 函数,main 里无法访问派生类非公有的、保护和私有类型成员。

4、

Q:

那我看派生类里写了个public然后直接可以用基类里的相应函数和变量了,为何我看你又写了一种写法是get和set呢?

A:

get 和 set 函数,是为了更好地封装和控制对成员变量的访问。直接用基类函数和变量可能会破坏封装性,而 get 和 set 能在访问时添加额外逻辑,如边界检查、数据验证等,增强代码的安全性和可维护性。

自己思考的很深,总结完得到豆包肯定后再回头看菜鸟教程里的“我们可以根据访问权限总结出不同的访问类型,如下所示”那个表就太小儿科了

自己问完豆包,再回来看多继承也明白了,感觉也没啥难的,就是太绕了,真他妈恶心

问了下豆包,找工作的东西,╮(╯▽╰)╭~~~~(>_<)~~~~ 之前的咨询

—————————————————— C++ 重载运算符和重载函数 ——————————————————

C++ 重载运算符和重载函数

重载函数

重载运算符

#include <iostream>

// 定义 Box 类
class Box {
private:
    double length;  // 盒子的长度
    double breadth; // 盒子的宽度
    double height;  // 盒子的高度

public:
    // 构造函数,用于初始化盒子的尺寸
    Box(double l = 0, double b = 0, double h = 0) : length(l), breadth(b), height(h) {}

    // 重载 + 运算符,用于把两个 Box 对象相加
    Box operator+(const Box& b) {
        Box box;
        box.length = this->length + b.length;
        box.breadth = this->breadth + b.breadth;
        box.height = this->height + b.height;
        return box;
    }

    // 成员函数,用于计算盒子的体积
    double getVolume() {
        return length * breadth * height;
    }
};

int main() {
    // 创建两个 Box 对象
    Box box1(1.0, 2.0, 3.0);
    Box box2(4.0, 5.0, 6.0);

    // 使用重载的 + 运算符将两个 Box 对象相加
    Box box3 = box1 + box2;

    // 输出每个盒子的体积
    std::cout << "Box 1 Volume: " << box1.getVolume() << std::endl;
    std::cout << "Box 2 Volume: " << box2.getVolume() << std::endl;
    std::cout << "Box 3 (box1 + box2) Volume: " << box3.getVolume() << std::endl;
}

关于未定义,我理解最终没初始化就是未定义:

不完全是。局部作用域的内置类型(如 intdouble)未初始化,其值是未定义的,使用会导致未预期结果;但全局或静态存储期的内置类型变量未显式初始化会被零初始化;类类型对象未显式初始化会调用默认构造函数,若默认构造函数完成初始化则并非未定义。

关于构造函数传参问题,上一个博客,说“对于这里的构造函数,可以不写”,但实际上虽然

#include <iostream>
#include <string>
using namespace std;
struct Books{
    string title;
    int book_id;
//    Books(string t, int id)
//        : title(t), book_id(id) {}
};
void printBookInfo(const Books& book) {
    cout << "书籍标题: " << book.title << endl;
    cout << "书籍 ID: " << book.book_id << endl;
}
int main(){
    Books Book1("C++ 教程",12345);
    Book1.title="C++ 教程";
    printBookInfo(Book1);
}

通过编译了,但说是可能版本还是编译器这些我最讨厌的问题,导致的

严格来说

没构造函数时,不能传参初始化对象

有带参数的构造函数,必须传参初始化对象,除非同时定义了无参数的构造函数

C++ 会根据操作数的类型来决定使用哪个 + 运算符:如果操作数是内置类型,就使用内置的加法规则;如果操作数是自定义类型且该类型重载了 + 运算符,就调用重载的运算符函数。

操作数是参与运算的对象,在 Box box3 = box1 + box2; 里 box1 和 box2 是操作数,在 int result = a + b; 中 a 和 b 是操作数。

当把加法运算符重载函数定义为非成员函数时,由于它不在类内部,没有隐含的当前对象,所以需要显式传入两个操作数,因此函数原型为 Box operator+(const Box&, const Box&); ,其中两个 const Box& 分别表示两个参与加法运算的 Box 对象的引用。

现在说下类成员函数

我一直因为是什么高大上的玩意,结果tmd根本不是类内函数本身有什么参数,而是main里传入的实参那,其实相当于省略了一个写法,无语

在 C++ 中,当调用类的成员函数时,编译器会自动将调用该函数的对象的地址作为一个隐含的参数传递给函数,这个隐含的参数就是 this 指针。对于运算符重载函数也是如此。

当在 main 函数中执行 box1 + box2 时,实际上等价于调用 box1.operator+(box2)。这里 box1 的地址通过 this 指针隐式传递给 operator+ 函数,而 box2 作为显式参数传递。

运算符重载实例

看到个有意思的

C++ 函数调用运算符 () 重载

函数调用运算符 () 可以被重载用于类的对象。当重载 () 时,您不是创造了一种新的调用函数的方式,相反地,这是创建一个可以传递任意数目参数的运算符函数。

C++ 下标运算符 [] 重载

safearay()Box(double l = 0, double b = 0, double h = 0) : length(l), breadth(b), height(h) {}都是构造函数,只是前者无参,在函数体中初始化数组,后者有默认参数,用成员初始化列表初始化成员变量。

safearay() 是 safearay 类的构造函数。在 C++ 中,构造函数是一种特殊的成员函数,其名称与类名相同,并且没有返回类型(包括 void)。构造函数的主要作用是在创建类的对象时对对象进行初始化操作。

在 safearay 类的构造函数中,它会遍历数组 arr 的每个元素,并将元素的下标值赋给该元素。具体代码如下:

safearay()
{
    int i;
    for(i = 0; i < SIZE; i++)
    {
        arr[i] = i;
    }
}

当你在 main 函数中创建 safearay 类的对象 A 时,safearay() 构造函数会被自动调用,从而将数组 arr 的元素初始化为 {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

int& operator[](int i) 是对下标运算符 [] 的重载。在 C++ 中,运算符重载允许你为自定义类型(如类)重新定义运算符的行为。

具体解释如下:

  • operator[]:这是 C++ 中用于重载下标运算符 [] 的固定语法。当你使用 对象名[索引] 的形式时,实际上会调用这个重载的 operator[] 函数。
  • int&:返回值类型为 int&,表示返回一个 int 类型的引用。引用的作用是允许你直接修改数组中的元素。如果返回的是 int 而不是 int&,那么返回的只是数组元素的一个副本,而不是元素本身,这样就无法通过返回值修改数组元素。
  • (int i):这是函数的参数列表,i 表示要访问的数组元素的下标。

例如,在 main 函数中使用 A[2] 时,实际上会调用 A.operator[](2),该函数会返回数组 arr 中索引为 2 的元素的引用,从而可以直接访问和修改该元素。

虽然使用普通的数组 int n[10] 可以很方便地定义和使用数组,但重载下标运算符在某些情况下具有重要的优势:

  • 边界检查:在实际应用中,普通数组不会进行边界检查,如果你访问的下标超出了数组的有效范围,会导致未定义行为,可能会引发程序崩溃或产生不可预测的结果。而通过重载下标运算符,你可以在 operator[] 函数中添加边界检查逻辑,确保访问的下标在有效范围内,从而提高程序的健壮性。例如:
int& operator[](int i)
{
    if (i < 0 || i >= SIZE) {
        // 处理越界情况,如抛出异常
        throw std::out_of_range("Index out of range");
    }
    return arr[i];
}
  • 封装性:使用类来封装数组可以隐藏数组的具体实现细节,只提供必要的接口给外部使用。这样可以避免外部代码直接访问和修改数组,提高代码的安全性和可维护性。

  • 自定义行为:重载下标运算符可以实现一些自定义的行为,例如对数组元素进行特殊的处理或转换。你可以根据具体需求在 operator[] 函数中添加额外的逻辑,而普通数组无法做到这一点。

A这个对象内部封装了一个数组。

C++ 类成员访问运算符 -> 重载

纯傻逼,先略过

———————————————————— C++ 多态 ————————————————————

先理清楚几个知识点(都是问豆包的)

1、继承机制产生了父子类的概念,

基类叫父(被继承的类通常被叫做基类或者父类)

派生类叫子(通过继承基类产生的新类被称作派生类或者子类)

2、当我们用子类去创建一个变量时,这个变量就代表了一个子类对象。

3、多态比继成功牛逼在哪啊?

#include <iostream>

// 父类 Animal
class Animal {
public:
    void call() {
        std::cout << "动物发出声音" << std::endl;
    }
};

// 子类 Cat
class Cat : public Animal {
public:
    void call() {
        std::cout << "喵喵喵" << std::endl;
    }
};

// 子类 Dog
class Dog : public Animal {
public:
    void call() {
        std::cout << "汪汪汪" << std::endl;
    }
};

int main() {
    Cat cat;
    Dog dog;
    cat.call();
    dog.call();
}

要是修改 Cat 类的 call 方法,由于调用是直接针对 Cat 对象,所有调用 cat.call() 的地方都要确认是否会受到影响。如果项目里有很多地方调用了 cat.call() ,修改时就很容易出错,维护成本高。

Q:不是吧,我想修改成“喵呜喵呜” ,直接把派生类输出换成 ”喵呜喵呜” 就好了啊,咋需要改那么多东西?

A:

你说得对,单纯修改派生类(子类)里的输出内容时,不管是多态还是普通继承,确实都只需要在子类的对应方法里改。但实际开发中,情况会更复杂,下面给你详细说说多态在增强可维护性上的优势。

假设除了输出叫声,call 方法还会调用其他辅助方法或者有一些额外逻辑,并且在不同调用处有不同的参数传递和后续操作。

普通继承场景

#include <iostream>

class Animal {
public:
    void commonFunction() {
        std::cout << "执行一些通用操作" << std::endl;
    }
};

class Cat : public Animal {
public:
    void call() {
        commonFunction(); 
        std::cout << "喵喵喵" << std::endl;
    }
};

void specificCall(Cat cat) {
    cat.call();
    // 这里有针对 Cat 对象 call 之后的特殊操作
    std::cout << "执行针对猫叫后的特殊操作" << std::endl;
}

int main() {
    Cat cat;
    specificCall(cat);
}

如果要修改 Cat 类的 call 方法,比如添加一些额外逻辑或者修改辅助方法的调用方式,由于 specificCall 函数是专门针对 Cat 对象编写的,可能需要检查并修改 specificCall 函数以及其他所有调用 Cat 对象 call 方法的地方,以确保修改不会引发错误

多态场景 

#include <iostream>

class Animal {
public:
    virtual void call() {
        std::cout << "执行一些通用操作" << std::endl;
    }
};

class Cat : public Animal {
public:
    void call() override {
        Animal::call(); 
        std::cout << "喵喵喵" << std::endl;
    }
};

void makeAnimalCall(Animal* animal) {
    animal->call();
    // 这里的操作不依赖具体是哪种动物
    std::cout << "执行通用的动物叫声后续操作" << std::endl;
}

int main() {
    Cat cat;
    makeAnimalCall(&cat);
}

在多态的情况下,makeAnimalCall 函数接受的是 Animal 类型的指针。当修改 Cat 类的 call 方法时,makeAnimalCall 函数不需要做任何修改,因为它只依赖于 Animal 类的接口,只要 Animal 类的接口不变,调用处就不受影响。这大大减少了修改代码时的影响范围,增强了代码的可维护性。

所以,虽然简单的输出修改在两种方式下看起来区别不大,但在实际复杂的项目中,多态能更好地隔离修改带来的影响,让代码更易于维护。

继续问豆包:

Q:关于你说的“牛逼四:统一处理逻辑”举例子

A:

#include <iostream>

// 父类 Animal
class Animal {
public:
    virtual void call() {
        std::cout << "动物发出声音" << std::endl;
    }
};

// 子类 Cat
class Cat : public Animal {
public:
    void call() override {
        std::cout << "喵喵喵" << std::endl;
    }
};

// 子类 Dog
class Dog : public Animal {
public:
    void call() override {
        std::cout << "汪汪汪" << std::endl;
    }
};

int main() {
    Animal* animals[2];
    Cat cat;
    Dog dog;
    animals[0] = &cat;
    animals[1] = &dog;

    for (int i = 0; i < 2; ++i) {
        animals[i]->call();
    }
    return 0;
}

这里可以把 Cat 和 Dog 对象的指针存到 Animal 类型的数组里,然后通过循环统一调用 call 方法,不管有多少种动物子类,都可以用这种简洁统一的方式处理。

单纯继承情况 

#include <iostream>

// 父类 Animal
class Animal {
public:
    void call() {
        std::cout << "动物发出声音" << std::endl;
    }
};

// 子类 Cat
class Cat : public Animal {
public:
    void call() {
        std::cout << "喵喵喵" << std::endl;
    }
};

// 子类 Dog
class Dog : public Animal {
public:
    void call() {
        std::cout << "汪汪汪" << std::endl;
    }
};

int main() {
    Cat cat;
    Dog dog;
    if (/* 判断是否是 Cat 对象 */) {
        cat.call();
    }
    if (/* 判断是否是 Dog 对象 */) {
        dog.call();
    }
    return 0;
}

当需要处理多个子类对象时,只能通过条件判断区分不同子类对象,然后分别调用对应方法。随着子类数量增多,条件判断会越来越复杂,代码可读性和可维护性变差,而多态避免了这些问题,让代码更简洁。

以上是我起手看菜鸟教程发现说跟继承有关系,故追问了许多

现在回头继续看菜鸟教程,直接把里面的实例1丢给又追问好久,发现懂了好多

不再记录了,太浪费时间了

不再强迫症了,以防失效,之前的 收获与成长

实例2,问完豆包后觉得太简单了,不过菜鸟教程里解释的也非常棒

以防失效

###:

妈逼的情人节0214图书馆坐位旁边一个身材一级棒,该肥的地方肥,该瘦的瘦,大奶子这tm又在磨练我心性好想一口下去
咳嗽一声太他妈销魂了,离得我还很近

可是经历使我立马冷静下来,做一个够狠够绝杀伐果断的人

女人只会磨灭我的斗志

一看是个考公的,呵呵,呵呵呵

View Code

###:学英文

class derived-class: access-specifier base-class

          衍生         接近-说明符

即:   派生                   访问说明符

polymorphism:多态性

###:豆包:

一句话回答我以下所有问题,且都是C++的,且每个问题换行,且如果需要写代码一律去掉最后的return 0

###:傻逼VS滚动代码跟他妈个僵尸一样,这么垃圾的残次品也这么受欢迎吗