类和对象(上)

类与对象(上)

本节内容
  • 类与对象的初步认知
  • 类的引入
  • 类的定义
  • 类的作用域
  • 类的实例化
  • 类的访问限定符及封装
  • 类的对象大小的计算
  • 类成员函数的this指针
类与对象的初步认知
  • 类:类是现实世界在计算机中的反映,它将数据和对这些数据的操作封装在一起(并没有开空间)
    在这里插入图片描述
  • 对象:类的实例(占有实际的空间)
  • 类是对象的抽象,对象是类的实例
  • 类是一类事物的描述,是抽象的(手机设计图)
  • 对象是一类事物的实例,是具体的(真正的手机)
类的引入
C语言中,结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。
#include<iostream>
using namespace std;
struct Student
{
	char name[20];
	int age;
	char gender;
};
//再C语言中的结构体,是只能放数据,不能放函数的,如果再C语言的前提下
//给结构体中放函数,那么编译的时候是会出错的,之能放数据
//因为C语言是面向过程的,数据和方法是分离开的
//但是在C++中,struct声明的结构体,不仅可以放数据还是可以放函数的
//也就是说C++把这些数据和操作这些数据的方法绑在一起了
int main()
{
	return 0;
}
#include<iostream>
#include<string>
using namespace std;
struct Student
{
	void InitStudent(const char* name, int age, const char* gender)
	{
		strcpy(_name, name);
		_age = age;
		strcpy(_gender, gender);
	}

	void PrintStudnet()
	{
		cout << _name << "-" << _age << "-" << _gender << endl;
	}

	char _name[20];
	int _age;
	char _gender[3];
};
int main()
{
	Student s1, s2, s3;  //学生对象(学生实体)
	s1.InitStudent("wq", 35, "男");
	s1.PrintStudnet();

	s2.InitStudent("ouhou", 5, "女");
	s2.PrintStudnet();

	s3.InitStudent("aha", 15, "男");
	s3.PrintStudnet();

	return 0;
}

上面的代码,在C++的方法下,是完全可以通过编译的,上面结构体的定义,在C++中更喜欢用class来代替

类的定义
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
  • class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号。类中的元素称为类的成员:类中的数据称为类的属性或者成员变量;类中的函数称为类的方法或者成员函数。
类的两种定义方式
  • 方式一:声明和定义全部放在类中(声明和定义全部放在类体中,需要注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理)
class Student
{
	//成员函数(也成为行为)
	void InitStudent(const char* name, int age, const char* gender)
	{
		strcpy(_name, name);
		_age = age;
		strcpy(_gender, gender);
	}

	void PrintStudnet()
	{
		cout << _name << "-" << _age << "-" << _gender << endl;
	}

	//数据(也成为属性)
	char _name[20];
	int _age;
	char _gender[3];
};
  • 方式二:声明放在.h文件中,类的定义放在.cpp文件中
//person.h

#pragma once 
class Person
{
	void InitPerson(char* name, char* gender, int age);
	void PrintPerson();

	char name[20];
	char gender[3];
	int age;
};
//Person.cpp

#include"Person.h"

#include<string.h>
#include<iostream>
using namespace std;

void Person::InitPerson(char* name, char* gender, int age)
{
	strcpy_s(_name, name);
	_age = age;
	strcpy_s(_gender, gender);
}
void Person::PrintPerson()
{
	cout << _name << "-" << _age << "-" << _gender << endl;
}
一般情况下,更期望采用第二种方式。

类的访问限定符及封装

  • 封装:隐藏内部的实现细节,暴露一些公有的方法与其他对象进行交互
  • C++是如何实现封装的?—用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用
  • 访问限定符限制的是类外的成员
#include<iostream>
#include<string>
using namespace std;
class Student
{
   //成员函数(也成为行为)
public:
   void InitStudent(const char* name, int age, const char* gender)
   {
   	strcpy(_name, name);
   	_age = age;
   	strcpy(_gender, gender);
   }

   void PrintStudnet()
   {
   	cout << _name << "-" << _age << "-" << _gender << endl;
   }
private:
   //数据(也成为属性)
   char _name[20];
   int _age;
   char _gender[3];
};
int main()
{
   Student s1, s2, s3;  //学生对象(学生实体)
   s1.InitStudent("wq", 35, "男");
   s1.PrintStudnet();

   s2.InitStudent("nana", 5, "女");
   s2.PrintStudnet();

   s3.InitStudent("aha", 15, "男");
   s3.PrintStudnet();

   return 0;
}
访问限定符说明
  • 访问限制符是限制类成员能否直接在类外进行访问的
  • public修饰的成员在类外可以直接被访问
  • protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
  • 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
  • class的默认访问权限为private,struct为public(因为struct要兼容C
  • 注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别(也就是说,当变量已经加载到内存中的时候,各个变量也就没有访问限定符的限制了,也可以理解成,访问限定符就是在编译阶段给编译器看的)
C++中struct和class的区别是什么?
  • 在C++中,class和struct做类型定义是只有两点区别:
  • (1)C++需要兼容C语言,所以C++中struct可以当成结构体去使用。另外C++中struct还可以用来定义类。和class是定义类是一样的,区别是struct的成员默认访问方式是public,class成员默认访问方式是private。(两者的默认访问权限是不一样的)
  • (2) class还可用于定义模板参数,像typename,但是关键字struct不能同于定义模板参数
  • (摘抄)
    1、class是引用类型,struct是值类型;
    2、class可以继承类、接口和被继承,struct只能继承接口,不能被继承;
    3、class有默认的无参构造函数,有析构函数,struct没有默认的无参构造函数,且只能声明有参的构造函数,没有析构函数;
    4、class可以使用abstract和sealed,有protected修饰符,struct不可以用abstract和sealed,没有protected修饰符;
    5、class必须使用new初始化,结构可以不用new初始化;
    6、class实例由垃圾回收机制来保证内存的回收处理,而struct变量使用完后立即自动解除内存分配;
    7、从职能观点来看,class表现为行为,而struct常用于存储数据;
    8、作为参数传递时,class变量以按址方式传递,而struct变量是以按值方式传递的。
类的作用域
  • 类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员,需要使用 :: 作用域解析符指明成员属于哪个类域。
class Person
{
public:
    void PrintPersonInfo();
private:
    char _name[20];
    char _gender[3];
    int _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
    cout<<_name<<" "_gender<<" "<<_age<<endl;
}
类的实例化
  • 用类类型创建对象的过程,称为类的实例化
  • 类只是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它
  • 一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量
  • 做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间
类对象模型
7.1 如何计算类对象的大小
7.2 类对象的存储方式猜测
  • 对象中包含类的各个成员—缺陷:每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间。那么如何解决呢?
  • 只保存成员变量,成员函数存放在公共的代码段
  • 问题:对于上述两种存储方式,那计算机到底是按照那种方式来存储的?
class A1 
{
public:
	void f1() 
	{

	}
private:
	int _a;
};
// 类中仅有成员函数
class A2 
{
public:
	void f2() 
	{

	}
};
// 类中什么都没有---空类
class A3
{

};
  • 上述三个类的大小分别为4,1,1
  • 为什么空类占用一个字节而不是零个字节呢?设想一下,假设现在需要用空类去实例化对象,假设空类占零个字节的话,那么空类所实例化的对象都处在同一个位置处,相当于实例化的不同对象成为了同一个对象,这显然是不对的,所以说,空类所占的大小是1个字节,但也不是在所有的编译器里面都是1,所以只能说,在主流的编译器中,空类的大小是1个字节
结论:一个类的大小,实际就是该类中”成员变量”之和,当然也要进行内存对齐,注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类
结构体内存对齐规则
  • 第一个成员在与结构体偏移量为0的地址处。
  • 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。注意:对齐数=编译器默认的一个对齐数与该成员大小的较小值。VS中默认的对齐数为8,gcc中的对齐数为4
  • 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
  • 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
this指针
this指针的引出
  • 我们先来定义一个日期类Date
#include<iostream>
#include<string.h>
using namespace std;
class Date
{
public:
	void Display()
	{
			cout << _year << "-" << _month << "-" << _day << endl;
	}
	void SetDate(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
int main()
{
	Date d1, d2;
	d1.SetDate(2018, 5, 1);
	d2.SetDate(2018, 7, 1);
	d1.Display();
	d2.Display();
	return 0;
}
  • 对于上述类,有这样的一个问题:
  • Date类中有SetDate与Display两个成员函数,函数体中没有关于不同对象的区分,那当s1调用SetDate函数时,该函数是如何知道应该设置s1对象,而不是设置s2对象呢?
  • C++中通过引入this指针解决该问题,即:C++编译器给每个“成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
class Student
{
public:
	//this:  Student* const   这是this指针的类型,虽然this指向的内容是可以修改的,但是this本身是不可以被修改的
	void InitStudent(char* _name, int age, char* gender)
	{
		//this = nullptr;
		strcpy(this->_name, _name);
		_age = age;
		strcpy(_gender, gender);
	}

	void PrintStudnet()
	{
		cout << _name << "-" << _age << "-" << _gender << endl;
	}

private:
	char _name[20];
	int _age;
	char _gender[3];
};
int main()
{
	Student s1;
	s1.InitStudent("Peter", 35, "男");
	s1.PrintStudnet();

	Student s2;
	s2.InitStudent("jingjing", 36, "女");
	s2.PrintStudnet();
	return 0;
}
  • this指针类似于C语言中的结构体
  • 用C语言进行模拟
//用C语言的方式进行模拟
#include<iostream>
#include<string.h>
using namespace std;
struct Student
{
	char _name[20];
	int _age;
	char _gender[3];
};

void StudentInit(Student* ps, char* name, int age, char* gender)
{
	strcpy(ps->_name, name);
	ps->_age = age;
	strcpy(ps->_gender, gender);
}

void PrintStudent(Student* ps)
{
	printf("%s %d %s", ps->_name, ps->_age, ps->_gender);
}

int main()
{
	return 0;
}
struct Student
{
	char _name[20];
	int _age;
	char _gender[3];
};

void InitStudent(Student* this, char* name, int age, char* gender)
{
	strcpy(this->_name, name);
	this->_age = age;
	strcpy(this->_gender, gender);
}

void PrintStudent(Student* this)
{
	printf("%s %d %s", this->_name, this->_age, this->_gender);
}

int main()
{
	return 0;
}
/*
大致过程:
1. 识别类名
2. 识别类中成员变量
3. 识别类中的成员函数&改写
*/

#if 0
class Student
{
public:
	/*
	void InitStudent(Student* const this, char* name, int age, char* gender)
	{    看起来成员函数有三个参数,实际上是有四个参数的,因为还有一个this指针
	    strcpy(this->_name, name);
	    this->_age = age;
	    strcpy(this->_gender, gender);
	}
	*/
	void InitStudent(char* _name, int age, char* gender);
// 	{
// 		strcpy(this->_name, _name);    在编译器中实际的样子
// 		_age = age;  
// 		strcpy(_gender, gender);
// 	}

	void TestFunc(...);
// 	{
// 	}

	/*
	// this是编译器自己维护(不是由我们来维护的)
	void PrintStudnet(Student* const this)
	{
	    cout << this->_name << "-" << this->_age << "-" << this->_gender << endl;
	}
	*/
	void PrintStudnet()
	{
		cout << _name << "-" << _age << "-" << _gender << endl;
	}

private:
	char _name[20];
	int _age;
	char _gender[3];
};
int main()
{
	Student s1;
	s1.TestFunc();
	s1.TestFunc(10);
	s1.TestFunc(10, 20);
	// Student::InitStudent(&s1, "Peter", 35, "男")
	s1.InitStudent("Peter", 35, "男");
	// Student::PrintStudnet(&s1);
	s1.PrintStudnet();

	Student s2;
	s2.InitStudent("jingjing", 36, "女");
	s2.PrintStudnet();
	return 0;
}
this指针的特性
  • this指针的类型:类类型* const
  • 只能在“成员函数”的内部使用
  • this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
  • this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
    在这里插入图片描述
高频题目
this指针可以为空吗
  • 首先我们知道,this指针指向的是当前对象
class A
{
public:
	void TestFunc1()
	{
		cout << this << endl;
		this->_a = 20;   //如果没有这句话的话,下面的代码是可以正常通过编译的
		cout << "TestFunc()" << endl;
	}

	void TestFunc2()
	{
		this->_a = 10;
	}

protected:
	int _a;
};


int g_a = 10;

int main()
{
	cout << g_a << endl;
	A* ps = nullptr;
	ps->TestFunc1();// TestFunc1(ps)
	return 0;
}

在这里插入图片描述

  • 得出结论,this指针是可以为空的,而且在某些情况下,this指针为空的时候,编译器并不会报错,只有在函数体内去访问变量的时候,this指针为空才会报错,从而导致代码的崩溃
    在这里插入图片描述
  • 可以为空,当我们在调用函数的时候,如果函数内部并不需要使用到this,也就是不需要通过this指向当前对象并对其进行操作时才可以为空(当我们在其中什么都不放或者在里面随便打印一个字符串),如果调用的函数需要指向当前对象,并进行操作,则会发生错误(空指针引用)就跟C中一样不能进行空指针的引用
this指针可以为空
/*
this是一个指针,里面放置的是当前对象的地址
当前对象指的是成员函数执行时,调用成员函数的对象
对成员变量的操作都是通过this指针来实现的

总结:什么是this指针,this指针是成员函数第一个隐藏的参数
该指针时时刻刻指向调用成员函数的对象(也就是当前对象)
也就是说this指针指向当前正在调用这个函数的对象
this指针只能在成员函数里面使用
this指针的类型是类类型的*const this
this指针没有存储在对象中,不会影响对象的大小
只是指向当前对象仅此而已
this指针主要是通过ecx寄存器来传递(并不是全部通过ecx寄存器传递)
this指针可以为空 ---当以对象.成员函数()时,this指针不可以为空
因为对象是可以为空的,此时this指针指向的是调用当前函数的对象
当以p->成员函数()时,是将p作为参数传递给成员函数了,如果p指向
的是空,那么将来的this指针其实就是空
所以this指针其实是可以为空的

另一个问题,如果this指针为空,成员函数能不能被正常调用--不能
因为现在this指针时空的,也就是说连空间都没有
那么既然连空间都没有的话,那么肯定就是不能去访问变量的
如果不访问变量的话,其实还是可以通过编译的
*/

this指针存在在哪里
class Test
{
public:
	void TestFunc()
	{
		//cout << &this << endl;
		Test* const& p = this;   //这句话就相当于是在打印this指针的地址
		//因为this指针不能直接打印地址,因为this指针是一个常量,所以打印
		//this指针别名的地址
		cout << &p << endl;
	}

public:
	int _t;
};

int main()
{
	int* p = nullptr;   //给成空
	int*& q = p;

	cout << &q << endl;
	cout << &p<< endl;
	//打印出来之后发现两个变量的地址是一样的,p和q的地址相同

	Test t;
	t.TestFunc();   //通过对象去调用函数

	return 0;
}
//然后之后去监视ebp和esp两个寄存器的地址,发现this的地址刚好在ebp和esp
//两个寄存器的地址之间
  • 所以得出结论,this指针在栈上
举个例子
class test{
    public:
    static void f1(){cout<<y<<endl;}
    void f2(){cout<<y<<endl;}
    void f3(){cout<<x<<endl;}
    void f4(int p){cout<<p<<endl;}
    int x;
    static int y;
};
int test::y=1;
int main()
{
    test* p=nullptr;
    p->f1();
    p->f2();
    p->f3();
    p->f4(5);
    getchar();
    return 0;
}
结果:
  • f1,f2,f4都会调用成功,f3调用失败。
原因
  • p为null,所以这个类的this指针为null。调用非静态成员函数时,编译器会默认把this指针作为第一个参数!
  • f1本身为静态成员函数,调用静态成员变量y,不需要this指针,成功。
  • f2是普通成员函数,也可以调用静态成员变量y,不需要this指针,成功。
  • f3调用了普通成员变量,这时需要this指针了,但this为null,所以相当于调用cout<x<<endl;这显然错误!
  • f4也不需要this指针,成功。
另外两个相关知识点:
  • 静态成员函数只能调用静态成员变量,普通成员函数可以调用静态成员变量。
  • 静态成员变量要在类内声明,类外初始化。
/*
C语言是面向过程的,C++是基于面向对象的(既有面向过程,又有面向过程)
因为C++是基于C语言的。Java是纯面向对象
面向过程和面向对象不是一门编程语言,而是解决问题的思路
面向过程就是讲解决一个问题的众多步骤按部就班的依次执行下去
通过函数之间的调用关系把想要完成的事情最终完成、
面向对象更加注重的是对象
如果想要去完成一件事情的话,首先需要知道完成这个事情都需要有哪些实体
知道了有那些实体之后,又要去了解每个实体都是干什么事情的
当每个实体把他们各自所需要完成的事情完成之后,那么这件事情才是完成了
面向对象更加侧重对象 面向过程更加侧重过程

C++想要实现面向对象的编程思想的话,class定义类是非常关键的一个步骤
C语言中可以使用struct来自定义一种新的类型
但是struct中不可以存放函数

*/
#if 0
//下面的代码在C语言中是无法通过编译的
//在C++中就是可以通过编译的
struct Student
{
	char name[20];
	int age;
	float score;
	int Add(int x, int y)
	{
		return x + y;
	}
};
int main()
{

	return 0;
}
#endif 


#if 0
//在C++中,struct定义出来的类型就可以看作是一个类了
//C语言中的结构体在C++中变成了类
//但是在C++中如果想定义类,一般来说,更喜欢使用class来定义类
#include<iostream>
#include<string>
using namespace std;
struct Student
{
	//结构体中的变量
	char _name[20];
	char _gender[3];
	int _age;
	
	//结构体中的函数
	//进行初始化的函数
	void InitStudent(const char name[], const char gender[], int age)
	{
		strcpy(_name, name);
		strcpy(_gender, gender);
		_age = age;
	}

	//进行打印的函数
	void Print()
	{
		cout << _name << "-" << _gender << "-" << _age << endl;
	}
};
int main()
{
	Student s1;
	s1.InitStudent("zhangsan", "男", 20);

	s1.Print();

	return 0;
}
#endif


#if 0
/*
类就是对对象来进行描述的
类的定义方式有两种
一种是声明和定义放在一起,另一种是声明和定义分开放
下面是声明和定义放在一起格式
*/
#include<iostream>
#include<string>
using namespace std;
class Student
{
public:
	//成员变量
	char _name[20];
	char _gender[3];
	int _age;

	//成员函数如果在类内定义,成员函数可能会被看成内联函数来对待
	void InitStudent(const char name[], const char gender[], int age)
	{
		strcpy(_name, name);
		strcpy(_gender, gender);
		_age = age;
	}

	void Print()
	{
		cout << _name << "-" << _gender << "-" << _age << endl;
	}
};
int main()
{
	Student s1;
	s1.InitStudent("zhangsan", "男", 20);

	s1.Print();

	return 0;
}
/*
类的定义方式还有第二种,第二种其实就是生命和定义分开来放的定义方式
但是如果使用这种方式来进行定义的话,其实就有一个需要注意的点
成员函数如果在类外来进行定义的话成员函数名字前面必须要加上
类名和作用域运算符,表明这个函数是哪个类里面的函数
*/
#endif

/*
面向对象三大特性:封装,继承,多态
类和对象模块主要处理的特性就是封装特性
那么,什么是封装?
封装其实就是将数据和操作数据的方法进行有机的结合,隐藏其内部
实现的细节,只暴露一些接口在外面,从而实现和外界的数据交互
访问限定符限制的是类外的东西能否在类外直接访问类内的成员变量
和类内的成员函数
struct定义的类默认的访问权限是公有的  public
class定义的类默认的访问权限是私有的 private
一般情况下数据给私有函数给成共有的

*/


/*
对象模型--对象在内存中的布局方式
*/


/*
如何计算一个类的大小,如果知道对象中都包含哪些内容的话
就可以求出类的大小,或者类实例化对象的大小
通过对象打点的内容,就可以发现类对象中都有哪些内容
里可以看到变量和方法
不管创建几个对象,对象中的内容都是一模一样的,不会有任何的差异
但是通过对代码的调试发现创建的不同的三个对象是调用的
都是同一个方法
如果每个对象都存储相同的方法的话,会比较浪费空间的
所以说方法只需要存储一份就可以了,就没有那么浪费空间了
将每个对象中存储的相同的方法来进行单独的存储也就是说
每个对象共享同一份方法
但是出现了一个新的问题:
对象的数据和方法分离开了,当对象使用成员函数时,如何知道成员函数在哪
那么就想出来了一个方法,给对象里面放了一个指针,让指针指向
函数所在的位置,然后就可以找到相应的函数的位置了
那么对象的大小就变成了成员变量的大小+指针的大小
但是上面的对象的大小组成其实也是一种推断
事实并不一定就是所有变量的大小+一个指针的大小,要通过调试看最终对象大小的组成
通过调试可以看出对象的大小只包括对象中所有成员变量的大小
并不包括所想的还有一个指针的大小
*/


/*
那么,现在要计算一个类的大小或者一个类的大小
只需要将类中的成员变量加起来然后注意内存对齐其实就是完全可以的了
成员函数单独进行存储,当程序运行起来之后,成员函数存储在代码段中
*/



/*
类A的大小是4个字节,类B的大小时1个字节,类C时空类
空类的大小是1,需要注意一下空类的大小
*/

#if 0
#include<iostream>
using namespace std;
class A
{
public:
	void SetA(int a)
	{
		_a = a;
	}
private:
	int _a;
};
class B
{
public:
	void TestB()
	{

	}
};
class C
{

};
int main()
{
	return 0;
}
#endif

/*
this是一个指针,里面放置的是当前对象的地址
当前对象指的是成员函数执行时,调用成员函数的对象
对成员变量的操作都是通过this指针来实现的

总结:什么是this指针,this指针是成员函数第一个隐藏的参数
该指针时时刻刻指向调用成员函数的对象(也就是当前对象)
也就是说this指针指向当前正在调用这个函数的对象
this指针只能在成员函数里面使用
this指针的类型是类类型的*const this
this指针没有存储在对象中,不会影响对象的大小
只是指向当前对象仅此而已
this指针主要是通过ecx寄存器来传递(并不是全部通过ecx寄存器传递)
*/
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值