C#构造函数、操作符重载以及自定义类型转换
构造器
构造器(构造函数)是将类型的实例初始化的特殊方法。构造器可分为实例构造器和类型构造器,本节将详细介绍有关内容。
实例构造器
顾名思义,实例构造器的作用就是对类型的实例进行初始化。如果类没有显示定义任何构造器,C#编译器会定义一个默认的无参构造器。相反,如果类中已经显示地定义了一个构造器,那么就不会再生成默认构造器了。定义实例构造器的语法这里就不再多做阐述了(该懂得要懂呀),下面通过一个简单的示例讲述实例构造器的执行原理。
public class Rapper
{private string name;private int age;private bool real = true;public Rapper(string name,int age){this.name = name;this.age = age;}
}
通过上述代码,我们创建了一个Rapper类,并定义了一个实例构造器,下面通过ildasm.exe工具查看构造器方法(.ctor)的IL代码。
.method public hidebysig specialname rtspecialname instance void .ctor(string name,int32 age) cil managed
{// Code size 30 (0x1e).maxstack 8IL_0000: ldarg.0IL_0001: ldc.i4.1IL_0002: stfld bool ConsoleApplication5.Rapper::realIL_0007: ldarg.0IL_0008: call instance void [mscorlib]System.Object::.ctor()IL_000d: nopIL_000e: nopIL_000f: ldarg.0IL_0010: ldarg.1IL_0011: stfld string ConsoleApplication5.Rapper::nameIL_0016: ldarg.0IL_0017: ldarg.2IL_0018: stfld int32 ConsoleApplication5.Rapper::ageIL_001d: ret
} // end of method Rapper::.ctor
执行步骤:
- Rapper的构造器把值true存储到字段real
- 调用Object类的构造器
- 加载第一个参数存储到字段name
- 加载第二个参数存储到字段age
虽然我们在声明real字段时直接赋值为true,但是在编译时,编译器会将这种语法转换成构造器方法中的代码来进行初始化。
我们知道,一个类型可以定义多个构造器,每个构造器须有不同签名,将Rapper类稍加修改.
public class Rapper
{private string name;private int age;private bool real = true;private bool diss = true;private bool forgetWords = true;public Rapper(string name, int age){this.name = name;this.age = age;}public Rapper(string name){this.name = name;}
}
通过ildasm.exe工具查看两段构造器的IL代码,会发现在每个方法开始的位置都包含用于初始化real,diss,forgetWords的代码。
为了减少生成的代码,可以利用this关键字显式调用另外一个构造器
public class Rapper
{private string name;private int age;private bool real = true;private bool diss = true;private bool forgetWords = true;public Rapper(string name, int age) : this(name){this.age = age;}public Rapper(string name){this.name = name;}
}
到目前为止,我们讨论的都是引用类型的实例构造器,下面,再来看看值类型的实例构造器。这里只用一句话来概括:值类型不允许包含显式的无参数构造器,如果为值类型定义构造器,必须显示调用才会执行。
类型构造器
类型构造器也称静态构造函数,类型构造器的作用是设置类型的初始状态。类型默认没有定义类型构造器。如果定义,只能定义一个。类型构造器没有参数。
类型构造器的特点:
- 定义类型构造器类似于实例构造器,区别在于必须标记为static
- 类型构造器总是私有的,静态构造器不允许出现访问修饰符
类型构造器的执行原理:
- JIT编译在编译一个方法时,会查看代码中所有引用的类型
- 判断类型是否定义了类型构造器
- 针对当前的AppDomain,检查是否已经调用了该类型构造器
- 如果没有,JIT编译器会在生成的native代码中添加对类型构造器的调用
类型构造器中的代码只能访问静态字段,与实例构造器相同,在类中声明静态字段并直接赋值时,编译器会自动生成一个类型构造器,并在类型构造器中初始化该值。为上面的Rapper类添加静态字段hobby
private static string hobby = "rap";
查看类型构造器方法(.cctor)的IL代码。
.method private hidebysig specialname rtspecialname static void .cctor() cil managed
{// Code size 11 (0xb).maxstack 8IL_0000: ldstr "rap"IL_0005: stsfld string ConsoleApplication5.Rapper::hobbyIL_000a: ret
} // end of method Rapper::.cctor
操作符重载方法
有的语言允许类型定义操作符来操作类型的实例。CLR对操作符一无所知,是编程语言定义了每个操作符的含义,以及调用这些操作符时生成的代码。向Rapper类添加如下代码:
public static string operator +(Rapper rapperA, Rapper rapperB){if (rapperA.name == "PGOne" || rapperB.name == "PGOne"){return "diss";}return "peace";}
注意:
- CLR规范要求操作符重载方法必须是public和static方法
- 使用operator关键字告诉编译器,这是一个自定义操作符重载方法
修改Main方法,声明两个Rapper对象,并输出rapperA + rapperB的返回值。
class Program
{static void Main(string[] args){Rapper rapperA = new Rapper("PGOne");Rapper rapperB = new Rapper("GAI");Console.WriteLine(rapperA + rapperB); //dissConsole.ReadLine();}
}
下面,使用ILDasm.exe工具查看编译器生成的IL代码。
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 43 (0x2b)
.maxstack 2
.locals init ([0] class ConsoleApplication5.Rapper rapperA,[1] class ConsoleApplication5.Rapper rapperB)
IL_0000: nop
IL_0001: ldstr "PGOne"
IL_0006: newobj instance void ConsoleApplication5.Rapper::.ctor(string)
IL_000b: stloc.0
IL_000c: ldstr "GAI"
IL_0011: newobj instance void ConsoleApplication5.Rapper::.ctor(string)
IL_0016: stloc.1
IL_0017: ldloc.0
IL_0018: ldloc.1
IL_0019: call string ConsoleApplication5.Rapper::op_Addition(class ConsoleApplication5.Rapper,class ConsoleApplication5.Rapper)
IL_001e: call void [mscorlib]System.Console::WriteLine(string)
IL_0023: nop
IL_0024: call string [mscorlib]System.Console::ReadLine()
IL_0029: pop
IL_002a: ret
} // end of method Program::Main
通过IL_0019一行,我们可以看到代码中出现+操作符时,实际调用的是op_Addition方法,再查看op_Addition方法的IL代码。
.method public hidebysig specialname static string op_Addition(class ConsoleApplication5.Rapper rapperA,class ConsoleApplication5.Rapper rapperB) cil managed
{
// Code size 61 (0x3d)
.maxstack 2
.locals init ([0] bool V_0,[1] string V_1)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldfld string ConsoleApplication5.Rapper::name
IL_0007: ldstr "PGOne"
IL_000c: call bool [mscorlib]System.String::op_Equality(string,string)
IL_0011: brtrue.s IL_0025
IL_0013: ldarg.1
IL_0014: ldfld string ConsoleApplication5.Rapper::name
IL_0019: ldstr "PGOne"
IL_001e: call bool [mscorlib]System.String::op_Equality(string,string)
IL_0023: br.s IL_0026
IL_0025: ldc.i4.1
IL_0026: stloc.0
IL_0027: ldloc.0
IL_0028: brfalse.s IL_0033
IL_002a: nop
IL_002b: ldstr "diss"
IL_0030: stloc.1
IL_0031: br.s IL_003b
IL_0033: ldstr "peace"
IL_0038: stloc.1
IL_0039: br.s IL_003b
IL_003b: ldloc.1
IL_003c: ret
} // end of method Rapper::op_Addition
执行步骤:
- 编译器为op_Addition方法生成元数据方法定义项,并在定义项中设置了specialname标志,表明这是一个特殊方法。
- 编译器发现代码中出现+操作符时,会检查是否有一个操作数的类型定义了名为op_Addition的specialname方法,而且该方法的参数兼容于操作数的类型。
- 如果存在这样的方法,就生成调用它的代码。
转换操作符方法
有时需要将对象从一种类型转换为另外一种全然不同的其他类型,此时便可以通过转换操作符实现自定义类型转换。同样的,CLR规范要求转换操作符重载方法必须是public和static的,并且要求参数类型和返回类型二者必有其一与定义转换方法的类型相同。
在C#中使用implicit和explicit关键字定义隐式/显示类型转换。在Implicit或explicit关键字后,要指定operator关键字告诉编译器该方法是一个转换操作符。在operator之后,指定目标类型,而在参数部分指定源类型。
依旧沿用上面的示例,为Rapper类添加Rap方法,并为其添加无参构造函数。
public void Rap()
{Console.WriteLine("Rap");
}public Rapper()
{}
新增Dancer类,添加Dance方法,使用implicit/explicit关键字定义隐式/显示类型转换。
public class Dancer
{public void Dance(){Console.WriteLine("Breaking");}public static implicit operator Rapper(Dancer dancer){return new Rapper();}public static explicit operator Dancer(Rapper rapper){return new Dancer();}
}
修改Main方法:
class Program
{static void Main(string[] args){Rapper rapperA = new Rapper();Dancer dancerA = (Dancer)rapperA;dancerA.Dance(); //BreakingDancer dancerB = new Dancer();Rapper rapperB = dancerB;rapperB.Rap(); //RapConsole.ReadLine();}
}
最后,查看编译器生成的IL代码可以发现,将对象从一种类型转换为另一种类型的方法总是叫做op_Implicit或op_Explicit。
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 48 (0x30)
.maxstack 1
.locals init ([0] class ConsoleApplication5.Rapper rapperA,[1] class ConsoleApplication5.Dancer dancerA,[2] class ConsoleApplication5.Dancer dancerB,[3] class ConsoleApplication5.Rapper rapperB)
IL_0000: nop
IL_0001: newobj instance void ConsoleApplication5.Rapper::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: call class ConsoleApplication5.Dancer ConsoleApplication5.Dancer::op_Explicit(class ConsoleApplication5.Rapper)
IL_000d: stloc.1
IL_000e: ldloc.1
IL_000f: callvirt instance void ConsoleApplication5.Dancer::Dance()
IL_0014: nop
IL_0015: newobj instance void ConsoleApplication5.Dancer::.ctor()
IL_001a: stloc.2
IL_001b: ldloc.2
IL_001c: call class ConsoleApplication5.Rapper ConsoleApplication5.Dancer::op_Implicit(class ConsoleApplication5.Dancer)
IL_0021: stloc.3
IL_0022: ldloc.3
IL_0023: callvirt instance void ConsoleApplication5.Rapper::Rap()
IL_0028: nop
IL_0029: call string [mscorlib]System.Console::ReadLine()
IL_002e: pop
IL_002f: ret
} // end of method Program::Main
扩展方法
扩展方法已经在《从LINQ开始之LINQ to Objects(下)》一文中进行了详细介绍,本篇就不再重复了。
转载于:https://www.cnblogs.com/Answer-Geng/p/7481294.html
C#构造函数、操作符重载以及自定义类型转换相关推荐
- 类型转换和操作符重载 (c#)
http://www.cnblogs.com/chenxizhang/archive/2008/09/14/1290735.html 也许你从来没有考虑过类型转换和操作符重载的问题,毕竟在很多时候,我 ...
- 类与对象:类的6个默认成员函数: 构造函数、析构函数、拷贝构造函数、赋值操作符重载、默认拷贝构造与赋值运算符重载的问题、const成员函数、 取地址及const取地址操作符重载
1.类的6个默认成员函数 如果一个类中什么成员都没有,简称为空类.任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数. 构造函数 析构函数 拷贝构造函数 赋值操作符重载 const成员函数 ...
- C++——构造函数(拷贝构造,拷贝复制),析构函数,操作符重载
C++--构造函数(拷贝构造,拷贝复制),析构函数,操作符重载 构造函数与析构函数:: 涉及构造函数还可以看这篇文章C++搞懂深拷贝初始化=与赋值=的区别 1.声明和定义构造函数和析构函数 构造函数在 ...
- 拷贝构造函数、析构函数、赋值操作符重载、取地址操作符重载和const修饰的取地址操作符重载...
2019独角兽企业重金招聘Python工程师标准>>> 在我们学习C++的过程中,我们难免会遇到类,在类中有6个默认的函数,它们分别为:构造函数.拷贝构造函数.析构函数.赋值操作符重 ...
- c++操作符重载,拷贝构造函数和友…
操作符重载 把c++的操作符在自己的新函数中重新实现就是操作符重载. 可以在类里面以成员函数的方式重载某个操作符,也可以在某个名字空间中重载操作符. ------------------------- ...
- const , 拷贝构造函数,赋值操作符重载
int a1 = 0;const int b1 = 1;int* p0; // 普通指针const int* p1; // 可修改所指对象,但不可修改所指对象的值int const* p2; // 可 ...
- C++中复制构造函数与重载赋值操作符
我们都知道,在C++中建立一个类,这个类中肯定会包括构造函数.析构函数.复制构造函数和重载赋值操作:即使在你没有明确定义的情况下,编译器也会给你生成这样的四个函数.例如以下类: class C ...
- C++赋值操作符重载
1.赋值操作符重载的原因 赋值操作符是一个使用频率最高的操作之一,通常情况下它的意义十分明确,就是将两个同类型的变量的值从一端(右端)传到另一端(左端).但在以下两种情况下,需要对赋值操作符进行重载. ...
- C/C++ 之 操作符重载
1.成员函数重载操作符:除".","*","::","?:"外其他操作符都可重载 (1) a.双目操作符声明格式: cl ...
最新文章
- c语言四则运算实验报告,c语言四则运算实验报告.doc
- 文件被后台程序占用无法删除_win10重装后系统占用50G?只要做好这2步,运行比win7还快...
- Wasserstein GAN
- HTML5 APP项目展示响应式网页模板
- 8635 气球(组合数)
- linux常用命令小结(二)
- 为什么GOF的23种设计模式里面没有MVC?
- 华为再次重申不造车!谁再言造车,调离岗位
- 两个有序链表序列的合并_leetcode链表之合并两个排序的链表
- 在训练CNN时,loss稳定在log(类别数)
- Java核心编程实践--视频
- JAVA 连等赋值问题
- ActivityMq下载、安装、使用
- PDF权限密码怎么解除
- NLP自然语言处理系列-时间序列数据分析-趋势性、周期性、自相关性、冲量、差分、移动平均误差计算
- 计算机运行慢 卡是什么原因是什么原因,电脑反应慢是怎么回事?五招让你的电脑快得飞起!...
- 钢铁侠c语言图片,揭秘!钢铁侠马克1型战衣原来使用了这个!
- Everyone Do this at the Beginning!!--kaggle数据预处理-超详细的解说
- android源码编译出现No private recovery resources for TARGET_DEVICE解决方法
- jQuery操作复选框checkbox技巧总结 ---- 设置选中、取消选中、获取被选中的值、判断是否选中等
热门文章
- mysql使用IS NULL查询null值
- 基于使用AspectJ实现AOP,注解AOP开发(基于xml文件、基于注解)
- docker删除本地已下载的镜像
- 通孔的作用是什么linux,电路板空洞的作用是什么 如何区分PTH与NPTH两种通孔
- 前端:Element UI 多选框组用法笔记
- WebSocket基础知识笔记
- android 飞框动画,AndroidTV中实现飞框选中效果
- java jre 中导入导出证书
- Android 第十五课 如何使用LitePal从SQLite数据库中删除数据(十四课用来保留讲解如何向SQLite数据库中存入数据)
- 为什么 Vue2 this 能够直接获取到 data 和 methods ? 源码揭秘!