C语言学习笔记04:C语言基础_数据类型_流程控制_数组_字符串_函数_指针_ 内存管理

1. C语言基础

  1. 操作系统:管理硬件资源。

    1. 针对普通用户,图形界面.
    2. 针对开发人员,命令行窗口、系统调用。
  2. 我们的程序运行在操作系统基础之上,编写的程序依赖操作系统。

  3. 编译型语言、解释性语言。

    1. 编译型语言,例如 C语言。先把源代码全部编译成可执行文件,再执行。效率高。
    2. 解释性语言,例如 Python Lua Js 解释性语言。读一行解释执行一行。效率低。
  4. C语言编译的过程: index.c

    1. 预处理: 处理预处理指令。简单文本替换。将 #define MAX 1024, 程序中所有使用 MAX 的地方

      在预处理完之后,全部替换成 1024.

      index.i 源代码文件

    2. 编译:

      1. 函数调用、变量使用做类型检查。
      2. 看看程序员有没有按照C语言语法要求编写代码。
      3. 代码优化。

      index.s 汇编文件

    3. 汇编

      1. 把汇编代码转换成二进制目标文件。

      index.o

    4. 链接

      1. 程序中用到很多库函数,负责找到这些函数,建立和这些函数连接。
      2. 添加启动代码。
      3. 目标文件、启动代码、库函数代码打包到一起,就变成 exe 文件。

2. 数据类型

  1. 变量类型

    1. 创建变量的时候,决定给变量分配多少内存。
    2. 编译器通过类型来确定运算规则。
  2. 整型变量:

    1. short 2个字节
    2. int 4 个字节
    3. long 4个字节(gcc 8字节)
    4. long long 8个字节
  3. 字符类型:

    1. 字符类型实际在内存中以 ASCII 码值(数字 0-255)
    2. 也可以把字符类型当做1字节长度的整型。
    3. -128-127, -0在程序中用-128来代替。
  4. 浮点型:

    1. float , 4 个字节, 6-7位,6位一定准,7位不一定准。
    2. double, 8个字节, 精确到 15-16位, 15一定准,16不一定准。
  5. 数值溢出:

    1. 有符号: C语言没有规定,未定义。VC编译器和有符号一样,char c = 127; c+=1; -128
    2. 无符号: C语言有规定。当这个数超过最大值,会回到原点。unsigned char c = 255; c += 1; 0
  6. 类型转换:

    1. 隐式类型转换。
    2. 显式类型转换(强制类型转转)。
    3. 尽量用强制类型转换,代码可读性好。
  7. 数据存储都是使用补码存储。

    1. 反码:符号位不变,其他位取反。
    2. 补码: 反码 + 1。
    3. 负数的补码:反码+1
    4. 正数的补码就是自身。
  8. 运算符:自增、自减运算

    1. 前置++:先加完再用。

    2. 后置++:先用再加。

      int a = 10;
      int c = ++a;  // 11
      /*
      	a = a + 1;
      	int c = a;
      */
      
      int c = a++;  // 10
      /*
      	int c = a;
      	a = a + 1;
      */
      
      

3. 流程控制

  1. if语句语法:

    if(条件)
    {
    
    }
    else if(条件)
    {
    
    }
    else if(条件)
    {
    
    }
    else
    {
    
    }
    

    使用的时候,else if 不能单独使用。

  2. 三目运算符。

    条件 ? 表达式 : 表达式; 
    a > b ? a : b;
    

    考试的时候,注意三母运算符嵌套。

    主要用于替换简单的 if 语句。

  3. switch 语句:

    switch(变量)
    {
      case:
        执行代码
        break;
       case:
        执行代码
        break; 
      default:
        break;
    }
    
  4. 循环语句:

    1. while for 循环语法。

    2. break 终止循环.

      while(true)
      {
        while(true)
        {
          // 不会终止外层的循环
       		break;  
        }
      }
      
    3. continue 终止本次循环。

    4. srand 设置随机数种子,以 time(NULL) 作为种子值. 默认种子值是 1.

    5. rand 函数根据新的种子值计算产生一个随机数(伪随机)

4. 数组

  1. 数组三部分: 数组元素类型、数组名、数组元素个数.

    int arr[3];
    
  2. 数组初始化

    int arr[3] = {0};  // 部分初始化, 未初始化元素设置为 0
    int arr[] = {10, 20, 30};  // 自动推到数组元素个数
    char s[] = "hello world";
    s[0] 获得 h
    s[1] 获得 e
    
  3. 数组注意点

    1. 数组下标越界
    2. 不能使用变量定义数组
    3. 数组定义之后无法修改大小
    4. 数组下标支持负数,但要注意越界
  4. 数组名的含义:指向数组首元素的指针常量(指针的指向不能修改)。

  5. 多维数组本质上是一位数组。

    int arr[2][3];
    int arr[2][3] = {
      {10, 20, 30},
      {40, 50, 60}
    };
    // 访问
    // arr[0][1] 等等
    
  6. 数组做函数参数

    void print_array(int arr[], int len){}
    // 数组名做函数参数
    // 数组名作为函数参数的话,会退化为指针,此时 sizeof 无法计算整个数组长度
    int arr1[] = {10, 20, 30};
    
    void print_array(int arr[][3], int r, int c){}
    int arr2[2][3];
    

5. 字符串

  1. 字符串定义:

    // 字符串常量: 用双引号括起来的字符串, 最后都会添加\0
    "hello world";
    
    // 1. 希望字符串从程序运行开始就创建,结束结束自动销毁
    void test01()
    {
      // ”hello world“ 存储在字符串常量区,整个程序运行期间一直存在
      // s 是函数内部的局部变量,函数结束之后,仍然会被销毁
      char *s = "hello world";
    }
    
    // 2. 希望字符串随着函数的执行结束就自动销毁
    void test02()
    {
      // s[] 函数内部定义局部数组, 数组随着函数结束就会被销毁
      // "hello world" 存储在字符串常量区
      // ”hello world“ 会被拷贝一份放到 s 数组中
      char s[] = "hello world";
    }
    
    // 3. 希望能够控制变量生死
    void test03()
    {
      // 给字符串开辟堆内存
      char *s = malloc(strlen("hello world") + 1);
      if(NULL == s)
      {
        return;
      }
      // 使用 strcpy  strncpy 可以将字符串数据拷贝到空间中
      strcpy(s, "hello world");
      // 打印字符串
      printf("%s", s);
      // 释放空间
     	if(s != NULL)
      {
        free(s);
        s = NULL;
      }
    }
    
  2. 字符串操作

    1. strlen, 计算字符串长度,不算 \0 字符
    2. strcpy、strncpy, 进行字符串拷贝
    3. strcat、strncat , 进行字符串拼接
    4. strcmp、strncmp , 进行字符串比较
    5. sprintf , 格式化字符串
    6. atoi,将字符串转换为数字

6. 函数

  1. 函数返回值类型:

    1. 使用 return 关键字.
    2. 一个函数只能有一个返回值.
    3. 真正返回的值要和写的返回值类型要匹配。
    4. 函数是否有返回值要根据编写业务功能、需求来分析确定。
  2. 函数参数:

    1. 函数参数可以0个或者多个

    2. 函数如果没有参数,应该写 void

      void func(void){}
      
    3. 函数调用时,函数需要几个参数,就要传递几个参数,不能省略。

    4. 函数在传递参数的时候是值传递。数组是地址传递。

  3. 函数体:

    1. 单一职责原则,函数尽量只做一件事,不要承担过多职责、
    2. 函数代码尽量少一些。
  4. 函数内部定义变量、函数形参叫做局部变量。 函数外部定义变量,叫做全局变量。

  5. C分文件编写:

    1. .h 文件: 函数声明、变量声明
    2. .c 文件: 函数定义、变量定义
    3. 一般负责开发 C文件,负责编写 h文件
    4. 一般先写头文件,再写C文件

7. 指针

  1. 普通变量存储数据。指针用来存储变量的地址。为了语法上区分普通变量、指针变量,指针变量多一个星号。

    1. 知道这个变量是一个指针

    2. 知道这个变量指向的是什么类型

      int a = 10;
      // *p 带了星号,表示p变量是一个指针变量
      // int 表示指针保存的是一个 int 类型变量的地址
      int *p = &a;
      
  2. 指针的操作

    1. 赋值。

      int a = 10;
      int b = 20;
      int *p = &a;
      p = &b;  // 赋值
      
    2. 解引用。

      1. 指针变量本身就是保存某一个变量的地址。

      2. 把指针变量存储的这个地址取出来。

      3. 找到这个地址的变量。

        int a = 10;
        int b = 20;
        int *p = &a;
        *p = 100; // 表示修改p指向的a变量的值
        int c = *p; // 把a变量的值赋值给c变量
        
    3. 步长操作。

      1. 指针步长,主要由指向的空间大小来决定。
        1. 比如指向的是 int 类型,int 类型空间 4个字节,步长就是4
        2. 比如指向的是 char 类型,步长就是1.
        3. char* *p; **char***四个字节, 所以 p+1, 加四个字节。
    4. 无类型指针。

      1. 不能进行步长运算。

      2. 不能解引用。

      3. 主要保存地址。

      4. 任何类型赋值给无类型指针不需要类型转换。

      5. 定义一个能够保存任何类型数据的数组:

        void* arr[10]; // void* void* void*
        
    5. const

      1. const 修饰普通变量。变量不能修改。

        int const a = 10;
        const int b = 20;
        
      2. const 修饰指针变量。

        int a = 10;
        // const 在星号左边,表示指向空间的值不能改。常量指针
        const int *p = &a;
        // const 在星号左边,表示指向空间的值不能改
        int const *p = &a;
        
        // 星号放在星号右边,指针指向不能改。 指针常量
        int* const p = &a;
        
    6. 指针应用场景:

      1. 修改外部变量的值
      2. 提高数据传递效率
      3. 函数返回多个结果
      void get_value(int arr[], int len,int *p_max, int *p_min, int *p_avg)
      {
      	// 求最大值、最小值、平均值
      	int my_min = arr[0];  // 假设第一个元素最小值
      	int my_max = arr[0];  // 假设第一个元素最大值
      	int my_sum = 0;  // 累加和
      
      	for (int i = 0; i < ARRAY_LENGTH; ++i)
      	{
      		// 记录最大值
      		if (arr[i] > my_max)
      		{
      			my_max = arr[i];
      		}
      
      		// 记录最小值
      		if (arr[i] < my_min)
      		{
      			my_min = arr[i];
      		}
      
      		// 累加元素和
      		my_sum += arr[i];
      	}
      
      	// 计算平均值
      	int my_avg = my_sum / ARRAY_LENGTH;
      
      	// 返回三个值
      	*p_max = my_max;
      	*p_min = my_min;
      	*p_avg = my_avg;
      }
      
    7. 多级指针:

      int a = 10;
      int *p = &a;
      
      // 区分变量是一个指针,并且还要区分变量指向的数据类型是什么?
      int **pp = &p;
      int ***ppp = &pp;
      

      多级指针解引用: 取地址+星, 解引用-星

      int a = 10;
      int *p = &a;
      
      // 区分变量是一个指针,并且还要区分变量指向的数据类型是什么?
      int **pp = &p;
      int ***ppp = &pp;
      
      // *ppp 是二级指针类型
      // **ppp 是一级指针类型
      
    8. 指针无论什么类型,无论几级指针,统统占4字节内存(32位)

8. 内存管理

9.语言第十天课程笔记

每一天的笔记包含如下内容:

  1. 当天授课内容安排
  2. 课堂重点内容笔记
  3. 课后思考题

9.1. 内容安排

第一节课: 作用域、变量分类(静态变量、非静态变量,静态函数、非静态函数)
第二节课: 内存分区(代码区、数据区-堆区、全局静态区、字符串常量区、栈区)
第三节课: 内存操作(malloc、free、memset、memcpy、memcmp、memmove)
第四节课: 结构体语法(结构体定义、结构体成员访问、结构体变量定义)
第五节课: 结构体使用注意、结构体作为函数参数
第六节课: typedef、enum

9.2. 作用域

作用域: 主要探讨标识符(函数名、变量名),这些名字在哪些范围内可以使用。变量名的可见范围.

函数只有文件作用域.

C作用域:

  1. 文件作用域. 例如: 有些变量在 a.c 定义的,在 b.c 就不能访问.

  2. 函数作用域. 例如: a函数内定义了一个变量 int number = 10, b函数中就不能使用 a 函数定义的 number 变量.

  3. 代码块作用域. 例如:

    if(条件)
    {
      int a= 10;
    }
    
    if(条件)
    {
      printf("a = %d\n", a);
    }
    

9.3. 变量分类

  1. 非静态变量: 全局变量 局部变量
    1. 非静态全局变量
      1. 作用域:整个项目中都可以访问
      2. 内存管理: main 函数执行之前被创建,main 函数执行结束,内存回收.
    2. 非静态的局部变量
      1. 作用域: 该变量只在函数内可见
      2. 内存管理: 局部变量内存函数调用时,分配内存,函数调用结束,回收内存.
  2. 静态变量:静态全局变量,静态局部变量
    1. 静态全局变量
      1. 作用域: 文件作用域. 只能在当前 .c 文件内访问,在其他 .c 文件中不可访问,不可使用.
      2. 内存管理: main 函数执行之前创建,main函数执行结束之后回收.
    2. 静态局部变量
      1. 作用域: 静态局部变量也是一个局部变量,作用域是当前函数内部.
      2. 内存管理: main 函数执行之前创建,main函数执行结束之后回收.

非静态变量:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

// 非静态变量: 全局变量 局部变量

// 非静态全局变量
// 作用域:整个项目中都可以访问
// 内存管理: main 函数执行之前被创建,main 函数执行结束,内存回收.
int g_number = 0;


void test01()
{
	// 非静态的局部变量
	// 作用域: 该变量只在函数内可见
	// 内存管理: 局部变量内存函数调用时,分配内存,函数调用结束,回收内存.
	int number = 0;
}

静态变量:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

// 静态变量:静态全局变量,静态局部变量

// s_number 静态全局变量
// 作用域: 文件作用域. 只能在当前 .c 文件内访问,在其他 .c 文件中不可访问,不可使用.
// 内存管理: main 函数执行之前创建,main函数执行结束之后回收.
static int s_number = 100;


void test01()
{
	// 静态局部变量
	// 作用域: 静态局部变量也是一个局部变量,作用域是当前函数内部.
	// 内存管理: main 函数执行之前创建,main函数执行结束之后回收.
	static int a = 100;
}

// 静态局部变量不会被销毁
void test02()
{
	static int a = 10;
	++a;
	printf("a = %d\n", a);
}

// 1. 允许
// 2. 原因: a 变量是静态变量,整个程序运行期间一直存在.
int* get_number_pointer()
{
	static int a = 100;
	return &a;
}

// 全局变量、静态变量都是程序执行之前创建,程序执行之后销毁,整个程序运行期间,内存一直存在.

int main()
{
	test02();
	test02();
	test02();
	test02();


	int *p = get_number_pointer();


	system("pause");
	return EXIT_SUCCESS;
}

9.4 函数分类

  1. 非静态函数

    void func()
    {
      printf("hello world");
    }
    

    非静态函数也叫做全局函数,可以在整个项目任何的 .c 文件中访问.

  2. 静态函数

    static void func()
    {
      printf("hello world");
    }
    

    静态函数只能在当前文件内访问.

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

// 访问在其他 .c 文件中定义的函数, 有两步:
// 1. 先声明该函数, 告诉编译器这个函数在其他文件中定义.
// 2. 调用函数.

// 如果 func 函数在其他文件中定义为静态函数,则在当前文件中不能使用.
void func();  // 函数声明

int main()
{

	func();


	system("pause");
	return EXIT_SUCCESS;
}

9.5. 内存分区

编译器会将程序所使用的的内存。代码区、数据区。代码区放在只读区。

为什么代码放在只读区?为什么数据放在可读可写的区域呢?

代码一旦编写完毕,运行过程中不允许任意修改,为了保护代码,在设计的时候,代码就放在只读的内存区域。放代码的区域,叫做代码区。

数据区:

栈区:自动申请,自动释放。

  1. 函数的参数
  2. 函数内部定义局部变量
  3. 可读、可写

全局/静态区: 在程序执行之前分配内存,程序结束之后回收内存。

  1. 全局变量
  2. 静态全局变量
  3. 静态局部变量
  4. 可读、可写

字符串常量区: 程序运行之前创建,程序运行之后销毁.

  1. 双引号括起来的字符串会放在字符串常量区.
  2. 只读,不能修改.

堆区: 手动管理内存申请、手动释放内存。

1. 根据需要申请任意大小的内存。
2. 根据需要选择在合适的时间释放内存。

内存4区: 最主要目的是让我清楚,变量的生命周期。

栈区: 函数开始存储,函数结束释放。

全局静态区: 整个程序运行期间都会存在.

堆区: 手动申请,手动释放,如果申请了没有释放,会造成内存问题:内存泄露。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

// 1. 申请和释放堆内存,需要两个底层函数  malloc  free
void test01()
{
	// 栈变量
	int a = 10;
	// 全局静态变量
	static int b = 20;
	// 向操作系统要了4字节内存,函数返回这个4个字节内存的首地址
	// p 指向的内存 1. 手动释放 2. 程序结束之后,统一回收
	int* p =  (int *)malloc(4);
	*p = 30;  // 堆内存赋值

	printf("*p = %d\n", *p);

	// free 函数用于释放堆内存
	// 当 free 调用之后,内存就不能再使用了。
	// free(p);
}


// 2. 给double类型分配堆空间
void test02()
{
	// 在堆上分配了8字节内存,用于存储 double 类型数据
	double *d = (double *)malloc(sizeof(double));
	// 如何写内存
	*d = 3.14;
	// 如何读内存
	printf("*d = %lf\n", *d);
	// 释放内存
	free(d);
}

long* create_long()
{
	// 动态申请一块long大小的内存
	long *l = malloc(sizeof(long));

	// 返回
	return l;
}

void test03()
{
	// 问题: ll 能不能使用? 程序有有什么问题?
	long *ll = NULL;
	ll = create_long();

	free(ll);
}

// 给 long 分配内存,并且赋值为 666, 打印输出

int main()
{

	test02();

	system("pause");
	return EXIT_SUCCESS;
}

9.6内存操作

代码区:不关心.

全局区:项目中所有的文件共享。变量和函数。

​ extern 类型 变量名;

​ 返回值类型 变量名(参数…);

静态区: 只要函数、变量加上 static 关键字,这些变量和函数只能在当前文件内访问。

字符串常量区: 双引号括起来的字符串。不能修改。

栈区:

  1. 系统自动管理,不能使用 free 去释放栈区内存。

  2. 栈区比较小。如果大数据不要放在栈上。当程序运行的时候,栈区大小是固定的。

    1. 栈空间占用如果超过最大上限,会出现 Stack Overflow 栈溢出。
    
  3. 栈区内存由系统管理,它的内存申请、释放效率非常高的。

    int arr1[100000000];
    	int arr2[100000000];
    	int arr3[100000000];
    	int arr4[100000000];
    

堆区:

  1. 堆区的内存由开发人员自己申请,自己释放。如果忘记 free, 会出现内存泄露。
  2. 堆区的内存比较大。真正开发环境下,大量的数据需要放在堆区存储。
  3. 有些数据,我们需要控制它的生命周期,将数据存储在堆上。
  4. 堆区相对于栈区,内存管理效率就很低。在项目中,进行优化。内存池。
    1. 程序一运行,一次性 malloc 一大块内存。
    2. 当程序需要内存时,找到自己的内存池,使用。
    3. 用完之后再放到内存池中。
    4. 减少 malloc free 的次数。

9.7. 内存操作

操作内存时,不用再区分堆、栈等区别。定义变量的时候,需要区分。

memset : 初始化内存

memcpy: 内存拷贝, 不能出现内存重叠。将内存中的字节拷贝到另外一个内存。处理字符串拷贝,建议还是用 strcpy

memmove: 内存移动. 处理内存重叠现象。效率比 memcpy 低。

memcmp: 内存比较。 strcmp 从开始位置到\0比较。

四个函数都是 mem 开头,#include <memory.h> .

memset例子:

// 1. memset 数组初始化方式、int arr[10] = { 0 }、for 循环、memset
void test01()
{
	// 数组初始化方式一
	// int arr[10] = {0};

	// 数组初始化方式二
	//int arr[10];
	//for (int i = 0; i < 10; ++i)
	//{
	//	arr[i] = 0;
	//}

	int arr[10];
	// 第一个参数,是初始化内存的首地址, 类型 void *
	// 第二个参数,将内存初始化成什么值,类型 int
	// 第三个参数,从首地址开始多少个字节,设置为 0
	memset(arr, 0, sizeof(arr));


	for (int i = 0; i < 10; ++i)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

// 将int类型设置为0值
void test02()
{
	int a = 412312;
	memset(&a, 0, sizeof(int));
	printf("a = %d\n", a);

}

void test03()
{
	// malloc 返回的指针类型,就是指向首元素类型的指针
	char *s = malloc(sizeof(char) * 32);
	// 将内存初始化成0
	memset(s, 0, 32);
	printf("s = %s\n", s);
}

内存拷贝:

// memcpy 不能有内存重叠,如果有内存重叠,不保证一定成功。
// memmove 内存实现交换两个变量的值
void test04()
{
	// 内存拷贝
	int a = 10;
	int b = 20;

	// 第一个参数: 目标空间的首地址
	// 第二个参数:  源空间的首地址
	// 第三个参数: 从源空间拷贝多少字节的数据到目标空间
	printf("a = %d, b = %d\n", a, b);
	memcpy(&a, &b, 4);
	printf("a = %d, b = %d\n", a, b);
}

void test05()
{
	int arr[] = { 1, 2, 3, 4, 5 };
	for (int i = 0; i < 5; ++i)
	{
		printf("%d ", arr[i]);
	}

	printf("\n");
	// 第一个参数: 目标空间的首地址
	// 第二个参数:  源空间的首地址
	// 第三个参数: 从源空间拷贝多少字节的数据到目标空间
	memmove(arr, arr + 1, sizeof(int) * 4);
	for (int i = 0; i < 5; ++i)
	{
		printf("%d ", arr[i]);
	}
}

// 使用 memcpy 交换两个变量的值
void test06()
{
	int a = 10;
	int b = 20;
	printf("a = %d, b = %d\n", a, b);

	int temp = 0;

	// 将 a 的数据拷贝到 temp 中
	memcpy(&temp, &a, 4);
	// 将 b 的数据拷贝到 a 中
	memcpy(&a, &b, 4);
	// 将 temp 的数据拷贝到 b 中
	memcpy(&b, &temp, 4);

	printf("a = %d, b = %d\n", a, b);
}

内存比较:

// memcmp 内存实现比较两个数组是否相等
// 主要用于判断是否相等, 逐个字节比较
void test07()
{
	int a = 10;
	int b = 20;

	// 第一个参数,参与比较的数据的首地址
	// 第二个参数, 参与比较数据的首地址
	// 第三个参数,从首地址开始比较的字节数
	if (memcmp(&a, &b, 4) == 0)
	{
		printf("相等!\n");
	}
	else
	{
		printf("不相等!\n");
	}

}

9.8. 结构体

结构体定义语法:

先定义类型,再使用类型定义变量.

// 类型定义
struct Person
{
	int age;
	double salary;
	char name[64];
};

// 3. 结构体是一个类型, 类型都拿来定义变量
void test01()
{
	struct Person  p;  // 使用 Person 类型定义出变量叫做 p
	p.age = 30;
	p.salary = 9999.99;
	strcpy(p.name, "Obama");

	printf("Name:%s Age:%d Salary%lf\n", p.name, p.age, p.salary);
}

定义类型之后,顺便定义全局变量:

// 2. 员工类型 emp, 员工编号 员工姓名 员工工资 员工电话
struct Emp
{
	int emp_no;  // 员工编号
	char emp_name[64];  // 员工姓名
	double emp_salary;  // 员工工资
	char emp_tele[128]; // 员工电话
}my_emp = {10001, "司马狗剩", 6789.98, "1234567890"};  // 定义类型之后,马上定义全局变量 my_emp

void test03()
{
	my_emp.emp_no = 10001;
	printf("%d\n", my_emp.emp_no);
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值