C语言基础知识

1 C 语言简介

C 语言是一种通用的高级语言,是用来与机器交流的一种语言,可移植性和执行效率都较高。

2 C 程序结构

Hello World 程序为例,C程序结构主要包含以下部分:

  • 预处理指令
  • 函数
  • 变量
  • 语句/表达式
  • 注释
#include <stdio.h>	// 系统路径下去找
#include "xxx.h"	// 当前路径下去找
int main()			// main实际上是一个地址0x0013161 别名 标识符
{
   /* Hello World 注释 */
   printf("Hello, World! \n");
   if (a)
   {
       return -1;
   }
   return 0;		
}
  1. #include<stdio.h> 是预处理指令,告诉编译器在编译前要包含 stdio.h 文件。
  2. int main() 是主函数,也是程序的入口,程序从这里开始执行。
  3. /* Hello World 注释 */会被编译器忽略,因为注释是写给人看的。
  4. printf("Hello, World! \n")是一个函数,可以在屏幕上打印Hello World
  5. return 0表示程序运行结束并返回0, 表示程序执行成功(成功只有返回0这一种情况,而错误则有很多种情况)。

3 C 基本语法

3.1 分号

分号是语句结束符,相当于语文中的句号,表示一条语句结束,这样可以区分语句。

3.2 注释

// 这是单行注释
/*
   这是多行注释
*/

注释是写给人看的,编译器看到 // 或者 /**/ 这样的,会自动忽略。

3.3 标识符

标识符就是用来标识变量、函数的,相当于为变量或者函数起一个名字。

命名规则是:以字母A-Z或a-z或下划线_开始,后面跟字母或下划线_或数字(0-9)。(命名不能跟关键字冲突!)

注意,命名中不能出现标点字符,比如@$%,而且C语言命名是区分大小写的!(大小写敏感)

常用的命名法则——驼峰命名法,采取大小写字母混用的命名方式。// 自说明变量

  • 小驼峰命名法:一般用于变量,比如int myStudentsCount;。// count 计数
  • 大驼峰命名法:一般用于类名(C++/Java),比如public class DataBaseUser;

3.4 关键字

关键字是C语言保留字(表示已被占用了),不能作为常量名、变量名或其他标识符名称。

关键字说明
auto声明自动变量(不常用)
goto无条件跳转语句(不常用)
volatile说明变量在程序执行中可被隐含地改变(不常用)
register声明寄存器变量(不常用)
union声明共用体类型(不常用)
enum声明枚举类型
signed声明有符号类型变量或函数
unsigned声明无符号类型变量或函数
break跳出当前循环
case开关(switch)语句分支
char声明字符型变量或函数返回值类型
const定义常量,如果一个变量被 const 修饰,那么它的值就不能再被改变
continue结束当前循环,开始下一轮循环
default开关语句中的"其它"分支
do循环语句的循环体
double声明双精度浮点型变量或函数返回值类型
else条件语句否定分支(与 if 连用)
extern声明变量或函数是在其它文件或本文件的其他位置定义
float声明浮点型变量或函数返回值类型
for一种循环语句
if条件语句
int声明整型变量或函数
long声明长整型变量或函数返回值类型
return子程序返回语句(可以带参数,也可不带参数)
short声明短整型变量或函数
sizeof计算数据类型或变量长度(即所占字节数)
static声明静态变量
struct声明结构体类型
switch用于开关语句
typedef用以给数据类型取别名 #define N 10
void声明函数无返回值或无参数,声明无类型指针
while循环语句的循环条件

3.5 空格

空格是编译器用来区分各个元素的,比如int a = 0;inta 之间就至少有一个空格,用于区分。

当然,为了代码的可读性,通常我们会适当的多加些空格,这样看起来不会很累。

比如int a = 0; 就比 int a=0; 看起来舒服点,字符间不会显得很密集。

4 C 数据类型

sizeof 可以得到对象或类型的存储大小。

int a[5];// sizeof(a) = 20 sizeof(a[0]) = 4

4.1 整数类型

1GB = 1024MB

1MB = 1024KB

1KB = 1024Byte

1Byte = 8bit

1111 1111 = 255

类型存储大小值范围
char1 字节-128 到 127
unsigned char1 字节0 到 255
signed char1 字节-128 到 127
int2 或 4 字节-32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647
unsigned int2 或 4 字节0 到 65,535 或 0 到 4,294,967,295
short2 字节-32,768 到 32,767
unsigned short2 字节0 到 65,535
long4 字节-2,147,483,648 到 2,147,483,647
unsigned long4 字节0 到 4,294,967,295

关于存储,在计算机中是以二进制形式存储的,也就是说计算机只认识0 或者 1

以char为例,1字节(byte)=8位(bit),存储大小对应的是2的8次方,即256个位置,刚好存放256个数。

4.2 浮点类型

类型存储大小值范围精度
float4 字节1.2E-38 到 3.4E+386 位有效位
double8 字节2.3E-308 到 1.7E+30815 位有效位
long double16 字节3.4E-4932 到 1.1E+493219 位有效位

4.3 void 类型

类型描述
函数返回为空C 中有各种函数都不返回值,或者您可以说它们返回空。不返回值的函数的返回类型为空。例如 void exit (int status);
函数参数为空C 中有各种函数不接受任何参数。不带参数的函数可以接受一个 void。例如 int rand(void);
指针指向 void类型为 void * 的指针代表对象的地址,而不是类型。例如,内存分配函数 void *malloc( size_t size ); 返回指向 void 的指针,可以转换为任何数据类型。

5 C 变量

5.1 变量定义

变量定义:变量类型 变量名1, 变量名2, 变量名3;

int i, j, k;

注意,命名不能取保留的关键字,必须以下划线或大小写字母开头!

5.2 变量声明

变量声明:一般使用extern关键字来声明,声明表示该变量已经定义,告诉别人该变量已经定义。

// main.c
int add(int a, int b);// 声明
int a;			// 全局
int main(void)
{
    add(1, 2);
    return 0;
}
// 定义
int add(int a, int b)
{
	return (a + b);
}
// 说明i已经在其他C文件被定义了,在这里声明是为了使用它
extern int i;

一般定义了,也就声明了,同一个变量,只能定义一次,但可以在外部文件被声明多次。

// a.c
extern int a;// 外部声明
...

5.3 左值与右值

  • 左值是指向存储空间的表达式,比如变量a,b。
  • 右值是具体的数值,比如数值10, 20。
// 有效语句
int a = 10;
int b = a;

// 无效语句
10 = 20;
10 = a;

6 C 常量

常量,即固定不变的值,定义后不能修改。

6.1 整数常量

整数常量一般为十进制、八进制或十六进制。

前缀用来指定进制,0x或0X表示十六进制,0表示八进制,无前缀则默认为十进制。

后缀用来指定无符号或长整数,U表示无符号整数(unsigned),L表示长整数(long),后缀大小写随意,但一般为大写。

85         /* 十进制 */
0213       /* 八进制 */
0x4b       /* 十六进制 */
30         /* 整数 */
30U        /* 无符号整数 */
30L        /* 长整数 */
30UL       /* 无符号长整数 */

6.2 浮点常量

浮点常量由整数部分、小数点、小数部分和指数部分组成,可以用小数形式或指数形式来表示浮点常量。

3.14159       /* 合法的 */
314159E-5L    /* 合法的 */
510E          /* 非法的:不完整的指数 */
210f          /* 非法的:没有小数或指数 */
.e55          /* 非法的:缺少整数或分数 */

E代表10为底数,-5为指数,后缀为L代表 long double,后缀为F代表 float

6.3 字符常量

字符常量可以为单个的普通字符(‘x’)、一个转义序列(‘\t’),或一个通用字符(‘\u02C0’)。

printf("123\r\n456");
123
456
printf("123\r456");
123
   456
	
转义序列含义
\\ 字符
’ 字符
"" 字符
?? 字符
\a警报铃声
\b退格键
\f换页符
\n换行符
\r回车
\t水平制表符
\v垂直制表符
\ooo一到三位的八进制数
\xhh . . .一个或多个数字的十六进制数

6.4 字符串常量

字符串常量是由""括起来的,一个字符串可以包含普通的字符、转义序列和通用的字符。

也可以使用空格做分隔符,把很长的字符串常量进行分行。

// 下面三种定义都是一样的效果
"hello,world"

"hello,\
world"
    
"hello," "w" "orld"

6.5 定义常量

6.5.1 #define 预处理器
#define PI		3.14
#define WIDTH 	10
#define LENGTH	20
6.5.2 const 关键字
const int i = 10;// const修饰,表示不能被改
str = "123";// 重要
int func(const char *str)
{
    // 只给看看 123
    str = 456
    str -> 123
    ...
}

7 C 运算符

7.1 算术运算符

以A=10,B=20为例:

printf("A++ = %d\n", A++);// 10	
printf("A = %d\n", A);	// 11
运算符描述实例
+把两个操作数相加A + B 将得到 30
-从第一个操作数中减去第二个操作数A - B 将得到 -10
*把两个操作数相乘A * B 将得到 200
/分子除以分母B / A 将得到 2
%取模运算符,整除后的余数B % A 将得到 0
++自增运算符,整数值增加 1A++ 将得到 11
自减运算符,整数值减少 1A-- 将得到 9

注意,如果打印A++,那么打印值仍为10,A++表示使用后再加1,而如果是打印++A,那么打印值为11,表示先加1,然后再使用。

7.2 关系运算符

运算符描述实例
==检查两个操作数的值是否相等,如果相等则条件为真。(A == B) 为假。
!=检查两个操作数的值是否相等,如果不相等则条件为真。(A != B) 为真。
>检查左操作数的值是否大于右操作数的值,如果是则条件为真。(A > B) 为假。
<检查左操作数的值是否小于右操作数的值,如果是则条件为真。(A < B) 为真。
>=检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。(A >= B) 为假。
<=检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。(A <= B) 为真。

7.3 逻辑运算符

以A=0,B=1为例:

运算符描述实例
&&称为逻辑与运算符。如果两个操作数都非零,则条件为真。(A && B) 为假。
||称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。(A || B) 为真。
!称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。!(A && B) 为真。

7.4 位运算符

&|^(与、或、异或)的真值表如下:

pqp & qp | qp ^ q
00000
01011
11110
10011

7.5 赋值运算符

运算符描述实例
=简单的赋值运算符,把右边操作数的值赋给左边操作数C = A + B 将把 A + B 的值赋给 C
+=加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数C += A 相当于 C = C + A
-=减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数C -= A 相当于 C = C - A
*=乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数C *= A 相当于 C = C * A
/=除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数C /= A 相当于 C = C / A
%=求模且赋值运算符,求两个操作数的模赋值给左边操作数C %= A 相当于 C = C % A
<<=左移且赋值运算符C <<= 2 等同于 C = C << 2
>>=右移且赋值运算符C >>= 2 等同于 C = C >> 2
&=按位与且赋值运算符C &= 2 等同于 C = C & 2
^=按位异或且赋值运算符C ^= 2 等同于 C = C ^ 2
|=按位或且赋值运算符C |= 2 等同于 C = C | 2
int c = 4; // 4 --- 0000 0100
c = c << 2;// 	--- 0001 0000
c = c & 2;// 0000 0100
		  // 0000 0010
		  // 0000 0000

7.6 杂项运算符

运算符描述实例
&返回变量的地址。&a; 将给出变量的实际地址。
*指向一个变量。*a; 将指向一个变量。
? :条件表达式如果条件为真 ? 则值为 X : 否则值为 Y

7.7 运算符优先级

类别运算符结合性
后缀() [] -> . ++ - -从左到右
一元+ - ! ~ ++ - - (type)* & sizeof从右到左
乘除* / %从左到右
加减+ -从左到右
移位<< >>从左到右
关系< <= > >=从左到右
相等== !=从左到右
位与 AND&从左到右
位异或 XOR^从左到右
位或 OR|从左到右
逻辑与 AND&&从左到右
逻辑或 OR||从左到右
条件?:从右到左
赋值= += -= *= /= %=>>= <<= &= ^= |=从右到左
逗号,从左到右

8 C 判断

C 语言中,判断决定了程序会执行哪一部分,条件为真时应该执行什么,为假时又应该执行什么。

语句描述
if 语句一个 if 语句 由一个布尔表达式后跟一个或多个语句组成。
if…else 语句一个 if 语句 后可跟一个可选的 else 语句,else 语句在布尔表达式为假时执行。
嵌套 if 语句您可以在一个 ifelse if 语句内使用另一个 ifelse if 语句。
switch 语句一个 switch 语句允许测试一个变量等于多个值时的情况。
嵌套 switch 语句您可以在一个 switch 语句内使用另一个 switch 语句。
switch (s1)
{
    case 1:
        switch (s2)
        {
            case 4:break;
            default:break;
        }
        break;
    case 2:break;
    case 3:break;
    default:break;
}

9 C 循环

9.1 循环类型

循环类型描述
while 循环当给定条件为真时,重复语句或语句组。它会在执行循环主体之前测试条件。
for 循环多次执行一个语句序列,简化管理循环变量的代码。
do…while 循环除了它是在循环主体结尾测试条件外,其他与 while 语句类似。

9.2 循环控制语句

控制语句描述
break 语句终止循环switch 语句,程序流将继续执行紧接着循环或 switch 的下一条语句。
continue 语句告诉一个循环体立刻停止本次循环迭代,重新开始下次循环迭代。
goto 语句将控制转移到被标记的语句。但是不建议在程序中使用 goto 语句。

10 C 函数

10.1 函数定义

// 函数返回类型 + 函数名 + 函数参数
int function(int para)
{
    /* 函数主体 */
    ...
    rertun 0;
}

10.2 函数声明

//  函数返回类型 + 函数名 + 函数参数 + ;
int function(int para);

10.3 函数调用

// 函数名 + 函数实参
function(10);

10.4 函数参数

调用类型描述
传值调用该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数不会影响实际参数。
传址调用通过指针传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。
int x = 3, y = 4;
// 传值	交换失败!
swap1(x, y);
int swap1(int a, int b)
{
    int t;
    t = a;
    a = b;
    b = t;
}
// 传址	交换成功!
swap2(&x, &y);
int swap2(int *p1, int *p2)
{
    int t;
    t = *p1;
    *p1 = *p2;
    *p2 = t;
}

11 C 数组

一些数据元素的组合,按照顺序存储在内存中,通过下标索引来访问数据元素。(集合)

int a;
int a[5] = {1,2,3,4,5};
a[0] -- a[4]

11.1 声明并初始化数组

int a[5] = {1, 2, 3, 4, 5};

11.2 访问数组元素

for (int i = 0;i < 5;i++)
{
    printf("a[%d] = %d\n", i, a[i]);
}
/*
	a[0] = 1
	a[1] = 2
	a[2] = 3
	a[3] = 4
	a[4] = 5
*/

12 C 指针

12.1 指针定义与使用

int *p = NULL;			// 定义一个指针变量 p,可存放地址
int a = 3;				// 定义一个整型变量 a,存放的值为3
p = &a;					// 将 a 的地址赋给 p
printf("*p = %d\n", *p);// *p 为解引用,找到 p 存放的 a 地址然后打印里面的值
/*
实例讲解:
int *p 		给一个抽屉取名为p,我们叫它抽屉p,它存的的是int *类型,也就是地址
int a = 3 	给一个抽屉取名为a,我们叫它抽屉a,它存的是int类型,也就是整数类型
p = &a 		往抽屉p里放了一把抽屉a的钥匙
*p 			打开抽屉p取东西,取出来的是抽屉a的钥匙,所以打印*p,实际上就是打印抽屉a里存的值
*/

12.2 NULL 指针

int *p = NULL;	// 没有NULL,p就是一个野指针
p = (int *)malloc(sizeof(int));
if (NULL == p)
{
    return -1;
}
printf("p = 0x%p\n", p);	// p = 0x0

赋为 NULL 值的指针被称为空指针,它指向地址为0的内存,而在操作系统中,地址为0的内存是保留的,是不允许被访问的。

12.3 二重指针

二重指针其实就是嵌套了一层。

int *p1 = NULL;				// 给一个抽屉取名为p1,里面放的是int *类型,是地址
int **p2 = NULL;			// 给一个抽屉取名为p2,里面放的是int **类型,也是地址
int a = 3;					// 给一个抽屉取名为a,里面放的是int类型,也就是整数类型
p1 = &a;					// 往抽屉p1里放抽屉a的钥匙
p2 = &p1;					// 往抽屉p2里放抽屉p1的钥匙
printf("*p2 = %p\n", *p2);	// 打开抽屉p2取东西,取出来的是抽屉p1的钥匙,是地址
printf("**p2 = %d\n", **p2);// 打开抽屉p2,拿到抽屉p1的钥匙后,再去打开抽屉p1,取出来的就是a,是整数

12.4 函数指针

函数指针,从字面意思上来看,就是存储函数地址的指针变量。

int add(int x, int y)
{
    return (x + y);
}
int main(void)
{
    int (*pFunc)(int, int) = add;
    int sum = pFunc(1, 2);
    printf("sum = %d\n", sum);		// sum = 3
    return 0;
}

12.5 回调函数

回调函数其实是函数指针的一个应用,当你调用函数A时,需要把函数B当作A的参数传进去,那么此时,函数B就叫回调函数。

#include <stdlib.h>  
#include <stdio.h>
 
// 往数组里填充随机值
void populate_array(int *array, int arraySize, int (*getNextValue)(void))
{
    for (int i = 0; i < arraySize; i++)
        array[i] = getNextValue();// 假设我有个函数,已经实现了,而且就存在函数指针里
}
 
// 获取随机值	回调函数
// 回调函数可以自由修改,比如设置随机值范围
// 回过头去调用的函数
// rand()%100 + 1	[1, 100]
int getNextRandomValue(void)
{
    return rand();// 返回一个随机值,系统函数
}
 
int main(void)
{
    int myarray[10];
    // 往数组里填充 10 个随即值
    // &a[0]	--- 数组首元素地址
    // myarray  --- 数组地址
    populate_array(myarray, 10, getNextRandomValue);
    for(int i = 0; i < 10; i++) {
        printf("%d ", myarray[i]);
    }
    printf("\n");
    return 0;
}

13 C 字符串

char str1[6] = {'h', 'e', 'l', 'l', 'o', '\0'};		// 输出 hello
char str2[] = "hello";								// 输出 hello
函数目的
strcpy(s1, s2);复制字符串 s2 到字符串 s1。
strcat(s1, s2);连接字符串 s2 到字符串 s1 的末尾。
strlen(s1);返回字符串 s1 的长度。
strcmp(s1, s2);如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。
strchr(s1, ch);返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
strstr(s1, s2);返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。

14 C 可变参数

double getAverage(int num, ...)
{
    va_list args;					// 定义一个va_list变量 args
    double sum = 0.0;
    int i;
    va_start(args, num);			// 初始化参数列表 num代表传入参数总数
    for (i = 0;i < num;i++)
    {
        sum += va_arg(args, int);	// va_arg可以访问参数列表里的每一个参数
    }
    va_end(args);					// 清理分配给va_list变量的内存
    return sum/num;					
}

15 C 命令行参数

// ./test.exe 10 20
// argc = 3
// argv[0] = ./test.exe
// argv[1] = 10
// argv[2] = 20
int main( int argc, char *argv[] )  
{
   for (int i = 0;i < argc;i++)
   {
       printf("argv[%d] = %s\n", i, argv[i]);
   }
    return 0;
}

16 C 内存管理

函数描述
void *calloc(int num, int size);在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是0。
void free(void *address);该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。
void *malloc(int num);在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。
void *realloc(void *address, int newsize);该函数重新分配内存,把内存扩展到 newsize。
char *p = (char *)malloc(100*sizeof(char));	// 动态分配内存
if (NULL == p)								// 判断分配结果
{
    printf("malloc error!\n");
    return -1;
}
memset(p, 0, 100);							// 清理分配的内存
...											// 使用分配的内存,如复制字符串之类的
free(p);									// 释放内存

17 C 作用域规则

17.1 局部变量

// 声明于函数或块内部的变量
int main(void)
{
    int a;		// 局部变量声明
    a = 10;		// 局部变量初始化
    return 0;
}

17.2 全局变量

int g;			// 定义与函数外部(一般在程序顶部),任意函数都能访问 
int main(void)
{
    int a = 10, b = 20;
    g = a + b;
    printf("g = %d\n", g);
    return 0;
}

注意:在函数内,如果全局变量名和局部变量名相同,会使用局部变量值(就近原则)。

18 C 枚举(enum)

// 枚举也是一种数据类型,字面意思就是把一个个可能用到的数据都列出来
// 比如定义一星期
// 1. 采用宏定义方式
#define MON	1
#define TUE	2
#define WED	3
#define THU 4
#define FRI 5
#define SAT 6
#define SUN	7
// 2. 采用枚举方式(更简洁)
enum DAY
{
    MON = 1,	// 默认从0开始的,这里让 MON = 1
    TUE,
    WED,
    THU,
    FRI,
    SAT,
    SUN
};
enum DAY day;
day = WED;
printf("day = %d\n", day);	// day = 3

19 C 结构体

// 结构体变量初始化
struct EmployeeInfo
{
	int id;					// 职工编号
	char name[20];			// 职工姓名
}employee = {123, "Jack"};
// 访问结构体成员	使用 . 来访问
printf("id:%d, name:%s\n", employee.id, employee.name);

// 指向结构的指针
void printEmployee(struct EmployeeInfo *employee)
{
    printf("id: %d, name:%s\n", employee->id, employee->name);
}
int main(void)
{
    struct EmployeeInfo em1;
    em1.id = 123;
    strcpy(em1.name, "Jack");
    printEmployee(&em1);		// 通过传地址来输出信息
    return 0;
}

20 C 递归

递归就是函数自己调用自己。

// 斐波那契数列 1 1 2 3 5 8 13 21 34
int fibonaci(int i)
{
    if (0 == i)
    {
        return 0;
    }
    if (1 == i)
    {
        return 1;
    }
    return fibonaci(i-1) + fibonaci(i-2);
}    
for (int i = 1;i < 10;i++)
{
    printf("%d\n", fibonaci(i));
}

21 C 排序算法

21.1 冒泡排序

void swap(int *p1, int *p2)
{
	int temp;
	temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}
void bubble_sort(int arr[], int len) 
{
    int i, j;
    for (i = 0; i < len - 1; i++)			// 比较趟数,最多len-1趟
    {
        for (j = 0; j < len - 1 - i; j++)	// 每趟比较次数
        {
            if (arr[j] > arr[j + 1]) 		// 需要交换
            {
                swap(&arr[j], &arr[j + 1]);
            }
        }
    }
}

21.2 选择排序

void swap(int *p1, int *p2)
{
	int temp;
	temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}
void select_sort(int arr[], int len) 
{
    int i, j, min;
 
    for (i = 0 ; i < len - 1 ; i++) 
    {
        min = i;                  		// 记录最小值,第一个元素默认最小
        for (j = i + 1; j < len; j++)	// 访问未排序的元素
        {
            if (a[j] < a[min])    		// 找到目前最小值
            {
                min = j;    			// 记录最小值
            }
        }
        if(min != i)					// 需要交换
        {
           swap(&a[min], &a[i]);
        }
    }
}
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

GentleTK

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

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

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

打赏作者

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

抵扣说明:

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

余额充值