嵌入式C语言学习(二)

0、基础补充

程序注释

程序中的注释是给程序员看的,便于程序员对代码进行维护整改,电脑不会执行。

C语言中的注释方式主要有两种。

多行注释:/*注释内容*/   

单行注释:// 注释内容     

使用printf()函数时需要注意

\ 接续符:反斜线(\)可以用来在一行结束后将代码续写到下一行。

\n 换行符

double float用%f打印,char用%c和%d打印,int用%d,字符串用%s打印,指针用%p打印

:以小数点(.)开头的数字可以控制浮点数或字符串的精度

%.2f:控制浮点数的小数部分保留2位有效数字。

注意:对于整形和字符型来说,有符号数和无符号数表示的范围是不同的。
譬如字符型,有符号数范围是-128~127,无符号数的范围是0~255

当我们定义了一个局部变量,但是没有初始化的时候,这个值是随机的。

存储单位

  • 一个字节是8个二进制位
  • 1 KB(千字节)= 1024 字节,相当于1024个字节。
  • 1 MB(兆字节)= 1024 KB,相当于1024 * 1024个字节。
  • 1 GB(千兆字节)= 1024 MB,相当于1024 * 1024 * 1024个字节。
  • 1 TB(太字节)= 1024 GB,相当于1024 * 1024 * 1024 * 1024个字节。

1、C语言数据类型

1.1、整形

        C语言中的整形对应数学中的整数,整形变量是用来描述一个整数值的,整形变量经过计算后也只能是整数(整型),不可能出现小数(浮点型)。
要求:要学会整形变量的定义,赋值,计算
        要学会使用printf函数打印出一些整形变量的值,作为输出和调试。

        C语言中整形有三种:
            (1)int              整形
            (2)short int, 又叫做short,短整形(比整形短)
            (3)long int,  又叫做long,长整型(比整形长)

1.2、浮点型

        C语言中浮点型对应数学中的小数。浮点型有float和double两种。使用方式相同,不同在于表示范围和精度。float表示的范围小,精度低(小数点后6位);而double表示范围大,精度 高。(小数点后16位)范围是说表示的数有多大,精度是指这个数的分辨率有多细。

1.3、字符型

        字符型对应ASCII字符。ASCII字符是一种编码,就是用数字编码来表示一个符号的一种方法
本质上说,字符型其实也是整形,只是这些整形数被用来表示一些字符的ASCII编码值,所
以叫做字符型。字符型一般用8位二进制表示,无符号字符型范围是0~255。
字符型其实是一种比short还短的整形,所以它可以和int相运算。

1.4、有符号数和无符号数

        数学中数是有符号的,有整数和负数之分。所以计算机中的数据类型也有符号,分为有符号数和无符号数。

有符号数:
    整形:signed int(简写为 int)
               signed long,也写作signed long int,(简写为long)
               signed short,也写作signed short int(简写为short)
               signed(表示signed int)
    浮点型:
               signed float(简写为float)
               signed double(简写为double)
    字符型:
               signed char(简写为char)
        
无符号数:
    整形:整形有无符号数,用来表示一些编码编号之类的东西。譬如身份证号,房间号
              unsigned int(没有简写)
              unsigned long int(简写unsigned long)
              unsigned short int(简写为unsigned short)
    
    浮点数:没有无符号浮点数。也就是说,小数一般只用在数学概念中,都是有符号的。
    
    字符型:字符型有无符号数
                  unsigned char(没有简写)

2、C语言常用运算符

C语言常用的运算符包括算术运算符、关系运算符、逻辑运算符、赋值运算符、位运算符等。

2.1、算数运算符号

跟数学中理解相同:

+    加号
-    减号
*    乘号
/    除号,相除以后的商
%    取余符号,相除以后余数是几
()    括号括起来优先级最高,先计算

2.2、赋值运算符

=    赋值运算符,与数学中的等号完全不同。赋值运算符作用是经过运算后符号左边的部分(
左值,一般是一个变量),的值就等于右边部分(右值,一般是常数或变量)了。
+=        a = a + b;        等同于     a += b;
-=        a = a - b;        等同于     a -= b;
*=        a = a * b;        等同于     a *= b;
/=        a = a / b;        等同于     a /= b;
%=        a = a % b;    等同于     a %= b;

2.3、关系运算符(判断运算符)

==    等于
!=    不等于
>    大于
<    小于
>=    大于等于
<=    小于等于

2.4、自增和自减

+        a++; 等同于 ++a;     等同于 a = a + 1;    等同于 a += 1;
--        a--; 等同于 --a;    等同于 a = a - 1;    等同于 a -= 1;

区别:++放在前面先+1后进行运算;++放在后面先进行运算后+1

2.5、逻辑运算符

!,&&,||分别是逻辑非、逻辑与、逻辑或

(1)逻辑非运算符(!):逻辑非运算符用于对一个逻辑值进行取反操作。如果操作数为真(非零),则结果为假(0);如果操作数为假(0),则结果为真(非零)。

(2)逻辑与运算符(&&):逻辑与运算符用于判断多个条件是否同时满足,只有当所有条件都为真(非零)时,结果才为真;否则,结果为假(0)。

(3)逻辑或运算符(||):逻辑或运算符用于判断多个条件中是否至少有一个满足,只有当至少一个条件为真(非零)时,结果才为真;否则,结果为假(0)。

2.6、逗号运算符

,     逗号运算符的主要作用是用来分割

3、程序结构

在C语言程序里,一共有三种程序结构:顺序结构、选择结构(分支结构)、循环结构

顺序结构:按照实物本身特性,必须一个接着一个来完成。
选择结构:到某个节点后,会根据一次判断结果来决定之后走哪一个分支。
循环结构:循环结构有一个循环体,循环体是一段代码。对于循环结构来说,关键在于根据判断的结果,来决定循环体执行多少次。

3.1、顺序结构

对于顺序结构来说,不需判断,因为下一句指令就是你要执行的。

逻辑上有一种类型,叫bool类型(又写作boolean类型,中文叫布尔类型)。布尔类型只有两个值,真和假。利用判断运算符,可以写出一个判断表达式,这个判断表达式最终的值就是一个bool类型。这个判断表达式的bool值就决定了选择结构如何选择,循环结构如何循环。

3.2、选择结构

C语言中选择结构一共有两种:if else,  switch case

if else

涉及关键字:if     else       else if

一种情况的判断

if (bool值)                // 如果bool值为真,则执行代码段1,否则执行代码段2
{
    代码段1
}
else
{
    代码段2
}

多种情况判断(多个分支)

if (bool值1)                // 如果bool值1为真,则执行代码段1
{                            // 否则则判断bool值2是否为真,若为真则执行代码段2
    代码段1                    // 否则直接执行代码段3
}
else if (bool值2)            // 开头的if和结尾的else都只能有一个,但是中间的
{                            // else if可以有好多个。
    代码段2
}
else
{
    代码段3
}

switch case

涉及到的关键字:switch    case    break    default

switch (变量)                // 执行到这一句时,变量的值已经知道了
{                            // switch case语句执行时,会用该变量的值一次与各个
    case 常数1:                // case后的常数去对比,试图找到第一个匹配项。
        代码段1;            // 找到匹配的项目后,就去执行该case对应的代码段
        break;                // 如果没找到则继续下一个case,直到default。
    case 常数2:                // 如果前面的case都未匹配,则default匹配。
        代码段2;
        break;
        
    .....
    
    default:
        代码段n;
        break;
}

注意:
1、case中必须是常数,而且必须是整形(不能是float double,可以是int char)
2、一般来说,每个case中代码段后都必须有一个break;
3、case之后一般都会有default。语法上允许没有default,但是建议写代码时一定要写。

switch case和if else对比

1、if else适合对比条件比较复杂,但是分支比较少的情况;switch case适合那种对比条件
不复杂,但是分支数很多的情况。
2、所有的选择结构,其实都可以用if else来实现。但是只有部分才可以用switch case实现。
一般的做法是:在适合使用switch case的情况下会优先使用switch case,如果不适合使用
switch case,则不得不使用if else。

3.3、循环结构

C语言中常用的循环结构有三个:for循环、while循环、do while循环。

for循环

for (循环控制变量初始化; 循环终止条件; 循环控制变量增量)
{
    循环体
}

for循环的执行步骤:
1、先进行循环控制变量初始化
2、执行循环终止条件,如果判断结果为真,则进入第3步;如果为假则循环终止,退出。
3、执行循环体。
4、执行循环控制变量增量,转入第2步。

while循环

i = 1;
sum = 0;                    // 循环初始化
while (i < 100)                // 终止条件
{
    printf("i = %d.\n", i);
    sum += i;                // 循环体
    i += 2;                    // 循环控制增量,属于循环体的一部分
}

while循环的执行步骤:
0、首先是循环初始化。这一部分其实不属于while循环本身。
1、先判断终止条件是否满足。如果是真,则进入第2步;否则直接退出。
2、执行循环体,然后转入第1步。

do while循环

i = 1;
sum = 0;                    // 初始化条件
do
{
    printf("i = %d.\n", i);
    sum += i;
    i += 2;                    // 增量,循环体的一部分
}while (i < 100);            // 终止条件

do while循环的执行步骤:
0、首先是循环初始化。这一部分其实不属于do while循环本身。
1、执行循环体(循环控制变量的增量是循环体的一部分)
2、判断终止条件。若成立,则转入1;若不成立则退出。

总结:不管哪种循环结构,都不能缺少一些要素:
循环控制条件初始化,终止条件,循环控制变量增量,循环体。
不同的循环方式(for和while和do while)都有这些,只是格式不同,表现形式不同,放的地方
不同,可读性不同,看起来和设计起来难度不同。

while循环和do while循环区别:while循环是先判断后执行,do while循环是先执行后判断
等循环开始转了之后,其实是一样的。

4、函数

函数是C语言中的一种重要的代码组织和复用工具。函数是一段封装了特定功能的代码块,可以重复调用,提高代码的可读性和可维护性。函数是C语言代码的基本组成部分,它是一个小的模块,整个程序由很多个功能独立的模块(函数)组成。这就是程序设计的基本分化方法。

这个函数不是程序员自己写的,是C语言标准库提供的一个库函数。在C语言中写代码时可以引用库函数,但是必须使用#include引用这个库函数所在的头文件。

int main(void) 
{
    // 主函数中的代码
    return 0;
}

main:C语言中的主函数,一个C语言程序只能有且必须有一个main函数。C语言规定,一个C语言程序从主函数开始执行,到主函数执行完结束。

主函数可以接受命令行参数,如:

int main(int argc, char *argv[]) 
{
    // 主函数中的代码
    return 0;
}

argc表示命令行参数的数量,argv是一个指针数组,存储了命令行参数的值。通过命令行参数,可以在运行程序时向主函数传递额外的信息。

4.1、函数的关键部分

函数定义:函数定义是关键,是这个函数的实现。函数定义中包含了函数体,函数体中的代码段   决定了这个函数的功能。

函数声明:函数声明实际上是叫函数原型声明。什么叫原型?函数的原型包含三部分:函数名,返回值类型,函数参数列表。通俗讲,函数原型就是这个函数叫什么,接收什么类型的几个参数,返回一个什么样的返回值。函数声明的作用,在于告诉使用函数的人,这个函数使用时应该传递给他什么样的参数,它会返回什么样类型的返回值。这些东西都是写函数的人在函数定义中规定好的,如果使用函数的人不参照这个原型来使用,就会出错,结果就会和你想的不一样。

函数调用:函数调用就是使用函数名来调用函数完成功能。调用时必须参照原型给函数传参,然后从函数得到适当的返回值作为结果。

4.2、函数参数

形参:形式参数的简称。在函数定义和函数声明中的参数列表中的参数,都是形参。
实参:实际参数的简称。函数调用中,实际传递的参数才是实参。

函数调用的过程,其实就是实参传递给形参的一个过程。这个传递实际是一次拷贝。实际参数的时候,实参(本质是一个变量)本身并没有进入到函数内,而是把自己的值复制了一份传给了函数中的形参,在函数中参与运算。这种传参方法,就叫做传值调用。

4.3、返回值:(关键字return)

当函数执行完之后,会给调用该函数的地方返回一个值。这个值的类型就是函数声明中返回值类型,这个值就是函数体中最后一句return xxx;返回的那个值。

4.4、函数名,变量名

第一点:起名字时候不能随意,要遵守规则。这个规则有两个层次:第一层就是合法,第二层是合理。合法就是符号C语言中变量名的命名规则。合理就是变量名起的好,人一看就知道什么意思,一看就知道这个函数是干嘛的,而且优美、好记。
第二点:C语言中,所有的符号都是区分大小写的。也就是说abc和Abc和aBc都是不同的符号。
第三点:C语言函数名变量名的命名习惯。没有固定的结论,有多种使用都很广泛的命名方式。介绍两种这里,
一种是linux的命名习惯    student_age        str_to_int
另一种是驼峰命名法        studentAge        StrToInt

5、数组

C语言的基本数据类型有整形、浮点型、字符型。再往后就是复合数据类型。所谓复合数据类型,是指由简单数据类型,经过一定的数据结构封装,组成而成的新的数据类型。例如:数组、结构体、公用体。

数组是一种存储相同类型元素的连续内存空间的数据结构。它可以容纳多个元素,并且每个元素都可以通过索引值来访问。数组是C语言中非常重要和常用的数据结构之一。

5.1、定义数组

int a[4];            数组中元素类型    数组名[数组元素个数];

注:数组中的所有元素必须是同一种数据类型,不可能在一个数组中存储两种数据类型的数。

5.2、数组的使用

数组定义的时候作为整体定义。但是使用的时候不能作为整体使用,使用时必须拆开使用数组中的各个元素。如数组int a[4],使用其中的四个元素,分别用a[0]``a[3],其中[]是数组的标志,[]中的
数字叫做数组下标(index,索引),下标是我们访问数组中各个元素的指引。下标是0代表数组中第一个元素,下标是1代表数组第二个元素。如果数组长度为n,下标中最后一个是n-1。访问数组时要特别注意下标,下标是从0开始的,如果下标超出了n-1,会产生越界访问,结果不可预判。

5.3、数组的初始化

初始化(initinalize,简写为init),是为了让对象有一个预定的初始状态。

(1)简单变量的初始化
当一个局部变量定义时没有初始化,它的值是随机的。解决方案有两个:
第一个,在定义过后明确给它赋值,使用=运算符。
第二个,定义该变量时,同时进行初始化。
总结:
1、一般来讲,只要你记得显示赋值,则两种方式并无优劣差异。但是人会犯错,会不小心,所以还是定义同时初始化好一点,因为这个定义的时候就有了固定值,即使之后忘记显示赋值也不会造成结果是随机的。
2、一般情况下,定义的同时都将变量初始化为0。局部变量定义同时初始化为0,这是一个写代码好习惯。

(2)数组的初始化
第一种:完全初始化。依次赋值
第二种:不完全初始化。初始化式中的值从a[0]开始,依次向后赋值,不足的默认用0填充赋值

5.4、不同数据类型数组

    int a[5];            // 整形数组
    float a[5];            // 浮点型数组
    double a[5];        // 双精度浮点型数组
    char a[5];            // 字符数组

程序在环境中运行时,需要一定的资源支持。这些资源包括:CPU(运算能力)、内存等,这些资源一般由运行时环境(一般是操作系统)来提供,譬如我们在linux系统上./a.out运行程序时,linux系统为我们提供了运算能力和内存。

程序越庞大,运行时消耗的资源越多。譬如内存占用,越大的程序,占用的内存越多。占用内存的其中之一,就是我们在程序中定义的变量。

C语言程序中,变量的实质就是内存中的一个格子。当我们定义(创造一个变量)了一个变量后,就相当于在内存中得到了一个格子,这个格子的名字就是变量名,以后访问这个内存格子就使用该变量名就行了。这就是变量的本质。

数据类型的实质是内存中格子的不同种类。譬如在32位机器上
短整形格子(short)                    占用2字节空间 16位
整形格子(类型是int)、                占用4字节空间 32位
单精度浮点型格子(float)、            占用4字节空间
双精度浮点型格子(double)、        占用8字节空间 64位
字符型格子(char)。                占用1字节空间 8位

5.5、sizeof运算符

作用:返回一个变量或者一个数据类型的内存占用长度,以字节为单位。

sizeof(a)/sizeof(a[0])        测试一个数组中究竟有多少个元素

#include <stdio.h>

int main(void)
{
    int len;
    int a[5];         
    // sizeof测试数组变量占用内存
    len = sizeof(a);
    
    printf("len = %d.\n", len);
}

5.6、字符数组

char a[5];

基础知识:
1、在C语言中引用一个单个字符时,应该用单引号''括起来,譬如'a'。
2、定义数组同时初始化,则可以省略数组定义时[]中的长度。C语言编译器会自动推论其长度,推论依据是初始化式中初始化元素的个数。由此可知,省略[]中数组元素个数只有一种情况,那就是后面的初始化式必须为完全初始化。
3、在C语言中引用一个字符串时,应该用""括起来,譬如"abcde"

"abcde"实际上有6个字符,分别是'a' 'b' 'c' 'd' 'e' '\0'

'\0' 这个字符是ASCII码表中的第一个字符,它的编码值是0,对应的字符是空字符(不可见
字符,在屏幕上看不见,没法显示,一般要用转义字符方式来显示。譬如'\n'表示回车符,'\t'表示Tab,'\0'代表空字符)

'\0'是C语言中定义的字符串的结尾标志。所以,当c语言程序中使用"abcde"这种方式去初始化时,编译器会自动在字符'e'后面添加一个'\0'。于是乎变成了6个字符。

6、指针

        指针全称是指针变量,是C语言的一种变量。这种变量比较特殊,通常它的值会被赋值为某个变量的地址值(p = &a),然后我们可以使用*p这样的方式去间接访问p所指向的那个变量。
        指针存在的目的就是间接访问。有了指针之后,我们访问变量a不必只通过a这个变量名来
访问。而可以通过p = &a; *p = xxx;这样的方式来间接访问变量a。

6.1、两种重要运算符:&和*

&:取地址符,将它加在某个变量前面,则组合后的符号代表这个变量的地址值。
例如: int a; int *p; p = &a; 则将变量a的地址值赋值给p。
就在上面的例子中,有以下一些符号:
    a          代表变量a本身
    p          代表指针变量p本身
    &a        代表变量a的地址值
    *p         代表指针变量p所指向的那个变量,也就是变量a
    &p        代表指针变量p本身的地址值。符号合法,但对题目无意义
    *a         把a看作一个指针,*a表示这个指针所指向的变量。该符号不合法

*:指针符号。指针符号在指针定义和指针操作的时候,解析方法是不同的。
int *p;        定义指针变量p,这里的*p含义不是代表指针变量p所指向的那个变量,在定义时
这里的*含义是告诉编译器p是一个指针。
int p;        // p是一个整形变量
int *p;        // p是一个指针变量,该指针指向一个整形数
使用指针的时候,*p则代表指针变量p所指向的那个变量。

6.2、指针的定义和初始化

指针既然是一种变量,那么肯定也可以定义,也可以初始化
第一种:先定义再赋值
    int *p;        // 定义指针变量p
    p = &a;        // 给p赋值                
第二种:定义的同时初始化
    int *p = &a;    // 效果等同于上面的两句

6.3、各种不同类型的指针

指针变量本质上是一个变量,指针变量的类型属于指针类型。int *p;定义了一个指针类型的
变量p,这个p所指向的那个变量是int型。

int *pInt;                // pInt是指针变量,指向的变量是int类型
char *pChar;            // pChar是指针类型,指向的变量是char类型
float *pFloat;
double *pDouble;
各种指针类型和它们所指向的变量类型必须匹配,否则结果不可预知。

6.4、指针定义的两种理解方法

int *p;    
第一种:首先看到p,这个是变量名;其次,p前面有个*,说明这个变量p是一个指针变量;最后,*p前面有一个int,说明这个指针变量p所指向的是一个int型数据。

char *(*(*pfunc)[])(char *, char *) 类似的复杂表达式,可以用相同的分析方法得到

第二种:首先看到p,这个是变量名;其次,看到p前面的int *,把int *作为一个整体来理解,
int *是一种类型(复合类型),该类型表示一种指向int型数据的指针。

总结:第二种方法便于理解,但是不够本质;建议用第一种方法来理解,因为这种思维过程可以帮我们理解更复杂的表达式。

6.5、指针与数组的初步结合

数组名:做右值时,数组名表示数组的首元素首地址,因此可以直接赋值给指针。
如果有 int a[5];
则 a和&a[0]都表示数组首元素a[0]的首地址。
而&a则表示数组的首地址。

注意:数组首元素的首地址和数组的首地址是不同的。前者是数组元素的地址,而后者是数组整体的地址。两个东西的含义不同,但是数值上是相同的。

根据以上,我们知道可以用一个指针指向数组的第一个元素,这样就可以用间接访问的方式去逐个访问数组中各个元素。这样访问数组就有了两种方式。
有 int a[5];  int *p; p = a;
数组的方式依次访问:a[0]    a[1]    a[2]    a[3]    a[4]
指针的方式依次访问:*p        *(p+1)  *(p+2)    *(p+3)    *(p+4)

6.6、指针与++ --符号进行运算

指针本身也是一种变量,因此也可以进行运算。但是因为指针变量本身存的是某个其他变量的地址值,因此该值进行* / %等运算是无意义的。两个指针变量相加本身也无意义,相减有意义。指针变量+1,-1是有意义的。+1就代表指针所指向的格子向后挪一格,-1代表指针所指向的格子向前挪一格。

*p++就相当于*(p++),p先与++结合,然后p++整体再与*结合。
*p++解析:++先跟p结合,但是因为++后置的时候,本身含义就是先运算后增加1(运算指的是p++整体与前面的*进行运算;增加1指的是p+1),所以实际上*p++符号整体对外表现的值是*p的值,运算完成后p再加1.
所以*p++等同于:*p;   p += 1;

*++p等同于 p += 1;    *p;

(*p)++,使用()强制将*与p结合,只能先计算*p,然后对*p整体的值++。

++(*p),先*p取值,再前置++,该值+1后作为整个表达式的值。

总结:++符号和指针结合,总共有以上4种情况。--与++的情况很类似。

6.6、函数传参中使用指针

int add(int a, int b)    函数传参使用了int型数,本身是数值类型。实际调用该函数时,
实参将自己拷贝一份,并将拷贝传递给形参进行运算。实参自己实际是不参与的。
所以,在函数中,是没法改变实参本身的。

7、结构体

在C语言中,数据的组织依靠:变量+数组。

最初最简单的时候,只需要使用基本数据类型(int char float double)来定义单个变量,需要几个变量就定义几个。后来情况变复杂了,有时需要很多意义相关的变量(譬如需要存储及运算一个班级的学生分数)这时候数组出现了。数组解决了需要很多类型相同、意义相关的变量的问题。
但是数组是有限制的。数组最大的不足在于,一个数组只能存储很多个数据类型相同的变量。
所以碰到需要封装几个类型不同的变量的时候,数组就无能为力。就需要结构体了

结构体是一个集合,集合中包含很多个元素,这些元素的数据类型可以相同,也可以不相同。所以结构体是一种数据封装的方法。结构体存在的意义就在于,把很多数据类型不相同的变量封装在一起,组成一个大的新的数据类型。

数据结构:把庞大复杂的数据用一定的方式组织管理起来,便于操作(查找,增加,删除等)这就叫数据结构。

7.1、结构体和数组的关联

数组是一种特殊的结构体,特殊之处在于封装内的各个元素类型是相同的。结构体和数组都是对一些子元素的封装,因此定义的时候都是封装作为整体定义,但是使用的时候,都是使用封装中的子元素。一般结构体变量和数组变量都不会作为一个整体操作。

7.2、结构体的使用步骤

第一步:定义结构体类型。结构体类型的定义是在函数外面(函数外面 == 全局)的
第二步:使用第一步定义的类型来定义结构体变量。
第三步:使用变量。实际上使用结构体变量的时候,使用的是结构体变量中封装的各个子元素,而不是结构体变量本身。

7.3、结构体的初始化

结构体变量和普通变量一样,作为局部变量时,如果定义的时候无初始化也无显式赋值,则结构体变量中的子元素的值是随机的。

发现2种C语言接受的结构体初始化方式。
第一种,完全初始化。{xx, xx, xx, xx, xx};
第二种,部分初始化。
{
    .a = xx,
    .b = xx,
    .c = xx,
    .d = xx,
};
新增关键字: struct
新增操作符: .


 

  • 22
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白不想画工图

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值