速览 · A
语法速览:一章过完 C#
这一章不深挖任何一条差异,只做一件事:把 C# 的语法面从上到下、一次性扫完。
你可以把它当成"全书的地图"——先知道 C# 里有什么,再回到后面的核心章节去理解为什么和 C++ 不一样。 也可以把它当成"速查手册",写代码时回来翻一翻。
- 给你一张完整的 C# 语法结构图,不再有"某个东西我不知道它存在"的盲区。
- 用表格把 C# 的类型、运算符、修饰符、关键字一次性过一遍,并标注和 C++ 的差异点。
- 每个语法点只给"最小可运行示例",深挖交给后面的章节(会标出"详见第几章")。
第一遍:只扫表和代码骨架,不求记住。重点是建立"C# 里长什么样"的整体印象。
第二遍:读完之后回到这里,逐条自问"这个我会不会写"。不会的就去对应章节。
每一节末尾都有 → 详见 指向深入章节。
A.1 程序结构:一个 .cs 文件长什么样
#include、没有前置声明——一个文件既是"声明"也是"实现"。#include <iostream> // 文本包含
#include "myclass.h" // 必须手动引入
int main() { // 入口是全局函数
std::cout << "hi";
return 0;
}
// 写法一:顶级语句(新项目默认,最简)
Console.WriteLine("hi");
// 编译器自动生成 Main 和 class Program
// 写法二:传统显式结构
using System;
namespace MyApp;
class Program {
static void Main(string[] args) { }
}
| 概念 | C++ | C# |
|---|---|---|
| 引入其它代码 | #include 头文件 | 同一项目自动可见;外部用"引用程序集" |
| 命名空间 | namespace A { namespace B { } } | namespace A.B;(文件作用域,只用一次) |
| 入口点 | 全局 int main() | static void Main(string[]) 或顶级语句 |
| 语句结束 / 块 | ; 和 { } | 一样 |
| 大小写 | 敏感 | 敏感 |
| 注释 | // /* */ | 一样,另有 /// XML 文档注释 |
| 预处理 | 宏、条件编译、include | 只剩 #if/#region/#nullable 等,没有宏 |
→ 详见 第 02 章 · 编译与运行模型
A.2 类型系统速查
整数:大小固定是最大的不同
C++ 里 int/long 的大小随平台和编译器变化;C# 的整数类型位数是写死的,跨平台一致。
| C# 类型 | 位数 / 范围 | .NET 全名 | 字面量后缀 | C++ 对应 |
|---|---|---|---|---|
sbyte | 8 位有符号 −128~127 | SByte | — | signed char / int8_t |
byte | 8 位无符号 0~255 | Byte | — | unsigned char / uint8_t |
short | 16 位 ±3.2 万 | Int16 | — | short |
int ★ | 32 位 ±21 亿 | Int32 | — | int(但大小不保证 32 位) |
long | 64 位 | Int64 | L | long long |
uint / ulong | 32 / 64 位无符号 | UInt32 / UInt64 | U / UL | unsigned |
nint / nuint | 与指针同宽(64 位机上 64 位) | IntPtr | — | intptr_t |
char | 16 位,存 UTF-16 码元 | Char | 'a' | char 通常是 8 位(注意差异) |
① C# 的 int 永远是 32 位,不会因为平台变成 64 位。(C++ 的 long 在 Windows 是 32 位、Linux 是 64 位,这种坑 C# 没有。)
② C# 的 char 是 2 字节的 UTF-16 码元,不是 1 字节。所以 "abc".Length 和"字节数"不是一回事,中文一个字通常占 1 个 char。
浮点与小数
| C# 类型 | 位数 / 精度 | 后缀 | C++ 对应 |
|---|---|---|---|
float | 32 位,约 7 位有效数字 | f | float |
double ★ | 64 位,约 15-16 位有效数字(小数默认类型) | 无 / d | double |
decimal | 128 位,约 28-29 位,专为钱 | m | 没有对应物(C++ 里一般用定点或整数分) |
Half | 16 位半精度 | — | 无 |
double d = 3.14; // 小数默认是 double
float f = 3.14f; // float 必须加 f
decimal m = 19.99m; // 钱用 decimal,必须加 m
Console.WriteLine(0.1 + 0.2); // 0.30000000000000004
Console.WriteLine(0.1m + 0.2m); // 0.3 ← decimal 精确
其它基本类型
| C# 类型 | 说明 | C++ 对应 |
|---|---|---|
bool | 只有 true / false,不能和整数互转 | bool(但 C++ 可隐式转 int,C# 不行) |
string | 引用类型、不可变、UTF-16 | std::string(可变) |
object | 所有类型的根 | void* / std::any 的意思,但类型安全 |
dynamic | 关闭编译期类型检查,运行期绑定 | 无直接对应(近似 std::any + 反射) |
var | 只是推断,仍是强类型 | auto |
复合类型与可空
int[] arr = { 1, 2, 3 }; // 数组(引用类型,带边界检查)
int[,] grid = new int[2, 3]; // 二维数组
int[][] jag = new int[3][]; // 交错数组
List<int> list = new(); // 泛型集合
(int a, string b) t = (1, "x"); // 元组
var (x, y) = t; // 解构
int? maybe = null; // 可空值类型(Nullable<int>)
string? s = null; // 可空引用类型(C# 8+,编译期警告)
int v = maybe ?? -1; // ?? 提供默认值
默认值(C# 里字段一定有初值)
| 类型 | 默认值 | C++ 里 |
|---|---|---|
| 所有数值类型 | 0 | 未初始化是垃圾值 |
bool | false | 垃圾值 |
char | '\0' | 垃圾值 |
引用类型 / string / 数组 | null | 未初始化指针 |
struct 字段 | 逐字段取各自默认值 | 未初始化 |
可空类型 int? | null | — |
① 类的字段不用你初始化,编译器保证有默认值(C++ 里是垃圾值——C# 消除了这个坑)。
② 局部变量必须先赋值才能用,否则编译不过(叫"明确赋值"检查)。所以 C# 里不会出现"用了未初始化的变量"。
→ 详见 第 04 章 · 值类型 vs 引用类型、第 08 章 · 字符串、集合、LINQ
A.3 变量、常量与字面量
int a; // 局部变量:声明后必须赋值才能用
a = 1;
int b = 2;
int c = 3, d = 4; // 可以一行声明多个
var e = 5; // 推断为 int(等价于 int e = 5)
List<int> g = new(); // 目标类型 new(右侧可省类型)
// 常量与只读
const double Pi = 3.14159; // 编译期常量,必须在声明处赋值
readonly int _id; // 运行期只读,可在构造函数里赋值
static readonly DateTime Start = DateTime.Now; // 常用的"运行时静态常量"
// 字面量小技巧
int big = 1_000_000; // 下划线分隔(可读性)
int hex = 0xFF; // 十六进制
int bin = 0b1010; // 二进制
long l = 9999999999L; // 超出 int 要加 L
| 写法 | 含义 | C++ 里 |
|---|---|---|
const | 编译期常量,只能是基本类型 / string / 枚举 | constexpr;C++ 的 const 语义更宽 |
readonly | 运行期只读字段,可在构造函数里赋值 | const 成员 / 初始化列表 |
static readonly | "运行时才知道的常量" | 静态成员 + 初始化 |
var | 类型推断(编译期确定,仍是强类型) | auto |
字符串字面量
string s1 = "普通\t字符串\n"; // 有转义
string s2 = @"C:\temp\new"; // 逐字字符串:反斜杠不转义
string s3 = $"我是 {name},{age} 岁"; // 插值
string s4 = $@"第 {n} 行:C:\temp"; // 插值 + 逐字
string s5 = """ 原样 "引号" 也不怕 """; // 原始字符串字面量(C# 11)
char ch = 'A'; // 单引号 = char(不是 string!)
string one = "A"; // 双引号 = string
C# 里 'A'(char)和 "A"(string)是两个完全不同的类型,不能混用;'A' 也不能隐式转成 int。
C++ 里 char 和 int 可以随意互转,这个习惯带过来会编译报错。
A.4 运算符全表
| 类别 | C# 运算符 | 说明 / 与 C++ 的差异 |
|---|---|---|
| 算术 | + - * / % ++ -- | 同 C++。注意整数除法仍然截断 |
| 比较 | == != < > <= >= | 同符号,但 == 对引用类型是比引用(见第 04 章) |
| 逻辑 | && || ! | 同 C++;没有 and/or/not 替代写法(C++ 有) |
| 位运算 | & | ^ ~ << >> >>> | >>> 是无符号右移(C# 11,C++ 没有) |
| 赋值 | = += -= *= /= %= &= |= ^= <<= >>= ??= | ??= 是 C# 独有(左值为 null 才赋值) |
| 三元 | cond ? a : b | 同 C++ |
| 空值 | ?? ?. ?[] ?.Invoke() ?[i] | C# 独有的一族"空安全"运算符 |
| 类型测试 | is / as / typeof / sizeof / nameof | 替代 dynamic_cast / typeid / sizeof / 手写字符串 |
| 范围 / 索引 | a[^1](倒数第一)a[1..3](切片) | C# 独有 |
| Lambda | => | 对应 C++ 的 [](){} |
| 溢出控制 | checked / unchecked | 默认不检查整数溢出(和 C++ 一样),可显式开启 |
| 指针(unsafe) | &x / *p / p->M | 只在 unsafe 块里可用(见第 07 章) |
// ① ?? :左值若为 null,取右值(给默认值)
string name = input ?? "(匿名)";
// ② ?. :左值若为 null,整个表达式为 null(不抛异常)
int? len = text?.Length; // text 是 null → len 是 null
var city = user?.Address?.City; // 可以链式一路判空
// ③ ??= :只在为 null 时赋值(惰性初始化)
_cache ??= BuildCache();
// ④ is :类型判断 + 顺便取值
if (obj is Circle c && c.R > 10) { }
if (x is not null) { }
if (n is > 0 and < 100) { } // 关系模式(第 11 章)
A.5 控制流
| 语句 | C++ 里有吗 | 备注 |
|---|---|---|
if / else | 有 | 条件必须是 bool,不能写 if (ptr) |
switch(语句) | 有 | 每个 case 必须 break(不能像 C++ 那样"贯穿") |
switch(表达式) | 无 | var s = x switch { ... }; —— 直接返回值 |
for | 有 | 语法一致 |
foreach | 有(range-for) | foreach (var x in coll),任何可枚举对象都能用 |
while / do-while | 有 | 一致 |
break / continue / return | 有 | 一致 |
goto | 有 | 存在,但几乎不用(goto case 偶尔用) |
using | 无(靠 RAII) | 离开作用域自动 Dispose()(见第 09 章) |
lock | 无(靠 RAII) | lock (obj) { } 自动加解锁 |
yield return | 无 | 写迭代器 / 惰性序列(IEnumerable) |
await | 无(C++20 协程很不同) | 异步等待(见 A.10) |
switch (n) {
case 1:
case 2:
Do(); // 1、2 都走这里(贯穿)
break;
default: break;
}
switch (n) {
case 1:
case 2:
Do(); break; // 多个 case 共享,需 break
default: break;
}
// 表达式版:直接产出值
string s = n switch {
1 => "一",
2 => "二",
_ => "其它" // _ 是兜底
};
// foreach:任何实现了 IEnumerable 的对象都能遍历
foreach (var item in collection) { }
// 带索引(C# 7+ 的元组解构写法)
foreach (var (i, item) in list.Select((v, i) => (i, v))) { }
// 自己写一个"惰性序列":yield return
IEnumerable<int> Fib() {
int a = 0, b = 1;
while (true) {
yield return a;
(a, b) = (b, a + b);
}
}
A.6 方法与参数
// 普通方法
public int Add(int a, int b) { return a + b; }
// 表达式体(一行方法)
public int Add2(int a, int b) => a + b;
// 默认参数(C++ 也有)
void Log(string msg, int level = 0) { }
// 命名实参(C# 独有,调用时可读性极强)
Log(level: 2, msg: "hi");
// params:可变参数(C++ 是 ... 或 initializer_list)
int Sum(params int[] nums) => nums.Sum();
Sum(1, 2, 3, 4); // 直接展开传
// ref / out / in:见第 07 章
void Inc(ref int x) => x++;
bool TryGet(string k, out int v) { v = 0; return false; }
double Dist(in BigStruct a) => 0;
// 局部函数(方法里再定义方法)
void Outer() {
int Inner(int x) => x * 2;
Console.WriteLine(Inner(3));
}
// 扩展方法:给"别人的类型"加方法(C++ 里做不到)
public static class StringExt {
public static bool IsEmail(this string s) => s.Contains('@');
}
// 用法:"abc@x.com".IsEmail() ← 像自带的方法一样
| 修饰符 | 含义 | C++ 对应 |
|---|---|---|
static | 属于类型而非实例 | 同 |
virtual | 可被重写(必须显式写) | virtual |
override | 重写基类虚方法(必须显式写) | override(C++ 里可选) |
abstract | 无实现,子类必须实现 | = 0 纯虚 |
sealed | 封死,不能再被重写 / 继承 | final(C++11) |
new | 隐藏基类同名成员(不是重写) | 无(C++ 里就是普通同名函数) |
partial | 允许把类型拆到多个文件 | 无 |
async | 方法内含 await | 无(C++20 协程不同) |
unsafe | 允许裸指针 | —(C++ 到处都行) |
extern | 声明实现在外部(P/Invoke) | extern 链接 |
A.7 类与成员速查
public class Sample : IDisposable {
// ① 常量 / 静态字段 / 字段
public const int Max = 100;
private static int _counter;
private readonly string _name;
// ② 属性(C# 的招牌)
public int Id { get; set; } // 自动属性
public string Name => _name; // 只读计算属性
public string City { get; init; } = "上海"; // init:只在创建时能赋值
public static int Count => _counter; // 静态属性
// ③ 索引器:让对象支持 []
public int this[int i] { get => i; set { } }
// ④ 构造函数 / 静态构造函数 / 终结器
public Sample(string name) { _name = name; _counter++; }
static Sample() { _counter = 0; } // 只执行一次的静态初始化
~Sample() { } // 终结器(≠ C++ 析构函数!见第 03 章)
// ⑤ 方法 / 虚方法
public void Run() { }
public virtual void Step() { }
// ⑥ 事件与委托成员
public event Action? Changed;
public Func<int, int>? Transform;
// ⑦ 运算符重载 / 转换运算符
public static Sample operator +(Sample a, Sample b) => a;
public static implicit operator string(Sample s) => s._name;
// ⑧ 嵌套类型
public enum Kind { A, B }
// ⑨ 实现接口
public void Dispose() { }
}
| 类型关键字的种类 | C# | 特点 |
|---|---|---|
| 类 | class | 引用类型,单继承,可多实现接口 |
| 结构 | struct | 值类型,不能继承,适合小数据 |
| 记录 | record / record struct | 值语义的类,自动生成 Equals / 哈希 / ToString |
| 接口 | interface | 只约定能力,可实现多个 |
| 枚举 | enum | 底层是整数,有 Enum.GetValues 等工具 |
| 委托类型 | delegate | 类型安全的函数签名 |
| 静态类 | static class | 只能有静态成员,用来放工具方法 |
| 分部 | partial class/struct/interface | 拆成多个文件,编译时合并 |
| 抽象 / 密封 | abstract class / sealed class | 不能实例化 / 不能被继承 |
| 访问级别 | 可见范围 | C++ 对应 |
|---|---|---|
public | 任何地方 | 同 |
private | 仅本类(成员默认值) | private |
protected | 本类 + 派生类 | protected |
internal | 本程序集(本项目)内 | 无(近似匿名命名空间) |
protected internal | 派生类 或 同程序集 | 无 |
private protected | 派生类 且 同程序集 | 无 |
→ 详见 第 05 章 · 面向对象差异、第 11 章 · 现代 C# 特性
A.8 继承 · 多态 · 接口
public interface IShape { // 接口:只声明能力
double Area();
string Name { get; }
}
public abstract class Shape : IShape { // 抽象类:唯一基类,可给部分实现
public abstract double Area(); // 子类必须实现
public virtual string Name => "形状"; // 子类可选重写
}
public sealed class Circle : Shape { // sealed:到此为止,不许再继承
public double R { get; }
public Circle(double r) => R = r;
public override double Area() => Math.PI * R * R;
public override string Name => "圆";
}
// 多态:基类引用指向派生类对象
Shape s = new Circle(2);
Console.WriteLine(s.Area()); // 走 Circle 的实现
// 类型判断与转换
if (s is Circle c) { } // 安全:是才转
Circle? c2 = s as Circle; // 不是则为 null
Circle c3 = (Circle)s; // 强制转,失败抛异常
| 特性 | C# | C++ |
|---|---|---|
| 类多继承 | 不支持 | 支持 |
| 接口多实现 | 支持 | 用纯虚类模拟 |
| 默认虚方法 | 不虚,要写 virtual | 不虚,要写 virtual(一样) |
| 重写时 | 必须写 override | 可写可不写 |
| 隐藏成员 | 必须写 new | 无此概念 |
| 基类构造调用 | : base(args) | 初始化列表 |
| 所有类型的根 | object | 无 |
| 接口默认实现 | C# 8+ 支持 | 纯虚类可带实现 |
A.9 泛型 · 委托 · Lambda · LINQ
// 泛型类
public class Box<T> {
public T Value { get; set; }
}
// 泛型方法 + 约束
T Max<T>(T a, T b) where T : IComparable<T>
=> a.CompareTo(b) > 0 ? a : b;
// 常用约束:where T : struct / class / new() / Base / IFoo / notnull / unmanaged
// 自定义委托类型
public delegate int Calculator(int a, int b);
// 内置的(日常几乎只用这三个)
Action<int> log = x => Console.WriteLine(x); // 无返回值
Func<int, int, int> add = (a, b) => a + b; // 最后一个是返回类型
Predicate<int> isPos = x => x > 0; // 返回 bool
// 多播:一个委托串多个方法(C# 独有)
log += x => Console.WriteLine($"第二个:{x}");
log -= log; // 取消挂载
// 事件:对外只能 += / -=
public event EventHandler? Clicked;
Clicked?.Invoke(this, EventArgs.Empty);
using System.Linq;
var result = people
.Where(p => p.Age >= 18) // 筛选
.OrderByDescending(p => p.Age) // 排序
.Select(p => p.Name) // 投影
.ToList(); // 立刻执行,得到 List
// 聚合与判断
int total = nums.Sum();
double avg = nums.Average();
bool anyBig = nums.Any(n => n > 100);
var first = people.FirstOrDefault(p => p.Age > 60);
// 分组
var groups = people.GroupBy(p => p.City);
// 查询语法(等价写法,处理 join 时更好读)
var q = from p in people
where p.Age > 18
orderby p.Age descending
select p.Name;
Where / Select / OrderBy 不会立刻计算,
只是搭了一条"管道";真正遍历它(foreach、ToList()、Count())时才执行。
想固定结果就加 .ToList()。
→ 详见 第 06 章 · 泛型 vs 模板、第 08 章 · 字符串、集合、LINQ、第 10 章 · 委托、事件、Lambda
A.10 异常 · 资源 · 异步
try {
RiskyWork();
}
catch (FileNotFoundException ex) { Console.WriteLine(ex.Message); }
catch (Exception ex) when (ex.HResult != 0) { throw; } // when = 异常筛选
finally { Cleanup(); } // 一定执行
// 自定义异常
public class MyException : Exception {
public MyException(string msg, Exception? inner = null) : base(msg, inner) { }
}
// 关键:重新抛出用 throw;(不要 throw ex;)
catch (Exception ex) { Log(ex); throw; }
using var reader = new StreamReader("a.txt"); // 作用域结束自动 Dispose()
string line = reader.ReadLine();
using (Stream s = ..., StreamReader r = new(s)) { } // 也能一次写多个
lock (_gate) { } // 自动加锁 / 解锁,相当于 C++ 的 lock_guard
// 自己写资源类:实现 IDisposable
public void Dispose() { /* 释放句柄、连接等 */ GC.SuppressFinalize(this); }
public async Task<string> FetchAsync(string url) {
using var http = new HttpClient();
string body = await http.GetStringAsync(url); // 不阻塞线程
return body;
}
// 并行等多个
string[] all = await Task.WhenAll(urls.Select(FetchAsync));
await Task.Delay(1000); // 异步延时
// 返回值:Task(无返回)/ Task<T>(有返回)/ ValueTask<T>(高频小结果)
// 三不要:不要 .Result / 不要 .Wait() / 不要 async void(除事件处理器)
→ 详见 第 09 章 · 异常与资源管理、第 11 章 · 现代 C# 特性
A.11 命名规范(和 C++ 差别很大)
| 成员 | C# 惯例 | 常见 C++ 写法 |
|---|---|---|
| 类 / 结构 / 接口 / 记录 | PascalCase;接口加 I 前缀 | PascalCase / snake_case;接口常见 Shape |
| 方法 / 属性 / 事件 | PascalCase | PascalCase 或 snake_case |
| 局部变量 / 参数 | camelCase | snake_case 很常见 |
| 私有字段 | _camelCase(下划线开头) | m_ 前缀 / name_ 后缀 |
| 常量 | PascalCase(不是全大写) | MAX_SIZE |
| 泛型参数 | T、TKey、TValue | T、Key |
| 命名空间 | PascalCase.Point.Separated | 常见全小写 foo::bar |
① 方法名首字母大写:calculateTotal() → CalculateTotal()。
② 常量不要全大写:MAX_SIZE → MaxSize。
③ 长度是属性不是方法:s.size() → s.Length;list.size() → list.Count(不带括号)。
A.12 关键字总表(可筛选)
下面这张表把 C# 的关键字按用途归类。在输入框里打字可以实时筛选——比如输入 ref 或 async,只留下相关行。
共 60 条
| 关键字 | 作用 | C++ 里对应 / 差异 |
|---|---|---|
namespace | 组织类型、避免重名 | namespace(可用文件作用域写法) |
using | ① 引入命名空间 ② 自动释放资源 | ① 近似 using namespace;② RAII(C# 独有) |
class | 引用类型 | class |
struct | 值类型,不可继承 | struct(但 C++ 的 struct 是类,不是值类型) |
record | 值语义数据类,自动生成比较 / 哈希 / 打印 | 无(要手写 operator==) |
interface | 能力约定,可多实现 | 纯虚类 |
enum | 具名整数常量组 | enum class |
delegate | 定义函数签名的类型 | 函数指针 / std::function(但委托是对象) |
event | 受保护的多播委托 | 无 |
partial | 把类型拆到多个文件 | 无 |
public | 公开 | public |
private | 仅本类(成员默认) | private |
protected | 本类 + 派生类 | protected |
internal | 同程序集可见 | 无对应(近似匿名命名空间) |
static | 属于类型本身 | static |
readonly | 运行期只读字段 / 只读结构成员 | const 成员 |
const | 编译期常量 | constexpr |
virtual | 声明"可被重写"(默认不虚) | virtual |
override | 重写基类虚方法(必须写) | override(可选) |
abstract | 抽象成员 / 抽象类 | = 0 纯虚 |
sealed | 禁止继续重写 / 继承 | final |
new | ① 创建对象 ② 隐藏基类成员 | ① new ② 无此概念 |
base | 访问基类成员 / 调用基类构造 | Base:: / 初始化列表 |
this | 当前实例;也用于扩展方法的第一个参数 | this |
ref | 按引用传递(调用处也要写) | T&(C# 更显式、更安全) |
out | 输出参数,函数内必须赋值 | 输出参数 T* |
in | 只读引用参数(省拷贝) | const T& |
params | 可变参数数组 | ... / initializer_list |
value | 属性的 set 访问器里的隐式参数 | 无 |
get / set / init | 属性访问器 | 无(要手写 GetX / SetX) |
operator | 运算符重载 / 转换运算符 | operator |
implicit / explicit | 隐式 / 显式类型转换运算符 | 转换构造函数 / operator T() |
if / else | 条件分支 | 同 |
switch / case / default | 多路分支 / 模式匹配 | 同(但不允许贯穿) |
when | case 的附加条件 / 异常筛选 | 无 |
for | 计数循环 | 同 |
foreach / in | 遍历可枚举对象 | range-based for |
while / do | 条件循环 | 同 |
break / continue | 跳出 / 继续 | 同 |
return | 返回 | 同 |
goto | 跳转(极少用) | 同 |
yield | 写迭代器 / 惰性序列 | 无(C++20 生成器很不同) |
throw | 抛异常 | 同(C# 只能抛 Exception 派生类) |
try / catch / finally | 异常处理 | 前两个有;没有 finally |
lock | 互斥区,自动加解锁 | std::lock_guard |
checked / unchecked | 开启 / 关闭整数溢出检查 | 无 |
async / await | 异步方法 / 异步等待 | 无(C++20 协程差别很大) |
unsafe | 允许裸指针与指针运算 | —(C++ 默认就能用) |
fixed | 固定内存地址防止 GC 搬移 | 无(C++ 没有 GC) |
stackalloc | 在栈上分配数组 | alloca |
typeof | 拿到类型对象(运行期) | typeid |
nameof | 把标识符名字变成字符串 | 无(靠宏或手写) |
sizeof | 取大小(非托管类型) | sizeof |
is / as | 类型判断 / 安全转型 | dynamic_cast |
typeof / dynamic | 静态类型对象 / 动态类型 | typeid / std::any |
true / false / null | 布尔与空 | true/false;nullptr |
void | 无返回值 | void |
var | 类型推断 | auto |
object | 所有类型的根类型 | 无 |
string / char / bool | 内置类型别名(映射到 .NET 类型) | std::string / char / bool(char 是 2 字节) |
where | 泛型约束 | C++20 requires / concept |
extern | 声明外部实现(P/Invoke) | extern |
volatile | 禁止编译器优化该字段访问 | volatile(语义有差异) |
required | 要求对象创建时必须初始化该成员(C# 11) | 无 |
C# 里有一批词只在特定位置才是关键字,所以你依然可以拿它们当变量名:
value、get、set、init、
add、remove、where、select、
from、let、orderby、join、
on、equals、into、when。
这是为了保持向后兼容——C++ 里加新关键字常会砸坏老代码,C# 用"上下文关键字"绕开了这个问题。
小测验:你现在能写出多少?
Q1:不看上面,写出一个"值语义 + 自动比较 + 自动打印"的类型展开
public record Money(decimal Amount, string Currency);
var a = new Money(10m, "CNY");
var b = new Money(10m, "CNY");
Console.WriteLine(a == b); // True(按内容比)
Console.WriteLine(a); // Money { Amount = 10, Currency = CNY }
var c = a with { Amount = 20m }; // 基于 a 改一个字段
一行 record 就替代了 C++ 里"重载 ==、写 <<、写哈希"的一大段样板。详见第 11 章。
Q2:把 C++ 的"遍历容器并筛选出偶数平方和"改写成 C#展开
int sum = 0;
for (int x : v)
if (x % 2 == 0)
sum += x * x;
int sum = v
.Where(x => x % 2 == 0)
.Select(x => x * x)
.Sum();
C# 的写法是"声明我要什么",不是"告诉它怎么循环"。这套 LINQ 是 C++ 里最缺少的那类能力。
Q3:下面哪几行是编译不过的?为什么?展开
A. int? x = null;
B. int y = null;
C. char c = "A";
D. char c = 'A';
E. if (new List<int>()) { }
F. string s = "a"; s[0] = 'b';
B、C、E、F 编译不过。
· B:int 是值类型,不能为 null(要写 int?)。
· C:char 必须用单引号,双引号是 string,不能隐式转。
· E:if 只接受 bool;C++ 里"对象能转 bool"这套在 C# 不存在(可以写 if (list != null) 或 if (list.Count > 0))。
· F:string 不可变,索引器只读。
语法面扫完了,接下来去理解"为什么这么设计"——那是这套教程真正的主线:
01 为什么会有 C# → 02 编译与运行模型 → 03 内存管理,
之后按 04 到 12 顺序推进即可。