c语言指针小白基础教学
指针
- 1. 什么是指针?
- 2. 如何编址(即如何给地址分配空间呢)
- 3. 概念和基本术语
- 3.1指针的值==指针所指向的地址/内存区
- 3.2 指针的类型(指针本身的类型)
- 思考:
- 3.3 指针所指向的类型
- 3.4 指针本身所占据的内存区
- 3.5 指针数组和数组指针(注意区分)
- 3.6 野指针
- 4. 指针运算
- 5. 指针关系运算
- 6. 二级指针
- 7. 字符串与指针的应用
- 8. 指针数组的应用
- 9. 数组指针的应用
- 10. 指针的传参
- 11. 函数指针
- 12. 函数指针数组
- 13. 回调函数
1. 什么是指针?
指针:是一个32位数的一个数。只不过这个数是二进制表示的。例如0000 0000 0000 0000 … 0001(32个bit位)。为了方便表示,因此转换成16进制所表示的一个数。
0000 0000 0000 0000 0000 0000 0000 0000 32bit
--------- ========= --------- =========1 2 3 4 字节数
内存区:最小的存储单元是一个字节,每一个存储单元都有其对应且唯一的编号。这个编号我们称为地址,也叫指针。通过地址/指针就能找到某一块内存的首地址/某一个内存单元。
拿32位平台举例,一个计算机的某一个内存区有2^32个存储单元。
指针即地址,地址即指针。指针具体来讲就是一个值,不过这个值是内存区的一个地址。
2. 如何编址(即如何给地址分配空间呢)
我们知道,如果给每一个地址分配一个字节的空间。从32位二进制的第一个地址(全是0)开始到最后一个地址(全是1)一共有2^32个字节。
2^32 byte == 4GB (2^32/1024/1024/1024 == 4GB)
3. 概念和基本术语
3.1指针的值==指针所指向的地址/内存区
指针的值是指针本身存储的数值,而不是一个一般的数值,这个值将被编译器当作一个地址。在32 位程序里,所有类型的指针的值都是一个32 位整数,因为32 位程序里内存地址全都是32 位长。指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)的一片内存区。
以后,我们说一个指针的值是0xFFFF,就相当于说该指针指向了以0xFFFF 为首地址的一片内存区域;
反过来,一个指针指向了某块内存区域,则该指针的值是这块内存区域的首地址。指针所指向的内存区和指针所指向的类型是两个完全不同的概念。
以32位举例,一个内存有32字节,一共有2^32个内存单元,一个字节是一个内存单元。
1 byte = 8 bit
3.2 指针的类型(指针本身的类型)
去掉指针名字
(1)int*ptr;//指针的类型是int*
(2)char*ptr;//指针的类型是char*
(3)int**ptr;//指针的类型是int**
(4)int(*ptr)[3];//指针的类型是int(*)[3]
(5)int*(*ptr)[4];//指针的类型是int*(*)[4]
思考:
为什么要搞不同类型的指针呢?,
为什么不能用一个通用的指针访问任何类型的变量呢?
定义指针类型的意义是什么?
1.指针类型决定了指针解引用的权限有多大,不同类型的指针解引用的权限不同。比如int*类型的指针,可以访问4个字节,double *可以访问8个字节。而char *类型只能访问1个字节。
2.指针类型决定了指针的步长有多大。不同类型的指针步长不同,比如int *类型的指针,步长为4,double *步长为8。而char *类型步长为1。
3.3 指针所指向的类型
去掉*和指针名字就是指针所指向的类型
(1)int*ptr; //指针所指向的类型是int
(2)char*ptr; //指针所指向的的类型是char
(3)int**ptr; //指针所指向的的类型是int*
(4)int(*ptr)[3]; //指针所指向的的类型是int()[3]
(5)int*(*ptr)[4]; //指针所指向的的类型是int*()[4]
指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。
3.4 指针本身所占据的内存区
指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。指针是用来存放地址的,所以存储地址有多大,指针就有多大。在32 位平台里,指针本身占据了4 个字节的长度。(在64位平台里,指针占据8个字节)。指针本身占据的内存这个概念在判断一个指针表达式(后面会解释)是否是左值时很有用。
3.5 指针数组和数组指针(注意区分)
Ex1.int * p //p只是一个指针
Ex2.int *p[3] //p是存放整型指针的数组
Ex3.int (*p)[3] //p是指向整型数组的指针
原理:如Ex3.*优先级小于[],因此若加括号的话,表明是p优先与*结合,因此p是指针,再与[]结合,表明指针所指向的是一个数组。Ex2可举一反三。
指针数组的进阶玩法
//数组名本身就是一个地址,而地址即指针
int main() {int arr[5] = {1, 2, 3, 4, 5};int *p = arr;//arr <==> &arr[0]printf("%d %d %d %d", arr[0], 1[arr], p[2], 3[p]); //arr[2] <==> *(p+2) <==> *(2+p) <==> 2[p] <==> 2[arr]}
由此可见,只要p指向数组名,其实本质上就是数组的首地址赋值给了p这个"指针变量",所以p和arr是等价的。那么结合[]就可以玩出“新花样”。重点是arr、如同p本质上都是指针(地址)。而[1]、[2]相当于这个指针向后移动了1位、2位。
下面这个内存变化图你看不懂?你要是看不懂,把上面文字重新读一遍吧。
3.6 野指针
定义:不知道指向哪里的指针,随机的地址。
什么样会导致野指针?
- 指针未初始化若指针未初始化,那么该指针会随机找一块内存区分配地址,这个地址大概率不是计算机允许访问的区段。因此,如果不知道指针一开始要访问的空间,则置为NULL。
- 指针越界访问
int main() {int arr[10] = {0};int *p = arr;for (int i = 0; i <= 10; i++) {//当i=10时,p访问的不是给数组分配的地址空间*p = i;p++;}return 0;
}
- **指针指向的空间已经被提前释放了。**比如前一秒指针在使用的时候申请了内存空间,但是你用完之后把它释放了,当你想要再去使用该指针访问这个空间时,已经没有权限了。
int* test(){int a = 10;//分配四个字节内存空间return &a;//返回a的地址
}int main(){int *p = test();//当test函数调用完,a的地址对应的空间已经被释放了,此时你再用p去访问这个空间,就是非法访问。return 0;
}
注意:以下写法是错误的。
int main(){int *p = NULL;*p = 10;//空指针,也叫空地址,是允许读,不允许写的,会抛出写入访问权限异常return 0;
}
思考一下怎么解决?其实很简单,加个判空条件即可。
因此,所有的指针/地址无非就两类,叫有效指针和空指针。
4. 指针运算
int main() {int arr[10] = {1, 0, 0, 0, 0, 0, 0, 0, 0, 0};int *p1 = &arr[0];int *p2 = &arr[9];printf("p1 = %p,p2 = %p\n", p1, p2);printf("%d", p2 - p1);return 0;
}
打印出来的结果是9,这是因为,首指针到尾指针需要4*9个字节才能到达尾指针(变量)的首地址。以%d输出即9;故指针-指针==指针需要走多少步==两个指针之间的元素个数;
假设我们要自己写一个统计字符串长度的函数。
例1:统计字符串长度;
int main() {char str[] = "abcde";int len = my_strlen(str);printf("%d", len);return 0;
}
没学指针前的你可能会这么写:
int my_strlen(char str[]) {int count = 0;for (int i = 0; str[i] != '\0'; i++) {count++;}return count;
}
学了指针后:
int my_strlen(char *p) {int count = 0;while (*p != '\0') {count++;p++;}return count;
}
int my_strlen(char *p) {char *start = p;while (*p != '\0')p++;return p - start;
}
这里传过去的str实际上是一个str中a的首地址,本质上还是指针,所以我们能通过一个*p来指向它。
5. 指针关系运算
例2:将某数组元素全部置为1;
#define N_VALUES 5int main() {int values[N_VALUES] = {0};int *p = values;for (p = &values[0]; p < &values[N_VALUES]; p++)*p = 1;for (int i = 0; i < 5; i++)printf("%d ", values[i]);return 0;
}
你会发现p = &values[0]、p++可以省略;因为指针一开始就初始化了,p++可以放到for里面。其实就是一个while循环。
int *p = values;
while(p < &values[N_VALUES]) *p++ = 1;
或者:
for (int *p = values; p < &values[N_VALUES]; p++)*p = 1;
6. 二级指针
int main() {int a = 10;int *pa = &a;int * *ppa = &pa;printf("%d\n", *pa);printf("%d", **ppa); //*ppa <==>pareturn 0;
}
上述代码内存表示图
7. 字符串与指针的应用
例1
int main() {
// char *p = "hello world!";//存放字符串的指针pchar str[] = "hello world!";char *p = "hello world!"//本质上是将字符串的首字符(h)的地址存放到p里面printf("%c\n", *p);//h printf("%s\n", str);//hello world!printf("%s\n", p);//hello world!return 0;
}
例2
char *p1 = "hello";
char *p2 = "hello";
char str1[] = "hello";
char str2[] = "hello";
printf("%d",str1==str2);//0
printf("%d",p1==p2);//1
为什么会这样呢? 这是因为,当我们用指针来声明一个字符串时:
第一,其实就是把字符串的首字符的地址存放到指针变量中(因为一个字符占一个字节),所以首字符的地址就是整个字符串的地址。
第二,如果我们用一个指针去声明某个字符串时,该字符串就是一个常量字符串。我们无法对这个常量字符串进行写操作。
上述代码内存表示图
例3
char *p1 = "hello";
p1 = "world";//非法操作,因为p1是一个常量字符串
char str[] = "hello";
char *p2 = str;
p2 = "world";//p2是一个指向str的一个指针变量,修改p2就相当于修改str,因此可行
现在能理解为什么打印p是全部字符串,而*p是首字符了吧?
因为*p是取出字符串的首地址的内容,即字符串的首字符的地址的内容。在例1中即h
而p,无论是指向一个字符串,还是自己声明一个常量字符串。它本质上都是将字符串赋值给这个指针变量p而已。
8. 指针数组的应用
如何玩转指针数组?假设你已经知道什么是指针数组。我们尝试:
例1
int main(){int a = 1,b = 2,c = 3;int* ptr[] = {&a,&b,&c};for(int i=0;i<3;i++){int* temp = ptr[i];//拿到指针数组printf("%d ",*temp);//解引用每一个指针}return 0;
}
例2
int main(){int arr1[] = {1,2,3,4,5};int arr2[] = {1,2,3,4,5};int arr3[] = {1,2,3,4,5};int* ptr[] = {arr1,arr2,arr3};//数组名本身就是地址/指针for(int i=0;i<3;i++){int* temp_arr = ptr[i];for(int j=0;j<5;j++){printf("%d ",*(temp_arr+j));//temp_arr(数组名)是地址,数组名+j就是当前数组第j个地址//temp_arr[j] <==> *(temp_arr+j) <==> *(ptr[i]+j) <==> ptr[i][j]}printf("\n");}return 0;
}
temp_arr[j] <==> *(temp_arr+j) <==> *(ptr[i]+j) <==> ptr[i][j]//变形为二维数组
由此,不难发现其实一个指针数组可以模拟二维数组。
只不过,对于以上,我个人更喜欢这种方式:
int main() {int arr1[] = {1, 2, 3, 4, 5};int arr2[] = {1, 2, 3, 4, 0};int arr3[] = {1, 2, 3, 4, 3};int *ptr[] = {arr1, arr2, arr3}; //数组名本身就是地址/指针for (int i = 0; i < 3; i++) {int *temp_arr = ptr[i];for (int j = 0; j < 5; j++) {printf("%d ", *(temp_arr++));//temp_arr <==> ptr[i]可简化}printf("\n");}return 0;
}
9. 数组指针的应用
一定要复习前面的指针所指向的类型,再来学以下内容。
例1
//数组指针
int main() {int arr[] = {1, 2, 3};//arr <==> &arr[0] 是数组的首元素地址 思考:&arr是什么?答:数组地址(数组指针)//顾名思义:数组指针,就是存放数组地址的指针。printf("%p %p %p", &arr, arr, &arr[0]);//地址相同,但含义不同int (*p)[3] = &arr;//(*p)代表这是一个指针,[3]代表p指向的是一个长度为3的数组,int代表该数组的元素是int类型的。printf("%p ", *p);return 0;
}
ps: 数组名是数组首元素地址这个公式有两个例外:
1.sizeof(数组名)2. &数组名 这两种情况的数组名都是代表的整个数组
例2:
void print_arr(int (*parr)[3][5], int r, int l) {//*parr//解引用拿到的是首元素地址:即第一行for (int i = 0; i < r; i++) {int (*temp)[5] = *parr + i; //每一行for (int j = 0; j < l; j++) {printf("%d ", *(*temp + j));}printf("\n");}
}int main() {int arr[3][5] = {1, 2, 3, 4, 5, 2, 3, 4, 5, 6, 3, 4, 5, 6, 7};print_arr(&arr, 3, 5);//&arr代表是整个数组:传参给数组指针return 0;
}
思考:
- int (*p)[5] // 数组指针,指向的数组是int [5]
- int (*p[10])[5]是什么?//数组指针的数组
即把原来那个数组指针复制十份。得到:p是一个长度为10的数组,该数组里面存放的是数组指针,每一个数组指针指向的数组类型是int[5]。
10. 指针的传参
实参写什么?
一级指针作为函数参数时,它能接受的参数类型有:只要是地址就可以。
二级指针作为函数参数时,它能接受的参数类型有:1.二级指针2.一级指针的地址 3.存放一级指针的指针数组
11. 函数指针
函数指针是什么?函数指针就是存放函数地址的指针。
讲到&的用法时我们回顾一下数组名加&的区别,数组名就是首元素地址,相当于&数组名[0],而&数组名是取出整个数组的地址,它代表的是整个数组的地址。
不同于数组名,而函数名 、&函数名 是完全等价的。也就是说,无论加不加&,他们都表示取出函数的地址。
例1
int Add(int a, int b) {return a + b;
}
int main() {int (*pf)(int, int) = &Add; //&加不加都是代表Add的地址int x = (*pf)(1, 3);//4int y = pf(1, 4);//5 //Add <==> pfprintf("%d %d", x, y);return 0;
}
函数指针的定义很类似于数组指针。比如上例,取出函数Add的地址,给到我们的指针pf,因此(*pf)表示这是一个指针(也是为了防止与后面的括号结合),(*pf)(int, int)表示该指针指向的是函数,最后前面的int 代表该函数的返回类型是int。
例2
(*( void(*)())0) ();
将0强转为函数指针类型,解引用拿到这个函数,再调用函数。
例3
void(* signal(int,void(*)(int)) )(int);
signal是一个函数指针,这个函数指针内,将函数指针作为参数,即函数指针套函数指针。
12. 函数指针数组
void (*p[5]) (int,int)//*p[5]去掉就是它的类型,可见它是一个函数,不加括号会报错
例1
//计算器模拟int cal_add(int x, int y) {return x + y;
}int cal_sub(int x, int y) {return x - y;
}int cal_mul(int x, int y) {return x * y;
}int cal_div(int x, int y) {return x / y;
}int main() {int (*cal[5]) (int, int) = {0, cal_add, cal_sub, cal_mul, cal_div};//将函数指针存放到函数指针数组中int op = 999;while (op != 0) {printf("请输入两个数\n");int x, y = 0;scanf("%d", &x);scanf("%d", &y);printf("请选择操作符:exit:0 +:1 -:2 *:3 /:4 \n");scanf("%d", &op);int res = cal[op](x, y);//取出函数指针进行调用printf("%d\n", res);}return 0;
}
指向函数指针数组的指针
int (*f_arr[5])(int,int) //函数指针数组
int (*(*p)[5])(int,int) = &f_arr;//要加&表示整个数组,去掉*p就是它的类型
去掉*p后,表示它的类型为函数指针数组,因此叫指向函数指针数组的指针。
13. 回调函数
回调函数就是,把函数作为形参进行处理,最后返回这个形参函数,这样的函数就叫回调函数。
例1
int f1(int x) {x++;return x;
}int f2(int (*pf)(int), int z) {//回调函数return pf(z);
}int main() {int y = f2(f1, 2);printf("%d", y);return 0;
}
在例1中,pf作为函数指针充当形参,最后再返回它自己。
首先由于f2(f1,2)是一个回调函数,它把pf充当f1最后其实调用的就是f1(2),得到3;
那么它的实际意义是什么呢?我们再来看一个例子。
假如我们把之前学习函数指针数组用到的计算器程序进行改装一下:
例2
int cal_random(int (*cal)(int,int),int x,int y){return cal(x,y);
}
int main(){cal_random(cal_div,3,4);//3/4cal_random(cal_mul,1,8);//1*8
}
由此可见,我们可以把通过回调函数,来解决多个重复类型函数的定义所带来的代码冗余。此外,这样的代码,更加具备可读性。
相关文章:

c语言指针小白基础教学
指针 1. 什么是指针?2. 如何编址(即如何给地址分配空间呢)3. 概念和基本术语3.1指针的值指针所指向的地址/内存区3.2 指针的类型(指针本身的类型)思考: 3.3 指针所指向的类型3.4 指针本身所占据的内存区3.5…...
面向对象设计之里氏替换原则
设计模式专栏:http://t.csdnimg.cn/4Mt4u 思考:什么样的代码才算违反里氏替换原则? 目录 1.里氏替换原则的定义 2.里氏替换原则与多态的区别 3.违反里氏替换原则的反模式 4.总结 1.里氏替换原则的定义 里氏替换原则(Liskov S…...

MySQL·SQL优化
目录 一 . 前言 二 . 优化方法 1 . 索引 (1)数据构造 (2)单索引 (3)explain (4)组合索引 (5)索引总结 2 . 避免使用select * 3 . 用union all代替u…...
Dockerfile指令大全
Dockerfile文件由一系列指令和参数组成。指令的一般格式为INSTRUCTION arguments。具体来说,包括"配置指令"(配置镜像信息)和"操作指令"(具体执行操作)。每条指令,如FROM,都是大小写不敏感的。但是为了区分指令和参数&am…...

第八个实验:(A+B)-C的结果判断奇偶特性
实验内容:(A+B)-C的结果判断奇偶特性,最后显示结果 实验步骤: 第一步:建立项目 第二步:实验步骤,编写程序 第三步:实验结果...

设计模式:观察者模式 ⑧
一、思想 观察者模式是一种常见的设计模式,也称作发布-订阅模式。它主要解决了对象之间的通知依赖关系问题。在这种模式中,一个对象(称作Subject)维护着一个对象列表,这些对象(称作Observers)都…...

【重温设计模式】迭代器模式及其Java示例
迭代器模式的介绍 在编程领域,迭代器模式是一种常见的设计模式,它提供了一种方法,使得我们可以顺序访问一个集合对象中的各个元素,而又无需暴露该对象的内部表示。你可以把它想象成一本书,你不需要知道这本书是怎么印…...

(001)UV 的使用以及导出
文章目录 UV窗口导出模型的主要事项导出时材质的兼容问题unity贴图导出导出FBX附录 UV窗口 1.uv主要的工作区域: 2.在做 uv 和贴图之前,最好先应用下物体的缩放、旋转。 导出模型的主要事项 1.将原点设置到物体模型的底部: 2.应用修改器的…...

一文理解CAS和自旋的区别(荣耀典藏版)
目录 一、自旋 二、CAS 三、什么是 ABA 问题 大家好,我是月夜枫,通常在面试的时候,或者在学习的时候,经常性的会遇到一些关于锁的问题,尤其是面试官会提出提问,你对锁了解的多么?你知道锁的原…...

【吊打面试官系列】Java虚拟机JVM篇 - 关于内存溢出
大家好,我是锋哥。今天分享关于内存溢出的JVM面试题,希望对大家有帮助; 什么是内存溢出? 内存溢出(OOM)是指可用内存不足。程序运行需要使用的内存超出最大可用值,如果不进行处理就会影响到其他…...

思科网络中如何配置标准ACL协议
一、什么是标准ACL协议?有什么作用及配置方法? (1)标准ACL(Access Control List)协议是一种用于控制网络设备上数据流进出的协议。标准ACL基于源IP地址来过滤数据流,可以允许或拒绝特定IP地址范…...

蓝桥杯刷题(二)
参考大佬代码:(区间合并二分) import os import sysn, L map(int, input().split()) # 输入n,len arr [list(map(int, input().split())) for _ in range(n)] # 输入Li,Si def check(Ti, arr, L)->bool:sec [] # 存入已打开的阀门在…...

【Python】牛客网—软件开发-Python专项练习(day1)
1.(单选)下面哪个是Python中不可变的数据结构? A.set B.list C.tuple D.dict 可变数据类型:列表list[ ]、字典dict{ }、集合set{ }(能查询,也可更改)数据发生改变,但内存地址不变 不…...
P3405 [USACO16DEC] Cities and States S题解
题目 Farmer John有若干头奶牛。为了训练奶牛们的智力,Farmer John在谷仓的墙上放了一张美国地图。地图上表明了每个城市及其所在州的代码(前两位大写字母)。 由于奶牛在谷仓里花了很多时间看这张地图,他们开始注意到一些奇怪的…...

JavaScript原型和原型链
JavaScript每个对象拥有一个原型对象 需要注意的是,只有函数对象才有 prototype 属性 当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索ÿ…...

PyTorch之完整的神经网络模型训练
简单的示例: 在PyTorch中,可以使用nn.Module类来定义神经网络模型。以下是一个示例的神经网络模型定义的代码: import torch import torch.nn as nnclass MyModel(nn.Module):def __init__(self):super(MyModel, self).__init__()# 定义神经…...

基于神经网络的偏微分方程求解器再度取得突破,北大字节的研究成果入选Nature子刊
目录 一.引言:神经网络与偏微分方程 二.如何基于神经网络求解偏微分方程 1.简要概述 2.基于神经网络求解偏微分方程的三大方向 2.1数据驱动 基于CNN 基于其他网络 2.2物理约束 PINN 基于 PINN 可测量标签数据 2.3物理驱动(纯物理约束) 全连接神经网路(FC-NN) CN…...

Linux的基本权限
一、对shell的浅显认识 shell是操作系统下的一个外壳程序,无论是Linux操作系统,还是Windows操作系统,用户都不会直接对操作系统本身直接进行操作,需要通过一个外壳程序去间接的进行各种操作 在Linux的shell外壳就是命令行&#…...

指纹加密U盘/指纹KEY方案——采用金融级安全芯片 ACH512
方案概述 指纹加密U盘解决方案可实现指纹算法处理、数据安全加密、数据高速存取(EMMC/TF卡/NandFlash),可有效保护用户数据安全。 方案特点 • 采用金融级安全芯片 ACH512 • 存储介质:EMMC、TF卡、NandFlash • 支持全系列国密…...

Cloud-Sleuth分布式链路追踪(服务跟踪)
简介 在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的服务节点调用来协同产生最后的请求结果,每一个前端请求都会形成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败 GitHub - spring-cloud/spring-cloud-sl…...

工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果
mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...

如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...

R 语言科研绘图第 55 期 --- 网络图-聚类
在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。 为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式: R 语言科研绘图模板 --- sciRplothttps://mp.…...