C/C++ 初级球球大作战练手

效果演示:

https://live.csdn.net/v/385490

游戏初始化

  • #include <stdbool.h>
    #include<stdio.h>
    #include<stdlib.h>
    #include<time.h>
    #include<graphics.h>
    #include <algorithm>
    #include<math.h>
    #include<mmsystem.h>
    #include <iostream>
    using namespace std;
    #pragma comment(lib,"winmm.lib")  
    //所需要的库引入
  • 需要准备的相关宏定义:
    #define WIN_WIDTH 1024 
    #define WIN_HEIGHT 640
    #define MAP_WIDTH  (WIN_WIDTH*5)
    #define MAP_HEIGHT (WIN_HEIGHT*5)
    #define FOOD_NUM 1000
    #define AI_NUM 500
  • 初始化显示终端:initgraph(1024,640)
    int main() {initgraph(WIN_WIDTH, WIN_HEIGHT);//初始化界面gameinit();while (true) {aimove();gamedraw();eatfood();playercontroller(10);}/*getchar();*/return 0;
    }
  • 根据main函数主体逻辑串联整个代码体系

游戏初始化操作

  • 游戏 初始化:在已经有的显示画面上进行布置球球的操作
  • 通过随机种子 :随机布置食物球球的位置  玩儿家的随机小球位置  自由移动的小球的位置
  • 前置定义:玩儿家,自由移动小球,食物小球
    struct ball
    {int x;int y;int r;bool flag;DWORD color;
    }player,food[FOOD_NUM],ai[AI_NUM];

进入游戏的主题循环

  • 自由小球的移动设置
    void Chase(struct ball* chase,struct ball run)
    {chase->x < run.x ? chase->x += 2 : chase->x -= 2;chase->y < run.y ? chase->y += 2 : chase->y -= 2;
    }void aimove()
    {for (int i = 0; i < AI_NUM; i++){if (ai[i].flag){Chase(&ai[i], player);}}
    }
    
  1. 通过循环遍历 ai 数组。
  2. 检查每个元素的 flag 是否为真(即激活状态)。
  3. 如果是真,调用 Chase 函数,将 ai[i](追击者的位置)和 player(目标的位置)作为参数传递给 Chase
  4. 如果 chase->x(追击者的横坐标)小于 run.x(目标的横坐标),则将 chase->x 增加 2。
  5. 否则(即 chase->x 不小于 run.x),将 chase->x 减少 2。
  6. 同理,对于纵坐标 y,如果 chase->y 小于 run.y,则 chase->y 增加 2;否则 chase->y 减少 2
  • 缺乏边界检查:可按照如下的案例变动:
    #include <limits.h> // 引入INT_MAX以确保不会除以零void Chase(struct ball* chase, struct ball run) {int dx = run.x - chase->x; // 计算目标和追击者之间的横坐标差int dy = run.y - chase->y; // 计算目标和追击者之间的纵坐标差// 确保不会除以零if (dx == 0 && dy == 0) {return;}// 限制移动速度int max_speed = 5; // 假设最大移动速度为5int speed = fmin(abs(dx), abs(dy)) * (dx < 0 ? -1 : 1); // 计算实际移动速度speed = fmin(speed, max_speed); // 限制速度不超过最大值// 更新追击者的位置if (dx < 0) {chase->x += speed;} else if (dx > 0) {chase->x -= speed;}if (dy < 0) {chase->y += speed;} else if (dy > 0) {chase->y -= speed;}// 确保移动后的位置不会超出边界int max_x = INT_MAX; // 假设横坐标的最大值int max_y = INT_MAX; // 假设纵坐标的最大值int min_x = INT_MIN; // 假设横坐标的最小值int min_y = INT_MIN; // 假设纵坐标的最大值chase->x = fmin(fmax(chase->x, min_x), max_x);chase->y = fmin(fmax(chase->y, min_y), max_y);
    }void aimove() {for (int i = 0; i < AI_NUM; i++) {if (ai[i].flag) {Chase(&ai[i], player);// 这里可以添加其他逻辑,例如检查玩家是否在AI的视野范围内}}
    }

进入游戏绘制

  • 本质:在有了结构体 相当于有了对象,根据结构体对象,使其在画面上显示
  •  
    IMAGE map(MAP_WIDTH, MAP_HEIGHT);
    POINT cameraPos;void CameraUpdate()
    {cameraPos.x = player.x - WIN_WIDTH / 2;cameraPos.y = player.y - WIN_HEIGHT / 2;if (cameraPos.x < 0) cameraPos.x = 0;if (cameraPos.y < 0) cameraPos.y = 0;if (cameraPos.x > MAP_WIDTH - WIN_WIDTH) cameraPos.x = MAP_WIDTH - WIN_WIDTH;if (cameraPos.y > MAP_HEIGHT - WIN_HEIGHT) cameraPos.y = MAP_HEIGHT - WIN_HEIGHT;}void gamedraw() {BeginBatchDraw();SetWorkingImage(&map);//设置图像setbkcolor(WHITE);//设置背景颜色cleardevice();//准备工作//绘制图像//1.食物圆for (int i = 0; i< FOOD_NUM; i++) {if (food[i].flag) {setfillcolor(food[i].color);solidcircle(food[i].x, food[i].y, food[i].r);}}//2.ai圆for (int i = 0; i < AI_NUM; i++){if (ai[i].flag){setfillcolor(ai[i].color);solidcircle(ai[i].x, ai[i].y, ai[i].r);}}//3.玩儿家圆if (player.flag){setfillcolor(player.color);solidcircle(player.x, player.y, player.r);settextcolor(BLACK);setbkmode(TRANSPARENT);// 确保文件编码支持中文,并且项目设置使用MBCSouttextxy(player.x, player.y, "百年好合");}//显示页面SetWorkingImage();CameraUpdate();putimage(0, 0, WIN_WIDTH, WIN_HEIGHT, &map, cameraPos.x, cameraPos.y);EndBatchDraw();
    }

  • BeginBatchDraw();: 开始批处理绘图,这通常意味着接下来所有的绘图操作都将被累积起来,直到 EndBatchDraw 被调用时一次性渲染到屏幕上。

    SetWorkingImage(&map);: 设置当前工作图像为 map,这通常是游戏中的地图或者背景图像

  • setbkcolor(WHITE);: 设置背景颜色为白色。

  • cleardevice();: 清除设备,通常是清除屏幕,使其变为背景颜色。

  • 关注代码设计的核心逻辑  置于陌生的调用方法 了解能用即可

现在整个画面已经布局好 但是静态的  需要令其动起来

游戏运行  吃食物 根据按键动作

  • 吃食物:遇见食物的坐标自身的小球的半径扩大即可
  • double DisTance(struct ball b1, struct ball b2)
    {return sqrt((double)(b1.x - b2.x) * (b1.x - b2.x) + (b1.y - b2.y) * (b1.y - b2.y));
    }void eatfood() {//吃食物的逻辑  :  遇见食物就圆扩大for (int i = 0; i < FOOD_NUM; i++){if (food[i].flag && DisTance(player, food[i]) < player.r){food[i].flag = false;player.r += food[i].r / 4;}}
    }

    函数 DisTance

    DisTance 函数计算两个球体之间的欧几里得距离。这个函数对于游戏中的碰撞检测非常有用,比如判断玩家控制的球体是否接触到食物。函数的逻辑如下:

  • 接受两个 ball 类型的参数 b1 和 b2,代表两个球体的位置。
  • 返回两个球体之间的距离,使用欧几里得距离公式:(𝑥2−𝑥1)2+(𝑦2−𝑦1)2(x2​−x1​)2+(y2​−y1​)2​。

        函数 eatfood  函数处理玩家球体吃食物的逻辑。当玩家球体接触到食物时,球体会变大。函数的逻辑如下:

  • 遍历 food 数组,该数组包含多个食物元素。
  • 对于每个食物元素,检查它是否激活(food[i].flag 为 true)以及玩家球体与食物之间的距离是否小于玩家球体的半径(player.r)。
  • 如果条件满足,说明玩家球体已经吃到食物,将食物的 flag 设置为 false,表示食物已被吃掉。
  • 增加玩家球体的半径,增加的大小是食物半径的四分之一(food[i].r / 4)。这表示玩家球体在吃到食物后会变大。

游戏核心玩儿家运作

  • 实质:根据按键  玩家控制小球的移动
  • void playercontroller(int speed) {//根据按键玩家控制移动if (GetAsyncKeyState(VK_UP) && player.y - player.r >= 0){player.y -= speed;}if (GetAsyncKeyState(VK_DOWN) && player.y + player.r < MAP_HEIGHT){player.y += speed;}if (GetAsyncKeyState(VK_LEFT) && player.x - player.r >= 0){player.x -= speed;}if (GetAsyncKeyState(VK_RIGHT) && player.x + player.r < MAP_WIDTH){player.x += speed;}
    }

    使用了 GetAsyncKeyState 函数来检测按键的状态

  • if (GetAsyncKeyState(VK_UP) && player.y - player.r >= 0): 这行代码检查 ‘上’ 箭头键是否被按下,并且玩家的当前位置的 y 坐标减去其半径是否大于或等于 0。如果这两个条件都为真,意味着玩家不在屏幕底部,可以向上移动。

  • player.y -= speed;: 如果 ‘上’ 箭头键被按下,并且玩家不在屏幕底部,玩家的 y 坐标会减去速度值,这样玩家就会在游戏世界中向上移动。

  • if (GetAsyncKeyState(VK_UP) && player.y - player.r >= 0): 这行代码检查 ‘上’ 箭头键是否被按下,并且玩家的当前位置的 y 坐标减去其半径是否大于或等于 0。如果这两个条件都为真,意味着玩家不在屏幕底部,可以向上移动。

  • player.y -= speed;: 如果 ‘上’ 箭头键被按下,并且玩家不在屏幕底部,玩家的 y 坐标会减去速度值,这样玩家就会在游戏世界中向上移动。。。。。。。。。。

补充:

`CameraUpdate` 函数是用于更新游戏相机位置的函数。相机在游戏中的作用是让玩家能够看到游戏世界的一部分,通常是一个窗口(视口)。这个函数确保相机的位置始终在游戏世界的可视区域内。
以下是 `CameraUpdate` 函数的原理和逻辑:
1. `cameraPos.x = player.x - WIN_WIDTH / 2;`:计算相机在 x 轴的位置。相机的 x 坐标是玩家的 x 坐标减去屏幕宽度的一半。这样,相机就会在玩家正中间的位置。
2. `cameraPos.y = player.y - WIN_HEIGHT / 2;`:计算相机在 y 轴的位置。相机的 y 坐标是玩家的 y 坐标减去屏幕高度的一半。这样,相机就会在玩家正中间的位置。
3. `if (cameraPos.x < 0) cameraPos.x = 0;`:如果相机的位置在 x 轴上小于 0,则将相机的位置设置为 0。这意味着相机不能移动到游戏世界的左侧边界之外。
4. `if (cameraPos.y < 0) cameraPos.y = 0;`:如果相机的位置在 y 轴上小于 0,则将相机的位置设置为 0。这意味着相机不能移动到游戏世界的顶部边界之外。
5. `if (cameraPos.x > MAP_WIDTH - WIN_WIDTH) cameraPos.x = MAP_WIDTH - WIN_WIDTH;`:如果相机的位置在 x 轴上大于游戏世界的宽度减去屏幕宽度,则将相机的位置设置为游戏世界的右侧边界。
6. `if (cameraPos.y > MAP_HEIGHT - WIN_HEIGHT) cameraPos.y = MAP_HEIGHT - WIN_HEIGHT;`:如果相机的位置在 y 轴上大于游戏世界的高度减去屏幕高度,则将相机的位置设置为游戏世界的底部边界。
通过这些逻辑,`CameraUpdate` 函数确保相机始终在游戏世界的可视区域内,无论玩家在游戏世界中如何移动。这对于保持玩家在屏幕上的中心位置并显示游戏世界的重要部分非常重要。

全部代码:

//#include "contains.h"
#include <stdbool.h>
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<graphics.h>
#include <algorithm>
#include<math.h>
#include<mmsystem.h>
#include <iostream>
using namespace std;
#pragma comment(lib,"winmm.lib")#define WIN_WIDTH 1024 
#define WIN_HEIGHT 640
#define MAP_WIDTH  (WIN_WIDTH*5)
#define MAP_HEIGHT (WIN_HEIGHT*5)
#define FOOD_NUM 1000
#define AI_NUM 500struct ball
{int x;int y;int r;bool flag;DWORD color;
}player,food[FOOD_NUM],ai[AI_NUM];
/*结构体定*/IMAGE map(MAP_WIDTH, MAP_HEIGHT);
POINT cameraPos;void PlayBackgroundMusic() {// 使用相对路径打开音频文件,假设音频文件名为"example.mp3"if (mciSendString("open mus.mp3 alias BGM", NULL, 0, NULL) != 0) {std::cerr << "Failed to open the audio file." << std::endl;return;}// 播放音频并设置为循环播放if (mciSendString("play BGM repeat", NULL, 0, NULL) != 0) {std::cerr << "Failed to play the audio." << std::endl;mciSendString("close BGM", NULL, 0, NULL); // 如果播放失败,关闭文件return;}
}void gameinit() {PlayBackgroundMusic();//bgm//随机种子srand((unsigned)time(NULL));//角色player.x = rand() % MAP_WIDTH;player.y = rand() % MAP_HEIGHT;player.r = 15;player.flag = true;player.color = RGB(rand() % 256, rand() % 256, rand() % 256);//食物for (int i = 0; i < FOOD_NUM; i++){food[i].x = rand() % MAP_WIDTH;food[i].y = rand() % MAP_HEIGHT;food[i].r = rand() % 5 + 1;food[i].flag = true;food[i].color = RGB(rand() % 256, rand() % 256, rand() % 256);}//自由移动者for (int i = 0; i < AI_NUM; i++){ai[i].x = rand() % MAP_WIDTH;ai[i].y = rand() % MAP_HEIGHT;ai[i].r = rand() % 15 + 1;ai[i].flag = true;ai[i].color = RGB(rand() % 256, rand() % 256, rand() % 256);}
}void Chase(struct ball* chase,struct ball run)
{chase->x < run.x ? chase->x += 2 : chase->x -= 2;chase->y < run.y ? chase->y += 2 : chase->y -= 2;
}void aimove()
{for (int i = 0; i < AI_NUM; i++){if (ai[i].flag){Chase(&ai[i], player);}}
}void CameraUpdate()
{cameraPos.x = player.x - WIN_WIDTH / 2;cameraPos.y = player.y - WIN_HEIGHT / 2;if (cameraPos.x < 0) cameraPos.x = 0;if (cameraPos.y < 0) cameraPos.y = 0;if (cameraPos.x > MAP_WIDTH - WIN_WIDTH) cameraPos.x = MAP_WIDTH - WIN_WIDTH;if (cameraPos.y > MAP_HEIGHT - WIN_HEIGHT) cameraPos.y = MAP_HEIGHT - WIN_HEIGHT;}void gamedraw() {BeginBatchDraw();SetWorkingImage(&map);//设置图像setbkcolor(WHITE);//设置背景颜色cleardevice();//准备工作//绘制图像//1.食物圆for (int i = 0; i< FOOD_NUM; i++) {if (food[i].flag) {setfillcolor(food[i].color);solidcircle(food[i].x, food[i].y, food[i].r);}}//2.ai圆for (int i = 0; i < AI_NUM; i++){if (ai[i].flag){setfillcolor(ai[i].color);solidcircle(ai[i].x, ai[i].y, ai[i].r);}}//3.玩儿家圆if (player.flag){setfillcolor(player.color);solidcircle(player.x, player.y, player.r);settextcolor(BLACK);setbkmode(TRANSPARENT);// 确保文件编码支持中文,并且项目设置使用MBCSouttextxy(player.x, player.y, "百年好合");}//显示页面SetWorkingImage();CameraUpdate();putimage(0, 0, WIN_WIDTH, WIN_HEIGHT, &map, cameraPos.x, cameraPos.y);EndBatchDraw();
}double DisTance(struct ball b1, struct ball b2)
{return sqrt((double)(b1.x - b2.x) * (b1.x - b2.x) + (b1.y - b2.y) * (b1.y - b2.y));
}void eatfood() {//吃食物的逻辑  :  遇见食物就圆扩大for (int i = 0; i < FOOD_NUM; i++){if (food[i].flag && DisTance(player, food[i]) < player.r){food[i].flag = false;player.r += food[i].r / 4;}}
}void playercontroller(int speed) {//根据按键玩家控制移动if (GetAsyncKeyState(VK_UP) && player.y - player.r >= 0){player.y -= speed;}if (GetAsyncKeyState(VK_DOWN) && player.y + player.r < MAP_HEIGHT){player.y += speed;}if (GetAsyncKeyState(VK_LEFT) && player.x - player.r >= 0){player.x -= speed;}if (GetAsyncKeyState(VK_RIGHT) && player.x + player.r < MAP_WIDTH){player.x += speed;}
}int main() {initgraph(WIN_WIDTH, WIN_HEIGHT);//初始化界面gameinit();while (true) {aimove();gamedraw();eatfood();playercontroller(10);}/*getchar();*/return 0;
}

总结:至此简单实现的游戏完结,主要在于理清思路,拿此训练思维,孰能生巧。至于高级低级全看个人,随之成长日趋强键。

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

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

相关文章

有刷电机、无刷电机

阅读引言&#xff1a; 最近在备赛&#xff0c; 自己之前虽然用过电机&#xff0c; 但是发现在一些高要求的应用场景&#xff0c; 发现自己对电机的知识理解得不是很透彻&#xff0c; 所以写下这篇文章。 目录 一、 有刷电机内部原理 二、有刷电机一些关键参数 三、无刷电机内…

986: 哈夫曼译码

解法&#xff1a;先把代码粘贴到编译器&#xff08;vs&#xff09;上&#xff0c;分享一个一键去除空白行的操作&#xff0c;ctrlf调出查找窗口&#xff0c;输入查找(?<\r\n)\r\n&#xff0c;选择正则表达式&#xff0c;替换就可以发现会去掉一百多行空白行。 本题只需要利…

【c1】数据类型,运算符/循环,数组/指针,结构体,main参数,static/extern,typedef

文章目录 1.数据类型&#xff1a;编译器&#xff08;compiler&#xff09;与解释器&#xff08;interpreter&#xff09;&#xff0c;中文里的汉字和标点符号是两个字节&#xff0c;不能算一个字符&#xff08;单引号&#xff09;2.运算符/循环&#xff1a;sizeof/size_t3.数组…

6份不用辞职就能赚钱的副业,上班族必看!

在这个经济浪潮中&#xff0c;生活成本的上升与工资增长的缓慢形成了鲜明对比。对于许多上班族来说&#xff0c;寻找额外收入的途径显得尤为迫切。 今天&#xff0c;就让我们一起探索那些适合在业余时间开展的副业&#xff0c;为你的财务自由之路添砖加瓦。 1. 闲鱼二手手机售卖…

【镜像仿真篇】磁盘镜像仿真常见错误

【镜像仿真篇】磁盘镜像仿真常见错误 记系统镜像仿真常见错误集—【蘇小沐】 1、实验环境 2023AFS39.E01&#xff08;Windows11系统镜像&#xff09;Arsenal Image Mounter&#xff0c;[v3.10.262]‍Vmware Workstation 17 Pro&#xff0c;[v17.5.1]Windows 11 专业工作站版…

某盾BLACKBOX逆向关键点

需要准备的东西&#xff1a; 1、原JS码 2、AST解混淆码 3、token(来源于JSON) 一、原JS码很好获取&#xff0c;每次页面刷新&#xff0c;混淆的代码都会变&#xff0c;这是正常&#xff0c;以下为部分代码 while (Qooo0) {switch (Qooo0) {case 110 14 - 55: {function O0…

TensorFlow、pytorch和python对应的版本关系

安装深度学习框架的时候需要考虑版本的关系&#xff0c;不然装了用不了就尴尬了。 深度学习首先得问题就是用CPU跑&#xff0c;还是GPU跑。。当然有英伟达显卡的都想用GPU跑&#xff0c;不然买显卡是做啥、、GPU跑得多块&#xff0c;一下就训练完了。但是有的同学没得gpu&…

Fortinet的安全愿景SASO概述

FTNT SASE的独特方法&#xff0c;使其成为一家适应性极强的厂商&#xff0c;能够应对不断变化的网络和网络安全环境。FTNT开发了一种名为Secure Access Service Omni&#xff08;SASO&#xff09;的变体&#xff0c;以更准确地反映FTNT在融合网络和安全功能方面的实力。我们预计…

Linux内存管理——Swap

swap space 一个磁盘区域&#xff0c;作为内存使用。当系统内存不足时&#xff0c;会将一些很久不使用的数据转移到swap space中。 优点&#xff1a;扩展了内存空间 缺点&#xff1a;用磁盘做内存&#xff0c;读写效率降低。 swappiness swappiness的值表示建议swap space替…

Java线程池(更新中)

1.线程池介绍 顾名思义&#xff0c;线程池就是管理一系列线程的资源池&#xff0c;其提供了一种限制和管理线程资源的方式。每个线程池还维护一些基本统计信息&#xff0c;例如已完成任务的数量。 总结一下使用线程池的好处&#xff1a; 降低资源消耗。通过重复利用已创建的…

Mac 上安装多版本的 JDK 且实现 自由切换

背景 当前电脑上已经安装了 jdk8; 现在再安装 jdk17。 期望 完成 jdk17 的安装&#xff0c;并且完成 环境变量 的配置&#xff0c;实现自由切换。 前置补充知识 jdk 的安装路径 可以通过查看以下目录中的内容&#xff0c;确认当前已经安装的 jdk 版本。 cd /Library/Java/Java…

框架漏洞RCE-1

一、前提 1、命令执行漏洞&#xff1a;直接调用操作系统命令。攻击者构造恶意命令&#xff0c;将命令拼接到正常的输入中&#xff0c;达到恶意攻击的目的。 (1)、常见命令执行函数 PHP&#xff1a;exec、shell_exec、system、passthru、popen、proc_open、反引号等 ASP.N…

Linux(openEuler、CentOS8)企业内网DHCP服务器搭建(固定Mac获取指定IP)

----本实验环境为openEuler系统<以server方式安装>&#xff08;CentOS8基本一致&#xff0c;可参考本文&#xff09;---- 目录 一、知识点二、实验&#xff08;一&#xff09;为服务器配置网卡和IP&#xff08;二&#xff09;为服务器安装DHCP服务软件&#xff08;三&a…

leetcode-岛屿数量-99

题目要求 思路 1.使用广度优先遍历&#xff0c;将数组中所有为1的元素遍历一遍&#xff0c;遍历过程中使用递归&#xff0c;讲该元素的上下左右四个方向的元素值也置为0 2.统计一共执行过多少次&#xff0c;次数就是岛屿数量 代码实现 class Solution { public:int solve(vec…

ROS 2边学边练(45)-- 构建一个能动的机器人模型

前言 在上篇中我们搭建了一个机器人模型(其由各个关节&#xff08;joint&#xff09;和连杆&#xff08;link&#xff09;组成)&#xff0c;此篇我们会通过设置关节类型来实现机器人的活动。 在ROS中&#xff0c;关节一般有无限旋转&#xff08;continuous&#xff09;,有限旋转…

【工具推荐定制开发】一款轻量的批量web请求命令行工具支持全平台:hey,基本安装、配置、使用

背景 在开发 Web 应用的过程中&#xff0c;作为开发人员&#xff0c;为了确认接口的性能能够达到要求&#xff0c;我们往往需要一个接口压测工具&#xff0c;帮助我们快速地对我们所提供的 Web 服务发起批量请求。在接口联调的过程中&#xff0c;我们通常会用 Postman 等图形化…

python 中如何匹配字符串

python 中如何匹配字符串&#xff1f; 1. re.match 尝试从字符串的起始位置匹配一个模式&#xff0c;如果不是起始位置匹配成功的话&#xff0c;match()就返回none。 import re line"this hdr-biz 123 model server 456" patternr"123" matchObj re.matc…

unity制作app(2)--主界面

1.先跳转过来&#xff0c;做一个空壳&#xff01;新增场景main为4号场景&#xff01; 2.登录成功跳转到四号场景&#xff01; 2.在main场景中新建canvas&#xff0c;不同的状态计划用不同的panel来设计&#xff01; 增加canvas和底图image 3.突然输不出来中文了&#xff0c;浪…

新手做抖音小店,卖什么最容易出单?抖音必爆类目来了!

哈喽&#xff01;我是电商月月 新手做抖音小店没有经验&#xff0c;也不了解市场需求&#xff0c;最好奇的就是&#xff1a;卖什么商品最容易出单&#xff0c;还在犹豫的朋友可以看看这五种类目&#xff0c;在2024年下半年必定火爆一次 一&#xff0e;生活电器类 天气炎热&a…

Objective-C的对象复制与拷贝选项

对象复制与拷贝 文章目录 对象复制与拷贝copy与mutablecopycopy与mutablecopy的简介示例&#xff1a;不可变对象的复制可变对象的复制 NSCopying和NSMutableCopying协议深复刻和浅复刻浅拷贝&#xff08;Shallow Copy&#xff09;&#xff1a;深拷贝&#xff08;Deep Copy&…