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

目录

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需要管理它存储的字符串的生命周期。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

工智能图像降噪软件 ON1 NoNoise AI 2024 for Mac激活版

ON1 NoNoise AI 2024 for Mac是一款专为Mac用户设计的先进人工智能图像降噪软件。其核心功能在于能够利用机器学习技术&#xff0c;快速并智能地消除图像中的噪点&#xff0c;无论是亮度噪点还是颜色噪点&#xff0c;都能得到显著的改善。 软件下载&#xff1a;ON1 NoNoise AI …

【高项】信息化发展

目录 1.1 信息与信息化 1.1.1 信息 1.信息的定义 2.信息的特征与质量 1.1.2 信息系统 1.信息系统及其特性 2.信息系统生命周期 1.1.3 信息化 1.信息化内涵 2.信息化体系&#xff08;口诀&#xff1a;上应下技左人右规&#xff0c;中资网&#xff09; 1.2 现代化基础…

Vue笔记 2

数据代理 数据代理&#xff1a;通过一个对象代理对另一个对象中属性的操作&#xff08;读/写&#xff09; let obj{x:100} let obj2{y:200} Object.defineProperty(obj2,x,{get(){return obj.x},set(value){obj.x value} })Vue中的数据代理 Vue中的数据代理&#xff1a; 通…

【go从入门到精通】作用域,包详解

作者简介&#xff1a; 高科&#xff0c;先后在 IBM PlatformComputing从事网格计算&#xff0c;淘米网&#xff0c;网易从事游戏服务器开发&#xff0c;拥有丰富的C&#xff0c;go等语言开发经验&#xff0c;mysql&#xff0c;mongo&#xff0c;redis等数据库&#xff0c;设计模…

u盘为什么一插上电脑就蓝屏,u盘一插电脑就蓝屏

u盘之前还好好的,可以传输文件,使用正常,但是最近使用时却出现问题了。只要将u盘一插入电脑,电脑就显示蓝屏。u盘为什么一插上电脑就蓝屏呢?一般,导致的原因有以下几种。一,主板的SATA或IDE控制器驱动损坏或安装不当;二,电脑系统分区存在磁盘或文件故障错误;三,电脑中…

【力扣】125.验证回文串

刷题&#xff0c;过了真的好有成就感&#xff01;&#xff01;&#xff01; 题解&#xff1a; 根据题目要求&#xff0c;我们需要处理一下几个问题&#xff1a; 将大写字母转变成小写对原来的字符串进行处理&#xff0c;只要字母和数字考虑只有一个和字符串为空的情况 1、将…

element-ui backtop 组件源码分享

今日简单分享 backtop 组件的源码实现&#xff0c;从以下三个方面&#xff1a; 1、backtop 组件页面结构 2、backtop 组件属性 3、backtop 组件事件 一、backtop 组件页面结构 二、backtop 组件属性 2.1 target 属性&#xff0c;触发滚动的对象&#xff0c;类型 string&am…

【保姆级讲解Nginx】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

【Linux系统编程】第一弹---背景介绍

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、Linux 背景介绍 1.1、发展史 1.1.1、UNIX发展的历史 1.1.2、Linux发展历史 2、开源精神 3、Linux内核官网 4、企业应用…

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

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

2024年思维100春季线上比赛倒计时8天,来做做官方样题

今天是2024年4月12日&#xff0c;距离2024年春季思维100活动第一阶段的线上比赛4月20日还有8天。今年思维100活动的考试重点是什么呢&#xff1f;虽然主办方未公布&#xff0c;我们可以从主办方发布的参考题目中来推测今年的考试重点&#xff0c;并且按照这个来举一反三&#x…

PP-LCNet:一种轻量级CPU卷积神经网络

PP-LCNet: A Lightweight CPU Convolutional Neural Network 最近看了一个新的分享&#xff0c;在图像分类的任务上表现良好&#xff0c;具有很高的实践意义。 论文&#xff1a; https://arxiv.org/pdf/2109.15099.pdf项目&#xff1a; https://github.com/PaddlePaddle/Padd…

百科引流攻略|小马识途分享百科营销的五个技巧

纵观整个互联网领域&#xff0c;国内几大巨头百度、抖音、腾讯都布局了自身的百科平台&#xff0c;百科营销也始终作为网络营销一个重要分支而存在。很多人都知道百科营销是品牌背书的一把王牌&#xff0c;但很少有人提及百科营销的引流作用。 有人可能会说&#xff0c;百科词条…

K8S资源管理之计算资源管理

1.详解Requests和Limits参数 以CPU为例&#xff0c;下图显示了未设置Limits与设置了Requests和Limits的CPU使用率的区别 尽管Requests和Limits只能被设置到容器上&#xff0c;但是设置了Pod级别的Requests和Limits能大大提高管理Pod的便利性和灵活性&#xff0c;因此在Kubernet…

【RV1106的ISP使用记录之二】设备树的构建

基于MIPI接口的两种摄像头接入方式&#xff0c;理清楚各链路关系&#xff0c;方便后续的开发调试工作&#xff0c;先上一张图&#xff0c;后面再补充解释。

Git可视化工具 - 推荐

概述 Git版本管理工具是我们日常开发中常用的工具&#xff0c;熟练使用它可以提高我们的工作效率。 当然老司机基本使用命令行的方式进行操作&#xff0c;新手可借助可视化工具来进行过渡&#xff0c;命令行与可视化工具结合使用来加深对Git的熟悉程度。 下面推荐两个较受欢迎…

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

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

Asterisk 21.2.0编译安装经常遇到的问题和解决办法之pjproject风云再起

目录 pjproject问题的另外一种形式上传文件来解决关于pjproject 为什么要用指定版本的 pjproject问题的另外一种形式 在反复测试Asterisk 21.2.0版本安装的时候&#xff0c;在 ./configure 的时候又遇到一个跟pjproject有关的问题&#xff0c;错误提示信息是这样的&#xff1a…

【Linux】UDP协议

UDP协议 1.再谈端口号端口号划分认识知名端口号(Well-Know Port Number)两个问题netstatpidof 2.UDP协议2.1UDP的特点2.2面向数据报2.3UDP的缓冲区2.4UDP使用注意事项2.5基于UDP的应用层协议 喜欢的点赞&#xff0c;收藏&#xff0c;关注一下把&#xff01; 1.再谈端口号 端口…

七月审稿之提升模型效果的三大要素:prompt、数据质量、训练策略(附PeerRead)

前言 我带队的整个大模型项目团队超过40人了&#xff0c;分六个项目组&#xff0c;每个项目组都是全职带兼职&#xff0c;且都会每周确定任务/目标/计划&#xff0c;然后各项目组各自做任务拆解&#xff0c;有时同组内任务多时 则2-4人一组 方便并行和讨论&#xff0c;每周文档…