异步编程(Promise详解)

目录

异步编程

回调函数

回调地狱

Promise

基本概念

Promise的特点

1.Promise是一种构造函数

2.Promise接收函数创建实例

3.Promise对象有三种状态

4.Promise状态转变不可逆

5.Promise 实例创建即执行

6.Promise可注册处理函数

7.Promise支持链式调用

Promise的静态方法

1.resolve()方法

2.reject()方法

 3.all()方法

4.race()方法

5.allSettled() 方法

Promise的实例方法

1.then() 方法

2.catch() 方法

 3.finally() 方法

async/await 

基本概念

基本用法

async

await

使用示例

async/await 的优势


异步编程

JavaScript 在设计之初就是单线程的,这意味着它在同一时间只能执行一个任务,然而,浏览器和 Node.js 环境都提供了多种机制来允许 JavaScript 代码在不阻塞主线程的情况下执行异步操作。

此处仅以浏览器环境下举例( 参考浏览器渲染基本原理 )

浏览器有三大进程 —— 网络进程,浏览器进程和渲染进程。每个窗口开启一个渲染进程。每个渲染进程仅开启一个渲染主线程,主线程负责执行代码(HTML,CSS,JS),渲染页面。

所以在浏览器中,JS是运行在承担着诸多工作的渲染主线程上的。如果使用同步的方式(等待任务结束再执行下一个任务),就有可能会造成阻塞,浪费主线程的宝贵时间;还可能导致页面无法及时更新,给用户造成“卡死”现象。

所以采用异步执行来避免:当发生耗时任务时,渲染主线程会将任务交给其它渲染子线程取处理,自身则立即结束任务的执行,转而执行后续代码。当其他线程完成处理后,会将事先传递的回调函数包装成任务,加到消息队列末尾排队,等待渲染主线程通过事件循环机制调度执行。

在这种异步模式下,浏览器永不阻塞,从而最限度的保证了单线程的流畅运行。

 举例:

setTimeout(function () {console.log('异步任务')
}, 0)
console.log('同步任务')
// 输出顺序  同步任务 ,异步任务

 使用异步方式执行任务,在不阻塞主线程的同时还能极大的缩短程序执行所需的时间

该图摘自菜鸟教程中的异步编程小节

回调函数

在JavaScript中,回调函数是一段可执行的代码段(function),它作为一个参数传递给其他的代码,其作用是在需要的时候方便调用这段代码(简单来说就是函数作为参数传递到另外一个函数中,这个作为参数的函数就是回调函数)。

回调函数是 JavaScript 异步编程中最早也是最基本的模式。当一个异步操作完成时,它会调用事先定义好的回调函数来处理异步操作完成后的结果或响应。

回调函数在异步编程中的作用:将异步操作的处理逻辑封装在回调函数中,然后在异步操作完成时执行这些逻辑。这有助于将异步代码与同步代码分离,使得程序的结构更加清晰。

const success = (data) => {console.log('请求成功,开始处理' + data)//....其他操作
}
const fail = (err) => {console.log('请求失败,开始处理错误' + err)//....其他操作
}
//回调函数callback1,请求失败时的操作
//回调函数callback2,请求成功时的操作
function foo(signal, callback1, callback2) {//模拟异步请求setTimeout(() => {if (signal === 'ok') {callback1('data')} else callback2('err')}, 1000)
}
foo('ok', success, fail)

在上面的例子中,等待1s后(模拟耗时任务),根据任务结果(此处我用foo()的第一个参数‘ok’代替),传递任务结果并调用相应的回调函数。

回调地狱

最简单的网络请求大概是这样

请求1(function callback(请求1的结果) {处理请求1的结果
})

很简单

但是随着需求的变化,当我们需要根据第一次请求的结果进行第二次请求时

请求1(function callback(请求结果1) {//根据请求1的结果,进行请求2请求2(function callback(请求结果2) {处理请求结果2})
})

也不复杂

直至,需求不断变化,最终出现了类似下面的情况

请求1(function callback(请求结果1) {//根据请求1的结果,进行请求2请求2(function callback(请求结果2) {//根据请求2的结果,进行请求3请求3(function callback(请求结果3) {//根据请求3的结果,进行请求4请求4(function callback(请求结果4) {//根据请求4的结果,进行请求5请求5(function callback(请求结果5) {// ...})})})})
})

这下懵圈了,在异步请求和回调函数的不断嵌套下,臭名昭著的 回调地狱 诞生了。

试想一下,在回调函数内部可能会存在大量的处理逻辑,并且每一次的请求结果会成为下一请求的依赖,请求又会存在有失败和成功两种情况.....如此一来,代码将显得十分臃肿复杂,后期为维护将变得十分困难。

回调地狱带来的负面作用

  • 代码臃肿
  • 可读性差
  • 耦合度过高,可维护性差
  • 代码复用性差
  • 容易滋生 bug
  • 只能在回调里处理异常

Promise

基本概念

为了优雅的解决回调地狱的问题,在ES6中,一种新的异步编程的一种解决方案Promise诞生了。

在JS中,Promise是一个代表了异步操作最终完成或失败,及其结果值的对象。它允许开发人员为异步操作的成功和失败情况注册处理程序,极大地简化了异步编程的复杂性。

Promise的特点

1.Promise是一种构造函数

通过new关键字创建实例对象,构造函数自身有all,race,reject,resolve等静态方法,其原型对象上有then,catch,finally等方法。

let promise = new Promise(() => {})
console.log(typeof Promise)//function
console.log(typeof promise)//object//查看Promise函数对象
console.dir(Promise)

2.Promise接收函数创建实例

创建一个新的Promise实例时,必须传递一个函数作为参数给Promise的构造函数,它定义了异步操作的逻辑。这个函数被称为执行器(executor)函数,它本身接受两个函数作为参数:resolvereject。这两个参数也是函数,由JavaScript引擎提供,用于改变Promise的状态。

let promise = new Promise((resolve, reject) => {})
3.Promise对象有三种状态

分别为pending(进行中),fulfilled(已成功)和rejected(已失败),其通过执行器(executor)函数接收的resolve和reject来改变。

        1). new Promise()

let promise = new Promise((resolve, reject) => {})
console.dir(promise)

 新创建的promise实例,其状态为pending(进行中),结果为undefined

        2). resolve(value)

let promise = new Promise((resolve, reject) => {resolve('完成结果值')
})
console.dir(promise)

resolve()接收异步任务执行结果的值作为参数,并将promise实例的状态转变为fulfilled(已成功),结果为 resolve()接收的值

         3).rejected(reason)

let promise = new Promise((resolve, reject) => {reject('完成失败原因')
})
console.dir(promise)

rejected()接收异步任务执行失败的原因作为参数,并将promise实例的状态转变为rejected(已失败),结果为 rejected()接收的失败原因

4.Promise状态转变不可逆

 Promise 状态的转变是不可逆且只能发生一次,且只能由异步操作结果决定。任何其他操作都不能改变这个状态

5.Promise 实例创建即执行
let promise = new Promise((resolve, reject) => {reject('获取数据失败')
})

创造 promise 实例后,它会立即执行的特点,所以该段代码运行后会立即抛出一个错误

但如果将其放入函数的私有作用域内,由于函数的特性,promise仅在函数被调用时,才会执行

6.Promise可注册处理函数

可通过.then()和.catch()方法为promise注册处理函数,在promise状态改变时调用(then和catch的具体用法见下方Promise的实例方法

7.Promise支持链式调用

建议先阅读下方Promise的实例方法Promise链式调用的核心时then方法,由于then方法返回一个新的 Promise实例对象(该对象也就可以调用promise的实例方法),这使得我们可以继续链式调用其他then()方法。如此一来便可以很容易地处理异步操作的序列。

注意:then()的链式调用中,下一个依赖于上一个返回结果

举例:

function fetchData(url) {return new Promise((resolve, reject) => {// 假设这是一个异步请求数据的函数setTimeout(() => {if (url) {resolve(`数据来自 ${url}`)} else {reject('URL 不能为空')}}, 1000)})
}
// 链式调用
fetchData('https://example.com').then((data) => {console.log(data) // 输出:数据来自 https://example.comreturn fetchData('https://another.com') // 返回另一个 Promise}).then((anotherData) => {console.log(anotherData) // 输出:数据来自 https://another.com}).catch((error) => {console.error('捕获到错误:', error) // 如果有 Promise 被拒绝,则执行这里}).finally(() => {console.log('所有操作完成,无论成功还是失败') // 无论成功还是失败都会执行})

Promise的静态方法

Promise 提供了几个静态方法,这些方法不是用于创建新的 Promise 实例的,而是用于处理 Promise 对象的集合或进行某些特定的操作(只能由构造函数调用)。

1.resolve()方法

静态方法 Promise.resolve(value) 将异步执行的结果value作为参数,返回一个以给定值解析后的已完成状态的 Promise 对象。

 Promise.resolve('成功了').then((value) => {console.log(value) // 输出: 成功了})// 如果传入的是 Promise 对象,则直接返回该 Promise 对象let promise = new Promise((resolve) =>resolve('直接返回的 Promise'))Promise.resolve(promise).then((value) => {console.log(value) // 输出: 直接返回的 Promise})
2.reject()方法

静态方法 Promise.reject(reason) 返回一个以特定原因(reason)拒绝状态的 Promise 对象。

Promise.reject('出错了').catch((error) => {console.log(error) // 输出: 出错了
})
 3.all()方法

静态方法 Promise.all( iterator ) 用于处理一个 Promise 对象的数组(或可迭代对象),并返回一个新的 Promise 实例。这个新的 Promise 实例会在所有给定的 Promise 都成功完成时才会成功完成,其结果是一个数组,包含了所有给定 Promise 的结果(按相同的顺序)。

function delay(time, value) {return new Promise((resolve) =>setTimeout(() => resolve(value), time))
}
Promise.race([delay(1000, '1s'),delay(2000, '2s'),delay(3000, '3s')
]).then((results) => {console.log(results) // 输出: ['1s', '2s', '3s']
})
4.race()方法

静态方法 Promise.race( iterator ) 同样接收一个 Promise 对象的数组(或可迭代对象),但它返回的新 Promise 实例会在给定的 Promise 数组中任何一个 Promise 改变状态(无论是成功还是失败)时,立即以那个 Promise 的结果作为自己的结果来改变状态。

function delay(time, value) {return new Promise((resolve) =>setTimeout(() => resolve(value), time))
}
Promise.race([delay(1000, '1s'),delay(2000, '2s'),delay(3000, '3s')
]).then((results) => {console.log(results) //  输出: 1s,因为第一个 delay(1000) 最快完成  
})
5.allSettled() 方法

静态方法 Promise.allSettled(iterable)(ES2020 新增) 返回一个在所有给定的 Promise 都已经 settled(完成,无论结果如何)后完成的 Promise 实例,结果是一个数组,数组中的每个元素都是一个对象,表示对应的 Promise 的结果。

Promise.allSettled([Promise.resolve(3),Promise.reject(-1),Promise.resolve(5),Promise.reject(4)
]).then((results) => {console.log(results)
})
/* 输出:  
[  { status: 'fulfilled', value: 3 },  { status: 'rejected', reason: -1 },  { status: 'fulfilled', value: 5 },  { status: 'rejected', reason: 4 },  
]  
*/

Promise的实例方法

Promise 的实例方法主要包括 .then(), .catch(), 和 .finally()。这些方法允许你以链式调用的方式处理 Promise 的结果或错误。

1.then() 方法

   1).then(onFulfilled , onRejected)

  • onFulfilled :指定resolve时调用的函数,它接收 Promise 的值作为参数(一般由resolve传递)
  • onRejected( 可选 ):指定reject时调用的函数,它接收 Promise 的拒绝原因作为参数(一般由reject传递)

    2).then() 方法主要用于处理 Promise 成功的情况,但也允许提供可选第二个参数                        onRejected 函数来处理其失败的情况

   3).then() 返回一个新的 Promise 实例对象

let promise = new Promise((resolve, reject) => {setTimeout(() => reject('出错了'), 1000)
})
promise.then((value) => console.log(value), // 不会被调用,因为 Promise 被拒绝了(reason) => console.log(reason) // 会被调用,并输出: 出错了
)
2.catch() 方法

   1).catch(onRejected)方法是 .then(null, onRejected) 的语法糖

  • onRejected:指定reject时调用的函数,它接收 Promise 的拒绝原因作为参数(一般reject传递)

    2)catch() 主要用于处理 Promise 拒绝(rejected)的情况

    3).catch() 也返回一个新的 Promise 实例对象

let promise = new Promise((resolve, reject) => {setTimeout(() => reject('出错了'), 1000)
})
promise.catch((error) => {console.log(error) // 输出: 出错了// 可以选择在这里解决这个 Promise,返回一个值或者另一个 Promise// return '错误已处理';
})

      4).不过它还有另外一个作用:在执行 resolve 的回调(也就是上面 then 中的第一个参

       数)时,如果抛出异常了(代码出错了),那么并不会报错卡死 js,而是会进到这个 

  catch 方法中。

let promise = new Promise((resolve, reject) => {setTimeout(() => reject('失败'), 1000)
}).then((value) => {console.log(value)return '继续处理'}).catch((error) => {console.log(error) //输出失败})

 控制台结果

 

let promise = new Promise((resolve, reject) => {setTimeout(() => reject('失败'), 1000)
}).then((value) => {console.log(value)return '继续处理'})

 控制台结果

 catch 既能处理 reject 回调,也能捕捉错误

 3.finally() 方法
  • .finally(onFinally)

  • onFinally :当 Promise 结束时调用的函数,它不接收任何参数,也不关心 Promise 的结果。在 Promise 结束时,无论它是成功还是失败,都会回调执行。这为在 Promise 链的末尾执行清理操作提供了一种方式,无论 Promise 的结果如何
  • finally() 也返回一个新的 Promise 实例对象
let promise = new Promise((resolve, reject) => {setTimeout(() => resolve('成功'), 1000)// 或者 setTimeout(() => reject('失败'), 1000);
})
promise.then((value) => {console.log(value) // 如果 Promise 成功,则输出 '成功'return '继续处理' // 可以返回一个新的值或另一个 Promise}).catch((error) => {console.error(error) // 如果 Promise 失败,则输出错误信息}).finally(() => {console.log('Promise 结束了,无论成功还是失败')})

async/await 

基本概念

Promise 虽然摆脱了回调地狱,但 then 链式调⽤的阅读负担还是存在的,为了优代码的阅读体验,增强了代码的可读性和可维护性,一种新的异步编程解决方案async/await诞生了。

async/await 是 JavaScript中用于处理异步操作的关键字对。它们使得异步代码看起来和写起来更像是同步代码,从而大大增强了代码的可读性和可维护性。这对关键字特别适用于处理如文件读写、网络请求等耗时的异步操作。

基本用法

async
  • async关键字用于声明异步函数。这意味着该函数内部可以进行异步操作,比如发送网络请求、读取文件等。
  • 异步函数内部可以使用 await 关键字来等待异步操作的完成。
  • 异步函数会隐式地返回一个 Promise 对象。如果函数执行成功并且显式地返回了一个值,那么这个值会被封装成一个解析状态(fulfilled)的 Promise 对象。如果没有显式地返回任何值,或者返回了一个非 Promise 类型的值,JS会自动将这个值封成 Promise.resolve(value)(其中 value 是函数的返回值或 undefined),并返回这个 Promise 对象。
  • async function fun() {return 'async函数显示返回的值'
    }
    console.log(fun())

  • 如果异步函数在执行过程中抛出了异常,那么这个异常会被捕获,并导致返回的 Promise 对象进入拒绝(rejected)状态,异常信息作为拒绝的原因。
await
  • await 关键字只能在 async 函数内部使用。它用于等待一个 Promise 完成。
  • 使用 await 时,async 函数的执行会暂停,直到等待的 Promise 被解析(fulfilled)或拒绝(rejected)。
  • 如果 Promise 被解析,await 表达式的结果就是 Promise 解析的值。此时,async 函数的执行会继续进行。
  • 如果 Promise 被拒绝,await 表达式会抛出一个异常。这个异常可以被 async 函数内部的 try...catch 语句捕获,也可以继续向上传播,直到被更高层级的错误处理机制捕获。
使用示例

假设我们有一个异步函数 fetchData,它返回一个 Promise,该 Promise 在一段时间后解析为一些数据:

function fetchData() {return new Promise((resolve, reject) => {setTimeout(() => {resolve('数据加载完成')}, 1000)})
}

使用 async/await 来调用这个函数:

async function loadData() {try {const data = await fetchData()console.log(data) // 输出: 数据加载完成} catch (error) {console.error('数据加载失败:', error)//throw error; // 可选:重新抛出错误,以便上层调用者可以处理  }
}
loadData()

在这个例子中,loadData 是一个 async 函数,它内部使用了 await 来等待 fetchData 函数返回的 Promise 完成。当 Promise 被解析时,await 表达式的结果(即 '数据加载完成')被赋值给 data 变量。如果 fetchData 函数抛出了一个错误(虽然在这个例子中没有),那么错误会被 catch 块捕获。

注意:

  • await 只能在 async 函数内部使用。
  • await 会暂停 async 函数内部后续代码的执行,直到 await 后的 Promise 完成。
  • await 可以用在任何返回 Promise 的表达式上,不仅仅是函数调用。
  • 使用 async/await 可以使异步代码看起来更加直观和易于理解,但也要注意不要滥用,特别是在处理大量并发异步操作时。
async/await 的优势
  1. 代码清晰async/await 使得异步代码看起来和写起来更像是同步代码,提高了代码的可读性和可维护性。

  2. 错误处理:使用 try...catch 可以很容易地捕获和处理 await 表达式抛出的异常,这与同步代码中的错误处理非常相似。

  3. 条件语句和循环:在 async 函数中,可以像处理同步代码一样使用 if 语句、for 循环等控制流语句来等待 Promise 完成。

  4. 中间件和组合:虽然 async/await 本身并不提供新的组合或中间件模式,但它与 Promises 很好地结合,使得可以轻松地组合多个异步操作。


---------------------------------------------------------------------------------------------------------------------------------

若有错误或描述不当的地方,烦请评论或私信指正,万分感谢 😃

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Qt编译错误: error: msvc-version.conf loaded but QMAKE_MSC_VER isn‘t set

方法一:清空构建目录 清空当前目录的多余文件即可,具体操作如下 一个正常的Qt项目刚被创建且没有编译时是这样的 一个main文件,一个pro文件,一个user文件,一个头文件(.h),和一个源文件(.cpp),一…

Java并发—ReetrantLock详解及应用

目录 一、ReetrantLock的特性 1、非阻塞获取锁 2、带超时的锁获取: 3、锁的公平性 4、锁的可中断性 5、Condition条件变量 6、锁的可重入性 可重入锁 不可重入锁 7、性能优化 二、ReentrantLock和Synchronized的区别 1、语法和使用方式 2、锁的获取和释放 3、高级…

手机卡换了上网的ip会改变吗

在数字化时代,互联网已成为我们日常生活不可或缺的一部分。无论是工作、学习还是娱乐,我们都离不开网络的支持。而每当涉及到网络连接,IP地址这一概念便显得尤为重要。IP地址不仅是设备在网络中的唯一标识,还关系到我们的网络体验…

Axure 变量魔法:揭秘局部与全局的动态协同

前言 在 Axure 的世界中,变量是连接设计者意图与用户行为的桥梁。 局部变量,以其独特的灵活性和针对性,允许我们在特定情境下快速响应用户的操作。 而全局变量,则以其广泛的覆盖范围,为跨页面的一致性和连贯性提供了…

最长路(有负权边)spfa

前言:这个题目中有负权重的边,狄克斯特拉算法坑定是用不了的,学一下spfa算法吧,发现就是bellman算法的队列优化 还有一个关键就是我们求最长的权重,我们要将边权重变为-的,最后答案取反就行 #define _CRT_S…

HTTP、HTTPS、SOCKS5三种协议特点

在互联网通信中,HTTP、HTTPS和SOCKS5是三种至关重要的协议,它们各自具有独特的特点和应用场景。本文将详细探讨这三种协议的特点,帮助读者更好地理解它们在网络通信中的作用。 一、HTTP协议特点 HTTP(Hypertext Transfer Protoc…

使用 Prometheus 和 Grafana 监控 FastAPI 服务

在现代应用开发中,监控和可视化服务的运行状态和性能指标对于保证系统稳定性至关重要。本文将介绍如何使用 Prometheus 和 Grafana 对 FastAPI 服务进行监控和可视化,并展示如何通过 prometheus_fastapi_instrumentator 将 FastAPI 应用与 Prometheus 集…

【LVS】部署DR模式集群

一、配置实验环境 每台主机的防火墙和SELinux都要关掉 systemctl stop firewalld setenforce 0 1、client(eth0为nat模式) 配置好网卡IP和网关IP,然后重启网卡 nmcli connection reload nmcli connection up eth0 [rootclient ~]# cat /etc/NetworkManager/syst…

使用自定义注解和AOP解决登录校验问题

1、如果每次都从Redis获取token,会有很多冗余代码 2、使用面向切面编程的思想 在不改变源代码或者很少改变源代码的情况下,增强类的某些方法。 在业务代码之前设置 切入点 创建切面类,也就是比如登录校验的某些公共方法 切面类从切入点切入流…

json配置文件读入redis - 包含命令行解析示例

需要的取用,也是笔记,python argv经常会需要解析。类linux的命令行参数处理,这也是一个示例。 1.源码 #!/usr/bin/env python3 # -*- coding: utf-8 -*- # 获取当前脚本文件所在目录的父目录,并构建相对路径 import os import …

【C语言篇】深入理解指针1

文章目录 内存和地址内存编址 指针变量和地址取地址操作符指针变量和解引用操作符指针变量指针变量类型解引用操作符指针变量的大小 指针变量类型的意义指针的解引用指针-整数void*指针 const修饰指针指针运算指针-整数指针-指针指针的关系运算 野指针野指针成因如何规避野指针…

嵌入式人工智能ESP32(2-GPIO之LED与按键)

1、ESP32引脚 ESP32 38脚与30脚的主要区别在于引脚数量和功能。‌选择38脚的ESP32开发板通常能提供更多的功能和更好的扩展性,‌适合需要连接多种传感器和外设以及进行复杂通信的应用。‌而30脚的ESP32开发板则可能更适合简单应用或成本敏感的项目。 SP32的引脚图…

STM32 | SPI+flash闪存(第十一天)W25Q128举例

点击上方"蓝字"关注我们 01、SPI 1.1 SPI概念理解 SPI 是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI 接口主要应用在 EEPROM,FLASH,实时时钟,AD 转换器,还有数字信号处理器和数…

MySQL笔记-基础篇(二):多表查询

​ 博客主页: 南来_北往 系列专栏:Spring Boot实战 前言 MySQL的多表查询是一项非常实用的数据库操作技术,它能够通过关联不同表中的数据来提供更加丰富和准确的信息。在实际应用中,数据通常不是孤立存在的,而是分布在多个…

Ubuntu(20.04)云主机SSH安全加固

1、新增SSH服务端口 #vim /etc/ssh/sshd_config 找到 #Port 22 去掉注释符,下面添加:Port [新端口] 2、本地防火墙放通 #ufw allow [新端口] #ufw reload //防火墙重新加载 #ufw status verbose //查询是否开放SSH新端口 3、腾讯云防火墙配…

提高PDF电子书的分辨率

解决方法出处 1. 安装ImageMagick brew install imagemagick brew install ghostscript2. 按流程进行 convert -density 600 your_pdf_filename.pdf output-%02d.jpg convert output*.jpg -normalize -threshold 80% final-%02d.jpg convert final*.jpg my_new_highcontras…

长语境窗口扩展:LongRoPE技术解析

人工智能咨询培训老师叶梓 转载标明出处 由于大模型高昂的微调成本、长文本的稀缺性,以及新引入的标记位置可能导致的灾难性值,目前扩展的语境窗口通常被限制在大约128k标记。为了克服这些限制,微软研究院的研究团队提出了一种名为LongRoPE的…

Apache OFBiz 曝出严重漏洞,允许预身份验证 RCE

近日,研究人员发现 Apache OFBiz 中存在一个新的关键漏洞,该漏洞是 Apache OFBiz 中的一个错误授权问题,被追踪为CVE-2024-38856。该漏洞影响 18.12.14 之前的版本,18.12.15 版本解决了该漏洞。 SonicWall 的安全研究员 Hasib Vh…

C语言常用的字符串函数(含模拟实现)

在使用C语言编写代码的时候,我们经常会用到一些库函数来实现一些平常难以实现的功能,今天我就为大家来分享一些我经常会用到的库函数,并且也会将他们的用法和部分的模拟实现函数分享给大家~ (文中部分图片来取自strlen - C Refer…

优购电商小程序的设计

管理员账户功能包括:系统首页,个人中心,用户管理,商品分类管理,商品信息管理,留言板管理,订单管理,系统管理 微信端账号功能包括:系统首页,商品信息&#xf…