Glib库使用 (1)

1. 简介

        GLib-2.0的GLib部分提供了许多有用的数据类型、宏、类型转换、字符串实用程序、文件实用程序、主循环抽象等等,具体包含:字符串操作、文件操作、数据校验、编解码、字符集转换、随机数生成器、命令行解析器、xml解析器、正则表达式、单链表、双链表、 数组、指针数组、双端队列、哈希表、平衡二叉树、N维树、泛型、主循环、多线程、线程池、异步队列、内存分配、内存片段、错误系统、测试框架等等一系列功能。

2. 环境搭建

2.1 下载安装

下载链接:

wget http://ftp.acc.umu.se/pub/GNOME/sources/glib/2.45/glib-2.45.2.tar.xz
 

解压:

tar -xf glib-2.45.2.tar.xz

编译:

cd ./glib-2.45.2

./configure

make

make install

编译问题解决:

  1. zlib库问题

configure: error: *** Working zlib library and headers not found ***

解决:
yum install libffi-devel

2.2 配置glib库开发环境

  1. 配置PATH环境变量
vi /etc/profile    //添加内容如下
export PATH=/usr/local/glib/bin:$PATH
  1. 配置glib第三方库的C的头文件搜索路径
vi ~/.bash_profile  
C_GLIB_PATH=/usr/local/glib/include/glib-2.0:/usr/local/glib/lib/glib-2.0/include
export C_INCLUDE_PATH=$C_GLIB_PATH:$C_INCLUDE_PATH
  1. 配置glib三方库的链接库文件搜索路径
vi ~/.bash_profile 
export LD_LIBRARY_PATH=/usr/local/glib/lib:$LD_LIBRARY_PATH
export LIBRARY_PATH=/usr/local/glib/lib:$LIBRARY_PATH

2.3 测试验证

gcc glib_version.c -o glib_version -lglib-2.0

/*
**程序描述:获取glib库的版本号。
*/
#include <glib.h>
 
int main()
{
    const gchar *str;
    str=glib_check_version(GLIB_MAJOR_VERSION,GLIB_MINOR_VERSION,GLIB_MICRO_VERSION);
    if(str!=NULL)
        g_print("%s\n",str);
    g_print("Current Glib Version: %u.%u.%u\n", GLIB_MAJOR_VERSION,GLIB_MINOR_VERSION,GLIB_MICRO_VERSION);
    return 0;
}

3. glib库的使用

3.1 数据结构

3.1.1 字符串

gchar* g_strdup(const gchar* str);
// 功能:复制字符串
// 参数:要复制的字符串
// 返回值:复制后的新字符串

// 示例:
const gchar* original = "Hello, world!";
gchar* duplicate = g_strdup(original);
g_print("Duplicate string: %s\n", duplicate);


gchar* g_strjoin(const gchar* separator, ...);
// 功能:连接多个字符串
// 参数1:用于分隔各个字符串的分隔符
// 可变参数:要连接的多个字符串,以 NULL 结尾
// 返回值:连接后的新字符串

// 示例:
const gchar* str1 = "Hello";
const gchar* str2 = "world!";
gchar* result = g_strjoin(" ", str1, str2, NULL);
g_print("Joined string: %s\n", result);


gint g_strcmp0(const gchar* str1, const gchar* str2);
// 功能:比较字符串
// 参数1:第一个要比较的字符串
// 参数2:第二个要比较的字符串
// 返回值:0 表示字符串相等,正数表示 str1 大于 str2,负数表示 str1 小于 str2

// 示例:
const gchar* str1 = "abc";
const gchar* str2 = "def";
gint result = g_strcmp0(str1, str2);
if (result == 0) {
    g_print("Strings are equal\n");
} else if (result > 0) {
    g_print("str1 is greater than str2\n");
} else {
    g_print("str1 is less than str2\n");
}


gchar** g_strsplit(const gchar* string, const gchar* delimiter, gint max_tokens);
// 功能:将字符串拆分为子字符串数组
// 参数1:要拆分的字符串
// 参数2:用于指定拆分位置的分隔符
// 参数3:最大拆分数,-1 表示不限制拆分数
// 返回值:拆分后的字符串数组

// 示例:
const gchar* string = "Hello,world,!";
const gchar* delimiter = ",";
gchar** tokens = g_strsplit(string, delimiter, -1);
for (gint i = 0; tokens[i] != NULL; i++) {
    g_print("Token %d: %s\n", i, tokens[i]);
}
g_strfreev(tokens); // 释放拆分后的字符串数组

const gchar* g_strstr_len(const gchar* haystack, gssize haystack_len, const gchar* needle);
// 功能:在指定长度的字符串中查找子字符串
// 参数1:要查找的字符串
// 参数2:字符串的长度
// 参数3:要查找的子字符串
// 返回值:指向子字符串在字符串中的位置的指针,或者 NULL 表示未找到

// 示例:
const gchar* haystack = "Hello, world!";
const gchar* needle = "world";
const gchar* result = g_strstr_len(haystack, -1, needle);
if (result != NULL) {
    g_print("Substring found at position: %ld\n", result - haystack);
} else {
    g_print("Substring not found\n");
}

3.1.2 单链表

GSList* g_slist_alloc (void);
// 功能:分配一个新的单链表
// 返回值:分配的单链表的指针

GSList* g_slist_append (GSList *list, gpointer data);
// 功能:将元素添加到单链表的末尾
// 参数1:要添加元素的单链表
// 参数2:要添加的元素数据
// 返回值:更新后的单链表的指针

GSList* g_slist_prepend (GSList *list, gpointer data);
// 功能:将元素添加到单链表的开头
// 参数1:要添加元素的单链表
// 参数2:要添加的元素数据
// 返回值:更新后的单链表的指针

GSList* g_slist_insert (GSList *list, gpointer data, gint position);
// 功能:在指定位置插入元素到单链表中
// 参数1:要插入元素的单链表
// 参数2:要插入的元素数据
// 参数3:要插入的位置(从0开始计数)
// 返回值:更新后的单链表的指针

GSList* g_slist_remove (GSList *list, gconstpointer data);
// 功能:从单链表中移除指定的元素
// 参数1:要移除元素的单链表
// 参数2:要移除的元素数据
// 返回值:更新后的单链表的指针

GSList* g_slist_remove_all (GSList *list, gconstpointer data);
// 功能:从单链表中移除所有与指定元素匹配的元素
// 参数1:要移除元素的单链表
// 参数2:要移除的元素数据
// 返回值:更新后的单链表的指针

void g_slist_free (GSList *list);
// 功能:释放单链表及其元素的内存
// 参数:要释放的单链表

guint g_slist_length (GSList *list);
// 功能:获取单链表的长度(元素个数)
// 参数:要获取长度的单链表
// 返回值:单链表的长度
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <glib.h>

int main()
{
    GSList *slist = NULL;
    GSList *st;
    int nums[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

    int i;
    for (i = 0; i < 10; i++) {
        //尾插法添加数据
        slist = g_slist_append(slist, &nums[i]);
    }

    //插入数据7表示插入位置,8表示插入数值
    slist = g_slist_insert(slist, &nums[8], 7);
    slist = g_slist_reverse(slist); //翻转链表

    //根据元素删除
    slist = g_slist_remove(slist, &nums[8]);

    for (i = 0; i < 10; i++) {
        //头插法添加数据
        slist = g_slist_prepend(slist, &nums[i]);
    }

    for (i = 0; i < 11; i++) {
        //打印数据
        st = g_slist_nth(slist, i);
        g_printf("%d ", *(gint *)st->data);
    }
	g_printf("\n");
    
    g_slist_free(slist);

    return 0;
}
9 8 7 6 5 4 3 2 1 0 9

3.1.3 动态数组

  1. 数组类似于标准 C 数组,不同之处在于它们会自动增长 作为元素的添加。
  2. 数组元素可以是任何大小(尽管一个数组的所有元素都是 大小相同),数组可以自动清除为'0'和 零端接。
  3. 要创建新数组,请使用 g_array_new()。
  4. 若要向数组添加元素,请使用 g_array_append_val()g_array_append_vals()g_array_prepend_val()g_array_prepend_vals()。
  5. 要访问数组的元素,请使用 g_array_index()。
  6. 要设置数组的大小,请使用 g_array_set_size()。
  7. 要释放数组,请使用 g_array_free()。
typedef struct {
    gchar * data;
    guint len;
} GArray;
GArray* g_array_new (gboolean zero_terminated, gboolean clear_, guint element_size);
// 功能:  新建一个数组
// 参数1: 如果数组末尾应该有一个额外的元素,则为TRUE。 设置为 0
// 参数2: 如果应自动清除 GArray 元素,则为TRUE当它们被分配时
// 参数3: 每个元素的大小(以字节为单位)
// 返回值:GArray*

GArray* g_array_sized_new (gboolean zero_terminated,gboolean clear_, guint element_size, guint reserved_size);
// 功能:  新建一个数组 (避免频繁重新分配空间)
// 参数1: 如果数组末尾应该有一个额外的元素,则为 TRUE。 设置为 0
// 参数2: 如果应自动清除 GArray 元素,则为 TRUE 当它们被分配时
// 参数3: 每个元素的大小(以字节为单位)
// 参数4: 预分配的元素数
// 返回值:GArray*

#define g_array_append_val  (a,v)
GArray* g_array_append_vals (GArray *array, gconstpointer data, guint len);
// 功能: 将 len 元素添加到数组的末尾
// 参数1:数组指针
// 参数2:指向要追加到数组末尾的元素的指针
// 参数3:要追加的元素数
// 返回值:GArray*

#define g_array_prepend_val (a,v)
GArray* g_array_prepend_vals (GArray *array, gconstpointer data, guint len);
//功能: 将 len 元素添加到数组的开头 (比尾插慢,需要移动数据)
// 参数1:数组指针
// 参数2:指向要追加到数组末尾的元素的指针
// 参数3:要追加的元素数
// 返回值:GArray*

#define g_array_insert_val (a,i,v)
GArray* g_array_insert_vals (GArray *array, guint index_, gconstpointer data, guint len);
// 功能: 将元素插入到给定索引处的数组中
// 参数1:数组指针
// 参数2:要放置元素的索引
// 参数3:指向要插入的元素的指针
// 参数4:要插入的元素数
// 返回值:GArray*

GArray* g_array_remove_index (GArray *array, guint index_);
// 功能: 从 GArray 中删除给定索引处的元素。 以下元素下移一个位置。
// 参数1:数组指针
// 参数2:要删除的元素的索引
// 返回值:GArray*

GArray* g_array_remove_index_fast (GArray *array, guint index_);
// 功能:从 GArray 中删除给定索引处的元素。数组中的最后一个元素用于填充空格,因此此函数不保留 GArray 的顺序。但它比 g_array_remove_index快。
// 参数1:数组指针
// 参数2:要删除的元素的索引
// 返回值:GArray*

GArray* g_array_remove_range (GArray *array, guint index_, guint length);
// 功能: 从 GArray 中删除从给定索引开始的给定数量的元素。移动以下元素以缩小差距。
// 参数1:数组指针
// 参数2:要删除的第一个元素的索引
// 参数3:要删除的元素数
// 返回值:GArray*

void g_array_sort (GArray *array, GCompareFunc compare_func);
// 功能:使用 compare_func 对 GArray 进行排序,这应该是--样式比较函数(返回小于零的第一个参数小于第二个参数,
//       零表示相等,如果第一个参数大于第二个参数,则大于)。qsort()
// 参数1:数组指针
// 参数2:比较功能

void g_array_sort_with_data (GArray *array, GCompareDataFunc compare_func, gpointer user_data);
// 功能: 与g_array_sort()类似,但比较函数接收额外的用户数据论点。
// 参数1:数组指针
// 参数2:比较功能
// 返回值:要传递给compare_func的数据

#define g_array_index (a,t,i)
GArray* g_array_set_size (GArray *array, guint length);
// 功能: 设置数组的大小,必要时展开它。 如果创建的数组clear_设置为 TRUE,则新元素设置为 0。
// 参数1:数组指针
// 参数2:GArray 的新大小。
// 返回值:GArray*

gchar* g_array_free  (GArray *array, gboolean free_segment);
// 功能: 释放为 GArray分配的内存
//        如果free_segment为TRUE,则释放保存元素的内存块 也。
//        传递 FALSE 如果你想释放GArray包装器但保留用于其他地方的基础数组。
// 参数1:数组指针
// 参数2:如果为 TRUE,则实际元素数据也会被释放
// 返回值:元素数据(如果free_segment为FALSE,否则为 NULL。 应使用 g_free() 释放元素数据。
#include <stdio.h>
#include <glib.h>

// 比较函数,用于排序
gint compare_func(gconstpointer a, gconstpointer b) {
    gint value_a = *((gint*)a);
    gint value_b = *((gint*)b);
    return value_b - value_a;
}

int main() {
    int i = 0; 
    //创建动态数组
	GArray* garray = g_array_new(FALSE, FALSE, sizeof(int));

    // 向动态数组中添加元素
	for (i = 0; i < 10; i++) {
        g_array_append_val(garray, i);
    }

    // 遍历并打印动态数组的元素
    for (i = 0; i < garray->len; i++)
    {
        int val = g_array_index(garray, int, i);
        printf("%d ", val);
    }
	printf("\n");

	// 修改动态数组中的元素
    g_array_index(garray, int, 5) = 42;
    
	// 排序动态数组
    g_array_sort(garray, compare_func);

    // 遍历并打印动态数组的元素
    for (i = 0; i < garray->len; i++)
    {
        int val = g_array_index(garray, int, i);
        printf("%d ", val);
    }
	printf("\n");
    
    // 释放动态数组的内存
    g_array_free(garray, TRUE);
    
    return 0;
}
0 1 2 3 4 5 6 7 8 9 
42 9 8 7 6 4 3 2 1 0 

3.1.4 哈希表

GHashTable* g_hash_table_new (GHashFunc hash_func, GEqualFunc key_equal_func);
// 功能:创建一个新的哈希表
// 参数1:哈希函数,用于计算键的哈希值
// 参数2:键相等函数,用于比较键的相等性
// 返回值:新创建的哈希表的指针

void g_hash_table_insert (GHashTable *hash_table, gpointer key, gpointer value);
// 功能:向哈希表中插入键值对
// 参数1:要插入的哈希表
// 参数2:要插入的键
// 参数3:要插入的值

gboolean g_hash_table_contains (GHashTable *hash_table, gconstpointer key);
// 功能:检查哈希表中是否包含指定的键
// 参数1:要检查的哈希表
// 参数2:要检查的键
// 返回值:如果哈希表包含指定的键,则返回TRUE;否则返回FALSE

gpointer g_hash_table_lookup (GHashTable *hash_table, gconstpointer key);
// 功能:查找指定键在哈希表中对应的值
// 参数1:要查找的哈希表
// 参数2:要查找的键
// 返回值:键对应的值,如果键不存在,则返回NULL

gboolean g_hash_table_lookup_extended (GHashTable *hash_table, gconstpointer lookup_key, gpointer *orig_key, gpointer *value);
// 功能:查找指定键的值,并返回键、值以及是否找到的信息
// 参数1:要查找的哈希表
// 参数2:要查找的键
// 参数3:用于接收原始键的指针变量
// 参数4:用于接收键对应的值的指针变量
// 返回值:如果找到了指定键,则返回TRUE;否则返回FALSE

void g_hash_table_remove (GHashTable *hash_table, gconstpointer key);
// 功能:从哈希表中移除指定的键及其对应的值
// 参数1:要移除键值对的哈希表
// 参数2:要移除的键

void g_hash_table_destroy (GHashTable *hash_table);
// 功能:销毁哈希表,并释放相关的内存
// 参数:要销毁的哈希表
#include <stdio.h>
#include <glib.h>

int main() {
    GHashTable *hashTable = g_hash_table_new(g_str_hash, g_str_equal);

    // 向哈希表中插入键值对
    g_hash_table_insert(hashTable, "key1", "value1");
    g_hash_table_insert(hashTable, "key2", "value2");
    g_hash_table_insert(hashTable, "key3", "value3");

    // 查找指定键的值
    const gchar *value = g_hash_table_lookup(hashTable, "key2");
    if (value != NULL) {
        printf("Value for key2: %s\n", value);
    }

// 查找指定键的值和键
    {
        gpointer key = NULL;
        gpointer value = NULL;
        gboolean found = g_hash_table_lookup_extended(hashTable, "key2", &key, &value);
        if (found) {
            printf("Found key2: Key: %s, Value: %s\n", (gchar*)key, (gchar*)value);
        } else {
            printf("Key2 not found\n");
        }
    }
    // 从哈希表中移除键值对
    g_hash_table_remove(hashTable, "key3");

    // 销毁哈希表
    g_hash_table_destroy(hashTable);

    return 0;
}
Value for key2: value2
Found key2: Key: key2, Value: value2

3.2 base64编解码

gchar* g_base64_encode (const guchar *data, gsize len);
// 功能:对给定数据进行Base64编码
// 参数1:要编码的数据
// 参数2:数据的长度
// 返回值:Base64编码后的字符串

guchar* g_base64_decode (const gchar *str, gsize *out_len);
// 功能:对给定的Base64编码字符串进行解码
// 参数1:要解码的Base64编码字符串
// 参数2:用于返回解码后数据的长度
// 返回值:解码后的数据

gchar* g_base64_encode_inplace (guchar *data, gsize len);
// 功能:对给定数据进行原地(in-place)Base64编码
// 参数1:要编码的数据
// 参数2:数据的长度
// 返回值:Base64编码后的字符串(与输入数据指向同一地址)

guchar* g_base64_decode_inplace (gchar *str, gsize *out_len);
// 功能:对给定的Base64编码字符串进行原地(in-place)解码
// 参数1:要解码的Base64编码字符串
// 参数2:用于返回解码后数据的长度
// 返回值:解码后的数据(与输入字符串指向同一地址)

gsize g_base64_encode_step (const guchar *data, gsize len, gboolean break_lines, gchar *result, gint *state, gint *save);
// 功能:对给定数据进行Base64编码的步进式操作
// 参数1:要编码的数据
// 参数2:数据的长度
// 参数3:是否在编码结果中插入换行符
// 参数4:存储编码结果的缓冲区
// 参数5:编码操作的状态
// 参数6:编码操作的保存值
// 返回值:编码结果的长度

gsize g_base64_decode_step (const gchar *str, gsize len, guchar *result, gint *state, guint *save);
// 功能:对给定的Base64编码字符串进行解码的步进式操作
// 参数1:要解码的Base64编码字符串
// 参数2:字符串的长度
// 参数3:存储解码结果的缓冲区
// 参数4:解码操作的状态
// 参数5:解码操作的保存值
// 返回值:解码结果的长度
#include <stdio.h>
#include <glib.h>

int main() {
    const gchar *data = "Hello, World!";
    gsize len = strlen(data);

    // Base64 编码
    gchar *encoded = g_base64_encode((const guchar *)data, len);
    printf("Base64 Encoded: %s\n", encoded);

    // Base64 解码
    gsize decoded_len = 0;
    guchar *decoded = g_base64_decode(encoded, &decoded_len);
    printf("Base64 Decoded: %.*s\n", (int)decoded_len, decoded);

    // 释放内存
    g_free(encoded);
    g_free(decoded);

    return 0;
}
Base64 Encoded: SGVsbG8sIFdvcmxkIQ==
Base64 Decoded: Hello, World!

3.3 数据完整校验

GChecksum* g_checksum_new(GChecksumType checksum_type);
// 功能:创建一个新的校验和对象
// 参数:校验和类型,可选值为 G_CHECKSUM_MD5、G_CHECKSUM_SHA1 等
// 返回值:新创建的校验和对象

void g_checksum_update(GChecksum *checksum, const guchar *data, gssize length);
// 功能:更新校验和
// 参数1:校验和对象
// 参数2:要更新的数据
// 参数3:要更新的数据的长度

void g_checksum_get_digest(GChecksum *checksum, guint8 *buffer, gsize *digest_len);
// 功能:获取校验和的结果
// 参数1:校验和对象
// 参数2:缓冲区,用于存储校验和结果
// 参数3:指向存储校验和结果长度的变量的指针

void g_checksum_free(GChecksum *checksum);
// 功能:释放校验和对象
// 参数:校验和对象
#include <glib.h>

int main() {
    // 创建 MD5 校验和对象
    GChecksum* checksum = g_checksum_new(G_CHECKSUM_MD5);

    // 数据分块
    const guchar* data1 = (const guchar*)"Hello, ";
    const guchar* data2 = (const guchar*)"world!";
    gssize length1 = strlen((const gchar*)data1);
    gssize length2 = strlen((const gchar*)data2);

    // 分块更新校验和
    g_checksum_update(checksum, data1, length1);
    g_checksum_update(checksum, data2, length2);

    // 获取最终校验和
    gsize digest_length = 16;  // MD5 校验和长度为 16 字节
    guint8 digest[16];  // 创建缓冲区来存储校验和结果
    g_checksum_get_digest(checksum, digest, &digest_length);
    g_print("MD5 digest: ");
    guint i;
    for (i = 0; i < digest_length; i++) {
        g_print("%02x", digest[i]);
    }
    g_print("\n");

    // 释放校验和对象
    g_checksum_free(checksum);

    return 0;
}
MD5 digest: 6cd3556deb0da54bca060b4c39479839

3.4 定时器

GSource* g_timeout_source_new(guint interval);
// 功能:创建一个基于时间的新源对象(定时器)
// 参数:定时器的触发间隔(以毫秒为单位)
// 返回值:新创建的源对象(定时器)

GSource* g_timeout_source_new_seconds(guint interval);
// 功能:创建一个基于时间的新源对象(定时器)
// 参数:定时器的触发间隔(以秒为单位)
// 返回值:新创建的源对象(定时器)

guint g_timeout_add(guint interval, GSourceFunc function, gpointer data);
// 功能:将函数添加为基于时间的源对象(定时器)的回调函数
// 参数1:定时器的触发间隔(以毫秒为单位)
// 参数2:回调函数,当定时器触发时调用
// 参数3:传递给回调函数的用户数据
// 返回值:用于标识定时器的ID

guint g_timeout_add_seconds(guint interval, GSourceFunc function, gpointer data);
// 功能:将函数添加为基于时间的源对象(定时器)的回调函数
// 参数1:定时器的触发间隔(以秒为单位)
// 参数2:回调函数,当定时器触发时调用
// 参数3:传递给回调函数的用户数据
// 返回值:用于标识定时器的ID

void g_source_destroy(GSource* source);
// 功能:销毁源对象
// 参数:要销毁的源对象(定时器)

gboolean g_source_remove(guint id);
// 功能:从主循环中移除源对象(定时器)
// 参数:要移除的源对象的ID
// 返回值:如果成功移除则为TRUE,否则为FALSE

guint g_timeout_add_full(gint priority, guint interval, GSourceFunc function, gpointer data, GDestroyNotify notify);
// 功能:以指定的优先级创建一个基于时间的源对象(定时器)并添加回调函数
// 参数1:源对象的优先级
// 参数2:定时器的触发间隔(以毫秒为单位)
// 参数3:回调函数,当定时器触发时调用
// 参数4:传递给回调函数的用户数据
// 参数5:在定时器被移除时调用的销毁通知函数
// 返回值:用于标识定时器的ID
#include <stdio.h>
#include <glib.h>

gboolean timer1_callback(gpointer data) {
    printf("Timer 1 callback called.\n");
    return TRUE;
}

gboolean timer2_callback(gpointer data) {
    printf("Timer 2 callback called.\n");
    return TRUE;
}

int main() {
    guint timer1_id, timer2_id;

    // 创建定时器1,每隔1秒触发一次
    timer1_id = g_timeout_add(1000, timer1_callback, NULL);
    printf("Timer 1 created. Timer ID: %u\n", timer1_id);

    // 创建定时器2,每隔2秒触发一次
    timer2_id = g_timeout_add(2000, timer2_callback, NULL);
    printf("Timer 2 created. Timer ID: %u\n", timer2_id);

    // 启动主循环以触发定时器回调
    g_main_loop_run(g_main_loop_new(NULL, FALSE));

    // 移除定时器1
    g_source_remove(timer1_id);
    printf("Timer 1 removed.\n");

    // 移除定时器2
    g_source_remove(timer2_id);
    printf("Timer 2 removed.\n");

    return 0;
}
Timer 1 created. Timer ID: 1
Timer 2 created. Timer ID: 2
Timer 1 callback called.
Timer 1 callback called.
Timer 2 callback called.
  • 11
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值