ObjectiveC-03-XCode的使用和基础数据类型

本节做为Objective-C的入门课程,笔者会从零基础开始介绍这种程序设计语言的各个方面。

术语

  • ObjeC:Objective-C的简称,因为完整的名称过长,后续会经缩写来代替;
  • 项目/工程:也称工程,指的是一个App的源文件的文件夹包和结构,有时也称为工程或项目工程。
  • 项目模板:不只ObjC,用不同编程语言开发App时其源码都会包含源文件、集成框架、配置文件、资源图片等,这些元素需要放在同一个文件中并要符合某种规则,而针对不同类型的App其元素和组织又不一样,这个工作比较费时,所以为了效率考虑一般会经一个母版进行修改,这个母版就称为项目模板;

Command Line Tool工程

首先,我们需要创建一个Command line tool工程项目(即不带图形化界面的项目)。
在这里插入图片描述
在上述工程创建界面上有很多模板项目,可以按需要选择相应的模板开发,这样省去了好多搭建框架的时间,但也可以选择从空项目开始。多数模板可以从字面意思就可以了解。

  • 现在我们只需要知道Command Line Tool工程模板就足够了(一种无UI界面的可在命令行执行的脚本工程模板);
  • 后续在涉及AppKit之前的所有代码我们全会以这类工程为载体演示代码,其它的工程模板在讲到其内容时再详细解释。

创建HelloWorld工程

按照惯例,我们还是以一个Hello Word项目做为开始,了解一下ObjC(ObjectiveC简称)项目结构和基础语法。项目名称暂时称为helloWorld,项目设置采用默认即可,不需要做任何改变,项目结构如下:

在这里插入图片描述

打开main.m文件,我们所有的测试代码暂时全写在这里面,.m是ObjC代码文件的后缀(.c是C语言的源码文件),运行时会交由程序编译器LLVM处理和运行。

//
//  main.m
//  helloWorld
//
//  Created by 刘东 on 2023/12/20.
//

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool { // 自动释放池,由系统来管理变量的内存
        // insert code here...
        NSLog(@"Hello, World!"); // 打包日志函数,用@修饰表示NSString对象
    }
    return 0;  // 规定 0 表示程序正常结束,其它值都是非正常结束
}

除了上述.m文件外还可以兼容几下以类源码文件:

  1. .c:C文件
  2. .cc, .cpp:C++文件
  3. .h:头文件
  4. .mm:Objective-C++文件,可以同时使用ObjectiveC和C++语法;
  5. .pl:Perl文件
  6. .o:ObjectiveC编译后的文件
  7. .m:Objective-c文件

代码注释

在ObjC中只有两种注释:

  • //:行注释,一般用于代码行或代码后面;
  • /* */:块注释,一般用于类说明,方法说明或代码块说明使用;
  • #pragma mark *** String funnel methods ***,这是一种特殊格式的指令,也能起到注释的作用,详细可参考代码导航一节;
// Secondary text that may be displayed

/*
 Secondary text that may be displayed adjacent to or below the primary title depending on the configuration of the window.
 A value of empty string will remove the subtitle from the window layout.
*/

模块导入

格式为:#import <Foundation/Foundation.h> 注意最后面没有;分号,表示为当前类的实现添加相关的模块依赖。如果导入的是自定义的实现,则需要用双引号(本地)替换<>(系统)。

#import <Foundation/Foundation.h>
#import "Fraction.h" 

上述所谓的系统其实称为框架更合适,比如Foundation、AppKit框架,每个框架都有一个主头文件,它包含了框架内所有的头文件,只需导入一次就可以使用此框架内所有的功能,这样就省去了一个个导入的麻烦。

MAC OS所有框架的目录位于 /System/Library/Frameworks 目录下。

入口函数main

程序运行的主入口函数,格式为:int main(int argc, const char * argv[]),程序的执行入口和java的main函数功能相同,每个App最多只能允许存在一个main函数。一般练习时用main函数调用就行,如果是大型项目ObjC也提供了专门的单元测试框架,后续会讲到,main.m语法格式如下:

static void method(){
}

/*
argc:命令行输入的参数个数
argcv:字符指针数组,即参数值
*/
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //代码位置
    }
    return 0;
}

运行传参

函数说明:int main(int argc, const char * argv[])

  • argc:命令行输入的参数个数
  • argcv:字符指针数组,即参数值,argcv[0]指向一个函数,argcv[1]为一个字符数组
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        struct entry dict[100] = {
            {"abyss", "a bottomless pit"},
            {"addle", "to become confused"}
        };
        
        int entries = 10;
        int entryNumber;
        int lookup(struct entry dict[], char search[], int entries);
        
        if (argc!=2){
            NSLog(@"no word typed on the command line.");
            return 1;
        }
        
        entryNumber = lookup(dict, argv[1], entries);
        if(entryNumber!=-1){
            NSLog(@"%s", dict[entryNumber].definition);
        }
    }
    return 0;
}

调用方法如下,可从命令行,也可从Xcode中执行

clang -fobjc -arc main.m lookup abyss - 

上述程序会调用函数lookup,然后在dict字典中查找argv[1]中的单词,如果找到就返回详细的解释。

添加函数

在main函数所在的类中也可以添加自定义的方法,但方法的命名方式和ObjC的语法有很大不同,这一点需要额外注意。在main中定义的方法是C语言的语法。

#import <Foundation/Foundation.h>

//无参方法,在方法前面也可以添加static关键字
void nsRangeTest(){
    NSRange range1 = {17, 4};
}

//有参方法
NSComparisonResult *compareArray(id element, id compareEle){
    return [[compareEle name] compare: [element name]];
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        nsRangeTest();
    }
    return 0;
}

打印函数NSLog

打印函数,相当于C语言或JAVA语言的print()函数,这里需要注意写法,一定是以@开头,这也说明了NSLog函数的入参是一个NSString类型的对象(在ObjC语言中,字符串用@""表示),另外所有的Cocoa函数和对象全部以NS做为命名前缀(也被称命名空间),也有一些老的API是采用CF开头的但不建议使用了。

NSLog(@"Hello, World!");
int sum = 20+25;
NSLog(@"The sum is %i", sum); //NSLog函数如果发现%,则视为占位符,这样的占位符有很多,后续会讲到

也可以用printf()函数来代码,但不是太建议,因为NSLog添加了很多格式化的信息,注意看下面代码的输出

        NSLog(@"Hello, World!\n");
        printf("Hello, World!\n");
        
        ~~~~
        2024-03-26 19:31:52.091265+0800 helloWorld[46546:5251675] Hello, World!
        Hello, World!

键盘输入

scanf()函数也可以使用占位符,因为键盘接收的原始数据全是字符串,在程序中需要做一些类型转换工作。下在程序运行后在scanf处会卡住,然后在控制台输入相应字符后就会往下执行了。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age;
        scanf("%i", &age); //注意&的用法,它表示一个指针引用,
        NSLog(@"age is %i", age);
    }
    return 0;
}

/*~~
aaa
2024-03-27 14:39:56.103878+0800 objcBase[59017:6048518] age is 32759
Program ended with exit code: 0
*/

程序运行

除了使用Xcode工具运行,还可以使用命令行工具执行。其命令如下,其中prog1为重命名的项目名称。但这种方式并友好,因为还在设置PATH等资源目录。

(base) MacBook:~ liudong$ clang -fobjc-arc main.m -o prog1

(base) MacBook:~ liudong$ clang -fobjc-arc -framework Foundation main.m -o prog1

输入类似下面的界面:
在这里插入图片描述
用NSLog输出时,上面的4596表示当前应用的进程ID号。另外编译好的文件一般会存放在以下目录中,其中helloWorld-ghwqfuwptpnvhtfpzqoxxzcbnzow是一串随机值。不同版本的OS操作系统存放的位置有可能不太一样。

~/Library/Developer/Xcode/DerivedData/helloWorld-ghwqfuwptpnvhtfpzqoxxzcbnzow/Build/Products/Debug

进入这个目录下可以执行./helloWorld就可以直接运行应用了。
在这里插入图片描述

程序中止

有两种方式一种是在main函数中中止,一种是在方法中强制中止,在main函数中直接使用retrun 0即可,则在方法中需要使用以下代码。

exit(0);

设置XCode

首先来讲,苹果公司通常喜欢在不同版本的XCode中增加或移动一些功能,而且这些功能对开发代码的效率是非常高的。下面就以XCode V13版本为例来说明下这些设置如何操作。

主界面

在这里插入图片描述

  • 导航器:用来显示项目的源码,最上面的有9个左右的工具栏,包括:符号、搜索、总是、调试、断点和日志等,快捷键盘是command+数字1~9;
  • 工具栏:这里最主要的是库面板,需要从菜单View-Show Toolbar打开;
  • 检查器:不同的文件会有不同的设置,也就是属性设置面板;
  • 代码编辑区:在最上面有一个面包屑栏和工具栏,工具栏上有导航文件功能,面包屑栏显示了当前文件一些内容。这块的功能比较简单,点一次就可以记住了;

环境下载

在Preferences偏好设置中可以下载不同的运行环境:
在这里插入图片描述

代码自动完成

XCode有代码提示功能,输入一个字符会有默认提示,通过ESC键来打开或关闭提示框,然后通过Control+/-可实现快速翻页。
在这里插入图片描述

  • # :代表define指令;
  • m :表示method;
  • f :表示函数;
  • c :表示类

代码导航

可以通过在源码中设置特殊的标识来把需要关心的代码加入到代码面包屑工具栏中,这些标识在编译时会被编译器忽略掉。
在这里插入图片描述
这些特殊标记通常用:

  1. -,减号实现一个分隔线,见上图
  2. whatever,任意字符;
  3. //开头的特殊标记,以关键字+冒号+空格+文字描述格式,比如//TODO: 未完成的工作,这些关键字一般有TODO:、FIXME:、!!!:、???:

快捷键

  1. 鼠标左键+option,然后点击某个类型,在弹出窗口点击相应的类型名可直接跳转到document;

  2. control+i,格式化代码;

  3. command+d,删除行(需要在keymap中搜索delete line自行设置);

  4. command+左/右箭头,快速移到行首和行尾

  5. command+shift+o,快速查找;

  6. command+option+左/右箭头:展开和折叠代码,功能们于Edit-Code Folding下面

  7. command+option+shift+左/右箭头:展开和折叠所有方法

  8. command+r,运行程序

  9. command+u,测试程序

另外可供编辑使用的快捷键盘就是电脑上的触摸屏,可通过Edit-Customizer Touch bar 来设置,如下:
在这里插入图片描述

程序调试

主要使用以下几个工具,依次是:跳到下一个断点、下一行、进入被调用的方法、跳出被调用的方法。分别对应快捷键F5~F8。

在这里插入图片描述

另一个调试窗口在导航区上,与调试区联动,主要是下图中这两个标签页,一个是性能查看,另一个是断点浏览
在这里插入图片描述
鼠标悬浮到某个程序变量上也会显示相应的信息
在这里插入图片描述
还有一些更高级的功能可以在控制台输入相应的指令,比如:

  • call [exp]:调用给定对象的方法;
  • print [exp]:打印表达式的原始值,比如print [int] [obj length];
  • print-object [exp]:打印表达式的对象值;
  • set [v] = [exp]:给表达式赋值;
  • whatis [exp]:判断变量的类型;
  • help:帮助;
    在这里插入图片描述

静态检查器

这个功能是不是一个新的功能,很多IDE都有此种能力,有些还会以插件的形式存在,比如sonna, understand或是idea中的各种分析插件。

静态检查器的功能就是不运行代码来分析代码中可能存在的一些问题,在xcode中其功能集中在菜单"project-Analyze"中,它可以检查代码中的:

  1. 安全问题;
  2. 并发问题;
  3. 逻辑问题;
  4. 冗余代码;
    在这里插入图片描述
    疑似有问题的代码可以在导航面板中查看,找到问题后可在面板中点Fix来修复这些警告信息。
    在这里插入图片描述
    有时也会误报,因为检查器毕竟也是一段逻辑固定的程序,没办法覆盖所有的代码模式,如果发现了语报除了用上面 Fix 来关闭外,也可以在方法后加一个特殊标识来告诉检查器这块的这个问题不要检查了,比如:
//类似这样的标签还有很多,可以按需选择
static void dataFun (void) NS_RETURNS_RETAINED {  }

基础数据类型

基础数据类型

布尔类型

关键字BOOL,其值默认只有YES或NO,在Objc中只可与1和0相互转换,占8位存储空间,在写程序时也可以用#define把TRUE和FALSE定义为1 和 0,示例如下:

BOOL areIn(int thing, int ti){
    if (thing == ti){
        return (YES);
    }
    return NO;
    
    //return thing = ti; 这行代码有问题,因为ObjC中只有0和1来平替YES和NO
}
NSString *bool2Str(BOOL y){
    if (y == YES){
        return @"yes";
    }else{
        return @"no";
    }
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        BOOL *boolV = areIn(1, 2); 
        NSLog(@"%d = %@", boolV, bool2Str(boolV)); //0 = no
        
        NSLog(@"%d = %@", 1, bool2Str(1)); //1 = yes
        NSLog(@"%d = %@", YES, bool2Str(YES)); //1 = yes
        
    }
    return 0;
}

char字符类型

单个字符,用’'单引号表示

char c = 'a';

int整数类型

int i = 1000;

float浮点类型

带小数点的变量,比如

float f = 123.95;

double双精度浮点数

双精度

double d = 8.44e+11;

字符串常量

字符不是一个基本类型,它是一个对象,在使用时除了用对象实例化后,简单的可以直接用@“”,来表示:

NSString *str = @"korgs";
创建一个可变字符串
NSMutableString *stringM1 = [[NSMutableString alloc] initWithString:@"字符串"];

可用cString打印字符串内容,它返回的是一个char *指针地址。

 NSString *string = @"abdc";
        NSLog(@"%@, %s", string, [string cString]);

类型限定词

ObjC中的数据类型定义非常有意思,支持组合定义,这些限定词主要包括:long, long long, short, unsigned和signed这几个,它的作用是扩充原有数字的表述范围,具体的范围会根据系统决定,比如

long int factorial; //声明为long的整形变量

long, long long, short, unsigned和signed

类型运算符

  1. 四则运算: +、 -、 *、 \、 %、++、–
  2. -(负号)
  3. 类型强转,这里和java一样, 比如这样的写法 int a = (int)f,f为一float类型
  4. 赋值运算:=、!=、+=、-=、/=、*=
  5. 三元运算符:condition ? expression1: expression2
  6. 位运算:&、 |、 ^、 ~(求反)、 << 、>>
  7. 关系运算:==、!=、< 、 > 、 <= 、 >=

运算规则

主要是数值上面:

  • 同类型的的数相互运算,结果是同类型;
  • bool, char, short, int, bit field, enum全部转为int再运算;
  • 大类型与小类型运算结果为大类型,比如long int / short int = long int;

数据打印

以上类型如果需要用NSLog等函数打印时,其占位符表示都不太一样,大体如下。当用%@时表示可以打印任何内容。看似很复杂,其实就四个特殊的,float, long, unisigned, long long,分别用f, l(L), u(U), ll(LL)来表示,其它的不是太常用

类型实例示例NSLog字符
char‘a’ ‘\n’%c
short int123%hi, %hx, ho
unsigned short int123%hu, %hx, %ho,%hx
int12, -97, 0177(8进制)0xFEE0(16进制)%i, %x, %o
unsigned int12u, 100U, 0xFFU%x, %0, %u
long int12L, -2001, 0xFFFFL%li, %lx, %lo
unsigned long int12UL, 100ul, 0xFFFFUL%lu, %lx, %lo
long long int500ll, 0xe5e5e5LL%llu, %llx, %llo
float12.32f, 3.1e-5f%f, %e, %,g, %a
double12.32, 3.1e-5%f, %e, %g, %a
long double12.34L, 3.1e-5l%Lf, %Le, %Lg
idnil%p
**p(指针)%@
  1. %@:是一个通用字符可表示任何数据,可归类为打印对象,它会调用类的description:方法;
  2. %s:打印字符串

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

canvas画图,画矩形,圆形,直线,曲线可拖拽移动

提示&#xff1a;canvas画图&#xff0c;画矩形&#xff0c;圆形&#xff0c;直线&#xff0c;曲线可拖拽移动 文章目录 前言一、画矩形&#xff0c;圆形&#xff0c;直线&#xff0c;曲线可拖拽移动总结 前言 一、画矩形&#xff0c;圆形&#xff0c;直线&#xff0c;曲线可拖…

bugku-web-源代码

查看源码 <html> <title>BUGKUCTF-WEB13</title> <body> <div style"display:none;"></div> <form action"index.php" method"post" > 看看源代码&#xff1f;<br> <br> <script> …

【御控物联】 JavaScript JSON结构转换(4):对象To对象——规则属性重组

文章目录 一、JSON结构转换是什么&#xff1f;二、术语解释三、案例之《JSON对象 To JSON对象》四、代码实现五、在线转换工具六、技术资料 一、JSON结构转换是什么&#xff1f; JSON结构转换指的是将一个JSON对象或JSON数组按照一定规则进行重组、筛选、映射或转换&#xff0…

【Linux】Linux进程控制>进程创建进程终止进程等待进程程序替换

主页&#xff1a;醋溜马桶圈-CSDN博客 专栏&#xff1a;Linux_醋溜马桶圈的博客-CSDN博客 gitee&#xff1a;mnxcc (mnxcc) - Gitee.com 目录 1.进程创建 1.1 fork函数 1.2 fork函数返回值 1.2.1 写时拷贝 1.3 fork常规用法 1.4 fork调用失败的原因 、 2.进程终止 2.1…

什么是 SSL 证书?

SSL 证书的介绍 SSL&#xff08;Secure Sockets Layer&#xff09;证书是一种由数字证书颁发机构&#xff08;CA&#xff09;签发的加密证书&#xff0c;用于在 Web 浏览器和服务器之间建立安全连接。SSL 证书能够确保网站和应用程序的数据传输过程中不被窃听、篡改或伪造&…

【详解】运算放大器工作原理及其在信号处理中的核心作用

什么是运算放大器 运算放大器&#xff08;简称“运放”&#xff09;是一种放大倍数非常高的电路单元。在实际电路中&#xff0c;它常常与反馈网络一起组成一定的功能模块。它是一种带有特殊耦合电路和反馈的放大器。输出信号可以是输入信号的加法、减法、微分和积分等数学运算…

《AIGC重塑金融:AI大模型驱动的金融变革与实践》

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-oBSlqt4Vga1he7DL {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

【学习】软件测试行业 ,有哪些以就业为主的学习侧重点

今天给所有入行软测的同学们&#xff0c;帮大家梳理下以就业为主的学习侧重点&#xff0c;简单来说就是【这些都是重点&#xff0c;圈起来&#xff0c;要考的】&#xff0c;有需要的小伙伴可以往下看。 建议一&#xff1a;一定要学习一门编程语言&#xff0c;再开始使用自动化测…

uniapp实现列表动态添加

1.效果图&#xff1a; 2.代码实现&#xff1a; 这里没有用uniapp提供的uni-list控件 <template> <view id"app"> <!-- 这里为了让标题&#xff08;h&#xff09;居中展示&#xff0c;给h标签设置了父标签&#xff0c;并设置父标签text-…

微信公众号运营必备工具合集

微信公众号运营必备工具合集 各位同学&#xff0c;想要成为一名合格的公众号运营&#xff0c;必须要搭建一个属于自己的运营工具库&#xff0c;可以在日常工作中最大限度的提高效率。 91微信编辑器 &#xff1a;http://bj.91join.com/ 壹伴助手&#xff1a;https://yiban.io…

深入理解Happens-Before原则:以实例解析并发编程的基石

在最近的一次面试中面试官问到了Happens-Before原则&#xff0c;作此篇回顾下知识点。 在并发编程中&#xff0c;为了保证程序的正确性和可预测性&#xff0c;我们需要理解并遵循一系列内存访问规则。Happens-Before原则定义了线程间可见性和顺序性的保证。所有此篇文章将通过…

针对pycharm打开新项目需要重新下载tensorflow的问题解决

目录 一、前提 二、原因 三、解决办法 一、前提 下载包之前&#xff0c;已经打开了&#xff0c;某个项目。 比如&#xff1a;我先打开了下面这个项目&#xff1a; 然后在terminal使用pip命令下载&#xff1a; 如果是这种情况&#xff0c;你下载的这个包一般都只能用在这一个…

【学习笔记】java项目—苍穹外卖day04

文章目录 1. 新增套餐1.1 需求分析和设计1.2 代码实现1.2.1 DishController1.2.2 DishService1.2.3 DishServiceImpl1.2.4 DishMapper1.2.5 DishMapper.xml1.2.6 SetmealController1.2.7 SetmealService1.2.8 SetmealServiceImpl1.2.9 SetmealMapper1.2.10 SetmealMapper.xml1.…

【Docker】搭建强大的Nginx可视化配置工具 - nginxWebUI

【Docker】搭建强大的Nginx可视化配置工具 - nginxWebUI 前言 本教程基于绿联的NAS设备DX4600 Pro的docker功能进行搭建。 简介 NginxWebUI是一个基于Java的&#xff0c;专门用来管理Nginx的图形界面工具。它是开源的&#xff0c;使用相对简单且功能全面。 使用NginxWebUI…

Android裁剪图片为波浪形或者曲线形的ImageView

如果需要做一个自定义的波浪效果的进度条&#xff0c;裁剪图片&#xff0c;对ImageView的图片进行裁剪&#xff0c;比如下面2张图&#xff0c;如何实现&#xff1f; 先看下面的效果&#xff0c;看到其实只需要对第一张高亮的图片进行处理即可&#xff0c;灰色状态的作为背景图。…

link 样式表是否会阻塞页面内容的展示?取决于浏览器,edge 和 chrome 会,但 firefox 不会。

经过实测&#xff1a; 在 head 中 link 一个 1M 大小的样式表。设置网络下载时间大概为 10 秒。 edge 和 chrome 只有在下载完样式表后&#xff0c;页面上才会出现内容。而 firefox 可以直接先显示内容&#xff0c;然后等待样式表下载完成后再应用样式。 DOMContentLoaded 事…

我如何学会在学术界培养人际关系,并变得更加友善

我是一名初级教授&#xff0c;压力很大&#xff0c;工作到筋疲力尽&#xff0c;但在工作和家庭中仍然感到不足。因此&#xff0c;当我的入门编程课程的三名学生在学期结束时来到我的办公室&#xff0c;对他们的成绩感到担忧时&#xff0c;我觉得我没有时间处理他们的抱怨。我觉…

Vulnhub:MY FILE SERVER: 1

目录 信息收集 1、arp 2、nmap 3、whatweb WEB web信息收集 dirmap FTP匿名登录 enum4linux smbclient showmount FTP登录 ssh-kegen ssh登录 提权 系统信息收集 脏牛提权 get root 信息收集 1、arp ┌──(root㉿ru)-[~/kali/vulnhub] └─# arp-scan -l I…

2024UI自动化面试题汇总【建议收藏】

1.你是如何搭建ui自动化框架的&#xff1f; 在搭建ui自动化框架&#xff0c;使用的是po设计模式&#xff0c;也就是把每一个页面所需要 操作的元素和步骤都封装成一个页面类中。然后使用seleniumunittest搭建 四层框架实现数据、脚本、业务逻辑分离&#xff08;关键字驱动&…

【微服务】配置Nacos管理SpringBoot配置文件(附解压包)

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 一、什么是Nacos Nacos可以帮助我们配置和管理微服务&#xff0c;是阿里的一个开源产品&#xff0c;是针对微服务架构中的服务发现、配置管理、服务治理的综合型解决方案。Nacos可以用来实现配置中心和服务注册中心。 …
最新文章