【面向对象语言三大特性之 “继承”】

这篇具有很好参考价值的文章主要介绍了【面向对象语言三大特性之 “继承”】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

1.继承的概念及定义

1.1继承的概念

1.2 继承定义

1.2.1定义格式

 1.2.2继承关系和访问限定符

 1.2.3继承基类成员访问方式的变化

2.基类和派生类对象赋值转换

3.继承中的作用域

4.派生类的默认成员函数

5.继承与友元

6. 继承与静态成员

7.复杂的菱形继承及菱形虚拟继承

8.继承的总结和反思

9.笔试面试题


1.继承的概念及定义

1.1继承的概念

继承 (inheritance) 机制是面向对象程序设计 使代码可以复用 的最重要的手段,它允许程序员在 保持原有类特性的基础上进行扩展 ,增加功能,这样产生新的类,称派生类。继承 呈现了面向对象 程序设计的层次结构 ,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用, 承是类设计层次的复用。

 我们来举个栗子:

首先我们定义一个基类Person:

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

再定义两个子类Stuend和Teacher,这两个子类是继承了父类的成员(成员函数+成员变量)

我们可以通过监视窗口来查看:

【面向对象语言三大特性之 “继承”】

 不难发现的确子类继承了父类的成员。

1.2 继承定义

1.2.1定义格式

下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类。

 【面向对象语言三大特性之 “继承”】

 1.2.2继承关系和访问限定符

 【面向对象语言三大特性之 “继承”】

 1.2.3继承基类成员访问方式的变化

类成员 / 继承方式
public继承 protected继承 private继承
基类的 public 成员
派生类的public成员
派生类的 protected
成员
派生类的 private
成员
基类的 protected 成员
派生类的 protected
成员
派生类的 protected
成员
派生类的 private
成员
基类的 private 成员
在派生类中不可见 在派生类中不可见
在派生类中不可见
总结:
  • 1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在子类里面还是子类外面都不能去访问它
  • 2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected可以看出保护成员限定符是因继承才出现的
  • 3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式)public > protected > private
  • 4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public不过最好显示的写出继承方式
  • 5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

 这些大家可以自行去验证,这里我就不再多说了。


2.基类和派生类对象赋值转换

  • 派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
  • 基类对象不能赋值给派生类对象。
  • 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(Run Time Type Information)dynamic_cast  来进行识别后进行安全转换。(ps:这个我们后面再讲解,这里先了解一下)

 看下面这样一段代码:

int main()
{
	Student sobj;
	Person pobj = sobj;
	Person* pp = &sobj;
	Person& rp = sobj;
	return 0;
}

我们发现编译既然是没有错误的,这里就将子类切片给了父类。

(注意:切片行为是不存在隐式类型转换的,是直接将子类切片给父类)

【面向对象语言三大特性之 “继承”】

 但是我们要注意:基类对象不能赋值给派生类对象 基类的指针可以通过强制类型转换赋值给派生类的指针 (这样会有越界的风险)

    //基类对象不能赋值给派生类对象
    sobj = pobj;
    
    //基类对象不能引用给派生类对象
    Person p;
	Student& rs = p;

    //基类的指针可以通过强制类型转换赋值给派生类的指针
    pp = &sobj
    Student* ps1 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题
    ps1->_No = 10;
    
   

3.继承中的作用域

  • 1. 在继承体系中基类派生类都有独立的作用域
  • 2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问
  • 3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
  • 4. 注意在实际中在继承体系里面最好不要定义同名的成员

 就像下面这段程序:

class Person
{
protected:
	string _name = "小李子"; // 姓名
	int _num = 111;// 身份证号
};
class Student : public Person
{
public:
	void Print()
	{
		cout << " 姓名:" << _name << endl;
		cout << " 身份证号:" << Person::_num << endl;
		cout << " 学号:" << _num << endl;
	}
protected:
	int _num = 999; // 学号
};
void Test()
{
	Student s1;
	s1.Print();
};

 这段程序能够正确运行:

【面向对象语言三大特性之 “继承”】

 通过以前学习的知识我们也知道当同名变量出现在一起时会优先使用局部,而这里_num出现在两个不同的类域中,默认情况就在本域去寻找,指定类域后就在指定类域去寻找。这种情况就叫做隐藏。

再来看看一段程序:

// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
class A
{
public:
 void fun()
 {
 cout << "func()" << endl;
 }
};
class B : public A
{
public:
 void fun(int i)
 {
 A::fun();
 cout << "func(int i)->" <<i<<endl;
 }
};
void Test()
{
 B b;
 b.fun(10);
};

由于fun()函数在不同作用域中,所以他们构成隐藏而不是重载。


4.派生类的默认成员函数

6 个默认成员函数, 默认 的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类
中,这几个成员函数是如何生成的呢?
  • 1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
  • 2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
  • 3. 派生类的operator=必须要调用基类的operator=完成基类的复制。
  • 4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。
  • 5. 派生类对象初始化先调用基类构造再调派生类构造。
  • 6. 派生类对象析构清理先调用派生类析构再调基类的析构。
  • 7. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同(这个我们后面讲解多态会详细讲解)。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。
class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}

	Person(string name= "张三", int age=18)
		:_name(name)
		, _age(age)
	{
		cout << "Person(string name= 张三, int age=18)" << endl;
	}

	Person(const Person& p)
		:_name(p._name)
		,_age(p._age)
	{
		cout << "Person(const Person& p)" << endl;
	}

	Person& operator=(const Person& p)
	{
		_name = p._name;
		_age = p._age;
		cout << "Person& operator=(const Person& p)" << endl;
		return *this;
	}

	~Person()
	{
		//如果有额外资源需要自己动手清理
		cout << "~Person()" << endl;
	}

protected:
	string _name;//  姓名
	int _age;//  年龄
};

class Student : public Person
{
public:
	Student(string name = "张三", int age = 18, int stuid=1)
		:Person(name, age)//这里要手动去调用基类的构造函数,
		,_stuid(stuid)
	{
		cout << "Student(int stuid=1)" << endl;
	}

	Student(const Student& s)
		:Person(s)//手动调用基类的拷贝构造,一种切片行为
		, _stuid(s._stuid)
	{
		cout << "Student(const Student& s)" << endl;
	}

	Student& operator=(const Student& p)
	{
		cout << "Student& operator=(const Student& p)" << endl;
		if (this != &p)
		{
			Person::operator=(p);//手动调用基类的赋值运算符重载,一种切片行为
			_stuid = p._stuid;
		}
		return *this;
	}

	~Student()
	{
		//this->~Student();//自己不要手动调用,编译器会自己调用,否则同一块资源有可能会被重复析构两次
		cout << "~Student()" << endl;
	}

protected:
	int _stuid; // 学号

};

不知道大家注意到了没有,由于上面程序子类继承父类的成员以及子类自己的成员都没有额外资源,所以这时就算我们不写,用系统默认生成的也能够完成任务,但是如果子类非继承成员需要额外资源的话这些都必须由我们自己实现。

如果父类没有默认构造,那么子类就要自己在初始化列表列表阶段显示调用。


5.继承与友元

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。

看下面一段程序:

class Student;
class Person
{
public:
	friend void Display(const Person& p, const Student& s);
protected:
	string _name; // 姓名
};
class Student : public Person
{
protected:
	int _stuNum; // 学号
};
void Display(const Person& p, const Student& s)
{
	cout << p._name << endl;
	cout << s._stuNum << endl;
}
void main()
{
	Person p;
	Student s;
	Display(p, s);
}

上面程序中Display是People的友元,所以在Display可以访问到People的保护成员,但是Display不是Student的友元,所以不能够访问非继承下来的私有成员。要想访问还得在Student中加入Display的友元声明。


6. 继承与静态成员

基类定义了 static 静态成员,则整个继承体系里面只有一个这样的成员 。无论派生出多少个子
类,都只有一个 static 成员实例
class Person
{
public:
	Person() { ++_count; }
protected:
	string _name; // 姓名
public:
	static int _count; // 统计人的个数。
};
int Person::_count = 0;

class Student : public Person
{
protected:
	int _stuNum; // 学号
};
class Graduate : public Student
{
protected:
	string _seminarCourse; // 研究科目
};
void TestPerson()
{
	Student s1;
	Student s2;
	Student s3;
	Graduate s4;
	cout << " 人数 :" << Person::_count << endl;
	Student::_count = 0;
	cout << " 人数 :" << Person::_count << endl;
}

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

像上面的基类中定义了一个静态变量,在子类中所有成员共享这个变量,只会实例化出一份。


7.复杂的菱形继承及菱形虚拟继承

单继承:一个子类只有一个直接父类时称这个继承关系为单继承 。

【面向对象语言三大特性之 “继承”】

 多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承。

【面向对象语言三大特性之 “继承”】

 菱形继承:菱形继承是多继承的一种特殊情况。

【面向对象语言三大特性之 “继承”】

 而菱形继承会有一些问题:大家想想,Student 和 Teacher各自保存了一份People的数据,那么Assitant中不就保存了两份People的数据吗?这样就不太合理了吧,如果People中数据很大时那不就浪费了很多空间去保存两份一样的数据吗?

我们来看下面这段代码:

class Person
{
public:
	string _name; // 姓名
};
class Student : public Person
{
protected:
	int _num; //学号
};
class Teacher : public Person
{
protected:
	int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};
void Test()
{
	Assistant a;
	//a._name = "peter";// 这样会有二义性无法明确知道访问的是哪一个
	// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";
}

虽然上面的代码解决了二义性的问题,但是数据冗余问题还没有解决,那有啥办法可以解决菱形继承中数据冗余的问题呢?

虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在 Student
Teacher 的继承 Person 时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地
方去使用。
class Person
{
public:
	string _name; // 姓名
};
class Student : virtual public Person
{
protected:
	int _num; //学号
};
class Teacher :virtual public Person
{
protected:
	int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};
void Test()
{
	Assistant a;
	a._name = "peter";
}

这里值得大家注意的是virtual究竟该用在哪一个类里。下面给大家一种继承关系,大家能分析下virtual应该加在哪里呢?

【面向对象语言三大特性之 “继承”】

是在B和C之间加还是在C和D之间加?大家要想想这么一个问题,数据冗余是从哪儿开始出现的?是不是因为B和C继承了A中的数据,所以大家心里应该有了答案,应该在B和C之间加virtual。

这样就解决了菱形继承中数据冗余的问题。那菱形继承的原理是啥呢?

为了研究虚拟继承原理,我们给出了一个简化的菱形继承的继承体系,再借助 内存窗口观察对象成
员的模型。
下面给出这种模型:

【面向对象语言三大特性之 “继承”】

这里温馨提示一下大家一定要在32位平台下进行验证(这是博主血的教训)

【面向对象语言三大特性之 “继承”】

 这里不难看出_a已经重复冗余了,_a的数据被重复保留了两份。当我们加上virtual时:

【面向对象语言三大特性之 “继承”】

 我们发现_a的数据已经只有了一份了,这时无论是对B的_a做修改还是对C的_a做修改,其实本质上他们指向的是同一个,就没有数据冗余了。

这时大家或许还有疑问,保存数据03和04上面那一行是什么鬼呀?

我们可以打开另外的监视窗口进行查看:

【面向对象语言三大特性之 “继承”】

 其实03和04上面的地址就是虚基表指针,而他们指向了一个虚基表,这个虚基表里面存放的就是偏移量,那么这个偏移量又是啥?有啥作用吗?

存放偏移量是为了B和C都能快速找到他们公共的_a,我们看左边第一个内存窗口,从0X007EF710到0X007EF71C一共需要的字节是12个字节,而右边虚基表中恰好存放的是C(12个字节),同理从007EF708到0X007EF71C一共需要的字节是20字节,而右边虚基表中恰好存放的是14(20个字节)

为什么D中B和C部分要去找属于自己的A?那么大家看看当下面的赋值发生时,d是不是要去找出B/C成员中的A才能赋值过去?
D d;
B b = d;
C c = d;

这里d赋值给B类型的b和C类型的c实质是一种切片行为,那么d肯定就要去找他们各自对应的偏移量才能够正确的赋值过去。


8.继承的总结和反思

1. 很多人说 C++ 语法复杂,其实多继承就是一个体现。有了多继承 ,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂。所以一般不建议设计出多继承,一定不要设 计出菱形继承。否则在复杂度及性能上都有问题。
2. 多继承可以认为是 C++ 的缺陷之一,很多后来的很多 语言都没有多继承,如 Java
3. 继承和组合
  • public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
  • 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。
优先使用对象继承,而不是类继承https://www.cnblogs.com/nexiyi/archive/2013/06/16/3138568.html
  • 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语白箱是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。
  • 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复(black-box reuse),因为对象的内部细节是不可见的。对象只以黑箱的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。
  • 实际尽量多去用组合。优先使用对象组合有助于你保持每个类被封装,并被集中在单个任务上。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。

9.笔试面试题

  • 什么是菱形继承?菱形继承的问题是什么?

有一个基类被多个子类继承,这多个子类有两个及两个以上又被同一个子类所继承,构成这种类似于菱形关系的继承。菱形继承的问题是使用时有二义性以及数据冗余的问题。

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

菱形虚拟继承是用virtual来修饰数据冗余那一部分类,让他们被继承下来的成员共享同一块空间。解决数据冗余只能用菱形虚拟继承,二义性除了用菱形虚拟继承外还可以用类作用域限定符。

  •  继承和组合的区别?什么时候用继承?什么时候用组合?

继承允许你根据基类的实现来定义派生类的实现,基类的内部细节对子类可见 ,这种通过生成派生类的复用通常被称为白箱复用对象组合是类继承之外的另一种复用选择,新的更复杂的功能可以通过组装或组合对象来获得,对象的内部细节是不可见的,这种复用风格被称为黑箱复用

能用组合优先用组合,优先使用对象组合有助于你保持每个类被封装,并被集中在单个任务上。但有些类与类之间关系十分紧密的话就可以用继承,另外多态的实现也必须使用继承。文章来源地址https://www.toymoban.com/news/detail-405163.html

到了这里,关于【面向对象语言三大特性之 “继承”】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 【JAVASE】带你了解面向对象三大特性之一(继承)

    【JAVASE】带你了解面向对象三大特性之一(继承)

    ✅作者简介:大家好,我是橘橙黄又青,一个想要与大家共同进步的男人😉😉 🍎个人主页:再无B~U~G-CSDN博客 Java 中使用类对现实世界中实体来进行描述,类经过实例化之后的产物对象,则可以用来表示现实中的实体,但是现实世界错综复杂,事物之间可能会存在一些关

    2024年04月09日
    浏览(13)
  • 【面向对象语言三大特性之 “多态”】

    【面向对象语言三大特性之 “多态”】

    目录  1. 多态的概念 1.1 概念 2. 多态的定义及实现 2.1多态的构成条件 2.2 虚函数  2.3虚函数的重写 2.4 C++11 override 和 final  2.5 重载、覆盖(重写)、隐藏(重定义)的对比  3. 抽象类 3.1 概念 3.2 接口继承和实现继承  4.多态的原理 4.1虚函数表  4.2多态的原理 4.3 动态绑定与静态绑定

    2023年04月17日
    浏览(16)
  • 【C++庖丁解牛】面向对象的三大特性之一多态 | 抽象类 | 多态的原理 | 单继承和多继承关系中的虚函数表

    【C++庖丁解牛】面向对象的三大特性之一多态 | 抽象类 | 多态的原理 | 单继承和多继承关系中的虚函数表

    🍁你好,我是 RO-BERRY 📗 致力于C、C++、数据结构、TCP/IP、数据库等等一系列知识 🎄感谢你的陪伴与支持 ,故事既有了开头,就要画上一个完美的句号,让我们一起加油 需要声明的,本节课件中的代码及解释都是在vs2013下的x86程序中,涉及的指针都是4bytes。如果要其他平台

    2024年04月10日
    浏览(12)
  • 【Go语言快速上手(四)】面向对象的三大特性引入

    【Go语言快速上手(四)】面向对象的三大特性引入

    💓博主CSDN主页:杭电码农-NEO💓   ⏩专栏分类:Go语言专栏⏪   🚚代码仓库:NEO的学习日记🚚   🌹关注我🫵带你学习更多Go语言知识   🔝🔝 GO语言也支持面向对象编程,但是和传统的面向对象语言(如CPP)有明显的区别,GO并不是纯粹的面对对象编程语言.所以说GO是支持面向对

    2024年04月26日
    浏览(13)
  • 面向对象详解,面向对象的三大特征:封装、继承、多态

    面向对象详解,面向对象的三大特征:封装、继承、多态

    一、面向对象与面向过程 面向对象编程 (Object-Oriented Programming,简称OOP)和 面向过程编程 (Procedural Programming,简称PP)是两种不同的 编程范式 。 面向对象编程强调把问题分解成对象,通过封装、继承和多态等机制,来处理对象之间的关系 。每个对象都可以独立地处理自

    2024年02月21日
    浏览(13)
  • 面向对象三大特性之一——継承(上篇)

    面向对象三大特性之一——継承(上篇)

    目录 前文 一.什么是継承? 1.1 継承的定义 1.2 継承的格式 1.2.1 継承的使用格式 1.2.2 継承关系和访问限定符 二,基类和派生类对象复制转换 三,継承中的作用域 四,派生类/子类中的默认成员函数 六,継承与友元  六,継承与静态成员 总结 本篇文章主要是详解面向对象三大

    2024年02月03日
    浏览(10)
  • C++ 面向对象三大特性——多态

    C++ 面向对象三大特性——多态

    ✅1主页:我的代码爱吃辣 📃2知识讲解:C++ 继承 ☂️3开发环境:Visual Studio 2022 💬4前言:面向对象三大特性的,封装,继承,多态,今天我们研究研究 C++的多态 。 目录 一.多态的概念 二.多态的定义及实现 1.多态的构成条件 2. 虚函数 3.虚函数的重写 4. C++11 override 和 fina

    2024年02月12日
    浏览(39)
  • Unity-C# (面向对象三大特性)

    Unity-C# (面向对象三大特性)

    传值调用和引用调用 输出:11 传值调用中形参为一个新的临时变量,赋值由实参拷贝而来,只是赋予了与实参一样的值所以在函数体内部修改并不会影响实参 输出:22 引用调用时,形参拷贝的是实参的地址,二者指向同一个堆空间,所以形参改变会对实参造成影响 输出:

    2024年04月09日
    浏览(9)
  • 【java】面向对象三大特性之多态

    【java】面向对象三大特性之多态

            俗话说的好,“一龙生九子,九子各不同”,这句话就蕴含了面向对象三大特性之一的多态的思想。那么多态具体有什么特点呢,就由博主来带大家梳理一下吧🤔 目录 一、什么是多态 二、重写 三、向上转型和向下转型 1、向上转型 2、向下转型 四、多态的优缺点

    2024年03月15日
    浏览(8)
  • 【JAVASE】面向对象程序三大特性之一( 封装)

    【JAVASE】面向对象程序三大特性之一( 封装)

    ✅作者简介:大家好,我是橘橙黄又青,一个想要与大家共同进步的男人😉😉n 🍎个人主页:再无B~U~G-CSDN博客 目标: 1.包的使用 2.static的使用 3. 代码块概念以及分类 面向对象程序三大特性:封装、继承、多态 。而类和对象阶段,主要研究的就是封装特性。何为

    2024年04月17日
    浏览(10)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包