C语言 13 指针

news2024/12/29 9:12:14

指针可以说是整个 C 语言中最难以理解的部分了。

什么是指针

还记得在前面谈到的通过函数交换两个变量的值吗?

#include <stdio.h>

void swap(int, int);

int main() {
    int a = 10, b = 20;
    swap(a, b);
    printf("a = %d, b = %d", a, b);
}

void swap(int a, int b){
    // 这里对a和b的值进行交换
    int tmp = a;   
    a = b;
    b = tmp;
}

实际上这种写法是错误的,因为交换的并非是真正的 a 和 b,而是函数中的局部变量。

那么有没有办法能够直接对函数外部的变量进行操作呢?这就需要指针的帮助了。

程序中使用的变量实际上都是在内存中创建的,每个变量都会被保存在内存的某一个位置上(具体哪个位置由系统分配),所有的变量在对应的内存位置上都有一个地址(地址是独一无二的),可以通过这个地址寻找到这个变量本体,比如 int 占据 4 字节,因此 int 类型变量的地址就是这 4 个字节的起始地址,后面 32 个 bit 位全部都是用于存放此变量的值的。

这里的0x是十六进制的表示形式(10 - 15 用字母 A - F 表示)

如果能够知道变量的内存地址,那么无论身在何处,都可以通过地址找到这个变量了。

而指针的作用,就是专门用来保存这个内存地址的。

来看看如何创建一个指针变量用于保存变量的内存地址:

#include <stdio.h>

int main() {
    int a = 10;
    // 指针类型需要与变量的类型相同,且后面需要添加一个*符号(注意这里不是乘法运算),表示是对于类型的指针
    // 这里的&并不是进行按位与运算,而是取地址操作,也就是拿到变量a的地址
    int* p = &a;                         
    // 地址使用%p表示
    printf("a在内存中的地址为:%p", p);  
}
a在内存中的地址为:00000000005ffe84

可以看到,通过取地址操作&,将变量 a 的地址保存到了一个地址变量p中。

拿到指针之后,就可以很轻松地获取指针所指地址上的值:

#include <stdio.h>

int main() {
    int a = 666;
    int* p = &a;
    // 可以在指针变量前添加一个*号(间接运算符,也可以叫做解引用运算符)来获取对应地址存储的值
    printf("内存%p上存储的值为:%d", p, *p);  
}
内存00000000005ffe84上存储的值为:666

注意这里访问指针所指向地址的值时,是根据类型来获取的,比如 int 类型占据 4 个字节,那么就读取地址后面 4 个字节的内容作为一个 int 值,如果指针是 char 类型的,那么就只读取地址后面 1 个字节作为 char 类型的值。

同样的,也可以直接像这样去修改对应地址存放的值:

#include <stdio.h>

int main() {
    int a = 666;
    int* p = &a;
    // 通过*来访问对应地址的值,并通过赋值运算对其进行修改
    *p = 999;  
    printf("a的值为:%d", a);
}
a的值为:999

实际上拿到一个变量的地址之后,完全不需要再使用这个变量,而是可以通过它的指针来对其进行各种修改。

因此,现在想要实现对两个变量的值进行交换的函数就很简单了:

#include <stdio.h>

// 这里是两个指针类型的形参,其值为实参传入的地址,
// 虽然依然是值传递,但是这里传递的是地址
// 只要知道地址改变值就很容易了
void swap(int* a, int* b) {
    // 先暂存一下变量a地址上的值
    int tmp = *a;  
    // 将变量b地址上的值赋值给变量a地址上的值
    *a = *b;       
    // 最后将a的值赋值给b地址上的值,这样就成功交换两个变量的值了
    *b = tmp;      
}

int main() {
    int a = 10, b = 20;
    // 只需要把a和b的内存地址给过去就行了,这里取一下地址
    swap(&a, &b);  
    printf("a = %d, b = %d", a, b);
}
a = 20, b = 10

通过地址操作,就轻松实现了使用函数交换两个变量的值了。


了解了指针的相关操作之后,再来看看scanf函数,实际上就很好理解了:

#include <stdio.h>

int main(){
    int a;
    // 这里就是取地址,需要告诉scanf函数变量的地址,这样它才能通过指针访问变量的内存地址,对变量的值进行修改,这也是为什么scanf里面的变量(除数组外)前面都要进行一个取地址操作
    scanf("%d", &a);   
    printf("%d", a);
}

当然,和变量一样,要是不给指针变量赋初始值的话,就不知道指向哪里了,因为指针变量也是变量,存放的对应变量的地址值也在内存中保存,如果不给初始值,那么存放变量地址的这块内存可能在其他地方使用过,这样就不知道初始值是多少了(那么指向的地址可能是一个很危险的地址,随意使用可能导致会出现严重错误),所以一定要记得给个初始值或是将其设定为 NULL,表示空指针,不指向任何内容。

#include <stdio.h>

int main(){
    int* a = NULL;
}

接着来看看const类型的指针,这种指针比较特殊:

#include <stdio.h>

int main() {
    int a = 9, b = 10;
    const int* p = &a;
    // 报错,因为被const标记的指针,所指地址上的值不允许发生修改
    *p = 20;
    // 但是指针指向的地址是可以发生改变的
    p = &b;
}

再来看另一种情况:

#include <stdio.h>

int main() {
    int a = 9, b = 10;
    // const关键字被放在了类型后面
    int* const p = &a;
    // 允许修改所指地址上的值
    *p = 20;
    // 报错,不允许修改指针存储的地址值,其实就是反过来了
    p = &b;
}

当然也可以双管齐下:

#include <stdio.h>

int main(){
    int a = 9, b = 10;
    const int * const p = &a;
    *p = 20;   //两个都直接报错,都不让改了
    p = &b;
}

指针与数组

前面介绍了指针的基本使用,来回顾一个问题,为什么数组可以原身在函数之间进行传递呢?

先说结论,数组表示法实际上是在变相地使用指针,甚至可以理解为数组变量其实就是一个指针变量,它存放的就是数组中第一个元素的起始地址

为什么这么说?

#include <stdio.h>

int main() {
    char str[] = "Hello World!";
    // 为什么能直接把数组作为地址赋值给指针变量
    char* p = str;  
    // 还能正常使用,打印出第一个字符
    printf("%c", *p);  
}
H

还能这样玩:

int main() {
    char str[] = "Hello World!";
    char* p = str;
    // 还可以像在使用数组一样用指针
    printf("%c", p[1]);
}
e

怎么数组和指针还能这样混着用呢?先来看看数组在内存中是如何存放的:

数组在内存中是一块连续的空间,所以为什么声明数组一定要明确类型和大小,因为这一块连续的内存空间生成后就固定了。

而数组变量实际上存放的就是首元素的地址,而实际上之前一直使用的都是数组表示法来操作数组,这样可以很方便地对内存中的各个元素值进行操作:

int main(){
    char str[] = "Hello World!";
    // 直接在中括号中输入对应的下标就能访问对应位置上的数组了
    printf("%c", str[0]);   
}

而实际上str表示的就是数组的首地址,所以完全可以将其赋值给一个指针变量,因为指针变量也是存放的地址:

char str[] = "Hello World!";
// 直接把str代表的首元素地址给到p
char* p = str;   

而使用指针后,实际上可以使用另一种表示法来操作数组,这种表示法叫做指针表示法

#include <stdio.h>

int main() {
    char str[] = "Hello World!";
    char* p = str;
    // 通过指针也可以表示对应位置上的值
    printf("第一个元素值为:%c,第二个元素值为:%c", *p, *(p + 1));
}
第一个元素值为:H,第二个元素值为:e

比如现在需要表示数组中的第二个元素:

  • 数组表示法:str[1]
  • 指针表示法:*(p+1)

虽然写法不同,但是他们表示的意义是完全相同的,都代表了数组中的第二个元素,其中指针表示法使用了p+1的形式表示第二个元素,这里的+1操作并不是让地址+1,而是让地址+ 一倍的对应类型大小,也就是说地址后移一个char 的长度,所以正好指向了第二个元素,然后通过*取到对应的值(注意这种操作仅对数组是有意义的,如果是普通的变量,虽然也可以获得后一个 char 的长度的数据,但是毫无意义)

这两种表示法都可以对内存中存放的数组内容进行操作,只是写法不同罢了,所以数组和指针混用也就不奇怪了。

了解了这些东西之后,再来看看下面的各个表达式分别代表什么:

#include <stdio.h>

int main() {
    char str[] = "Hello World!";
    char* p = str;
    // 数组的第一个元素
    printf("*p的值:%c\n", *p);   
    // 数组的第一个元素的地址
    printf("p的值:%p\n", p);
    // 肯定是真,因为都是数组首元素地址
    printf("p == str的值:%d\n", p == str);   
    // 因为str就是首元素的地址,所以这里对地址加*就代表第一个元素,使用的是指针表示法
    printf("*str的值:%c\n", *str);    
    // 这里得到的实际上还是首元素的地址
    printf("&str[0]的值:%p\n", &str[0]);   
    // 代表第二个元素
    printf("*(p + 1)的值:%c\n", *(p + 1));   
    // 第二个元素的内存地址
    printf("p + 1的值:%p\n", p + 1);    
    // 注意*的优先级比+要高,所以这里代表的是首元素的值+1,得到字符'I'
    printf("*p + 1的值:%c\n", *p + 1);    
}
*p的值:H
p的值:00000000005ffe7b
p == str的值:1
*str的值:H
&str[0]的值:00000000005ffe7b
*(p + 1)的值:e
p + 1的值:00000000005ffe7c
*p + 1的值:I

所以不难理解,为什么printf函数的第一个参数是const char*了,实际上就是需要传入一个字符串而已,只不过这里采用的是指针表示法而已。

当然指针也可以进行自增和自减操作,比如:

#include <stdio.h>

int main() {
    char str[] = "Hello World!";
    char* p = str;
    // 自增后相当于指针指向了第二个元素的地址
    p++;
    // 所以这里打印的就是第二个元素的值了
    printf("%c", *p);
}
e

一维数组看完了,再来看看二维数组,那么二维数组在内存中是如何表示的呢?

int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};

这是一个2x3的二维数组,其中存放了两个能够容纳三个元素的数组,在内存中,是这样的:

所以也可以使用指针来进行访问:

#include <stdio.h>

int main() {
    int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
    // 因为是二维数组,注意这里要指向第一个元素,需要降一个维度才能正确给到指针
    int* p = arr[0];  
    // 同理如果这里是arr[1]的话那么就表示指向二维数组中第二个数组的首元素
    // 实际上这两种访问形式都是一样的
    printf("%d = %d", *(p + 4), arr[1][1]);  
}
5 = 5

多级指针

实际上指针本身也是一个变量,它存放的是目标的地址,但是它本身作为一个变量,也要将地址信息保存到内存中,所以,实际上当有指针之后:

实际上,还可以继续创建一个指向指针变量地址的指针,甚至可以创建更多级(比如指向指针的指针的指针)

比如现在要创建一个指向指针的指针:

落实到代码中:

#include <stdio.h>

int main() {
    int a = 20;
    // 指向普通变量的指针
    int* p = &a;
    // 因为现在要指向一个int *类型的变量,所以类型为int* 再加一个*
    // 指向指针的指针(二级指针)
    int** pp = &p;
    // 指向指针的指针的指针(三级指针)
    int*** ppp = &pp;
    // 使用一次*表示二级指针指向的指针变量,继续使用一次*会继续解析成指针变量所指的普通变量
    printf("p = %p, a = %d", *pp, **pp);  
}
p = 00000000005ffe84, a = 20

本质其实就是一个套娃而已,只要把各个层次分清楚,实际上还是很好理解的。

特别提醒: 一级指针可以操作一维数组,那么二级指针是否可以操作二维数组呢?不能!因为二级指针的含义都不一样了,它是表示指针的指针,而不是表示某个元素的指针了。下面会认识数组指针,准确的说它才更贴近于二维数组的形式。

指针数组与数组指针

前面了解了指针的一些基本操作,包括它与数组的一些关系。接着来看指针数组和数组指针,这两词语看着就容易搞混,不过哪个词在后面就哪个,先来看指针数组,虽然名字很像数组指针,但是它本质上是一个数组,不过这个数组是用于存放指针的数组。

#include <stdio.h>

int main() {
    int a, b, c;
    // 可以看到,实际上本质还是数组,只不过存的都是地址
    int* arr[3] = {&a, &b, &c};
    // []运算符的优先级更高,所以这里先通过[0]取出地址,然后再使用*将值赋值到对应的地址上
    *arr[0] = 999;  
    printf("%d", a);
}

当然也可以用二级指针变量来得到指针数组的首元素地址:

#include <stdio.h>

int main(){
    int * p[3];   //因为数组内全是指针
    int ** pp = p;  //所以可以直接使用指向指针的指针来指向数组中的第一个指针元素
}

实际上指针数组还是很好理解的,那么数组指针呢?可以看到指针在后,说明本质是一个指针,不过这个指针比较特殊,它是一个指向数组的指针(注意它的目标是整个数组,和之前认识的指针不同,之前认识的指针是指向某种类型变量的指针)

数组指针表示指向整个数组:

// 注意这里需要将*p括起来,因为[]的优先级更高
int (*p)[3];   

注意它的目标是整个数组,而不是普通的指针那样指向的是数组的首个元素:

int arr[3] = {111, 222, 333};
// 直接对整个数组再取一次地址(因为数组指针代表的是整个数组的地址,虽然和普通指针一样都是指向首元素地址,但是意义不同)
int (*p)[3] = &arr;  

那么现在已经取到了指向整个数组的指针,该怎么去使用呢?

#include <stdio.h>

int main() {
    int arr[3] = {111, 222, 333};
    // 直接对整个数组再取一次地址
    int(*p)[3] = &arr;  
    // 要获取数组中的每个元素,稍微有点麻烦
    printf("%d, %d, %d", *(*p + 0), *(*p + 1), *(*p + 2));  
}
111, 222, 333

注意此时:

  • p代表整个数组的地址
  • *p表示所指向数组中首元素的地址
  • *p + i表示所指向数组中第i个(0 开始)元素的地址(实际上这里的 *p 就是指向首元素的指针)
  • *(*p + i)就是取对应地址上的值了

虽然在处理一维数组上感觉有点麻烦,但是它同样也可以处理二维数组:

#include <stdio.h>

int main() {
    int arr[][3] = {{111, 222, 333}, {444, 555, 666}};
    // 二维数组不需要再取地址了,因为现在维度提升,数组指针指向的是二维数组中的其中一个元素(因为元素本身就是一个数组)
    int (*p)[3] = arr;
    // 现在想要访问第一个数组的第二个元素
    // 因为上面直接指向的就是第一个数组,所以想要获取第一个数组的第二个元素和之前是一模一样的
    printf("%d\n", *(*p + 1));
    // 现在想要获取第二个数组中的最后一个元素
    // 首先*(p + 1)为一个整体,表示第二个数组(因为是数组指针,所以这里 +1 一次性跳一个数组的长度),然后再到外层 +2 表示数组中的第三个元素,最后再取地址,就是第二个数组的第三个元素了
    printf("%d\n", *(*(p + 1) + 2));
    // 当然也可以使用数组表示法
    // 这就是二维数组的用法,甚至可以认为这两个是同一个东西
    printf("%d\n", p[1][2]);
}
222
666
666

指针函数与函数指针

函数可以返回一个指针类型的结果,这种函数就称为指针函数

#include <stdio.h>

// 函数的返回值类型是int*指针类型的
int* test(int* a) {  
    return a;
}

int main() {
    int a = 10;
    // 使用指针去接受函数的返回值
    int* p = test(&a);  
    printf("%d\n", *p);
    // 当然也可以直接把间接运算符在函数调用前面表示直接对返回的地址取地址上的值
    printf("%d\n", *test(&a));  
}
10
10

不过要注意指针函数不要尝试去返回一个局部变量的地址:

#include <stdio.h>

int* test(int a) {
    int i = a;
    // 返回局部变量i的地址
    return &i;
}

int main() {
    // 连续调用两次test函数
    int* p = test(20);  
    test(30);
    // 这里会报错
    printf("%d", *p);
}

为什么会这样呢?因为函数一旦返回,那么其中的局部变量就会全部销毁了,至于这段内存之后又会被怎么去使用,就不得而知了。


接着来看函数指针,实际上指针除了指向一个变量之外,也可以指向一个函数,当然函数指针本身还是一个指针,所以依然是用变量表示,但是它代表的是一个函数的地址(编译时系统会为函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址)

来看看如何定义:

#include <stdio.h>

int sum(int a, int b) {
    return a + b;
}

int main() {
    // 类型 (*指针变量名称)(函数参数...)  
    // 注意一定要把*和指针变量名称括起来,不然优先级不够
    int (*p)(int, int) = sum;
    printf("%p", p);
}
00007ff6524713b4

这样就拿到了函数的地址,既然拿到函数的地址,就可以通过函数的指针调用这个函数了:

#include <stdio.h>

int sum(int a, int b) {
    return a + b;
}

int main() {
    int (*p)(int, int) = sum;
    // 就像正常使用函数那样,(*p)表示这个函数,后面依然是在小括号里面填上实参
    int result1 = (*p)(1, 2);
    printf("%d\n", result1);
    // 当然也可以直接写函数指针变量名称,效果一样
    int result2 = p(1, 2);
    printf("%d\n", result2);
}
3
3

有了函数指针,就可以编写函数回调了(所谓回调就让别人去调用提供的函数,而不是主动来调别人的函数)

比如现在定义了一个函数,不过这个函数需要参数通过一个处理的逻辑才能正常运行,所以就还要给他一个其他函数的地址:

#include <stdio.h>

// 将函数指针作为参数传入
int sum(int (*p)(int, int), int a, int b) {
    // 函数回调
    return p(a, b);
}

// 这个函数实现了a + b
int sumImpl(int a, int b) {  
    return a + b;
}

int main() {
    // 拿到实现那个函数的地址
    int (*p)(int, int) = sumImpl;  
    printf("%d", sum(p, 10, 20));
}
30

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

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

相关文章

SSH 弱密钥交换算法 通过禁用CBC模式解决SSH服务器CBC加密模式漏洞(CVE-2008-5161)

自查方法 查看当前支持的加密算法 man sshd_config |grep -A 40 -w KexAlgorithms 修复方法 Linux平台 修改sshd_config配置文件&#xff0c;删除不安全的加密算法 重启服务 systemctl restart sshd 3.查看修改后的配置文件 sshd -T | grep -w kexalgorithms SSH 弱密…

【Python基础】Python迭代器与生成器(两种强大工具)

本文收录于 《Python编程入门》专栏&#xff0c;从零基础开始&#xff0c;分享一些Python编程基础知识&#xff0c;欢迎关注&#xff0c;谢谢&#xff01; 文章目录 一、前言二、迭代器2.1 创建迭代器2.2 自定义迭代器2.3 处理大型文件 三、生成器四、生成器表达式五、实际应用…

【数据结构初阶】队列接口实现及用队列实现栈超详解

文章目录 1. 概念1. 1 队列底层结构选型1. 2 队列定义 2. 接口实现2. 1 初始化2. 2 判空2. 3 入队列2. 4 出队列2. 5 队尾元素和队头元素和队列元素个数2. 6 销毁2. 7 接口的意义 3. 经典OJ题3. 1 用队列实现栈3. 1. 1 栈的定义3. 1. 2 栈的初始化3. 1. 3 入栈3. 1. 4 出栈3. 1…

计算机视觉(二)—— MDPI特刊推荐

特刊征稿 01 期刊名称&#xff1a; Applied Computer Vision and Pattern Recognition: 2nd Volume 截止时间&#xff1a; 摘要提交截止日期&#xff1a;2024年10月30日 投稿截止日期&#xff1a;2024年12月30日 目标及范围&#xff1a; 包括但不限于以下领域&#xff1a…

C++:线程库

C&#xff1a;线程库 threadthreadthis_threadchrono 引用拷贝问题 mutexmutextimed_mutexrecursive_mutexlock_guardunique_lock atomicatomicCAS condition_variablecondition_variable thread 操作线程需要头文件<thread>&#xff0c;头文件包含线程相关操作&#xf…

上班炒股会被开除吗?公司是如何发现员工上班炒股?一文告诉你答案!

随着互联网金融的发展&#xff0c;股票交易变得越来越便捷&#xff0c;不少上班族选择利用工作之余的时间来进行股票投资。 然而&#xff0c;这种行为是否合规&#xff1f;公司又是如何发现并处理这种情况的呢&#xff1f;本文将为您解答这些问题。 一、上班炒股是否合规&…

JAVA毕业设计175—基于Java+Springboot+vue3的医院预约挂号管理系统(源代码+数据库)

毕设所有选题&#xff1a; https://blog.csdn.net/2303_76227485/article/details/131104075 基于JavaSpringbootvue3的医院预约挂号管理系统(源代码数据库)175 一、系统介绍 本项目前后端分离(可以改为ssm版本)&#xff0c;分为用户、医生、管理员三种角色 1、用户&#x…

交换机最常用的网络变压器分为DIP和SM

华强盛电子导读&#xff1a;交换机通用网络变压器插件48PIN最为常见 您好&#xff01;今天我要给您介绍一款真正能为您的工业生产带来变革的产品——华强盛工业滤波器。在如今这个高度数字化的工业时代&#xff0c;可靠的网络连接至关重要&#xff0c;而华强盛工业网络变压器就…

Smartbi体验中心新增系列Demo,用户体验更丰富

为进一步提升用户体验&#xff0c;让大家更直观地了解Smartbi产品在数据分析方面的功能优势&#xff0c;Smartbi体验中心近期新增了一系列Demo。这些更新旨在优化产品操作流程&#xff0c;并为用户提供更多真实场景下的应用参考。接下来&#xff0c;我们一起简要浏览此次体验中…

KEIL仿真时弹窗 “Cannot access target.”

现象 仿真时&#xff0c;点击暂停就会弹出下图窗口。 Cannot access target. Shutting down debug session. 解决方法 开启STM32的Debug&#xff0c;如下图。

基于WIFI的开关控制器设计与实现

本设计基于STC89C52RC单片机设计的WiFi开关器系统&#xff0c;旨在通过软硬件设计实现按键和手机APP远程同步控制4个继电器驱动3个LED灯、1个风扇&#xff0c;并且具备时间显示、开关状态显示、定时驱动&#xff0c;时间设定及保存等功能。该设计在硬件方面采用STC89C52单片机作…

软考中项(第三版) 项目成本管理总结

前言 系统集成项目管理工程师考试&#xff08;简称软考中项&#xff09;&#xff0c;其中案例分析也是很大一部分考试内容&#xff0c;目前正在学习中&#xff0c;现总结一些可能会考到的知识点供大家参考。 1.1、项目成本管理总线索 1、项目成本失控的原因 &#xff08;1&a…

python库安装失败问题

pip install XXXX 报错信息如下 D:\Dev>pip install D:\Dev\robotlib-0.0.33.tar.gz DEPRECATION: Loading egg at d:\app\dev\python\lib\site-packages\fs11a3_package-1.3-py3.11.egg is deprecated. pip 24.3 will enforce this behaviour change. A possible replace…

【机器学习】使用Numpy实现神经网络训练全流程

文章目录 网络搭建前向传播反向传播损失计算完整代码 曾经在面试一家大模型公司时遇到的面试真题&#xff0c;当时费力写了一个小时才写出来&#xff0c;自然面试也挂了。后来复盘&#xff0c;发现反向传播掌握程度还是太差&#xff0c;甚至连梯度链式传播法则都没有弄明白。 网…

solidity-19-fallback

接收ETH receive和fallback receive和callback是solidity中两个特殊的回调函数&#xff0c;一个处理接收ETH,一个处理不存在的函数调用。本质上就是吧fallback拆成了两个回调函数。我暂时不知道什么是fallback fallback调用不存在的函数时会被调用也就是这个函数是不是等价于…

视频转音频,分享这六种转换操作

视频转音频&#xff0c;随着多媒体技术的发展&#xff0c;人们越来越频繁地需要将视频中的音频部分提取出来单独使用。无论是为了制作播客、获取音乐片段还是其他需求&#xff0c;视频转音频都是一项非常实用的技能。为了让你轻松应对各种场合的需求&#xff0c;下文将为你详细…

day-55 不同路径

思路 动态规划&#xff1a;因为只能向右或向下移动&#xff0c;可以得出状态转换方程&#xff1a;dp[i][j]dp[i-1][j]dp[i][j-1] 解题过程 直接令第一行和第一列全为1&#xff0c;然后通过状态转换方程进行计算&#xff0c;返回dp[m-1][n-1]即可 Code class Solution {publi…

Centos挂载和删除nfs

一、Centos挂载nfs 1、安装NFS客户端软件 sudo yum install nfs-utils 2、 创建一个挂载点目录 mkdir -p /mnt/nfs 注意:目录可以随意创建 3、永久挂载nfs 即系统在每次启动后自动挂载NFS共享 (1)编辑 /etc/fstab vim /etc/fstab (2)添加nfs <nfs_server_ip&…

AI算法盒如何精准守护你的安全区域

在当今智能化时代&#xff0c;安全防范已成为社会各个领域的核心需求之一。万物AI算法盒&#xff0c;作为前沿科技的集大成者&#xff0c;其内置的区域人员入侵检测视觉算法&#xff0c;以卓越的性能和广泛的应用场景&#xff0c;为各行各业提供了高效、精准的安全解决方案。 核…

使用iperf3测试局域网服务器之间带宽

文章目录 一、下载安装1、windows2、centos 二、使用0、参数详解1、centos 一、下载安装 1、windows https://iperf.fr/iperf-download.php 拉到最下面选最新版&#xff1a; 2、centos yum install iperf3二、使用 0、参数详解 服务器或客户端&#xff1a; -p, --port #…