# 本文参考
C#语言入门详解 —— 刘铁猛
C# 刘铁猛笔记
# 类与名称空间
- 类
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(通用建模语言)类图
# 排除错误
- 仔细阅读编译器的报错
- 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 实现桌面时钟(侧重事件)
... | |
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 |
变量 | 内存编号 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
---|---|---|---|---|---|---|---|---|---|---|
b(100) | 10000000 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 0 | |
sb(-100) | 10000001 | 1 | 0 | 0 | 1 | 1 | 1 | 0 | 0 | |
us(1000) | 10000002 | 1 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 低八位 |
10000003 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 高八位 | |
s(-1000) | 10000004 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 低八位 |
10000005 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 高八位 |
# 引用类型的变量与实例
关系:引用类型变量里存储的数据是对象的内存地址
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
Student stu; | |
} | |
} | |
class Student | |
{ | |
uint ID; | |
ushort Score; | |
} |
计算机看到引用类型,直接给它分配 4 个字节,而且全部 Bit 置 0,告诉你这个变量没有引用任何实例
Student stu; | |
stu = new Student(); |
现在引用变量里面存的是实例的地址。 图中左侧是栈,右侧是堆
Student stu; | |
stu = new Student(); | |
Student stu2; | |
stu2 = stu; |
局部变量是在 stack(栈)上分配内存
# 变量的默认值
一旦变量在内存中分配好后,它的内存块就被统统刷成 0,这就是它的默认值
局部变量没有默认值,因为 C# 为了避免不安全代码,要求局部变量必需有显式赋值
# 常量
值不可改变的变量
# 装箱与拆箱
int a = 100; | |
object x; |
# 装箱(栈到堆)
int a = 100; | |
object x; | |
x = a; |
# 拆箱(堆到栈)
int a = 100; | |
object x; | |
x = a; | |
int b = (int)x; | |
Console.WriteLine(b); |
# 方法
# 由来
方法(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
# Step-out
Shift + F11:用于跳出当前方法并返回到调用它的方法
# Locals
# 方法的调用与栈
方法调用时栈内存的分配
- 对 stack frame 的分析