C语言指针——初步认识

引言

  指针是C语言中一种重要的数据类型,它保存了一个变量或对象的内存地址。通过使用指针,程序可以直接访问和修改内存中的数据,从而实现高效的数据结构和算法。指针的灵活性为C语言带来了很多优势和便利,比如动态内存分配、数组遍历和函数参数传递等。正因为指针在内存管理和数据结构方面具有出色的表现,所以被称为C语言的精髓之一
  本篇文章对C语言指针的基本概念、语法、运算符、算术运算、种类以及应用做了初步的讲解。

一、指针的基本概念和语法

  当定义一个变量时,系统会为其分配一块内存区域以保存该变量的值。C语言中指针同样是变量,但它的值却不是保存普通数值,而是保存其他变量或对象的内存地址。通过指针变量所保存的地址,我们可以方便地访问和修改变量的值,也可以用来动态分配内存。

1.指针的定义

  在C语言中,指针变量的定义方式与普通变量类似,只需在变量名前加上"*"类型说明符即可。

  例子:

int a;  // 定义一个普通的int整型变量a
int *p;  // 定义一个保存int整型变量地址的指针变量p

  这里我们定义了两个变量a和p,其中p是一个int类型的指针变量,这也就意味着p变量只能存储整型空间的地址,而不能存储其他类型的地址。

2.指针的初始化

  在定义指针变量时,我们可以使用"&"符号取得变量的地址,对指针变量进行初始化。

  例子:

int a= 10;  // 定义一个普通的int整型变量a,并初始化为10
int *p= &a;  // 定义一个保存int整型变量地址的指针变量p,并进行初始化指向变量a的地址

  这里我们定义了一个整型变量a,并将其地址赋值给指针变量p。这样,p就指向了a的内存地址。

  需要注意的是,指针变量必须初始化为一个合法的地址值,否则会出现运行时错误。

3.指针的引用

  指针引用是指使用指针来访问所指向的变量或对象。也就是当我们想要访问指针所指向的内存时,可以使用“*”运算符来获取指针所指向的内存中存储的值。例如:

int a = 10;  // 定义一个普通的int整型变量a,并初始化
int *p = &a;  // 定义一个指向a的指针变量p
int b = *p;  // 使用指针引用操作获取a的值

  这里我们定义了一个整型变量a,并将其地址赋值给指针变量p。然后使用指针引用操作获取a的值,并将其赋值给另一个整型变量b。

4.指针的解引用

  指针解引用是指使用指针来修改所指向的变量或对象。也就是还可以使用“*”运算符来修改指针所指向的内存中的值。

  例子:

int a = 10;  // 定义一个普通的int整型变量a,并初始化
int *p = &a;  // 定义一个指向a的指针变量p
*p = 20;  // 使用指针解引用操作修改a的值

  这里我们定义了一个整型变量a和一个指向a的指针变量p。然后使用指针解引用操作将a的值修改为20。

5.空指针和野指针

  指针变量在没有被初始化或赋值之前,其值是不确定的,也就是说它可能会指向一个无效的内存地址。这种指向无效内存地址的指针称为野指针(Dangling Pointer)。为了避免出现野指针,我们可以将指针初始化为一个空指针,即指针初始化为NULL。

  例子:

int *ptr = NULL;

  这里我们定义指针变量“ptr”且初始化为NULL,表示当前没有指向任何有效内存地址。

6.指针的常量性

  指针本身也可以被定义为常量,也就是我们所说的常量指针(后续有介绍),以防止指针指向的内存区域被修改。

  例子:

const int *ptr;

  这里我们定义指针变量“ptr”且把它声明为一个指向常量整型的指针,表示指针所指向的内存区域不能被修改。

7.指针的指向关系

  在C语言中,指针可以指向多种类型的数据,例如整型、字符型、浮点型等。同时,指针也可以指向数组、结构体、函数等。而且通过改变指针所指向的数据类型,可以实现对不同类型数据的访问和操作。因此指针的种类也多种多样。

二、指针运算符

  在C语言中,*和&是两种重要的运算符,分别表示取值运算符(间接运算符)和取地址运算符。

1.取值运算符(间接运算符) *

  若星号(*)出现在指针变量定义时,是类型说明符
  而出现在某条语句的某个指针变量的前面,这时用于间接引用指针变量中存储的地址,获取该地址上存储的数据值。例如,如果a是一个指向整数类型的指针变量,则表达式 *a 表示访问a所指向的地址中存储的整数值。因此,*通常被称为“取值运算符”。

  例子1:

int a = 10;
int *p = &a;  //这里的*运算符为类型说明符
printf("%d," *p);  // 输出10

  这里我们定义了一个整型变量a,并将其地址赋值给指针变量p。然后,我们使用取值运算符*来访问p所指向的内存地址中存储的值,即输出a的值10。

  例子2:

int a = 10;
int *p = &a;  //这里的*运算符为类型说明符
*p = 20;
printf("%d," a);  // 输出20

  这里我们同样定义了一个整型变量a,并将其地址赋值给指针变量p。然后,我们使用取值运算符*来修改p所指向的内存地址中存储的值,即将a的值修改为20。最后,我们输出a的值,发现已经变成了20。

2.取地址运算符 &

  它返回操作数(变量)的内存地址。例如,如果a是一个整数变量,则&a表示a在内存中的地址。因此,&通常被称为“取地址运算符”。

  例子1:

int a = 10;
int *p = &a;
printf("%p", &a);  // 输出a的内存地址

  这里我们定义了一个整型变量a,并将其地址赋值给指针变量p。然后,我们使用取地址运算符&来获取a的内存地址,并输出它。

  例子2:

int a = 10;
int *p = &a;
printf("%p", p);  // 输出p所指向的内存地址

  这里我们同样定义了一个整型变量a,并将其地址赋值给指针变量p。然后,我们直接输出p的值。

三、指针的算术运算

  在C语言中,指针可以进行算术运算,包括加、减运算和比较等。指针的算术运算非常灵活,它可以用于数组遍历、内存管理等多种场景。

1.指针加减运算

  指针加减运算的结果是一个指针变量。例如,给定一个指向某个内存地址的指针P,那么表达式 P+1 的结果就是指向 P 所指向内存地址后面一个地址的指针

  这个算术运算通常用于处理数组。我们知道,C语言中的数组名本身就是一个指针,指向数组第一个元素的地址。
  因此,对于数组a,a+1等价于&a[1],即指向数组第二个元素的指针。同样,a+i等价于&a[i],表示指向数组第i+1个元素的指针。

  例子1:

int arr[] = {1, 2, 3, 4};
int *p = arr; // 指向数组首元素的指针,等价于 p=&arr[0]

printf("%d\n", *(p + 1));  // 输出2
printf("%d\n", *(p + 2));  // 输出3

  这里我们首先定义了一个整型数组arr,然后定义了一个指向数组首元素的指针p。使用“*p”可以访问指针指向的元素。
  接着,使用“p + 1”将指针增加一个单位,即指向数组第二个元素,并输出该元素的值。再次使用“p + 2”将指针增加两个单位,即指向数组第三个元素,并输出该元素的值。

  例子2:

int arr[] = {1, 2, 3, 4};
int *p1 = &arr[0];  // 指向数组首元素的指针
int *p2 = &arr[3];  // 指向数组最后一个元素的指针

printf("%d\n", p2 - p1);  // 输出3

  这里我们首先定义了一个整型数组arr,然后分别定义了指向数组首元素和最后一个元素的两个指针变量p1和p2。
  使用“p2 - p1”可以计算它们之间的距离,即输出数组中元素的个数。

2.指针自增自减运算

  指针自增和自减运算是指对指针变量进行加1或减1的操作。这里的加1和减1并不是简单的数值加减,而是根据指针类型的不同进行的位移操作

  指针自增运算符将指针变量的值增加指针类型所占的字节数。例如,对于一个int类型的指针变量p,执行p++操作将使p的值增加4个字节(因为int类型占4个字节)。
  指针自减运算符同理,将指针变量的值减少指针类型所占的字节数。

  指针自增运算符和指针自减运算符如下:

指针自增运算符:++
指针自减运算符:--

  它们可以用来移动指针所指向的内存地址,从而实现对数据结构的遍历、访问等操作。

  例子1:

int arr[] = {1, 2, 3, 4};
int *p = arr;  // 指向数组首元素的指针
printf("%d\n", *p);  // 输出1
p++;  // 指向下一个元素
printf("%d\n", *p);  // 输出2

  这里我们首先定义了一个整型数组arr,然后定义了一个指向数组首元素的指针p。使用“p”可以访问指针指向的元素。
  接着,使用“p++”将指针向后移动一个单位,即指向数组第二个元素。最后,再次输出p的值,即输出2。

  例子2:

int arr[] = {1, 2, 3, 4};
int *p = &arr[3];  // 指向数组最后一个元素的指针
printf("%d\n", *p);  // 输出4
p--;  // 指向上一个元素
printf("%d\n", *p);  // 输出3

  这里我们首先定义了一个整型数组arr,然后定义了一个指向数组最后一个元素的指针p。使用“p”可以访问指针指向的元素。
  接着,使用“p--”将指针向前移动一个单位,即指向数组第三个元素。最后,再次输出p的值,即输出3。

3.指针比较

  指针比较是指比较两个指针变量的值。指针变量存储的是内存地址,因此指针比较实际上是比较两个内存地址的大小关系

  指针比较有以下几种情况:
  (1)相等比较:当两个指针变量指向同一个内存地址时,它们的值相等。可以使用“==”运算符进行比较。
  (2)大小比较:当两个指针变量指向不同的内存地址时,它们的值大小关系取决于它们所指向的内存地址的大小关系。可以使用“>”、“<”、“>=”、“<=”运算符进行比较。
  (3)无法比较:当两个指针变量指向不同的内存区域时,它们的值大小关系是未定义的,因此不能进行比较。例如,int类型指针和char类型指针,不同类型的指针所指向的内存地址是不同的,因此它们之间是无法进行比较的。

  例子:

int a[] = {10, 20, 30};
int *p = a;
int *q = &a[2];

if (p == q) {
    printf("p and q point to the same address.\n");  // 不会输出
}

if (p < q) {
    printf("p points to an element before q.\n");  // 输出:p points to an element before q.
}

  在上面的代码中,我们声明了一个整型数组a,并用指针变量p和q分别指向数组第一个元素和最后一个元素。然后我们对p和q进行了比较运算,输出结果如注释所示。

  需要注意的是,指针变量也可以与NULL(或0)进行比较,用于判断指针是否为空。如果指针变量p的值为NULL,则表示它不指向任何有效的内存地址,否则表示它指向一个有效的地址。例如:

int *p = NULL;

if (p == NULL) {
    printf("The pointer points to NULL.\n");  // 输出:The pointer points to NULL.
}

if (p != NULL) {
    printf("The pointer is not NULL.\n");
}

4.数组遍历

  在C语言中,通过指针在数组中进行遍历非常高效。我们可以定义一个指针变量来指向数组的首地址,然后通过不断地指针加减运算来依次访问数组中的元素。

  例子:

/*演示怎样通过指针来遍历数组*/
int a[] = {10, 20, 30};
int* p = a;
for (int i = 0; i < 3; i++) {
    printf("%d ", *p);
    p++;
}
// 输出:10 20 30

  这里我们将数组a的首地址赋给指针变量p,然后使用一个for循环以指针变量为索引来遍历整个数组,每次输出当前指针指向的元素值,并将指针加1以指向下一个元素。

四、指针的种类

1.指向基本数据类型的指针

  这类指针指向基本数据类型,如整数、浮点数和字符。
  (1)整型指针:用于存储整数类型变量的地址。分为 int 型、short 型、long 型等。
  (2)浮点型指针:用于存储浮点类型变量的地址。分为 float 型和 double 型。
  (3)字符型指针:用于存储字符类型变量的地址。通常用于处理字符串。
  例子:

int a = 10;
int *p = &a;  // p 是一个指向 int 类型的指针,存储变量 a 的地址

float f = 3.14;
float *pf = &f;  // pf 是一个指向 float 类型的指针,存储变量 f 的地址

char c = 'A';
char *pc = &c;  // pc 是一个指向 char 类型的指针,存储变量 c 的地址

char str[] = "Hello World";
char *str_ptr = str;  // str_ptr 是一个指向 char 类型的指针,存储字符串 str 的首字符地址

  这里我们声明了一个整数变量a,一个浮点数变量f,一个字符变量c和一个字符数组str,并分别用整数指针p、浮点数指针pf、字符指针pc和字符指针str_ptr指向它们。

2.指向数组的指针

  数组是一种连续存储多个相同类型变量的数据结构。
  数组指针用于存储数组的内存地址。在实际使用过程中,数组名本身就是一个指向数组首元素的指针

  例子:

int arr[] = {1, 2, 3, 4};
int *parr = arr;  // parr 是一个指向 int 类型的指针,存储数组 arr 的首元素地址

  这里我们声明了一个整数数组arr,并用指针parr指向它的第一个元素。

3.指向函数的指针

  函数指针用于存储函数的入口地址。通过函数指针可以实现回调函数等高级功能。

  例子:

int add(int a, int b) {
    return a + b;
}

int main() {
    int (*func_ptr)(int, int); // 函数指针
    func_ptr = add;
    int result = func_ptr(10, 20);
}

  这里我们声明了一个返回值为整数的函数指针func_ptr,并让它指向add函数。然后通过func_ptr调用add函数,计算两个整数的和。

4.指向结构体的指针

  结构体是一种用户自定义的数据类型,可以将多个不同类型的变量组合成一个有机的整体。
  结构体指针用于存储结构体类型变量的地址。通过结构体指针可以方便地访问结构体成员,实现结构体的动态创建和修改等高级功能。

  例子:

struct Student {
    int age;
    char name[20];
};

struct Student stu;
struct Student *pstu = &stu;  // pstu 是一个指向结构体类型 Student 的指针,存储结构体变量 stu 的地址

  这里我们声明了一个名为Student的结构体类型,包含姓名和年龄字段。我们创建了一个Student类型的变量stu,并用指针pstu指向它。

5.指向共用体的指针

  共用体(union)是一种特殊的数据类型,它允许在同一段内存空间中存储多个不同类型的变量。与结构体类似,共用体也可以包含多个成员变量,但是每次只能使用其中的一个成员变量。
  共用体指针用于存储共用体类型变量的地址。通过使用指向共用体的指针,我们可以方便地访问和修改共用体变量中的各个成员。

  例子:

union Data {
    int i;
    float f;
    char str[20];
};

union Data data = {0};
union Data *p = &data; // p是一个指向共用体类型 Data 的指针,存储共用体变量 data 的地址

  这里我们声明了一个名为Data的共用体类型,包含整数、浮点数和字符串字段。我们创建了一个Data类型的变量data,并用指针p指向它。

6.指向枚举类型的指针

  枚举类型是一种用户自定义的数据类型,它可以将多个相关的常量组合成一个有机的整体。
  枚举指针用于存储枚举类型变量的地址。它可以指向枚举类型变量的地址,从而对枚举类型变量进行操作。

  例子:

enum fruit { APPLE, ORANGE };

enum fruit f = APPLE;
enum fruit *ptr = &f;  // ptr 是一个指向枚举类型 fruit 的指针,存储枚举变量 f的地址

  这里我们声明了一个名为fruit 的枚举类型,包含两个枚举成员。我们创建了一个Data类型的变量f ,并用指针ptr指向它。

7.指向指针的指针(多级指针)

  指向指针的指针用于存储指针变量的地址。通过指向指针的指针可以实现多级指针的应用,例如动态内存分配和链表等数据结构的实现。同时,指向指针的指针也是函数指针和结构体指针等高级指针类型的基础。

  例子:

int a = 10;
int *p = &a; // 整数指针
int **pp = &p; // 指向整数指针的指针

8.指向常量的指针

  const常量指针用于指向一个不可修改的常量。通过将const关键字放在指针变量前面,可以将指针声明为常量指针。这种指针类型不允许通过指针来改变所指向的值,但是可以通过指针来访问所指向的值

  例子:

int a = 5, b=6;
const int *p = &a;

*p = 10;  //错误,不能通过常量指针修改所指内容
a = 10;  //正确
p = &b;  //正确,因为p本身是变量,可以指向其他整型变量

  这里我们将指针变量p声明为常量指针,并使用&p来获取变量a的地址。
  由于p是一个常量指针,它指向的地址中的值被定义为常量,所以不能通过p来修改a的值;虽然p指向的值是一个常量,但b本身不是常量,可以被修改。也就是说常量指针可以被赋值为变量的地址,之所以叫常量指针,是限制了通过这个指针修改所指变量的值

  常量指针通常用于函数参数列表中,表示该函数不会修改指针所指向的值,从而提高程序的健壮性和可维护性。常量指针还可以用于函数返回值,避免返回指向本地变量的指针引起的问题。

9.void指针(万能指针)

  void指针是一种不具体指向任何数据类型的指针,也被称为万能指针。可以用于指向任何类型的数据,因此可以存储各种数据类型的地址。
  但是,由于void指针没有类型信息,因此在使用时需要进行类型转换。

  例子:

void *ptr;
int num = 10;
float fnum = 3.14;

ptr = &num;  // void指针指向int类型的变量
printf("Value of num = %d\
", *(int*)ptr);  // 强制转换为int类型指针再进行取值运算

ptr = &fnum;  // void指针指向float类型的变量
printf("Value of fnum = %f\
," *(float*)ptr);  // 强制转换为float类型指针再进行取值运算

  这里我们首先声明了一个void指针ptr,然后将它分别指向int类型的变量num和float类型的变量fnum。在使用void指针时,我们需要先将其强制转换为指向具体类型的指针,然后才能访问指针所指向的数据。

10.空指针

  空指针是一种特殊的指针,其值为空(NULL)。空指针通常用作初始化或者表示一个无效地址。

  例子:

int *p= NULL;  // pnull 是一个空指针,表示无效地址

if (p== NULL) {
    printf("pis a null pointer\n");
}

  这里我们将指针变量p初始化为NULL,然后通过判断p是否为NULL来确定它是否为空指针。

11. 野指针

  野指针是指未初始化或已被释放的指针。它指向的内存地址是未知的或无效的,使用野指针会导致程序崩溃或产生不可预知的结果

  例子:

int *p;

*p = 5; // 这里p是一个野指针,会导致运行时错误

  这里我们没有为指针变量p分配内存,直接对其进行赋值操作会导致产生未定义的行为

12.悬浮指针

  悬浮指针是指指向已经释放的内存区域的指针。释放该内存区域后,程序仍然保留了该内存地址,因此使用该地址可以访问到原本已释放的内存区域,会导致程序错误或不可预知的结果

  例子:

int *p = (int *)malloc(sizeof(int));
free(p);

*p = 5; // 这里p是一个悬浮指针,会导致运行时错误

  这里我们先使用malloc函数(后续有介绍)为指针变量p分配了一块内存,然后又使用free函数将其释放。但是,由于程序仍保留了该内存地址,所以后续对该地址的操作会导致出现悬浮指针。

小总结

  (1)指向基本数据类型的指针:这种指针可以指向任何基本数据类型(如整型、浮点型等),并且可以通过指针来访问和修改该类型的值。
  (2)指向数组的指针:这种指针可以指向一个数组,并且可以通过指针来访问和修改数组元素。
  (3)指向函数的指针:这种指针可以指向一个函数,并且可以通过指针来调用该函数。
  (4)指向结构体的指针:这种指针可以指向一个结构体变量,并且可以通过指针来访问和修改结构体成员。
  (5)指向共用体的指针:这种指针可以指向一个共用体变量,并且可以通过指针来访问和修改共用体成员。
  (6)指向枚举类型的指针:这种指针可以指向一个枚举类型变量,并且可以通过指针来访问和修改枚举类型的值。
  (7)指向指针的指针:这种指针可以指向一个指针变量,并且可以通过指针来访问和修改指针所指向的地址。
  (8)指向常量的指针:这种指针可以指向一个常量,并且可以通过指针来读取常量的值,但不能修改常量的值。
  (9)void指针:可以指向任何类型的指针,需要进行类型转换才能访问指针指向的值。
  (10)空指针:这种指针是指向空地址的指针,用于表示指针不指向任何有效的数据或对象。
  (11)野指针:这种指针指向未知的位置,其值是未定义的。
  (12)悬浮指针:这种指针指向已经释放掉的内存区域或者未初始化的内存区域,其值是不可预测的。

  野指针、悬浮指针和空指针都是指针的一种特殊情况,但它们的含义和影响是不同的。
  避免产生野指针和悬浮指针的方法是在使用指针前进行初始化和释放操作,并保证指针所指向的内存地址是有效的。对于空指针,则可以使用条件判断来避免出现空指针引起的错误。
  在使用指针时遵守一些原则,例如保证指针指向有效的内存区域,避免出现野指针和悬浮指针等问题。同时,在涉及到复杂数据类型(如结构体、共用体等)的操作时,需要特别小心,以避免出现不可预计的错误。

五、指针的应用

  指针可以方便地访问和操作内存中的数据,在C语言中有广泛的应用。包括动态内存分配、函数参数传递、数组访问、结构体和共用体等复杂数据类型操作等方面。

1.动态内存分配

  malloc函数
  malloc的全称是memory allocation,中文叫动态内存分配,用于申请一块连续的指定大小的内存块区域。
  malloc()函数的原型如下:

void* malloc(size_t size);

  其中size表示要分配的内存空间大小,单位是字节。malloc()函数返回一个指向void类型的指针,该指针指向分配的内存空间的起始地址。由于返回的指针类型为void,因此需要将其强制转换为其他类型的指针才能访问分配的内存空间。

  但如果在程序使用完毕后不及时释放分配的内存空间,就会导致内存泄漏的问题。内存泄漏会占用系统资源,导致程序效率降低,甚至可能导致系统崩溃。
  因此,malloc()函数需要和free()函数配合使用,以实现动态内存空间的分配和释放。
  free()函数的原型如下:

void free(void *ptr);

  其中,void *ptr是指向要释放的内存块的指针。

  使用标准库函数malloc()和free()可以在程序运行时动态分配和释放内存。malloc()函数返回一个void型指针,指向分配的内存空间的起始地址,程序员可以将该指针转换为其他类型的指针来访问内存中的数据。

  例子:

int *arr = (int*)malloc(5*sizeof(int));
if (arr == NULL) {
    printf("Memory allocation failed.\n");
    return 1;
}
// 分配了5个整型变量的内存空间
arr[0] = 1;
arr[1] = 2;
...
free(arr); // 释放内存空间

2.函数参数传递

  使用指针作为函数的参数,可以使函数能够修改调用者传递的变量的值。
  例子:

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    int x = 10, y = 20;
    swap(&x, &y);
    printf("x = %d, y = %d\n", x, y);
    return 0;
}

  这里我们定义了一个swap()函数,它使用指针作为参数,通过指针访问变量的值来交换两个变量的值。在调用swap()函数时,需要传递变量的地址(即指向变量的指针)。

3.数组访问

  数组实际上是一段连续的内存空间,可以使用指针访问和修改数组元素的值。
  例子:

int arr[5] = {1, 2, 3, 4, 5};
int *p = &arr[0];
printf("%d\n", *p); // 输出arr[0]的值
p++;
printf("%d\n", *p); // 输出arr[1]的值

  这里我们定义了一个整型数组arr和一个指向arr[0]的指针p,通过指针访问数组元素的值。

4.复杂数据类型的操作

  指针还可以用于结构体、共用体等复杂数据类型的操作。
  例子:

typedef struct {
    char name[20];
    int age;
} Person;

void print_person(Person *p) {
    printf("%s is %d years old.\n", p->name, p->age);
}

int main() {
    Person tom = {"Tom", 20};
    Person *ptr = &tom;
    print_person(ptr);
    return 0;
}

  这里我们定义了一个名为Person的结构体,表示一个人的姓名和年龄,使用指针作为函数的参数,打印出一个人的信息。在主函数中,定义了一个Person类型的变量tom,使用指针ptr访问tom的成员。通过指针访问结构体成员是非常常见的操作,可以方便地传递和修改复杂数据类型的值。

小总结

  总的来说,指针可以用于动态内存分配和释放,使用malloc()和free()函数可以在程序运行时申请和释放内存空间,使得程序更加灵活和高效;
  指针还可以作为函数的参数,通过指针来访问和修改调用者传递的变量的值,实现函数与调用者之间的交互;
  指针还可以用于数组访问,通过指针访问数组元素的值,实现对数组的遍历和操作;
  指针还可以用于结构体和共用体等复杂数据类型的访问,通过指针访问结构体成员或共用体的不同字段,实现对复杂数据类型的操作和管理。

结束语

  所谓初步认识,只是能够建立起对指针的基本印象和知识框架,但还不够全面和深入。好的,对于C语言中指针的初步认识就介绍到这里。夯实基础,勇攀高峰,加油!💪
  本次励志:
  Life is full of possibilities!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值