.NET Book Zero 读书笔记(一)(从C++的使用者角度学习C#)

一. 什么是.NET

.NET
.NET实际上是一堆动态链接库的集合,是一个大型的类的库,里面的东西可以用来做Web应用客户端应用.NET库并不是C#语言的专属,实际上任何语言都能使用.NET,但使用.NET库需要满足以下两个标准:

  • 使用.NET库的语言必须满足对应的语言要求
  • 使用.NET库的系统适用于第一条提出的编程语言,系统需要定义基本的数据成员(比如int,float,string)

上述第一条称为.NET Common Language Specification,简称CLS,第二条称为.NET Common Type System,简称CTS

CTS和CLS实际上并不是C#语言的专属,很多语言都满足这种需求,所以CLS和CTS也是Common Language Infrastructure(通用语言基础,简称CLI)的一部分。

C#语言与C++语言不同,C++语言经过编译后可以直接得到机器语言,但是像C#、JAVA这种语言与C++的编译机制不一样,.NET工程在编译后,不会生成机器语言,而是会生成一种类似机器语言的汇编语言,叫做Microsoft Intermediate Language(MSIL),简称IL(中间语言),这种中间语言还有一种更广泛的叫法,叫Common Intermediate Language(简称CIL)。


二. 机器语言、汇编语言与C++和C#之间的关系

首先有以下概念:

  • Machine Code:机器语言
  • Native Code
  • Unmanaged Code
  • Managed Code
  • Assembly Code:汇编码
  • Assembly Language

Machine Code
机器语言是最简单的,就是简单的二进制语言,没有人能读懂,直接由计算机硬件执行。

Navtive Code
这个概念有点混淆,有时候指的是Machine Code,有时候指的是Unmanaged Code。

Unmanaged Code
用C或C++语言写的代码,叫做Unmanaged Code,会被编译器直接Compile成Machine Code.

Managed Code
由C#,、VB.NET、 Java等语言写的代码,叫做Managed Code,这种语言会在虚拟的环境上(虚拟机)上进行编译(比如说C#对应的.NET、JAVA对应的.JAVA VM)
Perl, Python, PHP 和Ruby这些动态类型的语言严格意义上也是Managed Code,但是通常Managed Code还是指的是C#和JAVA这种大型商业语言。

Unmanaged Code 与 Managed Code的区别
Managed Code会自动执行GC(Garbage Collector),而且是通过引用来获取物体
(managed code “manages” the resources (mostly the memory allocation) for you by employing garbage collection and by keeping references to objects opaque.)
Unmanaged code 需要手动分配和释放内存,忘记释放内存会造成Memory Leak,过早释放内存会造成segmentation faults 。Unmanaged Code没有在运行时对空指针和数组越界的检查机制,所以这也是为什么我们用C#能轻易的知道哪里数组越界,但是在C++就很难知道具体的报错情况。

Assembly Code
汇编码(Assembly Code)是方便人们阅读的一种二进制代码,叫做Assembly Code,这种语言叫汇编语言,举个例子,C++中,对一个.cpp文件可以编译得到对应的.obj文件,.obj文件是Machine Code,是纯粹的机器语言,但是为了方便阅读,我们可以通过VS项目设置,在编译时生成相应的.asm文件,如下图所示:
在这里插入图片描述

通过使用Assembler(汇编器)这个程序,可以将Assembly Code转换成真正的Binary Code,这个转换过程与编译过程不同,因为这个过程的转换相当于从1-转换为-1,都是二进制代码的转换,本质上转换的内容并不多。之所以叫Assembly Code,是因为这些代码都会被Assembler转换成最后真正的二进制代码

通过Assembler将Assembly Code转换后二进制代码,有多种不同的类型,对于C/C++这种Unmanaged Code,得到的二进制代码就是真正的Machine Code,真正的硬件可以直接进行读取,而对于C#或JAVA这种Managed Code,得到的是一种特殊的二进制代码,这些二进制代码虚拟机上运行的,比如C#,汇编码会被转换为CIL(Common Intermediate Language)代码,在JAVA叫做JAVA byte-code。

CIL是汇编语言吗?
学完上述概念我还是有这个疑问,不过有一篇很好的微博解释了这个问题:
https://www.cnblogs.com/eaglet/archive/2009/06/02/1494290.html
这个问题略有争议,首先,汇编语言是可读的,而CIL也是可读的,CIL在定义上讲是汇编语言,但在C++里面我们所说的汇编语言转换后的机器语言,是直接运行与真正的硬件上的,在C#和JAVA这种的CIL转换成二进制语言后,是运行与虚拟机上的,有人觉得汇编语言是低级的机器语言,所以会觉得CIL略显高级,不能属于汇编语言,但总的来说,CIL也是汇编语言。


三. .NET Runtime

运行.NET程序,需要两个环境前提,一是安装.NET runtime,二是安装.NET Framework Sofware Development Kit(SDK)

在命令行可以把C#的编译器加入到环境变量之中,具体参考链接https://blog.csdn.net/yual365/article/details/87283945。
可以用notepad命令写一个.cs文件,代码很简单,如下所示,用csc(csc是C#语言的编译器,一般在.NET FRAMEWORK文件夹下)运行后,可以得到一个.exe文件

class FirstProgram
{
    public static void Main()
    { 
         System.Console.WriteLine("Hello World!");
    }
}

对于C#生成的.NET的.exe,SDK提供了一个工具,叫做ildasm.exe(IL Disassembler),该工具也叫反汇编工具,打开该软件,可以利用该软件将之前生产.exe文件转化为可阅读的CIL语言,如下所示:
在这里插入图片描述
其中,ldstr是load string的缩写,call来调用函数,利用.NET的CLR(Common Language Runtime)可以将上述CIL转化为真正的机器语言

如果要在命令行调用的话参考ILDASM and ILASM, how use them?,使用下面这个命令就行:

ildasm Unity.Cecil.dll -output=MyL.il

如果没有生成.i文件,可能是系统权限弄的,可以考虑复制一份出去再操作

还可以下载一个ILSpy.exe,打开exe把DLL拖进去直接就能转成C#代码,很好用


四. Assembly

Assembly本意是集合的意思,Assembly可以包含多个文件,在C#中代表着已经编译好的代码库,也就是由CIL组成的代码,会再被CLR转换为真正的机器语言,Assembly分为两种:

  • process assemblies (EXE)
  • library assemblies (DLL)

五. MSBuild

.csproj
在Visual Studio中使用.NET生成的项目,会有一个格式为.csproj的文件,这个文件具有以下特点:

  • 是XML文件
  • 包含了对cs文件的引用
  • 包含了VS维护和编译项目的信息

MSBuild is the build platform that enables all build activity in the Visual Studio world.
主要为:

  1. 所有的.csproj文件都是msbuild文件(The .csproj files (every C# project) are msbuild files)
  2. 按F5时,相当于调用msbuild.exe来进行编译(When you hit F5, you basically (oversimplifying) call msbuild.exe and passing in your .csproj file.),MSBuild软件会调用Csc和其他的一些工具。

比如说在.csproj所在的文件夹对应目录的命令行,输入msbuild myprogram.csproj,会自动编译该项目,并在对应路径生成.exe文件。

如果需要用命令行来buildC#工程,可以使用MSBuild程序来进行。


六. C#中的Field和Property

在C++中,为了安全,对于一个类的数据成员,往往是将其设置为private,然后使用对于的Get和Set功能的API去调用和写入值,在C#中,可以通过Field和Property来实现对应的功能,用一句最简单的话就是,Properties expose fields,Property是field的接口,如下述代码所示:

public class MyClass
{
    // this is a field.  It is private to your class and stores the actual data.
    private string _myField;

    // this is a property. When accessed it uses the underlying field,
    // but only exposes the contract, which will not be affected by the underlying field
    public string MyProperty
    {
        get
        {
            return _myField;
        }
        set
        {
            _myField = value;
        }
    }

    // This is an AutoProperty (C# 3.0 and higher) - which is a shorthand syntax
    // used to generate a private field for you
    public int AnotherProperty{get;set;} 
}

注意:field成员一般(或者说总是)被声明为private,C#3.0以后支持只写Property而不用在类里再写一个private的field,会自动生成对应的field

更多详情可以参考StackOverflow


七. C++和C#的编译方式

对于库文件,或者是lib或者是dll,C++ 中先是把自己的cpp文件编译为.obj文件,(编译阶段只需要用其头文件知道函数使用方式就好),然后用Linker去链接他们,而C#就没有这么多过程,之前提到了,C#就是一堆dll的集合,C#的编译器,或者说CLR自带这些dll,所以省去了Link这一操作,相应的函数都是CLR自带的。C#中,大多数类与结构体的实现在mscorlib.dll中.

对于第三方库文件,需要在项目中给CLR添加引用,(如果是命令行用/r),如下图所示:
在这里插入图片描述

八. C#中的using指令

代码如下所示

using System;	//这玩意儿叫 directive
using System.name1;

using System的含义是告诉编译器,如果找不到特定的函数,比如说Console.WriteLine(),就尝试把System.加到函数前面再去寻找这个函数。注意,using System这一句话跟头文件没有关系,因为C#没有头文件的说法,也跟库文件没有关系,这一行指令与引用无关,using System只是一种单纯的文本简化的写法,如果不要这句话,换成System.Console也可以,只是告诉我们System是一个命名空间,如果使用的函数找不到,就去这里找。

using 指令可以帮助实现文本简化,如果有两个namespaceA和B,二者都定义了print函数,在一个cs文件需要用到两个print函数,这个时候,这么写:

======= 这种写法是错误的,因为并没有说明到底调用A.print还是B.print ========
using A;
using B 
void Func()
{
	print();
}
====== 分别调用======

void Func()
{
	A.print();
}

如果A、B名字太长,可能会很麻烦,比如A是KareninaSoftware.HandyDandyLibrary,B叫BovaryEnterprises.VeryUsefulLibrary,每次这么写就太麻烦了KareninaSoftware.HandyDandyLibrary.print(),可以简化成:

using Emma = BovaryEnterprises.VeryUsefulLibrary;
using Anna = KareninaSoftware.HandyDandyLibrary;
void Func()
{
	Emma.print();
}

写DLL时,一定要用自己的namespace把它包含起来,否则别人用的时候报错了毫无办法。


九. String

C#的String和C++的String
在C++里,可以用char数组或string类来表示string,C++用数值为00的字节作为字符串结束的标识,在C#中,String是一个类,一个string字符由Char类的成员组成,也可以用小写的string,后者是System.String的别名,同理,char是System.Char的别名,int是System.Int32的别名。

二者的区别在于:
Strings are immutable and hence the characters of strMyCSharpString can‘t be altered(C#的string不允许针对特定索引位置进行改变)

C#的string的索引位置只可读,不可写,具体是这么定义的:

        public char this[int index] { get; }
        // 所以C#里,这么写会报错
        str1[0] = 'h'; // Error

这也意味着,对于一个string a,很多C++中操作string的函数是直接改变a的值,而C#中则是创建一个新的string,不改变a的值。举个例子,代码如下:

string a; // 或String a
// 把a里的字母改为大写

// 在C++中这么写
_strupr(a); // a中的字母改为大写

// 在C#中这么写
a.ToUpper(); // a不变
String b = a.ToUpper(); //b是a的字母改为大写后的结果
a = a.ToUpper(); // 或者这么写,原来的值会由于未被引用而被GC回收

string改变特定索引位置的值
可以用对应的api

string str = "abcdifg";
str[4] = 'e'; // Won't work! 
str = str.Replace('i', 'e');	// 替换元素
str = str.Remove(4, 1);	// 删除第四个索引的一个元素
str = str.Insert(4, "e");	
str = str.Remove(4, 1).Insert(4, "e");	// 同时删除和添加操作
str = str.Substring(0, 4) + "e" + str.Substring(5);	// 取子串

也可以先转成char数组,再转回去

char[] buffer = str.ToCharArray();
buffer[4] = 'e';
str = new string(buffer);

C#中的String不允许单斜杠的存在,如下图所示,在表示路径时需要用“\”
在这里插入图片描述
如果不想让string里面的内容发生改变,那就在string前面加上@符号,如下图所示:
在这里插入图片描述
如果想要在string里面显示引号,对于双引号,原本的"A"要写成"“A”“, 单引号不变,如下图所示:
在这里插入图片描述
也可以借助转义符号\来输出”",如下图所示:
在这里插入图片描述

ToString方法
C#中每个实例对象都继承于Object类,所以定义了ToString方法,ToString有以下四种输入类型,可以很灵活的进行格式的转换:

string ToString()
string ToString(string format)
string ToString(IFormatProvider provider)
string ToString(string format, IFormatProvider provider)

举一些具体使用的例子:

    Console.WriteLine("Currency C3: " + Math.PI.ToString("C3"));	// 表示货币,输出¥3.142
    Console.WriteLine("Exponential E3: " + Math.PI.ToString("E3"));	// 科学记数法(Exponential format)
    Console.WriteLine("Fixed-Point F3: " + Math.PI.ToString("F3"));	// 小数点保留三位,输出3.142
    Console.WriteLine("General G3: " + Math.PI.ToString("G3"));	// 三位有效数字
    Console.WriteLine("Number N3: " + Math.PI.ToString("N3"));	// 每千位会加一个逗号
    Console.WriteLine("Percent P3: " + Math.PI.ToString("P3"));		// 百分数
    Console.WriteLine("Round-Trip R3: " + Math.PI.ToString("R3"));	//	获取所有位
    Console.WriteLine();
    Console.WriteLine("Fixed-Point F3: " + 12345678.9.ToString("F3"));
    Console.WriteLine("General G3: " + 12345678.9.ToString("G3"));
    Console.WriteLine("Number N3: " + 12345678.9.ToString("N3"));
    Console.WriteLine();
    Console.WriteLine("Decimal D3: " + 55.ToString("D3"));
    Console.WriteLine("General G3: " + 55.ToString("G3"));
    Console.WriteLine("Hexadecimal X3: " + 55.ToString("X3"));

输出结果如下:
在这里插入图片描述
ToString不只是有一个参数,其他的参数可以规定一些文本的格式,举个例子,可以规定输出不同的货币。
在这里插入图片描述
String里常见的实用操作
这种替换好像还挺常见的,差不多就是把它当宏用了:

Console.WriteLine("{0} times {1} equals {2}", A, B, A * B);
Console.WriteLine("{2} equals {0} times {1}", A, B, A * B); // 逆序也可以
Console.WriteLine("{0} times {0} equals {1}", A, A * A); // 反复出现也可以
Console.WriteLine("{1} times {2} equals {4}", C, A, B, D, A * B, E); // 省去第三个也可以
String s = String.Format("{0}", "Hello");
Console.WriteLine("{0} is a curly bracket {{ }} ", "here"); // 如果想输出{ }需要写成{{ }}

Console.WriteLine("Please deposit {0} dollar{1}.", dollars, dollars == 1 ? '' : 's'); // 当只有一刀时,dollar后面不加s

配合上面的{0}写法,可以使用分号加字母规定输出数字的格式:

    double A = Math.PI;
    double B = Math.PI;
    Console.WriteLine("{0:N2} times {1:N3} equals {2:N4}", A, B, A * B);
	Console.WriteLine(A.ToString("N2") + " times " + B.ToString("N3") +
 " equals " + (A * B).ToString("N4"));

结果输出如下:
在这里插入图片描述

还可以规定{}里字符串的最短长度,如果不够就用空格补齐,即使超过这个长度,也会输出字符串,不会截断:

Console.WriteLine("{0,5:N2} times {1,5:N2} equals {2,10:N4}", A, B, A * B);	// 第一个A至少占一个5个字符,A*B的输出至少占10个字符

注意,C#使用Console.WriteLine时,实际上是调用String.Format函数,再把得到的string打印出来。

另外,还有个有意思的地方,在C#里Debug的时候,下方Watch窗口里会显示类下的ToString返回的内容,如下图所示:
在这里插入图片描述


十. C#中的转义字符(Escape Character)

有这几种转义字符\t,\n,\r,\r\n\t代表加Tab,\n代表换行符,\r是从最老的打字机引入的概念,表示回到本行的开始位置,\r\n连用,表示跳到下一行,并且返回到下一行的起始位置,额外提一下\r,代表回到本行的开始位置,如下所示:

string s = "1234\r567";

最后输出的s是5674,前面的123被覆盖掉了。


十一. C#中的Constructor

C#也有构造函数,都是在调用new关键字时调用的构造函数,类与结构体都可以定义构造函数,如下所示:

int index = new int(); // 默认构造函数会把index设置为0
int index = new System.Int32(); // int是Int32的别名

十二.C#的Object类

Object类是所有.NET的类与结构体的鼻祖,都继承与它,Object里定义了ToString()的虚函数,所以所有的C#内的对象都有.ToString函数,这也是为什么,我们可以轻易的将任何类型的数据与string对象相加,因为这些类型都会隐式调用其.ToString函数,如下图所示:
在这里插入图片描述


十三. C#中的Environment类

C#为实现跨平台定义了一个类Environment,这个类有很多跨平台相关的功能:

// 跨平台保证成功换行
string NL = Environment.NewLine;
string res = "There once was a coder named Otto" + Environment.NewLine;

还有更多的内容:

    class ShowEnvironmentStuff
    {
        static void Main()
        {
            // 获取当前目录
            Console.WriteLine("My Documents is actually " +
            Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments));
            Console.WriteLine();
            // 获取当前设备已运行的时间
            int msec = Environment.TickCount;
            Console.WriteLine("Windows has been running for " +
            msec + " milliseconds");
            Console.WriteLine("\tor " + msec / 3600000.0 + " hours");
            Console.WriteLine();
            // 获取当前的操作系统版本和.NET版本
            Console.WriteLine("You are running " + Environment.OSVersion);
            Console.WriteLine("\tand .NET version " + Environment.Version);	// Environment.Version类型是Version而不是String,这在C#里是很常见的
            Console.WriteLine();
        }
    }

十四. C#中的static

static可以用于类或类内的成员,用于类的成员或成员函数时,表示该内容是类共用的,当用于类时,表示该类是静态的,不可以被实例化,所以该类不可以调用ToString成员,因为没有实例

        class A
        {
            private class B
            {
            }
        }

        static class C
        {
            private class B
            {
            }
        }

        static void Main()
        {
            A a = new A();	// 编译正确
            C c = new C();	// 编译错误,不可以实例化一个static类的对象
        }

Int32和Double类不仅仅是有实例化的函数,也有少量的静态函数,比如Parse函数,可以将字符串类型的数字提取出来:

  Console.WriteLine(Int32.Parse("42132"));	// 正确
  Console.WriteLine(Int32.Parse("2124f"));		// 运行时报错,字符串格式不对
  Console.WriteLine(Int32.Parse("asf"));		// 运行时报错
  
  // 为了保证不报错,可以验证字符串的格式
  int a = 1;
  Int32.TryParse("asf", out a);
  Console.WriteLine(a);	//	如果格式错误a会变成0

也可以这么写,不过比较古怪:

 int a = int.Parse("123");	// int是Int32的别名


十五. C#的基础数据类型

在C++里,int类型最低4个字节,short最低2个字节,long类型最低4个字节,如下图所示:
在这里插入图片描述
但在C#中,这些数据结构都被定死了大小,跟平台无关,C# short is always 16 bits wide, the int is 32 bits wide, and the long is 64 bits wide. 、

隐/显式转换
低精度的可以隐式转换为高精度的,但是高精度的必须显式转换为低精度的类型:

    int a = 5; // 正确
    long b = a;	// 正确
    int c = b;	// 编译错误
    int d = (int)b;	// 正确

正常在浮点数后面加f,同样,在u类型后面可以加U或u,在long类型后面加L或l,写法相当灵活:

// 以下写法都是对的
uint a1 = 54u;
uint a2 = 54U;
long b1 = 54l;
long b2 = 54L;
ulong c1 = 54UL;
ulong c2 = 54Ul;
ulong c3 = 54ul;
ulong c4 = 54uL;
ulong c5 = 54LU;
ulong c6 = 54Lu;
ulong c7 = 54uL;
ulong c8 = 54lu;

C#里无法表示8进制和2进制数
C++里用0x代表16进制数,用0表示8进制数,(在C++14中支持2进制数,用0b表示),C#里只能表示16进制数,如下所示:

// 0x或0X均可
int hex1 = 0X4AbC;
int hex2 = 0x4AbC;

byte和sbyte
byte相当于C++的unsigned char类型,sbyte相当于C++的char类型

const
C#中的const很类似于一个宏,const常量必须在初始化的时候被赋值,const常量不占任何运行时的空间,只是在编译时替换而言,const must be available at compile time

checked
使用checked可以使得当存在overflow/underflow时报错,可以这么写B = unchecked(5 * A);,也可以用括号指定一个区域,举个例子,3443243 * 3443243 = 11,855,922,357,049‬:
以下代码不会报错,但输出结果不对:
在这里插入图片描述
使用checked之后可以提示这种错误:
在这里插入图片描述
但是用checked会降低效率,其实在项目属性里也能在Build的高级选项里选择是否检查数据溢出,如果不想在检查溢出的环境里检查特定的数据,可以用uncheck,写法是一样的

C#中的符号位数据结构不符合CLS标准
CLS(Common Language Specification)里规定了使用,NET库的最低语言要求,但里面没有规定要实现带有unsigned类型的数据成员,所以C#中以下变量ushort、uint和ulong是不符合CLS标准的,如下图所示:
在这里插入图片描述
在这里插入图片描述
这会造成一个很大的影响,当写C#用于Dynamic Link Libraty(DLL)库文件时,需要避免使用带有unsigned符号的数据类型,具体有以下几种:

  • 导出public函数的参数和返回类型都不允许使用unsigned符号的数据类型
  • 不允许声明public的unsigned符号数据类型的Property和Field成员

比如说这里就有一个问题关于此问题报错的链接:https://stackoverflow.com/questions/14013920/returning-a-unsigned-long-array-from-c-dll-into-c-sharp-as-uint-why-is-a-marsh

Char
char类型占2个字节,Char类型里定义了很多函数,可以来判断是否是数字、字母等,比如IsControl, IsSeparator, IsWhiteSpace, IsPunctuation, IsSymbol, IsDigit, IsNumber, IsLetter, IsUpper, IsLower, IsLetterOrDigit, IsSurrogate:

Char.IsControl(str[index]);	// 是否为control字符
Char.IsControl(str, index);

Doube和Float
float是类Single的别名,double是类Double的别名:
写法有多种,都得要看懂
在这里插入图片描述
注意,正常低精度像高精度数据类型转换都是隐式转换,高精度向低精度类型都是显式转换,但是用于浮点数可能有点特殊,因为浮点数可以表示无穷大和出错的数据,所以浮点数转向别的一般都需要显示转换。

Decimal
Decimal为SQL Server、MySql等数据库的一种数据类型,可以在定义时划定整数部份以及小数部分的位数,以保证存储的数据==完全精确=。
C中没有这个类型,在做数据库C语言开发时,可以将此类型数据定义为double类型数据。这种类型经常用于金融、利率等方面。

decimal m = 55.23m;

Decimal类型一共128个位,也就是128/8 = 16个字节,其中1个位为符号位,96个位为数字位,还有5位用来表示小数点的位置(准确的说是28位,因为2^96 = 7.9228163e+28,最多28个数字),也可以叫做scaling factor,因为代表着10^(-scaling factor),剩余的26位是无效位

举个例子,对于12.34,其数字位是1234,1234 = 0x4D2,scaling factor为-2,代表着1234 * 10^(-2),可以看到,decimal类型的数字,原值是多少,就是多少,而不会像浮点数一样,总有精度问题,所以decimal类型适合用于金融,因为这是完全精确的数字记法

还可以在C#里这么构造一个decimal

// 前三个数是三个int,一共加起来是32 * 3 = 96位,后面是符号位和10的指数位,scale在0~28之间
// 注意输入的虽然是int,但是会被当做unsinged int处理(为什么不输入uint类型,是因为CLS中没有unsigned int这个类型)
decimal m = new decimal(low, middle, high, isNegative, scale); 

//12.34567
new decimal(1234567, 0, 0, false, 5)//最大的decimal数字,因为-1是负数,负数在计算机是用补码表示的
//1000 0000 0000 0000 0000 0000 0000 0001(32位iint的-1原码)
//取反码加一表示成补码后,正好是0xffffffff
new decimal(-1, -1, -1, false, 0); 

//1 × 10^(-28)
new decimal(1, 0, 0, false, 28);

还可以反向解析出Decimal数据:

// 解析出decimal数据
//  A[0], A[1], andA[2]) are the low, medium, and high components
int[] A = Decimal.GetBits(m);

//The fourth element contains the sign and the scaling factor. Bits 0 through 15 are 0; bits 16 through 23 contains a scaling factor between 0
//and 28; bits 24 through 30 are 0; and bit 31 is 0 for positive and 1 for negative. In other words, 
//if A[3] is negative, the decimal number is negative. The scaling factor is:
// A[3]最右边16位全是0,是无用数据
(A[3] >> 16) & 0xFF

注意几点:

  • CIL 并不包含Decimal类型
  • 不可以将check和uncheck用于Decimal类型
  • 虽然decimal类型的范围比float小,但是由于decimal的精度比float高,所以二者相互转换时,也需要显示转换

Math
Math类位于System下,有两个常数:PI和E(对数那个e = 2.7…)

有Abs函数,MAX,MIN函数,还有Sign函数(用来判断正负号的),关于Abs函数注意这一点:

 // 下面两句都会报错,OverflowException,因为int类型最小值的绝对值比最大值的绝对值要大一位
 float x1 = Math.Abs(Int32.MinValue)
 float x2 = Math.Abs(Int16.MinValue);

还有用于一些用于乘法和除法的API:

long l = Math.BigMul(i1, i1);	// 防止int乘法溢出,用long类型作为结果
long l = (long)i * i;	// 这样写跟上面一行作用是一样的
c = Math.DivRem(a, b, out d); // c = a/b, d =a%b

Math里面用于取整的函数:

Math.Floor(3.5); // returns 3
Math.Ceiling(3.5); // returns 4
Math.Floor(-3.5); // returns  -4
Math.Ceiling(-3.5); // returns -3

// Floor : round toward negative infinity
// Ceiling : rounding toward positive infinity
// (int): rounding roward zero,也叫truncation(截断)
int(1.4); // returns 1
int(-1.4); // returns -1

// Math.Round(): 取整函数
Math.Round(4.5)// returns 4
Math.Round(5.5)// returns 6(0.5的时候优先返回偶数)
Math.Round(5.5, MidpointRounding.ToEven); // returns 6 0.5的时候返回偶数,等同于上句话
Math.Round(5.5, MidpointRounding.AwayFromZero); // returns 5
Math.Round(5.285, 2); // returns 5.28
Math.Round(5.285, 2, MidpointRounding.AwayFromZero); // returns 5.29

Math中三角函数和反三角函数,统一由弧度制来表示,如下所示:

Math.Sin(Math.PI * angle / 180);

反三角函数是在对应的API前面加上字母A,如下所示:
在这里插入图片描述



十六. Expression and Statement

Expression: Something which evaluates to a value. Example: 1+2/x
Statement: A line of code which does something. Example: GOTO 100



十七. C# 中的运算符和表达式

运算符优先级
在这里插入图片描述

C#中的位运算符
C++中的布尔值之间的逻辑判断是通过&&||实现的,单个符号&代表位运算符,而C#中的&符号,不仅代表了位运算符,还可以代表逻辑运算,也就是说C++里是不能用&表示逻辑运算符的,但是C#中可以,举个例子:,举个例子:

    int x = 0x0001;	
    int y = 0x0002;
    bool a = true;
    bool b = false;
    Console.WriteLine(a && b); // 打印false
    Console.WriteLine(a & b); // 打印false
    Console.WriteLine(a ^ b); // 打印true
    Console.WriteLine(x | y); // 打印3

Switch case在C#和C++中的差异
C++中,可以这么写:

switch(a)
{
	case 3:
	 b = 7;
	 // Fall through isn’t allowed in C#
	case 4:
 	c = 3;
 	 break;
	default:
 	b = 2;
 	c = 4;
	 break;
}

但在C#中,就得用goto:

switch (a)
{
	case 3: //如果没有任何操作
	case 4:
 	b = 7;
 	c = 3;
	 break;
	default:
	 b = 2;
 	c = 4;
	 break;
}

switch (a)
{
	case 3: //使用goto来代替
	b = 7;
	goto case 4;
	case 4:
 	b = 7;
 	c = 3;
	 break;
	default:
	 b = 2;
 	c = 4;
	 break;
}

foreach in C#
the foreach statement works with collections that implement the IEnumerable interface,foreach里的元素必须实现IEnumerable接口。

关于foreach值得注意的一点是,foreach里的取得的每一个元素本身的值是只读的,但里面的值是可以改的,具体的可以举个例子:

// 下面这个foreach会报错
foreach (var item in MyObjectList)
{
    item = Value;
}
// 但是这个就不会
foreach (var item in MyObjectList)
{
    item.Value = Value;
}

具体解释见下:
foreach is a read only iterator that iterates dynamically classes that implement IEnumerable, each cycle in foreach will call the IEnumerable to get the next item, the item you have is a read only reference, you can not re-assign it, but simply calling item.Value is accessing it and assigning some value to a read/write attribute yet still the reference of item a read only reference.

跳转指令(Jump Statements)

  • return
  • goto
  • break
  • continue
  • throw

goto
前面提到了goto可以用于switch语句,还可以在C#中设置label,然后用goto跳转到那里,代码如下所示:

static void Main()
{
    for (int i = 0; i < 300; i++)
    {
        if (i == 55)
        {
            goto Label;
        }
    }
    Label: // 用冒号声明一个可以跳转的label
    return ;
}

注意,goto必须在label的同一block或者是子block里,不然goto的时候如果label不存在就尴尬了:

for (int i = 0; i < 300; i++)
{
    if (i == 55)
    {
        goto Label; // Error, No such 'Label' within the scope of goto statement
    }
    for (int j = 0; j < 300; j++)
    {
        Label:
    }
}


十八. Stack and Heap

Heap
Windows下的每一个进程都有一个Local Heap,由其内部的所有线程共享

== A reference type is stored on the stack as a reference to an area of memory allocated from the heap==
举个例子,String 类型的变量a,其引用的地址在Stack里,但是其真正的数据在Heap上。
这样做是为了提高效率,因为Heap上找数据很慢,为什么很慢,这里有两个原因:

  • Heap上分配内存时,经常进行内存整合,特别是内存不太够用的时候
  • Heap需要进行GC

Stack不适合大小会变的数据

Memory allocated from the heap is always initialized to zero
比如int[] A = new int[100],初始值都是0,至于引用类型的变量,同样也是这样,string[] strs = new string[10则下面的strs[5] == null是true

C#中Reference和Pointer的区别

  • The reference is managed, and the memory it references is known as the managed heap
  • The program can‘t manipulate or perform any arithmetic on this reference
  • most importantly, if a memory block allocated from the heap no longer has any references pointing to it, that memory block becomes eligible for garbage collection

第三条就提到了GC的本质,对于堆上的物体,如果没有任何指向它的引用,则其会被GC销毁。

GC
并不是说只有创建类这种显示的方法才会有GC,下面这种隐式的方法一样会有GC,因为实际上等式右边一直在Heap上创建类的实例:

public class TestE1 : MonoBehaviour
{
    string s;
    // Update is called once per frame
    void Update()
    {
        for (int i = 0; i < 280; i++)
        {
            s = string.Format("HHAHAHA");
        }
    }
}

public class TestE2 : MonoBehaviour
{
    string s;
    // Update is called once per frame
    void Update()
    {
        for (int i = 0; i < 280; i++)
        {
            s = string.Format("{0}/HHAHAHA", i);
        }
    }
}

有意思的是,拿Unity进行测试,TestE2的GC差不多是TestE1的三倍,我觉得应该是GC并不是立马进行的缘故,一段时间后原来的“HHAHAHA”还没被释放,又被引用了,这就省了很大一部分的GC

String A = null; // A的引用值为0,heap上没有任何反应
String B;// Stack上分配了记录引用值的空间,但需要进行初始化
String C = ""// Stack和Heap上都分配了对应的内存

举个例子,下面语句并没有用到new,但是却是在堆上进行了内存的分配

double[] D = { 3.14, 2.17, 100 };
// 上面这句话其实等同于:
double[] D = new double[]{ 3.14, 2.17, 100 };


十九. Array

Any array is implicitly an object of type System.Array.

int[] A; // 声明一个数组,A是一个引用类型,会在Stack上预留一块存Array引用的空间,但还没初始化
A = null; // 初始化A,这个时候A = 0,Heap上还是没有分配内存
A = new int[100]; // Heap上分配了100个32位的整数数组,改A作为首地址的引用,在堆上初始化的,其初始值都是0

以下是几种常见的声明方式:

double[] D = new double[3] { 3.14, 2.17, 100 };
double[] D = new double[] { 3.14, 2.17, 100 };
double[] D = { 3.14, 2.17, 100 };
// 但是这么写不行
double[] D;
D = { 3.14, 2.17, 100 }; // Won’t work!

多维数组

int[,,] three;
three = new int[8, 5, 3];
 three.Rank // returns 3,代表数组的维度
three.GetLength(1); // returns 5
three.Length; // returns 8*5*3 = 120

string[,] senators = new string[50,2]; // That‘s 50 states and 2 senators each
int[,,] arr = new int[3, 2, 4] {{{ 8, 3, 4, 2}, { 7, 4, 1, 2}},
 {{ 2, 7, 3, 6}, { 5, 1, 9, 0}},
{{ 0, 4, 9, 7}, { 3, 9, 8, 5}}};

// 也可以这么写
int[,,] arr = {{{ 8, 3, 4, 2}, { 7, 4, 1, 2}},
 {{ 2, 7, 3, 6}, { 5, 1, 9, 0}},
 {{ 0, 4, 9, 7}, { 3, 9, 8, 5}}};

多维数组支持低维度的数组的大小不是统一的

string[][] jaggedArray = new string[4][];
jaggedArray[0] = new string[5];
jaggedArray[1] = new string[2];
jaggedArray[2] = new string[8];
jaggedArray[3] = new string[4];

// 也可以指么写
string[][] jaggedArray = new string[4][]
 {
 new string[] { "Jill", "Alice", "Billy", "Judy", "Sammy" },
 new string[] { "James", "Ellen" },
 new string[] { "Steve", "Sue", "Bernie", "Rich",
 "Chris", "Erika", "Michelle", "Alyssa" },
 new string[] { "Jack", "Diane", "Bobby", "Sally" }
 };

const和static readonly的区别

C#中,const是在编译时期就进行了替换,而static readonly是运行时的,二者基本差不多
A const is evaluated during compilation and the value is substituted wherever it‘s used. A static readonly field is evaluated at runtime. But in practice they‘re pretty much the same

the readonly modifier is applicable only for fields, and can‘t be used for local variables

关于函数重载
C++中可以出现同名函数,只要参数个数、参数类型和参数顺序不同,都认作是函数重载,注意返回值类型不同不可以算函数重载

ref和out关键字
ref跟out差不多,传入参数时不再传值,而是传引用,ref传入的参数必须初始化,但out的不需要。



二十. delegate和event

假设有两个类A和B,A负责获取外界的information,然后传递给B,为了实现这种功能,有以下做法:

  • A中设计一个bool值,用来表示是否获取到了新的外界信息,B不断去check这个布尔值,如果为true,那么B去调用A的GetInfomation()函数,这种方法叫做polling
  • B中设计一个方法,当A接收到数据时,直接调用B的对应的方法

A需要在得到数据的第一时间,把数据传给B,这一场景,C#提供了相应的keyword,叫做event,于是做法就变成了:

  • A定义一个event
  • B定义一个event handler,也就是一个函数,然后把这个event handler登记到A的event里

然后具体怎么写呢,先在A中定义对应的event:

class A{
	// EventHandler是C#里提供的一个委托, 对应的函数签名为void(object, EventArgs)
	// Event 类似于观察者模式中的被观察者(或者类比于主播)
	public event EventHandler InformationAlert;
}

注意这里的EventHandler并不是我自己定义的类型,而是C#的.Net里固有的东西,代码如下所示:

namespace System
{
    //
    // Summary:
    //     Represents the method that will handle an event that has no event data.
    //
    // Parameters:
    //   sender:
    //     The source of the event.
    //
    //   e:
    //     An object that contains no event data.
    public delegate void EventHandler(object sender, EventArgs e);
}

可以看到,EventHandler就是一个委托函数,第一个参数是object类型,也就是任意的C#对象,第二个参数EventArgs同样是C#的System里提供的类型,是C#里很多用于Event连接的类的基类( it is the base class for many derived classes that are used in connection with events)

所以这句话public event EventHandler InformationAlert什么意思呢,event参数表明这是一个event,EventHandler规定了,被通知的类(比如B),定义的EventHandler的函数签名。也就是说,B要想收到A的event的通知,必须声明以下函数:

// 观察者(或者说订阅主播的人), 必须是这种格式的函数
void MyInformationAlertHandler(object sender, EventArgs e)//名字不重要,但是必须是void返回值,参数是object和EventArgs
{
// process the event
}

OK,A的event声明好了,B的EventHandler也声明好了,剩下的就是将B登记到A里了,类似于观察者模式里的,Subject.List.AttachObserver的方式,登记EventHandler和解除登记的写法如下:

// 注意这里是用的A的实例a进行调用的
a.InformationAlert += new EventHandler(MyInformationAlertHandler);
// 解除登记
a.InformationAlert -= new EventHandler(MyInformationAlertHandler);

.NET 2.0以后有了更简单的写法,如下所示:

// 登记
a.InformationAlert += MyInformationAlertHandler;
// 解除登记
a.InformationAlert -= MyInformationAlertHandler;

那么,当A的对象a的event产生的时候,就会通知所有登记过的对象,调用各自的EventHandler:

//注意, 这是event通知订阅者的写法
if(InformationAlert != null)
	InformationAlert(this, new EventArgs());//就会把a本身的event和new出来的EventArgs作为参数传给相应的EventHandler

注意这里的,EventArgs只是一个参数,如果想要传多个参数,则需要创建继承于EventArgs的子类

先举个最简单的例子:

public class A
{
    public static event Action s_OnFunctionCalled;

	public void Function()
	{
         if (s_OnFunctionCalled != null)
             s_OnFunctionCalled.Invoke();// 会调用所有listen此event的Actions
	}
}

public class B
{
    Action action = (() => { Debug.Log("ss");});
	
	public void RegisterListeners()
	{
		// 先-后+是为了防止重复登记Action	
        A.s_OnFunctionCalled-= action;
        A.s_OnFunctionCalled+= action;
	}
}

event常常用于处理外界的Input,比如鼠标点击、键盘按键等,这里举个更具体的例子,C#的.NET系统设计了一个计时器(类似于古代打更的更夫),隔一段时间可以给予一个通知,叫做Timer。Timer本身就是一个event,叫做Elapsed,意思是时间流逝的意思。

更夫的名字叫Elapsed,实现类是Timer,定义在Timers的命名空间下,代码如下:

namespace System.Timers
{
	public class Timer : Component, ISupportInitialize
    {
		//一个用于通知的更夫,通知的方式是通过ElapsedEventHandler对应的函数签名, 对应的函数对象
		public event ElapsedEventHandler Elapsed;
		...//其他的代码省略
	}
}

然后查看EventHandler的定义,如下所示:

namespace System.Timers
{
    // Represents the method that will handle the System.Timers.Timer.Elapsed event
    // of a System.Timers.Timer.
    public delegate void ElapsedEventHandler(object sender, ElapsedEventArgs e);// 函数签名是最通用的那种
}

这里我有点疑问,C#可以这样单独把一个delegate,不放在任何类里暴露出来吗?

查阅资料后发现,定义委托也就是定义一个类。所以,只要是可以定义类的地方,都可以定义委托。委托既可以在另一个类的内部定义,也可以在任何类的外部定义,还可以在命名空间中把委托定义为顶层对象。委托和类、接口类似,通常比较多的放在类的外部或命名空间中。

所以是可以在类外定义委托的,比如我下面的代码可以成功运行:

public delegate void ElapsedEventHandler(object sender, ElapsedEventArgs e);
namespace ConsoleApp1
{
    class Program
    {
        public static void Main()
        {
            Console.WriteLine("Hello World");
        }
    }
}

继续写上面的例子,要实现这样一个功能,每隔一秒钟打印出当前的时间,代码如下所示:

namespace ConsoleApp1
{
    class Program
    {
        public static void Main()
        {
            Timer timer = new Timer();
            // Elapsed属性是一个event类型
            timer.Elapsed += MyEventHandler;
            timer.Interval = 1000;//ms
            timer.Enabled = true;

            Console.ReadLine();
            timer.Elapsed -= MyEventHandler;
        }

        //public delegate void EventHandler(object sender, ElapsedEventArgs e);
		
		// 函数签名必须与event声明时候的EventHandler的签名相同,这里的static是为了方便Main函数调用
        static void MyEventHandler(object sender, ElapsedEventArgs args)
        {
            Console.Write("\r{0} ", args.SignalTime.ToLongTimeString());// \r是转义字符,每次从行首开始复写
        }
    }
}

然后就能看到屏幕不断打印出当前的时间,显示上午10点29分39秒,每隔一秒更新一次,如下图所示:
在这里插入图片描述

.NET 2.0开始可以使用类似Lambda表达式的方式,去简化这个过程,写法如下:

static void Main()
{
	Timer tmr = new Timer();
	tmr.Elapsed += delegate(object sender, ElapsedEventArgs args)
	{
		Console.Write("\r{0} ", args.SignalTime.ToLongTimeString());
	};
	tmr.Interval = 1000;
	tmr.Enabled = true;
	Console.ReadLine();
}

如果不需要任何参数,还可以再进行简化:

tmr.Elapsed += delegate
{
	Console.Write("\r{0} ", DateTime.Now.ToLongTimeString());//利用DateTime类打印出当前时间
};

前面说的是C#自己的Event,坦白说,其中的interval怎么和event交互的,书里还是没有介绍。现在再看个例子,这个例子是为了实现,当一个参数发生改变的时候,立马作为事件,调用对应的处理函数,代码如下所示:

class Program
{
    // 定义事件发生时,调用的回调函数的签名方式:void类型,2个int类型参数
    public delegate void argChangedEventHandler(int oldVal, int newVal);

    class Example
    {
            // 为这个类定义一个event,名字叫PriceChangedObserver, 通知的对象是函数, 函数签名是void(int, int)
            public event argChangedEventHandler PriceChangedObserver;
			// 类对象有一个公开的回调函数,可以等待被赋值,其实就是一个委托对象等待被赋值
            public argChangedEventHandler whenChangedDoSomething;
            private int _price;

            public Example(){ _price = 0; }

            public int Price
            {
                set
                {
                	// 在Set属性里,去判断,如果与原来的值不一样,则调用类对象内部记载的回调函数
                    if (_price != value)
                    {	
                    	// 当值改变时, 去调用事件对应的委托, value是原本的值
                        whenChangedDoSomething?.Invoke(_price, value);  //trigger Event when Price was changed
                    }
                    _price = value;
                }
            }
  	}

    public static void PrintWhenPriceChanged(int oldVal, int newVal)
    {
        Console.WriteLine("Old value is :" + oldVal);
        Console.WriteLine("New value is :" + newVal);
    }

    static void Main(string[] args)
    {
        Example item = new Example();
        item.whenChangedDoSomething += PrintWhenPriceChanged;
        item.Price = 123;   //会去调用对应的回调函数
    }
}

最后打印如下:

Old value is :0
New value is :123
请按任意键继续. . .


二十一. new keyword

C++的new正常是会调用全局的operator new函数,类下可能会调用类自己重载的operator new函数,还有placement new等用法,而C#里的new比这个就简单多了,对于struct或者class,new关键字承担了初始化的工作,对于类内的值类型变量,都会置为0,对于引用类型变量,都会置为null,举个例子,代码如下所示:

 struct MyStruct
 {
 	public int a;
}

public static void Main()
{
	MyStruct s1 = new MyStruct();
	MyStruct s2;
	Console.WriteLine(s1.a);// 正确,a被new初始化为0
	Console.WriteLine(s2.a);// 编译报错,a没有初始化
}

再考虑一下内存的问题,看看下面一行代码:

Data myData;

这一行语句只会在stack上面分配内存,如果Data是Struct类型,那么Stack上分配的内存就是Data的一个对象的大小,比如Data如果有三个interger,就是分配12个字节,如果Data是引用类型,则分配的是一个引用,或者说指针,一共四个字节。

myData = null;//只有当myData是Class类型的时候才可以这么写
myData = new myData();//如果是Struct,则这里进行了初始化,如果是Class类型,则在Heap上分配了内存,并进行了初始化

如果用的是new [],那就不一样了,C#里的数组总是分配在Heap上的,如下所示:

dates = new Date[27];

对于上面这句话,如果Data是Struct类型,那么会立马在Heap上分配27个Instance的大小,而且每个Instance都会被初始化,而对于Class类型,只会在Heap上分配27个Reference的大小,,对应的引用都是null,具体的Instance处于未被创建的状态,需要单独去new才能创建具体的对象。

Struct类型的内存消耗更小,适合存放Vector3f这种类似interger的小数据类型



二十二. Boxing and Unboxing

Boxing causes the value to be copied from the Stack, put in an Object, and placed on the Heap

decimal pi = 3.14159m;
object obj = pi;

上述代码进行了Boxing的行为,具体在内存上的行为是,当decimal进行装箱时,会在Heap分配对应的内存,然后把pi从stack上复制存储到heap中,Heap上除了分配内存去存储其对应的值外,还分配的内存用来存储装箱过程原来的值对应的类型,方便后面进行Unboxing,拆箱的过程则正好相反,是从Heap上复制内存到Stack上,注意以上两个过程都是复制,原本的内容都还在,可以看下面两个例子

class Program
{
    struct Point1
    {
        public int x, y;
        public Point1(int x, int y)
        {
            this.x = x;
            this.y = y;
        }
    }

    class Point2
    {
        public int x, y;
        public Point2(int x, int y)
        {
            this.x = x;
            this.y = y;
        }
    }

    public static void Main()
    {
        Point1 p1 = new Point1(1, 1);
        object o1 = p1;
        p1.x = 2;
        Console.WriteLine(((Point1)o1).x);// Print 1, 因为装箱过程只是一个复制,创建了新的对象,不会影响原来的对象

        Point2 p2 = new Point2(1, 1);
        object o2 = p2;
        p2.x = 2;
        Console.WriteLine(((Point2)o2).x);// Print 2
    }
}

什么时候需要Boxing
参考链接:https://stackoverflow.com/questions/2111857/why-do-we-need-boxing-and-unboxing-in-c#:~:text=Boxing%20and%20Unboxing%20are%20specifically,types%20as%20instances%20of%20Object.

There are some basic structures in .Net that still demand passing Value Types as object (and so require Boxing), but for the most part you should never need to Box

感觉是尽量不要用Boxing,不过.NET的一些历史遗留问题会导致不得不使用Boxing,主要有以下三点:

  • The Event system turns out to have a Race Condition in naive use of it, and it doesn’t support async. Add in the Boxing problem and it should probably be avoided. (You could replace it for example with an async event system that uses Generics.)
  • The old Threading and Timer models forced a Box on their parameters but have been replaced by async/await which are far cleaner and more efficient.
  • The .Net 1.1 Collections relied entirely on Boxing, because they came before Generics. These are still kicking around in System.Collections. In any new code you should be using the Collections from System.Collections.Generic, which in addition to avoiding Boxing also provide you with stronger type-safety.

翻译过来就是:

  • 关于C#的Event和Race Condition里面会用到,Remain
  • 一些老的Thread和Timer类会需要这个,不过可以用async/await函数解决这个问题
  • 老的.NET 1.1的容器会用到Boxing,代码如下所示:
// 应该用Generic命名空间下的容器
using System.Collections.Generic;

var employeeCount = 5;
var list = new List<int>(10);

// 而下面的容器就尽量少用
using System.Collections;

Int32 employeeCount = 5;
var list = new ArrayList(10);


二十三. Action和Func

其实是一个很简单的东西,进去看可以看到这样的定义:

namespace System
{
    //
    // Summary:
    //     Encapsulates a method that has no parameters and does not return a value.
    public delegate void Action();
}

其实就是帮我们定义了一个回调函数的函数签名而已,这个函数没有参数,返回类型为void,举个使用的例子:

void func(Param param, Action action)
{
	// Do something
	// ...
	
	action();//在做完对应的处理后,调用传进来的这个回调函数action
}

如果想要为Action对应的回调函数加上参数,可以这么写:

static void ConsolePrint(int i)
{
    Console.WriteLine(i);
}

static void Main(string[] args)
{
    Action<int> printActionDel = ConsolePrint;
    printActionDel(10);
}

注意这里的Action返回类型是void,如果想返回内容,就不能用Action了,而是要用Func参数,其定义如下:

namespace System
{
    //
    // Summary:
    //     Encapsulates a method that has no parameters and returns a value of the type
    //     specified by the TResult parameter.
    //
    // Type parameters:
    //   TResult:
    //     The type of the return value of the method that this delegate encapsulates.
    //
    // Returns:
    //     The return value of the method that this delegate encapsulates.
    public delegate TResult Func<out TResult>();
}
一个复杂的例子

比如我要把这个C++的写法翻译为C#的写法,即把函数签名对应的lambda函数存成函数对象:

auto PopulateMarkerNameArray = [](TArray<string>& Pattern, TArray<FAnimSyncMarker>& AuthoredSyncMarkers)
{
	Pattern.Reserve(AuthoredSyncMarkers.Num());
	for (FAnimSyncMarker& Marker : AuthoredSyncMarkers)
	{
		Pattern.Add(Marker.MarkerName);
	}
};

改法如下,C#里的Action是没参数,无返回值(即void),Func是有返回值,有参数,虽然我这里函数返回类型为void,但是好像还是得随便return个什么东西:

Func<List<string>, List<AnimSyncMarker>, bool> PopulateMarkerNameArray
 = (Pattern, AuthoredSyncMarkers) =>
{
	Pattern.Capacity = AuthoredSyncMarkers.Num();
	foreach (AnimSyncMarker Marker in AuthoredSyncMarkers)
		Pattern.Add(Marker.MarkerName);

	return false;
};
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一部久享盛誉的程序开发宝典。精选570个典型范例,全面覆盖实用和热点技术,涉及面广,实用性强源于实际项目开发,帮助读者短时间掌握更多实用技术,提高编程水平范例经过精心编排,重点、难点突出,易学易懂书后附录提供快速索引,即查、即学、即用。 第1章 窗体与界面设计 1 1.1 菜单应用实例 2 实例001 带历史信息的菜单 2 实例002 菜单动态合并 3 实例003 像开始菜单一样漂亮的菜单 4 实例004 任务栏托盘菜单 4 实例005 可以拉伸的菜单界面 5 实例006 级联菜单 6 1.2 工具栏设计 6 实例007 带背景的工具栏 7 实例008 浮动工具栏 7 实例009 带下拉菜单的工具栏 8 实例010 具有提示功能的工具栏 8 1.3 状态栏设计 9 实例011 在状态栏中显示检查框 9 实例012 带进度条的状态栏 10 实例013 状态栏中加入图标 11 1.4 导航菜单界面 11 实例014 OutLook界面 11 实例015 带导航菜单的主界面 12 实例016 图形化的导航界面 14 1.5 特色程序界面 15 实例017 隐藏式窗体 15 实例018 类似Windows XP的程序界面 18 实例019 软件启动界面 19 实例020 以树形显示的程序界面 20 实例021 动态按钮的窗体界面 21 1.6 特殊形状的窗体 23 实例022 非矩形窗体 23 实例023 建立字体形状窗体 24 实例024 控件随窗体自动调整 25 实例025 带分隔栏的窗体 25 实例026 随机更换主界面背景 26 1.7 多媒体光盘 27 实例027 自动启动的多媒体光盘程序 27 实例028 为触摸屏程序添加虚拟键盘 28 1.8 窗体效果 29 实例029 半透明渐显窗体 29 实例030 窗口颜色的渐变 30 1.9 窗体动画 31 实例031 窗体中的滚动字幕 31 实例032 动画显示窗体 32 实例033 制作闪烁的窗体 33 实例034 实现任务栏通知窗口 34 实例035 动画形式的程序界面 38 1.10 标题栏窗体 39 实例036 使窗体标题栏文字右对齐 39 实例037 没有标题栏但可以改变大小的窗口 39 1.11 设置窗体位置 40 实例038 设置窗体在屏幕中的位置 40 实例039 始终在最上面的窗体 41 实例040 从桌面右下角显示的窗体 41 1.12 设置窗体大小 43 实例041 获取桌面大小 43 实例042 在窗口间移动按钮 44 实例043 如何实现Office助手 45 1.13 窗体控制技术 46 实例044 在关闭窗口前加入确认对话框 46 实例045 通过子窗体刷新父窗体 47 实例046 拖动无边框窗体 51 1.14 其他技术 52 实例047 禁用窗口上的关闭按钮 52 实例048 实现动态系统托盘图标 53 实例049 实现气泡提示窗口 54 第2章 控件应用 56 2.1 TextBox控件应用 57 实例050 只允许输入数字的TextBox控件 57 实例051 在TextBox控件底端显示下划线 59 实例052 屏蔽TextBox控件上的粘贴功能 61 实例053 屏蔽TextBox控件上默认的右键菜单 62 2.2 ComboBox控件应用 63 实例054 美化ComboBox控件下拉列表 63 实例055 用ComboBox控件制作浏览器网址输入框 64 2.3 RichTextBox控件应用 65 实例056 在RichTextBox控件中添加超链接文字 65 实例057 在RichTextBox控件中显示RTF格式的文件 66 实例058 为RichTextBox控件添加自定义滚动条 68 实例059 在RichTextBox控件中实现关键字描红 69 实例060 在RichTextBox控件中实现项目编号功能 71 实例061 设置RichTextBox控件中文本对齐方式 74 2.4 ListBox控件应用 77 实例062 在ListBox控件间交换数据 77 实例063 将数据库数据添加到ListBox控件中 78 实例064 借助绑定控件实现数据选择录入 79 实例065 设置ListBox控件选择项 80 2.5 选择类控件应用 83 实例066 利用选择控件实现权限设置 83 实例067 利用选择控件实现复杂查询 85 2.6 ListView控件应用 87 实例068 ListView控件间的数据移动 87 实例069 将数据库数据添加到ListView控件 90 实例070 在ListView控件中实现修改功能 91 实例071 在ListView控件中对数据排序或统计 92 实例072 在ListView控件中绘制底纹 93 实例073 在列表视图中拖动视图项 94 实例074 使ListView控件中的选择项高亮显示 97 实例075 带复选框的ListView控件 99 2.7 TreeView控件应用 100 实例076 将数据库数据显示到树视图中 100 实例077 用TreeView控件制作导航界面 102 实例078 用TreeView控件遍历磁盘目录 102 实例079 修改TreeView控件中的节点 105 实例080 将XML文件节点绑定到TreeView控件中 106 2.8 DataGridView控件应用 108 实例081 DataGridView控件的分页功能 108 实例082 从DataGridView控件拖放数据至TreeView控件 113 实例083 在DataGridView控件中实现合并单元格 116 实例084 在DataGridView控件中显示图片 118 实例085 为DataGridView控件实现复选功能 120 实例086 禁用DataGridView控件列表头自动排序 122 2.9 其他控件典型应用 123 实例087 TrackBar控件的简单应用 123 实例088 自制平滑进度条控件 125 实例089 MaskedTextBox控件的简单应用 128 实例090 制作日历计划任务 130 实例091 在ProgressBar控件中显示进度百分比 132 实例092 在NumericUpDown控件中显示当前系统日期 133 2.10 控件技术 134 实例093 程序运行时智能增减控件 134 实例094 多控件的焦点循环移动 136 实例095 动态创建控件 138 实例096 在Button按钮上绘图 138 2.11 焦点变换与输入控制 140 实例097 按回车键焦点在控件中移动的录入窗口 140 实例098 程序运行时拖动控件 141 实例099 控件得到焦点时变色 142 实例100 控件的输入限制 143 2.12 特殊控件 144 实例101 为控件制作立体效果 144 实例102 获取控件名称和内容 145 第3章 组件应用 147 3.1 BackgroundWorker组件 148 实例103 BackgroundWorker组件执行异步操作 148 3.2 ErrorProvider组件 150 实例104 使用ErrorProvider组件验证文本框输入 150 3.3 EventLog组件 151 实例105 使用EventLog组件读写Windows系统事件日志 151 实例106 使用EventLog组件保存Windows系统日志 153 实例107 使用EventLog组件向本机现有日志中添加条目 155 3.4 FileSystemWatcher组件 156 实例108 使用FileSystemWatcher组件监视系统日志文件是否被更改 157 3.5 HelpProvider组件 158 实例109 使用HelpProvider组件调用帮助文件 159 3.6 Process组件 159 实例110 使用Process组件访问本地进程 159 3.7 Timer组件 161 实例111 使用Timer组件制作计时器 161 实例112 使用Timer组件显示当前系统时间 165 实例113 使用Timer组件制作左右飘动的窗体 166 实例114 使用Timer组件实现奥运倒计时 167 3.8 ServiceController组件 169 实例115 使用ServiceController组件控制计算机的服务 169 3.9 ImageList组件 171 实例116 使用ImageList组件制作动画图片 171 3.10 DirectoryEntry组件 172 实例117 使用DirectoryEntry组件建立虚拟目录 172 第4章 图形技术 174 4.1 绘制图形 175 实例118 绘制公章 175 实例119 在图片中写入文字 176 实例120 局部图片的复制 178 实例121 波形图的绘制 179 4.2 图形转换 180 实例122 BMP转换成JPG格式 181 实例123 JPG转换成BMP格式 182 实例124 位图转化为WMF格式 183 实例125 ICO文件转化为位图 184 实例126 图片批量转换工具 185 4.3 图像预览 187 实例127 局部图像放大 187 实例128 浏览大图片 188 实例129 剪切图片 189 实例130 图像旋转 192 实例131 鼠标拖曳图像 193 4.4 图形缩放与变换 194 实例132 如何放大和缩小图像 194 实例133 如何旋转JPG图像 195 实例134 如何实现图形翻转 196 4.5 图像效果 196 实例135 百叶窗效果显示图像 197 实例136 推拉效果显示图像 198 实例137 水平交错效果显示图像 199 实例138 垂直交错效果显示图像 200 实例139 图像纹理效果 201 实例140 图像浮雕效果 203 实例141 积木效果 204 实例142 马赛克效果显示图像 205 实例143 雾化效果显示图像 206 实例144 锐化效果显示图像 207 实例145 黑白效果显示图像 208 实例146 光晕效果显示图像 209 4.6 图像字体 210 实例147 倒影效果的文字 210 实例148 投影效果的文字 211 实例149 印版效果的文字 213 实例150 阴影效果的文字 214 实例151 倾斜效果的文字 214 实例152 渐变效果的文字 215 实例153 缩放效果的文字 216 实例154 辉光效果文字 217 实例155 如何在图片中移动文字 220 4.7 图像动画 221 实例156 动画背景窗体 221 实例157 随鼠标移动的图像 223 实例158 十字光标定位 224 实例159 抓取鼠标指针的形状 225 实例160 图像的上下对接显示 226 实例161 任意角度旋转图像 227 实例162 以四周扩散形式显示图像 229 4.8 图像识别 230 实例163 查看图片的像素 230 实例164 设置图像中指定位置的像素值 230 实例165 在图像文件中实现自定义标记 232 实例166 获取指定点的RGB值 234 4.9 图像工具 235 实例167 获取图片类型 235 实例168 简单画图程序 236 实例169 看图工具 239 实例170 文字保存为图片 240 实例171 捕获屏幕 241 4.10 图像应用 243 实例172 随机更换壁纸程序 243 实例173 屏幕保护 244 实例174 模拟石英钟 247 实例175 生肖速查 249 第5章 多媒体技术 251 5.1 CD、VCD播放 252 实例176 播放指定的avi-mid-wav文件 252 实例177 获取多媒体详细信息列表 253 5.2 MP3、WAV播放 254 实例178 带记忆功能的MP3播放器 254 实例179 自动播放的MP3播放器 257 实例180 学校体操定时音乐播放 258 实例181 播放系统自带的事件声音 259 实例182 获取MP3文件的歌词 260 实例183 M3U文件的创建及删除 264 实例184 获取MP3文件的播放时间 267 实例185 异步加载并播放声音文件 269 5.3 动画播放 270 实例186 播放Flash动画 270 实例187 制作AVI播放器 272 实例188 播放GIF动画 273 实例189 利用Image制作小动画 274 5.4 媒体控制 275 实例190 检测是否安装声卡 275 实例191 打开和关闭CDROM 276 实例192 控制PC喇叭发声 277 实例193 获取显示设备的名称及PNPDeviceID 278 实例194 如何收听网络电台 279 实例195 获取显示设备的最大、最小及当前刷新率 281 实例196 隐藏控制面板中的声音设备 281 实例197 获取显示设备的当前显示模式 282 实例198 获取声音设备的名称及PNPDeviceID 283 实例199 语音计算器 284 5.5 多媒体应用 286 实例200 开机祝福程序 286 实例201 制作家庭影集 287 实例202 产品电子报价 288 实例203 产品滚动展示程序 290 实例204 将图片资源添加到EXE里 291 5.6 屏幕保护相关程序 292 实例205 电子相册屏幕保护程序 292 实例206 歌曲播放屏幕保护程序 293 第6章 文件系统 294 6.1 创建、删除文件和文件夹 295 实例207 创建和删除文件夹 295 实例208 建立临时文件 296 实例209 根据日期动态建立文件 296 实例210 清空回收站 298 6.2 查找文件 299 实例211 搜索文件 299 实例212 检查文件是否存在 300 实例213 提取指定文件夹目录 301 6.3 修改文件 302 实例214 更改文件名称 302 实例215 修改文件属性 303 实例216 修改文件及目录的名称 304 6.4 文件目录 305 实例217 获得临时文件目录 305 实例218 获取应用程序所在目录 305 实例219 得到系统当前目录 306 实例220 在程序中改变当前路径 307 6.5 复制文件 307 实例221 使用FileStream复制大文件 308 实例222 复制文件时显示复制进度 310 实例223 批量复制文件 312 6.6 指定类型的文件操作 313 实例224 文本文件的操作 313 实例225 使用ROT13加密解密文件 314 6.7 其他 315 实例226 获取窗口文本 315 实例227 判断文件是否正在被使用 316 实例228 在程序中调用.HLP文件 317 实例229 C#中实现文件拖放 317 实例230 文件比较 318 实例231 获取文件夹中的图标资源 319 实例232 获取文件夹下的所有文件夹及文件的名称 321 第7章 操作系统与Windows相关程序 324 7.1 启动相关 325 实例233 进入Windows系统前发出警告 325 实例234 实现注销、关闭和重启计算机 326 7.2 获得磁盘属性 328 实例235 获得硬盘序列号 328 实例236 获取映射驱动器路径 330 实例237 判断驱动器类型 331 实例238 获取所有逻辑分区 332 7.3 磁盘相关设置 333 实例239 取消磁盘共享 333 实例240 检查驱动器容量 334 实例241 检测磁盘是否准备好 335 实例242 图表显示磁盘容量 335 实例243 格式化磁盘 337 7.4 系统控制 338 实例244 将计算机设置为休眠状态 338 实例245 切换输入法 339 实例246 创建应用程序快捷方式 340 7.5 系统设置 340 实例247 设置桌面背景 341 实例248 设置系统时间 342 实例249 设置屏幕分辨率 343 7.6 系统监控 344 实例250 检测系统启动模式 344 实例251 内存使用状态监控 345 实例252 键盘钩子屏蔽热键 346 实例253 CPU使用率 348 7.7 系统软件信息 350 实例254 获取计算机中已安装的字体 350 实例255 获取计算机的显示设备信息 351 实例256 获取系统启动后经过的时间 351 实例257 系统已经安装的打印机信息 352 7.8 鼠标操作 353 实例258 切换鼠标左右键 353 实例259 限制鼠标活动区域 354 实例260 获取鼠标在任意点的颜色值 355 实例261 设置鼠标样式 357 7.9 程序控制 359 实例262 打开控制面板中的程序 359 实例263 添加程序托盘 360 实例264 不出现在任务栏上的程序 360 实例265 怎样调用外部的EXE文件 361 实例266 关闭外部已开启的程序 362 7.10 程序运行 363 实例267 防止程序多次运行 363 实例268 程序运行时禁止关机 364 实例269 获取任务栏尺寸大小 365 实例270 改变系统提示信息 366 实例271 获取系统环境变量 367 实例272 启动屏幕保护 368 7.11 系统隐藏 369 实例273 隐藏、显示任务栏 369 实例274 隐藏、显示开始按钮 370 实例275 查看当前系统版本 371 实例276 使桌面图标文字透明 372 实例277 检索系统中正在运行的任务 373 实例278 隐藏、显示桌面图标 374 7.12 其他 375 实例279 两种信息发送方式 375 实例280 判断计算机中是否安装了SQL软件 378 第8章 注册表 380 8.1 个性桌面 381 实例281 禁用桌面选项卡 381 实例282 禁用外观选项卡 382 实例283 禁用屏幕保护选项卡 383 实例284 设置任务栏时间样式 383 8.2 系统设置 384 实例285 隐藏桌面快捷方式图标的小箭头 384 实例286 禁用Windows任务管理器 385 实例287 禁用任务栏的右键菜单 386 8.3 IE浏览器设置 387 实例288 修改IE浏览器标题栏内容 387 实例289 获取IE浏览器版本信息 388 实例290 设置IE浏览器的默认主页 388 实例291 禁止修改IE浏览器主页 389 8.4 应用软件设置 390 实例292 建立数据文件与程序的关联 390 实例293 使应用程序开机自动运行 391 第9章 数据库技术 393 9.1 连接Access数据库 394 实例294 连接Access数据库 394 实例295 连接加密的Access数据库 394 实例296 自动识别Access 2000数据库路径 396 实例297 连接网络上共享的Access 2000数据库 397 实例298 将Access数据库导入Excel文件中 398 9.2 连接SQL Server数据库 400 实例299 使用ODBC DSN连接SQL Server数据库 400 实例300 使用ODBC非DSN连接SQL Server数据库 402 实例301 使用OLE DB连接SQL Server数据库 403 实例302 建立SQL Server数据库连接 404 9.3 连接其他数据库 405 实例303 连接Excel文件 405 实例304 连接Oracle数据库 406 9.4 数据库结构的读取与修改 407 实例305 读取SQL Server数据库结构 407 实例306 修改SQL Server数据库结构 409 9.5 数据录入 412 实例307 利用数据绑定控件录入数据 412 实例308 使用ADO.NET对象录入数据 414 实例309 利用SQL语句录入数据 416 实例310 利用存储过程录入数据 417 9.6 图片存取技术 420 实例311 使用存取文件名的方法存取图片 420 实例312 使用ADO.NET对象向SQL Server数据库存入图片 421 9.7 数据修改 423 实例313 利用数据绑定控件修改数据 424 实例314 利用数据对象修改数据 426 实例315 利用SQL语句修改数据 427 实例316 利用存储过程修改数据 429 9.8 数据保存前判断 431 实例317 判断输入数据是否符合要求 431 实例318 判断是否重复输入数据 433 9.9 数据删除 434 实例319 删除表格中指定的记录 434 实例320 利用SQL语句删除数据 435 9.10 数据记录 436 实例321 分页显示信息 436 实例322 移动记录 437 9.11 数据维护 439 实例323 在C#中分离SQL Server数据库 439 实例324 在C#中附加SQL Server数据库 440 实例325 在C#中附加单文件SQL Server数据库 441 9.12 数据备份恢复 442 实例326 备份SQL Server数据库 442 实例327 还原SQL Server数据库 445 9.13 管理系统开发相关 447 实例328 开启SQL Server数据库 447 实例329 断开SQL Server数据库与其他应用程序的连接 449 实例330 带图像列表的系统登录程序 450 实例331 利用SQL语句执行外围命令 451 实例332 系统初始化 452 第10章 SQL查询相关技术 454 10.1 SELECT子句 455 实例333 查询特定列数据 455 实例334 使用列别名 457 实例335 在列上加入计算 458 实例336 使用函数设置条件 459 10.2 查询常量 461 实例337 查询数字 461 实例338 查询字符串 462 实例339 查询日期数据 464 实例340 查询逻辑型数据 465 实例341 查询空(“”或Null)数据 466 10.3 查询变量 467 实例342 利用变量查询字符串数据 467 实例343 利用变量查询数值型数据 468 实例344 利用变量查询日期型数据 469 10.4 模式查询 471 实例345 利用“_”通配符进行查询 471 实例346 利用“%”通配符进行查询 472 实例347 利用“[]”通配符进行查询 473 实例348 利用“[^]”通配符进行查询 474 实例349 复杂的模式查询 475 10.5 TOP和PERCENT限制查询结果 476 实例350 查询前10名数据 476 实例351 取出数据统计结果的后10名数据 478 实例352 查询销售量占前50%的图书信息 479 实例353 查询库存数量占后20%的图书信息 480 10.6 周期、日期查询 481 实例354 查询指定日期的数据 481 实例355 查询指定时间段的数据 482 实例356 按年、月或日查询数据 484 10.7 比较、逻辑、重复查询 486 实例357 利用运算符查询指定条件的数据 486 实例358 NOT与谓词进行组合条件的查询 488 实例359 查询时不显示重复记录 489 实例360 列出数据表中的重复记录和记录条数 491 10.8 在查询中使用OR和AND运算符 492 实例361 利用OR运算符进行查询 492 实例362 利用AND运算符进行查询 493 实例363 同时利用OR、AND运算符进行查询 495 10.9 排序、分组统计 496 实例364 数据分组统计(单列) 497 实例365 在分组查询中使用ALL关键字 498 实例366 在分组查询中使用CUBE运算符 500 实例367 在分组查询中使用ROLLUP运算符 502 实例368 对数据进行降序查询 503 实例369 对数据进行多条件排序 505 实例370 对统计结果进行排序 506 实例371 按仓库分组统计图书库存(多列) 507 实例372 多表分组统计 508 实例373 使用COMPUTE 509 实例374 使用COMPUTE BY 511 10.10 聚合函数 512 实例375 利用聚合函数SUM对销售额进行汇总 512 实例376 利用聚合函数AVG求某班学生的平均年龄 514 实例377 利用聚合函数MIN求销售额、利润最少的商品 515 实例378 利用聚合函数MAX求月销售额完成最多的员工 517 实例379 利用聚合函数COUNT求日销售额大于某值的商品数 518 实例380 利用聚合函数First或Last求数据表中第一条或最后一条记录 519 10.11 多表查询(连接查询) 521 实例381 利用FROM子句进行多表查询 521 实例382 使用表别名 522 实例383 合并多个结果集 523 10.12 嵌套查询 525 实例384 简单嵌套查询 525 实例385 复杂嵌套查询 526 实例386 嵌套查询在查询统计中的应用 528 10.13 子查询 530 实例387 用子查询做派生的表 530 实例388 用子查询作表达式 531 实例389 在Update语句中应用子查询 532 10.14 联合语句Union 533 实例390 使用联合查询 533 实例391 多表联合查询 535 实例392 对联合查询后的结果进行排序 536 10.15 内联接查询 537 实例393 简单内联接查询 538 实例394 复杂内联接查询 539 实例395 使用内联接选择一个表与另一个表中行相关的所有行 540 10.16 外联接查询 541 实例396 左外联接查询 541 实例397 右外联接查询 543 实例398 使用外联接进行多表联合查询 544 10.17 利用IN进行查询 545 实例399 用IN查询表中的记录信息 545 实例400 使用IN引入子查询限定查询范围 546 10.18 交叉表查询 547 实例401 利用Trasform分析数据 547 实例402 利用Trasform动态分析数据 549 实例403 静态交叉表(SQLServer 2000) 551 实例404 动态交叉表(SQLServer 2000) 553 10.19 函数查询 555 实例405 在查询语句中使用格式化函数 555 实例406 在查询语句中使用字符串函数 557 实例407 在查询中使用日期函数 558 10.20 having语句应用 559 实例408 利用having语句过滤分组数据 559 实例409 having语句应用在多表查询中 561 10.21 视图的应用 562 实例410 在C#中应用视图 562 实例411 获取数据库中的全部用户视图 563 实例412 通过视图修改数据 564 10.22 存储过程的应用 565 实例413 C#应用存储过程 565 实例414 应用存储过程添加数据 566 实例415 应用存储过程修改数据 567 实例416 应用存储过程删除数据 568 实例417 C#应用查询存储过程 569 实例418 获取数据库中的全部存储过程 570 实例419 加密存储过程 571 10.23 触发器的应用 572 实例420 Insert触发器的应用 572 实例421 Update触发器在系统日志中的应用 574 实例422 触发器的嵌套使用 575 实例423 获取数据库中的触发器 576 第11章 LINQ查询技术 577 11.1 使用LINQ技术操作SQL数据库 578 实例424 使用LINQ技术查询SQL数据库中的数据 578 实例425 使用LINQ技术向SQL数据库中添加数据 581 实例426 使用LINQ技术在SQL数据库中修改数据 582 实例427 使用LINQ技术在SQL数据库中删除数据 583 实例428 使用LINQ技术查询前5名数据 584 实例429 使用LINQ技术对数据进行排序 585 实例430 使用LINQ技术统计员工的工资总额 587 11.2 LINQ技术其他应用 588 实例431 使用LINQ技术获取文件详细信息 588 实例432 使用LINQ技术对XML文件进行操作 589 第12章 报表与打印技术 594 12.1 Windows打印组件 595 实例433 打印窗体中的数据 595 实例434 图形打印 597 12.2 利用报表生成器设计报表 598 实例435 利用报表专家设计并显示学生基本信息 598 实例436 分组统计报表 601 实例437 在水晶报表中添加图表 602 12.3 水晶报表基本操作 605 实例438 在水晶报表中使用Access数据库 605 实例439 在水晶报表中使用SQL Server数据库 606 实例440 订货总金额超过10万元显示“恭喜获奖”文字 607 实例441 薪资大于或等于1万元使用蓝色字体标记 609 实例442 筛选薪资大于2000元的男员工 610 实例443 按类别分组统计图书库存 611 实例444 按成绩总分降序排序 612 实例445 部门销售量占公司总销售量的业绩百分比 613 12.4 子报表的使用 614 实例446 插入子报表 615 实例447 编辑与重新导入子报表 615 实例448 根据需要显示子报表 617 12.5 调用Office进行打印 618 实例449 利用Word打印员工报表 618 实例450 利用Excel打印学生信息报表 620 第13章 图表技术 622 13.1 简单图表 623 实例451 绘制面形图 623 实例452 绘制椭圆 624 实例453 绘制矩形 625 实例454 绘制曲线 626 实例455 绘制柱形图 627 13.2 柱形图表 628 实例456 将汇总数据利用图表分析 628 实例457 柱形图表分析商品走势 630 实例458 对排序数据进行分析 632 实例459 利用控件实现柱形图分析 633 实例460 在柱形图的指定位置显示说明文字 634 13.3 折线图表 635 实例461 利用图表分析产品销售走势 635 实例462 利用图表分析彩票中奖情况 637 实例463 多曲线数据分析 638 实例464 网站人气指数曲线分析 641 13.4 饼形图表 643 实例465 利用饼形图分析公司男女比率 643 实例466 利用饼形图分析产品市场占有率 644 实例467 利用多饼形图分析企业人力资源情况 645 实例468 制作一个可以旋转的饼形图 647 13.5 图表技术的应用 648 实例469 绘制验证码 649 实例470 在饼形图的外围显示说明文字 650 第14章 硬件相关开发技术 655 14.1 串口控制 656 实例471 通过串口发送数据 656 实例472 通过串口关闭对方计算机 657 14.2 加密狗 659 实例473 密码写入与读出加密狗 659 实例474 使用加密狗进行身份验证 661 14.3 IC卡应用 662 实例475 向IC卡中写入数据 662 实例476 读取IC卡中的数据 665 实例477 利用IC卡制作考勤程序 666 14.4 指纹识别器应用 667 实例478 将指纹数据存入数据库中 667 实例479 使用指纹识别器进行员工考勤 669 14.5 监控 671 实例480 简易视频程序 671 实例481 摄像头监控录像 674 实例482 超市摄像头定时监控系统 675 14.6 语音卡控制 677 实例483 语音卡电话呼叫系统 677 实例484 客户来电查询系统 682 实例485 语音卡实现电话录音 683 14.7 手机程序开发 685 实例486 利用短信猫收发短信息 685 实例487 利用短信远程关闭计算机 688 实例488 短信息采集烟草销售数据 690 实例489 “春晚”节目评比短信息互动平台 692 14.8 其他程序 693 实例490 条形码扫描器销售商品 693 实例491 利用神龙卡制作练歌房程序 694 第15章 网络开发技术 697 15.1 计算机设置 698 实例492 通过计算机名获取IP地址 698 实例493 通过IP地址获取主机名称 699 实例494 修改本机IP地址 700 实例495 得到本机MAC地址 702 实例496 获得系统打开的端口和状态 703 实例497 更改DNS地址 705 15.2 远程控制 706 实例498 远程控制计算机 706 实例499 远程服务控制 708 15.3 网络复制文件 710 实例500 网络中的文件复制 710 15.4 局域网管理 712 实例501 在局域网内发送信息 712 实例502 获取网络中所有工作组名称 714 实例503 列出工作组中所有计算机 715 实例504 获取网络中某台计算机的磁盘信息 716 实例505 映射网络驱动器 718 15.5 网络连接与通信 719 实例506 编程实现Ping操作 719 15.6 网络聊天室 721 实例507 利用C#设计聊天程序 721 实例508 点对点聊天室 723 第16章 Web编程 726 16.1 浏览器应用 727 实例509 制作自己的网络浏览软件 727 实例510 XML数据库文档的浏览 730 16.2 上网控制 732 实例511 定时上Internet 732 实例512 监测当前网络连接状态 734 16.3 邮件管理 734 实例513 收取电子邮件 735 实例514 SMTP协议发送电子邮件 738 16.4 网上信息提取 739 实例515 提取并保存网页源码 739 实例516 提取网页标题 742 第17章 加密、安全与软件注册 744 17.1 数据加密与解密 745 实例517 数据加密技术 745 实例518 文本文件加密与解密 746 实例519 利用图片加密文件 750 17.2 Access数据库安全 753 实例520 如何编程修复Access数据库 753 实例521 访问带验证模式的SQL Server 2000数据库 754 17.3 软件注册与加密 756 实例522 利用INI文件对软件进行注册 756 实例523 利用注册表设计软件注册程序 758 实例524 利用网卡序列号设计软件注册程序 760 实例525 根据CPU序列号、磁盘序列号设计软件注册程序 762 第18章 数据结构与算法 765 18.1 链表的实现 766 实例526 单向链表的实现 766 18.2 双向链表 770 实例527 双向链表 770 18.3 堆栈 775 实例528 堆栈的实现 775 18.4 队列 777 实例529 队列的实现 777 18.5 树的实现 778 实例530 树的实现 778 18.6 排序 782 实例531 冒泡排序 783 实例532 选择排序 783 实例533 插入排序 784 实例534 希尔排序 785 18.7 常见算法的实际应用 786 实例535 判断素数的算法 786 实例536 加密和解密算法 787 实例537 判断身份证是否合法 788 实例538 判断IP地址是否合法的算法 790 实例539 按要求生成指定位数编号 791 实例540 身份证号从15位升到18位算法 791 实例541 百钱百鸡的算法 792 实例542 韩信点兵的算法 793 实例543 实现裴波那契数列求和 794 实例544 求水仙花数的算法 795 实例545 如何将B转换成GB、MB和KB 796 实例546 0~N位数的任意组合 796 实例547 在数组中快速查找近似值 799 第19章 C#高级开发 800 19.1 Windows服务开发 801 实例548 将局域网聊天程序开发成Windows服务 801 19.2 Remoting分布式开发 807 实例549 运用Remoting实现文件传送 807 实例550 大规模数据访问时缓解服务器压力 813 19.3 COM+服务开发 819 实例551 COM+服务实现银行转账系统 819 实例552 COM+服务解决同时访问大量数据并发性 823 第20章 实用工具 827 20.1 数据库工具 828 实例553 自动配置ODBC的程序 828 实例554 制作SQL Server提取器 831 20.2 个人工具 833 实例555 个人通讯录 833 实例556 电子名片盒 835 实例557 个人日记本 836 实例558 个人理财管理 838 20.3 实用工具 840 实例559 电话区号、邮编管理软件 840 实例560 IP地址及手机号码归属地查询 841 实例561 火车时刻查询软件 844 实例562 网站网址导航程序 847 20.4 其他工具 850 实例563 人民币金额转换 850 实例564 列举局域网SQL服务器 852 实例565 整点报时程序 853 实例566 红绿灯程序 854 实例567 万年历 857 实例568 彩票抽奖机 859 实例569 电子相册 860 第21章 程序打包 863 21.1 最简单的程序打包 864 实例570 最简单的程序打包 864 实例571 将特定文件安装到指定文件夹中 866 21.2 打包注册表信息 868 实例572 打包注册表信息 868 技术要点对应实例位置 871

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值