【C语言】使用指针时出现的各种错误总结以及处理方法

news2025/1/14 0:52:58

🏖️作者:@malloc不出对象
⛺专栏:《初识C语言》
👦个人简介:一名双非本科院校大二在读的科班编程菜鸟,努力编程只为赶上各位大佬的步伐🙈🙈
在这里插入图片描述

目录

    • 前言
    • 一、什么是野指针
    • 二、野指针出现的成因
      • 2.1 指针未进行初始化
      • 2.2 指针越界访问
      • 2.3 指针指向已经被释放的空间
      • 2.4 return返回局部/临时变量的地址
    • 三、常见的动态内存分配错误
      • 3.1 对NULL指针的解引用操作
      • 3.2 对动态开辟空间的越界访问
      • 3.3 对非动态开辟内存使用free释放
      • 3.4 使用free释放一块动态开辟内存的一部分
      • 3.5 对同一块动态内存多次释放
      • 3.6 动态开辟内存忘记释放(内存泄漏)
      • 3.7 几个经典的动态内存分配题
    • 四、如何规避野指针以及动态内存分配时出现的各种错误


前言

今天我们要来谈论的是使用指针会出现的各种错误以及如何规避错误的方法,其实呢大家也可以看到关于指针部分我也是花了很多功夫给大家进行讲解总结,由此可见指针的重要性,它也是我们即将学习数据结构的基石,,可以说指针没学好那么你数据结构一定是学不明白的,所以这里强烈建议大家把基础打扎实了再进行下一阶段的学习。

一、什么是野指针

概念:野指针就是指针指向的位置是不可预知的(随机的、不正确的、没有明确限制的)。

对于这个概念我们来看看一个形象的比喻:野指针是十分危险的,我们之前把它比作野狗,野狗是没有主人的,所以它随时可能对路人造成危险到处咬人,那该怎么去解决这个问题呢?最好的办法是限制它的行动嘛,就用一根铁链栓住这条野狗,这样就大概率不会出现咬人的风险了。

二、野指针出现的成因

2.1 指针未进行初始化

我们来简单的看一个例子:

#include<stdio.h>
int main()
{
    int* p;
    *p = 20;
    printf("%d\n",*p);
    
    return 0;
}

大家觉得会打印出什么结果呢?我们一起来看看:

在这里插入图片描述

为什么编译器也会给我们直接就报错了呢?下面我们来分析一下:

首先我们知道局部变量未进行初始化,默认是随机值;那么指针未进行初始化是不是也默认为随机地址,但是你也不知道到底是哪块地址,属不属于这个程序的地址,然后随便通过地址找到了这块内容并且将20给它放进去,这样可能就造成了非法访问内存,所以编译器是不允许这种行为的。

下面再给大家分享一道选择题:
在这里插入图片描述

这道题最主要考的就是指针初始化/赋值的问题,你注意到这个问题了,那么这道题可以直接就选出来了,答案是选D,下面我们简单的分析一下:

选项D,因为指针变量p没有进行初始化,所以指针变量p指向的地址是不确定的,那么scanf(在窗口输入内容)就不能随便对这块空间相当于进行赋值操作,而有的读者又问了,你这是不是由于没加&地址符而导致的呢?我们继续来想一下,&p就是指针变量的地址对吧,那么scanf往这块地址里面输入的内容是不是就是地址(指针)呢?因为指针变量p的内容就是地址(指针)嘛,但是输入的内容(地址)你怎么确定它就是该程序内的地址呢?是你有权限使用的地址呢?所以对于指针变量来说一定要进行初始化/赋值操作,scanf才能往指针变量所指向的地址空间内输入内容,,所以其实大家也可以发现scanf本质上是对指针变量所指向的地址空间进行赋值操作,而不是直接对指针变量进行赋值操作!!

下面还是举个简单的例子让大家明白清楚这个过程,大家先想一想这个过程实际上是在干嘛呢?打印出来的结果取决于什么?又或者是一个错误的程序呢?

#include<stdio.h>

int main()
{
	int a = 10;
	int* p = &a;
	scanf("%d", p);

	printf("a = %d\n", a);
	printf("*p = %d\n", *p);

	return 0;
}

下面我画个图向大家简单描述一下这个过程:

在这里插入图片描述

我们来看看答案:

在这里插入图片描述

2.2 指针越界访问

#include<stdio.h>
int main()
{
    int arr[10]={0};
    int* p = arr;
    for(int i = 0; i < 11; i++)
    {
        *(p+i) = i;    
        printf("%d ",arr[i]);
    }
    return 0;
}

这段代码很明显的指针访问越界了,它访问的最多元素个数只有10个现在让它访问11个,这时候指针就越界非法访问内存空间了,此时当指针指向的范围超出数组arr的范围时,p就是野指针。

在这里插入图片描述

2.3 指针指向已经被释放的空间

其实这个知识点已经在之前的一篇博客中提及过这个问题了,大家可以跳转到这篇博客在讲解free函数部分时讲的非常清楚,这里就不做赘述了。

2.4 return返回局部/临时变量的地址

#include <stdio.h>

char* show()
{
    char str[] = "hello world";
    return str;
} 

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

接下来我采用调试的方式让大家看的更加清楚:
起始指针变量s存的地址:

在这里插入图片描述

调用完show函数时指针变量s存的地址:

在这里插入图片描述

调用完printf函数之后,指针变量s存的地址未知:

在这里插入图片描述

所以最终我们打印出来的结果为乱码,并且也出现了一个警告,下面我们来解释这个现象:

在这里插入图片描述

函数栈帧的销毁并不是将返回函数的数据真正删除了,而是将该函数代码块内的数据设置为无效。在这篇博客讲解空间复杂度时我详细的讲了计算机中的删除问题,大家如果有不明白的话可以看下这个知识点。

下面看向这段代码,main函数调用show函数,show函数返回到main函数时show函数栈帧被销毁,但此时show函数代码块的数据还是原来的内容"hello world",只不过这个数据是无效的,在下次加载数据时可以被直接覆盖。换而言之,此时main函数内部指针变量s还是保存着"hello world"首字符的地址,但在进行下一步打印结果的时候,我们又调用了printf函数,printf函数开辟一块新的栈帧覆盖掉了原来被销毁的show函数的栈帧,调用完之后printf的栈帧又被销毁,此时“hello world”才是真正的不存在了,所以此时我们的指针变量存的地址就不可预知了,所以结果打印出随机值。

同时,我们可以得出一个结论:return语句不可返回指向“栈内存”的指针(地址),因为该内存在函数体结束时被自动销毁,随时被覆盖掉。

下面继续给大家拓展一下,我们来看看这段代码它会不会打印出随机值呢?

#include <stdio.h>

char* show()
{
    char* p = "hello world";
    return p;
} 

int main()
{
    char* s = show();
    printf("%s\n", s);
    

    return 0;
}

首先我们来分析一下这段代码跟上段代码有什么区别,上段代码的这样子来初始化的char str[] = “hello world”;此时的str是一个字符数组它是在栈上开辟的一块空间,它的内容是可以修改的;而这段代码char* p = “hello world”,字符指针变量p它存放的是首字符的地址,它的内容是不可进行更改的,那么它们的区别又在哪里呢?

我们从内存分布的角度来讲,常量字符串放在常量区,而上段代码的str是一个字符数组它是在栈上开辟一段连续的空间的。换而言之,上段代码的str数组在show函数返回时,执行到printf函数时,show函数里面的内容被覆盖掉了,而该段代码是放在常量区的,它的内容是不会被修改覆盖的,return p;拷贝一份p的地址,main函数中指针变量s接收该地址的信息,然后根据接收到的字符首地址访问这块空间,所以打印出来就是“hello world”。其实关于字符数组与字符串的区别我是早就讲过了的,如果是一直把我博客全部认真看完的伙计,这道题肯定能迅速的反应出来它们之间的区别。

下面我们来看看结果:

在这里插入图片描述

三、常见的动态内存分配错误

3.1 对NULL指针的解引用操作

我们来看一个例子:

#include<stdio.h>
#include<stdlib.h>
int main()
{
    int* p = (int*)malloc(INT_MAX);
    *p = 0;

    return 0;
}

我们来分析一下这个问题,malloc申请空间可能会返回空指针,对空指针解引用会造成内存的非法访问,可能会导致程序崩溃等一系列的问题,所以我们再开辟完空间之后要先进行判空操作。

正确的写法应该为这样:

#include<stdio.h>
#include<stdlib.h>
int main()
{
    int* p = (int*)malloc(INT_MAX); 
    if(p == NULL)
    {
         return 0;
    }
    *p = 0;

    return 0;
}

3.2 对动态开辟空间的越界访问

#include <stdio.h>
#include <stdlib.h>

int main()
{
     int *p = (int *)malloc(10*sizeof(int)); 
     if(NULL == p) 
     { 
         exit(-1);
     } 
     
     int i = 0; 
     for(i = 0; i <= 10; i++) 
     { 
         *(p+i) = i;
     } 
     free(p);
     p = NULL;
     
     return 0;
}

这段代码跟之前讲到的指针越界问题本质上都是因为指针越界访问了,只不过这里是动态申请的堆空间访问越界了,报错信息与上面稍有不同,这其实没啥影响我们明白原理就好了。

在这里插入图片描述

我们的解决方案是:编译器是不会提示这种错误的,所以是需要我们自行去检查是否越界问题的,其实越界了编译器也会出现一系列问题提示的,我们根据提示找到问题所在就可以了,但还是要避免少出现这种错误,毕竟bug少一事就是一事嘛🙈🙈

3.3 对非动态开辟内存使用free释放

#include<stdio.h>
int main()
{
    int a = 10; 
    int *p = &a; 
    free(p);//对非动态开辟内存进行释放
    p = NULL;
    return 0;
}

我们知道指针p是在栈区上的,而free只能释放堆区动态开辟的空间;动态内存也就是分布在堆区,非动态内存也就是分布在栈区,栈是系统管理的,当然不能free;你申请内存时,实际上系统是把一块标记为未使用的内存地址返回给你,然后把那个地址标记为已使用;你释放的时候,实际上就是把那块内存标记为未使用,你要对一个已经标记为使用的内存再标记成未使用,当然就不可以了!

在这里插入图片描述

3.4 使用free释放一块动态开辟内存的一部分

#include<stdio.h>
#include<stdlib.h>

int main()
{
    int *p = (int*)malloc(100); 
    if(p == NULL)
    {
        return 0;    
    }
    p++; 
    free(p);
    p = NULL;
    return 0;
}

在动态内存分配的那篇文章我提及过这个问题:free只能是从指向动态内存的起始位置开始释放的,它要释放的是一个完整的空间,而不能分块进行多次释放。这里的指针变量p改变了,那么此时的起始地址就变化了,所以free时会发生错误。

在这里插入图片描述

关于这个知识点我们之后也常见的会先用一个指针变量保存着一块空间的起始地址,这样对这块空间进行指针偏移操作时我们的指向的位置发生变化,而最后我们返回的要是起始地址,所以一开始的指针变量此时就起了作用,它指向的是这块空间的起始地址,所以最后返回它就可以了。为什么一定要返回起始地址,,关于指针部分最重要的其实就是起始地址,只有找到了起始地址你才能进行相应的指针偏移操作访问到这块空间访问内的其他内容,起始地址就是老大!!

3.5 对同一块动态内存多次释放

#include<stdio.h>
#include<stdlib.h>

int main()
{


    int* p = (int*)malloc(100);
    if(p == NULL)
    {
        return 0;    
    }
    free(p);
    free(p);

    p = NULL;

    return 0;
}

这段代码的指针变量p指向的空间重复进行释放了,指针指向的开辟空间只能释放一次,若多次free可能引发不可预测的问题,你想一下如果我们指向的空间释放之后,这块空间立马又被系统分配给其他程序,你再次进行释放编译器会让你进行嘛?

在这里插入图片描述

那么我们应该如何避免这种问题呢?我们应该在每次free掉原来的地址之后,将原地址置为NULL,这样即使再进行free,我们也知道free空指针是不执行任何操作的。

在这里插入图片描述

此时这样就不会出现问题了。

3.6 动态开辟内存忘记释放(内存泄漏)

关于这点其实很容易理解,我们可以这样来想:只要动态申请了堆空间没有进行free释放都可以叫内存泄露,关于内存泄露我在动态内存分配那篇文章也已经讲过。对于一般的程序来说就比如你动态开辟了一个节点等没有及时释放,对于程序来说其实是无伤大雅的,因为进程结束了内存泄露就不存在了,但我们平时就要养成好的习惯有借有还;而对于永远不会结束的进程来说,内存泄露就是一个非常大的问题了,所以我们的解决方法就是随时记得释放。

3.7 几个经典的动态内存分配题

题目一:

大家想一想这段代码中出现了哪些错误:

#include<stdio.h>
#include<stdlib.h>

void GetMemory(char* p)
{
    p = (char*)malloc(100);
}

void Test(void)
{
    char* str = NULL;
    GetMemory(str);
    strcpy(str, "hello world");
    printf(str);//相当于printf("%s",str);举个例子char* str = "abcdefg" == > str = "abcdefg"; printf("abcdefg") == printf(str)
}

int main()
{
    Test();

    return 0;
}

首先映入眼帘的是GetMemory函数,它在内部开辟了一块空间但是并没有释放,这就会造成内存泄露,这是第一个错误的地方。
第二个错误,str其实还是NULL,在strcpy时它不能被访问,所以会造成程序崩溃。我们来分析一下,在main函数中我们先调用Test函数,进入Test函数内部时,首先将NULL赋值给str,之后调用GetMemory函数,str是一个指针变量,它进行的是值传递传递的是变量本身而非地址,所以GetMemory函数的形参的改变并不会影响str,进入GetMemory函数内部,首先开辟一块100个字节大小的空间,指针变量p存放的是开辟的这块空间的首地址;而在GetMemory函数结束之后指针变量p就被销毁了(注意了此时的指针变量p虽然是一个局部变量,但是它保存的是堆空间的起始地址),无法通过这个地址找到它的内容了;所以此时我们的str还是为NULL,我们知道NULL是不能被访问的,所以在strcpy时程序会崩溃。
通过这段代码的分析其实我们很快能想出一个解决方案那就是在指针变量p销毁之前返回它指向的地址,因为我们知道堆空间是随着程序的退出而退出的,换而言之,这块空间是不会随着GetMemory函数的调用结束而销毁的,str接收到这块空间的起始地址就能使用这块空间了。

如果我们想得到我们想要的结果就应该这么写:

//第一种改正方式:址传递,改变实参会影响实参,所以p与str的操作可以认为是同步的,都是对同一块空间进行操作
#include<stdio.h>
#include<stdlib.h>
void GetMemory(char** p)
{
    *p = (char*)malloc(100);
}

void Test(void)
{
    char* str = NULL;
    GetMemory(&str);
    strcpy(str, "hello world"); 
    printf(str);
    
    free(str);
    str = NULL;                           
}

int main()
{
    Test();

    return 0;
}

//第二种改正方式:返回p的地址(此时return p;是堆空间的起始地址,而非局部/临时变量的地址),指针变量p在GetMemory函数里面接收到的是一块动态开辟的起始地址,而我们知道堆空间是随着程序的退出而退出的,
//换而言之,这块空间是不会随着GetMemory函数的调用结束而销毁的,所以str接收到这块空间的起始地址就能使用这块空间了。
#include<stdio.h>
#include<stdlib.h>

char* GetMemory(char* p)
{
    p = (char*)malloc(100); 
        return p;                  
}

void Test(void)
{
    char* str = NULL;
    GetMemory(str);
    strcpy(str, "hello world");        
    printf(str);
    
    free(str);
    str = NULL;
}

int main()
{
    Test();

    return 0;
}

题目二

#include<stdio.h>
char* GetMemory()
{
    char p[] = "hello world";   
    return p;
}

void Test(void)
{
    char* str = NULL;
    str = GetMemory();
    printf(str);
}

int main()
{
    Test();

    return 0;
}

大家有没有感觉这段代码很熟悉,其实在上面是已经讲解过几乎差不多的题了,那么这里就不做说明了,这里打印的结果会是一个随机值。

题目三

#include<stdio.h>
#include<stdlib.h>
void GetMemory(char** p, int num)
{
    *p = (char*)malloc(num);
}

void Test(void)
{
    char* str = NULL;
    GetMemory(&str, 100);
    strcpy(str, "hello");
    printf(str);
}

int main()
{
    Test();

    return 0;
}

这段代码进行的是址传递,打印的结果也是如我们所想的那样,只有一个问题就是动态申请的空间没有手动释放掉,会导致内存泄露。所以正确的写法就是将str使用完之后free释放掉,然后将其置为NULL。

题目四

#include<stdio.h>
#include<stdlib.h>
void Test(void)
{
    char* str = (char*)malloc(100);
    strcpy(str, "hello");
    free(str);
    if (str != NULL)
    {
        strcpy(str, "world");
        printf(str);
    }
}

int main()
{
    Test();

    return 0;
}

这段代码呢其实主要的是想让我们了解free的作用,free释放的本质并不是将这块空间的起始地址置为NULL,而是将这块空间做了一个标识,标识这块空间已经被释放了不能使用这块空间的内容了,所以这段代码free释放掉str之后,实际上呢str的地址还是没变,在判空之后会进入if条件执行strcpy操作,但我们知道这段已经被释放的空间是不能进行随便访问的,会造成非法访问内存,这就相当于没系保险在开车,不出事故算是运气,出了事故就是一命呜呼。问题造成的结果是“轻则扣奖金,重则被鄙视”!更有甚者数据完全改写,而找不到问题所在!所以打印出world是一个偶然事件,因为free之后str就成了一个野指针。

在VS下确实打印出world了:

在这里插入图片描述

在devc++上我们看到运行时程序已经崩溃了:

在这里插入图片描述

正确的写法就是释放完str之后将其置为NULL,它也就不会进入if条件中执行strcpy相关操作了。

关于这几道经典的题目其实是来自《高质量的C/C++编程》这本经典的书籍,大家有兴趣的话可以好好看看这本书,也是可以规范平时我们的代码风格习惯。


四、如何规避野指针以及动态内存分配时出现的各种错误

其实通过上述讲解动态内存分配出现的各种错误,我们也是发现部分其实就是野指针的问题所导致的,下面我对这部分进行总结一下。

如何规避野指针?

1. 一定要对指针进行初始化/赋值操作。
2. 小心指针越界访问。这个问题需自己去注意,如果没注意到其实程序也会崩溃停止也能说明问题。
3. 指针指向的空间释放之后及时置空,为了防止释放之后这段空间又被使用(此时我们是不确定这段空间是否有权限能访问的)。
4. 避免返回局部/临时变量的地址,因为它是随着函数栈帧的销毁而销毁的。(ps:是避免返回局部/临时变量的地址,千万不要一概认为return虽然是返回地址了主函数的指针变量也接收到这个地址了,但是由于这块空间是随着函数调用的结束而就销毁了;我们上面也提到一个局部变量p保存的是堆空间的地址,虽然函数调用结束了,但是堆空间依旧存在,所以主函数接收到这块堆空间的起始地址就能访问这块空间了)。
5. 指针使用之前检查有效性,这是由动态内存分配所产生的返回值问题,malloc、calloc以及realloc开辟空间失败都会返回NULL,它是不能被访问的。

如何避免动态内存分配造成的错误?

1. 动态申请的空间一定要记得free释放掉,并且将其置空也是一个好的习惯。
2. free函数的参数必须是要与malloc、calloc以及realloc动态申请的起始地址一致,它必须释放一块完整的空间。
3. free只能释放动态申请的空间(堆空间)。
4. 同一块动态内存空间不要释放多次。

最后我想说的一点:其实关于指针出现的各种问题本质上都是因为访问权限的问题而导致的,我们在使用指针时要时刻注意此时指针的状态,根据指针的状态来判断是否此时它能否被访问操作等…

本篇文章是关于指针部分最后总结的一篇文章了,如果你将我总结的几篇指针文章的知识点全部吸收了,相信对于指针部分理解起来是应该是没什么问题了!那么最后还是如果文章有任何疑问或者错处,欢迎大家评论区相互交流啊orz~🙈🙈

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

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

相关文章

Redis主从哨兵集群

主从 主从同步原理 1.全量同步 1.从节点刚连接到主节点时&#xff0c;主动请求数据同步 2.主节点判断是否是第一次同步&#xff0c;如果是&#xff0c;就返回主节点的数据版本信息 3.随后&#xff0c;主节点执行bgsave&#xff0c;生成RDB文件&#xff0c;并发送给从节点 4.这…

Java时间类

时间在Java中是两个类 Date 和 Calendar Date类 Date是在Java.util包中的也就是工具包。 测试 输出的是当前的系统时间 修改idea中的注释的颜色&#xff1a; 是date不是data package org.data; import java.util.Date; public class Test { public static void main(…

【Oracle】20230106PLSQL中文不显示,全部为?解决流程

问题&#xff1a;查询的表中有中文数据&#xff0c;全部显示为&#xff1f; 解决途径1&#xff1a;修改注册表以及环境变量&#xff08;改系统变量&#xff01;不要只改用户变量&#xff09; Oracle使用——PLSQL的中文乱码显示全是问号_海蓝树的博客-CSDN博客_plsql无法显示…

上线网站详细介绍(服务器购买-域名申请-SSL证书申请)

文章目录上线之前的准备工作&#xff0c;通俗的来讲&#xff1a;服务器是什么-云服务器购买域名解释-域名备案-域名如何申请①什么是域名&#xff0c;为什么要域名&#xff1f;②为什么非要域名备案和不备案的区别&#xff1f;③自己的主机怎么备案-自己的电脑可以作为服务器吗…

系分架构 - 软件架构设计

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 文章目录系分&架构 - 软件架构设计考点摘要概念架构的 4 1 视图软件架构风格经典五大架构风格调用/返回风格独立构件风格虚拟机风格仓库风格层次架构风格二层C/S架构三层C/S架构B/S架构混合架构风格闭环控制架…

Linux 软件管理 RPM 管理工具

概念引入 &#xff1a; # 首先提出一个问题&#xff0c;我们在 Linux 操作系统中是如何 安装软件的 &#xff1f;&#xff1f; >>> 在 Linux 系统中&#xff0c;安装软件是有三种方式 >>> 第一种 &#xff1a; RPM 管理工具 第二种 &#xff1a; …

如何在新环境接手项目?(上)【洞见2】

01、世界真实情况根据IDC机构在2022年统计显示&#xff0c;截止2021年我国中小企业数量已达到4881万家&#xff0c;同比增长8.5%。中小企业的行业分布不均匀&#xff0c;超过70%的中小企业分布于专业服务业&#xff08;含互联网和科技服务&#xff09;、物流批发、零售和制造业…

剑指offer----C语言版----第十一天

目录 1. 数值的整数次方 1.1 运行超时的思路 1.2 思路一: 快速幂 (递归实现) 1.3 思路二: 快速幂 (迭代实现) 1. 数值的整数次方 原题链接: 剑指 Offer 16. 数值的整数次方 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/shu-zhi-de-zheng-shu-ci-f…

web3学习:什么是以太坊

以太网是“世界的计算机”&#xff0c;这是以太坊平台的一种常见描述。这是什么意思呢&#xff1f;让我们首先从关注计算机科学的描述开始&#xff0c;然后对以太坊的功能和特性进行更实际的解读&#xff0c;并将其与比特币和其他分布式账本技术&#xff08;简单起见&#xff0…

深度学习笔记:神经网络(2)

对于神经网络上一篇文章&#xff0c;可见&#xff1a;https://blog.csdn.net/Raine_Yang/article/details/128473486?spm1001.2014.3001.5501 神经网络各层信号传递的实现&#xff1a; 对于全连接网络&#xff0c;连接权重的个数为&#xff08;前一层神经元总数 * 后一次神经…

el-table(type=“selection“)多选框两种回显

目录 一、前端数据回显&#xff08;页面间数据展示&#xff09; 1、图篇帮助理解 2、描述&#xff1a; 3、代码 4、两个API&#xff0c;一个v-model 二、数据库数据回显 1、描述&#xff1a; 2、核心代码: 3、比较完整代码&#xff1a;(这是element ui官方文档上的) 4…

XMLHttpRequest 对象(AJAX通信)

1.XMLHttpRequest 对象是什么&#xff1f; 浏览器与服务器之间&#xff0c;采用 HTTP 协议通信。用户在浏览器地址栏键入一个网址&#xff0c;或者通过网页表单向服务器提交内容&#xff0c;这时浏览器就会向服务器发出 HTTP 请求。 1999年&#xff0c;微软公司发布 IE 浏览器…

基于STM32F411使用SPI+DMA驱动LCD

先看效果 F411CE 硬件SPI&#xff0b;DMA驱动LCD基于HAL库 其实HAL库我用不太习惯&#xff0c;一直也是用的标准库 但HAL库确实是好上手一些&#xff0c;就迅速创建一个新的template 这次就当尝试一下吧&#xff0c;因为用的比较少&#xff0c;我会记录的详细 如图点击&…

火山引擎DataLeap数据调度实例的 DAG 优化方案

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;并进入官方交流群 实例 DAG 介绍 DataLeap 是火山引擎自研的一站式大数据中台解决方案&#xff0c;集数据集成、开发、运维、治理、资产管理能力于一身的大数据研发治理套件。在平台中&…

从0到1完成一个Vue后台管理项目(四、引入axios、配置路由)

往期 从0到1完成一个Vue后台管理项目&#xff08;一、创建项目&#xff09; 从0到1完成一个Vue后台管理项目&#xff08;二、使用element-ui&#xff09; 从0到1完成一个Vue后台管理项目&#xff08;三、使用SCSS/LESS&#xff0c;安装图标库&#xff09; axios axios就是…

手握智算中心“绿洲”,毫末跑在中美自动驾驶长跑第一线

作者 | 白日梦想家 编辑 | 王博2022年过去&#xff0c;数据驱动成为自动驾驶演进共识。沿着数据驱动这条路线&#xff0c;自动驾驶加速迈入智算时代。 智算中心应运而生。 实际上&#xff0c;将智算引入自动驾驶的开先河者是特斯拉&#xff0c;其率先发布了专用于自动驾驶训练的…

登录与授权

目录 1.获取用户信息 1.button.open-type.getUserInfo 2.open-data组件 3.wx.getUserProfile(Object object) 头像昵称填写功能 2.登录 登录的流程图 sessin_key 3.授权 wx.openSetting wx.getSetting wx.authorize 手机号授权 登录与授权是两个不关联的事情&…

[Leetcode] 相同的树、对称二叉树

相同的树和对称二叉树都可以使用递归实现。相同的树题目链接&#xff1a;https://leetcode.cn/problems/same-tree/solution/xiang-tong-de-shu-by-leetcode-solution/1.1 递归、深度优先搜索使用递归&#xff0c;将问题转换为 --> 判断当前节点是否相同 判断左右子树分别是…

以前不知道字节面试难在哪,现在体验到了,被虐的很惨...

人们都说&#xff0c;互联网寒冬来了&#xff0c;这个时候还在大面积招人的公司&#xff0c;必然是牛逼的公司。而这个时候勇敢跳槽的人&#xff0c;必然是牛逼的人。于是我开始了字节跳动的社招面试。 为了这天&#xff0c;我前一天排老长的队&#xff0c;理了个利落的发型&a…

蚂蚁帮路由器Antbang A3s V2.0刷入OpenWrt/LEDE

参考资料路由器基本常识_冰色阳光的博客-CSDN博客_路由器bootloader是什么https://www.right.com.cn/forum/thread-3191610-1-1.html已知问题刷入OpenWrt/LEDE后&#xff0c;似乎路由器的Reset键不起作用。路由器在启动时&#xff0c;正常会先运行引导程序Breed&#xff0c;然后…