C++1进阶(继承)

C++ 继承从入门到吃透
目录

1.唠唠继承是个啥 ------ 概念 + 定义
2.基类派生类的 "赋值小规则"------ 对象转换那些事
3.继承里的 "地盘之争"------ 作用域问题
4.派生类的 "六大默认成员函数"------ 编译器帮我们做了啥
5.友元和静态成员的 "继承小脾气"------ 能继承吗?咋用?
6.继承里的 "大坑"------ 菱形继承与虚拟继承
7.继承的 "灵魂反思"------ 咋用才对?继承 vs 组合
8.笔试面试高频题 ------ 考点速记

作为一名 C++ 初学者,刚接触继承 的时候总觉得它又绕又复杂,光是基类、派生类、访问限定符这些概念就记了好久,更别说菱形继承、虚拟继承这些 "进阶坑" 了。但学懂之后才发现,继承其实是 C++ 实现代码复用的核心手段,相当于站在已有类的基础上 "搭积木",不用再重复写相同的代码,这对后续写大型程序太重要了。

1. 唠唠继承是个啥 ------ 概念 + 定义
1.1 继承的核心概念

继承(inheritance)是 C++ 面向对象编程中类层次的代码复用手段 (之前我们接触的都是函数复用,比如写个函数反复调用)。简单来说:在原有类的基础上,增加新功能、扩展特性,生成的新类就是派生类(子类),原有类就是基类(父类)。

继承体现了 "由简单到复杂" 的认知逻辑,比如生活中 "人" 是基础类,有姓名、年龄等属性;"学生" 和 "老师" 都是人的子类,在人的基础上,分别增加了学号、工号等专属属性,这就是继承的思想

小例子(直观感受复用):

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

// 基类:人
class Person
{
public:
    void Print()
    {
        cout << "name:" << _name << endl;
        cout << "age:" << _age << endl;
    }
protected:
    string _name = "peter"; // 姓名
    int _age = 18; // 年龄
};

// 派生类:学生,public继承自Person
class Student : public Person
{
protected:
    int _stuid; // 学号(学生专属属性)
};

// 派生类:老师,public继承自Person
class Teacher : public Person
{
protected:
    int _jobid; // 工号(老师专属属性)
};

int main()
{
    Student s;
    Teacher t;
    // 直接复用基类的Print函数,不用重新写!
    s.Print();
    t.Print();
    return 0;
}

运行后会发现,Student 和 Teacher 对象能直接调用 Person 的 Print 函数,这就是继承的代码复用体现 ------ 父类的成员(函数 + 变量)都会成为子类的一部分。

1.2 继承的定义格式与访问规则
(1)基本定义格式

派生类和基类的关系定义很固定,记住这个格式就行:

cpp 复制代码
class 派生类名 : 继承方式 基类名
{
    // 派生类的成员(属性+函数)
};

比如class Student : public Person,其中public就是继承方式

(2)3 种继承方式 + 3 种访问限定符
C++ 里有public(公有)、protected(保护)、private(私有) 3 种继承方式 ,同时类的成员也有这 3 种访问限定符,两者组合决定了基类成员在派生类中的访问权限

(3)基类成员在派生类中的访问变化(必背表)

1.3 新手必懂的 5 个核心结论

这 5 条结论是理解继承访问规则的关键,记熟了能避开 80% 的基础坑:

1.基类的private 成员,无论哪种继承方式,在派生类中都不可见

✨ 新手注意:"不可见" 不是没继承,而是语法上禁止派生类(类内 + 类外)访问,成员其实还在派生类对象里。

2.**protected限定符是为继承而生的!**如果基类成员不想被类外访问,但想让派生类访问,就定义为protected(比 private 宽松,比 public 严格)。

3.访问权限的简易判断:基类成员在派生类的访问权限 = 最小值 (基类成员的访问限定符,继承方式) ,权限优先级:public > protected > private。

4.继承方式的默认规则:用class定义类时,默认private 继承 ;**用struct定义类时,默认public 继承。**✨ 新手建议:显式写出继承方式 ,别靠默认,代码可读性更高。

5.实际开发中,**只推荐 public 继承!**protected/private 继承的成员只能在派生类内使用,扩展和维护性极差,几乎不用。

2. 基类派生类的 "赋值小规则"------ 对象转换那些事

基类和派生类的对象、指针、引用之间的赋值转换,是笔试高频考点,也是易混点,核心记住 "切片 / 切割" 这个概念,再记 3 条规则就够了。
核心概念:切片 / 切割
派生类对象包含了基类的所有成员 + 自身的专属成员 ,当派生类对象赋值给基类对象 / 指针 / 引用时,编译器会把派生类中基类那部分成员 "切下来" 赋值过去,这就是切片。

3 条核心赋值规则(理解性记忆)
规则 1:派生类对象 → 基类对象 / 指针 / 引用 ✅(直接赋值,安全)

这是最常用的情况,直接赋值即可,编译器自动完成切片。
例子:

cpp 复制代码
class Person
{
protected:
    string _name;
    string _sex;
    int _age;
};
class Student : public Person
{
public:
    int _No; // 学号
};

void Test()
{
    Student sobj;
    // 子类对象赋值给父类对象/指针/引用,全部可行
    Person pobj = sobj; // 对象赋值
    Person* pp = &sobj; // 指针赋值
    Person& rp = sobj;  // 引用赋值
}

规则 2:基类对象 → 派生类对象 ❌(直接赋值,禁止)

基类对象只有基础成员,没有派生类的专属成员,直接赋值会导致派生类的专属成员无值可赋,编译器直接禁止

cpp 复制代码
sobj = pobj; // 错误!基类对象不能赋值给派生类对象

规则 3:基类指针 / 引用 → 派生类指针 / 引用 ⚠️(强制转换,谨慎使用)

编译器不会自动转换,需要强制类型转换 ,但只有基类指针 / 引用指向派生类对象时,转换才安全;如果指向基类对象,转换后访问派生类专属成员会越界访问,程序崩溃!

cpp 复制代码
void Test()
{
    Student sobj;
    Person pobj;
    Person* pp;

    // 情况1:基类指针指向派生类对象,强制转换安全
    pp = &sobj;
    Student* ps1 = (Student*)pp;
    ps1->_No = 10; // 正常访问,没问题

    // 情况2:基类指针指向基类对象,强制转换后越界访问!
    pp = &pobj;
    Student* ps2 = (Student*)pp;
    ps2->_No = 10; // 错误!越界访问,程序崩溃
}

✨ 新手拓展:如果基类是多态类型(有虚函数),可以用dynamic_cast进行安全转换,编译器会自动检查类型,后续学多态会详细讲,现在先了解即可。

3. 继承里的 "地盘之争"------ 作用域问题

继承体系中,基类和派生类都有独立的作用域 ,这是理解 "隐藏(重定义)" 的关键,新手要区分清楚隐藏和重载 ,别搞混了!
4 条核心规则

  • 基类和派生类是两个独立的作用域,这是隐藏的前提。
  • 子类和父类有同名成员 时,子类成员会屏蔽父类对同名成员的直接访问 ,这种现象叫隐藏(也叫重定义)。
    ✨ 解决方法:在子类中用基类名::基类成员显式访问父类的同名成员。
  • 成员函数的隐藏更 "宽松":只要函数名相同,就构成隐藏,不管参数列表、返回值是否相同(和重载不同!)。
  • 开发建议 :继承体系中,尽量不要定义同名成员,容易混淆,增加调试成本。

例子
例子 1:同名成员变量的隐藏

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

class Person
{
protected:
    string _name = "小李子";
    int _num = 111; // 身份证号(父类的num)
};

class Student : public Person
{
public:
    void Print()
    {
        cout << "姓名:" << _name << endl;
        // 父类num被隐藏,必须用Person::num显式访问
        cout << "身份证号:" << Person::_num << endl;
        // 直接访问的是子类自己的num
        cout << "学号:" << _num << endl;
    }
protected:
    int _num = 999; // 学号(子类的num,和父类同名)
};

void Test()
{
    Student s1;
    s1.Print(); // 输出:小李子 111 999
}

例子 2:同名成员函数的隐藏(区分重载)
重载的前提是同一作用域,而继承中基类和派生类是不同作用域,所以同名函数不是重载,而是隐藏!

cpp 复制代码
#include <iostream>
using namespace std;

class A
{
public:
    void fun()
    {
        cout << "func()" << endl;
    }
};

class B : public A
{
public:
    // 函数名相同,构成隐藏,不管参数是否不同
    void fun(int i)
    {
        A::fun(); // 显式访问父类的fun
        cout << "func(int i)->" << i << endl;
    }
};

void Test()
{
    B b;
    b.fun(10); // 调用子类的fun,输出:func() func(int i)->10
    // b.fun(); // 错误!父类fun被隐藏,直接调用会编译失败
    b.A::fun(); // 正确,显式调用父类fun
}

✨ 新手坑:别以为子类 fun 加了参数就是重载,只要不在同一作用域,永远是隐藏!

4. 派生类的 "六大默认成员函数"------ 编译器帮我们做了啥

C++ 中,类如果不自己写六大默认成员函数,编译器会自动生成,分别是**:构造函数、析构函数、拷贝构造函数、赋值运算符重载、普通对象取地址重载、const 对象取地址重载**(后两个几乎不用自己写,重点看前四个)。

派生类的默认成员函数不是 "凭空生成" 的,而是基于基类的成员函数来初始化 / 清理核心原则是:先处理基类,再处理派生类(构造);先处理派生类,再处理基类(析构)。

新手必懂的 7 条核心规则

1.派生类的构造函数 必须调用基类的构造函数,初始化基类的成员;如果基类没有默认构造函数(无参 / 全缺省) ,派生类必须在初始化列表中显式调用基类的构造函数

2.派生类的拷贝构造函数 必须调用基类的拷贝构造,完成基类成员的拷贝初始化。

3.派生类的赋值运算符重载(operator=) 必须调用基类的 operator=,完成基类成员的赋值。

4.派生类的析构函数 执行完成后,编译器会自动调用基类的析构函数保证先清理派生类成员,再清理基类成员

5.派生类对象的初始化顺序 :先调用基类构造函数,再调用派生类构造函数。

6.派生类对象的析构顺序 :先调用派生类析构函数,再调用基类析构函数(和构造相反)。

7.析构函数的隐藏特殊点 :编译器会把所有析构函数名统一处理为destructor(),所以即使子类析构函数名和父类看似不同(都是~类名),也构成隐藏(后续多态会讲如何打破这个隐藏,加 virtual)。

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

// 基类:Person
class Person
{
public:
    // 构造函数
    Person(const char* name = "peter")
        : _name(name)
    {
        cout << "Person()" << endl;
    }

    // 拷贝构造函数
    Person(const Person& p)
        : _name(p._name)
    {
        cout << "Person(const Person& p)" << endl;
    }

    // 赋值运算符重载
    Person& operator=(const Person& p)
    {
        cout << "Person operator=(const Person& p)" << endl;
        if (this != &p)
            _name = p._name;
        return *this;
    }

    // 析构函数
    ~Person()
    {
        cout << "~Person()" << endl;
    }

protected:
    string _name; // 姓名
};

// 派生类:Student
class Student : public Person
{
public:
    // 构造函数:初始化列表显式调用基类构造
    Student(const char* name, int num)
        : Person(name)
        , _num(num)
    {
        cout << "Student()" << endl;
    }

    // 拷贝构造:初始化列表显式调用基类拷贝构造
    Student(const Student& s)
        : Person(s)
        , _num(s._num)
    {
        cout << "Student(const Student& s)" << endl;
    }

    // 赋值运算符重载:显式调用基类的operator=
    Student& operator=(const Student& s)
    {
        cout << "Student& operator=(const Student& s)" << endl;
        if (this != &s)
        {
            Person::operator=(s); // 调用基类的赋值重载
            _num = s._num;        // 赋值自己的成员
        }
        return *this;
    }

    // 析构函数:编译器自动调用基类析构
    ~Student()
    {
        cout << "~Student()" << endl;
    }

protected:
    int _num; // 学号
};

// 测试:看输出顺序,理解构造/析构/拷贝/赋值的过程
void Test()
{
    cout << "---构造s1---" << endl;
    Student s1("jack", 18); // 先Person(),再Student()

    cout << "---拷贝构造s2---" << endl;
    Student s2(s1); // 先Person(const Person& p),再Student(const Student& s)

    cout << "---赋值s1 = s3---" << endl;
    Student s3("rose", 17);
    s1 = s3; // 先Student& operator=,再Person operator=

    cout << "---析构开始---" << endl;
    // 析构顺序:s3→s2→s1,每个对象先~Student(),再~Person()
}

int main()
{
    Test();
    return 0;
}

5. 友元和静态成员的 "继承小脾气"------ 能继承吗?咋用?

继承中,友元关系和静态成员的处理比较特殊,新手容易误以为 "友元能继承""静态成员每个子类都有一份",其实都是错的

5.1 继承与友元:友元关系不能继承

核心结论:基类的友元,不能访问派生类的私有 / 保护成员 ,友元关系是 "一对一" 的,不会随继承传递。
例子:基类 Person 的友元函数 Display,能访问 Person 的成员,但不能直接访问 Student 的_stuNum(除非 Student 也把 Display 声明为友元)。

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

class Student; // 前向声明
class Person
{
public:
    friend void Display(const Person& p, const Student& s);
protected:
    string _name = "peter"; // 姓名
};

class Student : public Person
{
    // 如果不加这行,Display访问_stuNum会报错
    // friend void Display(const Person& p, const Student& s);
protected:
    int _stuNum = 1001; // 学号
};

// 基类友元函数
void Display(const Person& p, const Student& s)
{
    cout << p._name << endl; // 正确,能访问基类保护成员
    // cout << s._stuNum << endl; // 错误!友元不能继承,无法访问子类保护成员
}

int main()
{
    Person p;
    Student s;
    Display(p, s);
    return 0;
}

✨ 注意:如果想让基类友元访问派生类成员,需要在派生类中重新将该友元声明为友元。

5.2 继承与静态成员:静态成员整个继承体系只有一份

核心结论:基类定义的static静态成员无论派生出多少个子类,整个继承体系中只有一个实例 ,所有基类和派生类对象共享这个静态成员。

简单来说:静态成员是 "类级别的",不是 "对象级别的",继承只是让子类拥有访问权限,不会创建新的静态成员。

cpp 复制代码
#include <iostream>
using namespace std;

class Person
{
public:
    Person() { ++_count; } // 构造一次,计数+1
protected:
    string _name;
public:
    static int _count; // 静态成员:统计人数
};
// 静态成员必须在类外初始化!新手别忘
int Person::_count = 0;

// 子类1:Student
class Student : public Person
{
protected:
    int _stuNum;
};

// 子类2:Graduate(研究生,继承自Student)
class Graduate : public Student
{
protected:
    string _seminarCourse;
};

// 测试:所有子类共享Person的_count
void TestPerson()
{
    Student s1, s2, s3;
    Graduate s4;
    cout << "人数:" << Person::_count << endl; // 输出4,4个对象构造
    cout << "人数:" << Student::_count << endl; // 输出4,子类可直接访问
    cout << "人数:" << Graduate::_count << endl; // 输出4

    Student::_count = 0; // 子类修改,基类也会变
    cout << "人数:" << Person::_count << endl; // 输出0,共享同一份
}

int main()
{
    TestPerson();
    return 0;
}

✨ 坑:静态成员必须在类外初始化,格式是类型 类名::静态成员名 = 初始值,别漏了!

6. 继承里的 "大坑"------ 菱形继承与虚拟继承

菱形继承是 C++ 继承中最复杂的问题 ,也是笔试高频考点,它是多继承的特殊情况 ,新手学这部分重点掌握:什么是菱形继承、有什么问题、怎么解决(虚拟继承)、虚拟继承的简单原理。
6.1 先搞懂:单继承 vs 多继承
单继承一个子类只有一个直接父类 ,比如Student : public Person,这是最安全的继承方式,推荐使用。
多继承 :一个子类有两个及以上直接父类,比如Assistant : public Student, public Teacher,这是菱形继承的根源,实际开发中尽量避免。

6.2 菱形继承:多继承的 "坑中之坑"

(1)什么是菱形继承?

子类的两个直接父类,都继承自同一个基类 ,形成一个 "菱形" 的继承结构,比如:
Person 是基类,Student和Teacher都继承自PersonAssistant(助教)又同时继承自Student和Teacher,结构如下:

(2)菱形继承的两大致命问题
问题 1:二义性 ------ 派生类对象访问公共基类的成员时,编译器不知道访问哪个父类的基类成员 ,直接编译失败。
问题 2:数据冗余 ------ 派生类对象中,公共基类的成员会存两份(一份来自 Student,一份来自 Teacher),浪费内存。

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

// 公共基类
class Person
{
public:
    string _name = "peter"; // 姓名
};

// 子类1:Student继承Person
class Student : public Person
{
protected:
    int _num; // 学号
};

// 子类2:Teacher继承Person
class Teacher : public Person
{
protected:
    int _id; // 工号
};

// 菱形子类:Assistant继承Student和Teacher
class Assistant : public Student, public Teacher
{
protected:
    string _majorCourse; // 主修课程
};

void Test()
{
    Assistant a;
    // a._name = "tom"; // 错误!二义性,不知道访问Student::_name还是Teacher::_name
    // 解决二义性:显式指定父类
    a.Student::_name = "tom";
    a.Teacher::_name = "jerry";
    // 数据冗余:a中有两份_name,分别是Student和Teacher的
}

6.3 菱形虚拟继承:解决二义性和数据冗余的 "神器"
(1)如何使用虚拟继承?

公共基类的直接子类 (菱形的左右两个子类)继承时,加上virtual 关键字,即virtual public 基类名,就能实现虚拟继承,解决菱形继承的两大问题。

修改后的例子(加入 virtual,解决问题):

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

class Person
{
public:
    string _name = "peter";
};

// 加入virtual,虚拟继承Person
class Student : virtual public Person
{
protected:
    int _num;
};

// 加入virtual,虚拟继承Person
class Teacher : virtual public Person
{
protected:
    int _id;
};

class Assistant : public Student, public Teacher
{
protected:
    string _majorCourse;
};

void Test()
{
    Assistant a;
    a._name = "tom"; // 正确!无歧义,只有一份_name
    cout << a._name << endl; // 输出tom
}

✨ 新手注意:virtual 只加在菱形的左右子类上,最底层的子类(Assistant)不用加,加了也没用。

(2)虚拟继承的简单原理(新手浅尝即可,不用深钻底层)

虚拟继承的底层实现比较复杂,新手只需要理解核心逻辑:

虚拟继承会让公共基类的成员在派生类对象中只存一份 ,并在菱形的左右子类(Student/Teacher)中添加一个虚基表指针 ,指向一个虚基表 ;虚基表中存储的是偏移量通过这个偏移量,子类能找到唯一的公共基类成员,从而解决二义性和数据冗余

简单来说:虚基表指针就是 "导航仪",告诉编译器公共基类成员的唯一位置,不用再存两份,也不会找错。

✨ 开发建议:**尽量不要设计多继承,更不要设计菱形继承!**虚拟继承虽然能解决问题,但底层实现复杂,会带来一定的性能开销,实际开发中能避免就避免。

7. 继承的 "灵魂反思"------ 咋用才对?继承 vs 组合

学完继承的所有知识点,新手可能会有一个疑问:既然继承能实现代码复用,那是不是所有场景都用继承?答案是不是 !C++ 中还有另一种更推荐的复用方式 ------组合实际开发中优先使用组合,而非继承。

这部分重点掌握:继承和组合的关系、各自的优缺点、使用场景。

7.1 继承:is-a 关系(是一种)

public 继承的本质是is-a(是一种)关系:每个派生类对象,都是一个基类对象。

cpp 复制代码
// Car是基类,BMW和Benz都是Car的子类,is-a关系
class Car{
protected:
    string _colour = "白色";
    string _num = "陕ABIT00";
};
class BMW : public Car{
public:
    void Drive() {cout << "好开-操控" << endl;}
};
class Benz : public Car{
public:
    void Drive() {cout << "好坐-舒适" << endl;}
};

7.2 组合:has-a 关系(有一个)

组合的本质是has-a(有一个)关系:一个类的对象,包含另一个类的对象。

比如:Car has a Tire(汽车有轮胎),Phone has a Battery(手机有电池),这种 "包含某个部件" 的关系,适合用组合。

cpp 复制代码
// Tire是轮胎类,Car组合Tire,has-a关系
class Tire{
protected:
    string _brand = "Michelin";
    size_t _size = 17;
};
class Car{
protected:
    string _colour = "白色";
    string _num = "陕ABIT00";
    Tire _t; // 组合Tire对象,汽车有轮胎
};

7.3 继承 vs 组合:优缺点对比

7.4 必懂的使用原则

  • 优先使用组合:组合耦合度低、封装性好、维护性高,是实际开发中更推荐的复用方式。
  • 继承的适用场景:
    类之间是is-a 关系时,用继承;
    需要实现多态时,必须用继承(后续学多态会知道,虚函数 + 继承是实现多态的核心)。
  • 折中原则:如果类之间既可以用继承,也可以用组合,优先用组合。

8. 笔试面试高频题 ------ 考点速记

继承是 C++ 笔试面试的必考知识点,以下是高频考题,直接背答案即可,覆盖 90% 的考点:

题 1:什么是菱形继承?菱形继承的问题是什么?

答案:菱形继承是多继承的特殊情况,指一个子类的两个直接父类继承自同一个公共基类,形成菱形的继承结构;其核心问题是数据冗余 (公共基类成员在派生类中存两份)和二义性(访问公共基类成员时编译器无法确定访问哪个父类的成员)。

题 2:什么是菱形虚拟继承?如何解决数据冗余和二义性的?

答案:在菱形继承的公共基类直接子类上,使用virtual 关键字进行的继承就是菱形虚拟继承;虚拟继承让公共基类的成员在派生类对象中只存储一份 ,同时通过虚基表指针和虚基表(存储偏移量)让子类能唯一找到公共基类成员,从而同时解决数据冗余和二义性问题。

题 3:继承和组合的区别?什么时候用继承?什么时候用组合?

答案:

  • 关系不同:继承是is-a(是一种)关系,组合是has-a(有一个)关系;
  • 复用方式不同:继承是白箱复用(基类细节可见),组合是黑箱复用(被组合类细节隐藏);
  • 耦合度不同:继承耦合度高,组合耦合度低;
  • 封装性不同:继承破坏基类封装,组合封装性好。
    使用场景:
  • 当类之间是is-a关系,或需要实现多态时,用继承;
  • 当类之间是has-a关系,或仅需要代码复用时,优先用组合。

题 4:基类和派生类的对象赋值转换有哪些规则?

答案:

1.派生类对象可以直接赋值给基类对象 / 指针 / 引用(切片,安全);

2.基类对象不能直接赋值给派生类对象(编译器禁止);

3.基类指针 / 引用可以通过强制类型转换赋值给派生类指针 / 引用,但只有基类指针 / 引用指向派生类对象时才安全,否则会越界访问。

题 5:派生类的构造函数和析构函数的执行顺序是什么?

答案:

  • 构造顺序:先调用基类的构造函数,再调用派生类的构造函数;
  • 析构顺序:先调用派生类的析构函数,再调用基类的析构函数(和构造顺序相反),编译器会自动在派生类析构后调用基类析构。

题 6:基类的 private 成员在派生类中能访问吗?protected 成员的作用是什么?

答案:基类的 private 成员在派生类中无论哪种继承方式都不可见 ,无法访问;protected 成员是为继承而生的,它的作用是:基类的 protected 成员不能被类外访问,但能被派生类访问,兼顾封装性和继承的复用性。

题 7:静态成员在继承体系中的特点是什么?

答案:基类的静态成员在整个继承体系中只有一个实例,所有基类和派生类的对象共享这个静态成员,继承只是让子类拥有对静态成员的访问权限,不会创建新的静态成员。

题 8:友元关系能继承吗?

答案:友元关系不能继承,基类的友元无法访问派生类的私有 / 保护成员,如果需要访问,必须在派生类中重新将该友元声明为友元。

相关推荐
来恩10034 小时前
JSTL的标签库种类
java·开发语言
Miss_min4 小时前
128K长序列数据生成
开发语言·python·深度学习
小宋0014 小时前
QT中控件qss样式修改
开发语言·qt
图像僧4 小时前
vs2019中的属性页使用说明
java·开发语言·jvm
YOU OU4 小时前
SpringBoot 日志
java·开发语言
智者知已应修善业4 小时前
【51单片机LED闪烁10次数码管显示0-9】2023-12-14
c++·经验分享·笔记·算法·51单片机
智者知已应修善业4 小时前
【51单片机2按键控制1个敞亮LED灯闪烁和熄灭】2023-11-3
c++·经验分享·笔记·算法·51单片机
江南十四行5 小时前
并发编程(二)
java·开发语言
weixin_471383035 小时前
统一缩放单位基础(px、em、rem)
开发语言·javascript·ecmascript