Go语言状态机的实现

目录
  • 一、状态机
  • 二、代码
    • 1. database包
    • 2. fsm包
    • 3. order包
    • 4. main包
  • 三、个人总结

一、状态机

1. 定义

有限状态机(Finite-state machine, FSM),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。

2. 组成要素

  • 现态(src state):事务当前所处的状态。
  • 事件(event):事件就是执行某个操作的触发条件,当一个事件被满足,将会触发一个动作,或者执行一次状态的迁移。
  • 动作(action):事件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当事件满足后,也可以不执行任何动作,直接迁移到新状态。
  • 次态(dst state):事件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。
  • 状态流转(transition):事物从现态转为次态的整个过程。

3. 优点

  • 代码抽象:将业务流程进行抽象和结构化,将复杂的状态转移图,分割成相邻状态的最小单元。这样相当于搭建乐高积木,在这套机制上可以组合成复杂的状态转移图,同时隐藏了系统的复杂度。
  • 简化流程:业务rd只需要关注当前操作的业务逻辑(状态流转过程中的业务回调函数),极大的解耦了状态和业务。
  • 易扩展:在新增状态或事件时,无需修改原有的状态流转逻辑,直接建立新的状态转移链路即可。
  • 业务建模:通过最小粒度的相邻状态拼接,最终组成了业务的整体graph。

二、代码

假设我们有要实现一个订单下单功能,上图是订单状态的流转图,方框为订单的状态,箭头旁的文字为事件。

1. database包

package database

import "fmt"

// DB 模拟数据库对象
type DB struct {
}

// Transaction 模拟事务
func (db *DB) Transaction(fun func() error) error {
    fmt.Println("事务执行开始。")
    err := fun()
    fmt.Println("事务执行结束。")
    return err
}

// Order 订单
type Order struct {
    ID    int64 // 主键ID
    State int   // 状态
}

type OrderList []*Order

// 查询所有订单
func ListAllOrder() (OrderList, error) {
    orderList := OrderList{
        &Order{1, 0},
        &Order{2, 1},
        &Order{2, 2},
    }
    return orderList, nil
}

// UpdateOrderState 更新订单状态
func UpdateOrderState(curOrder *Order, srcState int, dstState int) error {
    if curOrder.State == srcState {
        curOrder.State = dstState
    }
    fmt.Printf("更新id为 %v 的订单状态,从现态[%v]到次态[%v]\n", curOrder.ID, srcState, dstState)
    return nil
}

用来模拟数据库的操作,如数据库事务、查询所有订单、更新订单状态等。

2. fsm包

package fsm

import (
    "fmt"
    "reflect"
    "zuzhiang/database"
)

// FSMState 状态机的状态类型
type FSMState int

// FSMEvent 状态机的事件类型
type FSMEvent string

// FSMTransitionMap 状态机的状态转移图类型,现态和事件一旦确定,次态和动作就唯一确定
type FSMTransitionMap map[FSMState]map[FSMEvent]FSMDstStateAndAction

// FSMTransitionFunc 状态机的状态转移函数类型
type FSMTransitionFunc func(params map[string]interface{}, srcState FSMState, dstState FSMState) error

// FSMDstStateAndAction 状态机的次态和动作
type FSMDstStateAndAction struct {
    DstState FSMState  // 次态
    Action   FSMAction // 动作
}

// FSMAction 状态机的动作
type FSMAction interface {
    Before(bizParams map[string]interface{}) error                   // 状态转移前执行
    Execute(bizParams map[string]interface{}, tx *database.DB) error // 状态转移中执行
    After(bizParams map[string]interface{}) error                    // 状态转移后执行
}

// FSM 状态机,元素均为不可导出
type FSM struct {
    transitionMap  FSMTransitionMap  // 状态转移图
    transitionFunc FSMTransitionFunc // 状态转移函数
}

// CreateNewFSM 创建一个新的状态机
func CreateNewFSM(transitionFunc FSMTransitionFunc) *FSM {
    return &FSM{
        transitionMap:  make(FSMTransitionMap),
        transitionFunc: transitionFunc,
    }
}

// SetTransitionMap 设置状态机的状态转移图
func (fsm *FSM) SetTransitionMap(srcState FSMState, event FSMEvent, dstState FSMState, action FSMAction) {
    if int(srcState) < 0 || len(event) <= 0 || int(dstState) < 0 {
        panic("现态|事件|次态非法。")
        return
    }
    transitionMap := fsm.transitionMap
    if transitionMap == nil {
        transitionMap = make(FSMTransitionMap)
    }
    if _, ok := transitionMap[srcState]; !ok {
        transitionMap[srcState] = make(map[FSMEvent]FSMDstStateAndAction)
    }
    if _, ok := transitionMap[srcState][event]; !ok {
        dstStateAndAction := FSMDstStateAndAction{
            DstState: dstState,
            Action:   action,
        }
        transitionMap[srcState][event] = dstStateAndAction
    } else {
        fmt.Printf("现态[%v]+事件[%v]+次态[%v]已定义过,请勿重复定义。\n", srcState, event, dstState)
        return
    }
    fsm.transitionMap = transitionMap
}

// Push 状态机的状态迁移
func (fsm *FSM) Push(tx *database.DB, params map[string]interface{}, currentState FSMState, event FSMEvent) error {
    // 根据现态和事件从状态转移图获取次态和动作
    transitionMap := fsm.transitionMap
    events, eventExist := transitionMap[currentState]
    if !eventExist {
        return fmt.Errorf("现态[%v]未配置迁移事件", currentState)
    }
    dstStateAndAction, ok := events[event]
    if !ok {
        return fmt.Errorf("现态[%v]+迁移事件[%v]未配置次态", currentState, event)
    }
    dstState := dstStateAndAction.DstState
    action := dstStateAndAction.Action

    // 执行before方法
    if action != nil {
        fsmActionName := reflect.ValueOf(action).String()
        fmt.Printf("现态[%v]+迁移事件[%v]->次态[%v], [%v].before\n", currentState, event, dstState, fsmActionName)
        if err := action.Before(params); err != nil {
            return fmt.Errorf("现态[%v]+迁移事件[%v]->次态[%v]失败, [%v].before, err: %v", currentState, event, dstState, fsmActionName, err)
        }
    }

    // 事务执行execute方法和transitionFunc
    if tx == nil {
        tx = new(database.DB)
    }
    transactionErr := tx.Transaction(func() error {
        fsmActionName := reflect.ValueOf(action).String()
        fmt.Printf("现态[%v]+迁移事件[%v]->次态[%v], [%v].execute\n", currentState, event, dstState, fsmActionName)
        if action != nil {
            if err := action.Execute(params, tx); err != nil {
                return fmt.Errorf("状态转移执行出错:%v", err)
            }
        }

        fmt.Printf("现态[%v]+迁移事件[%v]->次态[%v], transitionFunc\n", currentState, event, dstState)
        if err := fsm.transitionFunc(params, currentState, dstState); err != nil {
            return fmt.Errorf("执行状态转移函数出错: %v", err)
        }
        return nil
    })
    if transactionErr != nil {
        return transactionErr
    }

    // 执行after方法
    if action != nil {
        fsmActionName := reflect.ValueOf(action).String()
        fmt.Printf("现态[%v]+迁移事件[%v]->次态[%v], [%v].after\n", currentState, event, dstState, fsmActionName)
        if err := action.After(params); err != nil {
            return fmt.Errorf("现态[%v]+迁移事件[%v]->次态[%v]失败, [%v].before, err: %v", currentState, event, dstState, fsmActionName, err)
        }
    }
    return nil
}

状态机包含的元素有两个:状态转移图和状态转移函数,为了防止包外直接调用,这两个元素都设为了不可导出的。状态转移图说明了状态机的状态流转情况,状态转移函数定义了在状态转移的过程中需要做的事情,在创建状态时指定,如更新数据库实体(order)的状态。

而状态转移图又包含现态、事件、次态和动作,一旦现态和事件确定,那么状态流转的唯一次态和动作就随之确定。

状态机的动作又包含三个:Before、Execute和After。Before操作在是事务前执行,由于此时没有翻状态,所以该步可能会被重复执行。Execute操作是和状态转移函数在同一事务中执行的,同时成功或同时失败。After操作是在事务后执行,因为在执行前状态已经翻转,所以最多会执行一次,在业务上允许执行失败或未执行。

状态机的主要方法有两个,SetTransitionMap方法用来设置状态机的状态转移图,Push方法用来根据现态和事件推动状态机进行状态翻转。Push方法中会先执行Before动作,再在同一事务中执行Execute动作和状态转移函数,最后执行After动作。在执行Push方法的时候会将params参数传递给状态转移函数。

3. order包

(1) order

package order

import (
    "fmt"
    "zuzhiang/database"
    "zuzhiang/fsm"
)

var (
    // 状态
    StateOrderInit          = fsm.FSMState(0) // 初始状态
    StateOrderToBePaid      = fsm.FSMState(1) // 待支付
    StateOrderToBeDelivered = fsm.FSMState(2) // 待发货
    StateOrderCancel        = fsm.FSMState(3) // 订单取消
    StateOrderToBeReceived  = fsm.FSMState(4) // 待收货
    StateOrderDone          = fsm.FSMState(5) // 订单完成

    // 事件
    EventOrderPlace      = fsm.FSMEvent("EventOrderPlace")      // 下单
    EventOrderPay        = fsm.FSMEvent("EventOrderPay")        // 支付
    EventOrderPayTimeout = fsm.FSMEvent("EventOrderPayTimeout") // 支付超时
    EventOrderDeliver    = fsm.FSMEvent("EventOrderDeliver")    // 发货
    EventOrderReceive    = fsm.FSMEvent("EventOrderReceive")    // 收货
)

var orderFSM *fsm.FSM

// orderTransitionFunc 订单状态转移函数
func orderTransitionFunc(params map[string]interface{}, srcState fsm.FSMState, dstState fsm.FSMState) error {
    // 从params中解析order参数
    key, ok := params["order"]
    if !ok {
        return fmt.Errorf("params[\"order\"]不存在。")
    }
    curOrder := key.(*database.Order)
    fmt.Printf("order.ID: %v, order.State: %v\n", curOrder.ID, curOrder.State)

    // 订单状态转移
    if err := database.UpdateOrderState(curOrder, int(srcState), int(dstState)); err != nil {
        return err
    }
    return nil
}

// Init 状态机的状态转移图初始化
func Init() {
    orderFSM = fsm.CreateNewFSM(orderTransitionFunc)
    orderFSM.SetTransitionMap(StateOrderInit, EventOrderPlace, StateOrderToBePaid, PlaceAction{})                  // 初始化+下单 -> 待支付
    orderFSM.SetTransitionMap(StateOrderToBePaid, EventOrderPay, StateOrderToBeDelivered, PayAction{})             // 待支付+支付 -> 待发货
    orderFSM.SetTransitionMap(StateOrderToBePaid, EventOrderPayTimeout, StateOrderCancel, nil)                     // 待支付+支付超时 -> 订单取消
    orderFSM.SetTransitionMap(StateOrderToBeDelivered, EventOrderDeliver, StateOrderToBeReceived, DeliverAction{}) // 待发货+发货 -> 待收货
    orderFSM.SetTransitionMap(StateOrderToBeReceived, EventOrderReceive, StateOrderDone, ReceiveAction{})          // 待收货+收货 -> 订单完成
}

// ExecOrderTask 执行订单任务,推动状态转移
func ExecOrderTask(params map[string]interface{}) error {
    // 从params中解析order参数
    key, ok := params["order"]
    if !ok {
        return fmt.Errorf("params[\"order\"]不存在。")
    }
    curOrder := key.(*database.Order)

    // 初始化+下单 -> 待支付
    if curOrder.State == int(StateOrderInit) {
        if err := orderFSM.Push(nil, params, StateOrderInit, EventOrderPlace); err != nil {
            return err
        }
    }
    // 待支付+支付 -> 待发货
    if curOrder.State == int(StateOrderToBePaid) {
        if err := orderFSM.Push(nil, params, StateOrderToBePaid, EventOrderPay); err != nil {
            return err
        }
    }
    // 待支付+支付超时 -> 订单取消
    if curOrder.State == int(StateOrderToBePaid) {
        if err := orderFSM.Push(nil, params, StateOrderToBePaid, EventOrderPayTimeout); err != nil {
            return err
        }
    }
    // 待发货+发货 -> 待收货
    if curOrder.State == int(StateOrderToBeDelivered) {
        if err := orderFSM.Push(nil, params, StateOrderToBeDelivered, EventOrderDeliver); err != nil {
            return err
        }
    }
    // 待收货+收货 -> 订单完成
    if curOrder.State == int(StateOrderToBeReceived) {
        if err := orderFSM.Push(nil, params, StateOrderToBeReceived, EventOrderReceive); err != nil {
            return err
        }
    }
    return nil
}

order包中做的事情主要有:

  • 定义订单状态机的状态和事件;
  • 创建一个状态机,并设置状态转移函数和状态转移图;
  • 执行订单任务,推动状态转移。

(2) order_action_place

package order

import (
    "fmt"
    "zuzhiang/database"
)

type PlaceAction struct {
}

// Before 事务前执行,业务上允许多次操作
func (receiver PlaceAction) Before(bizParams map[string]interface{}) error {
    fmt.Println("执行下单的Before方法。")
    return nil
}

// Execute 事务中执行,与状态转移在同一事务中
func (receiver PlaceAction) Execute(bizParams map[string]interface{}, tx *database.DB) error {
    fmt.Println("执行下单的Execute方法。")
    return nil
}

// After 事务后执行,业务上允许执行失败或未执行
func (receiver PlaceAction) After(bizParams map[string]interface{}) error {
    fmt.Println("执行下单的After方法。")
    return nil
}

(2) ~ (5)是订单不同动作的声明和实现。

(3) order_action_pay

package order

import (
    "fmt"
    "zuzhiang/database"
)

type PayAction struct {
}

// Before 事务前执行,业务上允许多次操作
func (receiver PayAction) Before(bizParams map[string]interface{}) error {
    fmt.Println("执行支付的Before方法。")
    return nil
}

// Execute 事务中执行,与状态转移在同一事务中
func (receiver PayAction) Execute(bizParams map[string]interface{}, tx *database.DB) error {
    fmt.Println("执行支付的Execute方法。")
    return nil
}

// After 事务后执行,业务上允许执行失败或未执行
func (receiver PayAction) After(bizParams map[string]interface{}) error {
    fmt.Println("执行支付的After方法。")
    return nil
}

(4) order_action_deliver

package order

import (
    "fmt"
    "zuzhiang/database"
)

type DeliverAction struct {
}

// Before 事务前执行,业务上允许多次操作
func (receiver DeliverAction) Before(bizParams map[string]interface{}) error {
    fmt.Println("执行发货的Before方法。")
    return nil
}

// Execute 事务中执行,与状态转移在同一事务中
func (receiver DeliverAction) Execute(bizParams map[string]interface{}, tx *database.DB) error {
    fmt.Println("执行发货的Execute方法。")
    return nil
}

// After 事务后执行,业务上允许执行失败或未执行
func (receiver DeliverAction) After(bizParams map[string]interface{}) error {
    fmt.Println("执行发货的After方法。")
    return nil
}

(5) order_action_receive

package order

import (
    "fmt"
    "zuzhiang/database"
)

type ReceiveAction struct {
}

// Before 事务前执行,业务上允许多次操作
func (receiver ReceiveAction) Before(bizParams map[string]interface{}) error {
    fmt.Println("执行收货的Before方法。")
    return nil
}

// Execute 事务中执行,与状态转移在同一事务中
func (receiver ReceiveAction) Execute(bizParams map[string]interface{}, tx *database.DB) error {
    fmt.Println("执行收货的Execute方法。")
    return nil
}

// After 事务后执行,业务上允许执行失败或未执行
func (receiver ReceiveAction) After(bizParams map[string]interface{}) error {
    fmt.Println("执行收货的After方法。")
    return nil
}

4. main包

package main

import (
    "fmt"
    "zuzhiang/database"
    "zuzhiang/order"
)

func main() {
    order.Init()
    orderList, dbErr := database.ListAllOrder()
    if dbErr != nil {
        return
    }
    for _, curOrder := range orderList {
        params := make(map[string]interface{})
        params["order"] = curOrder
        if err := order.ExecOrderTask(params); err != nil {
            fmt.Printf("执行订单任务出错:%v\n", err)
        }
        fmt.Println("\n\n")
    }
}

最后在main包里先初始化一个订单状态机,查询所有订单,并使用状态机执行订单任务,推动订单状态转移。注意多个订单可以用同一个状态机来进行状态的迁移。

三、个人总结

为什么要使用状态机,我想主要是它可以对一个复杂的业务流程进行模块化拆分,使得代码更为易读。并且扩展性更好,如果后续有新状态加入,只需要在原来的基础上进行扩展即可,甚至不需要了解整个业务流程。

其次,它将数据库实体的状态流转进行了模范化,避免了不同的开发人员在写更新数据库实体状态代码时可能导致的问题。

到此这篇关于Go语言状态机的实现的文章就介绍到这了,更多相关Go语言状态机内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Go语言做爬虫状态码返回418的问题解决

    目录 背景 原因分析 代码部分 背景  在使用Go语言做爬虫时,使用http.Get(url)去获取网页内容,状态码返回404,Body体为空. 原因分析  http.Get(url)是不需要设置header属性的http请求,比较简单快捷,但状态码返回418,表明我们需要设置其header属性,那么我们可以使用http.NewRequest,在设置其header属性即可~ 代码部分 func main7() { client := &http.Client{} url := "http

  • golang 设置web请求状态码操作

    我就废话不多说了,大家还是直接看代码吧~ package main import ( "net/http" ) func main() { //路由处理绑定 http.HandleFunc("/", Hander) //监听8080端口 http.ListenAndServe(":8080", nil) } func Hander(w http.ResponseWriter, req *http.Request) { //设置 http请求状态

  • GoLang channel关闭状态相关操作详解

    关于 channel 的使用,有几点不方便的地方: 1.在不改变 channel 自身状态的情况下,无法获知一个 channel 是否关闭. 2.关闭一个 closed channel 会导致 panic.所以,如果关闭 channel 的一方在不知道 channel 是否处于关闭状态时就去贸然关闭 channel 是很危险的事情. 3.向一个 closed channel 发送数据会导致 panic.所以,如果向 channel 发送数据的一方不知道 channel 是否处于关闭状态时就去贸然

  • Go WEB框架使用拦截器验证用户登录状态实现

    目录 wego拦截器 main函数 登录逻辑 登录拦截器的实现 index页面的实现 wego拦截器 wego拦截器是一个action(处理器函数)之前或之后被调用的函数,通常用于处理一些公共逻辑.拦截器能够用于以下常见问题: 请求日志记录 错误处理 身份验证处理 wego中有以下拦截器: before_exec :执行action之前拦截器 after_exec :执行action之后拦截器 本文用一个例子来说明如何使用拦截器来实现用户登录状态的判定.在这个例子中,用户访问login_get来

  • golang执行命令获取执行结果状态(推荐)

    这几天在用golang写一个工具,要执行外部命令工具,而且还要将外部命令工具输出的日志也要输出出来.网上找了一下,资料很多,关键是执行的结果成功或失败状态没找到好的方法获取到. 刚开始想的是看错误日志,如果有错误日志,那么就是执行失败.测试的时候发现这样不行,发现有些时候会用error输出日志,但不一定就是执行失败.后来想用日志中的关键字匹配,因为有些命令执行成功或失败都是有关键字输出的,测试发现也不太好. 最后没办法,看了一下Cmd.Wait()方法的实现,突然眼前一亮,找到方法了,有一个Cm

  • golang 检查网络状态是否正常的方法

    如下所示: package main import ( "fmt" "os/exec" "time" ) func NetWorkStatus() bool { cmd := exec.Command("ping", "baidu.com", "-c", "1", "-W", "5") fmt.Println("Net

  • Go语言状态机的实现

    目录 一.状态机 二.代码 1. database包 2. fsm包 3. order包 4. main包 三.个人总结 一.状态机 1. 定义 有限状态机(Finite-state machine, FSM),简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型. 2. 组成要素 现态(src state):事务当前所处的状态. 事件(event):事件就是执行某个操作的触发条件,当一个事件被满足,将会触发一个动作,或者执行一次状态的迁移. 动作(action):事件满足

  • C语言中的状态机设计深入讲解

    前言 本文不是关于软件状态机的最佳设计分解实践的教程.我将重点关注状态机代码和简单的示例,这些示例具有足够的复杂性,以便于理解特性和用法. 背景 大多数程序员常用的设计技术是有限状态机(FSM).设计人员使用此编程结构将复杂的问题分解为可管理的状态和状态转换.有无数种实现状态机的方法. A switch语句提供了状态机最容易实现和最常见的版本之一.在这里,每个案例在switch语句成为一个状态,实现如下所示: switch (currentState) { case ST_IDLE: // do

  • 简单理解Python中基于生成器的状态机

    简单生成器有许多优点.生成器除了能够用更自然的方法表达一类问题的流程之外,还极大地改善了许多效率不足之处.在 Python 中,函数调用代价不菲:除其它因素外,还要花一段时间解决函数参数列表(除了其它的事情外,还要分析位置参数和缺省参数).初始化框架对象还要采取一些建立步骤(据 Tim Peters 在 comp.lang.python 上所说,有 100 多行 C 语言程序:我自己还没检查 Python 源代码呢).与此相反,恢复一个生成器就相当省力:参数已经解析完了,而且框架对象正"无所事事

  • 状态机的概念和在Python下使用状态机的教程

    什么是状态机? 关于状态机的一个极度确切的描述是它是一个有向图形,由一组节点和一组相应的转移函数组成.状态机通过响应一系列事件而"运行".每个事件都在属于"当前"节点的转移函数的控制范围内,其中函数的范围是节点的一个子集.函数返回"下一个"(也许是同一个)节点.这些节点中至少有一个必须是终态.当到达终态,状态机停止. 但一个抽象的数学描述(就像我刚给出的)并不能真正说明在什么情况下使用状态机可以解决实际编程问题.另一种策略就是将状态机定义成一种强

  • C语言与Lua之间的相互调用详解

    前言 第一次接触Lua是因为Unity游戏中需要热更,但是一直没搞懂Lua是怎么嵌入到别的语言中执行的,如何互相调用的. lua是扩展性非常良好的语言,虽然核心非常精简,但是用户可以依靠lua库来实现大部分工作.除此之外,lua还可以通过与C函数相互调用来扩展程序功能.在C中嵌入lua脚本既可以让用户在不重新编译代码的情况下修改lua代码更新程序,也可以给用户提供一个自由定制的接口,这种方法遵循了机制与策略分离的原则.在lua中调用C函数可以提高程序的运行效率.lua与C的相互调用在工程中相当实

  • 深度解密 Go 语言中的 sync.map

    工作中,经常会碰到并发读写 map 而造成 panic 的情况,为什么在并发读写的时候,会 panic 呢?因为在并发读写的情况下,map 里的数据会被写乱,之后就是 Garbage in, garbage out,还不如直接 panic 了. 是什么 Go 语言原生 map 并不是线程安全的,对它进行并发读写操作的时候,需要加锁.而 sync.map 则是一种并发安全的 map,在 Go 1.9 引入. sync.map 是线程安全的,读取,插入,删除也都保持着常数级的时间复杂度. sync.

  • c语言实现简单的五子棋游戏

    本文实例为大家分享了c语言实现简单五子棋游戏的具体代码,供大家参考,具体内容如下 环境vs2017 一.游戏设计思想 1.该代码设置为 玩家1(*) vs 玩家2(O) 2.选择玩游戏 2.1 显示棋盘,玩家1下棋,判断游戏结果2.2 显示棋盘,玩家2下棋,判断游戏结果  3.判断游戏结果 有4种结果,玩家1赢,玩家2赢,平局,继续游戏若结果为玩家1赢或玩家2赢或平局,则显示结果,退回菜单界面,不再循环下棋        若结果为继续,则循环2.1和2.2 4.选择退出,则退出游戏 二.图片解说

  • Go语言实现枚举的示例代码

    在编程领域里,枚举用来表示只包含有限数量的固定值的类型,在开发中一般用于标识错误码或者状态机.拿一个实体对象的状态机来说,它通常与这个对象在数据库里对应记录的标识状态的字段值相对应. 在刚开始学编程的时候,你一定写过,至少见过直接使用魔术数字进行判断的代码.啥叫魔术数字呢,举个例子,要置顶一个文章的时候先判断文章是不是已发布状态. if (article.state == 2) {    // state 2 代表文章已发布 } 假如我们的代码里没有注释,或者等我们项目的代码里充斥着这些魔术数字

  • C语言数组超详细讲解上

    目录 前言 1.一维数组的创建和初始化 1.1 一维数组的创建 1.2 一维数组的初始化 1.3 一维数组的使用 1.4 一维数组在内存中的存储 2.二维数组的创建和初始化 2.1 二维数组的创建 2.2 二维数组的初始化 2.3 二维数组的使用 2.4 二维数组在内存中的存储 3.数组越界 4.数组作为函数参数 4.1 冒泡排序函数的错误设计 4.2 数组名是什么? 4.3 对数组名的用法进行总结 4.4 冒泡排序函数的正确设计 总结 前言 本文主要介绍数组相关的内容,主要内容包括: 一维数组

  • C语言简析指针用途

    目录 对象的访问方式 什么是指针 指针变量 与指针相关的运算符 指针变量作为函数参数 数组与指针 多维数组与指针 指针常量 和 常量指针 指针数组 与 数组指针 字符串与指针 函数指针 二级指针 与 多级指针 在C语言中,任何一个变量,都有两层含义: (1) 代表该变量的存储单元的地址:变量的地址 左值 lvalue (2) 代表该变量的值:右值 rvalue 对于一个变量的访问,只有两种情况: 一是把一个值写到变量的地址中去 (lvalue) 二是从变量的地址中取变量的值 (rvalue) 对

随机推荐