c语言小知识点小计

c语言小知识点小计

1、运算符的优先级

++运算符的优先级是和指针解引用*的优先级相同的,但在代码运行中执行顺序是从后往前的。因此下面代码

int a[10] = {1,2,3,4};
int* arr = a;
printf("%d",*arr++);//访问的值是2
//注意:printf("%d",*a++);这种语法是错误的,因为a是个常量(指针常量),不可更改

2、 strcpy和memcpy的使用

strcpy只用于字符串数组之间的拷贝,因为strcpy是以\0来判断结束的。使用方式如下:

char a[] = {"this is a test!"};
char b[100];
strcpy(b,a);//不像memcpy需要用户确定拷贝的大小,strcpy以'\0'为结束符判断拷贝的结束

memcpy()能够拷贝任意类型的数组,但需要手动确定拷贝的大小

int a[10] = {1,2,3,4,5};
int b[10];
strcpy(b,a,sizeof(a));//memcpy()需要手动确定拷贝数据的大小

3、数组和数组的首地址

由于数组名是一个指针常量,因此指针的指向是不能更改的,也就是说数组名的指向的地址是不能更改的。因此不能有数组名++的操作如下图代码:

int a[10] = {1,2,3,4} ;
//a++这种写法是错误的,当需要对数组的元素进行访问时,
//可以用下标或者是重新定义一个指针来对数组进行访问。如下所示:
//方法一
for (int i = 0;i<10;i++)
{
printf("数组中的元素是%d",a[i]);
}
//方法二,虽然不能使用++但是可以使用(地址+偏移值的方式)
for (int i = 0;i<10;i++)
{
printf("数组中的元素是%d",*(a+i));
}
//方法三
int *arr = a;
for(int i = 0;i<10;i++)
{
printf("数组中的元素是%d"*arr+i)
}

4、sizeof()的使用

sizeof可以获取当前变量的大小,如下面示例所见:
如果想获取数组的的所占内存大小只能使用sizeof(数组名)

int  arr[10] = {1,2,3,4};
pirnft("数组的大小是%d"sizeof(a))//获取的是10个int数据占据的空间大小
printf("数组指针的大小是%d"sizeof(&a);//获取的是指针的大小

5、指向常量和常量指针

所谓的常量指针是指常量的值不能通过指针来修改,但指针的指向是可以修改的;
所谓的指针常量是指指针的指向不可修改,在第一次绑定赋值后就不能在去绑定其他变量了。示例如下:

//指针常量
    int b1 = 10;
    int *const ddd = &b1; // 指针常量,指针的指向不能更改,需在初始化的时候就赋值
    //ddd = &c1;错误,因为指针常量的指向是不能修改的
    *ddd = 20;//语法正确
//常量指针
	const int *ccc;
    int c1 = 20;
    int d1 = 30;
    ccc = &c1;
    ccc = &d1;
//    *ccc = 40;语法错误,常量指针不能用过指针来修改变量的值,当可以修改指向
    cout << "数组的大小是:" << *ccc << endl;

6、不同系统中变量的大小

  • 在32位系统中指针的大小占4字节:32位
  • 在64位系统中指针的大小占8字节:64位
  • char———1字节
    short———2字节
    int————4字节
    long————4字节
    long long——8字节
    float————4字节
    double———8字节

7、c语言中局部变的内存分配

局部变量在函数执行时在栈区分配空间,并在函数执行结束后释放空间。

8、结构体的内存大小与字节对齐

  • 可用手动更改对齐系数:#pragma pack(n),n=1,2,4,8,16 来改变这一系数,其中的 n 就是你要指定的“对齐系数”
  • 数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在 offset 为 0 的地方,以后每个数据成员的对齐按照 #pragma pack 指定的数值和这个数据成员自身长度中,比较小的那个进行。
  • 结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
  • 结合1、2可推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。
#include<stdio.h>
struct S1
{
	char a;
	int b;
	char c;
};//占12字节
struct S2
{
	int a;
	char b;
	int c;
	char d;
};//占16字节
int main()
{
    // sizeof()功能:获取了数据在内存中所占用的存储空间,以字节为单位来计数。
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	return 0;
}
  • 这里可能不好理解在此对其进行解释(假设为32位系统,因此当有跨字节时进行4字节对齐):下图中每个框代表一个字节,由于ch占一个字节因此将其方到第一字节处。由于int 占4字节跨字节了,因此进行4字节对齐。short占2个字节因此放到后面的地址空间。由于char占一个字节没跨字节不用字节对齐。
    在这里插入图片描述
    内存对齐的规则:
    在这里插入图片描述
    在这里插入图片描述

9、数组和指针的区别

  • char arr1[] = “hello”;和char *arr2 = “hello”;两句代码中出现的字符串都存储在静态存储区中。
  • char arr1[] 声明了一个名为 arr1 的字符数组,这个数组的大小正好能够存放字符串 “hello” 加上一个结尾的空字符 ‘\0’。编译器会为 arr1 在栈上分配足够的空间,以存储所有这些字符。
  • 字符串字面量 “hello” 存储在程序的常量区。
    在函数执行时,“hello” 的内容会被复制到新分配的栈空间中,也就是 arr1 数组中。
    这通常是通过一个循环来完成的,每个字符(包括结尾的空字符 ‘\0’)都会从常量区复制到 arr1 的相应位置。
int main()
{
char arr1[] = "hello";
char *arr2 = "hello";
arr1[0] = 'A';//这种写法没有问题,因为arr1是存放在栈上的变量。
printf("%c\r\n",arr1[0]);
//arr2[0] = 'A';这种写法是错误的,因为arr2是指向静态存储区的一个常量指针,不能用过指针来修改值,而且“hello”存放在静态存储区,里面的值就是不能进行修改的
printf("%c\r\n",arr2[0]);
}

10、结构体简介

结构体定义的三种方式:

  • 1、最标准的方式:

#include <stdio.h>
struct student //结构体类型的说明与定义分开。声明
{
  int age;  /*年龄*/
  float score; /*分数*/
  char sex;   /*性别*/
};
int main ()
{
  struct student a={ 20,79,'f'}; //定义
  printf("年龄:%d 分数:%.2f 性别:%c\n", a.age, a.score, a.sex );
  return 0;
}

2、不环保的方式(声明和定义一起)


#include <stdio.h>
struct student /*声明时直接定义*/
{
  int age;  /*年龄*/
  float score;  /*分数*/
  char sex;   /*性别*/
 /*这种方式不环保,只能用一次*/
} a={21,80,'n'};
int main ()
{
  printf("年龄:%d 分数:%.2f 性别:%c\n", a.age, a.score, a.sex );
}

3、最奈何人的方式(定义一次性的结构体变量)


#include <stdio.h>
struct   //直接定义结构体变量,没有结构体类型名。这种方式最烂
{
  int age;
  float score;
  char sex;
} t={21,79,'f'};

int main ()
{
  printf("年龄:%d 分数:%f 性别:%c\n", t.age, t.score, t.sex);
  return 0;
}

4、使用typedef定义结构体

用 typedef 定义新类型名来代替已有类型名,即给已有类型重新命名;
一般格式为;typedef 已有类型 新类型名;

typedef int Elem; 
typedef struct{
int date;
    .....
    .....
}STUDENT;
STUDENT stu1,stu2;
//这里也可与在前面加上结构体的名称如:
typedef struct student
{
int date;
    .....
    .....
}STUDENT;

//在定义结构体时可以直接使用:
struct student xiaoming;
//也可以直接使用:
STUDENT zhangshan;
  • 对于结构体的初始化
    结构体在没创建实例之前是不分配内存的
typedef struct student{
char name[20];
char sex[20];
int age;
}stu;

struct student stu1 = {"xiaomin","男",11};
//或者初始化时写成
stu stu2 = {
                    .name = "小张",
					.sex = "男".age = 12
			}
  • 结构体之间可以直接赋值(这里可以个数组之间不能直接赋值对比记忆)
    typedef struct  
    {
        int a;
    }stu1;
    stu1 ss1 = {10};
    stu1 ss2 = ss1;
    printf("ss2 = %d",ss2.a);
  • 结构体中的位域:(但对成员进行读写时花费的时间可能较长)

在成员变量之后使用冒号并声明这个变量将要占用的比特位将大大节省空间。例如你需要9位的一个变量来表示一个数据,但c语言中只有uint16但使用u16将浪费7位的内存,因此可以写成uint16 sign :9;

typedef struct stu
{
uint 8 flag   :1;
uint16 num    :9;
}STU;

11、c语言中的结构体

  • 内存的最小索引单元是1字节,那么你其实可以把内存比作一个超级大的「字符型数组」。在上一节我们讲过,数组是有下标的,我们是通过数组名和下标来访问数组中的元素。那么内存也是一样,只不过我们给它起了个新名字:地址。每个地址可以存放「1字节」的数据,所以如果我们需要定义一个整型变量,就需要占据4个内存单元
  • 获取某个变量的地址,使用取地址运算符&,定义指针的时候也使用了*,这里属于符号的「重用」,也就是说这种符号在不同的地方就有不同的用意:在定义的时候表示「定义一个指针变量」,在其他的时候则用来「获取指针变量指向的变量的值」。
char* pa = &a;
int* pb = &f;
printf("%c, %d\n", *pa, *pb);
  • 使用scanf(“请输入一个数%d”,&a);这里的需要输入a的地址。可以简单的理解是你想将输入的数据放到内存中的那个地方,这个地方的地址是多少。
//如果是使用数组来接收字符串就不需要加&
char arr[100];
scanf("%s",arr);//为什么不需要加&呢?因为数组名就是地址了
  • 定义指向数组的指针
char a[10];
char *p;
p = a;
p = &a[0];
//对数组的访问可以通过三中方式
 printf("*p = %d, *(p+1) = %d, *(p+2) = %d\n", *p, *(p + 1), *(p + 2));
 printf("a = %d, a+1 = %d, a+2 = %d", *a, *(a+1), *(a+2));//主要这里不能使用++运算来对地址进行改写
 printf("a = %d, a+1 = %d, a+2 = %d", a[0], a[1], a[2]);
  • 在c语言中字符串结束标识符‘\0’占一个字节

12、指针数组和数组指针

int* p1[5];//指针数组,数组中的每一个元素都是int指针类型
int*p2)[5];//数组指针,指向一个int类型的数组,且数组中有5个元素,格式和函数指针相似

#include "stdio.h"
int main(void)
{
    char* p1[5] = {
        "人生苦短,我用Python。",
        "PHP是世界上最好的语言!",
        "One more thing...",
        "一个好的程序员应该是那种过单行线都要往两边看的人。",
        "C语言很容易让你犯错误;C++看起来好一些,但当你用它时,你会发现会死的更惨。"
    };
    int i;
    for (i = 0; i < 5; i++)
    {
        printf("%s\n", p1[i]);//如果想访问,每个字符数组的某个元素可以使用p1[i][i]
    }
    return 0;
}
  • 整形指针指向数组和数组指针的区别
#include 
int main(void)
{
    int temp[5] = {1, 2, 3, 4, 5};
    int(*p2)[5] = &temp;//这里要注意,数组指针指向是数组的地址,而不是数组首元素的地址
    int i;
    for (i = 0; i < 5; i++)
    {
        printf("%d\n", *(*p2 + i));//因此这里第一次解引用时获取首元素的地址,第二次解引用是获取数据
    }
    return 0;
}

13、指针和二维数组

  • 二维数组在内存中也是线性存放的,例如int arry[3][4]其实是申请了一块连续的20字节的地址用于存放数据。
  • array作为数组的名称,显然应该表示的是数组的「首地址」。由于二维数组实际上就是一维数组的「线性拓展」,因此array应该就是指的指向包含4个元素数组的指针。指向第一个数组的首地址。
    int array[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    这里注意要访问数据时要么直接通过下标进行访问,
    要么需要通过两次解应用才能访问数据,因为第一次解引
    用获取的是内存数组的地址,第二次才是获取内部数组中的数据
      printf("二维数组中第五个元素数 = %d\r\n", array[1][0]);
      printf("二维数组中第五个元素数 = %d\r\n", *(*array+4));
      printf("二维数组中第六个元素数 = %d\r\n", *(array[1]+1));  

14、void指针

void 不能直接用来定义一个数据类型,但可以定义一个指针类型。void 类型的指针可以转换位各种类型的指针。如果void类型没有进行强制类型转换不能用于接收其他类型的指针。

  void *a;
  a = malloc(sizeof(int) * 5);
  for (int i = 0; i < 5; i++)
  {
      *(((int *)a) + i) = i;
  }
  for (int i = 0; i < 5; i++)
  {
      printf("----->%d\r\n", *(((int *)a)++));这句代码是错误的,注意这里对a进行了强制类型转换,
      系统会生成一个临时变量其生存周期为当前执行语句,
      因此无法对其进行++操作
      printf("----->%d\r\n", *((int *)a+i));
  }

14、实现函数宏的三种方式

  • {} 方式:这种方式在有if和while判断语句时仍然受判断语句控制
#define INT_SWAP(a,b)\
{                   \
 int tmp = a;    \
 a = b;          \
 b = tmp;        \
}
int main()
{
int var_a = 1;
int var_b = 2;

INT_SWAP(var_a, var_b);
printf("var_a = %d, var_b = %d\n", var_a, var_b);   // var_a = 2, var_b = 1

if (1)
 INT_SWAP(var_a, var_b);
printf("var_a = %d, var_b = %d\n", var_a, var_b);   // var_a = 1, var_b = 2
}
但当没有花括号时将导致问题,问题出现的原因是当没有花括号时判断语句后面本来是只能接一句语的。
  • do{…}while(0) 方式需要注意的是do{}while()语句必须使用分号;结尾
#define INT_SWAP(a,b)  \
do{                 \
 if (a < 0 || b < 0) \
  break;   \
    int tmp = a;     \
    a = b;           \
    b = tmp;         \
}while(0)

//因此下面代码将有问题:if (1)
if (1)
   INT_SWAP(var_a, var_b)//按正常思维来说这里确实不应该有分号,但由于这个宏定义是do{}while()结尾,因此c语言要求必须加上冒号
else
{
 printf("hello world!\n"); 
}
  • ({}) 方式
do{...}while(0) 相同,({}) 支持在无花括号且有分支的 if 语句中直接调用。例如:
#define INT_SWAP(a,b)   \
({                      \
    int tmp = a;        \
    a = b;              \
    b = tmp;            \
})
C 语言规定 ({}) 中的最后一条语句的结果为该双括号体的返回值
int a = ({1,2,3});//根据运算的优先级先算后面的括号中内容,算完后返回3

15、在c++ 中指定使用c语言的方式编译

  • 兼容性:C++语言支持函数重载,而C语言不支持。C++编译器在编译时会对函数名进行名称修饰(name mangling)以支持重载,而C编译器不会。使用extern "C"可以确保C++代码中声明的函数或变量在链接时使用C语言的名称修饰规则,这样C++程序就能正确地调用C库中的函数。
  • 链接C库:当你想在C++程序中使用C库(例如标准C库或第三方C库)时,你需要使用extern "C"来声明这些库中的函数,这样C++编译器才知道如何正确地处理这些函数的名称
extern "C" {
    // 这里可以包含C头文件,或者直接声明C函数
    #include <c_header.h>
    // 或者
    int c_function(int arg);
}
-------------------------------------------------------------------------------------------------
// C++程序
#include <iostream>
extern "C" {
    #include "c_lib.h"//这个头文件中包含的有add(int,int)函数
}

int main() {
    int result = add(5, 3); // 正确调用C库中的函数
    std::cout << "The sum is: " << result << std::endl;
    return 0;
}

16、位域

在结构体定义时,我们可以指定某个成员变量所占用的二进制位数(Bit),这就是位域。请看下面的例子:
位域定义的结构体也存在内存对齐,当前指定的位域小于当前的数据类型时(或者没有超过系统定义最小对齐字节时则紧挨着存储,否则进行字节对齐)

typedef struct bs{
    unsigned m;
    unsigned n: 4;
    unsigned char ch: 6;
};
C语言标准还规定,只有有限的几种数据类型可以用于位域。
在 ANSI C 中,这几种数据类型是 intsigned intunsigned intint 默认就是 signed int);到了 C99,_Bool 也被支持了。

  • 如果成员之间穿插着非位域成员,那么不会进行压缩。例如对于下面的 bs:
1struct bs{
2    unsigned m: 12;
3    unsigned ch;
4    unsigned p: 4;
5};

17、#error的目的是什么?

  • 可以提醒程序员定义重复定义的某些变量
#define a 10
#ifdef  a
#error "请取消定义a"
#endif

18、c语言中使用的3种死循环

while(1)
{}

for(;;)
{}

do{}while(1);

loop:...
 
goto loop;

19、static关键字

在C语言中,关键字static有三个明显的作用:

第一、在修饰变量的时候,static修饰的静态局部变量只执行一次,而且延长了局部变量的生命周期,直到程序运行结束以后才释放。

第二、static修饰全局变量的时候,这个全局变量只能在本文件中访问,不能在其它文件中访问,即便是extern外部声明也不可以。

第三、static修饰一个函数,则这个函数的只能在本文件中调用,不能被其他文件调用。Static修饰的局部变量存放在全局数据区的静态变量区。初始化的时候自动初始化为0;
(1)不想被释放的时候,可以使用static修饰。比如修饰函数中存放在栈空间的数组。如果不想让这个数组在函数调用结束释放可以使用static修饰
(2)考虑到数据安全性(当程想要使用全局变量的时候应该先考虑使用static)

20、关键字volatile有什么含意?并给出三个不同的例子。

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:

1:并行设备的硬件寄存器(如:状态寄存器)
2:一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3:多线程应用中被几个任务共享的变量

21、在嵌入式系统中将a的第三位置一和清零

#define BIT3 (0x1Ul << 3)
static int a;

void set_bit3(void)
{
    a |= BIT3;
}
void clear_bit3(void)
{
    a &= ~BIT3;
}

22、c语言中的__interrupt关键字

  • 使用这个函数定义一个中断服务子程序(ISR)
函数存在的问题:
①函数不能有返回值
②函数不能有参数
③不要在中断服务函数中做浮点运算
④不要在中断服务函数中进行耗时的操作和不可重入函数:在C语言中,函数的重入性(reentrancy)
是指一个函数在执行过程中,可以被中断,并且在稍后重新开始执行而不会产生错误的能力。
重入函数和不可重入函数的概念主要与多线程编程和中断处理相关。
__interrupt double compute_area (double radius)
{
    double area = PI * radius * radius;
    printf("\nArea = %f", area);
    return area;
}

23、c语言中的自动类型转换

  • 是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。
void foo(void)
{
    unsigned int a = 6;
    int b = -20;
    (a+b > 6) ? puts("> 6") : puts("<= 6");
}//结果是大于6

24、使用typedef定义指针类型时需要注意不能让连续定义两个指针类型,否则第二个定义失败

#define dPS struct s *
typedef struct s * tPS;
dPS p1,p2;
tPS p3,p4;
第一个扩展为
struct s * p1, p2;

25、c语言中与内存相关的错误

  • 间接应用坏指针:
scanf("%d", &value);//正确的写法\
scanf("%d", value);//系统会讲value里面的值当成地址,将数据写到这个地址中
  • 读未初始化的内存
int *a = (int *)malloc(5*sizeof(int));
for(int i = 0;i<5;i++)
{
	a[i]+=i;//这句代码是不合法的因为在申请空间后并没有对空间中的数据进行初始化,因此不能将里面的数据看成0
}
  • 栈缓冲区溢出
void buff()
{
    char buf[6];//由于当前定义的缓冲区较小,因此下面定的gets输入的字符不能大于这个值,否则会发生溢出,虽然在vs中运行没有报错但当前已经发生的栈溢出。

    gets(buf);
    return;
}
  • 假设指针和它们指向的对象大小相同
int **makeArray(int n, int m)
{
   int i;
   int **A = (int **)malloc(n * sizeof(int)); /* 注意此处语句,存在问题 */

   for(i = 0; i < n; i++)
   {
       A[j] = (int *)malloc(m * sizeof(int));
   }
   return A;
}
此程序的目的是创建一个由 n 个指针组成的数组,每个指针都指向一个包含 m 个 int 的数组。
然而,第 4 行程序代码将 sizeof(int *)写成了 sizeof(int),代码实际上创建的是一个 int 的数组。
这段代码只有在 int 和指向 int 的指针大小相同的机器上运行良好;否则,就会出现错误。

在这里插入图片描述

  • 内存越界
    代码试图访问申请空间之外的空间将导致内存越界
int **makeArray(int n, int m)
{
    int i;
    int **A = (int **)malloc(n * sizeof(int*)); /* 注意此处语句,存在问题 */

    for(i = 0; i <= n; i++) /* 注意循环终止条件:这里访问的空间总大小为n+1将造成空间越界 */
    {
        A[j] = (int *)malloc(m * sizeof(int));
    }
    return A;
}
  • * 和++ 运算符的优先级

*p++ 等价于*(p++)
如果想对指针指向的值进行自增操作需要用括号进行优先级控制
(*p)++ 这个相当于对指针指向的值进行自加

  • 寻找数组中的特定值
int * secrch(int *p,int value)
{
while(*p && *p != value)//第一分p判断当前的值是否为空,第二个用来判断当前的值是否为目标值
{
	p++;
}
return p;
}
  • 使用malloc等函数后没有释放空间造成内存泄
void leak(int n)
{
    int *x = (int *)malloc(n * sizeof(int));

    return;
}

26、c语言中几种分配内存的函数

  • malloc(size)以直接为单位分配未初始化的空间;
  • calloc(number,size)以字节为单位分配number个每个大小为size的空间,并将数据初始化为0;
  • realloc(void *ptr,size)重新分配ptr的大小,如果ptr = null则相当于malloc函数,如果size = 0相当free函数

27、c语言中内存拷贝的内存重叠现象

  • 使用memcpy(目的,源,大小)拷贝内存中的数据时可能由于内存重叠而造成影响
  • 使用memove(目的,源,大小)不会因为内存重叠而造成数据混乱
    在这里插入图片描述
void *Memcpy2(void *dest, const void *src, size_t count)  
{  
 char *d;  
 const char *s;  
   
 if (((int)dest > ((int)src+count)) || (dest < src))  
    {  
    d = (char*)dest;  
    s = (char*)src;  
    while (count--)  
        *d++ = *s++;          
    }  
 else /* overlap :存在内存的重叠*/  
    {  
    d = (char *)((int)dest + count - 1); /* 指针位置从末端开始,注意偏置 */  
    s = (char *)((int)src + count -1);  
    while (count --)  
        *d-- = *s--;  
    }  
 return dest;  
}  

28、内存分配函数 malloc 原理及实现

  • 虚拟内存地址与物理内存地址

为了简单,现代操作系统在处理内存地址时,普遍采用虚拟内存地址技术。即在汇编程序(或机器语言)层面,当涉及内存地址时,都是使用虚拟内存地址。采用这种技术时,每个进程仿佛自己独享一片2N字节的内存,其中N是机器位数。例如在64位CPU和64位操作系统下,每个进程的虚拟地址空间为264Byte。
由于在机器语言层面都是采用虚拟地址,当实际的机器码程序涉及到内存操作时,需要根据当前进程运行的实际上下文将虚拟地址转换为物理内存地址,才能实现对真实内存数据的操作。

  • 页与地址的构成

在现代操作系统中,不论是虚拟内存还是物理内存,都不是以字节为单位进行管理的,而是以页(Page)为单位。一个内存页是一段固定大小的连续内存地址的总称,具体到Linux中,典型的内存页大小为4096Byte(4K)。
在这里插入图片描述
由于每页的大小都是4k = 4096字节因此寻址时只需要12位就可以完成,因此每个地址用页号和偏移号来代表在内存中的位置在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值