从0开始创建单链表
前言
这次我来为大家讲解链表,首先我们来理解一下什么是单链表,我们可以将单链表想象成火车
每一节车厢装着货物和连接下一个车厢的链子,单链表也是如此,它是将一个又一个的数据封装到节点上,节点里不仅包含着数据,还又指向下一个节点的指针。
因此单链表的结构体表示如下:
typedef int SLTDataType;typedef struct SListNode
{int data;struct SListNode* next;
}SListNode;
所以单链表的特点是:
在物理结构上不一定是线性的
在逻辑结构上是线性的
我们通常会将单链表抽象成如下图所示: 这样会方便我们接下来的思考和代码的实现。
单链表代码实现
打印
我们先来写打印代码,这个代码也方便我们后期的测试。
打印单链表,我们需要遍历单链表的所有数据,这个函数的形参传一级指针就可以了,因为打印并不需要改变头指针。
void SListPrint(SListNode* phead)
{SListNode* ptail = phead;while (ptail){printf("%d->", ptail->data);ptail = ptail->next;}printf("NULL\n");
}
这里定义一个变量ptail,是为了不想改变phead,说不定哪天要在这个函数再次使用phead,所以我定义了一个ptail来进行遍历,以便后面想使用phead的时候,可以快速增加代码
创建新节点
鉴于插入数据都需要创建一个新节点,为了方便后面的头插,尾插等各种插入,于是我们来封装一个函数来创建新节点:
SListNode* CreatNewnode(SLTDataType x)
{SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));if (newnode == NULL){perror("malloc fail");exit(1);}newnode->data = x;newnode->next = NULL;return newnode;
}
尾插
尾插需要在单链表的尾部插入一个新的数据,画图理解一下:
需要我们找到单链表原先的尾节点,将尾节点的next指向newnode,于是我们很快就会写出如下的代码:
void SListPushBack(SListNode** pphead, SLTDataType x)//尾插
{assert(pphead);SListNode* newnode = CreatNewnode(x);//找尾节点SListNode* pcur = *pphead;while (pcur->next){pcur = pcur->next;}pcur->next = newnode;
}
由于上面的代码是基于链表不为空这一种情况实现的,这时候我们还要思考,如果链表为空的时候,上面的代码还能实现吗?
我们来走一下代码,如果链表为空,pcur==NULL,pcur->next一定会报错,对空指针是不能解引用的,所以上面的代码无法处理链表为空的情况,我们就需要单独进行处理!!!
这个尾插代码应该是这样的:
void SListPushBack(SListNode** pphead, SLTDataType x)//尾插
{assert(pphead);SListNode* newnode = CreatNewnode(x);//如果链表为空if (*pphead == NULL){*pphead = newnode;}else{//找尾节点SListNode* pcur = *pphead;while (pcur->next){pcur = pcur->next;}pcur->next = newnode;}
}
所以当链表为空的时候,我们需要改变头指针的指向,所以传二级指针!!!
头插
头插需要我们在单链表的最前面插入一个数据,我们画图来理解一下:
头插很显然要改变头指针,所以形参要影响实参需要传二级指针。
这时候是先newnode指向原先的第一个节点,再改变phead?还是先改变phead再将newnode 指向原先的第一个节点?
在不创建另外一个变量的时候,我们要找到原先第一个节点只能通过phead->next,如果先改变了phead 的话,我们就找不到原先的第一个节点了。
所以需要将新节点的next指向原先的第一个节点,然后我们将头指针改变,指向新节点。
void SListPushFront(SListNode** pphead, SLTDataType x)//头插
{assert(pphead);SListNode* newnode = CreatNewnode(x);newnode->next = *pphead;*pphead = newnode;
}
这个时候,由于上面的代码还是基于链表不为空的条件下进行的,所以我们还要考虑链表为空的情况,走一下代码,*pphead == NULL,newnode->next = NULL,*phead = newnode ,很显然这个代码也能处理好链表为空的情况,于是我们不需要任何的改动。
尾删
尾删指删除单链表最后一个节点。画图理解一下:
这时候我们需要找到尾节点和尾节点的前一个节点,将尾节点释放掉,改变现在的尾节点的next指向,置为NULL。
所以我们需要两个临时变量,一个来遍历链表找到尾节点,一个来保存上一个节点!!!
这时我们来写代码:
void SListPopBack(SListNode** pphead)//尾删
{assert(pphead && *pphead);//不能传NULL,链表也不能为空//找尾节点SListNode* pcur = *pphead;SListNode* prev = *pphead;while (pcur->next){prev = pcur;pcur = pcur->next;}free(pcur);pcur = NULL;prev->next = NULL;}
这里我们需要断言一下,就是链表不能为空,链表为空,删什么?
上面的代码是基于链表至少又两个节点的情况下,如果链表只有一个节点呢?也就是头指针指向的就是你要尾删的节点,走一下代码:*pphead == NULL,pcur = prev =NULL,此时while(pcur->next),对空指针解引用,直接报错,既然如此,我们就写多一点代码来专门处理只有一个节点的情况。
void SListPopBack(SListNode** pphead)//尾删
{assert(pphead && *pphead);//不能传NULL,链表也不能为空//链表只有一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{//找尾节点SListNode* pcur = *pphead;SListNode* prev = *pphead;while (pcur->next){prev = pcur;pcur = pcur->next;}free(pcur);pcur = NULL;prev->next = NULL;}
}
由于可能会出现只有一个节点的情况,删除的话也需要改变头指针,所以传二级指针。
头删
头删指删除第一个节点。画图理解一下:
我们需要改变头指针的指向,所以必须传二级指针!!!
void SListPopFront(SListNode** pphead)//头删
{assert(pphead && *pphead);//不能传NULL,链表也不能为空SListNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}
老规矩,如果链表只有一个节点,走一下代码:SListNode->next = (*pphead) -> next =NULL; 释放掉第一个节点,*pphead = next = NULL,刚好可以处理这种情况,代码不需要修改。
查找
写查找代码也是方便我们后续的指定位置的插入删除作准备。
这个代码和简单,只需要遍历单链表。
SListNode* SListFind(SListNode* phead, SLTDataType x)
{SListNode* pcur = phead;while (pcur){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}
删除指定节点
我们画图理解一下:
我们需要改变两个个节点,pos前一个节点的next指向改成pos后一个节点。
void SListPopPos(SListNode** pphead, SListNode* pos)
{assert(pphead && *pphead);assert(pos);SListNode* pcur = *pphead;while (pcur->next != pos){pcur = pcur->next;}pcur->next = pos->next;free(pos);pos = NULL;}
如果pos节点就是第一个节点(即pphead == pos),我们来走一下代码,在while循环中pcur->next 不会找到pos,因为pcur = *phead = pos,所以循环最后会导致pcur走到NULL,然后发生对空指针的解引用,所以我们需要单独处理这一种情况
void SListPopPos(SListNode** pphead, SListNode* pos)
{assert(pphead && *pphead);assert(pos);if (pos == *pphead){//调用头删SListPopFront(pphead);}else{SListNode* pcur = *pphead;while (pcur->next != pos){pcur = pcur->next;}pcur->next = pos->next;free(pos);pos = NULL;}
}
由于pos 就是头指针,所以pos的删除就是头删,我们可以调用我们之前写好的头删函数,无论你要不要调用头删函数,都要对头指针进行修改,所以要传二级指针(头指针的地址)。而pos不需要传二级指针是因为一般情况下我们不会再次使用pos,所以我们不需要改变pos的指向,也就不用传pos 的地址过去。
删除指定位置之前的数据
画图理解:
我们需要找到pos前两个节点,释放pos 前一个节点,改变pos前前节点的next 的指向,变为指向pos这个节点。
void SListPopPosFront(SListNode** pphead, SListNode* pos)
{assert(pphead && pos); assert(pos != *pphead);SListNode* pcur = *pphead;SListNode* prev = *pphead;while (pcur->next != pos){prev = pcur;pcur = pcur->next;}prev->next = pos;free(pcur);pcur = NULL;}
我们要考虑一下如果pos前面只有一个节点的情况下,我们来走一下代码:当prev->next = pos(pphead = pos),释放pcur(也就是释放了pphead)。这时候头指针没了,你找不到后面的数据了!!!
所以我们单独处理一下这种情况。
void SListPopPosFront(SListNode** pphead, SListNode* pos)
{assert(pphead && pos); assert(pos != *pphead);if ((*pphead)->next == pos){//头删SListPopFront(pphead);}else{SListNode* pcur = *pphead;SListNode* prev = *pphead;while (pcur->next != pos){prev = pcur;pcur = pcur->next;}prev->next = pos;free(pcur);pcur = NULL;}
}
头删,需要改变头指针的指向,所以要用二级指针来接收头指针的地址。
删除指定位置之后的数据
画图理解:
由于我们可以通过pos 就可以找到pos后面的节点,所以我们直接传pos就可以了。
我们需要找到pos后后一个节点,然后改变pos 的next 指向指向后后一个节点,为了方便书写代码,我们可以定义一个临时变量del来保存要删除的节点。
这里要注意,指定位置之后必须要有一个节点,否则删什么?所以这里断言一下。
void SListPopPosAfter(SListNode* pos)
{assert(pos && pos->next);SListNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}
我们来考虑一下,如果要删除的节点刚好是尾节点,我们来走一下代码:pos->next = del->next = NULL;free(del),没有问题,那就不用单独处理。
指定位置前插入
画图理解一下:
我们需要改变前一个节点,为了找到pos 的前一个结点,我们需要传入头指针对链表进行遍历。
void SListPushPosFront(SListNode** pphead, SListNode* pos, SLTDataType x)
{assert(pphead && *pphead);assert(pos);SListNode* newnode = CreatNewnode(x);SListNode* pcur = *pphead;while (pcur->next != pos){pcur = pcur->next;}newnode->next = pos;pcur->next = newnode;
}
如果pos就是第一个节点,上面的代码中while循环就无法找到pos前一个节点,所以我们要单独处理一下这种情况,这时可以调用一下我们写过的头插函数。
头插,需要改变头指针,所以传二级指针。
void SListPushPosFront(SListNode** pphead, SListNode* pos, SLTDataType x)
{assert(pphead && *pphead);assert(pos);if (*pphead == pos){//头插SListPushFront(pphead, x);}else{SListNode* newnode = CreatNewnode(x);SListNode* pcur = *pphead;while (pcur->next != pos){pcur = pcur->next;}newnode->next = pos;pcur->next = newnode;}
}
指定位置之后插入
画图理解一下:
这个代码我们可以通过pos找到pos后一个节点,所以不需要传入头指针。
void SListPushPosAfter(SListNode* pos, SLTDataType x)
{assert(pos);SListNode* newnode = CreatNewnode(x);SListNode* next = pos->next;pos->next = newnode;newnode->next = next;
}
我们来考虑一下pos就是尾节点的情况,能不能走得通,走一下代码,next = pos->next = NULL,pos->next = newnode,newnode->next = next = NULL,可以,没有任何问题,就不需要改代码了。
销毁链表
销毁链表需要一个一个节点依次删除,所以要遍历链表。
void SListDestroy(SListNode** pphead)
{SListNode* pcur = *pphead;while (pcur){SListNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}
测试
每写完一个函数,我们就需要进行调试测试来看代码有没有问题,这时一个好的习惯,希望各位也能这样做。
一下是我自己的测试代码:我放在了test.c文件里(就是测试文件)
#include "SList.h"void test1()
{SListNode* plist = NULL;//测试尾插//SListPushBack(&plist, 1);//SListPrint(plist);//SListPushBack(&plist, 2);//SListPrint(plist);//SListPushBack(&plist, 3);//SListPrint(plist);//测试头插SListPushFront(&plist, 10);SListPrint(plist);SListPushFront(&plist, 20);SListPrint(plist);SListPushFront(&plist, 30);SListPrint(plist);//测试尾删/* SListPopBack(&plist);SListPrint(plist);SListPopBack(&plist);SListPrint(plist);SListPopBack(&plist);SListPrint(plist);*///测试头删//SListPopFront(&plist);//SListPrint(plist);//SListPopFront(&plist);//SListPrint(plist);//SListPopFront(&plist);//SListPrint(plist);//测试查找/* SListNode* find = NULL;find = SListFind(plist, 30);*//* if (find == NULL){printf("找不到\n");}else{printf("找到了!%d\n", find->data);}*///测试删除pos节点/*SListPopPos(&plist, find);SListPrint(plist);*///删除指定位置之前的数据//SListPopPosFront(&plist, find);//SListPrint(plist);//删除指定位置之后的数据/* SListPopPosAfter(find);SListPrint(plist);*///指定位置前插入/* SListPushPosFront(&plist, find, 100);SListPrint(plist);*///指定位置之后插入/* SListPushPosAfter(find, 100);SListPrint(plist);*/SListDestroy(&plist);SListPrint(plist);
}int main()
{test1();return 0;
}
分装函数
SList.h
#pragma once#include <stdio.h>
#include <stdlib.h>
#include <assert.h>typedef int SLTDataType;typedef struct SListNode
{int data;struct SListNode* next;
}SListNode;//打印
void SListPrint(SListNode* phead);//插入
void SListPushBack(SListNode** pphead, SLTDataType x);//尾插
void SListPushFront(SListNode** pphead, SLTDataType x);//头插//删除
void SListPopBack(SListNode** pphead);//尾删
void SListPopFront(SListNode** pphead);//头删//查找
SListNode* SListFind(SListNode* phead, SLTDataType x);//删除pos节点
void SListPopPos(SListNode** pphead, SListNode* pos);//删除指定位置之前的数据
void SListPopPosFront(SListNode** pphead, SListNode* pos);//删除指定位置之后的数据
void SListPopPosAfter(SListNode* pos);//指定位置前插入
void SListPushPosFront(SListNode** pphead, SListNode* pos, SLTDataType x);//指定位置之后插入
void SListPushPosAfter(SListNode* pos, SLTDataType x);//销毁链表
void SListDestroy(SListNode** pphead);
SList.c
#include "SList.h"//创建新节点
SListNode* CreatNewnode(SLTDataType x)
{SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));if (newnode == NULL){perror("malloc fail");exit(1);}newnode->data = x;newnode->next = NULL;return newnode;
}//打印
void SListPrint(SListNode* phead)
{SListNode* ptail = phead;while (ptail){printf("%d->", ptail->data);ptail = ptail->next;}printf("NULL\n");
}//插入
void SListPushBack(SListNode** pphead, SLTDataType x)//尾插
{assert(pphead);SListNode* newnode = CreatNewnode(x);//如果 *pphead==NULLif (*pphead == NULL){*pphead = newnode;}else{//找尾节点SListNode* pcur = *pphead;while (pcur->next){pcur = pcur->next;}pcur->next = newnode;}
}void SListPushFront(SListNode** pphead, SLTDataType x)//头插
{assert(pphead);SListNode* newnode = CreatNewnode(x);newnode->next = *pphead;*pphead = newnode;
}void SListPopBack(SListNode** pphead)//尾删
{assert(pphead && *pphead);//不能传NULL,链表也不能为空//链表只有一个节点if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{//找尾节点SListNode* pcur = *pphead;SListNode* prev = *pphead;while (pcur->next){prev = pcur;pcur = pcur->next;}free(pcur);pcur = NULL;prev->next = NULL;}
}void SListPopFront(SListNode** pphead)//头删
{assert(pphead && *pphead);//不能传NULL,链表也不能为空SListNode* next = (*pphead)->next;free(*pphead);*pphead = next;
}//查找
SListNode* SListFind(SListNode* phead, SLTDataType x)
{SListNode* pcur = phead;while (pcur){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}//删除pos节点
void SListPopPos(SListNode** pphead, SListNode* pos)
{assert(pphead && *pphead);assert(pos);if (pos == *pphead){//调用头删SListPopFront(pphead);}else{SListNode* pcur = *pphead;while (pcur->next != pos){pcur = pcur->next;}pcur->next = pos->next;free(pos);pos = NULL;}
}//删除指定位置之前的数据
void SListPopPosFront(SListNode** pphead, SListNode* pos)
{assert(pphead && pos); assert(pos != *pphead);if ((*pphead)->next == pos){//头删SListPopFront(pphead);}else{SListNode* pcur = *pphead;SListNode* prev = *pphead;while (pcur->next != pos){prev = pcur;pcur = pcur->next;}prev->next = pos;free(pcur);pcur = NULL;}
}//删除指定位置之后的数据
void SListPopPosAfter(SListNode* pos)
{assert(pos && pos->next);SListNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}//指定位置前插入
void SListPushPosFront(SListNode** pphead, SListNode* pos, SLTDataType x)
{assert(pphead && *pphead);assert(pos);if (*pphead == pos){//头插SListPushFront(pphead, x);}else{SListNode* newnode = CreatNewnode(x);SListNode* pcur = *pphead;while (pcur->next != pos){pcur = pcur->next;}newnode->next = pos;pcur->next = newnode;}
}//指定位置之后插入
void SListPushPosAfter(SListNode* pos, SLTDataType x)
{assert(pos);SListNode* newnode = CreatNewnode(x);SListNode* next = pos->next;pos->next = newnode;newnode->next = next;
}//销毁链表
void SListDestroy(SListNode** pphead)
{SListNode* pcur = *pphead;while (pcur){SListNode* next = pcur->next;free(pcur);pcur = next;}*pphead = NULL;
}
单链表完结撒花!!!
相关文章:

从0开始创建单链表
前言 这次我来为大家讲解链表,首先我们来理解一下什么是单链表,我们可以将单链表想象成火车 每一节车厢装着货物和连接下一个车厢的链子,单链表也是如此,它是将一个又一个的数据封装到节点上,节点里不仅包含着数据&…...

STC89C52学习笔记(十)
STC89C52学习笔记(十) 综述:本文介绍了DS18B20和单总线协议,以及讲述了如何使用DS18B20测量温度。 一、单总线协议 1.只有一根通讯线:DQ (常见的运用单总线的两种设备:DS18B20和DHT11&#…...

初识二叉树和二叉树的基本操作
目录 一、树 1.什么是树 2. 与树相关的概念 二、二叉树 1.什么是二叉树 2.二叉树特点 3.满二叉树与完全二叉树 4.二叉树性质 相关题目: 5.二叉树的存储 6.二叉树的遍历和基本操作 二叉树的遍历 二叉树的基本操作 一、树 1.什么是树 子树是不相交的;…...

如何开辟动态二维数组(C语言)
1. 开辟动态二维数组 C语言标准库中并没有可以直接开辟动态二维数组的函数,但我们可以通过动态一维数组来模拟动态二维数组。 二维数组其实可以看作是一个存着"DataType []"类型数据的一维数组,也就是存放着一维数组地址的一维数组。 所以&…...

【MATLAB第104期】基于MATLAB的xgboost的敏感性分析/特征值排序计算(针对多输入单输出回归预测模型)
【MATLAB第104期】基于MATLAB的xgboost的敏感性分析/特征值排序计算(针对多输入单输出回归预测模型) 因matlab的xgboost训练模型不含敏感性分析算法,本文通过使用single算法,即单特征因素对输出影响进行分析,得出不同…...

C语言程序与设计——工程项目开发
之前我们已经了解了C语言的基础知识部分,掌握这些之后,基本就可以开发一些小程序了。在开发时,就会出现合作的情况,C语言是如何协作开发的呢,将在这一篇文章进行演示。 工程项目开发 在开发过程中,你接到…...

【Java核心技术】第6章 接口
1 接口 接口不是类,而是对希望符合这个接口的类的一组需求 1.1 Comparable 接口 要对对象进行比较,就要实现(implement)比较(comparable)接口 注意: implements Comparable<Manager> Comparable接口是泛型接口 class Manager exten…...

【Java探索之旅】从输入输出到猜数字游戏
🎥 屿小夏 : 个人主页 🔥个人专栏 : Java编程秘籍 🌄 莫道桑榆晚,为霞尚满天! 文章目录 📑前言一、输入输出1.1 输出到控制台1.2 从键盘输入 二、猜数字游戏2.1 所需知识:…...

【动态规划】【01背包】Leetcode 1049. 最后一块石头的重量 II
【动态规划】【01背包】Leetcode 1049. 最后一块石头的重量 II 解法 ---------------🎈🎈题目链接🎈🎈------------------- 解法 😒: 我的代码实现> 动规五部曲 ✒️确定dp数组以及下标的含义 dp[j]表示容量为…...
2023 年上海市大学生程序设计竞赛 - 四月赛
A. 宝石划分 A. 宝石划分 - 2023 年上海市大学生程序设计竞赛 - 四月赛 - ECNU Online Judge 找距离N最近的M的因数q,输出M/q 如果是暴力所的话,会超时 #include <bits/stdc.h> using namespace std; int main(){ios::sync_with_stdio(false)…...

别让这6个UI设计雷区毁了你的APP!
一款成功的APP不仅仅取决于其功能性,更取决于用户体验,这其中,UI设计又至关重要。优秀的UI设计能够为用户带来直观、愉悦的交互体验,甚至让用户“一见钟情”,从而大大提高产品吸引力。 然而,有很多设计师在…...

继承【C/C++复习版】
目录 一、什么是继承?怎么定义继承? 二、继承关系和访问限定符? 三、基类和派生类对象可以赋值转换吗? 四、什么是隐藏?隐藏vs重载? 五、派生类的默认成员函数? 1)派生类构造函…...

题目 2694: 蓝桥杯2022年第十三届决赛真题-最大数字【暴力解法】
最大数字 原题链接 🥰提交结果 思路 对于每一位,我我们都要尽力到达 9 所以我们去遍历每一位, 如果是 9 直接跳过这一位 如果可以上调到 9 我们将这一位上调到 9 ,并且在a 中减去对应的次数 同样的,如果可以下调到 9,我…...
【C语言】- C语言字符串函数详解
C语言字符串函数详解 1、void *memset(void *dest, int c, size_t count); 将dest前面count个字符置为字符c. 返回dest的值. 2、void *memmove(void *dest, const void *src, size_t count); 从src复制count字节的字符到dest. 如果src和dest出现重叠, 函数会自动处理. 返回…...

如何实现小程序滑动删除组件+全选批量删除组件
如何实现小程序滑动删除组件全选批量删除组件 一、简介 如何实现小程序滑动删除组件全选批量删除组件 采用 uni-app 实现,可以适用微信小程序、其他各种小程序以及 APP、Web等多个平台 具体实现步骤如下: 下载开发者工具 HbuilderX进入 【Dcloud 插…...

基于SSM+Jsp+Mysql的农产品供销服务系统
开发语言:Java框架:ssm技术:JSPJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包…...

网络编程学习探索系列之——广播原理剖析
hello !大家好呀! 欢迎大家来到我的网络编程系列之广播原理剖析,在这篇文章中, 你将会学习到如何在网络编程中利用广播来与局域网内加入某个特定广播组的主机! 希望这篇文章能对你有所帮助,大家要是觉得我写…...

小程序开发SSL证书下载和安装
在开发小程序时,确保数据的安全传输至关重要,而实现这一目标的关键在于正确获取与安装SSL证书。以下详细介绍了从获取到安装SSL证书的完整流程,以助您为小程序构建可靠的加密通信环境。 一、小程序SSL证书类型选择: 域名验证型D…...

医疗图像分割 | 基于Pyramid-Vision-Transformer算法实现医疗息肉分割
项目应用场景 面向医疗图像息肉分割场景,项目采用 Pytorch Pyramid-Vision-Transformer 深度学习算法来实现。 项目效果 项目细节 > 具体参见项目 README.md (1) 模型架构 (2) 项目依赖,包括 python 3.8、pytorch 1.7.1、torchvision 0.8.2(3) 下载…...

蓝桥杯 每日2题 day5
碎碎念:哦哈呦,到第二天也是哦哈哟,,学前缀和差分学了半天!day6堂堂连载! 0.单词分析 14.单词分析 - 蓝桥云课 (lanqiao.cn) 关于这题就差在input前加一个sorted,记录一下下。接下来就是用字…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...

大话软工笔记—需求分析概述
需求分析,就是要对需求调研收集到的资料信息逐个地进行拆分、研究,从大量的不确定“需求”中确定出哪些需求最终要转换为确定的“功能需求”。 需求分析的作用非常重要,后续设计的依据主要来自于需求分析的成果,包括: 项目的目的…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...

CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...