C语言——指针的高级引用

news2024/11/25 11:27:35

目录

1.概述

2.虚拟内存空间

 2.1存储期限

2.2栈区管理

2.3堆区域的使用

3.动态内存分配和释放(重点)

3.1通用指针类型void

3.2内存分配malloc函数

3.2.1 malloc函数(memory allocation)(注意len*size,if(*p == NULL)!)

3.2.2不要用数组类型接收返回值

3.3 内存泄漏

3.4free函数(free(p);)

3.4.1尽量不要移动原始指针

3.4.2 不要两次free同一片内存区域,会导致未定义行为。

3.4.3 悬空指针

3.4.4 避免不可到达内存空间

4. 清零内存分配函数calloc(cleared allocation)动态分配初始化为0的数组

5. 内存重分配函数 realloc(reallocation) 数组和数据结构动态扩容收缩必备

6. 手动实现C++中的vector

6.1思路讲解

6.2头文件的使用

6.2.2 头文件保护

6.2.3 包含头文件与实现函数

6.3.1 实现vector_creat()

6.3.2vector_destroy()

6.3.3vector_push_back()


1.概述

动态内存分配

二级指针:啥叫

函数指针:啥叫

2.虚拟内存空间

 2.1存储期限

1.自动存储期限:栈,调用期间有效

2.静态存储期限:存在数据段的内容,主要指全局变量、静态局部变量以及static修饰的全局变量

3.动态存储期限:堆上的空间需要手动控制

2.2栈区管理

管理需要用到栈指针寄存器(Stack Pointer寄存器,简称SP寄存器)始终指向栈的顶部

sp+-已经确定大小的栈的空间

栈的优点: 简单高效,自动管理,线程安全(不能共享)

缺点:大小有限,不能当运行时才能确定大小的东西

堆区域弥补栈区域的缺点

栈区就是需要在编译前确定并且要很小(这点很难),所以说有时是堆区

2.3堆区域的使用

堆区域需要借助于malloc函数进行手动管理

优点:区域大,共享,灵活分配

缺点:管理繁琐,性能相比栈会差,线程 不安全(因为共享)

一般优先使用栈空间,除非不合条件,因为它的优点

3.动态内存分配和释放(重点)

动态内存分配主要应用于链式的结构像是链表,树,图。

一般来说,如无特别需求,不要在堆上为基本数据类型动态分配空间。

3.1通用指针类型void

1.可以存储任意类型数据的地址

2.将void转换为其他类型的指针在C++中需要加上显示类型,但是c语言中不需要

float* float_ptr = (float*)void_ptr;

3.不能直接进行操作,因为没有具体的类型不能通过void进行操作

进行类型的转换的时候也可能会出现问题,像是double->void->int就是错误的。

3.2内存分配malloc函数

在C语言中,想要在堆上动态分配内存空间,主要依赖三个函数来完成,它们都声明在头文件<stdlib.h>当中:

  1. malloc
  2. calloc
  3. realloc

3.2.1 malloc函数(memory allocation)(注意len*size,if(*p == NULL)!

堆空间上分配一块连续的空间,若分配成功会返回指向内存首字节地址的指针,类型为void(因此在操作之前要类型转换),若分配失败会返回NULL(因为在malloc之后都会进行判断是否成功),且malloc函数不会进行初始化(vs中 标记为cd)

int* arr_p = malloc(ARR_LEN * sizeof(int)); 

3.2.2不要用数组类型接收返回值

C语言的数组类型,其长度必须在编译时期确定,数组名本身在声明时就和一块固定大小的栈内存区域绑定。而malloc是在运行时动态分配内存的,数组类型变量显然不能用malloc函数进行初始化赋值。

在使用动态内存分配函数时,认准指针类型即可,不要考虑使用其它类型。

使用malloc可能会出现内存泄漏的问题

3.3 内存泄漏

数组和结构体是在堆上保存的,栈上保存的是指向的指针,

内存泄漏是指程序在运行过程中,未能适时释放不再使用的内存区域,导致这部分内存在程序的生命周期内始终无法被重用。

3.4free函数(free(p);)

若分配的内存不再使用,需要free函数及时释放。

1.参数必须是堆上申请内存块的地址(首字节地址),不能传递别的指针,否则会引发未定义行为。

2. free函数:只是标记区域为可用不修改,free函数不会修改传入的实参指针的指向

因为得到的是p指针的拷贝,当然不会修改指向

3.4.1尽量不要移动原始指针

尽量不要移动指向内存块的原始指针,若有移动指针的操作,可以创建副本指针来使用。

int* p = malloc(sizeof(int) * ARR_LEN);
// 定义一个临时指针用于移动指针操作  
int* tmp = p;

void (void* arr){}这是arr是可以进行移动,因为是拷贝指针

vs中 free函数之后区间会标记为dd

3.4.2 不要两次free同一片内存区域,会导致未定义行为。

往往出现在在一个函数中free过,后来忘记了又在另一个中free。

所以说free交给特定函数销毁(就像专门的人做清洁)和creat函数一样,需要注意这点!

需要考一些技巧避免:

3.4.3 悬空指针

free后的实参指针就变成了指向一片已释放区域的指针,这就是"悬空指针",使用空指针导致未定义行为。

为了避免悬空指针为程序安全带来隐患,推荐在free掉指针指向的内存块后,及时将指针置为空指针。

3.4.4 避免不可到达内存空间

会导致更严重的内存泄漏,p就是以后都不可以到达的内存块,所以应该先free q再赋值

p = malloc(...);
q = malloc(...);
p = q;

puts函数会返回字符串的长度包含末尾的/0;

c语言一般不会返回错误,所以返回值很重要

4. 清零内存分配函数calloc(cleared allocation)动态分配初始化为0的数组

最大特点:分配时会自动初始化为0,其余与malloc一致

void* calloc(size_t num, size_t size); num是元素数量, size是每个元素内存大小

因此常用于在堆上分配数组空间

//也可以分配结构体数组
typedef struct {
    int x;
    int y;
} Node;

Node* node_arr_p = calloc(3, sizeof(Node));

对比malloc:malloc性能好,calloc更安全

5. 内存重分配函数 realloc(reallocation) 数组和数据结构动态扩容收缩必备

void* realloc(void* ptr, size_t new_size); ptr:指向已分配内存,new_size新内存大小

1.表现:

ptr为空,与malloc一致(不要这样用)

size为0,与free一致(不要这样用)

其他,调整已分配内存块的大小,尽量进行在原位置扩容实在不够再复制找大的地方,扩容部分不会初始化(截断丢弃高地址端就是指针另一端 和 扩容需要复制时丢弃的部分,那部分会自动进行free

成功会返回新内存的指针,否则返回NULL(失败不会改变旧内存块

错误写法:

代码块 15. 正确使用realloc函数-演示代码1
int len = 5;
int* arr_p = calloc(len, sizeof(int));
if (calloc == NULL){
    // 分配失败处理
}
// 代码运行到这里,arr_p一定不是空指针

规范写法:

// p和arr_p指针类型一致
p = realloc(arr_p, new_size);
if (p == NULL){
   // 分配失败处理
   return 1;
}
// 代码运行到这里,realloc分配内存成功
arr_p = p;

规范行为:这样写代码既避免了(分配成功时)arr_p成为悬空指针,也不会因为realloc(分配失败)导致内存泄漏。不能用原始指针,而是用临时指针!

// 重分配内存缩减,惯用法
    int new_size = 3;
    int* tmp = realloc(arr, new_size * sizeof(int));
    if (tmp == NULL) {
        printf("realloc failed!\n");
        exit(-1);
    }
    arr = tmp;
    print_arr(arr, new_size);

    // 重分配内存扩容,惯用法
    int new_size2 = 10;
    int* tmp2 = realloc(arr, new_size2 * sizeof(int));
    if (tmp2 == NULL) {
        printf("realloc failed!\n");
        exit(-1);
    }
    arr = tmp2;

补:int* p2 = arr + size,一个指针加上数字表示从数组的下标为size开始的位置

6. 手动实现C++中的vector

数组在初始时就需要确定大小,vector可以进行动态扩容,c语言可以借助于malloc和realloc实现

6.1思路讲解

首先定义结构体

// 使用别名来命名元素类型,如果未来需要改变元素类型,只需修改这个别名即可。
// 这么做提升代码的可维护性和扩展性,这实际上是模拟了C++的泛型编程
typedef int ElementType;
//以后可能不是int类型这样,以后可以进行修改,但下文一定都用ElementType
typedef struct {
    ElementType *data;      // 指向动态分配数组的指针
    int size;    // 当前动态数组中元素的数量
    int capacity; // 动态数组当前分配的最大容量
} Vector;

还需要定义相关的操作 

// 初始化一个Vector动态数组.这实际上是模拟了C++的默认构造函数
Vector* vector_create();

// 销毁一个Vector动态数组,释放内存。这实际上模拟了C++的析构函数
void vector_destroy(Vector *v);

// 向动态数组末尾添加一个元素
void vector_push_back(Vector *v, ElementType element);

6.2头文件的使用

头文件主要用于存放以下结构:

  1. 函数的声明
  2. 结构体的定义
  3. 类型别名的定义
  4. 宏定义

头文件中进行声明,源文件中进行实现。可以实现多个源文件之间的共享函数的声明以及结构体、类型别名和宏的定义

实现模块化,复用性

6.2.2 头文件保护

头文件会互相依赖,源文件包含多个头文件,一个头文件可能会被包含多次

C/C++的头文件包含本质上是一种文本替换的过程,一个头文件被包含多次,就相当于一段代码在同一个文件中被书写多次,这在很多时候都是不允许,会引发编译错误。

c语言中头文件保护机制防止头文件出现多次

// 保护机制
#ifndef VECTOR_H
#define VECTOR_H

// 头文件中定义的函数的声明、结构体的定义、类型别名的定义等

#endif // !VECTOR_H

#pragma once  不是c/c++标准库中的内容,一般不用

宏命名时不能使用字符".",所以使用"_"替代文件后缀名中的"."

6.2.3 包含头文件与实现函数

1.使用" "   用于自定义的头文件

2.使用< > 用于标准库的头文件

6.3.1 实现vector_creat()

#define DEFAULT_CAPACITY 10 // 设置动态数组的默认最小容量

// 初始化一个Vector动态数组.这实际上是模拟了C++的默认构造函数
Vector* vector_create() {
    // 先在堆上分配结构体Vector
    Vector* v = calloc(1, sizeof(Vector));  // malloc需要手动初始化每一个成员,calloc方便安全一些
    if (v == NULL){
        printf("calloc failed in vector_create.\n");
        return NULL;    // 创建失败返回空指针
    }

    // 申请动态数组,并赋值给Vector的data成员
    v->data = calloc(DEFAULT_CAPACITY, sizeof(ElementType));    // 此时数组中的元素都具有0值,而不是随机值
    if (v->data == NULL){
        printf("malloc failed in vector_create.\n");
        // 不要忘记free结构体Vector,否则会导致内存泄漏
        free(v);
        return NULL;    // 创建失败返回空指针
    }

    // 继续初始化Vector的其它成员
    v->capacity = DEFAULT_CAPACITY;
    // size已自动初始化为0值,所以不需要再次赋值了。但如果用malloc就不要忘记初始化它

    return v;
}

当分配数组不能成功的时候,需要free(v)不然的话会内存泄漏

6.3.2vector_destroy()

// 销毁一个Vector动态数组,释放内存。这实际上模拟了C++的析构函数
void vector_destroy(Vector* v) {
    free(v->data);
    free(v);
}

应该先free动态数组,再free结构体。否则动态数组空间会无法释放,导致内存泄漏!

6.3.3vector_push_back()

#define THRESHOLD 1024

// 在C语言中,static修饰函数表示此函数仅在当前文件内部生效
// 类似于C++或Java中的访问权限修饰符private
static void vector_resize(Vector* v) {
    // 只要调用这个函数肯定就是需要扩容的
    int old_capacity = v->capacity;

    int new_capacity = (old_capacity < THRESHOLD) ?
        (old_capacity << 1) :   // 容量还未超出阈值每次扩容2倍
        (old_capacity + (old_capacity >> 1));       // 容量超出阈值每次扩容1.5倍

    // 利用realloc重新分配动态数组
    ElementType *tmp = realloc(v->data, new_capacity * sizeof(ElementType));    // realloc惯用法
    if (tmp == NULL){
        printf("realloc failed in resize_vector.\n");
        exit(1);    // 扩容失败,退出整个程序。或者也可以做别的处理
    }
    // 扩容成功,重新赋值Vector成员
    v->data = tmp;
    v->capacity = new_capacity;
}

// 向动态数组末尾添加一个元素
void vector_push_back(Vector* v, ElementType element) {
    // 先判断是否需要扩容
    if (v->capacity == v->size) {
        vector_resize(v);
    }
    // 扩容完成后或不需要扩容,即向末尾添加元素
    v->data[v->size] = element;
    v->size++;
}

采用位运算的方式,速度更快

(old_capacity + (old_capacity >> 1)); 必须要加括号,因为+的优先级最高

所以它既不需要声明在头文件中,在实现它时也应该使用static修饰,以避免它被外界所调用。

static可以修饰全局变量,也可以用来修饰函数用于隐藏函数,因为在别的源文件不会使用这个函数也尽量不去修改!!

7.二级指针(解引用一次为了修改一级指针指向

二级指针,或称为指针的指针,也就是一个指向另一个指针的指针,也就是存储了另一个指针变量地址的指针。通过两个星号(**)定义

7.1头插法实现单向链表

typedef int DataType;
typedef struct node {       // 这里的名字不能省略
    DataType data;
    // 编译到该行时,别名Node还未定,所以这里仍然需要使用struct关键字来声明指向下一个结点的结构体指针
    // Error: Node* next;
    struct node* next;
} Node;
Node* insert_head(Node* list, DataType data) {
    // 1.创建新节点
    Node *new_node = malloc(sizeof(Node));
    if (new_node == NULL){
        printf("malloc failed in insert_head.\n");
        exit(1);
    }

    // 2.初始化新节点的数据域
    new_node->data = data;

    // 3.新结点的next指针指向原本第一个节点
    new_node->next = list;

    // 4.返回新结点指针(头指针)
    return new_node;
}

【不行】 如果有返回值的话,就需要不断接,以及修改头指针,所以想直接修改头指针

// main函数中
Node *list = NULL;  // 表示链表为空,一个结点都没有
insert_head(list, 1);
insert_head(list, 2);
insert_head(list, 3);
insert_head(list, 4);
//错误的,不能够实现,因为以为修改的头指针实际上是指针的副本,所以采用二级指针
void insert_head(Node* list, E data) {
    // 1.创建新节点
    Node* new_node = malloc(sizeof(Node));
    if (new_node == NULL) {
        printf("malloc failed in insert_head.\n");
        exit(1);
    }

    // 2.初始化新节点的数据域
    new_node->data = data;

    // 3.新结点的next指针指向原本第一个节点
    new_node->next = list;

    // 4.将头指针指向新结点
    list = new_node;
}

 【还是不行】因为是指针的副本,所以修改的是副本的指向,是不对的,要用二级指针

因为二级指针是可以改变一级指针指向的地址

 二级指针 = 一级指针的位置

*二级 = 一级指针方向(一级指针的内容)

**二级 = 一级指针指向地址的内容

int *p;     // 一级指针,一般直接叫指针即可
int **pp;   // 二级指针

int num = 10;
p = &num;       // 一级指针指向value变量
pp = &p;  // 二级指针指向指针变量p
int another_value = 20;
p = &another_value;  // 通过一级指针修改指向
*pp = &another_value; // 通过二级指针修改一级指针p的指向

**pp = 100; // 通过二级指针修改num的值

这在函数调用中尤其有用,因为即使在值传递,函数只得到副本的情况下,也可以通过二级指针的副本来修改原始指针。

7.3 利用二级指针实现无返回值头插函数

void insert_head(Node** p, DataType data) {
    // 1.创建新节点
    Node* new_node = malloc(sizeof(Node));
    if (new_node == NULL) {
        printf("malloc failed in insert_head.\n");
        exit(1);
    }

    // 2.初始化新节点的数据域
    new_node->data = data;

    // 3.新结点的next指针指向原本第一个节点
    new_node->next = *p;

    // 4.将头指针指向新结点
    *p = new_node;
}

【可以】虽然不能修改二级指针的指向,但是可以修改一级指针(头指针)的指向

【注意】1.二级指针记得对于一级指针修改地址

2.悬空指针问题

需要使用二级指针借助上面的内容进行理解

8. 【了解除了回调】函数指针 (Pointer to Function))(存储函数的地址:指令序列起始位置)

将一个函数像是一个参数一样传给另外一个函数,这样的函数叫做回调函数

形式:函数返回值类型 (*函数指针名)(函数形参列表);(了解)

// 声明一个指向"返回值类型是void、不接受任何参数的函数"的指针
void (*fun_ptr)(void);

void (*a)(void) = fun_ptr; fun_ptr当作指针来使用

 test函数需要传入一个"返回值类型是void、不接受任何参数的函数"的指针

void test(void fun_ptr(void)){}*可以省略

8.2【了解】 给函数指针类型起别名

使用函数指针时,最好起别名增强可读性。

typedef void(*FunctionPtrype)(void);
void(*p)(void) = test;
FuntionPtrType p2 = test;
//使用别名FuntionPtrType表明一种函数,使用别名可以简洁的定义

回调函数】 函数指针的使用情景:让work表示一类函数

8.5 函数指针的经典应用:qsort函数【结构体排序】

不稳定,会改变元素的相对位置

void qsort(void *base, size_t num, size_t size, int (*compare)(const void *, const void *));

base数组,num元素数量,size元素大小,compare用于比较各种元素大小(数据结构,int)

例子:


typedef struct {
    int stu_id;
    char name[25];
    int age;
    int total_socre;
} Student;

 如何c语言输入:

// 用于初始化一个结构体元素
void init_student(Student* stu, int stu_id, const char* name, int age, int total_socre) {
    stu->stu_id = stu_id;
    strncpy(stu->name, name, sizeof(stu->name) - 1);
    stu->name[sizeof(stu->name) - 1] = '\0';  // 确保字符数组以空字符结束,能够表示一个字符串
    stu->age = age;
    stu->total_socre = total_socre;
}
// 用于打印结构体数组
void print_stus(Student* stus, int len) {
    for (int i = 0; i < len; i++) {
        printf("Student %d: ID=%d, Name=%s, Age=%d, Score=%d\n",
               (i + 1), stus[i].stu_id, stus[i].name, stus[i].age, stus[i].total_socre);
    }
}

// main函数当中:
int len = 10;
Student stus[10] = { 0 };

// 初始化结构体元素
init_student(stus, 1, "ZS", 18, 600);
init_student(stus + 1, 3, "Maria", 17, 620);
init_student(stus + 2, 9, "Mark", 20, 600);
init_student(stus + 3, 6, "LS", 18, 700);
init_student(stus + 4, 4, "BS", 18, 600);
init_student(stus + 5, 7, "WS", 30, 600);
init_student(stus + 6, 10, "TS", 18, 600);
init_student(stus + 7, 2, "ABC", 16, 600);
init_student(stus + 8, 5, "AA", 18, 600);
init_student(stus + 9, 8, "GG", 18, 400);

【理解】对于cmp函数的理解可以借助于c++中一般定义的static bool cmp {};

区别c++中是<

//c++中的应用
static bool cmp(vector<int>& a, vector<int>& b){
        if(a[0] == b[0]) return a[1] < b[1];
        return a[0] < b[0];
    }
sort(intervals.begin(), intervals.end(), cmp);

 【理解】如果从小到大就把一开始的放前面,用  - 号

// qsort函数会将学生数组按照学号,从小到大排序
// 该比较规则认为: 学号越小,学生越小
int my_cmp(const void* a, const void* b) {
    // void指针需要类型转换后才能使用
    Student* s1 = a;
    Student* s2 = b;

    return (s1->stu_id) - (s2->stu_id);
}

// qsort函数会将学生数组按照成绩由高到低排序
// 该比较规则认为: 成绩越高,学生越小
int my_cmp2(const void* a, const void* b) {
    // void指针需要类型转换后才能使用
    Student* s1 = a;
    Student* s2 = b;

    return (s2->total_socre) - (s1->total_socre);
}


/*
* qsort函数的排序规则是
* 先按总分从高到低进行排序,成绩相同则按照年龄从低到高排序
* 若仍然相同,按照名字的字典顺序排序
*/
int my_cmp3(const void* a, const void* b) {
    // void指针需要类型转换后才能使用
    Student* s1 = a;
    Student* s2 = b;

    if (s1->total_socre != s2->total_socre) {
        return s2->total_socre - s1->total_socre;
    }
    // 运行到这里,总分一定是相同的,继续依据年龄来比较
    if (s1->age != s2->age) {
        return s1->age - s2->age;
    }
    // 运行到这里,总分和年龄都相同了,继续根据名字的字典顺序排序
    return strcmp(s1->name, s2->name);
}
qsort(stus, len, sizeof(Student), my_cmp3);     // 按照my_cmp3函数的比较规则从小到大排序

c语言中函数名就是指针

练习:1. 错误反馈标志:

写宏函数进行实现 这样的话无论是指针还是int类型数字都是可以的而不能用函数

可以不用dowhile,只是为了防止else指针悬空。

2. 字面值字符串、全局变量静态存储期限字符,不用考虑生命周期问题整个程序运行期间都生效。

局部变量字符串,那么这个Vector将不能跨函数使用。

堆字符串,那么这个Vector需要管理它存储的字符串的生命周期。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1588065.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SAP SD学习笔记04 - 出荷Plant(交货工厂),出荷Point(装运点),输送计划,品目的可用性检查,一括纳入/分割纳入,仓库管理

上一章讲了SD的主数据。 SAP SD学习笔记03 - SD模块中的主数据-CSDN博客 本章讲出荷Plant&#xff08;交货工厂&#xff09;&#xff0c;出荷Point&#xff08;装运点&#xff09;和出和路线。 还是偏理论多一些&#xff0c;后面的文章尽量多加些练习巩固一下。 1&#xff0…

Element-UI plus 自定义-下拉框选择年份【vue3】

1.实现效果 2.实现代码展示 <template><el-select v-model"selectedYear" placeholder"请选择"><el-optionv-for"year in yearOptions":key"year":label"year":value"year"></el-option>…

实验四:基于内容的推荐

代码 import pandas as pd from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity from sklearn.metrics.pairwise import pairwise_distances import numpy as np news_dfpd.read_csv(C:/Users/Administrat…

【Linux】开始了解重定向

送给大家一句话&#xff1a; 人真正的名字是&#xff1a;欲望。所以你得知道&#xff0c;消灭恐惧最有效的办法&#xff0c;就是消灭欲望。 – 史铁生 《我与地坛》 开始了解重定向 1 前言2 重定向与缓冲区2.1 文件描述符分配规则2.2 重定向的现象2.3 重定向的理解2.4 缓冲区…

阿里云微调chatglm3-6b---只有一个python解释器但gradio要求版本不兼容怎么办

安装LLAMA参考博文http://t.csdnimg.cn/6yYwG 在用LLAMA微调大模型的时候总是出现connected error out并且出现这样的界面 这是由于LLMA所要求的gradio版本>4.0.0,<4.2.0&#xff0c;然而chatglm3-6b要求的gradio版本需要gradio3.39.0才能显示出web_demo_gradio.py渲染…

10.1K star !牛逼了!开源技术速查表,推荐人手一份!

1、前言 在当今信息爆炸的时代&#xff0c;知识的获取、整理和应用显得尤为重要。随着个人职业发展和学习需求的不断提升&#xff0c;搭建一个个人知识库已成为提升竞争力的关键一环。个人知识库不仅是一个信息的存储库&#xff0c;更是一个思维的工具箱&#xff0c;它能够帮助…

【前缀积】Leetcode 除自身以外数组的乘积

题目解析 238. 除自身以外数组的乘积 算法讲解 我们可以使用两个空间保存当前位置的左边积和右边积&#xff0c;需要注意的地方初始的dp表需要初始化为1&#xff0c;如果是0则无法得到结果&#xff0c;因为此处是乘法 class Solution { public:vector<int> productEx…

Python用于比较数据结构并生成差异报告的工具库之data-diff使用详解

概要 Python的data-diff库是一个用于比较数据结构并生成差异报告的工具。它可以处理各种数据类型,如字典、列表、集合等,使得开发者能够快速识别数据之间的差异。 安装 通过pip可以轻松安装data-diff: pip install data-diff特性 支持多种数据类型:能够比较字典、列表、…

鸿蒙+全国产化工业平板电脑在MES系统采集终端应用

在工业4.0的大浪潮推动下,原有制造行业面临原材料及人工成本上涨、生产现场管理混乱、定单杂、生产效率难以提升、生产异常难以实时监控等诸多因素,根本无法满足数字化工厂的基本需求,更难以与工业4.0接轨。 MES系统是一套面向制造企业车间执行层的生产信息化管理 系统。MES可以…

Mac下用adb命令安装apk到android设备笔记

查询了些资料记录备用。以下是在Mac上使用命令行安装APK文件的步骤&#xff1a; 1. 下载并安装ADB&#xff1a; 如果您的Mac上没有安装ADB&#xff0c;请从官方的Android开发者网站下载Android SDK Platform Tools&#xff1a;Android SDK Platform Tools。将下载的ZIP文件解…

Centos安装MySQL提示公钥尚未安装

一、问题 在Centos7.9使用yum安装MySQL时出现错误&#xff0c;提示&#xff1a;mysql-community-server-5.7.44-1.el7.x86_64.rpm 的公钥尚未安装&#xff0c;如下图所示&#xff1a; 执行命令&#xff1a;systemctl start mysqld也提示错误&#xff1a;Failed to start mysq…

spfa算法(java代码)

题目: 851. spfa求最短路 - AcWing题库 输入样例: 3 3 1 2 5 2 3 -3 1 3 4 输出样例: 2 分析&#xff1a; 先去定义一个class 类似于c里面的pair 里面有两个变量x, y 因为后面需要用优先队列来处理最短路问题需要指出比较x还是y 因此我们让这个pair类实现 Comparable 接口 实…

IP广播对讲系统停车场解决方案

IP广播对讲系统停车场解决方案 一、需求分析 随着国民经济和社会的发展&#xff0c; 选择坐车出行的民众越来越多。在保护交通安全的同时&#xff0c;也给停车场服务部门提出了更高的要求。人们对停车场系统提出了更高的要求与挑战&#xff0c; 需要停车场系统提高工作效率与服…

如何在CentOS7.x上生成自签名SSL证书

在配置HTTPS连接时&#xff0c;SSL证书是确保数据传输安全性的关键组件。自签名证书是一种不通过证书颁发机构&#xff08;CA&#xff09;签发的证书&#xff0c;适用于测试和内部使用。以下是在CentOS 7.x系统上生成自签名证书的详细步骤。 1. 安装OpenSSL OpenSSL是一个强大…

时间案例-倒计时

需求 休息日例子 自定义日期类MyDate 日期记录是否是休息日记录是否是周末 Data NoArgsConstructor AllArgsConstructor public class MyDate {// 日期LocalDate date;// 是否休息boolean isRest;// 是否是周末boolean isWeekend; }starter启动器 // 1. 定义起始的休息时间 202…

婚纱摄影从入门到精通,专业婚礼摄影实战指南

一、资料描述 本套婚纱摄影资料&#xff0c;大小543.64M&#xff0c;共有15个文件。 二、资料目录 《婚礼摄影实战指南》苏盛鑫.全彩版.pdf 《婚礼摄影幸福攻略》.pdf 《婚纱摄影8问-拍婚纱照注意事项》【新人必看】.pdf 《婚纱摄影摆姿》 Wedding Posing.pdf 《婚纱摄影…

ELK 企业级日志分析 ELFK

一 ELK 简介 ELK平台是一套完整的日志集中处理解决方案&#xff0c;将 ElasticSearch、Logstash 和 Kiabana 三个开源 工具配合使用&#xff0c; 完成更强大的用户对日志的查询、排序、统计需求。 1 ElasticSearch&#xff1a; 是基于Lucene&#xff08;一个全文检索引擎的…

FPGA核心板在声呐系统中的应用

前言 声纳系统使用声脉冲来探测、识别和跟踪水下物体。一个完整的声纳系统是由一个控制和显示部件、一个发射器电路、一个接收器电路和同时能作为发射装置&#xff08;扬声器&#xff09;和探测装置&#xff08;高灵敏度麦克风&#xff09;的传感器组成。 声纳系统图 技术挑战…

2024 年第十四届 Mathorcup 数学应用挑战赛题目C 题 物流网络分拣中心货量预测及人员排班完整思路以及源代码分享,仅供学习

电商物流网络在订单履约中由多个环节组成&#xff0c;图1是一个简化的物流网络示意图。其中&#xff0c;分拣中心作为网络的中间环节&#xff0c;需要将包裹按照不同流向进行分拣并发往下一个场地&#xff0c;最终使包赛到达消费者手中。分拣中心管理效率的提升&#xff0c;对整…

快速开始vue3

版本 node (20.11.1)vue3 (3.4.21) 脚手架创建项目并运行 安装脚手架并创建项目 npm create vuelatest这一指令将会安装并执行 create-vue&#xff0c;它是 Vue 官方的项目脚手架工具 2&#xff09; 安装以下进行选择 ## 配置项目名称 √ Project name: vue3_test ## 是否…