构造函数深入理解

目录

  • 构造函数
    • 构造函数体赋值
    • 初始化列表
      • 初始化列表格式
      • 初始化列表的意义以及注意点
        • const修饰的成员变量初始化
        • 对象成员具体初始化的地方
        • 缺省值存在的意义
          • 例子1
          • 例子2
        • 初始化与赋值
        • 引用成员变量的初始化
          • 注意点1
          • 注意点2
          • 我的疑惑
        • 自定义类型成员初始化
          • 例子1
          • 例子2
          • 例子3
          • 例子4
        • 初始化列表可以调用函数
          • 例子1
          • 例子2
          • 例子3
        • 我的疑惑
        • 拓展
          • 例子1
          • 例子2
          • 不同类型的赋值
          • 编译器优化拷贝构造函数的情景
            • 例子1
            • 例子2
        • 总结
    • explicit关键字
      • 例子1
      • 例子2
    • 补充

感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接
🐒🐒🐒 个人主页
🥸🥸🥸 C语言
🐿️🐿️🐿️ C语言例题
🐣🐣🐣 python
🐓🐓🐓 数据结构C语言
🐔🐔🐔 C++
🐿️🐿️🐿️ 文章链接目录

构造函数

构造函数体赋值

在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值

class Date
{
public:
 Date(int year, int month, int day)
 {
     _year = year;
     _month = month;
     _day = day;
 }
private:
 int _year;
 int _month;
 int _day;
};

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量
的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始
化一次,而构造函数体内可以多次赋值。

初始化列表

初始化列表格式

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟
一个放在括号中的初始值或表达式

class Date
{
public:
 Date(int year, int month, int day)
	 : _year(year)
     , _month(month)
     , _day(day)
 {}
  /*  Date(int year, int month, int day)之前的构造函数格式
   {
       _year = year;
       _day = day;
       _month = month;
   }*/
 
private:
 int _year;
 int _month;
 int _day;
};

下面的写法都是错误的
在这里插入图片描述
在这里插入图片描述

初始化列表的意义以及注意点

const修饰的成员变量初始化
class Date
{
public:
    Date(int year, int month, int day)
    {
        _n = 1;
        _year = year;
        _day = day;
        _month=month;
    }

private:
    int _year;
    int _month;
    int _day;
    const int _n;
};
int main()
{
    Date d1(2024, 5, 14);
    return 0;
}

const int _n不能够初始化
在这里插入图片描述
在这里插入图片描述
对于下面的代码我们都知道,这是声明,当对象实例化的时候他们才会整体定义,比如Date d1(2024,5,14)

private:
	int _year;
    int _month;
    int _day;
    const int _n;

但是有些成员在定义的时候是必须初始化的,就如 const int _n,因为const修饰了int_n,const只有一次修改的机会就是在初始化的时候,初始化的时候没有被修改,就会导致后面要想再修改就不可能了,因为const不允许修改被修饰的变量

对象成员具体初始化的地方

那对象成员具体初始化的地方都在哪呢?

在这里插入图片描述
上图我们可以认为是具体的初始化吗?来看看下面的图片
在这里插入图片描述
定义和初始化应该只有一个位置,但是上图中我们对_year进行了两次初始化,那这是开辟了两次空间吗?
当然不是,这样的情况我们并不想让他发生
所以构造函数才有了初始化列表,初始化列表是每个成员变量定义初始化的位置,也就是说想_year=1这样重复初始化的就别出现在初始化列表当中了
在这里插入图片描述
在这里插入图片描述
上图中_month和_day默认初始化为0
在之前的构造函数当中,有提到过缺省值

缺省值存在的意义
private:
    int _year=2;
    int _month=1;
    int _day=3;
    const int _n=1;

这里的_year _month _day _n的缺省值分别为2 1 3 1,而这个缺省值其实就是为初始化列表准备的,当初始化列表当中什么都没有的时候,缺省值就发挥作用了

例子1
class Date
{
public:
    Date(int year, int month, int day)
    
    {}
   void Print()
    {
       cout << _year << "/" << _month << "/" << _day << "/" << _n << endl;
    }
private:
    int _year=2;
    int _month=1;
    int _day=3;
    const int _n=1;
};
int main()
{
    Date d1(2024, 5, 14);
    d1.Print();
    return 0;
}

在这里插入图片描述

例子2

在这里插入图片描述

_year输出的结果是2因为在初始化列表当中我们对_year初始化成了2,所以并没有用到缺省值
_month输出结果是一个随机值因为_month即没给缺省值,又没对其进行初始化
_day输出结果是0,虽然_day给了缺省值,但是在初始化列表当中没有具体给初始化值,所以_day最后初始化的值为0(具体为什么我也不清楚,可能就觉得既然你都有缺省值了,要想让_day=缺省值,就不要在初始化列表里面写_day(),这样让人感觉你就像让_day默认初始化成0)
_n输出结果是4因为给的缺省值是4,虽然在初始化列表当中没有写n,但是初始化列表会用这个缺省值给n进行初始化

初始化与赋值

知道了初始化列表的用处后我们看看下面这个代码

class Date
{
public:
    Date(int year, int month, int day)
        :_year(1)
        ,_month(2)
        , _day(3)
        ,_n(4)
    {
        _year = year;
        _month = month;
        _day = day;
        _year = 1;
    }
   void Print()
    {
       cout << _year << "/" << _month << "/" << _day << "/" << _n << endl;
    }
private:
    int _year=1;
    int _month;
    int _day=2;
    const int _n=4;
};
int main()
{
    Date d1(2024, 5, 14);
    d1.Print();
    return 0;
}

在这里插入图片描述
结果是1/5/14/4,为什么不是1/2/3/4呢?不是说初始化列表值允许初始化1次吗?
初始化的确只能初始化1次,但是赋值可以赋值很多次
所以大括号里面的_year=year…其实是对_year…赋值

并且从这个例子我们也可以看出谁才是初始化,因为按照程序运行的顺序,初始化必然是排在最前面的,赋值是在初始化成功的基础上才能进行

尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化,所以最终的写法如下

class Date
{
public:
    Date(int year, int month, int day)
        :_year(year)
        ,_month(month)
        , _day(day)
        ,_n(4)
    {
       
    }
   void Print()
    {
       cout << _year << "/" << _month << "/" << _day << "/" << _n << endl;
    }
private:
    int _year=1;
    int _month;
    int _day=2;
    const int _n=4;
};
int main()
{
    Date d1(2024, 5, 14);
    d1.Print();
    return 0;
}

在这里插入图片描述

注意:
1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
引用成员变量
const成员变量
自定义类型成员(且该类没有默认构造函数时)

引用成员变量的初始化

为什么引用也要房子初始化列表当中呢?
在写引用的文章当中说过引用必须要在定义的时候初始化

注意点1

在这里插入图片描述
因为是引用,所以在初始化的时候我们可以填入成员变量,如下图_ref是_year的别名
在这里插入图片描述

注意点2

但同时也需要注意因为_ref是引用,所以在初始化的时候需要注意不可以成为const修饰成员的别名
在这里插入图片描述

我的疑惑

在写到这里的时候我还在想像这种引用放在初始化列表开始的位置,在_year还没初始化的时候,就让_ref作为_year的别名会不会报错呢?
在这里插入图片描述
这里我说一下我自己的想法,因为_year已经声明了,而_ref作为_year的别名,由于_year没有初始化,所以_ref只是套了一个空壳子,单号_year初始化后,_ref才真正的初始化成功
为了验证想法我见year的缺省值删掉,并且不在初始化列表当中加入_year,最后输出的是随机值
在这里插入图片描述
这时我有一点疑惑,如果没有初始化_year那_year应该只有一个声明才对,只有一个声明就说明_year没有空间,输出的时候应该会报错才对
于是我打印了一下_year和_ref的地址,发现他们是有地址的,说明_year和_ref是存在的
为了解释这种情况我觉得可能是在定义这个对象的时候想_year这些成员变量就已经有空间了,但是又和前面所的知识冲突,也可能是我自己没学懂😕😕😕
在这里插入图片描述

自定义类型成员初始化
例子1
class A
{
public:
    A(int a = 0)
        :_a(a)
    {
        cout << "A(int a=0)" << endl;
    }
private:
    int _a;
};
class Date
{
public:
    Date(int year, int month, int day)
        :_ref(_year)
        , _month(month)
        , _day(day)
        , _n(4)   
    {
       
    }
   void Print()
    {
       cout << &_year << "/" << _month << "/" << _day << "/" << _n << "/" << &_ref << endl;
    }
private:
    int _year;
    int _month;
    int _day=2;
    const int _n=4;
    int& _ref;
    A _aa;
};
int main()
{
    Date d1(2024, 5, 14);
    d1.Print();
    return 0;
}

现在有一个自定义类型A,将A_aa的声明放在Date的成员声明里,但是A_aa没有在Date的初始化列表当中定义,那A_aa会不会被定义呢?

我们需要理解下面这句话
我们不写默认构造函数,编译器会自动生成,编译器自动生成的默认构造函数不对内置类型进行处理,自定义类型调用他自己的默认构造函数

在Date的初始化列表当中由于没有写A的构造函数,所以在运行的过程当中会调用A的默认构造函数,而A的默认构造函数是在A这个类里面
在这里插入图片描述

例子2

当我们不给a默认构造呢(只写了int a,没有些int a=0)
在这里插入图片描述
因为我们给_a初始化成a,但是a没有给值,所以会报错

例子3

而当我们不给_a初始化成a时,也就是_a(),这样a就是初始化成0
在这里插入图片描述

例子4

我们也可以这样写
在这里插入图片描述

初始化列表可以调用函数
例子1
class Date
{
public:
    Date()
     :_p((int*)malloc(sizeof(4)))
    {
       //函数体
        if (_p == nullptr)
        {
            perror("malloc fail");
        }
    }
   void Print()
    {
        cout << _p << endl;
    }
private:
    int* _p;
};
int main()
{
    Date d1;
    d1.Print();
    return 0;
}

在这里插入图片描述

例子2
class Date
{
public:
    Date(int year, int month, int day)
        :_ref(_year)
        , _month(month)
        , _day(day)
        , _n(4)
        ,_p((int*)malloc(sizeof(4)))
    {
			 //函数体
        if (_p == nullptr)
        {
            perror("malloc fail");
        }
    }
    void Print()
    {
        cout << &_year << "/" << _month << "/" << _day << "/" << _n << "/" << &_ref << endl;
        cout << _p << endl;
    }
private:
    int _year;
    int _month;
    int _day = 2;
    const int _n = 4;
    int& _ref;
    int* _p;
};
int main()
{
    Date d1(2024, 5, 14);
    d1.Print();
    return 0;
}

在这里插入图片描述

例子3
class A
{
public:
    void Print()
    {
        cout << a << " " << p1 << " " << *p2 << endl;
    }
private:
    int a = 1;
    int* p1 = nullptr;
    int* p2 = (int*)malloc(4);

};
int main()
{
    A a;
    a.Print();
}

因为缺省值是给初始化列表的,这里的int* p2 = (int*)malloc(4)和上面初始化列表当中的 ,_p((int*)malloc(sizeof(4)))写法是相同的,既然初始化列表可以这样写,那缺省值也是可以这样写的

所以缺省值不一定是常量
在这里插入图片描述

我的疑惑
class Date
{
public:
    Date()
     :_p((int*)malloc(sizeof(4)))
    {
       //函数体
        if (_p == nullptr)
        {
            perror("malloc fail");
        }
    }
   void Print()
    {
        cout << _p << endl;
    }
private:
    int* _p;
};
int main()
{
    Date d1();
    d1.Print();
    return 0;
}

这段代码和上面的例子1的唯一区别就是Date d1有无括号,但是就是因为这个括号导致报错了,我也不清楚为什么会这样
例子2中Date d1有括号,并且也定义了_p,为什么就可以正常运行
在这里插入图片描述
在这里插入图片描述
除了上面的一些问题还有关于_p初始化的一些问题等

拓展
例子1
class C
{
public:
    C(int x = 0)
        :_x(x)
    {

    }
    void Print()
    {
        cout << _x << endl;
    }
private:
    int _x;
};
int main()
{
    C c1(1);
    c1.Print();
}

在这里插入图片描述

例子2
class C
{
public:
    C(int x = 0)
        :_x(x)
    {

    }
    void Print()
    {
        cout << _x << endl;
    }
private:
    int _x;
};
int main()
{
    C c2 = 2;
    c2.Print();
}

单参数构造函数支持隐式类型的转换,这里的2构造出了一个C的对象,我们先称为c3,然后将c3拷贝构造给c2,这样的话C c2=2就可以理解成c2=c3,

在这里插入图片描述

不同类型的赋值

另外之前提到过不同类型赋值过程也和这个是一样的
比如现在有一个int类型的a和double类型的b
在这里插入图片描述
现在要将b用来给a赋值,在赋值的过程中b会创建出一个临时变量,然后a拷贝这个临时变量,因为double类型有8个字节,而int类型只有4个字节,所以拷贝的时候就出现了数据丢失
在这里插入图片描述
在这里插入图片描述

同样的当一个char类型的变量赋值给int类型的变量,因为char类型只有1个字节,而int类型有4个字节,所以当char类型赋值给int类型的时候会出现类型提升
在这里插入图片描述

所以现在倒回来理解C c2=2应该就容易一点了

在这里插入图片描述

编译器优化拷贝构造函数的情景
例子1

我们也可以验证一下他时候调用了拷贝构造函数

在这里插入图片描述
这里没有调用拷贝构造函数是因为编译器优化了,同一个表达式连续步骤的构造,一般会合二为一

例子2
class C
{
public:
    C(int x = 0)
        :_x(x)
    {

    }
    C(const C& cc)
    {
        cout << "C(const C& cc)" << endl;
    }
   void Print()const
    {
        cout << _x << endl;
    }
private:
    int _x;
};
int main()
{
   const C& c2 = 2;
    c2.Print();
}

这里的c2引用的是临时变量,而临时变量具有常性
在这里插入图片描述

总结

类中以下成员必须放在初始化列表中初始化
引用成员变量
const修饰成员变量
自定义类型成员(且该类没有构造默认成员函数时)
其他成员可以在函数体内初始化也可以在初始化列表里初始化

这里的函数体具体是指哪里呢?

    Date(int year, int month, int day)
        :_ref(_year)
        , _month(month)
        , _day(day)
        , _n(4)   
        ,_aa(1)
    {
       //函数体
    }

explicit关键字

对于上面的代码有许多情况都是通过隐式类型转换,如果我们不想让这种转换发生,我们可以通过explicit去修饰

例子1

class C
{
public:
    C(int x = 0)
        :_x(x)
    {

    }
    C(const C& cc)
    {
        cout << "C(const C& cc)" << endl;
    }
    void Print()const
    {
        cout << _x << endl;
    }
private:
    int _x;
};
int main()
{
    const C& c2 = 2;
    c2.Print();
}

在这里插入图片描述

例子2

多参数的类也是可以支持explicit修饰

class A
{
public:
	A(int a1,int a2)
		:_a1(a1)
		,_a2(a2)
	{}
private:
	int _a1;
	int _a2;
};
int main()
{
	A b1 = { 1,2 };
	const A& b2 = { 1,2 };
	A b3(1,2);
	return 0;
}

需要注意的是A b1 = { 1,2 }中用的是花括号
在构造时我们我们可以写成A b3(1,2)

当加上explicit之后

class A
{
public:
	explicit A(int a1,int a2)
		:_a1(a1)
		,_a2(a2)
	{}
private:
	int _a1;
	int _a2;
};
int main()
{
	A b1 = { 1,2 };
	const A& b2 = { 1,2 };
	A b3(1,2);
	return 0;
}

在这里插入图片描述

补充

看一下下面这道题

class A
{
public:
    A(int a)
       :_a1(a)
       ,_a2(_a1)
   {}
    
    void Print() {
        cout<<_a1<<" "<<_a2<<endl;
   }
private:
    int _a2;
    int _a1;
};
int main() {
    A aa(1);
    aa.Print();
}
A. 输出1  1
B.程序崩溃
C.编译不通过
D.输出1  随机值

_a2输出的是一个随机值,这是因为成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
注意看private的中的_a1和_a2的先后顺序,_a2是在_a1前面的,所以_a2要比_a1先赋值
在这里插入图片描述

在这里插入图片描述
当我们调整顺序后,输出就是1 1了
在这里插入图片描述

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

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

相关文章

Sentinel链路流控模式失效的解决方法

解决方法 1、在pom.xml中增加sentinel-web-servlet的依赖&#xff0c;我使用的版本是1.7.1 <dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-web-servlet</artifactId> </dependency>2、在项目中添加一个FilterCon…

J-Flash刷机的步骤

1、Keil编译代码&#xff0c;生成文件&#xff1a;E:\automotive\xxx.axf 2、打开"SEGGER J-Flash V7.88k"&#xff0c;配置Project information 3、点击菜单栏中的"File"&#xff0c;open data file&#xff0c;找到所需的xxx.axf文件 4、按快捷键F7进…

Yolov8模型调参大全:超详细解读每一个参数

目录 1. 代码获取方式 2. YOLOv8网络配置 3. Yolov8使用 3.1. CLI 3.2. Python 4.default.yaml文件解读 1. 代码获取方式 Yolov8项目地址&#xff1a;https://github.com/ultralytics/ultralytics 这里就不详细介绍v8了 2. YOLOv8网络配置 # Ultralytics YOLO &…

利用多模态大模型解决旅行商问题

概述 ○ 该研究论文提出了一种利用多模态大型语言模型&#xff08;MLLM&#xff09;的视觉推理能力来解决旅行商问题&#xff08;TSP&#xff09;和多旅行商问题&#xff08;mTSP&#xff09;的新方法。 ○ 传统方法依赖于节点坐标和距离矩阵&#xff0c;而本研究则采用多智能…

ueditor解决无法抓取远程背景图片问题的方法(php)

背景 laravel后台经常有用到编辑器的地方&#xff0c;Dcat使用的一般都是UEditor编辑器。最近项目经理在秀米排版以后&#xff0c;将内容复制到UEditor编辑器保存后发现&#xff0c; 在网站页面中发现图片竟然展示失败。经过浏览器控制台发现&#xff0c;图片的域名还是秀米的…

HMI 的 UI 风格创新无限

HMI 的 UI 风格创新无限

变频器配置V20

变频器控制最好是变频电机&#xff0c;在速度不低的情况下工频电机 改变电机转速&#xff0c;调节扭矩&#xff0c; 变频器 L1 L2 L3 ,R S T 电机输入 uvw 电机输出 FSD 制动电阻 设置步骤 恢复出厂设置 p0010:30 p0970:21p0003:3(设定访问级别) P0003 用户访问级别 0 - 4 1…

cadence symbol修改之一

cdaence virtuoso 复制cell&#xff0c;或者拷贝symbol之后&#xff0c;再次调用的时候&#xff0c;symbol还是跟随原来的cell名字 解决办法 打开对应的symbol 修改partName为 cellName

RK3568驱动指南|第十六篇 SPI-第190章 配置模式下寄存器的配置

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

快团团能屏蔽团员某个人吗?有哪些操作步骤?

在快团团里团长不仅拥有发起团购、管理商品和订单的权利&#xff0c;还具备了一项关键功能——屏蔽特定团员的能力。这一功能确保了团长能够维护良好的社群环境&#xff0c;避免不必要的干扰。以下是屏蔽团员的具体步骤&#xff1a; 1. 登录快团团&#xff1a;首先&#xff0c;…

10 - Python文件编程和异常

文件和异常 在实际开发中&#xff0c;常常需要对程序中的数据进行持久化操作&#xff0c;而实现数据持久化最直接简单的方式就是将数据保存到文件中。说到“文件”这个词&#xff0c;可能需要先科普一下关于文件系统的知识&#xff0c;对于这个概念&#xff0c;维基百科上给出…

【Unity 3D角色移动】

【Unity 3D角色移动】 在Unity 3D中实现角色移动通常涉及到几个关键步骤&#xff0c;包括设置角色的物理属性、处理输入、更新角色的位置以及动画同步。下面是实现基本3D角色移动的步骤和示例代码&#xff1a; 步骤1&#xff1a;设置角色的物理属性 角色通常使用Character Co…

学校卫星电子怎么自动校准时间呢

在学校的教室里&#xff0c;卫星电子钟精准地为师生们提供着时间服务&#xff0c;而其自动校准时间的功能令人称奇。那么&#xff0c;学校卫星电子钟是如何实现自动校准时间的呢&#xff1f; 学校卫星电子钟自动校准时间的原理基于卫星导航系统。常见的如北斗卫星导航系统或 GP…

什么地方适合安装自动气象站?

随着科技的不断进步&#xff0c;自动气象站在气象观测、环境监测以及科研教学等领域发挥着越来越重要的作用。 一、科研机构和高校校园 科研机构和高校校园是安装自动气象站的理想场所。这些地方拥有专业的科研团队和丰富的教育资源&#xff0c;可以为气象站的建设和运营提供有…

学生用小台灯什么牌子的好?列举出几款学生用台灯推荐

眼睛是我们感知世界的窗口&#xff0c;但近年来&#xff0c;儿童青少年的视力健康却受到了严重困扰。数据显示&#xff0c;近视问题在儿童群体中呈现出明显的增长趋势&#xff0c;这给他们的学习和生活带来了诸多不便。虽然现代科技的快速发展使得电子产品成为了我们生活中不可…

VMware Workstation桥接模式无法上网

问题背景 我之前创建过一个虚拟机&#xff0c;当时虚拟机的网络模式使用的是桥接模式&#xff0c;配置好了固定ip地址&#xff0c;是可以正常上网的&#xff0c;中间没有做任何网络上面的配置。但是今天再打开这台虚拟机时&#xff0c;发现竟然不能上网了。 物理主机的ip信息配…

详解COB封装的定义

COB封装全称是Chip on Board&#xff08;板上芯片封装&#xff09;&#xff0c;是一种非常先进的电子封装工艺&#xff0c;其会涉及到将发光芯片直接封装于印刷电路板&#xff08;PCB&#xff09;或者其他类型的互连电气基板上&#xff0c;通过细小的金属线进行键合&#xff0c…

LangChain 入门上篇:模型 I/O 封装

LangChain 是面向大模型的开发框架&#xff0c;是 AGI 时代软件工程的探索和原型。学习 LangChain 需要关注接口的变更。 LangChain 的核心组件 1.模型 I/O 封装 LLMS 大语言模型Chat Models 一套基于 LLMS&#xff0c;但按对话结构重新封装PromptTemplate 提示词模板Output…

七、函数练习

目录 1. 写一个函数可以判断一个数是不是素数。&#xff08;素数只能被1或其本身整除的数&#xff09; 2. 一个函数判断一年是不是闰年。 3.写一个函数&#xff0c;实现一个整形有序数组的二分查找。 4. 写一个函数&#xff0c;每调用一次这个函数&#xff0c;使得num每次增…

Appium+python自动化(三十九)-Appium自动化测试框架综合实践 - 代码实现(超详解)

1.简介 今天我们紧接着上一篇继续分享Appium自动化测试框架综合实践 - 代码实现。由于时间的关系&#xff0c;宏哥这里用代码给小伙伴演示两个模块&#xff1a;注册和登录。 2.业务模块封装 因为现在各种APP的层出不群&#xff0c;各式各样的。但是其大多数都有注册、登录。为…