[c++] C Language Features

C++的基础也能够理解为C的基础。这里主要是复习下其中的相对简单的基础部分。html

C++ 是一种中级语言,它是由 Bjarne Stroustrup 于 1979 年在贝尔实验室开始设计开发的。ios

C++ 进一步扩充和完善了 C 语言,是一种面向对象的程序设计语言。C++ 可运行于多种平台上,如 Windows、MAC 操做系统以及 UNIX 的各类版本。c++

菜鸡教程:https://www.runoob.com/cplusplus/cpp-tutorial.htmlexpress

 

菜鸡基础

主要特征

  • 封装 encapsulation
  • 抽象 abstraction
  • 继承 inheritance
  • 多态 polymorphism

 

版本历史 

发布时间 文档 通称 备注
2015 ISO/IEC TS 19570:2015 - 用于并行计算的扩展
2015 ISO/IEC TS 18822:2015 - 文件系统
2014 ISO/IEC 14882:2014 C++14 第四个C++标准
2011 ISO/IEC TR 24733:2011 - 十进制浮点数扩展
2011 ISO/IEC 14882:2011 C++11 第三个C++标准
2010 ISO/IEC TR 29124:2010 - 数学函数扩展
2007 ISO/IEC TR 19768:2007 C++TR1 C++技术报告:库扩展
2006 ISO/IEC TR 18015:2006 - C++性能技术报告
2003 ISO/IEC 14882:2003 C++03 第二个C++标准
1998 ISO/IEC 14882:1998 C++98 第一个C++标准

 

编译选项

g++ 有些系统默认是使用 C++98,咱们能够指定使用 C++11 来编译 main.cpp 文件:数组

g++ -g -Wall -std=c++11 main.cpp
选项 解释
-ansi 只支持 ANSI 标准的 C 语法。这一选项将禁止 GNU C 的某些特点, 例如 asm 或 typeof 关键词。
-c 只编译并生成目标文件。
-DMACRO 以字符串"1"定义 MACRO 宏。
-DMACRO=DEFN 以字符串"DEFN"定义 MACRO 宏。
-E 只运行 C 预编译器。
-g 生成调试信息。GNU 调试器可利用该信息。
-IDIRECTORY 指定额外的头文件搜索路径DIRECTORY。
-LDIRECTORY 指定额外的函数库搜索路径DIRECTORY。
-lLIBRARY 链接时搜索指定的函数库LIBRARY。
-m486 针对 486 进行代码优化。
-o FILE 生成指定的输出文件。用在生成可执行文件时。
-O0 不进行优化处理。
-O 或 -O1 优化生成代码。
-O2 进一步优化。
-O3 比 -O2 更进一步优化,包括 inline 函数。
-shared 生成共享目标文件。一般用在创建共享库时。
-static 禁止使用共享链接。
-UMACRO 取消对 MACRO 宏的定义。
-w 不生成任何警告信息。
-Wall 生成全部警告信息。

 

关键字

下表列出了 C++ 中的保留字。这些保留字不能做为常量名、变量名或其余标识符名称。安全

asm else new this
auto enum operator throw
bool explicit private true
break export protected try
case extern public typedef
catch false register typeid
char float reinterpret_cast typename
class for return union
const friend short unsigned
const_cast goto signed using
continue if sizeof virtual
default inline static void
delete int static_cast volatile
do long struct wchar_t
double mutable switch while
dynamic_cast namespace template  

完整关键字介绍可查阅:C++ 的关键字(保留字)完整介绍并发

 

1. asm异步

asm (指令字符串):容许在 C++ 程序中嵌入汇编代码。函数

#include "stdafx.h" 
#include <iostream>

using namespace std; 
int _tmain(int argc, _TCHAR* argv[]) 
{

    unsigned int a; 
    char inputKey; 
    cout<<"输入一个整数:"<<endl; 
    cin>>a; 
    
    unsigned int *c = &a; 
__asm { mov eax, c;
//c中存储的a的地址->eax mov eax, [eax]; //a的值->eax add eax,1; mov a,eax; }
cout
<<a<<endl; cin>>inputKey; return 0; }

  

2. autopost

auto(自动,automatic)是存储类型标识符,代表变量"自动"具备本地范围,块范围的变量声明(如for循环体内的变量声明)默认为auto存储类型。

可能在使用cout这类自动判断类型的表达时能够考虑auto。

 

10. const_cast:

const_cast<type_id> (expression)

该运算符用来修改类型的 const 或 volatile 属性。除了 const 或 volatile 修饰以外, type_id 和 expression 的类型是同样的。常量指针被转化成很是量指针,而且仍然指向原来的对象;常量引用被转换成很是量引用,而且仍然指向原来的对象;常量对象被转换成很是量对象。

 

16. dynamic_cast

dynamic_cast(动态转换),容许在运行时刻进行类型转换,从而使程序可以在一个类层次结构安全地转换类型。dynamic_cast 提供了两种转换方式,把基类指针转换成派生类指针,或者把指向基类的左值转换成派生类的引用。

 

19. explicit

explicit(显式的)的做用是"禁止单参数构造函数"被用于自动型别转换,其中比较典型的例子就是容器类型。在这种类型的构造函数中你能够将初始长度做为参数传递给构造函数。

 

20. export

为了访问其余编译单元(如另外一代码文件)中的变量或对象,对普通类型(包括基本数据类、结构和类),能够利用关键字 extern,来使用这些变量或对象时;

可是对模板类型,则必须在定义这些模板类对象和模板函数时,使用标准 C++ 新增长的关键字 export(导出)。

 

21. extern

extern(外部的)声明变量或函数为外部连接,即该变量或函数名在其它文件中可见。被其修饰的变量(外部变量)是静态分配空间的,即程序开始时分配,结束时释放。用其声明的变量或函数应该在别的文件或同一文件的其它地方定义(实现)。在文件内声明一个变量或函数默认为可被外部使用。在 C++ 中,还可用来指定使用另外一语言进行连接,这时须要与特定的转换符一块儿使用。目前仅支持 C 转换标记,来支持 C 编译器连接。使用这种状况有两种形式:

extern "C" 声明语句

extern "C" { 声明语句块 }

31. mutable

mutable(易变的)是 C++ 中一个不经常使用的关键字。只能用于类的非静态和很是量数据成员。因为一个对象的状态由该对象的非静态数据成员决定,因此随着数据成员的改变,对像的状态也会随之发生变化。若是一个类的成员函数被声明为 const 类型,表示该函数不会改变对象的状态,也就是该函数不会修改类的非静态数据成员。可是有些时候须要在该类函数中对类的数据成员进行赋值,这个时候就须要用到 mutable 关键字。

 

32. namespace

namespace(命名空间)用于在逻辑上组织类,是一种比类大的结构。

 

34. operator

operator(操做符)用于操做符重载。这是 C++ 中的一种特殊的函数。

 

38.register

register(寄存器)声明的变量称着寄存器变量,在可能的状况下会直接存放在机器的寄存器中;但对 32 位编译器不起做用,当 global optimizations(全局优化)开的时候,它会作出选择是否放在本身的寄存器中;不过其它与 register 关键字有关的其它符号都对32位编译器有效。

 

39. reinterpret_cast

用法:

reinpreter_cast<type-id> (expression)

type-id 必须是一个指针、引用、算术类型、函数指针或者成员指针。它能够把一个指针转换成一个整数,也能够把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还能够获得原先的指针值)。

 

45. static_cast

用法:

static_cast < type-id > ( expression )

该运算符把 expression 转换为 type-id 类型,但没有运行时类型检查来保证转换的安全性。它主要有以下几种用法:

    • ① 用于类层次结构中基类和子类之间指针或引用的转换。进行上行转换(把子类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成子类表示)时,因为没有动态类型检查,因此是不安全的。
    • ② 用于基本数据类型之间的转换,如把 int 转换成 char,把 int 转换成 enum。这种转换的安全性也要开发人员来保证。
    • ③ 把空指针转换成目标类型的空指针。
    • ④ 把任何类型的表达式转换成void类?

注意 static_cast 不能转换掉 expression 的 const、volitale、或者 __unaligned 属性。

 

48. template

template(模板),C++ 中泛型机制的实现。

 

50. throw

throw(抛出)用于实现 C++ 的异常处理机制,能够经过 throw 关键字"抛出"一个异常。

 

52. try

try(尝试)用于实现 C++ 的异常处理机制。能够在 try 中调用可能抛出异常的函数,而后在 try 后面的 catch 中捕获并进行处理。

 

54. typeid

指出指针或引用指向的对象的实际派生类型。

 

55. typename

typename(类型名字)关键字告诉编译器把一个特殊的名字解释成一个类型。在下列状况下必须对一个 name 使用 typename 关键字:

    • 1. 一个惟一的name(能够做为类型理解),它嵌套在另外一个类型中的。
    • 2. 依赖于一个模板参数,就是说:模板参数在某种程度上包含这个name。当模板参数使编译器在指认一个类型时产生了误解。

 

61. volatile

volatile(不稳定的)限定一个对象可被外部进程(操做系统、硬件或并发线程等)改变,声明时的语法以下:

int volatile nVint;

这样的声明是不能达到最高效的,由于它们的值随时会改变,系统在须要时会常常读写这个对象的值。所以经常使用于像中断处理程序之类的异步进程进行内存单元访问。

 

62. wchar_t

wchar_t 是宽字符类型,每一个 wchar_t 类型占 2 个字节,16 位宽。汉字的表示就要用到 wchar_t。

 

 

常量

"hello, dear"
--------------------------
"hello, \

dear"
--------------------------
"hello, " "d" "ear"

 

 

类型限定符

类型限定符提供了变量的额外信息。

限定符 含义
const const 类型的对象在程序执行期间不能被修改改变。
volatile 修饰符 volatile 告诉编译器不须要优化volatile声明的变量,让程序能够直接从内存中读取变量。对于通常的变量编译器会对变量进行优化,将内存中的变量值放在寄存器中以加快读写效率。
restrict 由 restrict 修饰的指针是惟一一种访问它所指向的对象的方式。只有 C99 增长了新的类型限定符 restrict。

 

存储类

存储类定义 C++ 程序中变量/函数的范围(可见性)和生命周期。这些说明符放置在它们所修饰的类型以前。下面列出 C++ 程序中可用的存储类:

  • auto
  • register
  • static
  • extern
  • mutable
  • thread_local (C++11)

从 C++ 11 开始,auto 关键字再也不是 C++ 存储类说明符,且 register 关键字被弃用。

使用 thread_local 说明符声明的变量仅可在它在其上建立的线程上访问。 变量在建立线程时建立,并在销毁线程时销毁。 每一个线程都有其本身的变量副本。

 

thread_local (c++11)

疑惑:thread_local 说明符能够与 static 或 extern 合并?  

Ref: thread_local变量

C++中有4种存储周期:

    1. automatic
    2. static
    3. dynamic
    4. thread

哪些变量能够被声明为thread_local?

    1. 命名空间下的全局变量
    2. 类的static成员变量   (注意,静态全局变量 反而限制了其做用域)
    3. 本地变量
thread_local int x;  //A thread-local variable at namespace scope 全局变量
class X
{
    static thread_local std::string s; //A thread-local static class data member 静态成员
};
static thread_local std::string X::s;  //The definition of X::s is required 静态成员

void foo()
{
    thread_local std::vector<int> v;  //A thread-local local variable 本地变量
}

 

Ref: thread_local变量

线程外的影响不了线程内的东西,线程内的能力仍是能影响到主线程share的thread_local的变量。

#include <thread>

thread_local int g_n = 1;

void f()
{
    g_n++;
    printf("id=%d, n=%d\n", std::this_thread::get_id(),g_n);
}

void foo()
{
    thread_local int i=0;
    printf("id=%d, n=%d\n", std::this_thread::get_id(), i);
    i++;
}

void f2()
{
    foo();
    foo();
}

int main()
{
    g_n++; 
    f();    // 主线程这里本身+1,以后又被t1,t2分别再+1,最后获得3
    std::thread t1(f);  // 这里的全局的g_n是线程本身的
    std::thread t2(f);
    
    t1.join();
    t2.join();


    f2();
    std::thread t4(f2);
    std::thread t5(f2);

    t4.join();
    t5.join();
    return 0;
}

  

循环

for (auto &elem : 数组)

int my_array[5] = {1, 2, 3, 4, 5};
// 每一个数组元素乘于 2
for (int &x : my_array)
{
    x *= 2;
    cout << x << endl;  
}
// auto 类型也是 C++11 新标准中的,用来自动获取变量的类型 for (auto &x : my_array) { x *= 2; cout << x << endl; }

  

函数

Lambda 函数与表达式

C++11 提供了对匿名函数的支持,称为 Lambda 函数(也叫 Lambda 表达式)。

Lambda 表达式把函数看做对象。Lambda 表达式能够像对象同样使用。

Ref: c++ Lambda函数学习

对于sort这样的函数带来了福音,例如:

#include <algorithm>
#include <cmath>

void abssort(float *x, unsigned N)
{
  std::sort(x,
            x + N,
            [](float a, float b) { return std::abs(a) < std::abs(b); });
}

 

auto自动推断返回值

std::cout << [](float f)        { return std::abs(f); } (-3.5);

std::cout << [](float f) -> int { return std::abs(f); } (-3.5);

这个语句与前面的不一样之处在于,lambda 表达式的返回时不是 float 而是 int。

第一个返回3.5;第二个返回3。

 

本质:可调用对象模板类

std::function<int()> lambda = [] () -> int { return val * 100; };

 

传值传引用

float f0 = 1.0;
std::cout << [=](float f) { return f0 + std::abs(f); } (-3.5);

传值:其输出值是 4.5

---------------------------------------------------------------------------------

float f0 = 1.0;
std::cout << [&](float f) { return f0 += std::abs(f); } (-3.5);
std::cout << '\n' << f0 << '\n';

传引用:输出值是 4.54.5

---------------------------------------------------------------------------------

float f0 = 1.0;
std::cout << [=](float f) mutable { return f0 += std::abs(f); } (-3.5);
std::cout << '\n' << f0 << '\n';

若是以传值的形式捕获外部变量,那么,lambda 体不容许修改外部变量。 你会以为输出值是什么呢?答案是,
4.51.0。 --------------------------------------------------------------------------------- float f0 = 1.0f; float f1 = 10.0f; std::cout << [=, &f0](float a) { return f0 += f1 + std::abs(a); } (-3.5); std::cout << '\n' << f0 << '\n'; 混合机制:这个例子的输出是 14.514.5

小总结:

[]        // 不捕获任何外部变量
[=]       // 以值的形式捕获全部外部变量
[&]       // 以引用形式捕获全部外部变量
[x, &y]   // x 以传值形式捕获,y 以引用形式捕获
[=, &z]   // z 以引用形式捕获,其他变量以传值形式捕获
[&, x]    // x 以值的形式捕获,其他变量以引用形式捕获

 

数字

数学头文件 <cmath> 

序号 函数 & 描述
1 double cos(double);
该函数返回弧度角(double 型)的余弦。
2 double sin(double);
该函数返回弧度角(double 型)的正弦。
3 double tan(double);
该函数返回弧度角(double 型)的正切。
4 double log(double);
该函数返回参数的天然对数。
5 double pow(double, double);
假设第一个参数为 x,第二个参数为 y,则该函数返回 x 的 y 次方。
6 double hypot(double, double);
该函数返回两个参数的平方总和的平方根,也就是说,参数为一个直角三角形的两个直角边,函数会返回斜边的长度。
7 double sqrt(double);
该函数返回参数的平方根。
8 int abs(int);
该函数返回整数的绝对值。
9 double fabs(double);
该函数返回任意一个浮点数的绝对值。
10 double floor(double);
该函数返回一个小于或等于传入参数的最大整数。

 

随机数

#include <iostream>
#include <ctime>
#include <cstdlib>
 
using namespace std;
 
int main ()
{
   int i,j;
 
   // 设置种子
 srand( (unsigned)time(NULL) );
 
   /* 生成 10 个随机数 */
   for( i = 0; i < 10; i++ )
   {
      // 生成实际的随机数
      j= rand();
      cout <<"随机数: " << j << endl;
   }
 
   return 0;
}

 

数组

参数中的数组有size,好处就是”能够重载";平时仍是最好加个size的参数。

数组的缺陷 (1):

arr[5]做为参数的话,sizeof(arr)指的是其中一个元素的大小,不是整个数组的。

double getAverage(int *arr, int size); //形式参数是一个指针:
double getAverage(int arr[5]);         // 重载函数,形式参数是一个已定义大小的数组:
double getAverage2(int arr[]);         // 不可重载,形式参数是一个未定义大小的数组:

数组的缺陷 (2):

C++中函数是不能直接返回一个数组的,可是数组其实就是指针,因此可让函数返回指针来实现。

 

字符串

C++ 中有大量的函数用来操做以 null 结尾的字符串:

序号 函数 & 目的
1 strcpy(s1, s2);
复制字符串 s2 到字符串 s1。
2 strcat(s1, s2);
链接字符串 s2 到字符串 s1 的末尾。
3 strlen(s1);
返回字符串 s1 的长度。
4 strcmp(s1, s2);
若是 s1 和 s2 是相同的,则返回 0;若是 s1<s2 则返回值小于 0;若是 s1>s2 则返回值大于 0。
5 strchr(s1, ch);
返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
6 strstr(s1, s2);
返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。

 

指针和引用 

把引用做为返回值 

double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};
 
double& setValues( int i )
{
  return vals[i];   // 返回第 i 个元素的引用
}

// 函数能够放在左边
setValues(1) = 20.23; // 改变第 2 个元素
setValues(3) = 70.8;  // 改变第 4 个元素

注意:返回一个对局部变量的引用是不合法的,可是,能够返回一个对静态变量的引用

 

引用做为参数

Ref: 【C++】为何不能定义数组的引用,却可定义变量的引用

数组的引用的能够定义的,好比:

int a[10];
int(&ra)[10]=a;  // 这个才是正牌的 数组的引用,用sizeof(ra)能够看出来
或者:
// 数组是个地址,那么先定义一个int地址的引用int*& ,数组名有const特性,因此引用也要是个const
int* const& ra=a;

// 不能定义引用数组,就是全部元素都是引用的数组
int& ra[10];  //这个是不行的 ,定义数组时要分配空间,而引用是不占用内存空间的,因此c++规定不能够定义引用数组

引用指代数组的一个例子:

bool array_assign(int (&p)[3],int (&q)[3]) 
{   
//std::cout<<sizeof(p)<<std::endl;   if(sizeof(p)!=sizeof(q))
   {       std::cout
<<"The subscript values do not match@!!!"<<std::endl;       return false;   }   for(size_t i=0;i<sizeof(q)/sizeof(q[0]);i++){       p[i]=q[i];   }   return true; } int main() {   int a[3]={2,8,16};   int b[3];   bool rest;   rest=array_assign(b,a);   if(rest)
   {       
for(size_t i=0;i<3;i++)
       {           std::cout
<<b[i]<<std::endl;       }   } }

 

"指针”的不可替代性

[?] 引用与指针有何区别?什么时候只能使用指针不能使用引用?

1.若是一个指针所指向的对象,须要用分支语句加以肯定,或者在中途须要改变他所指的对象,那么在它初始化以后须要为他赋值,而引用只能在初始化时指定被引用的对象,因此不能胜任。【过程当中须要改变所指时】

2.有时一个指针的值多是空指针,例如当把指针做为函数的参数类型或返回类型是,有时会用空指针表达特定的含义,而没用空引用之说。【空指针】

3.使用函数指针,因为没有函数引用,因此函数指针没法被引用替代。【函数指针】

4.使用new建立的对象或数组,须要用指针来存储它的地址。【本就是存地址】

5.以数组形式传递大批量数据时,须要用指针类型接受参数。【数组的救星】

 

 

时间日期

Goto 菜鸡教程:https://www.runoob.com/cplusplus/cpp-date-time.html 

#include <iostream>
#include <ctime>
 
using namespace std;
 
int main( )
{
   // 基于当前系统的当前日期/时间
   time_t now = time(0);
 
   cout << "1970 到目前通过秒数:" << now << endl;
 
   tm *ltm = localtime(&now);
 
   // 输出 tm 结构的各个组成部分
   cout << "年: "<< 1900 + ltm->tm_year << endl;
   cout << "月: "<< 1 + ltm->tm_mon<< endl;
   cout << "日: "<<  ltm->tm_mday << endl;
   cout << "时间: "<< ltm->tm_hour << ":";
   cout << ltm->tm_min << ":";
   cout << ltm->tm_sec << endl;
}

 

 

基本的输入输出

输入输出

头文件 函数和描述
<iostream> 该文件定义了 cin、cout、cerr 和 clog 对象,分别对应于标准输入流、标准输出流、非缓冲标准错误流和缓冲标准错误流。
<iomanip> 该文件经过所谓的参数化的流操纵器(好比 setw 和 setprecision),来声明对执行标准化 I/O 有用的服务。
<fstream> 该文件为用户控制的文件处理声明服务。咱们将在文件和流的相关章节讨论它的细节。

预约义的对象 cout 是 iostream 类的一个实例。cout 对象"链接"到标准输出设备,一般是显示屏。cout 是与流插入运算符 << 结合使用的

预约义的对象 cin 是 iostream 类的一个实例。cin 对象附属到标准输入设备,一般是键盘。cin 是与流提取运算符 >> 结合使用的

预约义的对象 cerr 是 iostream 类的一个实例。cerr 对象附属到标准错误设备,一般也是显示屏,可是 cerr 对象是非缓冲的,且每一个流插入到 cerr 都会当即输出。

预约义的对象 clog 是 iostream 类的一个实例。clog 对象附属到标准错误设备,一般也是显示屏,可是 clog 对象是缓冲的。这意味着每一个流插入到 clog 都会先存储在缓冲在,直到缓冲填满或者缓冲区刷新时才会输出。

 

举个栗子:一个简单的权限控制打印,用于程序调试。 

/*****************************************************************************/

// ::print priority
#define USER_EMERG      "0"          /* system is unusable */  
#define USER_ALERT      "1"          /* action must be taken immediately */  
#define USER_CRIT       "2"          /* critical conditions */  
#define USER_ERR        "3"          /* error conditions */  
#define USER_WARNING    "4"          /* warning conditions */  
#define USER_NOTICE     "5"          /* normal but significant condition */  
#define USER_INFO       "6"          /* informational */  
#define USER_DEBUG      "7"          /* debug-level messages */  

#define USER_DEFAULT    USER_NOTICE  /* the default kernel loglevel */  

// ::print controller
#ifdef ENABLE_DSDEBUG
        #define ENABLE_DSINFO
        #define dcout std::cout
#else
        #define dcout 0 && std::cout
#endif

#ifdef ENABLE_DSINFO
        #define ENABLE_DSDEFAULT
        #define icout std::cout
#else
        #define icout 0 && std::cout
#endif

#ifdef ENABLE_DSDEFAULT
        #define ncout std::cout
        #define wcout std::cout
        #define ecout std::cout
        #define ccout std::cout
        #define acout std::cout
        #define ecout std::cout
#else
        #define ncout 0 && std::cout
        #define wcout 0 && std::cout
        #define ecout 0 && std::cout
        #define ccout 0 && std::cout
        #define acout 0 && std::cout
        #define ecout 0 && std::cout
#endif

 

文件和流

Ref: https://www.runoob.com/cplusplus/cpp-files-streams.html

要在 C++ 中进行文件处理,必须在 C++ 源代码文件中包含头文件 <iostream> 和 <fstream>。

数据类型 描述
ofstream 该数据类型表示输出文件流,用于建立文件并向文件写入信息。
ifstream 该数据类型表示输入文件流,用于从文件读取信息。
fstream 该数据类型一般表示文件流,且同时具备 ofstream 和 ifstream 两种功能,这意味着它能够建立文件,向文件写入信息,从文件读取信息。

 

 

结构体

初始化

struct Point
{
    int x;
    int y;
    int z;
};

Point p = {1, 2, 3};

 

"结构体指针" 做为函数参数

// 该函数以结构指针做为参数
void printBook( struct Books *book )
{
   cout << "书标题  : " << book->title <<endl;
   cout << "书做者 : " << book->author <<endl;
   cout << "书类目 : " << book->subject <<endl;
   cout << "书 ID : " << book->book_id <<endl;
}


Books Book1;        // 定义结构体类型 Books 的变量 Book1
printBook( &Book1 );

 

typedef 美化

typedef struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
}Books;

 

 End.

相关文章
相关标签/搜索