C语言入门——指针初阶(2)-程序员宅基地

技术标签: C  c语言  开发语言  

在上篇内容中,我们学习了关于指针变量、野指针和部分指针运算的知识,对于指针有了一个初步的了解。本篇文章中将延续上文展开剩余篇幅。

1.指针的关系运算

指针的关系运算实际上就是指针比较大小(地址比较大小)

当我们想要打印一个数组的内容的时候,通常会这么做

#include <stdio.h>

int main()
{
    int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    int sz = sizeof(arr) / sizeof(arr[0]);
    for (int i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

但是当我们学了指针的关系运算之后,我们就可以将指针的比较作为循环条件来打印数组

#include <stdio.h>

int main()
{
    int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    int sz = sizeof(arr) / sizeof(arr[0]);
    int *p = &arr[0];
    while (p < arr + sz)
    {
        printf("%d ", *p);
        p++;
    }
    return 0;
}

2.const修饰指针

2.1 const修饰变量

我们知道,变量是可以被修改的。但是有时候在程序中我们并不希望创建的变量被修改,此时就可以用const修饰这个变量

上图中,变量x没有被const修饰,所以可以后续进行修改数值;变量y被const修饰后,再对它进行修改就会报错。

虽然我们无法修改被const修改的变量y,但是const仅仅是在语法上做了限制,y的本质还是变量,所以习惯上,我们称呼y为常变量。

但是尽管const修饰了变量y,我们还是可以通过指针使用y的地址去修改y的数值

2.2 const修饰指针变量

接上文,如果我们要想避免别人通过地址来修改被const修饰的变量的数值,就要用const将指针变量也修饰了

此时*p被const修饰后,再想通过地址修改变量y的值就会报错

另外的,const可以放在 * 的前面,也可以放在 * 的后面

1.const int *p

2.int const *p

3.int * const p

1和2中,const都在 * 的左边,此时修饰的是*p,也就是此时我们不能使用*p修改其指向空间的内容,但是我们仍然可以改变*p指向的空间

3中const在 * 的右边,此时修饰的是p,也就是此时我们不能改变*p指向的空间了,但是仍然可以使用*p修改其指向空间的内容

有点绕,但是不难理解。


3.assert断言

C语言中有宏assert(),被称为断言。我们可以在括号中输入一个表达式作为参数,若表达式的结果为真,程序继续执行,结果为假则报错并终止运行。

例如在上面const的讲解中,假设const只修饰了变量y,我们希望避免误操作导致使用了地址修改其数值,此时就可以使用assert()宏,使用之前记得包含<assert.h>头文件

我们将y初始化为0并且使用const修改后,不希望后续操作中修改其数值,就使用assert(y==0)来验证变量y的数值是否被修改,此时再使用地址来修改其数值就会报错

assert()的使用对程序员是十分友好的,它不仅能给出出错的行号,同时当我们已经确认程序无误后,不需要再做断言,我们就可以在头文件#include <assert.h>前面定义一个宏NDEBUG

#define NDEBUG
#include <assert.h>

4.传值调用和传址调用

先举个例子,我们写一个Add函数实现整型相加

#include <stdio.h>

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int a = 2;
	int b = 3;
	int ret = Add(a, b);
	return 0;
}

此处,我们将a和b的值传到了Add函数中,此时为传值调用,Add的形参和实参占用的是不一样的空间

在一些情况中,我们使用传值调用就可以解决问题,但是当我们在解决一些交换变量内容之类的问题的时候,传值调用就没有办法得到我们想要的效果,例如:

这是因为,传值调用中,实参传递给形参的时候,形参会创建一块临时空间来存放实参的值,所以此时形参和实参并不位于同一块空间,对形参的修改也影响不到实参

要解决这个问题,我们只需要把地址作为参数传入函数中

就顺利实现了我们想要的效果,这种方式就叫传址调用。

如果我们只需要使用变量值来进行计算,可以使用传值调用;但是当我们要在函数中修改实参的内容时,就要使用传址调用了


5.数组名和指针访问数组

5.1 数组名的理解

实际上,数组名就是数组首元素的地址。验证如下:

#include <stdio.h>

int main()
{
    int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    printf("&arr[0] = %p\n", &arr[0]);
    printf("arr = %p\n", arr);
    return 0;
}

我们创建一个数组,然后分别取arr和&arr[0]并打印地址,接着我们会发现二者完全一样

但是例外的,当我们运行这段代码时

#include <stdio.h>

int main()
{
    int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    printf("%d",sizeof(arr));
    return 0;
}

输出的结果是40,也就是整个数组的大小,但是arr不是首元素地址吗?输出的应该是4或8才对

事实上数组名的理解有两个例外:

  • sizeof内单独存放数组名时,此时的数组名表示整个数组,而不是首元素的地址,所以计算的是整个数组的大小
  • 当我们&数组名的时候(&arr),此时取出的不是首元素地址,而是整个数组的地址,+1的话也是跳过整个数组而不是跳过一个元素

除此之外,其他任何地方在使用数组名的时候都表示首元素地址

5.2 指针访问数组

在学习指针之前,我们访问数组的内容通常是这样的

#include <stdio.h>

int main()
{
	int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

学习指针之后,我们就可以用这种方式

#include <stdio.h>

int main()
{
    int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    int sz = sizeof(arr) / sizeof(arr[0]);
    int *p = arr;
    for (int i = 0; i < sz; i++)
    {
        printf("%d ", *(p + i));
    }
    return 0;
}

我们会发现,现在arr和p中存放的都是首元素的地址,所以arr和p是等价的,那么我们是否可以把arr[i]换成p[i]呢

完全没问题。而且我们也可以将*(p+i)替换成*(arr+i)

 也就是说,arr[i]和*(arr+i)其实是等价的,按照交换律

* ( arr + i ) == * ( i + arr )

∴ arr [ i ] == i [ arr ]

 

这种方法虽然可以,但是并不推荐这么做。平时我们还是最好按照习惯去写


6.一维数组作为函数参数

我们知道,数组也可以作为参数传递给函数。但是实际上传参的时候并不是传递一整个数组,而是传递数组的首元素地址。

例如我们分别在函数外和函数内分别求一个数组的元素个数

因为只是把数组的首元素地址传入了函数中,所以无法得到正确的数组元素个数。另外,函数的形参既可以写成上图中数组的形式,也可以写成指针的形式


7.二级指针

我们知道,指针变量内部存放了变量的地址,但是指针变量本身也是个变量,也有自己的地址,那么:指针变量的地址该存到哪里呢?

这里就引入二级指针的概念了

#include <stdio.h>

int main()
{
    int a = 10;
    int *pa = &a;
    int **ppa = &pa;
    return 0;
}

二级指针的一个显著标志就是有两颗 *,后一个 * 说明ppa是指针变量,前一个 * 和 int 组合说明ppa指向的类型是 int * 类型的变量。

 (地址随便编的)

对二级指针解引用就像剥洋葱一样,一层解完解下一层

*ppa = pa

*pa = a


8.指针数组和数组指针

8.1 指针数组

学习指针数组的时候很多人都容易混淆:指针数组是数组还是指针?

实际上很容易区分,就像整型数组是存放整型的数组、字符数组是存放字符的数组一样:指针数组就是存放指针的数组

指针数组的每个元素都是一个指针,也就是一个地址,分别指向不同的区域。

这么说来,前面提到数组名就是数组首元素地址,那么我们是否能使用指针数组模拟二维数组呢?

8.2 指针数组模拟二维数组

#include <stdio.h>

int main()
{
    int arr1[5] = {1, 2, 3, 4, 5};
    int arr2[5] = {2, 3, 4, 5, 6};
    int arr3[5] = {3, 4, 5, 6, 7};
    int *arr[3] = {arr1, arr2, arr3};
    for (int i = 0; i < 3;i++)
    {
        for (int j = 0; j < 5;j++)
        {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
    return 0;
}

上面这段代码中,arr是一个指针数组,操作符[ ]的优先级比 * 高,所以arr先和[ ]结合,代表arr是一个数组,arr前面的int *说明了数组内的元素类型

虽然我们可以使用指针数组模拟二维数组,但是二维数组中的元素都是连续的,而指针数组中的元素并不连续。

8.3 数组指针变量

同上,数组指针变量就是指向数组的指针变量,存放了数组的地址,实质上是一个指针变量

那么问题来了,你能认得出哪个是指针数组哪个是数组指针吗

int  *p [10]

int (*p)[10]

上面刚提到,操作符[ ] 的优先级比 * 高,要创建一个数组指针变量,就要用括号将*和指针变量名括起来,变量名先和*结合说明是一个指针变量,否则就会先和[ ]结合变成一个数组,所以第一个是指针数组,第二个是数组指针变量

现在我们知道了数组指针变量是用来存放数组的地址的,现在试试使用&操作符将数组的地址存到数组指针中吧

数组指针的类型很好区分,我们去掉指针变量名,剩下的就是数组指针的类型:

type (*p ) [ ]

<type>是数组指针指向的数组的元素类型,方括号中的数字就是数组指针指向的数组的元素个数


9.二维数组传参的本质

前面我们已经使用指针数组模拟过二维数组了,实际上,二维数组传参的本质和其十分相似。

二维数组,可以看作每个元素都是一个一维数组的数组,前面我们在学习中也知道了数组名就是数组的首元素地址。所以二维数组传参的时候实际上就是传递了首元素的地址——也就是第一个一维数组的地址。

那么我们用二维数组名加减整数,就可以访问其内部的一维数组;再对其内部一维数组的地址加减整数,就可以访问一维数组中的每一个元素。

通过这两层关系,我们就可以实现访问二维数组的每一个元素。


10.字符指针变量

我们知道指针变量的类型有int *,char *等等,而类型为char *的字符指针变量也有一些知识需要我们了解

平时我们可能很少使用到char *类型的指针变量,顶多可能偶尔会写到这样的代码

#include <stdio.h>

int main()
{
    char ch = 'w';
    char *pc = &ch;
    return 0;
}

很简单,很表层,但是字符指针变量并不仅限于这点功能

还有一种使用方法如下:

#include <stdio.h>

int main()
{
    char *pc = "hello";
    printf("%s\n", pc);
    return 0;
}

可能有些同学会以为我将“hello”这一个字符串都放在字符指针中了,实际上,当我们想将一个字符串存到字符指针中时,其实只是把首字符的地址放在了这个字符指针中

《剑指offer》中有一道和字符指针、字符串相关的笔试题,通过这道题我们再对字符指针有一个深入的了解

#include <stdio.h>
int main()
{
 char str1[] = "hello bit.";
 char str2[] = "hello bit.";
 const char *str3 = "hello bit.";
 const char *str4 = "hello bit.";
 if(str1 ==str2)
 printf("str1 and str2 are same\n");
 else
 printf("str1 and str2 are not same\n");
 
 if(str3 ==str4)
 printf("str3 and str4 are same\n");
 else
 printf("str3 and str4 are not same\n");
 
 return 0;
}

 运行这段代码,输出如下

当我们使用相同的字符串去初始化两个数组的时候,他们会开辟出不同的空间;而当几个不同的指针指向同一个字符串的时候,实际上这些指针指向了同一块内存。


11.函数指针变量

11.1 函数指针变量的理解

根据之前学习不同指针变量的经验,我们不难理解:函数指针变量就是存放函数的地址的指针变量

难道函数也有地址吗?我们可以测试一下

#include <stdio.h>

void test()
{
    printf("hello");
}

int main()
{
    printf("%p\n", test);
    printf("%p\n", &test);
    return 0;
}

随便创建一个函数,我们尝试打印它的地址,输出结果如下

确实打印出来了地址,而且我们发现直接用函数名和&函数名都可以获得函数的地址

说明:函数名就是函数的地址

知道了怎么取出函数的地址,我们就要知道怎么存放函数地址。函数指针变量的指针类型长得其实和数组指针非常相似:

type (*p ) (  )

其中,<type>是函数指针指向的函数的返回类型,*后面跟着函数指针变量名,第二个括号内填入函数指针指向的函数的不同参数类型

例如:

#include <stdio.h>

void test()
{
    printf("hello");
}

int Add(int x,int y)
{
    return x + y;
}

int main()
{
    void (*p1)() = test;
    int (*p2)(int, int) = Add; //第二个括号中可以只写类型不写参数名
    return 0;
}

11.2 函数指针变量的使用

我们知道,平时调用函数的时候一般只会写函数名,而函数名也是函数的地址,我们将函数地址存到指针变量中后,指针变量名是否就和函数名等价了呢?

#include <stdio.h>

int Add(int x,int y)
{
    return x + y;
}

int main()
{
    int (*p)(int, int) = Add;
    printf("%d\n", (*p)(2, 3));
    printf("%d\n", p(3, 5));
    return 0;
}

事实上,不管是否对函数指针解引用,我们都可以调用函数指针指向的函数

输出结果:


12.typedef关键字

typedef 可以帮助我们将一些复杂的类型重命名

例如我觉得 unsigned int 这个类型太长了,写代码不方便,能不能改简单一点呢?

我们只需要:

typedef unsigned int uint;

上面,我们就使用了 typedef 将 unsigned int 重命名为了 uint ,在后续的代码中我们只需要使用 uint 就可以实现 unsigned int 的功能了

当然,指针变量也能修改

typedef int* pty_t;

但是对于数组指针和函数指针,修改的方式就有所不同了,我们要在第一个括号内部修改新类型名

typedef int(*parr_t)[10];
typedef void(*pfun_t)(int,int);

13.函数指针数组和转移表

13.1 函数指针数组

存放函数的地址的数组就是函数指针数组,我们已经学习了指针数组和函数指针,那么你知道函数指针数组该怎么定义吗?

type (*parr [ ]) ( )

 其中,parr先和 [ ] 结合,说明是一个数组,其内部元素类型是 type(* )( ) 

13.2 转移表

学习函数指针数组后,我们就可以用它制作转移表

例如我们写一个计算器,没学函数指针数组之前会这样写
 

#include <stdio.h>

int add(int a, int b)
{
    return a + b;
}
int sub(int a, int b)
{
    return a - b;
}
int mul(int a, int b)
{
    return a * b;
}
int div(int a, int b)
{
    return a / b;
}
int main()
{
    int x, y;
    int input = 1;
    int ret = 0;
    do
    {
        printf("*************************\n");
        printf("****** 1:add 2:sub ******\n");
        printf("****** 3:mul 4:div ******\n");
        printf("****** 0:exit      ******\n");
        printf("*************************\n");
        printf("请选择:");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = add(x, y);
            printf("ret = %d\n", ret);
            break;
        case 2:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = sub(x, y);
            printf("ret = %d\n", ret);
            break;
        case 3:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = mul(x, y);
            printf("ret = %d\n", ret);
            break;
        case 4:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = div(x, y);
            printf("ret = %d\n", ret);
            break;
        case 0:
            printf("退出程序\n");
            break;
        default:
            printf("选择错误\n");
            break;
        }
    } while (input);
    return 0;
}

 会发现,其中有很多段代码是重复的,太长了且太冗余

当我们使用函数指针数组后,就可以省去switch中的大段重复代码

#include <stdio.h>
int add(int a, int b)
{
    return a + b;
}
int sub(int a, int b)
{
    return a - b;
}
int mul(int a, int b)
{
    return a * b;
}
int div(int a, int b)
{
    return a / b;
}
int main()
{
    int x, y;
    int input = 1;
    int ret = 0;
    int (*p[5])(int x, int y) = {0, add, sub, mul, div}; // 转移表
    do
    {
        printf("*************************\n");
        printf("****** 1:add 2:sub ******\n");
        printf("****** 3:mul 4:div ******\n");
        printf("****** 0:exit      ******\n");
        printf("*************************\n");
        printf("请选择:");
        scanf("%d", &input);
        if ((input <= 4 && input >= 1))
        {
            printf("输⼊操作数:");
            scanf("%d %d", &x, &y);
            ret = (*p[input])(x, y);
            printf("ret = %d\n", ret);
        }
        else if (input == 0)
        {
            printf("退出计算器\n");
        }
        else
        {
            printf("输⼊错误\n");
        }
    } while (input);
    return 0;
}

上面这段代码中的函数指针数组就是转移表。转移表是一种数据结构,主要用于存储预先计算的结果,以便在需要时进行快速查找。


14.回调函数

回调函数就是通过函数指针调用的函数

例如上面的实现计算机功能中有这么一段代码

switch (input)
        {
        case 1:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = add(x, y);
            printf("ret = %d\n", ret);
            break;
        case 2:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = sub(x, y);
            printf("ret = %d\n", ret);
            break;
        case 3:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = mul(x, y);
            printf("ret = %d\n", ret);
            break;
        case 4:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = div(x, y);
            printf("ret = %d\n", ret);
            break;
        case 0:
            printf("退出程序\n");
            break;
        default:
            printf("选择错误\n");
            break;
        }

我们可以写一个函数来避免重复的代码,将上面的add、sub、mul、div函数作为参数传入

void calc(int (*pf)(int, int))
{
    int ret = 0;
    int x, y;
    printf("输⼊操作数:");
    scanf("%d %d", &x, &y);
    ret = pf(x, y); //通过指针调用函数
    printf("ret = %d\n", ret);
}

此时pf被用来调用其指向的函数,被调用的函数就是回调函数

优化后的代码:

switch (input)
{
case 1:
    calc(add);
    break;
case 2:
    calc(sub);
    break;
case 3:
    calc(mul);
    break;
case 4:
    calc(div);
    break;
case 0:
    printf("退出程序\n");
    break;
default:
    printf("选择错误\n");
    break;
}

明显更加简洁,避免了代码的重复

觉得有用的话可以点点关注点点赞,你的支持是我创作的动力

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Eristic0618/article/details/134545676

智能推荐

攻防世界_难度8_happy_puzzle_攻防世界困难模式攻略图文-程序员宅基地

文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文

达梦数据库的导出(备份)、导入_达梦数据库导入导出-程序员宅基地

文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作  导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释:   cwy_init/init_123..._达梦数据库导入导出

js引入kindeditor富文本编辑器的使用_kindeditor.js-程序员宅基地

文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js

STM32学习过程记录11——基于STM32G431CBU6硬件SPI+DMA的高效WS2812B控制方法-程序员宅基地

文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6

计算机网络-数据链路层_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输

软件测试工程师移民加拿大_无证移民,未受过软件工程师的教育(第1部分)-程序员宅基地

文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...

随便推点

Thinkpad X250 secure boot failed 启动失败问题解决_安装完系统提示secureboot failure-程序员宅基地

文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure

C++如何做字符串分割(5种方法)_c++ 字符串分割-程序员宅基地

文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割

2013第四届蓝桥杯 C/C++本科A组 真题答案解析_2013年第四届c a组蓝桥杯省赛真题解答-程序员宅基地

文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答

基于供需算法优化的核极限学习机(KELM)分类算法-程序员宅基地

文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。

metasploitable2渗透测试_metasploitable2怎么进入-程序员宅基地

文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入

Python学习之路:从入门到精通的指南_python人工智能开发从入门到精通pdf-程序员宅基地

文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf

推荐文章

热门文章

相关标签