C语言 | 指针 | 野指针 | 数组指针 | 指针数组 | 二级指针 | 函数指针 | 指针函数
文章目录
- 1.指针的定义
- 2.指针的加减运算
- 3.野指针
- 4.指针 & 数组 & 传参 & 字符数组
- 5.数组指针 & 指针数组
- 6.二级指针
- 7.指针函数 & 函数指针 & 回调函数
- 8.函数指针数组 & 指向函数指针数组的指针
1.指针的定义
指针是内存中一个最小单元的编号,也就是地址。平常口语中所说的指针,通常指的是指针变量,是用来存放内存地址的变量。指针的大小在32位平台是4个字节,在64位平台是8个字节。可以通过sizeof求得指针的大小。
#include <stdio.h>int main()
{int a = 5;char c = 'a';int* ptra = &a;char* ptrc = &c; printf("%ld\n",sizeof(ptra));printf("%ld\n",sizeof(ptrc));return 0;
}
我的系统64位的Ubuntu系统所以输出8。从上面的代码可以看出用 type + *
定义一个指针变量,而&
(取地址) 可以取得变量的地址。另外输出ptra
和ptrc
的结果都一样,难道它们的类型一样。不是的!ptra
的类型是int*
用来接收int
类型变量的地址,而ptrc
的类型是char*
用来接收char
类型变量的地址。对指针赋值的时候类型要匹配!
可以定义指针,那么如何取出指针的指向的内容呢,其实它和定义指针用的是同一个操作符 *
(解引用操作符或间接访问操作符)指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节),char* 指针的解引用就只能访问1个字节,int*指针的解引用就只能访问4个字节。
#include <stdio.h>int main()
{int a = 5;int* ptra = &a;printf("%d\n",*ptra);return 0;
}
2.指针的加减运算
C语言程序运行指针的整数加减,但是不能乘除!其实加减有时候就够头疼了,要是能乘除那不得了了!指针的加减会根据指针的类型决定了指针向前或者向后走一步有多大(距离),这时候能直观体现指针是有类型的。
#include <stdio.h>int main()
{int a = 5;char c = 'a';int* ptra = &a;char* ptrc = &c; printf("prta : before = %p : after = %p\n",ptra,ptra + 1);printf("prtc : before = %p : after = %p\n",ptrc,ptrc + 1);return 0;
}
输出结果:输出的结果是按照16进制来展示的。
prta : before = 0x7ffca65b0c74 : after = 0x7ffca65b0c78
prtc : before = 0x7ffca65b0c73 : after = 0x7ffca65b0c74
3.野指针
指针是很灵活的,但是有时候灵活就代表着容易出错,如果对指针操作不当那么很容易造成野指针。所谓的野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。
1、比如我对int*
类型的指针加一然后解引用访问,就会出错!指针越界访问!(int 只有4个字节大小的空间属于它) 通常越界访问是对数组经行操作,所以要注意数组下标访问是否越界。
int main()
{int a = 5;int* ptra = &a;printf("*prta : before = %d : after = %d\n",*ptra,*(ptra + 1));return 0;
}
输出:有些环境就程序直接就崩溃了,这里就输出了随机值。
*prta : before = 5 : after = 1747592652
2、指针未初始化
int main()
{int* ptra;printf("*prta : before = %d : after = %d\n",*ptra,*(ptra + 1));return 0;
}
3、指针指向的空间释放。对malloc动态申请的内存free后再去访问,也会造成野指针。
如何避免野指针的出现: 1.指针初始化、2.小心指针越界、3.指针指向空间释放即使置NULL、4.避免返回局部变量的地址、5.指针使用之前检查有效性。
上面的方法都是依靠程序员有良好的编码习惯,而人有时候会犯错,所以在C++中引入了智能指针来避免野指针。
4.指针 & 数组 & 传参 & 字符数组
数组名首元素的地址,它和&数组名输出的结果是一样但是它们代表并不是一个意思,因为指针是有类型的。
int main(int argc,char* argv[])
{int arr[5] = {1,2,3,4,5};printf("%p\n",arr);printf("%p\n",&arr);return 0;
}
输入结果
0x7ffebf362450
0x7ffebf362450
如果对数组名 + 1和&数组名 + 1会发现它们输出的内容是不一样的。
int main(int argc,char* argv[])
{int arr[5] = {1,2,3,4,5};printf("%p\n",arr);printf("%p\n",&arr);printf("%p\n",arr + 1);printf("%p\n",&arr + 1);return 0;
}
输出结果:
0x7ffd97b8f650
0x7ffd97b8f650
0x7ffd97b8f654
0x7ffd97b8f664
指针的类型决定了指针向前或者向后走一步有多大(距离)。数组名 + 1会跳过4个字节因为它的类型是 int*,而&数组名 + 1的类型是int (*)[5] 整形数组指针,跳过了20个字节。如果尝试这样赋值 int * p = &arr
,编译器会提醒你!
test.c:374:15: warning: initialization of ‘int *’ from incompatible pointer type ‘int (*)[5]’ [-Wincompatible-pointer-types]374 | int * p = &arr;
如果自动一个函数想要接收一个整形数组,不需要定义数组指针,只需要传递首元素的地址就能访问到整个数组的元素。可以有三种方式。
方式一:
void get(int* arr){}
方式二:这种方式需要和传入的数组的大小匹配,比较的麻烦,现实中定义函数的人也知道要定义多大。如果可以在函数中打印一下arr,发现就是一个指针的大小,而不是将整个数组传递。
void get(int arr[5])
{printf("%ld\n",sizeof(arr));
}
方式三:直接用方式一就好了。
void get(int arr[]){}
C语言没有像其他语言一样提供好用的String类型来处理字符串,它是通过char *指向 ""
引起的字符串常量,或者通过字符数组(栈),再或者存放到malloc开辟的动态内存空间中。不管怎么说以\0
标志结尾的C风格的字符串挺难用的。
那么char * (字符指针)和字符数组处理字符有什么区别呢:
int main()
{const char* str1 = "hello world";const char* str2 = "hello world";char str3[] = "hello world";char str4[] = "hello world";printf("str1 = %p : str2 = %p\n",str1,str2);printf("str3 = %p : str4 = %p\n",str3,str4);return 0;
}
输出结果
str1 = 0x55a2615af008 : str2 = 0x55a2615af008
str3 = 0x7fff95f06ca0 : str4 = 0x7fff95f06cac
str1 和 str2 指向了存放在常量区中的同一个"hello world"字符串的首元素的地址,而str3 和str4在栈区开辟了两块不同的内存存放Hello world。这样做虽然浪费了,一定的内存,但是字符数组中存放的内容可以修改,而char* 指向的字符串常量不能被修改,通常会加上一个const来修饰。另外,从键盘上输入字符就需要用到字符数组来接收。
int main()
{char str1[] = {'h','e','l','l','o'};char str2[] = {'h','e','l','l','o','\0'};char str3[] = "hello";printf("%ld\n",sizeof(str1));printf("%ld\n",sizeof(str2));printf("%ld\n",sizeof(str3));printf("%zd\n",strlen(str1));printf("%zd\n",strlen(str2));return 0
}
这里str1 定义并不是字符串,因为C字符串要以\0
结尾,所以strlen输出什么都是未定义的。这里注意区分sizeof 是计算内存的大小,而strlen是计算字符串的长度。
5.数组指针 & 指针数组
数组指针是指针,而指针数组是数组用来存放指针。它们的定义非常的相似。
数组指针: int (*p1)[5],p1是一个指针变量,指向的是一个大小为5个整型的数组。
指针数组: int *p2[5],p2是数组名,可以存放着5个int *
类型的指针。
这里定义数组指针需要括号,因为 [] 的优先级要高于 *
号的,加上()来保证p1先和*
结合。使用指针数组可以接收&一维数组名。也可以接收二维数组中的元素,注意二维数组中的元素是一维数组的地址。
void show(int(*p)[5],int cow,int col)
{for (int i = 0;i < cow ; i++) {for (int j = 0; j < col; j++) {printf("%d ",p[i][j]);}printf("\n");}
}int main()
{int a[2][5] = {{1,2,3,4,5},{6,7,8,9,10}};show(a,2,5);return 0;
}
上面show函数打印了二维数组的值,数组通过 [] 访问更加清晰一些。在C++中通过重载 [] 也让容器在逻辑上顺序访问,哪怕它的底层空间不是连续的。这里的[] 和 * 是等价的。也可以用下面的方式,访问到二维数组的元素。
printf("%d ",*(*p + i) + j);
printf("%d ",*((*(p + i)) + j));
再来看一看,指针数组。来定义一个指针数组,并打印这些值。
int main(int argc,char* argv[])
{char* names[5] = {"张三","李四","王五","赵六","田七"};for(int i = 0;i < 5;i++){printf("%s\n",names[i]);}return 0;
}
上面的例子似乎没有什么挑战,那如果从键盘上输入五个人的名字,并输出呢,怎么做。就有一个关键点,指针数组中存放的是指针,如果从键盘上输入要用字符数组,然后赋值给指针。
int main(int argc,char* argv[])
{char* names[5]; for(int i = 0; i < 5;i++){char name[25] = {0};printf("请输入一个名字:");scanf("%s",name);names[i] = name;}for(int i = 0;i < 5;i++){printf("%s\n",names[i]);}return 0;
}
我们来使用指针数组完成一个有意思的东西,学过Linux的同学知道cat 命令是查看文本中的内容。我们自己编写一个程序也实现cat 命令完成的任务。没学过Linux也没有关系,知道了怎么编写命令,学的时候就不会被它唬住。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main(int argc,const char* argv[])
{if(argc <= 1){printf("Usage: %s <file1> [file2 ...]\n",argv[0]);return -1;}for(int i = 1;i < argc;i++){FILE* file = fopen(argv[i],"r");if(file == NULL){perror("文件不存在!\n");return -1;}char ch;while((ch = fgetc(file)) != EOF){fputc(ch,stdout);}fclose(file);}return 0;
}
通过gcc编译并添加到环境变量中
☁ day2024_11_14 [master] ⚡ gcc test.c[自己的文件名] -o easy_cat
☁ day2024_11_14 [master] ⚡ sudo mv easy_cat /usr/bin
☁ day2024_11_14 [master] ⚡ easy_cat test.c
6.二级指针
指针变量也是变量,存放指针变量的指针称为二级指针。
int main()
{int num = 5;int *p = #int **pp = &p;printf(" pp : %p &p : %p\n", pp,&p);printf("**pp : %d *p : %d\n",**pp,*p);return 0;
}
输出结果
pp : 0x7fff341ddbc8 &p : 0x7fff341ddbc8
**pp : 5 *p : 5
那么二级指针的用处是什么呢。函数的传参的方式有传值和传地址,着两种方式,传值传递参数是指的拷贝,如果想改变两个变量的地址,那么就需要通过指针传递。同样的道理,我想改变指针变量的内容就需要指针变量的地址,那就是二级指针。
交换两个int 变量的值:
void swap(int* a,int* b)
{int tmp = *a;*a = *b;*b = tmp;
}int main()
{int a = 5;int b = 10;printf("before a = %d,b= %d\n",a,b);swap(&a,&b);printf("after a = %d,b= %d\n",a,b);
}
交换两个int* 指针变量指向的值:
void swap(char** a,char** b)
{char* tmp = *a;*a = *b;*b = tmp;
}
当你想改变指针变量的指向的时候可以考虑使用二级指针,很多的数据结构中,二级指针都是很常见的。另外在巩固一下,如果要编写一个函数打印指针数组的值改怎么做呢。
void showName(char** names,int size)
{for(int i = 0;i < size;i++){printf("%s ",names[i]);}printf("\n");
}int main()
{char* names[5] = {"张三","李四","王五","赵六","田七"};showName(names,5);return 0;
}
为什么需要二级指针接收呢,可以这么理解:names[0]
类型为char*
而&names[0]
的类型为char**
。数组名就是首元素的地址,name = &names[0]
实参传递的是 char**
类型,showName
的形参类型也要用二级指针来接收。
7.指针函数 & 函数指针 & 回调函数
函数返回值类型是指针的函数称为指针函数,通常是返回堆空间的指针,避免返回栈上空间,出了作用域被销毁,访问一块销毁的空间。
void* malloc(int size);
char* strcpy(char* dest,char* src);
函数指针的本质是指针,用来存放函数的地址,而函数名就是函数在内存中首元素的地址。如何判断函数的类型。如定义一个add函数:
int add(int a,int b)
确认一个函数类型最快速的方法是,去掉函数名和函数的形参就是函数的类型。add 函数的类型是int (int,int)
。然后添加一个 (* p)
就可以定义一个函数指针了。
#include <stdio.h>int add(int a,int b)
{return a + b;
}int main()
{int (*p1)(int,int) = add;int (*p2)(int,int);p2 = add;printf("%d\n",p1(1,2));printf("%d\n",p2(1,3));return 0;
}
定义的函数指针可以在定义的时候赋初值,也可以先声明在赋值。使用函数指针像函数一样使用即可。既然像函数一样使用,那干嘛要麻烦的定义函数指针呢?定义函数指针可以让函数以参数的方式传递,让声明和实现分离,提高了代码的模块化程度。 这就像其他如Java语言中定义接口,通过接口来调用实现接口的类。
下面实现了加减,如果发生乘除取模(整除)等添加另外的功能函数的类型一样都可以通过calculate去调用。
#include <stdio.h>int add(int a,int b)
{return a + b;
}int sub(int a,int b)
{return a - b;
}int calculate(int cal(int,int),int a,int b)
{return cal(a,b);
}int main()
{printf("%d\n",calculate(add,5,10));printf("%d\n",calculate(sub,10,5));return 0;
}
回调函数callback,通常我们调用别人的函数API这个过程叫做call,而别人在他定义的函数中调用我们的函数这个行为就是callback。上面的例子中calculate 调用cal 这个动作就是callback。在C语言中qsort 就用到了callback 。
#include <stdio.h>
#include <stdlib.h>int int_cmp(const void* p1,const void* p2)
{return *(int*)p1 - *(int*)p2;
}int main()
{int arr[] = {1,6,2,3,8,9,3};int size = sizeof(arr) / sizeof(arr[0]);qsort(arr,size,sizeof(int),int_cmp);for(int i = 0;i < size;i++){printf("%d ",arr[i]);}printf("\n");return 0;
}
那qsort大概是怎么实现的呢?使用一个冒泡排序,实现对任意类型数据的排序。
C语言中使用void* 来接收任意类型,但是void* 不能加减运算,但是类型又是不确定的,所以只能转成最小类型的指针char* ,按照一个字节来操作数据。比较的时候,只需要两个确定元素的地址,而char* 一次只能跳过一个字节,所以要根据size来确定跳几个字节。交换的时候是按照一个一个字节,交换内容,要交换size次。
void _swap(void* p1,void* p2,int size)
{for(int i = 0;i < size;i++){char ch = *((char*)p1 + i);*((char*)p1 + i) = *((char*)p2 + i);*((char*)p2 + i) = ch;}
}void my_qsort (void* base, size_t num, size_t size,int (*compar)(const void*,const void*))
{for(int i = 0; i < num - 1;i++){for(int j= 0;j < num -1 -i;j++){if(compar((char*)base + j * size,(char*)base + (j + 1) * size) > 0){ _swap((char*)base + (j* size),((char*)base + (j+ 1)* size),size);}}}
}int int_cmp(const void* p1,const void* p2)
{return *(int*)p1 - *(int*)p2;
}int main()
{int arr[] = {5,1,2,3,8,9,3};int size = sizeof(arr) / sizeof(arr[0]);my_qsort(arr,size,sizeof(int),int_cmp);for(int i = 0;i < size;i++){printf("%d ",arr[i]);}printf("\n");return 0;
}
8.函数指针数组 & 指向函数指针数组的指针
用来存放函数指针的数组。在上面的calculate例子中,可以使用函数指针数组来存放函数指针。
int add(int a,int b)
{return a + b;
}int sub(int a,int b)
{return a - b;
}int main()
{int (*p[2])(int,int) = {add,sub};for(int i = 0;i < 2;i++){printf("%d\n",p[i](10,5));}
}
当然还可以定义一个指向函数指针数组的指针
int (*(*pp)[2])(int,int) = &p;
相关文章:
C语言 | 指针 | 野指针 | 数组指针 | 指针数组 | 二级指针 | 函数指针 | 指针函数
文章目录 1.指针的定义2.指针的加减运算3.野指针4.指针 & 数组 & 传参 & 字符数组5.数组指针 & 指针数组6.二级指针7.指针函数 & 函数指针 & 回调函数8.函数指针数组 & 指向函数指针数组的指针 1.指针的定义 指针是内存中一个最小单元的编号&…...
mysql 的乐观锁和 mvcc 是一回事吗
MySQL 的乐观锁和 MVCC(多版本并发控制)是两个不同的概念,尽管它们都涉及到并发控制和数据的一致性,但在设计目的和实现方式上存在本质区别。 1. 乐观锁 概念 乐观锁是一种用于解决并发更新冲突的控制机制。它假设数据在大部分情况…...
redis的击穿和雪崩
Redis 是一个高性能的键值存储数据库,广泛用于缓存、会话管理等场景。然而,Redis 在高并发场景下可能会遇到一些问题,比如“击穿”和“雪崩”。下面详细解释这两个概念: 击穿(Hotspot) 击穿是指某个热点数…...

java中创建多线程的4种方式
目录 一、继承 Thread 类创建线程 步骤 示例代码 原理 二、实现 Runnable 接口创建线程 步骤 示例代码 原理 三、实现 Callable 接口创建线程 步骤 示例代码 原理 与Runnable接口相比的不同之处 四、使用线程池创建线程 步骤 示例代码(使用 Executo…...

MATLAB深度学习(二)——如何训练一个卷积神经网路
2.1 基本概念 从数学的角度看,机器学习的目标是建立输入和输出的函数关系,相当于 y F(x)的过程。F(x)就是我们所说的模型,对于使用者来说,这个模型就是一个黑箱,我们不知…...

删除k8s 或者docker运行失败的脚本
vi delete_exited_containers.sh#!/bin/bash# 列出所有停止的容器并存储到数组 list_exited_containers() {echo -e "\nStopped containers:"containers()# 获取停止的容器信息并存入数组while IFS read -r line; docontainers("$line")done < <(do…...
重置docker版本的octoprint管理员账号密码
我的情况是octoprint安装在HiNAS系统的机顶盒上,只有一个账号,但是忘记了用户名和密码。有两个选择: 可以试试先找回用户名,然后尝试你的常用密码。直接重置所有账号。 1.找回用户名: 使用使用 docker exec -it <…...
prometheus监控数据远程写入Kafka集群
文章目录 前言一、环境简介1.1 环境简介1.2 部署清单1.3 组件版本 二、部署步骤2.1 prometheus部署2.2 kafka集群部署2.3 prometheus-kafka-adapter部署 三、数据验证四、总结 前言 根据项目要求,需将prometheus监控数据存储到kafka中。前面为了图方便就搭建了单机…...

Excel使用-弹窗“此工作簿包含到一个或多个可能不安全的外部源的链接”的发生与处理
文章目录 前言一、探讨问题发生原因1.引入外部公式2.引入外部数据验证二、问题现象排查及解决1.排查公式2.排查数据验证3.特殊处理方式总结前言 作为一种常用的办公软件,Excel被大家所熟知。尽管使用了多年,有时候在使用Excel时候也会发生一些不太常见的现象,需要用心核查下…...
C++小白实习日记——Day 2 TSCNS怎么读取当前时间
和老板问了一下,今天就可以自己上手了: 用TSCNS写了一个cpp,运行出来老板说让我去看看另一个项目是怎么做的 用TSCNS和std库获取当前时间 #include <iostream> #include <iomanip> #include "tscns.h"using namespace std;TSCN…...

【Pythonr入门第二讲】你好,世界
"Hello, World!" 是一种传统的编程入门示例,通常是程序员学习一门新编程语言时编写的第一个程序。这个程序的目标非常简单:在屏幕上输出 "Hello, World!" 这个字符串。尽管它非常简单,但具有重要的象征意义和实际价值。 …...

3D Streaming 在线互动展示系统:NVIDIA RTX 4090 加速实时渲染行业数字化转型
随着科技的飞速发展,实时渲染正逐步成为游戏与实时交互领域的重要驱动力。与离线渲染不同,实时渲染需要极高的计算性能,对硬件设备尤其是GPU的性能要求极高。随着 RTX 4090 显卡的问世,其强大的算力和创新技术,为实时渲…...
Oracle 单机及 RAC 环境 db_files 参数修改
Oracle 数据库中 DB_FILES 定义了数据库中数据文件的个数,默认值为200,如果创建数据库文件时超过DB_FILES 定义的值就会报 ORA-00059 错误。 下面分别演示单机及 RAC 环境下修改 db_files 参数的操作步骤。 一、单机环境 1.查询当前参数值 SQL> sh…...

消息中间件分类
消息中间件(Message Middleware)是一种在分布式系统中实现跨平台、跨应用通信的软件架构。它基于消息传递机制,允许不同系统、不同编程语言的应用之间进行异步通信。 常见的消息中间件类型包括: 1. JMS(Java Message S…...
讯飞、阿里云、腾讯云:Android 语音合成服务对比选择
在 移动端 接入语音合成方面,讯飞和腾讯云等都是优秀的选择,但各有其特点和优势。咱们的需求是需要支持普通话/英语/法语三种语言,以下是对各个平台的详细比较: 一、讯飞语音合成介绍 与语音听写相反,语音合成是将一段…...
SpringBoot开发——整合AJ-Captcha实现安全高效的滑动验证码
文章目录 一、什么是AJ-Captcha二、项目配置1、Maven依赖配置2、滑动验证码的基本原理3、 后端实现3.1 生成滑动验证码图片代码解释3.2 校验滑块位置代码解释4、前端部分代码解释5、Redis 缓存滑动验证码信息5.1 Redis配置5.2使用Redis缓存验证码数据5.3 校验时从Redis获取总结…...
Spring Security 核心组件
Spring Security 是一个功能全面的安全框架,用于处理基于 Spring 应用程序的身份验证和授权。 它提供了开箱即用的支持,采用行业标准的做法和机制来保护你的应用。 无论你是开发简单的 Web 应用还是复杂的微服务架构,理解 Spring Security …...

聚焦 AUTO TECH 2025华南展:探索新能源汽车发展新趋势
随着“新四化”浪潮的推进,汽车行业正经历前所未有的变革。中国新能源汽车正逐渐走向世界。国内汽车制造巨头如比亚迪、吉利、奇瑞、长安等,已经将出口提升至核心战略地位。中国新能源汽车的发展,不仅推动了全球汽车产业的电动化转型…...

Python-简单病毒程序合集(一)
前言:简单又有趣的Python恶搞代码,往往能给我们枯燥无味的生活带来一点乐趣,激发我们对编程的最原始的热爱。那么话不多说,我们直接开始今天的编程之路。 编程思路:本次我们将会用到os,paltform,threading,ctypes,sys,…...

[STM32]从零开始的STM32 HAL库环境搭建
一、前言 之前在搭建STM32的标准库环境时就告诉过大家,开发STM32的方式主要有三种。一种是最原始但是效率最高的寄存器开发,另一种是效率仅次于寄存器难度相对较低的标准库开发,最后一种是最为简单但是程序效率最低的HAL库开发。如果对于初学…...

XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...

CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...

深度学习习题2
1.如果增加神经网络的宽度,精确度会增加到一个特定阈值后,便开始降低。造成这一现象的可能原因是什么? A、即使增加卷积核的数量,只有少部分的核会被用作预测 B、当卷积核数量增加时,神经网络的预测能力会降低 C、当卷…...

七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...