【再识C进阶2(下)】详细介绍指针的进阶——利用冒泡排序算法模拟实现qsort函数,以及一下习题和指针笔试题

前言

💓作者简介: 加油,旭杏,目前大二,正在学习C++数据结构等👀
💓作者主页:加油,旭杏的主页👀

⏩本文收录在:再识C进阶的专栏👀

🚚代码仓库:旭日东升 1👀

🌹欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖

学习目标:

       在上一篇博客中,我们学习了回调函数,以及怎么使用qsort函数去排序;那么在这一篇博客中,我们来更加详细地学习qsort函数内部是怎么进行排序的,以及想要用冒泡排序去模拟实现一下可以排序任意类型数据,最后我们要进行做题来巩固一下所学的知识点。


学习内容:

通过上面的学习目标,我们可以列出要学习的内容:

  1. 学习qsort函数内部是怎么进行排序的
  2. 用冒泡排序去模拟实现一下可以排序任意类型数据
  3. 做题巩固一下知识点
  4. 指针的笔试题

一、学习qsort函数内部是怎么进行排序的

       在学习qsort函数内部是如何实现的,我们需要在认真地将qsort函数的形参进行一遍分析,因为其形参的个数实在是太多了,这就和数学公式一样,要记住每一个字母表示的含义,这样我们才能用的对。 

1.1 qsort函数中的四个参数的意思

        这时,我们需要打开cplusplus的网站,进行搜索qsort函数,如下图所示:接下来一一分析:

1.1.1 void* base

       首先,映入我们眼帘的是其类型:void*

这个类型还是很重要的,void* 类型要记住一些要点:

  1. void* 创建的变量可以接收任意类型的指针变量;
  2. void* 创建的变量不能进行加减整数的运算,因为不知道类型,无法确定加减整数后,指针移动几位字节。

之后,这个参数存放的是待排序数组的第一个元素的地址

1.1.2  size_t num

       这个形参的类型可能读者也没有见过,简单地理解其就是无符号整形。我们在VS转到定义时,可以看到是用 typedef 定义的,如下图:

这个参数存放的是待排序数组的元素个数

1.1.3 size_t size

       因为qsort函数可以排序任意类型的数据,但是任意类型数据的大小是不同,我们需要将我们所要排序的数据类型传过去,让qsort函数知道数据的大小,好进行排序。

这个参数存放的是待排序数组中一个元素的大小

1.1.4 int (*comper) (const void*, const void*)

       这个参数就是我们上一篇博客所学习的函数指针,qsort函数需要我们自己写一个比较函数,因为qsort函数默认是升序排序的,这个要知道,不然容易迷。

       这个比较函数所返回的值与0进行比较,会产生不同的结果,如果大于0即成立,交换两个数,如果小于0即不成立,不交换。如下图:(如果图里的情况看不懂,我们可以看下面的代码)

int compare(const void* e1, const void* e2)
{int* p1 = e1;int* p2 = e2;if (*p1 > *p2){return 1;}else if (*p1 == *p2){return 0;}else if (*p1 < *p2){return -1;}
}

 这个参数的含义是:函数指针——cmp指向了一个函数,这个函数是用来比较两个元素的

1.2 引出qsort函数内部算法的思想

       qsort函数内部是用快速算法进行实现,因为我的算法博客也更新到这一篇,我就直接在这一篇上写了,快速算法肯定不同于我们之前所学的归并排序堆排序,我们先用一个简单的题目引出类似于快速排序算法的思路。

1.2.1 将数组分为两个区域的问题

       在讲述荷兰国旗问题之前,我们先来看一个更简单的题,来帮助我们进入荷兰国旗问题的思路。 

题目:

       给定一个数组arr,和一个数num,请把数组中小于等于num的数放在数组的左边,把数组中大于num的数放在数组的右边,无需排序。要求空间复杂度为:O(1),时间复杂度为:O(N)

思路:

       我们可以将数组分为两个区一个是<=num区一个是>num区。我们可以先将<=num区放在数组首元素的前一个位置,将指针指向数组中第一个元素与num进行比较。

       如果第一个元素比num小于或者等于,<=num区往前加一,将指针加一,继续与num进行比较;

       如果指针指向的元素比num大,<=num区不动,指针向后移动,直到比num小停止,将这个数与<=num区的后面一个数进行交换即可。循环整个过程,直到指针指向空。

代码:

void swap(int arr[], int a, int b)
{int temp = arr[a];arr[a] = arr[b];arr[b] = temp;
}void prather(int* arr, int left, int right, int num)
{int less = left - 1;int index = left;while (index <= right){if (arr[index] <= num){swap(arr, ++less, index++);}else{index++;}}
}int main()
{int arr[10] = { 2,3,6,3,4,7,9,0,2,3 };int left = 0;int right = 9;int num = 3;prather(arr, left, right, num);for (int i = 0; i < 10; i++){printf("%d ", arr[i]);}return 0;
}

       这个问题的关键主要就是将数组分为两个部分,而荷兰国旗问题是将数组分为三个部分,是属于其的进阶。 将数组分为两个区域,我们用了一个作用界限;而将数组分为三个区域是否需要两个作用界限呢?

1.2.2 荷兰国旗问题

题目:

       牛牛今天带来了一排气球,气球有n个,然后每一个气球里面都包含一个数字,牛牛是一个善于思考的人,于是他就想到了一个问题,牛牛随便给你一个值K,这个值在这些气球中不一定存在,聪明的你需要把气球中包含的数字是小于K的放到这排气球的左边,大于K的放到气球的右边,等于K的放到这排气球的中间,最终返回一个整数数组,其中只有两个值,分别是气球中包含的数字等于K的部分的左右两个下标值,如果气球中没有K这个数字就输出-1,-1。

思路:

       这个就相当于将一个数组通过所给的数字分为三个区一个是<num区一个是==num区一个是>num区。我们需要将<num区,和>num区同时卡住,这样剩下就是==num区的内容,于是思路就有了。

       定义两个指针变量,一个指针变量 less 指向数组首元素的前一个位置,一个指针变量 more 指向数组最后一个元素的后一个位置,用一个索引index指向数组的首元素。

       如果index指向的元素小于num,则less++,交换less++的元素与index指向的元素,然后index++

       如果index指向的数组元素等于num,则index++

       如果index指向的元素大于num,则交换index与more--的元素,index不++

代码:

#include <stdio.h>
#include <malloc.h>
void swap(int arr[], int a, int b) {int temp = arr[a];arr[a] = arr[b];arr[b] = temp;
}void Nether(int arr[], int left, int right, int k) {int less = left - 1;int more = right + 1;int index = left;while (index < more) {if (arr[index] < k)swap(arr, ++less, index++);else if (arr[index] > k)swap(arr, --more, index);elseindex++;}if (less + 1 <= more - 1) {int* ret = (int*)malloc(sizeof(int) * 2);ret[0] = less + 1;ret[1] = more - 1;for (int i = 0; i < 2; i++) {printf("%d ", ret[i]);}}else{for(int i = 0; i < 2; i++){printf("-1 ");}}
}int main() {int n = 0, k = 0;int arr[100010];scanf("%d %d", &n, &k);for (int i = 0; i < n; ++i) {scanf("%d ", &arr[i]);}int left = 0;int right = n - 1;Nether(arr, left, right, k);return 0;
}

1.3 qsort函数内部是怎么实现的

       在 1.2 中,学习了荷兰国旗问题的思路,其实qsort函数内部算法的实现是快速排序,而快速排序在发展中,有三个阶段:分别是快速排序 1.0快速排序 2.0 快速排序 3.0

1.3.1 快速排序 1.0

       在这个版本下,快速排序的思路与荷兰国旗问题的思路基本相同,不过不一样的地方是荷兰国旗问题比较的是随机给的数字,而快速排序比较的一定是数组中的数字

       不买关子了,快速排序的算法思路是:将数组最后一个元素作为比较数,将数组分为:<=比较数区,>比较数区,这样就将最后一个元素排好序了,其数组下标记作p;将数组分为左右两个部分,利用递归再比较p-1和p+1的元素,将他们排好序,递归下去,就可以将数组中的元素排好序。

       但这个思路有一个比较明显的不足,不足之处在于数组中有连续相等的数字,但也会损失一下常数时间,所以在快速排序 2.0 版本下,将数组分为三个区:<比较数区,==比较数区,>比较数区。这样就能节省一些常数时间。       

1.3.2 快速排序 2.0 

       在快速排序 1.0 中,已经提出了快速排序 2.0 的思路了,也就比快速排序 1.0优化了一下常数时间。不过这两种方式都不是最优的,如果我们考虑最差情况,在最差情况中,两者的时间复杂度接近于O(N*N),与冒泡排序没什么两样,为什么呢?

       原因如下:因为如果数组元素中最后一个数排序过后不在中间的位置,那么时间复杂度就会增加,如果说如果每一次要排序元素在正中间的话是最好的情况!不过不太可能,所以在这两个版本下,时间复杂度都会增加

1.3.3 快速排序 3.0

       快速排序 3.0中,我们不同于以往将数组的最后一个元素进行比较,而是将随机匹配一个数组元素进行比较根据概论发现,如果数组有N个元素,那么每一个元素被选中的概率是1/N。经过数学计算后,会发现这个排序的时间复杂度为:O(N*logN),达到了我们的目的

1.3.4 快速排序的代码

#include <stdlib.h>
#include <string.h>
#include <malloc.h>
void swap(int arr[], int a, int b) //交换函数
{int temp = arr[a];arr[a] = arr[b];arr[b] = temp;
}
int* partion(int arr[], int left, int right); //函数声明,不要忘记
void quicksort(int arr[], int left, int right)
{if (left < right) //递归终止条件{//先随机选一个元素与数组中最后一个元素进行交换,然后进行partionint num = rand() % (right - left + 1); //产生0~sz的任意数字swap(arr, left + num, right);int* ans = (int*)malloc(sizeof(int) * 2);ans = partion(arr, left, right);quicksort(arr, left, ans[0] - 1); //<arr[num]区quicksort(arr, ans[1] + 1, right);//>arr[num]区}
}
int* partion(int arr[], int left, int right)
{int less = left - 1;int more = right + 1;int index = left;int* ret = (int*)malloc(sizeof(int) * 2);while (index < more){if (arr[index] < arr[right]){swap(arr, ++less, index++);}else if (arr[index] > arr[right]){swap(arr, --more, index);}elseindex++;}ret[0] = less + 1;ret[1] = more - 1;return ret;
}

二、用冒泡排序去模拟实现一下可以排序任意类型数据

       在模拟实现冒泡排序前,先来熟悉一下冒泡排序的思想,记住,知识点要进行大量的重复记忆,这样才能会用

2.1 冒泡排序的思路

       冒泡排序,冒泡排序,就是让最大的数像重物一样沉到底部两两进行比较,然后进行交换位置,这就是基本思路。

       第一次将最大的数字沉到底部第二次将次大的数字沉到底部第三次……是一个循环将最大数从大到小依次沉到底部,所以我们来实现一下代码:

void bubble_sort(int arr[], int sz)
{for (int i = 0; i < sz - 1; ++i){for (int j = 0; j < sz - 1 - i; ++j){if (arr[j] > arr[j + 1]){swap(arr, j, j + 1);}}}
}

2.2 模拟实现任何类型的冒泡排序

       而单一的冒泡排序用法实在是太少了,在学习了qsort函数之后,我们就是能将冒泡排序也改造为能够排任意类型的数据呢?那么接下来,我们就要朝着这个方向进行:

2.2.1 实现任何类型的冒泡排序的主体

2.2.1.1 参数部分

       因为不同类型的数据进行比较时,他们的比较方法是不同,如果我们想要比较不同类型的数据还要写不同的比较方法,这样函数就变多了,这样就不是一个函数了,所以我们将这个比较方法抽离出来,让用户自己写。因为不同类型的数据的大小不同,所以用户要传入要排序数据的大小,还要传入要排序数据的多少。因此,此函数与qsort函数的参数类似。

void bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void*, const void*))

 2.2.1.2 主体部分

       在参数部分中,有一个函数指针,函数主体的大体部分是一样的,我们只需要改变一下比较条件,类似于qsort函数的条件,默认是一个是升序排序

void bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void*, const void*))
{for (int i = 0; i < num - 1; ++i){for (int j = 0; j < num - 1 - i; ++j){if (cmp((char* )base + j * size, (char*)base + (j + 1) * size) > 0){swap((char*)base + j * size, (char*)base + (j + 1) * size, size);}}}
}

2.2.2 实现任何类型的冒泡排序的交换部分

       不同数据类型的数据大小也是不一样的,因为数据类型大小的基本单位是字节,所以我们用char*指针来接收(char类型的大小为1个字节)。我们不能将这个数据进行交换,我们只能一个字节一个字节的交换

void swap(char* a, char* b, size_t size)
{for (int i = 0; i < size; ++i){char temp = *a;*a = *b;*b = temp;a++;b++;}
}

三、一些有关 sizeof 和 strlen 的题目

       这个题目有点多,可能会有点费时间,但是主要是将括号内的东西理解清楚是什么?我们来一一进行: 

3.1 sizeof 类型

//一维数组
int a[] = { 1,2,3,4 };            //答案  
printf("%d\n", sizeof(a));        //16  
printf("%d\n", sizeof(a + 0));    //4/8
printf("%d\n", sizeof(*a));       //4
printf("%d\n", sizeof(a + 1));    //4/8
printf("%d\n", sizeof(a[1]));     //4
printf("%d\n", sizeof(&a));       //4/8
printf("%d\n", sizeof(*&a));      //16
printf("%d\n", sizeof(&a + 1));   //4/8
printf("%d\n", sizeof(&a[0]));    //4/8
printf("%d\n", sizeof(&a[0] + 1));//4/8

解析: 

1. 数组名在一般情况下,表示的数组首元素的地址,而数组名单独放在sizeof内部,数组名表示整个数组,计算的是整个数组的大小,单位是字节。

2. 数组名未单独出现,也没有&,所以数组名表示的是首元素的地址,a + 0还是首元素的地址,是一个指针,大小是4/8个字节。

3. 数组名未单独出现,也没有&,所以数组名表示的是首元素的地址,解引用表示数组的首元素,大小为4个字节。

4. 与2类似。

5. 表示的是数组中第一个元素,其大小为4个字节。

6. &数组名取出的数组的地址,还是一个指针,大小为4/8个字节。

7. 解引用与取地址可以互相抵消,则其表示的是数组,计算的是数组的大小,为16个字节。

8. 与6类似,&数组名取出的是数组的地址,加1表示跳过一个数组大小,指向跳过一个数组的大小的地方。

9. &a[0]表示的是取出数组首元素的地址,是一个指针,大小为4/8个字节。

10. 与9类似,还是一个地址,大小为4/8个字节。

//字符数组
char arr[] = {'a','b','c','d','e','f'};    //答案
printf("%d\n", sizeof(arr));               //6
printf("%d\n", sizeof(arr + 0));           //4/8
printf("%d\n", sizeof(*arr));              //1
printf("%d\n", sizeof(arr[1]));            //1
printf("%d\n", sizeof(&arr));              //4/8
printf("%d\n", sizeof(&arr + 1));          //4/8
printf("%d\n", sizeof(&arr[0] + 1));       //4/8

 3.2 strlen 类型 

//字符数组
char arr[] = {'a','b','c','d','e','f'};    //答案
printf("%d\n", strlen(arr));               //随机值
printf("%d\n", strlen(arr + 0));           //随机值
printf("%d\n", strlen(*arr));              //error
printf("%d\n", strlen(arr[1]));            //error
printf("%d\n", strlen(&arr));              //随机值
printf("%d\n", strlen(&arr + 1));          //随机值-6
printf("%d\n", strlen(&arr[0] + 1));       //随机值-1

解析:

1. strlen计算的字符串的长度,要有'\0'的结束标志,由于这个字符数组中没有'\0'的结束标志,所以是随机值。

2. 同理,也是随机值。

3. *arr表示的是一个字符,但在strlen中,只能是地址,所以strlen认为这是一个非法地址,会报错。

4. 与3同理

5. &arr尽管取出的是数组的地址,但是在数值上与数组首元素的地址是一样,所以也是随机值。

6和7与5类似。

3.3 总结

再来复习一下数组名的意义:

  1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小;
  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址;
  3. 除此之外,所有的数组名都表示首元素的地址。

学习产出:

  1. 学习qsort函数内部是怎么进行排序的
  2. 用冒泡排序去模拟实现一下可以排序任意类型数据
  3. 做题巩固一下知识点
  4. 指针的笔试题

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

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

相关文章

探究Nginx应用场景

1 静态资源 Nginx是一个流行的Web服务器和反向代理服务器&#xff0c;它可以用于托管静态资源。下面是一个简单的案例&#xff0c;展示了如何使用Nginx来提供静态资源。 假设你有一个名为example.com的域名&#xff0c;并且你希望使用Nginx来托管位于/var/www/html目录下的静…

CopyOnWriteArrayList源码分析

其中唯一的线程安全 List 实现就是 CopyOnWriteArrayList。 特点 由于读取操作不会对原有数据进行修改&#xff0c;因此&#xff0c;对于每次读取都进行加锁其实是一种资源浪费。相比之下&#xff0c;我们应该允许多个线程同时访问 List 的内部数据&#xff0c;毕竟对于读取操…

MeterSphere压测,出现HttpHostConnectException

现象&#xff1a;MeterSphere更换压力机后&#xff0c;压测出现出现HttpHostConnectException 解决方案&#xff1a; net.ipv4.tcp_tw_reuse默认是0或者2&#xff0c;更改为1 net.ipv4.tcp_tw_reuse&#xff0c;表示是否允许重新应用处于TIME-WAIT状态的socket用于新的TCP连…

时序分解 | MATLAB实现基于SSA奇异谱分析的信号分解分量可视化

时序分解 | MATLAB实现基于LMD局部均值分解的信号分解分量可视化 目录 时序分解 | MATLAB实现基于LMD局部均值分解的信号分解分量可视化效果一览基本介绍程序设计参考资料 效果一览 基本介绍 奇异谱分解奇异谱分析SSA 可直接替换txt数据运行 Matlab 1.包含3D分解效果图 频谱图等…

Linux UDP编程流程

文章目录 UDP编程流程UDP协议无连接的特点UDP协议数据报的特点 UDP编程流程 UDP 提供的是无连接、不可靠的、数据报服务。服务器端和客户端没有什么本质上的区别。编程流程如下&#xff1a; socket()用来创建套接字&#xff0c;使用 udp 协议时&#xff0c;选择数据报服务 SOC…

Spring基础(2w字---学习总结版)

目录 一、Spirng概括 1、什么是Spring 2、什么是容器 3、什么是IoC 4、模拟实现IoC 4.1、传统的对象创建开发 5、理解IoC容器 6、DI概括 二、创建Spring项目 1、创建spring项目 2、Bean对象 2.1、创建Bean对象 2.2、存储Bean对象&#xff08;将Bean对象注册到容器…

IMU+摄像头实现无标记运动捕捉

惯性传感和计算机视觉的进步为在临床和自然环境中获得精准数据带来了新可能。然而在临床应用时需要仔细地将传感器与身体对齐&#xff0c;这减慢了数据收集过程。 随着无标记运动捕捉的发展&#xff0c;研究者们提出了一个新的深度学习模型&#xff0c;利用来自视觉、惯性传感…

金和OA GetSqlData.aspx 远程命令执行漏洞

一、漏洞简介 金和OA协同办公管理系统C6软件共有20多个应用模块,160多个应用子模块,涉及的企业管理业务包括协同办公管理、人力资源管理、项目管理、客户关系管理、企业目标管理、费用管理等多个业务范围,从功能型的协同办公平台上升到管理型协同管理平台,并不断的更新完善…

每日刷题-5

目录 一、选择题 二、算法题 1、不要二 2、把字符串转换成整数 一、选择题 1、 解析&#xff1a;printf(格式化串&#xff0c;参数1&#xff0c;参数2,.….)&#xff0c;格式化串: printf第一个参数之后的参数要按照什么格式打印&#xff0c;比如%d--->按照整形方式打印&am…

2023备战秋招Java面试八股文合集

Java就业大环境仍然根基稳定&#xff0c;市场上有很多机会&#xff0c;技术好的人前景就好&#xff0c;就看你有多大本事了。小编得到了一份很不错的资源&#xff0c;建议大家可以认真地来看看以下的资料&#xff0c;来提升一下自己的核心竞争力&#xff0c;在面试中轻松应对面…

HTTPS协议

文章目录 HTTPS协议1. HTTPS协议介绍2. 概念准备2.1 什么是"加密"2.2 为什么要加密2.3 常见的加密方式(1) 对称加密(2) 非对称加密 2.4 数据摘要 && 数据指纹(1) 应用场景 3. HTTPS理解链3.1 方案 1 - 只使用对称加密3.2 方案 2 - 只使用非对称加密3.3 方案 …

SeaArt.ai: 海艺AI绘画艺术图片模型创作平台

【产品介绍】 • 名称 SeaArt.ai • 具体描述 SeaArt.ai是一个基于人工智能技术的AI绘画工具&#xff0c;它可以根据你的描述或者关键词来生成符合你想象的图片。你可以选择不同的模式来创建不同类型的图片&#xff0c;比如人物、风景、建筑、神话、自…

投稿指南【NO.12_8】【极易投中】核心期刊投稿(组合机床与自动化加工技术)

近期有不少同学咨询投稿期刊的问题&#xff0c;大部分院校的研究生都有发学术论文的要求&#xff0c;少部分要求高的甚至需要SCI或者多篇核心期刊论文才可以毕业&#xff0c;但是核心期刊要求论文质量高且审稿周期长&#xff0c;所以本博客梳理一些计算机特别是人工智能相关的期…

Swift 新并发模型中 isolated 和 nonisolated 关键字的含义看这篇就懂了!

概览 在 Swift 新 async/await 并发模型中&#xff0c;我们可以利用 Actor 来避免并发同步时的数据竞争&#xff0c;并从语义上简化代码。 Actor 伴随着两个独特关键字&#xff1a;isolated 和 nonisolated&#xff0c;弄懂它们的含义、合理合规的使用它们是完美实现同步的必…

SpringMVC文件的上传下载JRebel的使用

目录 前言 一、JRebel的使用 1.IDea内安装插件 2.激活 3.离线使用 使用JRebel的优势 二、文件上传与下载 1 .导入pom依赖 2.配置文件上传解析器 3.数据表 4.配置文件 5.前端jsp页面 6.controller层 7.测试结果 前言 当涉及到Web应用程序的开发时&…

只依赖OPENCV的工作服安全帽检测YOLOV8S

工地安全帽工作服检测Y8S&#xff0c;采用YOLOV8S训练模型&#xff0c;然后使用OPENCV的DNN调用&#xff0c;彻底拜托PYTORCH依赖&#xff0c;可以在C,PYTHON,ANDROID上跑。附件是C生成的效果测试&#xff08;只需解压将图片或者视频放入VIDEOS文件夹&#xff0c;文件夹没图片或…

【UE 材质】制作飘动的旗帜

效果 步骤 1. 首先在建模软件中创建一个平面&#xff0c;注意分段数一定要多 2. 在UE中创建一个材质&#xff0c;这里命名为“Mat_Flag” 打开“Mat_Flag”&#xff0c;先将旗帜纹理连接到基础颜色 先选中导入的模型然后点击根据选中的模型预览材质 创建如下节点可以看到此时模…

如何让报表会说话?学会这一招就可以了

Smartbi电子表格软件进化到全新阶段&#xff0c;这轮更新最能吸引人的地方是什么呢&#xff1f;跟随小麦一起来感受下。 1、新增语音播放特性&#xff0c;让报表会说话 Smartbi电子表格软件最新上线超级实用的新特性——语音播报&#xff0c;即让报表自己开口说话~ 设想你打开…

matplotlib绘图

&#x1f433; 我正在和鲸社区参加“商业数据分析训练营活动” https://www.heywhale.com/home/competition/6487de6649463ee38dbaf58b &#xff0c;以下是我的学习笔记&#xff1a; 学习主题&#xff1a;matplotlib绘图 日期&#xff1a;2023.9.13 关键概念/知识点&#xf…

TikTok的生态封闭:如何应对新的商业格局

TikTok&#xff08;抖音国际版&#xff09;已经成为全球范围内最受欢迎的社交媒体平台之一&#xff0c;拥有数以亿计的活跃用户。然而&#xff0c;最近TikTok宣布了一项重大战略调整&#xff0c;即开始构建生态封闭&#xff08;walled garden&#xff09;模式。 这一变化将对出…