C++类和对象基础知识

C++类和对象系列文章目录

提示:C++类和对象的基础知识点,后续再补充



1.1C和C++中的结构体struct

在 C 语言中,结构体主要用于定义数据类型,主要用来组合不同类型的变量。然而,在 C++ 中,结构体的功能得到了扩展,不仅可以定义变量,还可以定义成员函数。因此,C++ 中的结构体实际上可以视为一种特殊的类。

C vs C++ 中的结构体

  • C 语言

    • 结构体只能用于定义数据类型和变量。
    • 结构体的功能相对简单,主要用于数据的组合。
  • C++ 语言

    • 结构体可以定义变量和成员函数。
    • 支持访问修饰符(如 publicprivate),使得结构体的封装性更强。
    • 可以进行继承和多态,进一步增强了其功能。

因此,虽然 C++ 中的结构体在某些方面与类相似,但由于它们在默认访问控制和用途上的一些区别,仍然可以将其称为结构体。

struct Student
{
	void SteStudentInfo(const char* name,const char* gender,int age)
	{
		strcpy(_name,name);
		strcpy(_gender,gender);
		_age = age;
	}
	void printStudentInfo()
	{
		cout<<_name<<" "<<_gender<<" "<<_age<<endl;
	}
	char _name[20];
	char _gender[3];
	int _age;
}

虽然结构体和类在功能上有很多相似之处,但 C++ 专门定义了 class 来实现面向对象编程,以提供更严格的封装和访问控制。这使得开发者可以更好地管理数据和功能,促进代码的重用和维护。

1.2 类声明和变量分离

person.h 文件中
class Person
{
	public:
		void ShowInfo();
	private:
		char* _name;
		char* _sex;
		int _age;
}
person.cpp文件中
#inclde "person.h"
void Person::showInfo()
{
	cout<<_name<<"_"<<_sex<<"_"<<_age<<_endl;
}

1.3 类的封装

public(公有) 类外也可以访问
protected(保护)和private(私有)类外无法访问

class默认访问权限:私有 private
struct默认访问权限:共有 public 兼容C

1.4 类外使用成员函数 在类外使用::作用域解析符来指明成员属于哪个类

class Person
{
	public:
		void ShowInfo();
	private:
		char* _name;
		char* _sex;
		int _age;
}void Person::showInfo()  //成员函数类外需要指明所属类
{
	cout<<_name<<"_"<<_sex<<"_"<<_age<<_endl;
}

1.5 声明和定义的拓展

声明是一种承诺,其不分配空间;
定义是对象的实例化,分配空间;
声明可以多次,但是定义只能一次
声明可以在多个源文件中,通过#ifndef来解决,而定义只能在一个源文件中。

1.定义的情况
万物皆可以对象
类名 对象名;(数据类型 变量) +函数定义

```cpp
int x;  //变量定义 分配空间
Person p; //对象定义  分配空间

int Add(int x,int y)  //函数定义 分配栈空间
{
	return x+y;
}
1.声明的情况
extern int x;
(extern) int Add(intx,int y);

1.6 对象中只会存储成员变量,而不会存储成员函数。why?

在 C++ 中,一个类可以实例化出多个对象,这些对象的成员函数相同,但它们的成员变量值各异。为了节省空间,成员变量存储在每个对象实例中,而成员函数则存储在公共代码区中。
注意:如果一个空对象有一个字节的空间,why,占位,方便取地址。

1.7 类对象中隐含的this指针

1.this指针指向 谁调用指向谁, this指针是类中的成员函数的第一个参数
2.this指针不能为空,空的this,会导致 this->变量是报错

class Date
{
publicvoid Init(int year,int month,int day)
	{
		_year = year;
		_month =month;
		_day = day; 
	}
	>>>>>>>>>>>>>>>>编译器编译为>>>>>>>>>>>>>>>>>>
	void Init(Data* this,int year,int month,int day)
	{
		this->_year = year;
		this->_month =month;
		this->_day = day; 
	}
	>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
	private:
	int _year;
	int _month;
	int _day;
};
int mian()
{
	Date d1;
	d1.Init(2020,4,7)//
	>>>>>>>>>>>>>>>>编译器编译为>>>>>>>>>>>>>>>>>>
	d1.Init(&d1,2020,4,7);
	>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
}

1.8 构造函数:初始化对象

1.函数名和类名一样
2.无返回值
3.对象实例化时自动调用
4.构造函数可以重载
5.如果未定义构造函数,编译器会自动生成一个无参的默认构造函数
6.默认的析构在定义对象 会随机初始化内置类型的值
对于自定义类型 会调用对于的构造函数

class Time
{
	public: 
	Time() //构造函数   函数名和类名一样  无返回值
	{
		_hour = 0;
		_minute = 0;
		_second =0;
	}
	private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
	void Print()
	{
		cout<<_year<<"_"<<_month<<"_"<<_day<<endl;
	}
	private:
	int _year; 
	int _month;
	int _day;
	Time _t;
};
int main()
{
	Date d1;
	//定义对象d1时,
	对象空间会创建变量int _year   int _month  int _day  这些对调用g++编译器生成的构造函数初始化为随机值
	而创建自定义类型Time _t ,也相当于实例化Time类,会自动调用Time的构造函数,使得_t._hour = 0;_t._minute = 0;_t._second =0;
	
	d1.Print();
	//_year  随机值
	//_month 
	return 0;
}

1.9 无参的构建函数和全缺省构造函数

为了兼容 Data d1;和Data d1(2020,4,6);这二种写法

可以采用函数重载来实现,但需要写二个函数
class Date
{
publicDate(int year,int month,int day)//兼容Data d1(2020,4,6);
	{
		_year = year;
		_month =month;
		_day = day; 
	}
	 Date()//兼容Data d1;
	{
		_year = 0;
		_month =0;
		_day = 0; 
	}
	>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
				采用全缺省构造函数可以合二为一
	Date(int year=0,int month=0,int da=0)//兼容Data d1(2020,4,6);和//Data d1;
	{
		_year = year;
		_month =month;
		_day = day; 
	}
	>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>		
	private:
	int _year;
	int _month;
	int _day;
};

1.10 析构函数

对象什么周期到了,自动调用,用来清理和释放空间资源。
1.函数名为 ~类名
2.无参 无返回值
3.一个类只有一个析构函数
4.未定义,编译器会自动生成一个默认析构函数
5.自定义类型 会调用它自身的构建函数/析构函数

class Stack
{
	~Stack()
	if(_a)
	{
	free(_a);
	_a = nullptr;
	_size = _capacity = 0;
	}
	private:
		int * _a;
		int _size;
		int _capacity;		
	
};

1.11 拷贝构造 :采用同类对象来构造新的对象


注意:使用传值的方式来实现拷贝构造函数会导致无限递归。在创建对象时,会调用一次拷贝构造函数。

因此,应该采用传引用的方式来实现拷贝构造函数。

疑问:如果传的是引用,那 d1d2 不是同一块空间吗?

传递引用d1 作为引用传递给拷贝构造函数,这只是为了访问 d1 的成员,而不是共享内存。

创建副本:拷贝构造函数会创建一个新的对象 d2,并将 d1 的成员值复制到 d2 中。因此,d2d1 是两个不同的对象,存在于不同的内存地址。

在创建对象时,会为 d2 分配一个新的对象空间(深拷贝),而传引用不会再次拷贝。

**总结:**使用传值的方式来实现拷贝构造函数,在创建对象时存在一个调用拷贝构造函数(产生新空间d2),在传参时还有创建一个临时对象tmp,这样起码会有二次深拷贝。

而采用采用传引用的方式来实现拷贝构造函数。只会在创建对象时存在一个调用拷贝构造函数(产生新空间d2),在传参时不在创建临时对象tmp,这样起码就不会有第二次深拷贝。

class Data
{	
	Data(Date d)//存在递归拷贝的问题
	{
		_year =d._year;
		_month = d._month;
		_day = d._day;
	}
	private:
	int _year;
	int _month;
	int _day;
};

Data d2(d1);//或者Data d2=d1;二者等效

关于构造函数Data(Date d)//存在递归拷贝的问题分析:
1.对象初始化时会地调用构造函数,调用之前要传参即将d1传给Data(Date d)中的参数d,由于这个是通过传值的方式传参,会创建一个临时Date tmp=d,会再次调用构造函数,调用之前要传参即将tmp传给Data(Date d),往复调用,无限递归。

无法理解记住即可

在这里插入图片描述

1.12 运算符重载

作用:让自定义类型可以向内存类型一样使用运算符 + == += -=等等

class Data
{	

	bool operator==(const Data& d)//第一个参数是隐藏的,是this指针
	{
		return this->_year==d._year && this->_month==d._month && this->_day==d._day;
	}
	Date(int year,int month,int day)//兼容Data d1(2020,4,6);
	{
		_year = year;
		_month =month;
		_day = day; 
	}
	private:
	int _year;
	int _month;
	int _day;
};

Data d1(2024,4,9);
Data d2(2024,5,6);
d2-d1;//需要自己定义
d2 == d1;//会被编译器编译为 operator==(&d2,d1);第一个参数是this指针,也就是&d2
d1 > d2;

如何实现连续操作符呢 d3=d2=d1
赋值运算先计算最右边,先计算,d2=d1返回需要也是一个对象,这样 (d3==对象)才能相比

********代码在类中*************
Data& operator=(const Data& d)
{
	if(this != &d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	return *this;
}

1.13 const如何修饰this指针

class Data
{	
	void print() const//放在外面修饰里面的this指针
	{
		cout<<_year<<"_"<<_month<<"_"<<_day<<endl;	
	}
	private:
	int _year;
	int _month;
	int _day;
};

1.14 关于类中const权限无法放大的应用

结论:const成员函数内 不能 调用其他非const成员函数
非const成员函数内 可以 调用其他const成员函数


<<<<<<<<<<<<<这些代码在类中>>>>>>>>>>>>>>>>>>>>>>>
void f1()   //void f1(Data* this)
{
	f2();   
//this->f2(this)  权限减小  这里相当于将权限大this传值给下面f2中的权限小的const对象的this
}

void f2() const//void f2(const Data* this)  
{}

void f3() const  //void f1(const Data* this)
{
	f4();   //this->f2(const this)  权限缩小
}

void f4() //void f2(Data* this)  
{}

<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>
int main()
{
	f1();//权限大的调用权限小的const,权限缩小 可以
	f3();//保存  权限小的const调用权限大的,权限放大,不可以
	return 0;
}

1.15 初始化列表 与 成员变量的声明和定义

1.类中的 引用变量成员 const成员 自定义成员(该类有可以不用传参就可以调用的构造函数)必须放在初始化类表中进行初始化。
2.成员变量的初始化顺序由声明顺序相关,而于初始化列表的顺序无关。

class Date
{
	publicDate(int year,int month,int day)
			:_year(year);
			,_month(month)
			,_day(day)
			{
			
			}
}

why?这三种变量必须放在初始化列表中呢?

class B
{
	public:
		B(int a,int ref)//构造函数时,会定义这些成员变量并初始化。因为引用,const变量和自定义函数(没有默认构造函数,它不会初始化) 必须在定义是赋一个初始值。
//默认构造函数指不需要传参的构造函数
>>>>>>>>这里是成员变量定义的地方>>>>>>>>>>>>>>>>>>>>_aobj(a)
		,ref(ref)
		,_n(10)

		{
			
		}
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

	private>>>>>>>>这里是成员变量声明的地方>>>>>>>>>>>>>>>>>>>>
		A _aobj;
		int& _ref;
		const int _n;
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
}

1.16 类中的 static成员变量和static成员函数

static和普通的成员函数和成员变量的区别:

类型存储位置this指针访问方式说明示例
静态成员变量静态区——可通过类名或对象访问不属于任何特定对象,都存在静态区中,所有对象共享同一份数据。Date::currentYear
静态成员函数静态区通常通过类名访问,也可通过对象访问(不推荐)不依赖于具体对象,所以没有this指针,不能访问非静态成员。Date::getCurrentDate()
成员变量对象中——只能通过对象访问每个对象都有自己的一份副本。Date date1; date1.day = 10;
成员函数公共区只能通过对象访问this 指针指向调用该函数的具体对象。date1.printDate();

总结和部分知识点:
1.只有成员函数有this指针,因为只有它是存储在对象中的,(this指针是指向自己所在的对象空间中对象的地址)。
2.成员函数和成员变量只能通过对象来访问
3.静态的成员函数和成员变量 可以通过对象(很少用)类域来访问。
4.静态成员函数因为没有this指针无法访问非静态成员变量
5.静态成员变量定义必须在类外必须进行。其在类中只能声明,无法定义
6.非静态成员变量:在构造函数中定义,在类中的private来声明。
7.类中成员变量没有定义只有什么,是在创建对象时,自动调用构建函数时对成员变量进行定义的。
补充:C++中静态成员变量必须在类外进行定义,主要原因:非静态成员变量放在对象中,而静态成员变量放在静态区中,放在外面定义以确保全局范围内只有一个唯一存储空间。

在C++中,静态成员变量必须在类外进行定义的原因与C++的链接性和内存管理有关。

关键点说明
链接性静态成员变量在类定义中声明时,它属于类的接口部分,但不分配内存。它不是特定于某个对象的,而是所有对象共享的,因此必须在类外定义以确保只分配一次内存。
内存分配静态成员变量在全局数据区分配内存,而不是在每个对象的内存块中分配。这意味着即使创建多个对象,静态成员变量也只有一个实例
初始化时机静态成员变量的初始化在 main 函数执行之前完成,即在程序启动阶段,确保在任何对象创建之前已经初始化并可以使用。
访问控制静态成员变量可以在类外直接通过类名访问,而不需要创建类的实例。这要求它们必须在类外有一个定义,以便编译器生成访问代码。
多重定义问题如果在类内定义静态成员变量,每次创建对象时都可能尝试定义该变量,导致链接错误,因为会有多个定义分配相同的内存空间
编译单元C++ 程序通常由多个编译单元组成,静态成员变量在类外定义确保它们在所有编译单元中唯一,避免重复定义的问题。
class Date {
public:
    static int currentYear; // 静态成员变量
    int day;                // 成员变量  

    static void getCurrentDate() { // 静态成员函数
        <<<<<<无法访问非静态成员>>>>>>>>>
    }

    void printDate() { // 成员函数
        std::cout << "Day: " << day << ", Year: " << currentYear << std::endl;
    }
};

>>>>>>>>初始化静态成员变量在类外必须进行初始化和定义>>>>>>>>>>>>>>>>>
int Date::currentYear = 2024;

// 使用示例
int main() {
    Date date1;
    date1.day = 10; // 设置成员变量
    date1.printDate(); // 调用成员函数
    Date::getCurrentDate(); // 调用静态成员函数
    return 0;
}

拓展问题:
1.静态成员函数可以调用非静态成员函数吗?不行 无 -> 有 无 无法 生有(×)
静态成员函数 无thsi指针, 非静态成员函数 有this指针,

有this指针才能调用非静态成员函数,如果有this指针,编译器会自动将this指针作为第一个参数传给非静态成员函数。

class Data
{	
	void fmon(int month) //void print(Data* this,int month)  
	{
		_month = month;//this->_month = month;
	}
	void fyear(int year)//fun1(Data* this,int year)
	{
		_year=year;  //this->_year=year;
		fmon(5);    //print(this,5)
	}
	private:
	int _year;
	int _month;
	int _day;
};
Data d1;
d1.fyear(2024)// d1.fyear(&d1,2014)     this接收到来自&d1的值(对象地址)

2.静态成员函数可以调用非静态成员函数吗?行
静态成员函数 无thsi指针, 非静态成员函数 有this指针,

1.18 数组对象 Data a[10]; 会调用10次构造函数

这个数组或者说这块连续的空间,会创建10个Data类型的对象,会调用10次构造函数。

1.19 类中声明时给缺省

class Data
{
	public:
		Data()
		{ }
		void Print()
		{
			cout<<_year<"-"<<_month<<"-"<<_day<<endl;
		}
	private:
		//类中变量不是定义,只是声明。定义是在创建对象时,通过自动调用构建定义生成的。
		>>>>>>声明时给缺省值,如果你没有初始时给值,就用缺省值>>>>>>>>>>>>>>>>>>
		>>>>>>注意:静态成员变量声明时不可以给值,它需要在类外单独赋值>>>>>>>>>>>>>>>>>>
		int _year =0;
		int _month = 0;
		int _day = 1;
};

1.20 有元函数

作用:在类外去访问类中的私有(private)和保护(protect)的成员变量
1.使用方法:先在类中声明,再在类外定义
2.有元函数可以访问类中私有和保护成员,但不是类的成员函数。
3.有元函数无this指针。
4.一个函数可以是多个类的有元函数。

class Data
{
	friend void f(Data& f);//先在类中声明
	public:
		Data()
		{ }
		void Print()
		{
			cout<<_year<"-"<<_month<<"-"<<_day<<endl;
		}
	private:
		int _year =0;
		int _month = 0;
		int _day = 1;
};

void  f(Data& d)
{
	d._year = 10;
	cout<<d._year<<endl;
}

有元函数的应用场景,普通的类函数也是在公共区,为什么还有搞一个有元函数呢?
举例

如何<<实现重载呢?,如果 operator<<定义在类中
cout<<d1;//编译器的编译为:operator<<(&cout,d1)
此时第一个参数不是&d1,不是this指针,
需要d1<<cout;  //编译器的编译为:operator<<(&d1,cout)  
但是此种方式和我们的用法习惯区别太大,有没有其他办法呢?
采用第一个参数不是this指针,而有可以使用类中成员变量的函数:有元函数
class Data
{
	friend void operator<<(OStream & out);//先在类中声明
	public:
		Data()
		{ }
		void Print()
		{
			cout<<_year<"-"<<_month<<"-"<<_day<<endl;
		}
	private:
		int _year =0;
		int _month = 0;
		int _day = 1;
};
ostream& operator<<(ostream& out,const Data& d)
{
	out<<d._year<<"/"<<d._month<<"/"<<d._day<<endl;
}

补充:
cin 数据类型为:isteam
cout 数据类型为osteam

1.21 有元类

应用场景:在类中想用其他类的私有和保护成员变量

class Time
{
	>>>>>>先声明>>>>>>>>>>>之后Data类就有使用Time中的私有和保护的成员变量了
	friend class Data;
	public:
		.......
	private:
		int _hour;
		int _minute;
		int _second;
};

class Data
{
	void SetTimeOfData(int hour,int monute,int second)
	{
		_t._hour=hour;
		_t.minute = minute;
		_t.second = second;
	}
	private:
		int _year;
		int _month;
		int _day;
		Time _t;
};

1.22 内存基础知识

在这里插入图片描述
计算机的内存地址和实际硬盘的地址是一一映射的关系
32位计算机 地址为32位,支持最大空间位2^32字节=4G空间
而硬盘有时候不是4G,而是2G呢?课件硬盘是通过挂载的方式,将物理地址和计算机的虚拟4G地址一一来映射的。

1.23 C++的动态内存(堆上空间)管理 new delete

c语言的malloc 申请空间
/calloc 申请空间+初始化
/realloc 对已有的空间进行扩容
/free 不做解释

C++的内存管理方式:new 和delete


int* ptr4 = new int; 申请一个int类型空间
int* ptr5 = new int(10);申请1int类型空间,并初始化为10
int* ptr6 = new int[3] ; 申请数组空间,里面有3int类型

delete ptr4;
delete ptr5;
delete[] ptr6; 数据空间的删除

new delete和malloc free的区别
1.对于内置类型,他们的效果是一样的
2.对于自定义类型(类),c++ 中的new可以申请空间,在申请成功后,会自动调用构造函数进行初始化,delete可以释放空间,同时自动调用析构函数

特性new/delete (C++)malloc/free (C )
内存分配new申请空间并调用构造函数malloc仅申请空间,不初始化
内存释放delete释放内存并调用析构函数free仅释放内存,不进行清理
类型安全类型安全,自动转换指针类型不是类型安全的,需要手动类型转换
返回类型对应类型的指针void*
异常处理处理内存分配失败时抛出异常不抛出异常,返回NULL
重载可以重载全局和类的new/delete不能重载,全局可用

1.24 operator new(不调用构造)和new(调用构造) operator delete(不调用析构) 和delete(调用析构)

malloc 失败返回NULL
operator new >>>> maloc+失败抛出异常
new 1.自动调用构造函数,失败抛出异常

A* p1 = (A*)malloc(sizeof(A));
A* p1 = (A*)operator new(sizeof(A));

size_t size=2;
void* p4 = malloc(size*1024*1024*1204);
cout<<p4<<endl;失败返回0.也就是NULL

void*p5 = operator new(size*1024*1024*1204);
cout<< p5 <<endl;失败抛异常

1.25 A=B的返回值是A

1.26 定位new

场景:对已存在的一块空间上调用构造函数来初始化
语法:new(空间指针)类名(参数);

int mian()
{
	A*p1 = new A;
	A* p2 = (A*)operator new(sizeof(A));
	
	new(p2)A(10);//手动调用构造
	p2->~A();//手动定义析构
	operator delete(p2);
}

1.27 模版template 兼容任何类型的代码 函数模版

void Swap(int& a,int& b)
{	
	int temp = a;
	a = b;
	b = temp;
}
void Swap(double& a,double& b)
{	
	double temp = a;
	a = b;
	b = temp;
}
void Swap(char& a,char& b)
{	
	char temp = a;
	a = b;
	b = temp;
}

代码重复过高,有没有采用一套代码兼容不同的数据类型呢?采用模版编程

template<class T>
void Swap(T& x1,T& x2)
{	
	T x = x1;
	x1 = x2;
	x2 = x;
}
int main()
{
	int a = 0 ,b = 1;
	Swap(a, b);
	double c=1.11, d = 2.22;
	Swap(c, d); 
	return 0;
}

模版原理:
在这里插入图片描述

1.28 类模版

c语言和c++的面向对象区别
在这里插入图片描述

c语言实现的stack

#include <iostream>
#include <cstdio>
typedef int STDateType;
typedef struct Stack_C
{
	STDateType *a;
	int _size;
	int _capacity;
}Stack_C;

void Stack_CInit(Stack_c *ps);
void Stack_CDestory(Stack_c *ps);
void Stack_cpush(Stack_c *ps,STDateType x);
void stack_cPop(Stack_c *ps);
int mian()
{
	Stack_C st_c;
	Stack_CInit(&st_c);
	Stack_Cpush(&st_c,1);
	Stack_Cpush(&st_c,2);
	Stack_CPop(&st_c);
	Stack_CDestory(&st_c);
}

模版+类的实现 stack

#include <iostream>
#include <cstdio>
using namepace std;
template <class T>
class Stack_Cpp
{
	public:
	Stack_CPP()
	{}
	~stack_CPP()
	{}
	void Push(T x);
	void Pop();
	ptivate:
	T* _a;
	int _size;
	int _capacity;
}
int main()
{
	Stack_CPP<int> st_cpp_int;
	st_cpp_int.Push(1);
	st_cpp_int.push(2.1); 2.1会被强制类型转换为2
	st_cpp_int.pop();
	
	Stack_CPP<double> st_cpp_double;
	st_cpp_double.Push(1.1);
	st_cpp_double.push(2);2会被强制类型转换为2.0
	st_cpp_double.pop();
}

1.29 模版底层原理

在这里插入图片描述

1.30 匿名对象 对象名();

class Solution 
{	
	void fun(int n)
	{
		cout<<n<<endl;
		retrun n;
	}
};
Solution s1;  s1的什名周期main函数中,mian函数结束时,才会调用析构函数
s1.fun(10);
Solution()    匿名对象,生命周期就在这一行,出了这一行自动调用析构函数
Solution().fun(10);  
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值