# 本文参考

# 类与名称空间

  • class :构成程序的主体
  • 名称空间 namespace :以树型结构组织类(和其他类型)
using System; // 引入命名空间
namespace HelloWorld // 命名空间
{
  class Program // 类名与文件名保持一致
  {
    static void Main(string[] args)
    {
      Console.WriteLine("hello!");
    }
  }
}

MSDN 文档(即添加 HelpViewer)开始菜单打开 Visual Studio Installer ➡ 修改 ➡ 单个组件 ➡ 搜索 HelpViewer

# 类库的引用

使用名称空间的物理基础

不同技术类型的项目会默认引用不同的类库

# DLL 引用(黑盒引用,无源代码)

  • 引用 ➡ 右键添加引用 ➡ 浏览 ➡ 手动添加.dll文件
  • 引用 ➡ 右键添加引用 ➡ 程序集 ➡ 搜索添加
案例:引用 System.Windows.Forms 来实现窗体
using System.Windows.Forms;
namespace HelloWorld
{
  class Program
  {
    static void Main(string[] args)
    {
      Form form = new Form();
      form.ShowDialog();
    }
  }
}

# NuGet 简介

引用 ➡ 右键管理 NuGet 程序包 ➡ 浏览下搜索 EntityFramework 安装

# 项目引用(白盒引用,有源代码)

引用 ➡ 右键添加引用 ➡ 项目(解决方案右键添加现有项)➡ 选择添加

一个项目可以应用在多个解决方案上

自己建类库左上角文件 ➡ 新建 ➡ 项目 ➡ 搜索栏搜类库模板

# 依赖关系

  • 类(或对象)之间的耦合关系

  • 优秀的程序追求” 高内聚,低耦合 “

    教学程序往往会违反这个原则

  • UML(通用建模语言)类图

    img

# 排除错误

  • 仔细阅读编译器的报错
  • MSDN 文档与搜索引擎结合

# 类、对象、类成员

# 定义

类是现实世界事物的模型

类是对现实世界事物进行抽象所得到的结果

  • 事物包括 “物质”(实体)与 “运动”(逻辑)

  • 建模是一个去伪存真、由表及里的过程

# 与对象的关系

  • 对象也叫实例,是类经过 “实例化” 后得到的内存中的实体

  • 依照类,我们可以创建对象,这就是 “实例化”

  • 使用 new 操作符创建类的实例

  • 引用变量与实例的关系

    (new Form()).Text = "My Form";
    (new Form()).ShowDialog(); // 另一个实例
    // 引用变量
    Form myForm = new Form();
    myForm.Text = "My Form";
    myForm.ShowDialog();

# 类的三大成员

  • 属性(Property)

    • 存储数据,组合起来表示类或对象当前的状态
  • 方法(Method)

    • 由 C 语言中的函数(function)进化而来,表示类或对象 “能做什么”

    • 工作中 90% 的时间是与方法打交道,因为它是 “真正做事”、“构成逻辑” 的成员

  • 事件(Event)

    • 类或对象通知其它类或对象的机制,为 C# 所特有(Java 通过其它办法实现这个机制)
  • 善用事件机制非常重要(切勿滥用)

😀

某些特殊类或对象在成员方面侧重点不同

  • 模型类或对象重在属性,如 Entity Framework

  • 工具类或对象重在方法,如 Math,Console

  • 通知类或对象重在事件,如各种 Timer

案例:使用 wpf 实现桌面时钟(侧重事件)

img

...
using System.Windows.Threading;
namespace EventSample
{
  public partial class MainWindow : Window
  {
    public MainWindow()
    {
      InitializeComponent();
      DispatcherTimer timer = new DispatcherTimer();
      timer.Interval = TimeSpan.FromSeconds(1);
      timer.Tick += Timer_Tick;
      timer.Start(); // 时钟开始
    }
    private void Timer_Tick(object sender, EventArgs e)
    {
      this.timeTextBox.Text = DateTime.Now.ToString();
    }
  }
}

# 静态成员与实例成员

  • 静态(Static)成员:类的成员

    物体固有的性质,隶属于某个类

  • 实例(非静态)成员:对象的成员

    实例成员是属于对象的,而非类

  • 绑定(Binding)指的是编译器如何把一个成员与类或对象关联起来

    不可小觑的 . 操作符 —— 成员访问

using System.Windows.Forms;
namespace Test
{
  class Program
  {
    static void Main(string[] args)
    {
      Console.WriteLine("静态方法");
      Form form = new Form();
      form.Text = "实例属性";
      form.ShowDialog();
    }
  }
}

🚩 MSDN 文档中标有 红色S 为静态成员

# 基本元素

构成 C# 语言的基本元素

  • 关键字(Keyword)

    MSDN 文档位置: Visual Basic 和 Visual C# ➡ C#参考 ➡ C#关键字

  • 操作符 / 运算符(Operator)

    MSDN 文档位置: Visual Basic 和 Visual C# ➡ C#参考 ➡ C#运算符

  • 标识符(Identifier)

  • 标点符号

  • 文本(字面值)

  • 注释与空白

前五种称为标记(token)

# 合法标识符

  • 标识符不允许是关键字,如果非要用关键字就在前面加 @ 符号

  • 标识符必需以字符或下划线开头

    字符包括英文字符,也包括汉语、俄语等字符

  • 开始字符的后面可以跟字符、数字、下划线

# 命名规范

  • 变量名 —— 驼峰命名法 Camel

    首字母小写,后续单词首字母大写

  • 方法、类、名称空间 —— 帕斯卡命名法 Pascal

    每个单词的首字母都大写

方法名应该是动词或动词短语

# 字面值

  • 整数

    int x = 10;
    long y = 10L;
  • 实数(小数)

    float x = 3.0F;
    double y = 4.0;
  • 字符

  • 字符串

    char c = 'a';
    string str = "abc";
    bool a = true;
  • 布尔

  • 空 null

# 初识类型、变量、方法

  • 初识类型(Type)

    亦称是数据类型(Data Type)

  • 变量:存放数据的地方,简称 “数据”

    • 变量的声明
    • 变量的使用
  • 方法(旧称函数):处理数据的逻辑,又称 “算法”

    • 方法的声明
    • 方法的调用
  • 程序 = 数据 + 算法

    有了变量和方法就可以写有意义的程序了

# 类型

Type 又名数据类型(Data Type)

  • 小内存容纳大尺寸数据会丢失精度、发生错误
  • 大内存容纳小尺寸数据会导致浪费
  • 编程语言的数据类型与数据的数据类型不完全相同

强类型语言与弱类型语言

  • 强类型:编写程序时,程序中的数据受到数据类型的约束

  • 弱类型:数据受类型约束不严格,或基本不受约束(如 JavaScript 动态类型)

  • C# 从 4.0 开始引入了 Dynamic,让它可以利用动态语言的一些特性,但 C# 依然是强类型编程语言

    namespace DynamicSample
    {
      class Program
      {
        static void Main(string[] args)
       {
          dynamic myVar = 100;
          Console.WriteLine(myVar);
          myVar = "Mr.Okay!";
          Console.WriteLine(myVar);
        }
      }
    }

# 作用

  • 存储此类型变量所需的内存空间大小
  • 此类型的值可表示的最大、最小值范围
  • 此类型所包含的成员(如方法、属性、事件等)
  • 此类型由何基类派生而来
  • 程序运行的时候,此类型的变量被分配在内存的什么位置
    • Stack 简介(栈)
    • Stack overflow 栈溢出
    • Heap 简介(堆)
    • 使用 Performance Monitor 查看进程的堆内存使用量
    • 关于内存泄漏
  • 此类型所允许的操作(运算)

对一个程序来说,静态指编辑期、编译期;动态指运行期
静态时装在硬盘里,动态时装在内存里

# 反射

通过反射在运行期获取类的属性和方法

static void Main(string[] args)
{
  Type myType = typeof(Form);
  Console.WriteLine(myType.BaseType.FullName + Environment.NewLine + myType.FullName);
  var pInfos = myType.GetProperties(); // 获取属性
  foreach (var p in pInfos)
  {
    Console.WriteLine(p.Name);
  }
  var mInfos = myType.GetMethods(); // 获取方法
  foreach (var m in mInfos)
  {
    Console.WriteLine(m.Name);
  }
}

# Stack & Heap

Stack overflow 栈溢出

  • 函数调用过多(算法错误)
  • 在栈上分配了过多内存
案例
// 调用过多(如递归)
static void Main(string[] args)
{
  BadGuy bg = new BadGuy();
  bg.BadMethod();
}
class BadGuy
{
  public void BadMethod()
  {
    int x = 100;
    this.BadMethod();
  }
}
// 在栈上分配过大内存
static void Main(string[] args)
{
  unsafe
  {
    int* p = stackalloc int[9999999];
  }
}

# 类型系统 —— 五大数据类型

  • 类(Classes):如 Windows、Forms、Console、String
  • 结构体(Structures):如 Int32、Int64、Single、Double
  • 枚举(Enumerations):如 HorizontalAlignment、Visibility
  • 接口(Interfaces)
  • 委托(Delegates)

#

type myType = typeof(Form);
Console.WriteLine(myType.FullName);
Console.WriteLine(myType.IsClass);

🚩 F12 跳转到源代码定义

# 结构体

int、long 是结构体类型

# 枚举

using System.Windows.Forms;
namespace Test
{
  class Program
  {
    static void Main(string[] args)
    {
      Form f = new Form();
      f.WindowState = FormWindowState.Maximized; // 最大化的窗口
      f.WindowState = FormWindowState.Normal; // 默认大小的窗口
      f.WindowState = FormWindowState.Minimized; // 最小化的窗口
      f.ShowDialog();
    }
  }
}

# 派生谱系

派生谱系

# 变量

# 定义

表面上看,变量的用途是存储数据

实际上,变量表示了存储位置,并且每个变量都有一个类型,以决定什么样的值能够存入变量

  • 变量名表示(对应着)变量的值在内存中的存储位置
  • 计算机系统通过变量的类型来分配给它对应大小的内存空间

变量 = 以变量名所对应的内存地址为起点,以其数据类型所要求的存储空间为长度的一块内存区域

😀

狭义的变量指局部变量,因为其它种类的变量都有自己的约定名称

  • 简单地讲,局部变量就是方法体(函数体)里声明的变量

😀 7 种变量:

  • 静态变量
  • 实例变量(成员变量、字段)
  • 数组元素
  • 值参数
  • 引用参数
  • 输出形参
  • 局部变量
演示
class Program
{
  static void Main(string[] args)
  {
    Student.Amount = 100; // 静态成员变量
    Student stu = new Student();
    stu.Age = 18; // 实例变量(字段:是属性的雏形,可以赋任意值)
    // 数组元素
    int[] arr = new int[100]; // 长度为 100 的数组
  }
  class Student
  {
    public static int Amout;
    public int Age;
    public string Name;
    //ref double a:引用参数变量
    //out double a:输出参数变量
    public double Add(double a, double b) //a、b 为值参数变量
    {
        double result = a + b; // 局部变量
        return result;
    }
  }
}

声明变量: 有效的修饰符组合类型opt 类型 变量名 初始化器opt ,pot:可有可无

# 值类型的变量

值类型没有实例,所谓的 “实例” 与变量合而为一

byte b = 100;
sbyte sb = -100; // 100 按位取反 + 1
ushort us = 1000; // 占 2 个字节
short s = -1000;
string str = Convert.ToString(s, 2); // 验证
Console.WriteLine(str); // 1111110000011000
变量内存编号76543210
b(100)1000000001100100
sb(-100)1000000110011100
us(1000)1000000211101000低八位
1000000300000011高八位
s(-1000)1000000400011000低八位
1000000511111100高八位

# 引用类型的变量与实例

关系:引用类型变量里存储的数据是对象的内存地址

class Program
{
  static void Main(string[] args)
  {
    Student stu;
  }
}
class Student
{
  uint ID;
  ushort Score;
}

计算机看到引用类型,直接给它分配 4 个字节,而且全部 Bit 置 0,告诉你这个变量没有引用任何实例

img

Student stu;
stu = new Student();

现在引用变量里面存的是实例的地址。 图中左侧是栈,右侧是堆

img

Student stu;
stu = new Student();
Student stu2;
stu2 = stu;

img

局部变量是在 stack(栈)上分配内存

# 变量的默认值

一旦变量在内存中分配好后,它的内存块就被统统刷成 0,这就是它的默认值

局部变量没有默认值,因为 C# 为了避免不安全代码,要求局部变量必需有显式赋值

# 常量

值不可改变的变量

# 装箱与拆箱

int a = 100;
object x;

img

# 装箱(栈到堆)

int a = 100;
object x;
x = a;

img

# 拆箱(堆到栈)

int a = 100;
object x;
x = a;
int b = (int)x;
Console.WriteLine(b);

img

# 方法

# 由来

  • 方法(method)的前身是 C/C++ 语言的函数(function)

    • 方法是面向对象范畴的概念,在非面向对象语言中仍然称为函数

    • 使用 C/C++ 语言做对比

      演示
      //c 语言
      #include <stdio.h>
      double Add(double a, double b)
      {
        return a + b;
      }
      int main()
      {
        double x = 3.0;
        double y = 5.0;
        double result = Add(x, y);
        printf("%f + %f = %f", x, y, reslut);
        return 0;
      }
      // c++
      #include <iostream>
      
      double Add(double a, double b)
      {
        return a + b;
      }
      
      int main()
      {
        double x = 3.0;
        double y = 5.0;
        double result = Add(x, y);
        std::cout << x << "+" << y << "=" << result;
        return 0;
      }
      
  • 方法永远都是类(或结构体)的成员

    • C# 语言中函数不可能独立于类(或结构体)之外
    • 只有作为类(或结构体)的成员时才被称为方法
    • C++ 中函数可以独立于类之外,称为 “全局函数”
  • 方法是类(或结构体)最基本的成员之一

    • 最基本的成员只有两个 —— 字段与方法(成员变量与成员方法),本质还是数据 + 算法
    • 方法表示类(或结构体)“能做什么事情”
  • 为什么需要方法和函数

    • 隐藏复杂的逻辑
    • 把大算法分解成小算法
    • 复用(reuse,重用)

⚠️ 注:C# namespace 里不能有方法

当一个函数以类的成员的身份出现时,它就被称为方法,方法有一个别名叫 “成员函数”

演示:c++ 中添加类

Student.h 文件

#pragma once
class Student
{
public:
  Student();
  ~Student();
  void SayHello();
};

Student.cpp 文件

#include "Student.h"
#include <iostream>
Student::Student()
{
}
Student::~Student()
{
}
void Student::SayHello()
{
  std::cout << "Hello! I'm a student!";
}

Source.cpp 文件

#include <iostream> // 标准库用 & lt;>
#include "Student.h" // 自己定义的头文件用 ""
int main()
{
  Student *pStu = new Student();
  pStu->SayHello();
  return 0;
}

# 声明

语法详解:

  • 参考 C# 语言文档(声明 / 定义不分家)
  • Parameter 全称为 “formal parameter”
  • 形式上的参数,简称 “形参”

命名规范:

  • 大小写规范:单词首字母都大写
  • 需要以动词或者动词短语作为名字

# 调用

  • Argument 中文 C# 文档的官方译法为 “实际参数”,简称 “实参”

    • 可理解为调用方法时的真实条件
  • 调用方法时的 argument 列表要与定义方法时的 parameter 列表相匹配

    • C# 是强类型语言,argument 是值,parameter 是变量,值与变量一定要匹配,不然编译器会报错

# 构造器

构造器(constructor)是类型的成员之一

狭义的构造器指的是 “实例构造器”(instance constructor)

namespace ConstructorExample
{
  class Program
  {
    static void Main(string[] args)
    {
      Student stu = new Student();
      Console.WriteLine(stu.ID);
    }
  }
  class Student
  {
    // 无参数构造器
    public Student()
    {
      this.ID = 1;
      this.name = "";
    }
    // 有参数构造器
    public Student(int InitId, string InitName)
    {
      this.ID = InitId;
      this.name = InitName;
    }
    public int ID;
    public string name;
  }
}

🚩 快捷方式:ctor + 两次 tab

构造器的内存原理

1、默认构造器

Student stu = new Student();

默认构造器内存原理

2、带参数构造器内存原理

Student stu = new Student(1, "Mr.Okay");

带参数构造器内存原理

# 方法的重载 Overload

  • 方法签名(method signature)由方法的名称、类型形参的个数和它的每一个形参(按从左到右的顺序)的类型和种类(值、引用或输出)组成。方法签名不包含返回类型
  • 实例构造函数签名由它的每一个形参(按从左到右的顺序)的类型和种类(值、引用或输出)组成
  • 重载决策(到底调用哪一个重载):用于在给定了参数列表和一组候选函数成员的情况下,选择一个最佳的函数成员来实施调用
演示
public int Add(int a, int b)
{
  return a + b;
}
public double Add(double a, double b)
{
  return a + b;
}
// 类型形参
public int Add<T>(int a, int b)
{
  T t;//...
  return a + b;
}
// 参数种类
public int Add(ref int a, int b) // 引用
{
  return a + b;
}
public int Add(int a,out int b) // 输出
{
  b = 100;
  return a + b;
}

# 对方法进行 debug

  • 设置断点(breakpoint)
  • 观察方法调用时的 call stack
  • Step-in(F11)、Step-over(F10)、Step-out(Shift + F11)
  • 观察局部变量的值与变化

# call stack

img

# Step-out

Shift + F11:用于跳出当前方法并返回到调用它的方法

# Locals

img

# 方法的调用与栈

方法调用时栈内存的分配

  • 对 stack frame 的分析