> 文档中心 > Essential C# 6.0 C#学习笔记 第一章 C#概述

Essential C# 6.0 C#学习笔记 第一章 C#概述


Essential C# 6.0 学习笔记

由于博主对C#的使用比较多,但是对C#的理解还是停留在表面,所以开创此系列篇章,详细记录在学习Essential C# 6.0 这本书时候遇到的问题,以及优秀的知识点的记录,还有自己对一些知识点的理解以及拓展。

该系列文章绝不是简单的阐述概念,而是将知识点互相串通,融入使用。欢迎大家关注,文章会持续更新!!!


第一章 C#概述

文章目录

  • Essential C# 6.0 学习笔记
  • 第一章 C#概述
    • 1.值类型和引用类型
    • 2.值类型和引用类型修改时的内存变化
    • 3.C# {0:x} 格式字符串
    • 4.unsafe关键字
    • 5.fixed关键字
    • 6.C# 装箱拆箱
    • 7.装箱拆箱为什么耗性能?
    • 8.C# String 类型比 StringBuilder 类型的优势是什么?
    • 9.Read() ReadKey()的区别
    • 10.字符串插值 和 复合格式化
    • 11.可扩展标记语言 XML
    • 12.应用程序接口
    • 13.托管执行(VES)和公共语言基础结构(CLI)
    • 14.CIL和ILDASM
  • 总结

1.值类型和引用类型

值类型和引用类型常见数据类型

值类型(Value types)
值类型变量可以直接分配给一个值。它们是从类 System.ValueType 中派生的。

值类型直接包含数据。比如 int、char、float,它们分别存储数字、字符、浮点数。当您声明一个 int 类型时,系统分配内存来存储值。

引用类型(Reference types)
引用类型不包含存储在变量中的实际数据,但它们包含对变量的引用。

换句话说,它们指的是一个内存位置。使用多个变量时,引用类型可以指向一个内存位置。如果内存位置的数据是由一个变量改变的,其他变量会自动反映这种值的变化。内置的 引用类型有:object、dynamic 和 string。

2.值类型和引用类型修改时的内存变化

static void Main(string[] args)    { int number=10; Console.WriteLine("值类型变量修改值:"); //获取栈上变量的地址 unsafe {     int* p = &number;     Console.WriteLine("Address of numbe:0x{0:x}", (int)p);     //修改了number “值类型” 的数据,对应地址也就发生了变化     number = 20;     int* p1 = &number;     Console.WriteLine("Address of numbe:0x{0:x}", (int)p1); } Console.WriteLine("引用类型变量修改值:"); string message = "Hello World!"; //获取堆上变量的地址 unsafe {     fixed (char* p = message)     {  Console.WriteLine("Address of message 0x{0:x}", (int)p);     }     //修改了string“引用类型”的数据,对应地址也就发生了变化     message = "Hello 荷兰猪小灰灰!";      fixed (char* p = message)     {  Console.WriteLine("Address of message 0x{0:x}", (int)p);     } }    }

Essential C# 6.0 C#学习笔记 第一章 C#概述
如图所示,我们对值类型修改,并不会改变变量地址
但是我们对引用类型进行修改的话,我们就会改变地址

下面对代码中出现的一些不常见关键字进行介绍

3.C# {0:x} 格式字符串

输出内容时的格式化字符,其中{n}代表占位符,例如:{0}说明使用后面的第一个参数代替到这个位置上输出。而后面的x则是具体的格式化控制信息,例如要输出的十六进制格式等。
其中:
c | C:代表货币格式
d | D:代表十进制格式
e | E:代表科学计数(指数)格式
f | F: 浮点格式
x | X: 十六进制格式。
还有这种输出方式是在C#语言中使用的,.net是一个开发平台

简单来说,就是格式转换

Console.WriteLine("Address of message 0x{0:x}", (int)p);

4.unsafe关键字

Essential C# 6.0 C#学习笔记 第一章 C#概述
为了保持类型安 全,默认情况下,C# 不支持指针算法。 不过,通过使用 unsafe 关键字,可以定义可使用指针的不安全上下文。

unsafe 在C# 程 序中的使用场合:

1)实时应用,采用指针来提高性能;

2)引用非.net DLL提供的如C++编写的外部函数,需要指针来传递该函数;

3)调试,用以检测程序在运行过程中的内存使用状况。

使用unsafe 的利弊:

好处:性能和灵活性提高;可以调用其他dll的函数,提高了兼容性;可以得到内存地址;

坏处:非法修改了某些变量;内存泄漏。

unsafe 与unmanaged的区别:

managed code是在CLR监管下运行的程序。以下任务由CLR来执行:管理对象内存,类型安全检测和冗余处理。

unmanaged code也就是能由程序员直接进行内存操作的程序。

unsafe 是介于managed和unmanaged之间的桥梁,它使得managed code也能使用指针来控制和操作内存。

5.fixed关键字

Essential C# 6.0 C#学习笔记 第一章 C#概述

fixed 语句可防止垃圾回收器重新定位可移动的变量
fixed 语句仅允许存在于不安全的上下文中。
fixed 还可用于创建固定大小的缓冲区。

实际这个关键字是为了兼容c的指针而存在的,fixed仅可用在不安全(unsaved)的上下文中为何需要使用fixed呢,因为这跟c#的GC(garbage collection)有关
C#的GC不仅仅是回收不使用的内存,它还会进行内存压缩,而这个内存压缩便是fixed的存在意义,因为每当进行GC,将会有大大小小的内存零零散散的被释放,因而产生许多内存碎片,当分配较大内存时这些碎片无法用上只能等待适合它们的内存分配请求,所以GC会进行内存压缩,当查找到较大的内存空间就会将标记的(还在使用的)对象的内存移到这个内存空间里,这样卡在标记对象内存之间的碎片就能重新合成一个大的内存空间方便下一次分配

当然标记对象的内存移动了它的引用会进行重定位,而指针却不会,因为指针只是耿直地记下内存的地址,一旦对象被重定位,指针就会变成一个野指针,多么可怕,所以才需要fixed固化内存,被fixed标记的变量或是固定大小的内存块将不会被进行重定位直到退出fixed上下文

下面在补充一下:需要初始化指针来固化内存,需要在fixed语句中初始化指针像这样:

fixed(T *p=&t){}

如果需要初始化多个相同类型的指针,可以在同一个语句中初始化多个指针:像这样:

fixed(T *p=&t,*p1=&t2){}

要是需要初始化多个不同类型的指针,可以使用嵌套像这样:

fixed(T *p=&t){ fixed(T2 *p1=&t2) {}}

需要注意的是,fixed语句中初始化的指针是只读变量,就是指针常量无法修改,要是想要修改它只能声明另一个指针,通过这个指针去修改。像这样:

fixed(T *p=&t){p++;//ErrorT *p2=p;p2++;//ok}

另外,在不安全模式中堆栈中的内存不受垃圾回收管理所以不需要固定。

总结一下:c#中指向堆内存的指针必须在unsaved和fixed的上下文中使用。

6.C# 装箱拆箱

装箱拆箱介绍
装箱是将值类型转换为引用类型 ;拆箱是将引用类型转换为值类型。

被装过箱的对象才能被拆箱

7.装箱拆箱为什么耗性能?

文章链接
进行一次装箱要进行分配内存和拷贝数据这两项比较影响性能的操作
拆箱严格意义上来说并不影响性能,但是在这里面的拷贝数据的操作就会用装箱一样影响性能

8.C# String 类型比 StringBuilder 类型的优势是什么?

如果是处理字符串的话,用 string 中的方法每次都需要创建一个新的字符串对象并且分配新的内存地址(这一点在标题2中得到验证
而 StringBuilder 是在原来的内存里对字符串进行修改,所以在字符串处理方面还是建议

用 StringBuilder 这样比较节约内存。
但是 string 类的方法和功能仍然还是比 StringBuilder 类要强。

string 类由于具有不可变性(即对一个 string 对象进行任何更改时,其实都是创建另外一个 string 类的对象)(书本P11有介绍),所以当需要频繁的对一个 string 类对象进行更改的时候,建议使用StringBuilder 类,StringBuilder 类的原理是首先在内存中开辟一定大小的内存空间,当对此 StringBuilder 类对象进行更改时, 如果内存空间大小不够, 会对此内存空间进行扩充,而不是重新创建一个对象,这样如果对一个字符串对象进行频繁操作的时候,不会造成过多的内存浪费,其实本质上并没有很大区别,都是用来存储和操作字符串的,唯一的区别就在于性能上。

String 主要用于公共 API,通用性好、用途广泛、读取性能高、占用内存小。
StringBuilder 主要用于拼接 String,修改性能好。

不过现在的编译器已经把 String 的 + 操作优化成 StringBuilder 了, 所以一般用String 就可以了

String 是不可变的,所以天然线程同步。
StringBuilder 可变,非线程同步。


9.Read() ReadKey()的区别

1.Console.Read()
返回的是读取字符的对应整数,为Int型
如果没有,则返回-1

    static void Main(string[] args)    { int x = Console.Read(); Console.WriteLine(x); Console.WriteLine(Convert.ToChar(x));    }

Essential C# 6.0 C#学习笔记 第一章 C#概述

Essential C# 6.0 C#学习笔记 第一章 C#概述
既然是获取一个字符,那么多个Console.Read就可以读取多个字符
Essential C# 6.0 C#学习笔记 第一章 C#概述
2.Console.ReadKey()
方法使程序等待按键,并且在按键之前阻止屏幕。简而言之,它获取下一个字符或用户按下的任何键。按下的键将显示在控制台窗口中(如果将进行任何输入过程)。

 static void Main(string[] args)    { Console.WriteLine("请按下Enter键退出程序"); while (Console.ReadKey().Key != ConsoleKey.Enter) ;    }

Essential C# 6.0 C#学习笔记 第一章 C#概述

10.字符串插值 和 复合格式化

字符串插值是编辑器将字符串花括号中的部分解释为可以嵌入代码(表达式)的区域,编辑器会对嵌入到表达式估值,并将其转换为字符串。

C#6.0之前的版本是通过复合格式化来定义输出格式
首先需要确定好格式字符串(3.C# {0:x} 格式字符串),来定义输出格式

格式项就是需要插入的参数
索引占位符就是0 1 2 3 4
格式项会根据索引占位符来找到位置

static void Main(string[] args)    { string firstname = "荷兰猪"; string lastname = "小灰灰"; //字符串插值 Console.WriteLine($"我的博客名为 {firstname}{lastname}"); //复合格式化 char a = 'a'; Console.WriteLine("a的十六进制为: Ox{0:x}", (int)a);    }

Essential C# 6.0 C#学习笔记 第一章 C#概述

11.可扩展标记语言 XML

菜鸟教程

什么是 XML?
XML 指可扩展标记语言(EXtensible Markup Language)。
XML 是一种很像HTML的标记语言。
XML 的设计宗旨是传输数据,而不是显示数据。
XML 标签没有被预定义。您需要自行定义标签。
XML 被设计为具有自我描述性。
XML 是 W3C 的推荐标准。

XML 用途
XML 应用于 Web 开发的许多方面,常用于简化数据的存储和共享。
Essential C# 6.0 C#学习笔记 第一章 C#概述

12.应用程序接口

一种数据类型的所有方法定义了这种类型的应用程序接口,API定义了软件程序如何与部件交互。
因此,不是一种数据类型,而是一组数据类型的所有API的结合组成了这组部件集合的API。例如,在.NET中,一个程序集包含的所有类型(以及这些类型的成员)构成这个程序集的API。同样,对于程序集的组合,每个程序集的API组合在一起构成一个更大的API,这个更大的API通常被称为框架。

.NET Framework就是指.NET包含的所有程序集对外暴露的API

一般地,API包括一系列接口和协议(或指令),他们定义了程序和一组部件交互的规则。实际上,在.NET中,协议本身就是.NET程序集执行的规则

13.托管执行(VES)和公共语言基础结构(CLI)

1.托管执行

首先,处理器不能直接解释程序集。程序集采用的是另外一种语言,即公共中间语言(CIL),简称为中间语言(CIL)
C#编辑器将C#源代码转换成这种中间语言(CIL)。为了将CIL代码转换成处理器能够理解的机器码,还要完成一个操作,就是虚拟执行系统(VES),VES也成为运行时,他根据需要来编辑CIL代码,这个过程称为即时编译或者JIT编译

假如代码在如上环境中运行,这些代码就被称为托管代码。这个执行过程就成为托管执行。之所以称为托管代码,是因为在 运行时(VES),管理着诸如内存分配、安全性、JIT编译等方面,从而控制了主要的程序行为。
而相反的就是,本机代码,也成为非托管代码。

2.公共语言基础结构

虚拟执行系统(VES)被约束在一个更大包容更广的规范内—CLI(公共语言基础结构)

CLI(公共语言基础结构)包含了以下规范:

VES(运行时)
CIL (公共中间语言)
公共类型系统(CTS)
编写通过CLI兼容语言访问的库的指导原则,这部分内容放在公共语言规范(CLS)中
使各种服务能被CLI识别的元数据(包括程序集的布局或文件格式规范)
基类库(BCL)

程序员可以在CLI(公共语言基础结构)实现的上下文中运行的程序中使用大量服务和功能,例如:

语言互操作性 :通过CIL来实现
类型安全 :检查类型间转换
代码访问安全性 : 程序集开发者的代码有权在计算机上执行的证明
垃圾回收 : 一种内存管理机制,自动释放“运行时”为数据分配的空间
平台可移植性:允许程序集在多种操作系统中运行
平台可以执行 BCL:提供开发者使用依赖的大型代码库,他们不必亲自编写这些代码

14.CIL和ILDASM

C#编辑器将C#源代码转换成这种中间语言(CIL),而不是机器码。处理器只能理解机器码,因此CIL代码必须先转换成机器码才能由处理器执行。

这里,我们就可以将程序集通过CIL反汇编程序将其析构成对应的CIL表示,从而查看CIL代码。这个就成为ILDASM(CIL反汇编程序),它可以对程序或者它的类库执行反汇编,显示CIL代码

事实上,我们可以将CIL自动反编译成C#,有一些免费工具
比如Red Gate Reflector、ILSpy、JustDecompile、dotPeek

使用这些工具,我们可以轻松阅读到程序集内部的代码!!!

总结

本章对C#进行了初步介绍,通过本章的学习,你熟悉了基本的C#语法。由于C#与C++风格语言的相似性,本章许多内容可能都是你所熟悉的。然而,C#和托管代码确实有一些独特性,比如会编译成CIL等。C#的另一个关键特征在于他是完全面向对象的。即使在控制台上读取和写入数据这样的事情,也是面向对象的。面向对象是C#的基础,这一点将贯彻全书。
下一章将探讨C#语言中的基本数据类型,并讨论如何讲这些数据类型应用于操作数来构成表达式