鸿蒙OSUniApp 移动端直播流播放实战:打造符合鸿蒙设计风格的播放器#三方框架 #Uniapp

news2025/6/4 19:33:17

UniApp 移动端直播流播放实战:打造符合鸿蒙设计风格的播放器

在移动互联网时代,直播已经成为一种主流的内容形式。本文将详细介绍如何使用 UniApp 框架开发一个优雅的直播流播放器,并融入鸿蒙系统的设计理念,实现一个既美观又实用的播放功能。

设计理念

在开发直播播放器之前,我们需要深入理解鸿蒙系统的设计哲学。鸿蒙系统强调"自然、统一、高效"的设计原则,这些特点将贯穿我们的整个功能实现过程:

  1. 自然:播放器的控制要符合用户的使用习惯
  2. 统一:视觉元素要保持一致性,符合系统设计规范
  3. 高效:确保流畅的播放体验和快速的加载速度
  4. 优雅:在各种状态下都保持良好的视觉表现

技术方案

在实现直播流播放功能时,我们需要考虑以下几个关键点:

  1. 播放器选型:使用原生组件 live-player
  2. 协议支持:RTMP、FLV、HLS 等主流直播协议
  3. 状态管理:加载中、播放中、错误等状态处理
  4. 性能优化:控制内存占用,优化渲染性能
  5. 交互设计:手势控制、全屏切换等

组件实现

1. 直播播放器组件

首先实现一个基础的直播播放器组件:

<!-- components/harmony-live-player/harmony-live-player.vue -->
<template>
  <view class="harmony-live-player" :class="{ 'fullscreen': isFullscreen }">
    <live-player
      class="player"
      :src="src"
      :mode="mode"
      :autoplay="autoplay"
      :muted="isMuted"
      :orientation="orientation"
      :object-fit="objectFit"
      @statechange="handleStateChange"
      @netstatus="handleNetStatus"
      @error="handleError"
      ref="livePlayer"
    >
      <!-- 播放器控制层 -->
      <view class="player-controls" :class="{ 'controls-visible': showControls }">
        <view class="top-bar">
          <view class="left-area">
            <text class="title">{{ title }}</text>
          </view>
          <view class="right-area">
            <text class="online-count" v-if="onlineCount">
              {{ formatNumber(onlineCount) }}人观看
            </text>
          </view>
        </view>
        
        <view class="bottom-bar">
          <view class="control-btn" @click="togglePlay">
            <text class="iconfont" :class="isPlaying ? 'icon-pause' : 'icon-play'"></text>
          </view>
          <view class="control-btn" @click="toggleMute">
            <text class="iconfont" :class="isMuted ? 'icon-mute' : 'icon-volume'"></text>
          </view>
          <view class="quality-switch" v-if="qualities.length > 1">
            <text 
              v-for="item in qualities" 
              :key="item.value"
              :class="{ 'active': currentQuality === item.value }"
              @click="switchQuality(item.value)"
            >
              {{ item.label }}
            </text>
          </view>
          <view class="control-btn" @click="toggleFullscreen">
            <text class="iconfont" :class="isFullscreen ? 'icon-fullscreen-exit' : 'icon-fullscreen'"></text>
          </view>
        </view>
      </view>
      
      <!-- 加载状态 -->
      <view class="loading-layer" v-if="isLoading">
        <view class="loading-animation"></view>
        <text class="loading-text">直播加载中...</text>
      </view>
      
      <!-- 错误状态 -->
      <view class="error-layer" v-if="hasError">
        <text class="error-icon iconfont icon-error"></text>
        <text class="error-text">{{ errorMessage }}</text>
        <view class="retry-btn" @click="retry">重试</view>
      </view>
    </live-player>
  </view>
</template>

<script>
const CONTROL_HIDE_TIMEOUT = 3000 // 控制栏自动隐藏时间

export default {
  name: 'HarmonyLivePlayer',
  props: {
    src: {
      type: String,
      required: true
    },
    title: {
      type: String,
      default: ''
    },
    mode: {
      type: String,
      default: 'RTC' // RTC模式延迟更低
    },
    autoplay: {
      type: Boolean,
      default: true
    },
    qualities: {
      type: Array,
      default: () => []
    },
    onlineCount: {
      type: Number,
      default: 0
    }
  },
  data() {
    return {
      isPlaying: false,
      isMuted: false,
      isLoading: true,
      hasError: false,
      errorMessage: '',
      isFullscreen: false,
      showControls: true,
      controlTimer: null,
      currentQuality: '',
      objectFit: 'contain',
      orientation: 'vertical',
      netStatus: {}
    }
  },
  watch: {
    src: {
      handler(newVal) {
        if (newVal) {
          this.hasError = false
          this.errorMessage = ''
          this.isLoading = true
          this.startPlay()
        }
      },
      immediate: true
    }
  },
  mounted() {
    // 初始化播放器
    this.initPlayer()
    
    // 监听触摸事件以显示/隐藏控制栏
    this.initTouchListeners()
  },
  beforeDestroy() {
    this.clearControlTimer()
    this.stopPlay()
  },
  methods: {
    initPlayer() {
      // 设置默认清晰度
      if (this.qualities.length > 0) {
        this.currentQuality = this.qualities[0].value
      }
      
      // 初始化播放状态
      if (this.autoplay) {
        this.startPlay()
      }
    },
    
    initTouchListeners() {
      const player = this.$refs.livePlayer
      if (!player) return
      
      player.$el.addEventListener('touchstart', () => {
        this.toggleControls()
      })
    },
    
    startPlay() {
      const player = this.$refs.livePlayer
      if (!player) return
      
      player.play({
        success: () => {
          this.isPlaying = true
          this.isLoading = false
          this.startControlTimer()
        },
        fail: (err) => {
          this.handleError(err)
        }
      })
    },
    
    stopPlay() {
      const player = this.$refs.livePlayer
      if (!player) return
      
      player.stop()
      this.isPlaying = false
    },
    
    togglePlay() {
      if (this.isPlaying) {
        this.stopPlay()
      } else {
        this.startPlay()
      }
    },
    
    toggleMute() {
      this.isMuted = !this.isMuted
    },
    
    toggleFullscreen() {
      const player = this.$refs.livePlayer
      if (!player) return
      
      if (this.isFullscreen) {
        player.exitFullScreen({
          success: () => {
            this.isFullscreen = false
            this.orientation = 'vertical'
          }
        })
      } else {
        player.requestFullScreen({
          direction: 0,
          success: () => {
            this.isFullscreen = true
            this.orientation = 'horizontal'
          }
        })
      }
    },
    
    switchQuality(quality) {
      if (this.currentQuality === quality) return
      
      this.currentQuality = quality
      this.$emit('quality-change', quality)
    },
    
    handleStateChange(e) {
      const state = e.detail.code
      switch (state) {
        case 2001: // 已经连接
          this.isLoading = false
          this.isPlaying = true
          break
        case 2002: // 已经开始播放
          this.isLoading = false
          break
        case 2103: // 网络断连
          this.handleError({ errMsg: '网络连接断开' })
          break
      }
    },
    
    handleNetStatus(e) {
      this.netStatus = e.detail
      this.$emit('net-status', e.detail)
    },
    
    handleError(e) {
      this.hasError = true
      this.isPlaying = false
      this.isLoading = false
      this.errorMessage = e.errMsg || '播放出错,请重试'
      this.$emit('error', e)
    },
    
    retry() {
      this.hasError = false
      this.errorMessage = ''
      this.isLoading = true
      this.startPlay()
    },
    
    toggleControls() {
      this.showControls = !this.showControls
      if (this.showControls) {
        this.startControlTimer()
      }
    },
    
    startControlTimer() {
      this.clearControlTimer()
      this.controlTimer = setTimeout(() => {
        this.showControls = false
      }, CONTROL_HIDE_TIMEOUT)
    },
    
    clearControlTimer() {
      if (this.controlTimer) {
        clearTimeout(this.controlTimer)
        this.controlTimer = null
      }
    },
    
    formatNumber(num) {
      if (num >= 10000) {
        return (num / 10000).toFixed(1) + 'w'
      }
      return num.toString()
    }
  }
}
</script>

<style lang="scss">
.harmony-live-player {
  position: relative;
  width: 100%;
  background-color: #000000;
  
  &.fullscreen {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 999;
  }
  
  .player {
    width: 100%;
    height: 100%;
  }
  
  .player-controls {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: linear-gradient(
      to bottom,
      rgba(0, 0, 0, 0.3) 0%,
      transparent 40%,
      transparent 60%,
      rgba(0, 0, 0, 0.3) 100%
    );
    opacity: 0;
    transition: opacity 0.3s ease;
    
    &.controls-visible {
      opacity: 1;
    }
    
    .top-bar {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      height: 88rpx;
      padding: 0 32rpx;
      display: flex;
      align-items: center;
      justify-content: space-between;
      
      .title {
        color: #ffffff;
        font-size: 32rpx;
        font-weight: 500;
      }
      
      .online-count {
        color: #ffffff;
        font-size: 24rpx;
        opacity: 0.8;
      }
    }
    
    .bottom-bar {
      position: absolute;
      bottom: 0;
      left: 0;
      right: 0;
      height: 88rpx;
      padding: 0 32rpx;
      display: flex;
      align-items: center;
      
      .control-btn {
        width: 80rpx;
        height: 80rpx;
        display: flex;
        align-items: center;
        justify-content: center;
        
        .iconfont {
          color: #ffffff;
          font-size: 48rpx;
        }
        
        &:active {
          opacity: 0.7;
        }
      }
      
      .quality-switch {
        flex: 1;
        display: flex;
        justify-content: center;
        gap: 24rpx;
        
        text {
          color: #ffffff;
          font-size: 24rpx;
          padding: 8rpx 16rpx;
          border-radius: 32rpx;
          background-color: rgba(255, 255, 255, 0.2);
          
          &.active {
            background-color: #2979ff;
          }
          
          &:active {
            opacity: 0.7;
          }
        }
      }
    }
  }
  
  .loading-layer {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    
    .loading-animation {
      width: 64rpx;
      height: 64rpx;
      border: 4rpx solid rgba(255, 255, 255, 0.1);
      border-top-color: #ffffff;
      border-radius: 50%;
      animation: spin 1s linear infinite;
    }
    
    .loading-text {
      color: #ffffff;
      font-size: 28rpx;
      margin-top: 16rpx;
    }
  }
  
  .error-layer {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    
    .error-icon {
      font-size: 80rpx;
      color: #ffffff;
      margin-bottom: 16rpx;
    }
    
    .error-text {
      color: #ffffff;
      font-size: 28rpx;
      margin-bottom: 32rpx;
    }
    
    .retry-btn {
      padding: 16rpx 48rpx;
      background-color: #2979ff;
      color: #ffffff;
      font-size: 28rpx;
      border-radius: 44rpx;
      
      &:active {
        opacity: 0.9;
      }
    }
  }
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}
</style>

2. 使用示例

下面展示如何在页面中使用直播播放器组件:

<!-- pages/live/live.vue -->
<template>
  <view class="live-page">
    <harmony-live-player
      :src="liveUrl"
      :title="liveInfo.title"
      :qualities="qualities"
      :online-count="liveInfo.onlineCount"
      @error="handlePlayError"
      @quality-change="handleQualityChange"
      @net-status="handleNetStatus"
    ></harmony-live-player>
    
    <view class="live-info">
      <view class="anchor-info">
        <image class="avatar" :src="liveInfo.anchorAvatar"></image>
        <view class="info-content">
          <text class="anchor-name">{{ liveInfo.anchorName }}</text>
          <text class="fans-count">{{ formatNumber(liveInfo.fansCount) }}粉丝</text>
        </view>
        <button class="follow-btn" 
                :class="{ 'followed': isFollowed }"
                @click="toggleFollow">
          {{ isFollowed ? '已关注' : '关注' }}
        </button>
      </view>
      
      <view class="live-stats">
        <view class="stat-item">
          <text class="label">观看</text>
          <text class="value">{{ formatNumber(liveInfo.viewCount) }}</text>
        </view>
        <view class="stat-item">
          <text class="label">点赞</text>
          <text class="value">{{ formatNumber(liveInfo.likeCount) }}</text>
        </view>
        <view class="stat-item">
          <text class="label">分享</text>
          <text class="value">{{ formatNumber(liveInfo.shareCount) }}</text>
        </view>
      </view>
    </view>
  </view>
</template>

<script>
import HarmonyLivePlayer from '@/components/harmony-live-player/harmony-live-player'

export default {
  components: {
    HarmonyLivePlayer
  },
  data() {
    return {
      liveUrl: '',
      qualities: [
        { label: '标清', value: 'SD' },
        { label: '高清', value: 'HD' },
        { label: '超清', value: 'FHD' }
      ],
      liveInfo: {
        title: '直播标题',
        anchorName: '主播昵称',
        anchorAvatar: '/static/default-avatar.png',
        onlineCount: 0,
        viewCount: 0,
        likeCount: 0,
        shareCount: 0,
        fansCount: 0
      },
      isFollowed: false
    }
  },
  onLoad(options) {
    // 获取直播信息
    this.fetchLiveInfo(options.id)
  },
  methods: {
    async fetchLiveInfo(id) {
      try {
        // 这里替换为实际的API调用
        const response = await this.$api.getLiveInfo(id)
        this.liveInfo = response.data
        this.liveUrl = response.data.playUrl
      } catch (error) {
        uni.showToast({
          title: '获取直播信息失败',
          icon: 'none'
        })
      }
    },
    
    handlePlayError(error) {
      console.error('播放错误:', error)
      uni.showToast({
        title: '播放出错,请重试',
        icon: 'none'
      })
    },
    
    handleQualityChange(quality) {
      console.log('切换清晰度:', quality)
      // 这里可以根据清晰度切换不同的播放地址
    },
    
    handleNetStatus(status) {
      console.log('网络状态:', status)
      // 这里可以根据网络状态做相应处理
    },
    
    toggleFollow() {
      this.isFollowed = !this.isFollowed
      // 调用关注/取消关注API
    },
    
    formatNumber(num) {
      if (num >= 10000) {
        return (num / 10000).toFixed(1) + 'w'
      }
      return num.toString()
    }
  }
}
</script>

<style lang="scss">
.live-page {
  min-height: 100vh;
  background-color: #f5f5f5;
  
  .live-info {
    padding: 32rpx;
    background-color: #ffffff;
    
    .anchor-info {
      display: flex;
      align-items: center;
      margin-bottom: 32rpx;
      
      .avatar {
        width: 88rpx;
        height: 88rpx;
        border-radius: 44rpx;
        margin-right: 24rpx;
      }
      
      .info-content {
        flex: 1;
        
        .anchor-name {
          font-size: 32rpx;
          color: #333333;
          font-weight: 500;
          margin-bottom: 8rpx;
        }
        
        .fans-count {
          font-size: 24rpx;
          color: #999999;
        }
      }
      
      .follow-btn {
        padding: 16rpx 48rpx;
        background-color: #2979ff;
        color: #ffffff;
        font-size: 28rpx;
        border-radius: 44rpx;
        
        &.followed {
          background-color: #f5f5f5;
          color: #666666;
        }
        
        &:active {
          opacity: 0.9;
        }
      }
    }
    
    .live-stats {
      display: flex;
      justify-content: space-around;
      padding: 24rpx 0;
      border-top: 2rpx solid #f5f5f5;
      
      .stat-item {
        display: flex;
        flex-direction: column;
        align-items: center;
        
        .label {
          font-size: 24rpx;
          color: #999999;
          margin-bottom: 8rpx;
        }
        
        .value {
          font-size: 32rpx;
          color: #333333;
          font-weight: 500;
        }
      }
    }
  }
}
</style>

性能优化

  1. 预加载优化

为了提升直播加载速度,我们可以在进入直播间前预加载资源:

// utils/preload.js
export const preloadLiveStream = (url) => {
  return new Promise((resolve, reject) => {
    const context = uni.createLivePlayerContext('preload-player')
    context.play({
      success: () => {
        setTimeout(() => {
          context.stop()
          resolve()
        }, 100)
      },
      fail: reject
    })
  })
}
  1. 状态管理优化

使用 Vuex 管理直播相关状态:

// store/modules/live.js
export default {
  state: {
    currentLive: null,
    quality: 'HD',
    netStatus: {}
  },
  mutations: {
    SET_CURRENT_LIVE(state, live) {
      state.currentLive = live
    },
    SET_QUALITY(state, quality) {
      state.quality = quality
    },
    UPDATE_NET_STATUS(state, status) {
      state.netStatus = status
    }
  },
  actions: {
    async enterLive({ commit }, liveId) {
      try {
        const live = await api.getLiveInfo(liveId)
        commit('SET_CURRENT_LIVE', live)
        return live
      } catch (error) {
        throw error
      }
    }
  }
}
  1. 内存优化

在离开直播页面时,及时释放资源:

export default {
  onUnload() {
    // 停止播放
    this.$refs.livePlayer.stopPlay()
    
    // 清理定时器
    this.clearAllTimers()
    
    // 重置状态
    this.$store.commit('SET_CURRENT_LIVE', null)
  }
}

适配建议

  1. 网络适配

根据网络状况自动调整清晰度:

handleNetStatus(status) {
  if (status.netSpeed < 1000 && this.currentQuality !== 'SD') {
    // 网速较慢时切换到标清
    this.switchQuality('SD')
  } else if (status.netSpeed > 5000 && this.currentQuality === 'SD') {
    // 网速较快时切换到高清
    this.switchQuality('HD')
  }
}
  1. 横竖屏适配
.harmony-live-player {
  &.fullscreen {
    .player-controls {
      .bottom-bar {
        padding: 0 48rpx;
        height: 120rpx;
      }
      
      .quality-switch {
        text {
          font-size: 28rpx;
          padding: 12rpx 24rpx;
        }
      }
    }
  }
}

总结

通过本文的讲解,我们实现了一个功能完整的移动端直播播放器。该播放器不仅具有优雅的界面设计,还融入了鸿蒙系统的设计理念,同时考虑了性能优化和多端适配等实际开发中的重要因素。

在实际开发中,我们还可以根据具体需求扩展更多功能:

  • 弹幕系统
  • 礼物特效
  • 互动游戏
  • 直播回放
  • 防盗链处理

希望这篇文章能够帮助你更好地理解如何在 UniApp 中开发高质量的直播功能,同时也能为你的实际项目开发提供有价值的参考。

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

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

相关文章

2_MCU开发环境搭建-配置MDK兼容Keil4和C51

MCU开发环境搭建-配置MDK兼容Keil4和C51 一、概述 本文以MDK-ARM V5.36版本基础介绍DMK-ARM工程兼容Keil4和C51的配置。 注:在阅读本文前,请先安装和配置完成MDK-ARM(Keil5)。 二、工具包下载 链接: https://pan.baidu.com/s/1Tu2tDD6zRra4xb_PuA1Wsw 提取码: 81pp 三、…

通过远程桌面连接Windows实例提示“出现身份验证错误,无法连接到本地安全机构”错误怎么办?

本文介绍通过远程桌面连接Windows实例提示“出现身份验证错误无法连接到本地安全机构”错误的解决方案。 问题现象 通过本地电脑内的远程桌面连接Windows实例提示“出现身份验证错误&#xff0c;无法连接到本地安全机构”错误。 问题原因 导致该问题的可能原因如下&#x…

百度golang研发一面面经

输入一个网址&#xff0c;到显示界面&#xff0c;中间的过程是怎样的 IP 报文段的结构是什么 Innodb 的底层结构 知道几种设计模式 工厂模式 简单工厂模式&#xff1a;根据传入类型参数判断创建哪种类型对象工厂方法模式&#xff1a;由子类决定实例化哪个类抽象工厂模式&#…

TC3xx学习笔记-启动过程详解(一)

文章目录 前言Firmware启动过程BMHD Check流程ABM启动Internal Flash启动Bootloader ModeProcessing in case no valid BMHD foundProcessing in case no Boot Mode configured by SSW 总结 前言 之前介绍过UCB BMHD的使用&#xff0c;它在启动过程中起着重要的作用&#xff0…

Scratch节日 | 六一儿童节抓糖果

六一儿童节怎么能没有糖果&#xff1f;这款 六一儿童节抓糖果 小游戏&#xff0c;让你变身小猫&#xff0c;开启一场甜蜜大作战&#xff01; &#x1f3ae; 游戏玩法 帮助小猫收集所有丢失的糖果&#xff0c;收集越多分数越高&#xff01; 小心虫子一样的“坏糖果”&#xff…

通信算法之280:无人机侦测模块知识框架思维导图

1. 无人机侦测模块知识框架思维导图, 见文末章节。 2. OFDM参数估计,基于循环自相关特性。 3. 无人机其它参数估计

【Doris基础】Apache Doris中的Coordinator节点作用详解

目录 1 Doris架构概述 2 Coordinator节点的核心作用 2.1 查询协调与调度 2.2 执行计划生成与优化 2.3 资源管理与负载均衡 2.4 容错与故障恢复 3 Coordinator节点的关键实现机制 3.1 两阶段执行模型 3.2 流水线执行引擎 3.3 分布式事务管理 4 Coordinator节点的高可…

【Kubernetes-1.30】--containerd部署

文章目录 一、环境准备1.1 三台服务器1.2 基础配置&#xff08;三台机通用&#xff09;1.3 关闭 Swap&#xff08;必须&#xff09;1.4 关闭防火墙&#xff08;可选&#xff09;1.5 加载必要模块 & 配置内核参数 二、安装容器运行时&#xff08;containerd 推荐&#xff09…

相机--相机标定

教程 内外参公式及讲解 相机标定分类 相机标定分为内参标定和外参标定。 相机成像原理 相机成像畸变 链接 四个坐标系的转变 内参标定 内参 相机内参通常用一个 33 矩阵&#xff08;内参矩阵&#xff0c;KK&#xff09;表示&#xff0c;形式如下&#xff1a; (1)焦距&…

MongoDB(七) - MongoDB副本集安装与配置

文章目录 前言一、下载MongoDB1. 下载MongoDB2. 上传安装包3. 创建相关目录 二、安装配置MongoDB1. 解压MongoDB安装包2. 重命名MongoDB文件夹名称3. 修改配置文件4. 分发MongoDB文件夹5. 配置环境变量6. 启动副本集7. 进入MongoDB客户端8. 初始化副本集8.1 初始化副本集8.2 添…

131. 分割回文串-两种回溯思路

我们可以将字符串分割成若干回文子串&#xff0c;返回所有可能的方案。如果将问题分解&#xff0c;可以表示为分割长度为n-1的子字符串&#xff0c;这与原问题性质相同&#xff0c;因此可以采用递归方法解决。 为什么回溯与递归存在联系&#xff1f;在解决这个问题时&#xff0…

[Java恶补day13] 53. 最大子数组和

休息了一天&#xff0c;开始补上&#xff01; 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连续子数组&#xff08;子数组最少包含一个元素&#xff09;&#xff0c;返回其最大和。 子数组是数组中的一个连续部分。 示例 1&#xff1a; 输入&#xff1a;nums …

摩尔投票算法原理实现一文剖析

摩尔投票算法原理&实现一文剖析 一、算法原理1.1 基本思想1.2 数学原理 二、算法实现2.1 Python实现2.2 Java实现2.3 C实现 三、复杂度分析四、应用场景4.1 多数元素问题4.2 扩展应用&#xff1a;寻找出现次数超过n/3的元素 五、算法优势与注意事项5.1 优势5.2 注意事项 总…

MyBatis操作数据库(2)

1.#{}和${}使用 Interger类型的参数可以看到这里显示的语句是:select username,password,age,gender,phone from userinfo where id? 输入的参数并没有在后面进行拼接,,id的值是使用?进行占位,这种sql称之为"预编译sql".这里,把#{}改成${}观察情况:这里可以看到…

C++面向对象(二)

面向对象基础内容参考&#xff1a; C面向对象&#xff08;一&#xff09;-CSDN博客 友元函数 类的友元函数是定义在类外部&#xff0c;但有权访问类的所有私有&#xff08;private&#xff09;成员和保护&#xff08;protected&#xff09;成员。尽管友元函数的原型有在类的定…

【C语言入门级教学】冒泡排序和指针数组

文章目录 1.冒泡排序2.⼆级指针3.指针数组4.指针数组模拟⼆维数组 1.冒泡排序 冒泡排序的核⼼思想&#xff1a;两两相邻的元素进⾏⽐较。 //⽅法1 void bubble_sort(int arr[], int sz)//参数接收数组元素个数 { int i 0;for(i0; i-1; i) { int j 0; for(j0; j-1; j) { …

shell脚本中常用的命令

一、设置主机名称 通过文件的方式修改通过命令修改 二、nmcli 查看网卡 ip a s ens160 (网卡名称) ifconfig ens160 nmcli device show ens160 nmcli device status nmcli connection show ens160 2.设置网卡 a)当网卡没有被设置时 b)网卡被设定&#xff0c;需要修改 三…

Nuxt3部署

最近接了一个项目&#xff0c;需要用到 nuxt3 技术来满足甲方所要求的需求&#xff0c;在部署的时候遇到了很多问题&#xff0c;这里我一一给大家讲述部署流程&#xff0c;以及所遇到的坑 打包部署 部署分为俩种方式&#xff1a; 静态(spa)部署 和 ssr部署 静态部署 静态部…

网络攻防技术一:绪论

文章目录 一、网络空间CyberSpace1、定义2、基本四要素 二、网络空间安全1、定义2、保护对象3、安全属性4、作用空间 三、网络攻击1、攻击分类2、攻击过程 四、网络防护1、定义2、安全模型3、安全服务5类4、特定安全机制8种5、普遍性安全机制5种 五、网络安全技术发展简史1、第…