当前位置: 首页 > news >正文

字符串和内存函数(2)

#define _CRT_SECURE_NO_WARNINGS

#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>

1.10 strerror

    char* strerror(int errnum);

    返回错误码,所对应的错误信息。

    /* strerror example : error list */
    #include <stdio.h>
    #include <string.h>
    #include <errno.h>
//必须包含的头文件
    int main()
    {
        FILE* pFile;
        pFile = fopen("unexist.ent", "r");
        if (pFile == NULL)
            printf("Error opening file unexist.ent: %s\n", strerror(errno));
      
 //errno: Last error number
        return 0;
    }

    字符转换:

    int tolower(int c);
    int toupper(int c);

    /* isupper example */
    #include <stdio.h>
    #include <ctype.h>

    int main()
    {
        int i = 0;
        char str[] = "Test String.\n";
        char c;
        while (str[i])
        {
            c = str[i];
            if (isupper(c))
                c = tolower(c);
            putchar(c);
            i++;
        }
        return 0;
    }

C语言的库函数,在运行时若发生错误,就会将错误码存在一个变量中,这个变量就是 errno。
其中,错误码通常都是数字,如:1 2 3 4 5 等等
由于其无法直观的被理解,则我们就需要将这些错误码翻译成我们能直接看懂的错误信息。

那么,这一步骤应该如何实现呢?

这里我们就要用到这样一个函数了:

strerror

    char* strerror(int errnum);

    get points to error message string

    函数头文件为:<string.h>

    因为每一个错误码都对应着一个错误信息,而函数 strerror :只要给它传递一个 int 类型的数字,它就能返回其所对应的错误信息的起始地址(错误码对应的错误信息字符串的首地址),返回类型为 char* 。

如以下代码所示:

#include<stdio.h>
#include<string.h>

int main()
{
    printf("%s\n", strerror(0));
//No error
    printf("%s\n", strerror(1));//Operation not permitted 操作被拒绝
    printf("%s\n", strerror(2));//No such file or directory 没有这样的文件或文件夹
    printf("%s\n", strerror(3));//No such process 没有这个进程
    printf("%s\n", strerror(4));//Interrupted function call 函数调用被中断
    printf("%s\n", strerror(5));//Input / output error 输入/输出错误
    printf("%s\n", strerror(6));//No such device or address 设备不存在或地址无效
    return 0;
}

在这段代码中,strerror函数分别返回了错误码 0,1,2,3,4,5,6 代表的错误信息的 字符串 的 首地址。

在实际工程中,这些错误码我们是并不知道的(这里仅是用0,1,2,3,4,5,6举例)。

这个函数函数应该怎么用呢?

正如前文所言,strerror 函数也会在运行过程中发生错误时,就会将错误码存在一个变量中,这个变量就是 errno。

接下来我们将通过以下代码来展开讲解:


#include<stdio.h>
#include<string.h>

    //补充
    //fopen
    //FILE * fopen(const char* filename,const char* mode);
    //                           文件名                打开方式
    //如果打开文件成功,就返回一个有效指针;
    //如果打开文件失败,就返回一个NULL指针。
    //头文件为:<stdio.h>

int main()
{
  
 //打开文件
    FILE* pf = fopen("text.txt", "r");//text.txt 文件名,r 打开方式
    //注意:如果没有指定路径的话,系统就会默认为当前工程的路径底下。

    if (pf == NULL)
    {
        printf("打开文件失败\n");
        return 1;
    }
 
   //读文件
    //关闭文件

    fclose(pf);

    return 0;
}

//这里打印结果只能显示"打开文件失败",但这里又是为什么打印结果是"打开文件失败"呢?
//是访问权限不够呢?还是文件不存在呢?还是其他方面的原因?
//单从这段代码来看我们无从得知。

因此,这里就要用到前面所提及的 strerror 函数。
故,我们需要进行如下修改:

#include<stdio.h>//printf、fopen、fclose 头文件
#include<string.h>//strerror、perror 头文件
#include<errno.h>//errno 头文件

int main()
{

    //打开文件
    FILE* pf = fopen("text.txt", "r");
    if (pf == NULL)
    {
        printf("%s\n", strerror(errno));
        
//通过 错误码 通过 strerror 函数 将 具体错误信息 打印出来
        //错误码 则是 来自 errno
        //要使用 errno 这个全局变量,要记得包含其头文件 <errno.h>

        return 1;
    }
  
 //读文件
    //关闭文件

    fclose(pf);

    return 0;
}


拓展

perror

    打印错误信息

    void perror(const char* str);

    头文件为:<string.h>

应用

#include<stdio.h>

int main()
{
    FILE* pf = fopen("text.txt", "r");

    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }
    fclose(pf);
    return 0;
}

总结:

perror 是直接打印错误信息,在打印错误信息之前,会先打印自定义的信息。如下图所示:

我们也可以认为:perror = printf + strerror

字符分类函数

① islower

    int islower(int c);

    头文件:<ctype.h>

    判断字符是否是小写字母 a~z  Check if character is lowercase letter.

如果是小写字母,函数则返回一个非0数字;
如果不是小写字母,函数则返回 0。

#include<stdio.h>
#include<ctype.h>

int main()
{
    int ret = islower('a');
    printf("%d\n", ret);
    return 0;
}


② isdigit

    int isdigit(int c);

    头文件:<ctype.h>

    判断字符是否是十进制数字字符 0~9 Check if character is decimal digit.

如果是数字字符,函数则返回一个非0数字;
如果不是数字字符,函数则返回 0。

#include<stdio.h>
#include<ctype.h>

int main()
{
    int ret = isdigit('3');
    printf("%d\n", ret);
    return 0;
}


③isupper

    int isupper(int c);

    头文件:<ctype.h>

    判断字符是否是大写字母A~Z

如果是大写字母,函数则返回一个非0数字;
如果不是大写字母,函数则返回 0。

    /* isupper example */
    #include <stdio.h>
    #include <ctype.h>

    int main()
    {
        int i = 0;
        char str[] = "Test String.\n";
        char c;
        while (str[i])
        {
            c = str[i];
            if (isupper(c))
                c = tolower(c);
            putchar(c);
            i++;
        }
        return 0;
    }


字符转换

只针对字母

tolower(int c);//大写字母转小写
toupper(int c);//小写字母转大写

//传字符或ASCII码值都可以(传字符就是传的对应的ASCII码值)

头文件为:<ctype.h>

应用

tolower

int main()
{
    printf("%c\n", tolower('S'));
    return 0;
}

toupper

int main()
{
    printf("%c\n", toupper('a'));
    return 0;
}


功能展示:

将字符串完全转换成大写 / 小写

Chen Xianglin PKU

方法一 :

int main()
{
    char arr[] = "Chen Xianglin PKU";
    int i = 0;
    while (arr[i])
    {
        if (isupper(arr[i]))
        {
            printf("%c", tolower(arr[i]));
         
   //注意:这里并没有改掉arr[]内的元素,只是在将是arr[]内的大写字母转换成对应的
            //        小写字母,并将其返回来。
            //同理,toupper 函数 也无法修改字符串内容。

        }
        else
        {
            printf("%c", arr[i]);
        }
        i++;
    }
    return 0;
}

方法二:

int main()
{
    char arr[] = "Chen Xianglin PKU";
    int i = 0;
    while (arr[i])
    {
        if (isupper(arr[i]))
        {
            arr[i] = tolower(arr[i]);
        }
        printf("%c", arr[i]);
        i++;
    }
    return 0;
}


总结:

①字符分类函数

函数      如果他的参数符合下列条件就返回真
iscntrl   任何控制字符
isspace   空白字符:空格‘ ’,换页‘\f’,换行'\n',回车‘\r’,制表符'\t'或者垂直制表符'\v'
isdigit   十进制数字 0~9
isxdigit  十六进制数字,包括所有十进制数字,小写字母a~f,大写字母A~F
islower   小写字母a~z
isupper   大写字母A~Z
isalpha   字母a~z或A~Z
isalnum   字母或者数字,a~z, A~Z, 0~9
ispunct   标点符号,任何不属于数字或者字母的图形字符(可打印)
isgraph   任何图形字符
isprint   任何可打印字符,包括图形字符和空白字符


②字符转换函数

int tolower(int c);
int toupper(int c);


③两类函数的头文件皆为:<ctype.h>。


④两类函数都是针对字符的。

针对内存

Memcpy
Memmove
Memcmp
Memset


1.11 memcpy

    void* memcpy(void* destination, const void* source, size_t num);
    //void*:不知道拷贝类型、返回类型是什么,因此用 void*。
    //num:拷贝字节数。

    函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
    这个函数在遇到 '\0' 的时候并不会停下来。
    如果source和destination有任何的重叠,复制的结果都是未定义的。

由此可知,其与strcpy的用法有所差异:

strcpy —— 仅用于字符串拷贝

memcpy —— 可用于多种类型的数据拷贝

    /* memcpy example */
    #include <stdio.h>
    #include <string.h>

    struct {
        char name[40];
        int age;
    } person, person_copy;
    int main()
    {
        char myname[] = "Pierre de Fermat";
          /* using memcpy to copy string: */
        memcpy(person.name, myname, strlen(myname) + 1);
        person.age = 46;
          /* using memcpy to copy structure: */
        memcpy(&person_copy, &person, sizeof(person));
        printf("person_copy: %s, %d \n", person_copy.name, person_copy.age);
        return 0;
    }

示例:

#include<stdio.h>
#include<string.h>

int main()
{
    int arr1[] = { 0,1,2,3,4,5,6,7,8,9 };
    int arr2[10] = { 0 };
    memcpy(arr2, arr1, 20);//20/sizeof(int)=5
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", arr2[i]);
    }printf("\n");
  
 //0 1 2 3 4 0 0 0 0 0
    memcpy(arr2, &arr1[3], 20);
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", arr2[i]);
    }printf("\n");
    
//3 4 5 6 7 0 0 0 0 0
    memcpy(arr2, arr1 + 3, 20);
  
 //arr1+3:数组名=数组首元素地址,arr1+3=数组内下标为3的元素的地址:&arr[3]
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", arr2[i]);
    }printf("\n");

    //3 4 5 6 7 0 0 0 0 0
    memcpy(arr2, arr1 + 3, 17);
    //07 00 00 00 —— 只拷贝了 前面三个元素 和 07
    //07 和 原 arr2 数组中的对应元素的 后三个字节相结合,组成新的 7。

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

    //3 4 5 6 7 0 0 0 0 0
    return 0;
}


memcpy的模拟实现——my_memcpy

方法一:

#include<stdio.h>
#include<assert.h>

void* my_memcpy(void* dest, const void* src, size_t num)
{
    assert(dest && src);
    void* ret = dest;
    assert(ret);
    while (num--)

    //num 初始为 20,num-- 返回 20(判断为真),然后 num 立即变为 19
    {
  
     *(char*)dest = *(char*)src;
        //dest 仍是 void* 类型、src仍是 const void* 类型

        //错误代码:

        //①
        //dest++, src++;
        //错误原因:
        //注意前面 (char*) 强制类型转换只进行了一次

        //②
        //(char*)dest++,(char*)src++;
            //错误原因:
                //①如在这段代码中:int* dest=&p;(char*)dest++;
                //    C语言中,dest是不是只强制类型转换了一次。
                //    且在++之后,dest跳过了4个字节,然后对++前的dest进行强制类型转换,
                //    而++后的dest仍然是int*类型的。
                //②将 dest++ 返回的原始值(指针)转换为 char* 类型。
                //③转换仅发生一次,且仅作用于 dest++ 的返回值(原始地址值)。
                //④由于 ++ 与 (char*) 的优先级都属于第二优先级,结合方式 为 从右到左。
                //⑤执行顺序是:先执行 dest++,再对递增前的原始值进行强制类型转换。

                //⑥ dest 在 强转 前为 void* 类型,因此 ++ 不合法。
        dest = (char*)dest + 1, src = (char*)src + 1;
      
 //dest仍是void* 类型、src仍是 const void* 类型
    }
    return ret;

    //注意:
    //函数返回类型是 void* ,不是 void 类型。

}
int main()
{
    int arr1[] = { 0,1,2,3,4,5,6,7,8,9 };
    int arr2[10] = { 0 };
    my_memcpy(arr2, arr1 + 3, 20);
    for (size_t i = 0; i < sizeof(arr2) / sizeof(arr2[i]); i++)
    {
        printf("%d ", arr2[i]);
    }
//3 4 5 6 7 0 0 0 0 0
    return 0;
}


拓展:

分析以下这两段代码是否正确:


void my_memcpy(void* dest, const void* src, size_t num)
{
    assert(dest && src);
    *(char*)dest = *(char*)src;
    dest = (char*)dest + 1, src = (char*)src + 1;
}


void my_memcpy(int* dest, const int* src, size_t num)
{
    assert(dest && src);
    *(char*)dest = *(char*)src;
    dest = (char*)dest + 1, src = (char*)src + 1;
}

分析:

第一段代码:使用 void* 参数

void my_memcpy(void* dest, const void* src, size_t num) {
    assert(dest && src);
    *(char*)dest = *(char*)src;
    dest = (char*)dest + 1;
 // ✅ 合法
    src = (char*)src + 1;    // ✅ 合法
}

正确性分析:

1.    拷贝操作:✅ 正确

        拷贝1字节数据(语法正确)

2.    指针移动:

        o    dest = (char*)dest + 1:✅ 合法
            void* 可以接受任何指针类型的赋值(包括 char* )
        o    src = (char*)src + 1:✅ 合法
            const void* 可以接受 char* 赋值(C标准允许)

3.    类型安全性:✅ 安全

        所有类型转换和赋值都符合C标准

4.    问题:

        o    缺少循环,只能拷贝1字节(功能不完整)
        o    参数是值传递,修改不会影响调用者的指针

第二段代码:使用 int* 参数

void my_memcpy(int* dest, const int* src, size_t num) {
    assert(dest && src);
    *(char*)dest = *(char*)src;
 // ✅ 正确
    dest = (char*)dest + 1;      // ❌ 错误
    src = (char*)src + 1;        // ❌ 错误
}

正确性分析:

1.    拷贝操作:✅ 正确

        拷贝1字节数据(语法正确)

2.    指针移动:

        o    dest = (char*)dest + 1:❌ 编译错误
            右边是 char* 类型
            左边是 int* 类型
            不能将 char* 赋给 int* (类型不兼容)
        o    src = (char*)src + 1:❌ 编译错误
            右边是 char* 类型
            左边是 const int* 类型
            类型不兼容 + const 限定符问题

3.    类型安全性:❌ 危险

        o    试图将字节指针赋给整型指针
        o    可能导致未定义行为(内存对齐问题)

关键对比:

特性                void* 版本                                        int* 版本
编译结果        ✅ 成功编译                                    ❌ 编译失败(类型错误)
dest 移动       ✅ 合法(void * 接受任何指针)    ❌ 错误(char * → int* )
src 移动         ✅ 合法                                           ❌ 错误(char * → const int* )
类型安全性    ✅ 安全                                           ❌ 危险
参数通用性    ✅ 高(接受任意指针)                  ❌ 低(仅接受 int 指针)
标准符合性    ✅ 完全符合 C 标准                        ❌ 违反类型规则

为什么第二段代码错误?

1.    类型系统冲突:

        C语言禁止在不同类型指针间直接赋值(除了 void* 的特定情况)

2.    内存对齐风险:

        int* dest = ...;
        dest = (char*)dest + 1; // 现在 dest 可能指向未对齐地址

        o    访问未对齐的 int* 指针在某些架构上会导致崩溃(如ARM)
        o    属于未定义行为

3.    const 正确性破坏:

        o    将非const的 char* 赋给 const int* 违反类型限定规则
        o    在第一段代码中,无论是const void* 还是void * ,都是可以接收任意类型的指针的


方法二:

#include<stdio.h>
#include<assert.h>

void* my_memcpy(void* dest, const void* src, size_t num)
{
    assert(dest && src);
    void* ret = dest;
    assert(ret);
    while (num--)
    {
        *(char*)dest = *(char*)src;
        ++(char*)dest, ++(char*)src;
        
//对比(char*)dest++,(char*)src++;,其正确的原因:
        //①执行顺序是:先执行 ++dest,再对递增后的值进行强制类型转换。
        //②在表达式 ++(char*)dest; 中,++ 操作的是 (char*)dest 的返回值(临时结果),
        //    而不是直接操作 dest 变量本身。

    }
    return ret;
}
int main()
{
    int arr1[] = { 0,1,2,3,4,5,6,7,8,9 };
    int arr2[10] = { 0 };
    my_memcpy(arr2, arr1 + 3, 20);
    for (size_t i = 0; i < sizeof(arr2) / sizeof(arr2[i]); i++)
    {
        printf("%d ", arr2[i]);
    }
//3 4 5 6 7 0 0 0 0 0
    return 0;
}

思考:

如果要将 arr1[] 中的前五个元素——0 1 2 3 4,拷贝到第四个元素到第八个元素这一段,用表达式:my_memcpy(arr1 + 3, arr1, 20); 可以实现吗?为什么?

示例:

#include<stdio.h>
#include<assert.h>

void* my_memcpy(void* dest, const void* src, size_t num)
{
    assert(dest && src);
    void* ret = dest;
    assert(ret);
    while (num--)
    {
        *(char*)dest = *(char*)src;
        ++(char*)dest, ++(char*)src;
    }
    return ret;
}
void test()
{
    int arr1[] = { 0,1,2,3,4,5,6,7,8,9 };
    my_memcpy(arr1 + 3, arr1, 20);
    for (size_t i = 0; i < sizeof(arr1) / sizeof(arr1[i]); i++)
    {
        printf("%d ", arr1[i]);
    }
}
int main()
{
    test();
    return 0;
}

//打印结果为:
//0 1 2 0 1 2 0 1 8 9

回答:

由上可知,用该表达式无法实现。其原因如下:
因为 arr1[0]~arr1[4] 与 arr1[3]~arr1[7] 两段有重叠部分——arr1[3], arr1[4],
因此,当 arr1[0]、arr1[1] 在赋值给 arr1[3]、arr1[4] 后,两段中的 arr1[3]、arr1[4] 的值均被改变,
3、4 被改成 0、1,也因此在后续的拷贝过程中拷贝的是已经被拷贝过的内容,因此该表达式无法实现。


思考:

既然有重叠的两部分,那又应该如何拷贝呢?

这就要引出我们接下来要学习的函数——memmove了。

1.12 memmove

    实现重叠字符串拷贝

    void* memmove(void* destination, const void* source, size_t num);

    和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
    如果源空间和目标空间出现重叠,就得使用memmove函数处理。

    /* memmove example */
    #include <stdio.h>
    #include <string.h>

    int main()
    {
        char str[] = "memmove can be very useful......";
        memmove(str + 20, str + 15, 11);
        puts(str);
        return 0;
    }

示例:

#include<stdio.h>
#include<string.h>

void test()
{
    int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
    memmove(arr + 3, arr, 20);
    for (size_t i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
    {
        printf("%d ", arr[i]);
    }
}
int main()
{
    test();
    return 0;
}

memmove 的模拟实现 —— my_memmove

#include<stdio.h>
#include<assert.h>

void* my_memmove(void* dest, const void* src, size_t num)
{
    void* ret = dest;
    assert(dest && src);

    //判断 dest 与 src 的位置
    //如果 dest 在 src 的右侧,则 从右往左 拷贝;
    //如果 dest 在 src 的左侧,则 从左往右 拷贝。

    if (dest < src)
    {
      
 // 从左往右 拷贝
        while (num--)
        {
            *(char*)dest = *(char*)src;
            ++(char*)dest, ++(char*)src;
        }
    }
    else
    {
      
 // 从右往左 拷贝
        while (num--)
          
 //以第一次循环为例:
            //num 初始为 20,num-- 返回 20(判断为真),然后 num 立即变为 19

        {
            *((char*)dest + num) = *((char*)src + num);
          
 //这里的 dest 和 src 加的 num 都是 19
        }
    }
    return ret;
}
void test()
{
    int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
    my_memmove(arr + 3, arr, 20);
    for (size_t i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
    {
        printf("%d ", arr[i]);
    }
}
int main()
{
    test();
    return 0;
}

注意:

在 VS 编译器中,memcpy 也能对重叠字符串进行拷贝。


1.13 memcmp

    int memcmp(const void* ptr1,
        const void* ptr2,
        size_t num);

    比较从ptr1和ptr2指针开始的num个字节
    返回值如下:

    Return Value

    Returns an integral value indicating the relationship between the content of the memory blocks :

 return value indicates
< 0the first byte that does not match in both memory blocks has a lower value in ptr1 than in ptr2(ifevaluated agunsigned char values)
= 0the contents of both memory blocks are equal
> 0the first byte that does not match in both memory blocks has a greater value in ptr1 than in ptr2(ifevaluated as unsiqned char values)

    /* memcmp example */
    #include <stdio.h>
    #include <string.h>

    int main()
    {
        char buffer1[] = "DWgaOtP12df0";
        char buffer2[] = "DWGAOTP12DF0";
        int n;
        n = memcmp(buffer1, buffer2, sizeof(buffer1));
        if (n > 0) printf("'%s' is greater than '%s'.\n", buffer1, buffer2);
        else if (n < 0) printf("'%s' is less than '%s'.\n", buffer1, buffer2);
        else printf("'%s' is the same as '%s'.\n", buffer1, buffer2);
        return 0;
    }

示例:

#include<stdio.h>
#include<string.h>

int main()
{
    int arr1[] = { 1,2,3 };
    int arr2[] = { 1,2,4 };
    int ret = memcmp(arr1, arr2, 8);
    printf("%d\n", ret);
//0
    ret = memcmp(arr1, arr2, 9);
    printf("%d\n", ret);
//-1
    int arr3[] = { 1,2,2 };
    ret = memcmp(arr1, arr3, 9);
    printf("%d\n", ret);
//1
    return 0;
}


1.14 memset

    void* memset(void* ptr, int value, size_t num);

    Fill block of memory :
    Sets the first num bytes of the block of memory pointed by ptr to the specified value(interpreted as an unsigned char).

    内存设置函数:以字节为单位来设置内存中的数据。

    Parameters :

ptrPointer to the block of memory to fill.
valueValue to be set.The value is passed as an int, but the function fills the block of memory using the unsigned char.conversion of this value.
numNumber of bytes to be set to the value.size_t is an unsigned integral type.

用法:

以字符串 "Welcome to PKU,Christophe." 为例,是否能将其中的前14个全为'P',后面内容不变的字符串?

#include<stdio.h>
#include<string.h>

int main()
{
    char arr[] = "Welcome to PKU,Christophe.";
    //错误代码:memset(arr, "PKU", 14);
    memset(arr, 'P', 14);
    printf("%s\n", arr);
    memset(arr+15, 'C', 10);
    printf("%s\n", arr);
    return 0;
}

思考:

以数组 arr[10] = { 0 }; 为例,可否利用 memset 函数,得出表达式 memset(arr, 1, 40); 将数组元素都改为 1 呢?为什么?

#include<stdio.h>
#include<string.h>

int main()
{
    int  arr[10] = { 0 };
    memset(arr, 1, 40);
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

①回答

经代码检验可知,该表达式并不能实现我们想要的结果。

②回答:

经代码调试可知,十六进制下,该表达式实际上是使 arr[i] 4个字节的每个字节都由 00 变为 01,因此最终 arr[i] 的结果实际上为:0x01010101

因此,这里就进一步验证了 memset 函数是以字节为单位进行内存数据的设置的。同时,也因此,其第二个参数不宜过大 ( - 255n≤ value ≤ 255n,n为非0整数,当 n != -1 || n != 1时,会发生阶段,因此value不宜过大)。


 

http://www.xdnf.cn/news/988075.html

相关文章:

  • MacBook M1 Pro下载安装MySql
  • Redis分布式缓存(RDB、AOF、主从同步)
  • force命令的使用
  • 图文教程——Deepseek最强平替工具免费申请教程——国内edu邮箱可用
  • 传统Web应用和RESTful API模式
  • javaee初阶-多线程
  • C++基础
  • Oracle集群OCR磁盘组掉盘问题处理
  • echart pie label.rich 颜色设置与项目同色
  • matlab红外与可见光图像配准算法
  • Flask 报错修复实战:send_file() got an unexpected keyword argument ‘etag‘
  • 什么是Power Distribution Unit(PDU)以及智能PDU:应用的演变历程
  • C#简单线程启动的几种方法总结
  • Windows平台网络通信
  • SQL Server 查询数据库中所有表中所有字段的数据类型及长度
  • 从“字对字“到“意对意“:AI翻译正在重塑人类的语言认知模式
  • 从代码学习深度学习 - 全局向量的词嵌入(GloVe)PyTorch版
  • 医疗行业双碳战略升维:从合规达标到价值创造的转型路径
  • JavaWeb(JavaBean预习)
  • 智慧园区智能化整体解决方案
  • c#实现绝对路径和相对路径的转换
  • 黑马python(四)
  • CentOS Stream 9——RustDesk基础版自建教程(Docker)
  • <script> 标签的 async 与 defer 属性详解
  • 《Linux C编程实战》笔记番外:如何避免子进程成为僵尸进程
  • 阿里云unbantu、Flask部署模型的一个错误
  • 安卓+苹果端签名教程
  • SiteAzure:文章删除后,前台还能搜索到
  • HarmonyOS - UIObserver(无感监听)
  • TF-IDF算法的代码实践应用——关键词提取、文本分类、信息检索