十、继承

十、继承

  • 继承的基本概念
  • 使用继承的原因
  • 继承的用途
  • 继承的注意事项
  • 继承的基本使用
    • 定义基类
    • 定义派生类
    • 使用派生类的对象
    • 注意事项
    • 示例:构造函数和析构函数的调用
  • 继承基本规则
  • 继承方式
    • 注意事项
    • 示例
  • 赋值兼容原则
  • 继承中的同名成员
    • 成员变量
    • 成员函数
    • 总结
  • 继承中的静态成员,构造函数与析构函数
    • 静态成员
    • 构造函数
    • 析构函数
    • 示例

C++中的继承是一种面向对象编程(OOP)的特性,它允许一个类(称为派生类或子类)继承另一个类(称为基类或父类)的属性和方法。通过继承,派生类可以重用基类的代码,同时添加新的属性和方法或修改继承来的方法。这种机制促进了代码的复用、扩展和维护。

继承的基本概念

  • 基类(Base Class):被继承的类,也称为父类或超类。它包含了一些基本的属性和方法,这些属性和方法可以被派生类继承。
  • 派生类(Derived Class):继承自基类的类,也称为子类或继承类。派生类可以包含基类的所有成员(除非它们被声明为私有并且没有被友元关系访问),并且还可以添加新的成员或重写继承来的成员。
  • 继承性(Inheritance):类之间的一种关系,其中一个类(派生类)继承另一个类(基类)的属性和方法。

使用继承的原因

在面向对象编程(OOP)中,继承是一种非常重要的特性,它提供了多种理由和优势来支持其使用。

  1. 代码复用
    继承允许我们重用基类中已经定义好的属性和方法,而不必在派生类中重新定义它们。这避免了代码的重复,使得代码更加简洁和易于维护。例如,如果我们有多个类表示不同类型的动物,它们可能都需要实现一些共同的行为(如吃、睡、移动等),那么我们可以将这些共同的行为定义在一个基类中,并让各个动物类继承这个基类。

  2. 扩展性
    通过继承,我们可以在不修改基类代码的情况下,为派生类添加新的属性和方法。这种扩展性使得我们可以轻松地根据需求对类进行扩展,而不需要担心会破坏现有的代码或功能。

  3. 多态性
    多态性是面向对象编程的一个重要概念,它允许我们以统一的接口来处理不同类型的对象。继承是实现多态性的一种手段。通过继承,我们可以定义基类的指针或引用来指向派生类的对象,并在运行时根据对象的实际类型调用相应的方法。这种能力使得我们的代码更加灵活和强大。

  4. 表达类之间的“是一个”关系
    在现实世界中,很多事物之间都存在着“是一个”的关系。例如,猫是一个动物,汽车是一个交通工具等。通过继承,我们可以在代码中表达这种关系。基类表示更一般的概念(如动物、交通工具),而派生类则表示更具体的概念(如猫、汽车)。这种表达方式使得我们的代码更加符合现实世界的逻辑。

  5. 框架和库的设计
    在设计和实现软件框架或库时,继承是不可或缺的一部分。通过定义一系列的基类和接口,我们可以为开发者提供一个可扩展的、可重用的代码基础。开发者可以通过继承这些基类和接口来创建自己的类,从而实现特定的功能或需求。

  6. 简化设计和实现
    继承可以简化类的设计和实现过程。通过将共通的属性和方法抽象到基类中,我们可以将注意力集中在派生类特有的属性和方法上。这种分而治之的策略使得类的设计和实现变得更加清晰和简单。

然而,也需要注意的是,过度使用继承可能会导致类层次结构变得复杂和难以维护。因此,在使用继承时应该谨慎考虑,并确保它确实是解决问题的最佳方案。在某些情况下,组合(composition)可能是比继承更好的选择。

继承的用途

  • 代码复用:通过继承,派生类可以重用基类的代码,避免重复编写相同的代码。
  • 多态性:继承是实现多态性的基础。通过继承,可以定义基类的指针或引用来指向派生类的对象,并在运行时根据对象的实际类型调用相应的方法。
  • 扩展性:派生类可以在继承基类的基础上添加新的属性和方法,从而扩展类的功能。

继承的注意事项

  • 避免过度继承:过度使用继承会使类层次结构变得复杂,难以理解和维护。应该优先考虑组合(composition)而不是继承。
  • 注意访问权限:在继承时,要注意基类成员的访问权限,确保派生类能够访问到需要的成员。
  • 构造函数和析构函数:派生类的构造函数需要调用基类的构造函数来初始化继承来的成员。同样,如果基类有析构函数(特别是如果它管理了资源),派生类也应该有一个析构函数来确保资源的正确释放。
  • 虚函数和纯虚函数:通过声明虚函数或纯虚函数,可以在基类中定义接口,让派生类来实现这些接口的具体行为。这是实现多态性的关键。

继承的基本使用

C++中的继承是一种面向对象编程的特性,它允许我们定义一个新的类(派生类或子类)来继承另一个类(基类或父类)的属性和方法。继承的基本使用包括定义基类、定义派生类、以及通过派生类的对象来访问基类的成员。以下是C++继承的基本用法示例:

定义基类

首先,我们需要定义一个基类,这个类包含了派生类将要继承的属性和方法。

class Base {
public:void display() {cout << "Displaying base class" << endl;}
};

定义派生类

接着,我们定义一个派生类,这个类通过继承关键字(:)来继承基类的成员。

class Derived : public Base {
public:void show() {cout << "Showing derived class" << endl;}
};

注意,在上面的例子中,public 关键字指定了继承的访问级别。C++ 支持三种继承方式:publicprotectedprivatepublic 继承使得基类的 publicprotected 成员在派生类中保持原有的访问级别。

使用派生类的对象

最后,我们可以通过创建派生类的对象并使用它来访问基类和派生类的成员。

#include <iostream>
using namespace std;// 基类和派生类的定义(如上所示)int main() {Derived d;// 访问派生类自己的方法d.show();// 访问继承自基类的方法d.display();return 0;
}

注意事项

  • 当派生类对象访问继承的成员时,如果派生类中有与基类同名的成员(包括成员变量和成员函数),则派生类成员会隐藏基类的同名成员。这被称为“名称隐藏”或“名称覆盖”。
  • 如果需要访问被隐藏的基类成员,可以使用作用域解析运算符(::)来明确指定访问的类。
  • 构造函数和析构函数不能被继承,但派生类可以定义自己的构造函数和析构函数来执行必要的初始化或清理工作。在派生类构造函数中,可以通过成员初始化列表显式地调用基类的构造函数。
  • 派生类可以覆盖(Override)基类的虚函数,以提供特定于派生类的实现。这是多态性的基础之一。

示例:构造函数和析构函数的调用

class Base {
public:Base() { cout << "Base Constructor" << endl; }~Base() { cout << "Base Destructor" << endl; }
};class Derived : public Base {
public:Derived() { cout << "Derived Constructor" << endl; }~Derived() { cout << "Derived Destructor" << endl; }
};int main() {Derived d;// 输出顺序:Base Constructor, Derived Constructor// 当d离开作用域时,析构函数按相反顺序调用// 输出顺序:Derived Destructor, Base Destructorreturn 0;
}

继承基本规则

  1. 继承类型

    • public 继承:基类的 publicprotected 成员在派生类中保持其访问级别。
    • protected 继承:基类的 publicprotected 成员在派生类中变为 protected 成员。
    • private 继承:基类的所有成员(publicprotectedprivate)在派生类中均为 private 成员。注意,private 继承并不常用,因为它基本上隐藏了基类接口。
  2. 构造函数和析构函数

    • 构造函数和析构函数不能继承,但派生类可以定义自己的构造函数和析构函数来执行必要的初始化或清理工作。
    • 派生类构造函数可以通过成员初始化列表显式地调用基类的构造函数。
    • 派生类对象的析构函数在派生类析构之后、基类析构之前被调用。
  3. 静态成员

    • 基类的静态成员被所有的派生类共享,不论派生类对象的数量。
    • 派生类不能定义与基类同名的静态成员(除非它们在不同的作用域内,例如通过嵌套类)。
  4. 方法重写(覆盖)

    • 派生类可以重写基类中的虚函数(通过相同的函数签名和 virtual 关键字)。
    • 如果基类中的函数不是虚函数,派生类中的同名函数将隐藏基类中的函数,而不是重写它。
  5. 访问权限

    • 派生类不能增加基类成员的访问权限(例如,基类中的 protected 成员在派生类中不能变为 public)。
    • 但派生类可以进一步限制基类成员的访问权限(例如,通过私有继承)。

继承方式

在C++中,继承过来的权限主要取决于继承方式(public、protected、private)以及基类成员的原始访问权限(public、protected、private)

  1. public继承

    • 当子类从父类以public方式继承时:
      • 父类的public成员在子类中保持为public成员,允许类以外的代码访问这些成员。
      • 父类的protected成员在子类中保持为protected成员,只允许子类及其派生类的成员访问。
      • 父类的private成员在子类中仍然不可访问,但它们在子类对象中是存在的(仅从内存布局角度)。
  2. protected继承

    • 当子类从父类以protected方式继承时:
      • 父类的public成员和protected成员在子类中均变为protected成员,只允许子类及其派生类的成员访问。
      • 父类的private成员在子类中仍然不可访问。
  3. private继承

    • 当子类从父类以private方式继承时:
      • 父类的public成员和protected成员在子类中均变为private成员,只允许子类自身的成员访问。
      • 父类的private成员在子类中仍然不可访问。

注意事项

  • 基类中的private成员在派生类中无论以何种方式继承都是不可见的,但这并不意味着它们不被继承。从内存布局的角度看,派生类对象中确实包含了这些私有成员。
  • 访问权限的变更仅影响继承后的成员的访问方式,不会改变基类中成员本身的访问权限。
  • 使用class关键字定义类时,默认的继承方式是private;而使用struct关键字时,默认的继承方式是public。然而,为了代码的清晰性和可维护性,建议显式指定继承方式。

示例

假设有以下基类Base和派生类Derived

class Base {
public:int publicMember;
protected:int protectedMember;
private:int privateMember;
};class DerivedPublic : public Base {
public:void accessMembers() {publicMember = 10; // 可以访问protectedMember = 20; // 可以访问// privateMember = 30; // 编译错误,不可访问}
};class DerivedProtected : protected Base {
public:void accessMembers() {publicMember = 10; // 可以在类内部访问,但外部不可访问protectedMember = 20; // 可以在类内部访问,但外部不可访问// privateMember = 30; // 编译错误,不可访问}
};class DerivedPrivate : private Base {
public:void accessMembers() {publicMember = 10; // 只能在类内部访问protectedMember = 20; // 只能在类内部访问// privateMember = 30; // 编译错误,不可访问}
};

在上述示例中,不同的继承方式导致了基类成员在派生类中具有不同的访问权限。

赋值兼容原则

在C++中,赋值兼容原则主要涉及指针和引用的赋值。基本规则是,派生类对象的指针或引用可以安全地赋值给基类类型的指针或引用,但反之则不然。这是因为基类指针或引用只能访问基类定义的接口,而派生类可能添加了额外的成员。

  • 基类指针/引用指向派生类对象:这是多态性的基础。通过基类指针或引用,我们可以调用派生类重写的虚函数,实现运行时多态。
  • 派生类指针/引用不能隐式转换为基类指针/引用:这是因为派生类可能添加了额外的成员,而基类指针或引用无法访问这些成员。如果确实需要将派生类指针或引用赋值给基类类型的变量,通常需要进行显式类型转换(如静态转换 static_cast 或动态转换 dynamic_cast)。

总之,C++的继承机制提供了一种强大的方式来复用代码、表达类之间的层次关系,并实现多态性。然而,它也伴随着一些复杂的规则和限制,需要开发者仔细理解和遵守。

继承中的同名成员

在C++中,当子类(派生类)和父类(基类)中存在同名成员时,这些成员实际上是在不同的作用域中定义的。这种情况主要涉及到成员函数(方法)和成员变量(属性)。处理这些同名成员时,需要特别注意作用域解析运算符(::)的使用以及成员函数重写(Overriding)和隐藏(Hiding)的概念。

成员变量

对于成员变量,如果子类定义了一个与父类同名的成员变量,那么这两个变量实际上是在不同的作用域中。子类中的同名成员变量会隐藏父类中的同名成员变量。此时,如果子类中的成员函数想要访问父类中被隐藏的成员变量,需要使用作用域解析运算符(::)来明确指定要访问的变量属于哪个类。

class Base {
public:int x;
};class Derived : public Base {
public:int x; // 隐藏了Base类中的xvoid show() {std::cout << "Derived::x = " << x << std::endl; // 访问Derived中的xstd::cout << "Base::x = " << Base::x << std::endl; // 访问Base中的x}
};

成员函数

对于成员函数,情况稍微复杂一些。成员函数存在重写(Overriding)和隐藏(Hiding)两种可能性。

  • 重写(Overriding):当子类定义了一个与父类中具有相同签名(函数名、参数列表、返回类型、const属性、volatile属性、引用属性等)的虚函数时,子类中的这个函数会重写(Override)父类中的虚函数。此时,通过基类指针或引用来调用该函数时,会调用到子类中的版本。

  • 隐藏(Hiding):如果子类中的函数与父类中的函数同名,但参数列表不同(或者函数不是虚函数),那么子类中的函数会隐藏父类中的同名函数。这种情况下,通过基类指针或引用来调用该函数时,不会调用到子类中的版本,除非使用子类类型的指针或引用来调用。

class Base {
public:virtual void func() { std::cout << "Base::func()" << std::endl; }void anotherFunc(int) { std::cout << "Base::anotherFunc(int)" << std::endl; }
};class Derived : public Base {
public:void func() override { std::cout << "Derived::func()" << std::endl; } // 重写void anotherFunc(double) { std::cout << "Derived::anotherFunc(double)" << std::endl; } // 隐藏Base::anotherFunc(int)
};// 使用
Base* basePtr = new Derived();
basePtr->func(); // 输出Derived::func(),因为func()被重写了
basePtr->anotherFunc(1); // 输出Base::anotherFunc(int),因为Derived::anotherFunc(double)隐藏了Base::anotherFunc(int)Derived* derivedPtr = new Derived();
derivedPtr->anotherFunc(1.0); // 输出Derived::anotherFunc(double),直接调用Derived的方法

总结

在C++中处理父子类中的同名成员时,需要注意成员变量会被隐藏,而成员函数则可能涉及重写或隐藏。了解这些概念对于编写清晰、可维护的C++代码非常重要。

继承中的静态成员,构造函数与析构函数

在C++中,父子类(派生类与基类)之间的静态成员、构造函数和析构函数的行为有其特定的规则和特性。

静态成员

静态成员(包括静态变量和静态成员函数)属于类本身,而不是类的某个对象。因此,无论创建了多少个类的对象,静态成员都只有一份拷贝。当静态成员被定义在基类中时,这些成员也会被子类继承,但它们仍然是属于基类的,而不是子类的一个独立拷贝。

  • 静态变量:所有派生类的对象共享基类中定义的静态变量。如果派生类定义了同名的静态变量,则它会隐藏基类中的同名静态变量,而不是覆盖它。

  • 静态成员函数:可以通过基类或派生类的对象(以及类名本身,如果它们是可访问的)来访问基类的静态成员函数。如果派生类定义了同名的静态成员函数,那么通过派生类的对象或类名调用该函数时,将调用派生类中的函数,这类似于非静态成员函数的隐藏行为。

构造函数

构造函数是特殊的成员函数,用于在对象创建时初始化对象。

  • 基类的构造函数:在创建派生类对象时,首先会调用基类的构造函数(如果有的话)。如果基类有多个构造函数,派生类构造函数可以通过初始化列表来指定使用哪一个。

  • 派生类的构造函数:派生类的构造函数可以初始化派生类特有的成员变量,并且可以通过初始化列表来调用基类的构造函数。

析构函数

析构函数也是特殊的成员函数,用于在对象销毁前进行清理工作。

  • 析构函数的调用顺序:与构造函数的调用顺序相反,当派生类对象被销毁时,首先会调用派生类的析构函数,然后是基类的析构函数。这确保了任何由派生类构造函数分配的资源都能被正确地释放,然后才是基类资源的释放。

示例

#include <iostream>class Base {
public:static int staticVar;Base() { std::cout << "Base Constructor\n"; }~Base() { std::cout << "Base Destructor\n"; }static void staticFunc() { std::cout << "Base Static Function\n"; }
};int Base::staticVar = 0;class Derived : public Base {
public:// 注意:这里没有定义staticVar或staticFunc,所以它们从Base继承Derived() { std::cout << "Derived Constructor\n"; }~Derived() { std::cout << "Derived Destructor\n"; }// 可以在这里定义同名的静态成员,但这将隐藏Base中的同名成员
};int main() {Derived d;// 调用静态函数,将输出Base Static FunctionBase::staticFunc();// 访问静态变量,修改后通过Base和Derived访问都会看到修改结果std::cout << "Before: " << Base::staticVar << std::endl;Base::staticVar = 5;std::cout << "After: " << Derived::staticVar << std::endl; // 注意:这里通过Derived访问也是合法的return 0; // 析构顺序:Derived Destructor, Base Destructor
}

在这个示例中,你可以看到静态成员(staticVarstaticFunc)是如何被基类和派生类共享的,以及构造函数和析构函数的调用顺序。注意,由于Derived类没有定义staticVarstaticFunc,所以它们是从Base类继承的。如果Derived类定义了同名的静态成员,那么这些成员将隐藏基类中的同名成员。

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

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

相关文章

PCIe总线-RK3588 PCIe平台驱动分析(十)

1.简介 RK3588 PCIe RC和EP使用同一个平台驱动&#xff0c;其主要的作用是解析设备树中的资源、初始化中断、使能电源、初始化PHY、使能时钟和释放复位&#xff0c;然后根据compatible属性初始化RC或者EP驱动。 2.入口 平台驱动的定义如下&#xff0c;当compatible属性为&qu…

【hadoop大数据集群 1】

hadoop大数据集群 1 文章目录 hadoop大数据集群 1一、环境配置1.安装虚拟机2.换源3.安装工具4.安装JDK5.安装Hadoop 一、环境配置 折腾了一下午/(ㄒoㄒ)/~~ 1.安装虚拟机 参考视频&#xff1a;https://www.bilibili.com/video/BV18y4y1G7JA?p17&vd_sourcee15e83ac6b22a…

C语言:静态库和动态(共享)库

相关阅读 C语言https://blog.csdn.net/weixin_45791458/category_12423166.html?spm1001.2014.3001.5482 在软件开发中&#xff0c;库&#xff08;Library&#xff09;是一个至关重要的概念。它们是由函数和数据的集合构成&#xff0c;用于实现特定的功能&#xff0c;供其他程…

使用Vuepress搭建个人网站

网站地址&#xff1a;bloggo.chat

vue中怎么改变状态值?

在Vue中&#xff0c;状态值通常指的是组件的data函数返回的对象中的属性&#xff0c;或者是Vuex状态管理库中的状态。以下是在Vue中改变状态值的几种常见方法&#xff1a; 1. 直接在组件内部改变状态值 在Vue组件中&#xff0c;你可以直接在methods中改变data函数返回的对象中…

MySQL学习作业二

作业描述 SQL语言 建库&#xff0c;使用库 mysql> create database mydb8_worker;#新建库mysql> use mydb8_worker; 建表&#xff0c;查看表 #建表 mysql> create table t_worker(department_id int(11) not null comment部门号,worker_id int(11) primary key no…

无人机足球比赛技术详解

一、无人机类型参数 在无人机比赛中&#xff0c;不同类型的无人机因其独特的参数配置而表现出不同的性能。这些参数包括但不限于&#xff1a; 1. 机体尺寸&#xff1a;小型无人机适合室内或狭窄空间比赛&#xff0c;而大型无人机则更适用于室外大场地赛事。 2. 动力系统&…

动态路由协议 —— EIGRP 与 OSPF 的区别

EIGRP&#xff08;增强内部网关路由协议&#xff09;和 OSPF&#xff08;开放式最短路径优先&#xff09;是两种最常见的动态路由协议&#xff0c;主要是用来指定路由器或交换机之间如何通信。将其应用于不同的情况下&#xff0c;可提高速率、延迟等方面的性能。那么它们之间到…

装修前需要提前准备啥

雅静说装修前自备15材料,才不会手慌脚乱      省的师父用的时候我们还得着急到处跑,关键质量和价格还没得选      1,保护膜,一开工就把入户门,电梯口的墙都包好      不然装完就像这样,磕碰的到处是口子      2,钥匙密码盒,有时候上班我们不在,师父或送材料的一…

IO多路复用-select的使用详解【C语言】

1.多进程/线程并发和IO多路复用的对比 IO多路转接也称为IO多路复用&#xff0c;它是一种网络通信的手段&#xff08;机制&#xff09;&#xff0c;通过这种方式可以同时监测多个文件描述符并且这个过程是阻塞的&#xff0c;一旦检测到有文件描述符就绪&#xff08; 可以读数据…

【数据结构进阶】二叉搜索树

&#x1f525;个人主页&#xff1a; Forcible Bug Maker &#x1f525;专栏&#xff1a; C || 数据结构 目录 &#x1f308;前言&#x1f525;二叉搜索树&#x1f525; 二叉搜索树的实现Insert&#xff08;插入&#xff09;find&#xff08;查找&#xff09;erase(删除)destro…

firefly rk3288 ubuntu23.10 网卡名为end0 改为eth0

1、内核源码修改u-boot/include/env_default.h文件第32行的bootargs参数&#xff0c;修改后&#xff1a; "bootargs net.ifrenames0 " CONFIG_BOOTARGS "\0"2、修改rootfs里的lib/systemd/network/99-default.link文件&#xff1a; [M…

分布式锁、Lua脚本、redisson、运行lua脚本优化代码

20240721 一、分布式锁1. 什么是分布式锁2. 分布式锁的实现3. 基于redis的分布式锁4 总结 二、对于lua脚本可以保证事务&#xff0c;要么成功要么失败。1. 在redis中调用lua脚本 三、Redisson1 步骤2. Redisson的总结3. 几种分布式锁的区别 三、优化我们的秒杀1. 我们在创建优惠…

Docker安装笔记

1. Mac安装Docker 1.1 Docker安装包下载 1.1.1 阿里云 对于10.10.3以下的用户 推荐使用 对于10.10.3以上的用户 推荐使用 1.1.2 官网下载 系统和芯片选择适合自己的安装包 1.2 镜像加速 【推荐】阿里镜像 登陆后&#xff0c;左侧菜单选中镜像加速器就可以看到你的专属地…

windows和linux的等保加固测评的经验分享

一头等保加固测评的牛马&#xff0c;需要能做到一下午测评n个服务器 接下来就讲讲如何当一头xxxxxxxxx》严肃的等保测评加固的经验分享&#xff08; 一、window等保 首先你要自己按着教程在虚拟机做过一遍&#xff08;win2012和win2008都做过一遍&#xff0c;大概windows的…

pico+unity3d 射线交互教程

前期配置&#xff1a;环境配置参考教程一&#xff0c;手部模型参考教程二&#xff0c;场景基于上一篇搭建。 最终效果&#xff1a;手部射线&#xff08;初始不可见&#xff09;对准 UI 显示&#xff0c;按下手柄 Trigger 键与可交互 UI&#xff08;如 Button、Toggle、Slider …

论文解读 | ICML2024:突破Transformer上下文学习中的瓶颈

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 作者简介 付靖文&#xff0c;西安交通大学博士生 简介 上下文学习&#xff0c;即从上下文示例中学习&#xff0c;是Transformer一项令人印象深刻的能力。然而&#xff0c;由于学习瓶颈的出现——在训练过程中模…

移动UI:任务中心的作用,该如何设计更合理?

任务中心是移动应用中用于展示和管理用户待办任务、提醒事项、用户福利、打卡签到等内容的功能模块。合理设计任务中心可以提升用户体验和工作效率。 以下是一些设计任务中心的合理建议&#xff1a; 1. 易于查看和管理&#xff1a; 任务中心的设计应该使用户能够快速、直观地…

C# 实现跨进程条件变量

C# 进程通信系列 第一章 共享内存 第二章 条件变量&#xff08;本章&#xff09; 第三章 消息队列 文章目录 C# 进程通信系列前言一、关键实现1、用到的主要对象2、初始化区分创建和打开3、变量放到共享内存4、等待和释放逻辑 二、完整代码三、使用示例1、同步控制2、跨进程控…

202496读书笔记|《飞花令·菊(中国文化·古典诗词品鉴)》——荷尽已无擎雨盖,菊残犹有傲霜枝

202496读书笔记|《飞花令菊&#xff08;中国文化古典诗词品鉴&#xff09;》——荷尽已无擎雨盖&#xff0c;菊残犹有傲霜枝 《飞花令菊&#xff08;中国文化古典诗词品鉴&#xff09;》素心落雪 编著。飞花令得名于唐代诗人韩翃《寒食》中的名句“春城无处不飞花”&#xff0c…