C语言基础:指针的使用

news2025/1/10 10:27:57

本文结合工作经验,研究C语言中指针的用法。

文章目录

  • 1 指针的概念
  • 2 用法与使用场景
    • 2.1 函数的指针参数
      • 2.1.1 基本概念
      • 2.1.2 使用场景1-函数返回多个值
      • 2.1.3 使用场景2-减少函数参数
    • 2.2 void*指针
      • 2.2.1 基本概念
      • 2.2.2 使用场景
    • 2.3 空指针
    • 2.4 const指针
      • 2.4.1 基本概念
      • 2.4.2 使用场景
  • 3 总结

1 指针的概念

指针是C语言的精髓,用于存放变量的地址。通过指针可以间接地访问该地址中所存储变量的数值。对于指针,首先需要理解&和*两个运算符的含义,举例如下。

#include <stdio.h>

int main()
{
    int a = 1;
    int* p = &a;
    int b = *p;
    printf("变量a的地址是%p\r\n", p);
    printf("变量a的数值是%d\r\n", a);
    printf("变量b的数值是%d\r\n", *p);
}

首先,定义一个int类型的变量a,同时赋值为1;接着定义一个指针p,赋值为变量a的地址(通过&运算符取地址);然后分别打印出变量a的地址p以及变量a的数值,接着打印变量b的数值,通过*运算符获取p地址中的变量。

上面是个非常基础的例子,是大学一年级学生就应该掌握的。博主根据工作经验,总结指针在汽车软件C语言开发中运用的场景。

2 用法与使用场景

2.1 函数的指针参数

2.1.1 基本概念

大学就学过,C语言函数的参数是形参。在函数内部,无论形参如何改变,都无法改变函数外的实参。典型的例子是通过函数交换a和b的数值,如下。

#include <stdio.h>

void swap(int a, int b)
{
    int temp;
    temp = a;
    a = b;
    b = temp;
}

void swap_pointer(int* a_p, int* b_p)
{
    int temp;
    temp = *a_p;
    *a_p = *b_p;
    *b_p = temp;
}

int main()
{
    int a = 1;
    int b = 2;
    printf("a = %d, b = %d \r\n", a, b);
    swap(a, b);
    printf("After swap(a, b) : a = %d, b = %d \r\n", a, b);
    swap_pointer(&a, &b);
    printf("After swap_pointer(a, b) : a = %d, b = %d \r\n", a, b);

}

代码中,定义了swap和swap_pointer两个函数。前者传参是int类型的变量,在函数内部交换a和b;后者传参是指针参数,在函数内部通过地址解引用的方式交换数值。运行代码后,如下图:

在这里插入图片描述
这是因为前者的函数传参是形参,只是外部传入的参数的复制,交换了数值不影响外部;而后者传入的地址是和外面的a,b的地址是一样的,所以直接操作地址对应的内存空间就能影响到函数外部。

2.1.2 使用场景1-函数返回多个值

指针作为函数参数比较常见于函数需要输出多个返回值的场景。

函数只有一个输出时,可以用return返回值的方式。例如下面的代码,通过将圆的半径作为参数传递给函数,函数经过计算返回圆的周长。

#include <stdio.h>

float calculate_perimeter(float radius)
{
    return 2 * 3.14 * radius;
}

int main()
{
    float radius = 2.0;
    float perimeter = calculate_perimeter(radius);
    printf("radius = %f, perimeter = %f \r\n", radius, perimeter);

}

通过调用calculate_perimeter()函数,从他的返回值获取了半径对应的周长。但是如果需求更加复杂一点,希望通过半径计算圆的周长和面积,如果还是通过返回值的形式就必须设计两个函数,如下。

#include <stdio.h>

float calculate_perimeter(float radius)
{
    return 2 * 3.14 * radius;//2*PI*R
}

float calculate_area(float radius)
{
    return 3.14 * radius * radius;//PI*R^2
}

int main()
{
    float radius = 2.0;
    float perimeter = calculate_perimeter(radius);
    float area = calculate_area(radius);
    printf("radius = %f, perimeter = %f, area = %f \r\n", radius, perimeter, area);
}

通过指针传参的方式,就可以设计一个函数,返回两个值,如下。

#include <stdio.h>

void calculate_perimeter_area(float radius,float* perimeter_p,float* area_p)
{
    *perimeter_p = 2 * 3.14 * radius;//2*PI*R
    *area_p      = 3.14 * radius * radius;//PI*R^2
}

int main()
{
    float radius = 2.0;
    float perimeter, area;
    calculate_perimeter_area(radius, &perimeter, &area);
    printf("radius = %f, perimeter = %f, area = %f \r\n", radius, perimeter, area);
}

另外,即使是只返回一个参数,也往往不用return的方式返回。这是因为,返回值用来作为函数是否运行成功的标志。

2.1.3 使用场景2-减少函数参数

很多企业规范要求C语言的函数参数尽量少一些,例如一个函数的参数少于5个。这样的要求通常是为了代码的可读性,以及节省栈空间的使用。

如果一个函数的输入确实很多,可以考虑把他们打包成结构体,再将结构体变量的指针作为函数参数。例如,上面的计算圆的半径、周长的函数可以改造一下。

#include <stdio.h>

typedef struct Circle_Tag
{
    float Radius;
    float Perimeter;
    float Area;
} Circle_Type;

void calculate_perimeter_area(Circle_Type* circle_p)
{
    circle_p->Perimeter = 2 * 3.14 * circle_p->Radius;//2*PI*R
    circle_p->Area      = 3.14 * circle_p->Radius * circle_p->Radius;//PI*R^2
}

int main()
{
    Circle_Type circle;
    circle.Radius = 2.0F;
    calculate_perimeter_area(&circle);
    printf("radius = %f, perimeter = %f, area = %f \r\n", circle.Radius, circle.Perimeter, circle.Area);
}

上面的代码中,把圆的半径、周长、面积三个属性定义在同一个结构体类型中。将结构体变量的地址作为参数传给函数,这样只需要传递一个地址变量,函数内部就能获得输入、输出的所有信息。同时,由于只传递一个地址,这个函数只用了4个字节的栈空间。而传递三个float类型的变量,就需要12个字节。

2.2 void*指针

2.2.1 基本概念

void* 指针是一种没有具体类型的指针。int类型的指针和void类型的指针都存放了一个地址,但是由于int类型指针指到它所指向的内存空间是int类型,就可以通过解引用得到该地址处4个字节的空间中的变量值。而void* 指针不知道这段地址占了几个字节,就取不出来变量数值。看一下下面这段代码:

#include <stdio.h>

int main()
{
	int a = 1;
	void* p = (void*)&a;
	int b = *p;
	printf("b = %d \r\n", b);
}

代码中,定义void*定义指针p,并且将变量a的地址赋值给p。然后又试图通过解引用的方式,把p指向的内存空间的变量数值赋值给b。运行代码就会报错如下:
在这里插入图片描述
因为指针变量p中只包含了地址,不知道具体类型,就无法从地址中获得数值。正确的做法是将void*指针先进行强制类型转换,再解引用。

#include <stdio.h>

int main()
{
	int a = 1;
	void* p = (void*)&a;
	int b = *(int*)p;
	printf("b = %d \r\n", b);
}

2.2.2 使用场景

void*类型指针用的时候还需要强制类型转换,看起来十分麻烦,但也有他所使用的场景。在函数设计的时候,需要明确参数的数据类型,这就导致了很多时候函数难以统一化和平台化。例如,还是需要设计用来计算周长和面积的函数,但是输入的几何图形是圆形和矩形两种。由于两种几何图形分别对应两个结构体类型,就必须设计两个函数分别用于计算周长和面积。如下代码。

#include <stdio.h>

typedef struct Circle_Tag
{
    float Radius;
    float Perimeter;
    float Area;
} Circle_Type;

typedef struct Rectangle_Tag
{
    float Length;
    float Width;
    float Perimeter;
    float Area;
} Rectangle_Type;

void calculate_circle(Circle_Type* circle_p)
{
    circle_p->Perimeter = 2 * 3.14 * circle_p->Radius;//2*PI*R
    circle_p->Area = 3.14 * circle_p->Radius * circle_p->Radius;//PI*R^2
}

void calculate_rectangle(Rectangle_Type* rectangle_p)
{
    rectangle_p->Perimeter = 2 * (rectangle_p->Length + rectangle_p->Width);//2*(L+W)
    rectangle_p->Area = rectangle_p->Length * rectangle_p->Width;//PI*R^2
}

int main()
{
    Circle_Type circle;
    circle.Radius = 2.0F;
    calculate_circle(&circle);
    printf("Circle: Radius = %f, Perimeter = %f, Area = %f \r\n", circle.Radius, circle.Perimeter, circle.Area);

    Rectangle_Type rectangle;
    rectangle.Length = 2.0F;
    rectangle.Width = 3.0F;
    calculate_rectangle(&rectangle);
    printf("Rectangle: Length = %f, Width = %f, perimeter = %f, area = %f \r\n", rectangle.Length, rectangle.Width, rectangle.Perimeter, rectangle.Area);
}

代码中,由于结构体类型不同,就必须设计两个函数来输入不同的参数,分别处理两种几何图形的计算。

利用void*类型指针,就可以设计为一个函数。代码如下:

#include <stdio.h>

typedef struct Circle_Tag
{
    float Radius;
    float Perimeter;
    float Area;
} Circle_Type;

typedef struct Rectangle_Tag
{
    float Length;
    float Width;
    float Perimeter;
    float Area;
} Rectangle_Type;

typedef enum Geometry_Tag
{
    Geometry_Circle,
    Geometry_Rectangle
} Geometry_Type;

void calculate_geometry(void* geometry, Geometry_Type geometry_type)
{
    if (geometry_type == Geometry_Circle)
    {
        ((Circle_Type*)geometry)->Perimeter = 2 * 3.14 * ((Circle_Type*)geometry)->Radius;//2*PI*R;
        ((Circle_Type*)geometry)->Area = 3.14 * ((Circle_Type*)geometry)->Radius * ((Circle_Type*)geometry)->Radius;//PI*R^2
    }
    else if (geometry_type == Geometry_Rectangle)
    {
        ((Rectangle_Type*)geometry)->Perimeter = 2 * (((Rectangle_Type*)geometry)->Length + ((Rectangle_Type*)geometry)->Width);//2*(L+W)
        ((Rectangle_Type*)geometry)->Area = ((Rectangle_Type*)geometry)->Length * ((Rectangle_Type*)geometry)->Width;//PI*R^2
    }
    else
    {
        //do nothing
    }
}

int main()
{
    Circle_Type circle;
    circle.Radius = 2.0F;
    calculate_geometry((void*)(&circle), Geometry_Circle);
    printf("Circle: Radius = %f, Perimeter = %f, Area = %f \r\n", circle.Radius, circle.Perimeter, circle.Area);

    Rectangle_Type rectangle;
    rectangle.Length = 2.0F;
    rectangle.Width = 3.0F;
    calculate_geometry((void*)(&rectangle), Geometry_Rectangle);
    printf("Rectangle: Length = %f, Width = %f, perimeter = %f, area = %f \r\n", rectangle.Length, rectangle.Width, rectangle.Perimeter, rectangle.Area);
}

通过calculate_geometry函数的第二个参数,可以判断出第一个参数的void*指针是由圆类型还是矩形类型转换来的,从而在函数内部将void*指针强制类型转换回原来的类型,再用进行对应的计算。

calculate_geometry函数使用了void*类型参数,可以称之为弱类型参数。明确定义了类型的参数,例如float和int等,称之为强类型参数。对于上面这种函数接口需要通用的场景,就可以使用弱类型参数。

2.3 空指针

在定义一个指针时,如果不立即赋值,指针就会指向一个随机的地址。比较好的做法是应该在定义指针的时候就赋值为空,在C语言中就是NULL,如下。

#include <stdio.h>

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

这样保证了指针的地址是0,但是指针还是不能解引用,因为程序员应该给指针真正赋值为有意义的地址,才能从内存的地址中取出变量。如果对空指针解引用,还是会报错,例如下面的代码。

#include <stdio.h>

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

    printf("a = %d \r\n", a);
}

在visual studio中运行后,会报出错误。

在这里插入图片描述
但是,同样的代码放到别的编译器中,就不一定报错。譬如通过Hightec或Tasking编译器,为嵌入式硬件编译代码,可以成功地生成elf文件。但是软件刷写到嵌入式控制器中,硬件运行就会卡死,需要花费大量的精力在硬件上debug才能定位到这个问题。

在代码编写的时候就应该注意校验指针是否为空指针。例如,把上面的计算圆形的周长面积的函数可以再做一个空指针校验。

#include <stdio.h>

typedef struct Circle_Tag
{
    float Radius;
    float Perimeter;
    float Area;
} Circle_Type;

int calculate_perimeter_area(Circle_Type* circle_p)
{
    int retVal = 0;
    if (NULL == circle_p)//校验是否为空指针
    {
        retVal = 0;
    }
    else
    {
        circle_p->Perimeter = 2 * 3.14 * circle_p->Radius;//2*PI*R
        circle_p->Area = 3.14 * circle_p->Radius * circle_p->Radius;//PI*R^2
        retVal = 1;
    }    
    return retVal;//返回校验的结果
}

int main()
{
    Circle_Type circle;
    circle.Radius = 2.0F;
    if (calculate_perimeter_area(&circle))
    {
        printf("radius = %f, perimeter = %f, area = %f \r\n", circle.Radius, circle.Perimeter, circle.Area);
    }
    else
    {
        printf("Function failed!");
    }    
}

这样设计函数,就可以通过返回值提示函数的调用者,函数是否调用失败,从而排查出参数传递了空指针。

2.4 const指针

2.4.1 基本概念

const关键字修饰变量时,表示这个变量的数值不能改变并且在被定义的时候需要立即赋值,后面就不可改变了。const关键字修饰指针的时候,根据const所处的位置,指针的特点有所不同。

1)如下代码是常量指针,在定义指针的时候先写const,再写int*。

const int* p = &a;

由于const是在int*之前的,所以这里的const的含义是指针所指向的内存的值是常量,这个值不能被修改。例如下面代码,试图修改常量指针所指向的值,就会报错。

#include <stdio.h>

int main()
{
    int a = 10;
    const int* p = &a;
    *p = 20;
}

运行代码后,会报错如下:
在这里插入图片描述
这里编译器就提示常量无法赋值。但是,指针所指向的地址是可以修改的,例如如下代码。

#include <stdio.h>

int main()
{
    int a = 10;
    const int* p = &a;
    int b = 20;
    p = &b;
}

2)如下代码是指针常量,在定义指针的时候先写int*,再写const。

int* const p = &a;

由于int*是在const之前的,所以这里的const的含义是指针所指向的地址是常量,不能改变它所指向的地址。例如下面代码,试图修改指针常量所指向的地址,就会报错。

#include <stdio.h>

int main()
{
    int a = 10;
    int* const p = &a;
    int b = 20;
    p = &b;
}

运行代码后,同样是会报错。
在这里插入图片描述
这表示指针所指向的地址无法被赋值为其他地址。但是,指针所指向的内存地址的值是可以修改的,例如如下代码。

#include <stdio.h>

int main()
{
    int a = 10;
    int* const p = &a;
    int b = 20;
    *p = b;
}

3)将以上两个const融合,就成为了指向常量的常指针,就意味着地址和值都不可以被改变。

const int* const p = &a;

具体就不再举例。

2.4.2 使用场景

const修饰指针的主要使用场景还是在函数的参数为指针的时候。当函数参数通过指针传参,就意味着函数内部对指针指向的值有读和写的权限。实际上某个指针是输入,不希望被函数修改,某些指针是输出,希望被函数修改,这就需要通过const关键字来约束函数修改指针的权限。

例如下面的代码,将圆的半径通过指针输入给函数,再通过函数计算出周长和面积通过指针输出。

#include <stdio.h>

void calculate_perimeter_area(float* radius_p, float* perimeter_p, float* area_p)
{
    *radius_p= 3;//输入被篡改
    *perimeter_p = 2 * 3.14 * (*radius_p);//2*PI*R
    *area_p = 3.14 * (*radius_p) * (*radius_p);//PI*R^2
}

int main()
{
    float radius = 2.0;
    float perimeter, area;
    calculate_perimeter_area(&radius, &perimeter, &area);
    printf("radius = %f, perimeter = %f, area = %f \r\n", radius, perimeter, area);
}

这里的圆半径也是通过指针参数传递给函数。但是由于函数内部获得了指针,就可以操作radius的地址。如果程序员在函数内部将radius篡改成别的数字,编译器也是不会报错的,因为这是符合语法规范的。运行结果如下:
在这里插入图片描述
由于输入的radius从2篡改到3,输出的值也是基于错误的输入得出的。

为防止这种情况,只要将函数的指针参数加上const修饰,就可以避免,修改如下。

#include <stdio.h>

void calculate_perimeter_area(const float* const radius_p, 
                                    float* const perimeter_p, 
                                    float* const area_p)
{
    *perimeter_p = 2 * 3.14 * (*radius_p);//2*PI*R
    *area_p = 3.14 * (*radius_p) * (*radius_p);//PI*R^2
}

int main()
{
    float radius = 2.0;
    float perimeter, area;
    calculate_perimeter_area(&radius, &perimeter, &area);
    printf("radius = %f, perimeter = %f, area = %f \r\n", radius, perimeter, area);
}

函数参数中,为radius_p指针参数加上了两个const,表示该指针参数所指向的地址,以及地址里的值都不能被修改掉,函数内部只能读取固定地址里的固定数值。输出的perimeter_p和area_p加了一个const,定义为指针常量,表示地址不能被修改但是值可以被修改。这样,函数输出的计算值只能写入固定的地址中。

如果函数内部还有类似的篡改行为,编译器就会报之前的错误。这样,就可以在编译软件的阶段发现软件问题,不必等到硬件中出现异常值再去排查。

3 总结

本文中列举了C语言中指针使用的一些常见场景,但不仅仅是上文提到的这些。在今后遇到更复杂的需求时再回来更新。

>>返回个人博客总目录

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

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

相关文章

三分钟学习一个python小知识5-----------我的对python中pandas的理解, 我列举了关于pandas常用的4个例子来深入理解pandas

这里写目录标题 1、Pandas是什么2、Pandas的常用功能&#xff1a;2.1. 读取和写入数据2.2. 数据清洗和转换2.3. 数据分析和计算2.4. 数据可视化总结 1、Pandas是什么 Pandas是Python中一个非常流行的数据处理和分析库&#xff0c;可以使用它对数据进行读取、清洗、转换、分析和…

【动态规划算法练习】day4

文章目录 一、213. 打家劫舍 II1.题目简介2.解题思路3.代码4.运行结果 二、740. 删除并获得点数1.题目简介2.解题思路3.代码4.运行结果 三、剑指 Offer II 091. 粉刷房子1.题目简介2.解题思路3.代码4.运行结果 总结 一、213. 打家劫舍 II 1.题目简介 213. 打家劫舍 II 你是一…

数字IC工程师的护城河是什么?

每个人都希望能够增加⾃⼰的核心竞争⼒&#xff0c;然后延展职业⽣涯。 可能IC研发工程师基本上都会有个40岁危机&#xff1f; 时代背景是最⼤的变数&#xff0c;它改变了⼈才供需和技术⾛向&#xff0c;⽐如做处理器 core曾经是屠⻰术&#xff0c;⽽现在是⻩⾦时代 处理器 cor…

堆和优先队列

文章目录 堆维护堆的性质建堆堆排序算法 优先队列详解cpp标准库 priority_queue 参考文献 堆 虽然“堆”这个词源自堆排序&#xff0c;但是目前它已经被引申为“垃圾存储机制”&#xff0c;例如在Java和Lisp语言中所定义的。强调一下&#xff0c;我们使用的堆不是垃圾收集存储…

设计模式之访问者模式笔记

设计模式之访问者模式笔记 说明Iterator(访问者)目录访问者模式示例类图抽象访问者角色类抽象元素角色类宠物猫类宠物狗类自己类其他人类家类测试类 说明 记录下学习设计模式-访问者模式的写法。JDK使用版本为1.8版本。 Iterator(访问者) 意图:表示一个作用于某对象结构中的…

平面设计师实习报告范文十篇

平面设计师实习报告范文1 经过了四年的系统性理论学习&#xff0c;使我对平面设计有了基本掌握&#xff0c;对于平面设计这个专业也有了一个系统的了解和掌握。我们即将离开大学校园&#xff0c;作为平面设计专业的毕业生&#xff0c;心中想得更多的是如何去做好一个平面设计师…

ETCD集群运维

ETCD集群运维 ETCD扩容节点ETCD参数调优时间参数快照磁盘网络 监控维护压缩历史版本消除碎片化存储配额快照备份 灾难恢复快照恢复 etcd gateway作用什么时候不应该使用网关启动方式 gRPC代理可扩展的watch API租约请求缓存启动gRPC代理 ETCD扩容节点 节点1&#xff1a;10.1.1…

管理类联考——逻辑——知识篇——论证推理——一、削弱——haimian

削弱 考点分析 削弱 年度 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023题量223262132 主要问法 以下哪项如果为真&#xff0c;则能最严重地削弱&#xff08;反驳&#xff09;上述结论?以下哪项如果为真&#xff0c;则最能削弱&#xff08;反驳&#xff09…

文心一言 VS 讯飞星火 VS chatgpt (46)-- 算法导论6.1 4题

四、假设一个最大堆的所有元素都不相同&#xff0c;那么该堆的最小元素应该位于哪里? 文心一言&#xff1a; 最大堆的最小元素位于堆的根结点。由于最大堆的性质&#xff0c;根结点是堆中所有元素的最大值&#xff0c;因此它也是堆中所有元素的最小值。 讯飞星火&#xff1a…

工具及方法 - 如何进行任务管理

如何做好你该做的事&#xff0c;这是你的一生之敌。 没有一步到位的工具和一劳永逸的方法&#xff0c;只有不断变化的需求和自己用起来顺手的感觉。 对于GTD工具&#xff0c;在手机上安装过番茄工作法的工具&#xff0c;在电脑的Excel上也做过任务列表&#xff0c;在免费的项目…

解决 kali换源之后签名无效

报错问题&#xff1a; apt-get update 报错 更新扩展知识&#xff1a;kali更新源 终端输入 vi /etc/apt/sources.list #中科大 deb http://mirrors.ustc.edu.cn/kali kali-rolling main non-free contrib deb-src http://mirrors.ustc.edu.cn/kali kali-rolling main …

计算机视觉中,有哪些基于控制点对的图像变换?

这里探讨的所有图像变换&#xff08;二维&#xff09;都是基于控制点对的&#xff0c;它们的主要区别在于&#xff1a; 1、它们是如何通过两张图像的控制点对产生变换场&#xff08;变换矩阵或者变换公式中的参数&#xff09;的 2、控制点之间的对应关系严格程度 这里说的变换…

远心镜头案例锦集

远心镜头&#xff08;Telecentric lens&#xff09; 定义&#xff1a;远心镜头是一类将其入瞳或出瞳放置于无穷远的光学系统。是为纠正传统镜头视差而设计&#xff0c;它可以在一定的物距范围内&#xff0c;使得到的图像放大倍率不会变化&#xff0c;即这种镜头拍出来的图像没…

人工智能(4):模型评估

模型评估是模型开发过程不可或缺的一部分。它有助于发现表达数据的最佳模型和所选模型将来工作的性能如何。 按照数据集的目标值不同&#xff0c;可以把模型评估分为分类模型评估和回归模型评估。 1 分类模型评估 准确率 预测正确的数占样本总数的比例。 其他评价指标…

嵌入式C开发 VS 嵌入式CPP开发!

目录 ​答主&#xff1a;听心跳的声音 答主&#xff1a;pansz 答主&#xff1a;candy 这是知乎嵌入式领域的一个热门话题&#xff0c;原文链接&#xff1a; https://www.zhihu.com/question/374663834 几个高赞回答&#xff1a; ​答主&#xff1a;听心跳的声音 单片机的主…

多项式回归的原理及实现、多重回归的原理

1.多项式回归的原理及实现 笔记来源于《白话机器学习的数学》 1.1 多项式回归的原理 预测一个变量 x x x与一个变量 y y y的关系 例如&#xff1a;广告费 x x x与点击量 y y y 用曲线拟合数据 求导过程类比本人之前的博客进行推导&#xff0c;相关笔记&#xff1a;最小二乘法的…

Nginx基于授权的访问控制步骤

目录 一、安装httpd-tools 二、生成用户密码认证文件 三、修改主配置文件相对应的目录&#xff0c;添加认证配置置顶 四、 重启服务 五、 访问网址 一、安装httpd-tools 二、生成用户密码认证文件 三、修改主配置文件相对应的目录&#xff0c;添加认证配置置顶 Vim /usr/loc…

【雕爷学编程】Arduino动手做(124)---24位WS2812环形灯板

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

tensorflow2模型保存和恢复

有两种方法可以保存模型&#xff1a; 使用检查点&#xff0c;一种简单的在硬盘上保存变量的方法使用SavedModel&#xff0c;模型结构及检查点 检查点不包含任何关于模型自身的描述&#xff1a;它们只是一种简单的存储参数并能让开发者正确恢复它的方法。 SavedModel格式在保…

Mysql复习多表查询

Mysql复习多表查询 1.多表关系2.多表查询概述3.内连接4. 外连接5. 自连接5.1 案例 6. 子查询6.1 标量子查询6.1.1 标量子查询案例 6.1 列子查询6.2 行子查询6.2.1 demo1 6.3 表子查询6.3.1 demo16.3.2 demo2 7.联合查询8.1 案例 附录 1.多表关系 >多表查询 项目开发中&…