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

【C语言】结构体与内存操作函数 总结

结构体

一、结构体简介

C 语言内置的数据类型,除了最基本的几种原始类型,只有数组属于复合类型,可以同时包含多个值,但是只能包含相同类型的数据,实际使用中并不够用。

实际使用中,主要有下面两种情况,需要更灵活强大的复合类型。

  • 复杂的物体需要使用多个变量描述,这些变量都是相关的,最好有某种机制将它们联系起来。
  • 某些函数需要传入多个参数,如果一个个按照顺序传入,非常麻烦,最好能组合成一个复合结构传入。

为了解决这些问题,C 语言提供了struct关键字,允许自定义复合数据类型,将不同类型的值组合在一起。这样不仅为编程提供方便,也有利于增强代码的可读性。

C 语言没有其他语言的对象(object)和类(class)的概念,struct 结构很大程度上提供了对象和类的功能。

struct fraction {int numerator;int denominator;
};
/*定义了新的数据类型以后,就可以声明该类型的变量,这与声明其他类型变量的写法是一样的。
*/
/*
声明自定义类型的变量时,类型名前面,不要忘记加上struct关键字。也就是说,必须使用struct fraction f1声明变量,不能写成fraction f1。
*/
struct fraction f1;f1.numerator = 22;
f1.denominator = 7;
/*
逐一对属性赋值。上面示例中,先声明了一个struct fraction类型的变量f1,这时编译器就会为f1分配内存,接着就可以通过.运算符为f1的不同属性赋值。
*/
struct car {char* name;float price;int speed;
};struct car saturn = {"Saturn SL/2", 16000.99, 175};
/*
可以使用大括号,一次性对 struct 结构的所有属性赋值。
*//*
变量saturn是struct car类型,大括号里面同时对它的三个属性赋值。如果大括号里面的值的数量,少于属性的数量,那么缺失的属性自动初始化为0。大括号里面的值的顺序,必须与 struct 类型声明时属性的顺序一致。否则,必须为每个值指定属性名。
struct car saturn = {.speed=172, .name="Saturn SL/2"};
*/
struct car saturn = {.speed=172, .name="Saturn SL/2"};
saturn.speed = 168;
/*
声明变量以后,可以修改某个属性的值。
*/
struct book {char title[500];char author[100];float value;
} b1 = {"title", "Author", 56.7};
/*
struct 的数据类型声明语句与变量的声明语句,可以合并为一个语句。
*/
struct {char title[500];char author[100];float value;
} b1;
/*
该语句同时声明了数据类型book和该类型的变量b1。如果类型标识符book只用在这一个地方,后面不再用到,这里可以将类型名省略。(什么是只用在一个地方???)
*/
struct {char title[500];char author[100];float value;
} b1 = {"Harry Potter", "J. K. Rowling", 10.0},b2 = {"Cancer Ward", "Aleksandr Solzhenitsyn", 7.85};
/*
`struct`声明了一个匿名数据类型,然后又声明了这个类型的变量`b1`。与其他变量声明语句一样,可以在声明变量的同时,对变量赋值。
*/
// 指针变量也可以指向struct结构。
struct book {char title[500];char author[100];float value;
}* b1;// 或者写成两个语句
struct book {char title[500];char author[100];float value;
};
struct book* b1;
/*
变量b1是一个指针,指向的数据是struct book类型的实例。
*/
// struct 结构也可以作为数组成员。
struct title numbers[1000];numbers[0].numerator = 22;
numbers[0].denominator = 7;

二、结构体内存对齐

struct 结构占用的存储空间,不是各个属性存储空间的总和,而是最大内存占用属性的存储空间的倍数,其他属性会添加空位与之对齐。

为什么浪费这么多空间进行内存对齐呢?这是为了加快读写速度,把内存占用划分成等长的区块,就可以快速在 struct 结构体中定位到每个属性的起始地址。

由于这个特性,在有必要的情况下,定义 struct 结构体时,可以采用存储大小递增(或递减)的顺序,定义每个属性,这样就能节省一些空间。

struct foo {int a; // 4 -> 8 空4个char* b; // 8char c; // 1 -> 8 空7个
};
printf("%d\n", sizeof(struct foo)); // 24
struct foo {char c; // 1 -> 4 空三个int a;  // 4char* b; // 8
};
printf("%d\\n", sizeof(struct foo)); // 16

上面示例中,占用空间最小的char c排在第一位,其次是int a,占用空间最大的char* b排在最后。整个strct foo的内存占用就从24字节下降到16字节。

三、struct 的赋值

第一种方法,struct 变量也类似于其他变量,可以使用赋值运算符(=),赋值给另一个变量。

struct cat { char name[30]; short age; } a, b;strcpy(a.name, "Hello");
a.age = 3;b = a; // struct赋值
b.name[0] = 'M';printf("%s\n", a.name); // Hello
printf("%s\n", b.name); // Mello

注意:

  1. =赋值要求两个变量是同一个类型,不同类型的 struct 变量无法互相赋值。
  2. C 语言没有提供比较两个自定义数据结构是否相等的方法,无法用比较运算符(比如==!=)比较两个数据结构是否相等或不等

第二种方法,可以使用函数memcpy来为结构体赋值,将一个结构体的指定长度的内存区域复制到另一个结构体的指定位置

// 定义两个学生结构体
Student student1 = { 1001, "Alice", 90.5 };
Student student2;// 为 student2 赋值
memcpy(&student2, &student1, sizeof(Student));

四、struct 指针

struct类型的指针和其他数据类型的指针是相似的,完全可以按照其他指针的理解方式来学习struct指针。

传入struct变量,函数内部得到的是一个原始值的副本。

#include <stdio.h>struct turtle {char* name;char* species;int age;
};void fun(struct turtle t) {t.age = t.age + 1;
}int main() {struct turtle myTurtle = {"Turtle", "sea turtle", 18};fun(myTurtle);printf("Age is %i\\n", myTurtle.age); // 输出 18return 0;
}

上面示例中,函数happy()传入的是一个 struct 变量myTurtle,函数内部有一个自增操作。但是,执行完happy()以后,函数外部的age属性值根本没变。原因就是函数内部得到的是 struct 变量的副本,改变副本影响不到函数外部的原始数据。

通常情况下,开发者希望传入函数的是同一份数据,函数内部修改数据以后,会反映在函数外部。而且,传入的是同一份数据,也有利于提高程序性能。这时就需要将 struct 变量的指针传入函数,通过指针来修改 struct 属性,就可以影响到函数外部。

传入struct指针,那么通过指针修改数据就会改变之前保存的数据(不再是副本)

void happy(struct turtle* t) {...}
happy(&myTurtle);

上面代码中,t是 struct 结构的指针,调用函数时传入的是指针。struct 类型跟数组不一样,类型标识符本身并不是指针,所以传入时,指针必须写成&myTurtle

函数内部也必须使用(*t).aget->age的写法从指针拿到 struct 结构本身。

void happy(struct turtle* t) {(*t).age = (*t).age + 1;// t->age = t->age + 1// 大大增强了代码的可读性
}

上面示例中,(*t).age不能写成*t.age,因为点运算符.的优先级高于**t.age这种写法会将t.age看成一个指针,然后取它对应的值,会出现无法预料的结果。

现在,重新编译执行上面的整个示例,happy()内部对 struct 结构的操作,就会反映到函数外部。

这里提及一个在链表中容易犯错的知识:

move = l1;move->next = l1; 是不完全等价的。

  1. move = l1; 是将指针 move 直接指向 l1 所指向的节点。这意味着 movel1 同时指向同一个节点,它们指向的是同一块内存地址。
  2. move->next = l1; 是将 move 指针所指向节点的 next 指针指向 l1 所指向的节点。这意味着将 l1 插入到 move 所指向节点的后面。

如果只执行 move = l1;,则 l1 并没有插入到新链表中,而是直接将 move 指向了 l1 所指向的节点。

如果只执行 move->next = l1;,则相当于将 l1 插入到了 move 所指向的节点的后面,但没有将 move 指针移动到新的节点。

五、struct 的嵌套

struct 结构的成员可以是另一个 struct 结构。

struct species {char* name;int kinds;
};struct fish {char* name;int age;struct species breed;
};// fish的属性breed是另一个struct结构species。

赋值的时候有多种写法。

// 写法一
struct fish shark = {"shark", 9, {"Selachimorpha", 500}};// 写法二
struct species myBreed = {"Selachimorpha", 500};
struct fish shark = {"shark", 9, myBreed};// 写法三
struct fish shark = {.name="shark",.age=9,.breed={"Selachimorpha", 500}
};// 写法四
struct fish shark = {.name="shark",.age=9,.breed.name="Selachimorpha",.breed.kinds=500// 引用breed属性的内部属性,要使用两次点运算符
};printf("Shark's species is %s", shark.breed.name);
struct name {char first[50];char last[50];
};struct student {struct name name;short age;char sex;
} student1;/*
对字符数组属性赋值,要使用strcpy()函数,不能直接赋值,因为直接改掉字符数组名的地址会报错。
*/
strcpy(student1.name.first, "Harry");
strcpy(student1.name.last, "Potter");// or
struct name myname = {"Harry", "Potter"};
student1.name = myname;

struct 结构内部不仅可以引用其他结构,还可以自我引用,即结构内部引用当前结构。比如,链表结构的节点就可以写成下面这样。

struct node {int data;struct node* next;
};
struct node {int data;struct node* next;
};struct node* head;// 生成一个三个节点的列表 (11)->(22)->(33)
head = malloc(sizeof(struct node));head->data = 11;
head->next = malloc(sizeof(struct node));head->next->data = 22;
head->next->next = malloc(sizeof(struct node));head->next->next->data = 33;
head->next->next->next = NULL;// 遍历这个列表
for (struct node *cur = head; cur != NULL; cur = cur->next) {printf("%d\\n", cur->data);
}

六、typedef关键字

  • 起别名

typedef type name; 为某个类型起别名

type代表类型名,name代表别名。

typedef unsigned char BYTE;
BYTE c = 'z';

typedef 可以一次指定多个别名。

~~typedef int antelope, bagel, mushroom;~~

typedef 可以为指针起别名。

typedef int* intptr;
int a = 10;
intptr x = &a;/*
使用的时候要小心,这样不容易看出来,变量x是一个指针类型。
*/

typedef 也可以用来为数组类型起别名。

typedef int five_ints[5];
five_ints x = {11, 22, 33, 44, 55};

typedef 为函数起别名的写法如下。

typedef signed char (*fp)(void);
/*
类型别名fp是一个指针,代表函数signed char (*)(void)。
*/

typedef为结构体起别名

typedef struct cell_phone {int cell_no;float minutes_of_charge;
} phone;
// 或者把typedef带下来,typedef struct cell_phone phone
// 还可以省略结构体的名字
phone p = {5551234, 5};
/*
typedef命令可以为 struct 结构指定一个别名,
这样使用起来更简洁。phone是别名,而不是结构体变量!
*/
  • 起别名的好处

(1)更好的代码可读性。

(2)为 struct、union、enum 等命令定义的复杂数据结构创建别名,从而便于引用。

(3)typedef 方便以后为变量改类型。

typedef float app_float;
app_float f1, f2, f3;
/*
变量f1、f2、f3的类型都是float。如果以后需要为它们改类型,只需要修改typedef语句即可。
*/

(4)可移植性

某一个值在不同计算机上的类型,可能是不一样的。

int i = 100000;

上面代码在32位整数的计算机没有问题,但是在16位整数的计算机就会出错。

C 语言的解决办法,就是提供了类型别名,在不同计算机上会解释成不同类型,比如int32_t

int32_t i = 100000;

上面示例将变量i声明成int32_t类型,保证它在不同计算机上都是32位宽度,移植代码时就不会出错。

这一类的类型别名都是用 typedef 定义的。下面是类似的例子。

typedef long int ptrdiff_t;
typedef unsigned long int size_t;
typedef int wchar_t;

这些整数类型别名都放在头文件stdint.h,不同架构的计算机只需修改这个头文件即可,而无需修改代码。

因此,typedef有助于提高代码的可移植性,使其能适配不同架构的计算机。

(5)简化类型声明

C 语言有些类型声明相当复杂,比如下面这个。

char (*(*x(void))[5])(void);

typedef 可以简化复杂的类型声明,使其更容易理解。首先,最外面一层起一个类型别名。

typedef char (*Func)(void);
Func (*x(void))[5];

这个看起来还是有点复杂,就为里面一层也定义一个别名。

typedef char (*Func)(void);
typedef Func Arr[5];
Arr* x(void);

上面代码就比较容易解读了。

  • x是一个函数,返回一个指向 Arr 类型的指针。
  • Arr是一个数组,有5个成员,每个成员是Func类型。
  • Func是一个函数指针,指向一个无参数、返回字符值的函数。

内存操作函数

一、内存简介

C 语言的内存管理,分成两部分。

一部分是系统管理的

系统管理的内存,主要是函数内部的变量(局部变量)。这部分变量在函数运行时进入内存,函数运行结束后自动从内存卸载。这些变量存放的区域称为”栈“(stack),”栈“所在的内存是系统自动管理的。

另一部分是用户手动管理的

用户手动管理的内存,主要是程序运行的整个过程中都存在的变量(全局变量),这些变量需要用户手动从内存释放。如果使用后忘记释放,它就一直占用内存,直到程序退出,这种情况称为**”内存泄漏“(memory leak)。**这些变量所在的内存称为”堆“(heap),”堆“所在的内存是用户手动管理的。

二、malloc()

功能:手动分配内存,系统就会在“堆”中分配一段连续的内存;动态数组

函数原型:void *malloc(size_t size)

参数:size是指分配的字节数

返回值:是一个指向分配内存首地址的指针;若分配失败,则返回NULL

注意:

  1. malloc分配内存并不会进行初始化,所以内存中还保留着之前的值

  2. 有时候为了增加代码的可读性,可以对malloc()返回的指针进行一次强制类型转换。

  3. 一定要注意检查malloc是否分配失败!

    /* malloc()最常用的场合,就是为数组和自定义数据结构分配内存。 */
    int *p = (int *) malloc(sizeof(int) * 10);for (int i = 0; i < 10; i++)p[i] = i * 5;
    
    /* malloc()用来创建数组,有一个好处,就是它可以创建动态数组,即根据成员数量的不同,而创建长度不同的数组。 */
    int *p = (int *) malloc(n * sizeof(int));/*注意,malloc()不会对所分配的内存进行初始化,里面还保存着原来的值。如果没有初始化,就使用这段内存,可能从里面读到以前的值。程序员要自己负责初始化,比如,字符串初始化可以使用strcpy()函数。
    */
    
    char *p = malloc(sizeof(char) * 4);
    strcpy(p, "abc");// or
    p = "abc";
    

三、 calloc()

功能:手动分配内存空间,并将内存存放的值初始化为0

函数原型:void* calloc(size_t n, size_t size);

返回值:一个指向分配内存首地址的指针;分配失败时,返回 NULL

参数:第一个参数是某种数据类型的值的数量;第二个是该数据类型的字节长度。分配内存的大小是由这两者的乘积决定。

注意:

  • malloc() 不会对内存进行初始化,如果想要初始化为0,还要额外调用memset()函数。

    	int* p = calloc(10, sizeof(int));// 等同于
    int* p = malloc(sizeof(int) * 10);
    memset(p, 0, sizeof(int) * 10);
    

四、realloc()

功能:realloc()函数用于修改已经分配的内存块的大小,可以放大也可以缩小

函数原型:void* realloc(void* block, size_t size)

返回值:返回一个指向新的内存块的指针。如果分配不成功,返回 NULL。

参数:

  • block:已经分配好的内存块指针(由malloc()calloc()realloc()产生)。
  • size:该内存块的新大小,单位为字节。

注意:

  1. realloc()可能返回一个全新的地址(数据也会自动复制过去),也可能返回跟原来一样的地址。

  2. realloc()优先在原有内存块上进行缩减,尽量不移动数据,所以通常是返回原先的地址。如果新内存块小于原来的大小,则丢弃超出的部分;如果大于原来的大小,则不对新增的部分进行初始化(程序员可以自动调用memset())。

  3. realloc()的第一个参数可以是 NULL,这时就相当于新建一个指针。

    char* p = realloc(NULL, 3490); // 等同于 char* p = malloc(3490);
  4. 如果realloc()的第二个参数是0,就会释放掉内存块。

  5. 由于有分配失败的可能,所以调用realloc()以后,最好检查一下它的返回值是否为 NULL。分配失败时,原有内存块中的数据不会发生改变。

    float* new_p = realloc(p, sizeof(*p * 40));if (new_p == NULL) {printf("Error reallocing\\n");return 1;
    }
  6. realloc()不会对内存块进行初始化。

五、free()

功能:释放手动分配的内存

函数原型:void free(void *block)

参数:指向开辟内存的指针block

返回值:无返回值

注意:

  1. 若手动分配的内存不释放掉,那这个内存块会一直占用到程序运行结束。

    // free()的参数是malloc()返回的内存地址
    // 分配内存后,若不再使用,一定要及时释放,否则容易造成内存泄露!
    int* p = (int*) malloc(sizeof(int));*p = 12;
    free(p);
    
  2. 分配的内存块一旦释放,就不应该再次操作已经释放的地址,也不应该再次使用free()对该地址释放第二次。

六、restrict 说明符

声明指针变量时,可以使用restrict说明符,告诉编译器,该块内存区域只有当前指针一种访问方式,其他指针不能读写该块内存。这种指针称为“受限指针”(restrict pointer)。

int *restrict p;
p = malloc(sizeof(int));

上面示例中,声明指针变量p时,加入了restrict说明符,使得p变成了受限指针。后面,当p指向malloc()函数返回的一块内存区域,就味着,该区域只有通过p来访问,不存在其他访问方式。

int* restrict p;
p = malloc(sizeof(int));int* q = p;
*q = 0; // 未定义行为

上面示例中,另一个指针q与受限指针p指向同一块内存,现在该内存有pq两种访问方式。这就违反了对编译器的承诺,后面通过*q对该内存区域赋值,会导致未定义行为。

七、memcpy()

功能:memcpy()用于将一块源地址内存拷贝到另一块目标地址内存中,**不能有重叠内存区域。**该函数的原型定义在头文件string.h

函数原型:void *memcpy(void *restrict dest, void *restrict source, size_t n);

参数:dest是目标地址,source是源地址,第三个参数n是要拷贝的字节数n

返回值:memcpy()的返回值是第一个参数,即目标地址的指针。

注意:

  1. **destsource都是 void 指针,表示可以移动任何类型的内存数据**
  2. 如果要拷贝10个 double 类型的数组成员,n就等于10 * sizeof(double),而不是10。该函数会将从source开始的n个字节,拷贝到dest
  3. **destsource都是 void 指针,表示这里不限制指针类型,各种类型的内存数据都可以拷贝**
  4. 两者都有 restrict 关键字,表示dest指针所指的区域,不能让别的指针来修改(包括source),同样,source指针所指的区域也不能让别的指针修改(也包括dest),即这两个内存块不应该有互相重叠的区域。
  5. 因为memcpy()只是将一段内存的值,复制到另一段内存,所以不需要知道内存里面的数据是什么类型。
  • 复制字符串

    	/* `memcpy()`可以取代`strcpy()`进行字符串拷贝,而且是更好的方法,不仅更安全,速度也更快,它不检查字符串尾部的`\0`字符。 
    */#include <stdio.h>
    #include <string.h>int main(void) {char s[] = "Goats!";char t[100];memcpy(t, s, sizeof(s));  // 拷贝7个字节,包括终止符printf("%s\n", t);  // "Goats!"return 0;
    }
    
  • my_memcpy.c

    void* my_memcpy(void* dest, void* src, int byte_count) {char* s = src;char* d = dest;while (byte_count--) {*d++ = *s++;}return dest;
    }
    /*不管传入的`dest`和`src`是什么类型的指针,将它们重新定义成一字节的 char 指针,这样就可以逐字节进行复制。`*d++ = *s++`语句相当于先执行`*d = *s`(源字节的值复制给目标字节),然后各自移动到下一个字节。最后,返回复制后的`dest`指针,便于后续使用。
    */
    

八、memmove()

功能:memmove()函数用于将一段源地址内存数据复制到另一段目标地址内存中。允许dest和source所指向的内存区域有重叠。该函数的原型定义在头文件string.h

函数原型:void *memmove(void *dest, void *source, size_t n);

参数:dest是目标地址,source是源地址,n是要移动的字节数。

返回值:memmove()返回值是第一个参数,即目标地址的指针。

注意:

  1. 如果dest和source所指向的内存区域发生重叠,源区域的内容会被更改;如果没有重叠,它与memcpy()行为相同。

    char x[] = "Hello world!";// 输出 world!world!
    printf("%s\n", (char *) memmove(x, &x[6], 6));
    

    上面示例中,从字符串x的6号位置开始6个字节,就是“world!”,memmove()将其前移到0号位置,所以x就变成了“world!world!”。

九、memcmp()

功能:memcmp()函数用来比较两个内存区域。它的原型定义在string.h

函数原型:int memcmp(const void *s1, const void *s2, size_t n);

参数:前两个参数是用来比较的指针,第三个参数指定比较的字节数。

返回值:两块内存区域的每个字节以字符形式解读,按照字典顺序进行比较(从左到右比较ASCII码值),如果两者相同,返回0;如果s1大于s2,返回大于0的整数;如果s1小于s2,返回小于0的整数。

注意:

  1. strncmp()不同,memcmp()可以比较内部带有字符串终止符\0的内存区域。

    char s1[] = {'b', 'i', 'g', '\0', 'c', 'a', 'r'};
    char s2[] = {'b', 'i', 'g', '\0', 'c', 'a', 't'};if (memcmp(s1, s2, 3) == 0) // true
    if (memcmp(s1, s2, 4) == 0) // true
    if (memcmp(s1, s2, 7) == 0) // false
    if (strncmp(s1, s2, 3) == 0) // true
    if (strncmp(s1, s2, 7) == 0) // true 注意strncmp不会比较'\0'之后的内容
    

十、memchr()

功能:在内存区域中查找指定字符。

函数原型:void *memchr(const void *s, int c, size_t n);

参数:第一个参数是一个指针,指向内存区域的开始位置;第二个参数是所要查找的字符,第三个参数是待查找内存区域的字节长度。

返回值:一旦找到,它就会停止查找,并返回指向该位置的指针。如果直到检查完指定的字节数,依然没有发现指定字符,则返回 NULL。

十一、memset()

功能:将一段内存全部设置为指定值。

函数原型:void *memset(void *s, int c, size_t n);

参数:第一个参数是一个指针,指向内存区域的开始位置;第二个参数是待写入的字符值;第三个参数是一个整数,表示需要待格式化的字节数。

返回值:返回第一个参数(指针)。

简单例子

char str[] = "BBBBBBBBBBBB";// 输出 bbbbbbbBBBBB
printf("%s\n", (char*) memset(str, 'b', 7));

注意:

  1. malloc开辟内存后没有初始化,memset()可以将开辟内存区域全部初始化为0。

    int *arr = (int *)malloc(100 * sizeof(int));
    memset(arr, 0, 100 * sizeof(int)); // 第三个参数不是sizeof(arr)// ...
    free(arr);
    

相关文章:

【C语言】结构体与内存操作函数 总结

结构体 一、结构体简介 C 语言内置的数据类型&#xff0c;除了最基本的几种原始类型&#xff0c;只有数组属于复合类型&#xff0c;可以同时包含多个值&#xff0c;但是只能包含相同类型的数据&#xff0c;实际使用中并不够用。 实际使用中&#xff0c;主要有下面两种情况&a…...

第12章_集合框架(Collection接口,Iterator接口,List,Set,Map,Collections工具类)

文章目录 第12章_集合框架本章专题与脉络1. 集合框架概述1.1 生活中的容器1.2 数组的特点与弊端1.3 Java集合框架体系1.4 集合的使用场景 2. Collection接口及方法2.1 添加2.2 判断2.3 删除2.4 其它 3. Iterator(迭代器)接口3.1 Iterator接口3.2 迭代器的执行原理3.3 foreach循…...

C语言进阶——数据结构之链表(续)

前言 hello&#xff0c;大家好呀&#xff0c;我是Humble&#xff0c;本篇博客承接之前的C语言进阶——数据结构之链表 的内容&#xff08;没看过的小伙伴可以从我创建的专栏C语言进阶之数据结构 找到那篇文章并阅读后在回来哦~&#xff09;&#xff0c;上次我们重点说了链表中…...

数据库课程设计-图书管理系统数据库设计

目录 一、实验目的 二、实验内容 三、实验要求 四、实验设计 4.1需求分析 4.1.1系统目标 4.1.2功能需求 4.1.3性能需求 4.14界面需求 4.2概念模型设计 4.2.1 实体及联系 4.2.2 E-R图 4.3 逻辑设计 4.3.1 E-R模型向关系模型的转换 4.3.2 数据库逻辑结构 4.3.3数据库模型函数依赖…...

【超简版,代码可用!】【0基础Python爬虫入门——下载歌曲/视频】

安装第三方模块— requests 完成图片操作后输入&#xff1a;pip install requests 科普&#xff1a; get:公开数据 post:加密 &#xff0c;个人信息 进入某音乐网页&#xff0c;打开开发者工具F12 选择网络&#xff0c;再选择—>媒体——>获取URL【先完成刷新页面】 科…...

CommunityToolkit.Mvvm支持环境

引言 CommunityToolkit.Mvvm 包&#xff08;又名 MVVM 工具包&#xff0c;以前称为 Microsoft.Toolkit.Mvvm&#xff09;是一个现代、快速和模块化的 MVVM 库。 它是 .NET 社区工具包的一部分&#xff0c;其中一条原则是&#xff1a; 独立于平台和运行时 - .NET Standard 2.0…...

探讨品牌设计的本质,为企业形象注入活力!

品牌设计作为一个行业&#xff0c;首先需要明确行业的本质和意义。由于越来越多的咨询公司和营销公司对品牌有不同的理解和解释&#xff0c;因为他们服务的内容和专业水平不同&#xff0c;什么是品牌的定义越来越复杂&#xff0c;逐渐成为一种神秘的知识。例如&#xff0c;特劳…...

【Maven】-- 打包添加时间戳的两种方法

一、需求 在执行 mvn clean package -Dmaven.test.skiptrue 后&#xff0c;生成的 jar 包带有自定义系统时间。 二、实现 方法一&#xff1a;使用自带属性&#xff08;不推荐&#xff09; 使用系统时间戳&#xff0c;但有一个问题&#xff0c;就是默认使用 UTC0 的时区。举例…...

图片分类: 多类别

最近需要训练一个有200多类的图片分类网络&#xff0c;搜了一遍&#xff0c;发现居然没有很合适用的开源项目&#xff0c;于是自己简单撸了一个轮子&#xff0c;项目地址: https://github.com/xuduo35/imgcls_pytorch。支持如下backbone: alexnetresnet18,resnet34,resnet50,r…...

python 抓包tcp数据拷贝转发

在Python中&#xff0c;你可以使用scapy库进行抓包&#xff0c;使用shutil或io库进行数据的拷贝&#xff0c;以及使用socket库进行数据转发。下面是一个简单的示例&#xff0c;展示了如何进行这些操作&#xff1a; 首先&#xff0c;你需要安装必要的库。你可以使用pip来安装它…...

ubuntu 各版本图形界面和命令行切换快捷键介绍

文章目录 前言一、ubuntu 图形界面和命令行模式切换的快捷键1. ubuntu 16.042. ubuntu 18.043. ubuntu 20.044. ubuntu 22.04 总结 前言 本文主要介绍如何使用快捷键进行ubuntu 的图形界面和命令行模式切换&#xff0c;涉及如下 几个ubuntu 版本 ubuntu16.04 ubuntu18.04 ubun…...

基于SpringBoot Vue博物馆管理系统

大家好✌&#xff01;我是Dwzun。很高兴你能来阅读我&#xff0c;我会陆续更新Java后端、前端、数据库、项目案例等相关知识点总结&#xff0c;还为大家分享优质的实战项目&#xff0c;本人在Java项目开发领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#x…...

关于预检请求

基本概述 预检请求&#xff08;Preflight Request&#xff09;是一种由浏览器自动发起的请求&#xff0c;用于检查实际请求是否安全可行。这种请求通常在跨域请求&#xff08;CORS&#xff09;中出现&#xff0c;并且只在某些特定条件下触发。以下是触发预检请求的具体条件&am…...

cookie in selenium 定时更新token

1.selenium添加cookie访问 需要登录才能访问的链接 selenium 访问 “https://developer.org.com”&#xff0c;如果没登陆,则跳转到"https://console.org.com/login"&#xff0c;此时selenium取到的cookie的domain是&#xff1a;.console.org.com。 而domain 是 .c…...

【MIdjourney】一些材质相关的关键词

1.多维剪纸(Multidimensional papercut) "Multidimensional papercut"&#xff08;多维剪纸&#xff09;是一种剪纸艺术形式&#xff0c;通过多层次的剪纸技巧和设计来创造出立体感和深度感。这种艺术形式通常涉及在不同的纸层上剪裁不同的图案&#xff0c;并将它们…...

递归组件怎么实现无线滚动

递归组件实现无限滚动的方法通常涉及到对数据的递归处理和组件的自我调用。以下是一个简单的示例&#xff0c;展示如何使用递归组件实现无限滚动&#xff1a; 首先&#xff0c;定义一个递归组件&#xff0c;该组件可以调用自己来渲染下一组数据。假设我们要展示一个滚动列表&a…...

致远OA如何开发 第十篇 数据库

数据库 此栏目技术支持 技术大佬对栏目文章的支持 特别感谢 如何编写dao实现数据的增删改查 新建文件 实现下面的方法即可&#xff0c;具体的sql操作需要自己组装 其中JDBCAgent 是致远封装过的工具Overridepublic void addData(String dataId, String agentId) {try (JDBC…...

信息检索与数据挖掘 | (十)线性回归与逻辑回归

文章目录 &#x1f4da;线性回归算法流程&#x1f4da;Bias and variance&#x1f4da;过拟合&欠拟合&#x1f4da;逻辑回归算法流程 &#x1f4da;线性回归算法流程 ybwx 使用loss function L来评估函数的好坏 从而我们要选择使L最小的模型参数w,b 使用梯度下降的方法…...

【issue-halcon例程学习】measure_arc.hdev

例程功能 检查倒角后铸件的细长孔之间的距离。 代码如下 read_image (Zeiss1, zeiss1) get_image_size (Zeiss1, Width, Height) dev_close_window () dev_open_window (0, 0, Width / 2, Height / 2, black, WindowHandle) set_display_font (WindowHandle, 14, mono, true,…...

RKE快速搭建离线k8s集群并用rancher管理界面

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 本文记录使用RKE快速搭建一套k8s集群过程&#xff0c;使用的rancher老版本2.5.7&#xff08;当前最新版为2.7&#xff09;。适用…...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

centos 7 部署awstats 网站访问检测

一、基础环境准备&#xff08;两种安装方式都要做&#xff09; bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats&#xff0…...

Go 语言接口详解

Go 语言接口详解 核心概念 接口定义 在 Go 语言中&#xff0c;接口是一种抽象类型&#xff0c;它定义了一组方法的集合&#xff1a; // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的&#xff1a; // 矩形结构体…...

将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?

Otsu 是一种自动阈值化方法&#xff0c;用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理&#xff0c;能够自动确定一个阈值&#xff0c;将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...

(二)原型模式

原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

Python如何给视频添加音频和字幕

在Python中&#xff0c;给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加&#xff0c;包括必要的代码示例和详细解释。 环境准备 在开始之前&#xff0c;需要安装以下Python库&#xff1a;…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)

骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术&#xff0c;它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton)&#xff1a;由层级结构的骨头组成&#xff0c;类似于人体骨骼蒙皮 (Mesh Skinning)&#xff1a;将模型网格顶点绑定到骨骼上&#xff0c;使骨骼移动…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战

在现代战争中&#xff0c;电磁频谱已成为继陆、海、空、天之后的 “第五维战场”&#xff0c;雷达作为电磁频谱领域的关键装备&#xff0c;其干扰与抗干扰能力的较量&#xff0c;直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器&#xff0c;凭借数字射…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...