目录
前言
最近学习一下C#,主要参考《C#高级编程(第11版)》,一千多页的大书!用的C#7,个人C/C++用的多一点,基本就是对照C++写的。大概内容就是归纳一下这本书里每一节觉得和不同的东西?在学习每种新语言的时候不管是刷书还是刷网课还是看文档,都会学习一些重复的东西,我就想写点东西,一是对比一下C++与C#,让会C++的能快速用起来这语言;二是让我以后忘了这语言的时候能赶紧捡起来;三是给自己一点刷书的念想,毕竟刷语法书真的是太无聊了!一遍又一遍学if-else,又生怕有漏掉的语法糖,蛋疼啊蛋疼。在这篇文章里我会把和C++重合的部分直接点出来,就没必要在这些地方浪费时间了。
本文大体架构是跟着《C#高级编程(第11版)》来的,不过这本书是C#7的,但是笔者敲下这个字的时候C#8已经出现了,可以想见以后还会有更新的东西,我会在学习后将这些东西直接插入到对应的地方。
希望能够帮到大家!
本文章目标读者:
- 我自己
- 有着C++基础想快速过完C#语法的人
基础部分
第二章 核心C#
2.1 基础部分
名称空间调用:
using System; //类比using namespace std;
using static System.Console; //打开静态成员
示例
using static System.Console;
namespace Study
{
class Program
{
static void Main(string[] args)
{
WriteLine("JMC!");
}
}
}
2.2 变量
方法的局部变量必须显式初始化,不然用的时候会报错
类的成员变量没有显式初始化那么默认为0
using System;
namespace Study
{
class Program
{
static int t;
static void Main(string[] args)
{
Console.WriteLine(Program.t);
}
}
}
var关键字自动推断,不解释。
变量的作用域推断,类比C++,要调用外部的同名变量手动调用,默认调用最近的。
using System;
namespace Study
{
class Program
{
static int t;
static void Main(string[] args)
{
int t = 1;
Console.WriteLine(t + Program.t) ;
}
}
}
const关键字:类比C++,不过是默认static的,且不能手动添加static且必须显式初始化,初始化的值必须是编译期可知的。
2.3 预定义数据类型
值和引用: 值在栈里,引用在堆里。基本类型默认是值,别的是引用,CLR自带GC。
预置类型:
有符号整数:
有符号整数 | 无符号整数 | |
---|---|---|
sbyte | 8位 | byte |
short | 16位 | ushort |
int | 32位 | uint |
long | 64位 | ulong |
C#7可以给数字加下划线分割,好看一点。
using System;
namespace Study
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(123_456);
}
}
}
浮点数: float、double不解释,decimal为128位高精度十进制数
bool型: 不解释,不过C#bool不能和int隐式转换了,再也不会有人if(a=1)了罢!
char型: 不解释
预定义引用类型
object - 其他类型的父类
string - Unicode字符串
字符串字面量:放在双引号里,转义字符不解释,前面加@表示取消转义,加$类比format
using System;
namespace Study
{
class Program
{
static void Main(string[] args)
{
var jmc = 1;
var t = "111";
Console.WriteLine(@$"hello\\{jmc}{t}");
}
}
}
2.4 程序流控制
条件语句不解释,if-else与switch,不过这个switch的case必须带break不然会被报错,除非case为空。不过switch不限于int。
循环语句不解释,for while do-while直接拿来用,foreach类比rfor,test:
using System;
namespace Study
{
class Program
{
static void Main(string[] args)
{
var num = new int[4] { 1, 2, 3, 4 };
foreach (var item in num)
{
Console.Write(item);
}
}
}
}
不过这个foreach不能修改值,真奇葩。
跳转语句:goto、break、continue、return,不解释。
2.5 名称空间
类比C++,不解释,不过对于多层名称空间可以这么写
这个类比C++20的语法糖
using语句,两种用法,一者声明命名空间,二者给命名空间取别名,免得太长了,不过和C++相比没了typedef的功能。取别名必须要在原命名空间前面取,使用别名的时候可以用.也可以用::。
using System;
using jmc = Study.sss.eee;
namespace Study.sss.eee.rrr
{
class Program
{
static int t = 0;
static void Main(string[] args)
{
Console.Write(jmc::rrr.Program.t);
}
}
}
2.6 Main()方法
不解释,可以带个字符串参数,
2.7 注释
//和/* */,不解释,类比C/C++
可以根据特定注释生成XML文档,有点像doxygen哈,具体标记这里不写了。
2.8 预处理器指令
和C/C++基本一样,用的不多的我提一下
#define #undef #if #elif #else #endif 不解释,直接类比C/C++
#warning #error,编译器遇到就抛warning或error
#region #endregion,将一段代码视为给定名称的一个块
#line,改变警告或错误信息中显示的文件名和行号
#pragma,抑制或还原指定的编译警告
2.9 C#编程准则
标识符命名规则,不解释,直接类比C/C++,保留字表参见官方文档
不过有的C#新关键字不属于保留字,不过建议不要用就行了。值得一提的是标识符是可以用Unicode字符的,比如\u005f啦啦啦,个人觉得中文变量名真的很能提升code效率!
命名规范:VS是有默认的建议的命名规范的,如果不按规范来会给你给一个建议的,具体可以查阅MSDN文档。
但是“但要注意,这些准则与语言规范不同。用户应尽可能遵循这些准则。但如果有很好的理由不遵循它们,也不会有什么问题。”关于规范方面,建议参阅《Clean Code》(代码整洁之道)。
第三章 对象与类型
3.2 类和结构
别的不解释,C#的结构(struct)和类(class)是有区别的,前者是值类型,通常存储在栈上且不支持继承,后者是引用类型,总是存储在堆上。声明实例时用new来声明实例。
using System;
namespace Study
{
struct Test
{
public int m;
}
class Program
{
static void Main(string[] args)
{
Test tt = new Test();
Console.Write(tt.m);
}
}
}
值得一提的是C#8有这么一个语法糖,相信一看就懂:
using System;
namespace Study
{
struct Test
{
public int m;
}
class Program
{
static void Main(string[] args)
{
Test tt = new();
Console.Write(tt.m);
}
}
}
以下讨论类的性质,不做说明则认为性质同样适用于结构
3.3 类
字段:就是类的成员变量。
只读字段:用readonly
修饰符声明的字段,只能在构造函数中分配值,注意区别const
。
属性:字段最好以private修饰,属性就是搭配字段的方法,不过用起来就像一个字段一样。它有get与set访问器,get访问器不带任何参数且必须返回属性声明的类型,set不需要指定任何显式参数,编译器假定它带了个和属性类型相同的参数value。
using System;
namespace Study
{
class Test
{
private int m = 5;
public int M
{
get
{
return m + 1;
}
set
{
m = value;
}
}
}
class Program
{
static void Main(string[] args)
{
Test tt = new();
Console.Write(tt.M);
}
}
}
C#7的语法糖可以让属性简化成这样:
using System;
namespace Study
{
class Test
{
private int m = 5;
public int M
{
get => m;
set => m = value;
}
}
class Program
{
static void Main(string[] args)
{
Test tt = new();
tt.M++;
Console.Write(tt.M);
}
}
}
还有个可以自动实现属性的语法糖,连私有字段都不需要,注意怎么初始化:
using System;
namespace Study
{
class Test
{
public int M { get; set; }
public int N { get; set; } = 3;
}
class Program
{
static void Main(string[] args)
{
Test tt = new();
tt.M++;
Console.Write(tt.M + tt.N);
}
}
}
访问器也可以添加修饰符:
直接删掉set访问器就可以自动创建一个只读属性:
同样的如果删去get访问器,可以(自动)创建一个只写属性,不过“这可能使客户端代码作者感到迷惑”,所以通常不被推荐。
C#6起,如果这个属性是只读的(只有get访问器),那么可以使用表达式体属性实现,有kotlin内味了。
using System;
namespace Study
{
class Test
{
public static int M => 5;
public int N { get; set; } = 3;
}
class Program
{
static void Main(string[] args)
{
Test tt = new();
Console.Write(Test.M + tt.N);
}
}
}
然后有一个概念:如果一个类型包含可改变的成员,则为可变类型。否则为不可变类型,其内容只能在初始化时设置。这么设计是为了多线程,多线程访问信息永远不会改变的同一个对象就不需要同步。比如String就是不可变类型:
匿名类型
var和new一起用的时候就可以穿件所谓的匿名类型。匿名类型只是一个继承自Object且没有名称的类。该类的定义从初始化器中推断,类似于隐式类型化变量。也就是大概可以在使用的时候临时创建一个临时的类型?感觉有点像tuple?不过看起来比tuple用起来要方便一点。
using System;
namespace Study
{
class Program
{
static void Main(string[] args)
{
var n = new { N = 1 };
Console.Write(n.N);
}
}
}
方法
首先一个注意,正式的C#术语区分函数和方法
方法的声明没啥新意,基础声明和主流语言都差不多,不过顺便吐槽一下,这C#的字符串format比C++的输出简直不知道高到哪里去了,刚刚用上的C++20format又不香了。
using System;
namespace Study
{
class Test
{
public static int M => 5;
public int N { get; set; } = 3;
public void Print()
{
Console.WriteLine($"M={M},N={N}");
}
}
class Program
{
static void Main(string[] args)
{
new Test().Print();
}
}
}
如果方法的实现只有一条语句,那么可以使用一个学名为表达式体方法的语法糖,比如上面那个:
using System;
namespace Study
{
class Test
{
public static int M => 5;
public int N { get; set; } = 3;
public void Print() => Console.WriteLine($"M={M},N={N}");
}
class Program
{
static void Main(string[] args)
{
new Test().Print();
}
}
}
kotlin也有这样的语法糖,不知道来源是哪。。
调用方法就不解释了,类比C++。
重载方法不解释,重载要求类比C++。
命名的参数:调用方法时,我们可以给他加上标签:
using System;
namespace Study
{
class Test
{
public static int M => 5;
public int N { get; set; } = 3;
public void Print(int x,int y)
=> Console.WriteLine($"M={M},N={N},x={x},y={y}");
}
class Program
{
static void Main(string[] args)
{
new Test().Print(x: 1,y: 2);
}
}
}
同时通过这个语法还可以调换传入参数的顺序:
using System;
namespace Study
{
class Test
{
public static int M => 5;
public int N { get; set; } = 3;
public void Print(int x,int y)
=> Console.WriteLine($"M={M},N={N},x={x},y={y}");
}
class Program
{
static void Main(string[] args)
{
new Test().Print(y: 1,x: 2);
}
}
}
同时在C#7.2后还允许使用不拖尾的命名参数,换言之,在这之前你必须给你提供了名称的变量后的所有变量提供名称:
using System;
namespace Study
{
class Test
{
public static int M => 5;
public int N { get; set; } = 3;
public void Print(int x,int y)
=> Console.WriteLine($"M={M},N={N},x={x},y={y}");
}
class Program
{
static void Main(string[] args)
{
new Test().Print(x: 1,2);
}
}
}
有C语言指定初始化器内味了。
可选参数: 基本语法类比C++默认参数,不解释,注意可选参数必须放在参数列表。不过这里结合刚刚的“命名的参数”的语法我们可以写出这样的代码:
using System;
namespace Study
{
class Test
{
public static int M => 5;
public int N { get; set; } = 3;
public void Print(int x = 1,int y = 2,int z = 3)
=> Console.WriteLine($"M={M},N={N},x={x},y={y},z={z}");
}
class Program
{
static void Main(string[] args)
{
new Test().Print(1, z: 4);
}
}
}
个数可变的参数,和C++用std::initializer_list实现的可变参数感觉还是挺像的,不过这里是用数组实现的:
using System;
namespace Study
{
class Test
{
public static int M => 5;
public int N { get; set; } = 3;
public void Print(params int[] num)
{
foreach (var item in num)
{
Console.WriteLine(item);
}
}
}
class Program
{
static void Main(string[] args)
{
new Test().Print(1, 2, 3, 4, 5, 6);
}
}
}
如果使用object数组,就可以传递任意参数了,因为object是所有其他类型的父类嘛。params关键字还可以配合方法签名定义的参数一起使用,不过params只能在最后一个参数,同时只能使用一次,这个想想也知道为什么。
using System;
namespace Study
{
class Test
{
public static int M => 5;
public int N { get; set; } = 3;
public void Print(int n,params object[] num)
{
Console.WriteLine(n);
foreach (var item in num)
{
Console.Write($"{item} ");
}
}
}
class Program
{
static void Main(string[] args)
{
new Test().Print(1, 2, 3, 4, 5, "hello");
}
}
}
构造函数,默认构造函数其实是不太有必要写的,首先它会在某些情况自动合成,然后初始化器啥的挺够用了。。其中自动合成的默认构造函数会将所有成员字段初始化为标准的默认值(比如引用类型为空引用,数值类型为0,bool为false)。构造函数也支持重载,不过如果提供了带参数的构造函数,编译器就不会自动提供默认构造函数了。
比如这样:可以看到再去尝试调用默认参数就会报错。
同时可以将构造函数设置成private或protected,显然,如果我们把构造函数设置为private,我们就不能用new去实例化他了,不过可以在类内提供一个public的静态属性或方法来实例化类,这通常用于实现所谓的单例模式。
先写到这,有缘再会。
下面则介绍了几个构造函数相关的语法:
表达式体和构造函数
还是熟悉的语法糖,如果构造函数的实现由一个表达式组成,那么构造函数可以写成一个表达式体:
using System;
namespace Study
{
class Test
{
private int _n;
public Test(int num) => _n = num;
public void Print() => Console.Write(_n);
}
class Program
{
static void Main(string[] args)
{
new Test(4).Print();
}
}
}
从构造函数中调用其他构造函数
和C++11的移动构造函数有点像,不过略有不同。
此外C#还可以使用此语法调用基类的构造函数,差别是将这里的 this 关键字换成 base 。
静态构造函数