C#1:类、名称空间
以刘铁猛老师和清华大学出版社《C#高级编程12版》进行学习。
C#程序的结构
名称空间(namespace){类(class){方法{语句、表达式;}}
}
using System;//使用名称空间
using System.Collections.Generic;
namespace IndexerExample//命名空间声明,用于组织代码
{class Program//类声明,所有代码必须包含在类中{static void Main(string[] args)//主方法(Main),程序的入口点{int x=10;Console.WriteLine(x);//语句与表达式,执行具体的输出操作}}
}
名称空间:以树型结构组织类(和其他类型)
namespace Example{ //写法一namespace ObjectExample{namespace IndexerExample{class Sample{public void CS(){Console.WriteLine("cs");}}}}
}
写法二
namespace Example.ObjectExample.IndexerExample{class Sample{public string CS(){int astring b= a+"cs";}}
}
//使用名称空间 using
using Example.ObjectExample.IndexerExample;//方法一
namespace A{class Program{static void Main(string[] args){string a = CS(1);//方法一:先调用后使用string b = using Example.ObjectExample.IndexerExample.CS(2);//方法2:使用时调用}}
}
//别名:给长的名称空间起一个小名字
using Webtimer = System.Web.UI.Timer;
类:构成程序的主体
类和名称空间
类和名称空间:类和名称空间都存储在类库中。类库引用是使用命名空间的物理基础。
例如:就像书是放在图书馆这样一个物理空间。Generic书在System图书馆的Collections中。Program在一个xx类库中,类库名字为IndexerExample。
声明
声明 Declaration:在程序中引入一个新的标识符,告诉编译器某个实体(如变量、类、方法等)的存在及其类型,但通常不一定会立即为它分配内存空间或赋予初始值。
- 变量声明:
int number;(告诉编译器这里有个整型变量叫 number) - 方法声明:定义一个方法的名字、返回值类型和参数列表。
- 类/结构体声明:例如
class Car { },定义了一个新的类型。 - 命名空间声明:例如
namespace MyProject { },用于组织代码逻辑组。
声明 vs 初始化 vs 实例化
-
声明(Declaration)
:只生成对象或变量名,不赋值,不分配内存。
- 代码:
string str;
- 代码:
-
初始化(Initialization):本质都是“给变量赋予初始状态”
:在声明的基础上,给变量或对象赋予一个初始值。
- 代码:
int x = 1; - 底层动作:
- 对于值类型(如
int a = 10;):初始化只是把数据10写入栈(Stack)上已经分配好的内存空间。 - 对于引用类型(如
MyClass obj = new MyClass();):初始化只是把堆上对象的内存地址(指针)写入栈(Stack)上的局部变量中。
- 对于值类型(如
- 代码:
-
实例化(Instantiation)
:专门针对“引用类型(如类)”,使用
new关键字在内存(堆)中为对象分配空间,并调用构造函数。- 代码:
A a = new A(); - 底层动作:当你使用
new关键字时,C# 运行时(CLR)会在内存的堆区中,根据类的结构精确地划出一块空间,用来存放这个对象的所有实例数据(成员变量)。
在 C# 的语境下,我们通常不说“引用类型不能被初始化”,而是这样理解:
- 对于值类型(如
int):初始化是直接把具体的数据(如10)拷贝到栈上的内存空间里。 - 对于引用类型(如
string或class):初始化是把对象的内存地址拷贝到栈上的变量里。
实例化必须带上new关键字(没有的说明C#进行了隐藏),引用数据类型必定实例化:string的编译器封装了实例化 + 初始化。
[!IMPORTANT]
“实例化” = 在堆上造房子(分配内存);“初始化” = 在栈上给门牌号(赋值/存地址)。
int [] myArray = new int []{1,2,3,4}; int[]myArray //声明 底层真相:这一步仅是在内存的“栈(Stack)”上创建了一个名为 myArray 的变量 myArray = new int []//实例化 底层真相:new 关键字触发了实例化。此时,C# 运行时会在内存的“堆(Heap)”上真正开辟一块连续的空间,用来存放n个int 类型的数据。同时,它会将这块内存的地址返回,赋给栈上的 myArray 变量。 {1,2,3,4}//初始化 语法糖 把 1、2、3、4 赋值给刚才在堆上创建好的那 4 个 int 空间,覆盖掉原本的 0。 - 代码:
声明与定义
声明只是提供名称和类型,而定义(Definition)则是在声明的基础上,为标识符分配内存并提供初始值或具体实现。
- 变量示例:
int number;是声明;number = 10;是定义(赋值)。 - 方法示例:
void MyMethod();是声明(只告诉编译器有这个方法);而包含具体代码块{ ... }的则是方法的定义。
| 概念 | 核心动作 | 发生时机 | 本质 |
|---|---|---|---|
| 声明 (Declaration) | 告诉编译器“有这个东西” | 编译时 | 占位,不分配内存 |
| 定义 (Definition) | 真正“创造”这个东西 | 编译时 | 分配内存 / 生成代码 |
| 初始化 (Initialization) | 给变量/对象赋予初始值 | 编译时/运行时 | 赋值,不一定分配内存 |
| 实例化 (Instantiation) | 用类在内存中创建对象 | 运行时 | 在堆上分配内存,创建对象 |
初解: 类(class):现实世界事物的模型
-
类是对现实世界事物抽象的结果(建模)。
- 事物包括“物质”(实体)与“运动”(逻辑)。
- 建模是一个去伪存真,由表及里的过程。
目标:学习类的三大成员,两大状态,类与对象的关系
类的三大成员,两大状态
| 静态状态(Static Members) | 实例状态(Instance Members ) | |
|---|---|---|
| 字段:(Field) | ||
| 常量:(Constant) | ||
| 属性:(property) | ||
| 索引器(Indexer) | ||
| 方法:(Method) | ||
| 事件:(Event) | ||
| 运算符重载(Operator) | ||
| 构造函数(Constructor) | ||
| 终结器/析构函数(Finalizer) | ||
| 嵌套类型(Nested Type) |
using System;
public class Person
{// ================= 1. 数据成员(状态/数据) =================// 字段(Field):私有状态,通常用于内部存储private string _name;
// 常量(Constant):编译时确定的固定值
public const string Species = "Homo Sapiens"; // 属性(Property):公有状态,通过 get/set 访问器控制数据的读写
public int Age { get; set; } // 索引器(Indexer):允许对象像数组一样被索引
private string[] _nicknames = new string[3];
public string this[int index]
{get => _nicknames[index];set => _nicknames[index] = value;
}// ================= 2. 函数成员(行为/操作) =================
// 方法(Method):定义对象可以执行的操作
public void Introduce()
{Console.WriteLine($"我是 {_name},今年 {Age} 岁。");
}// 事件(Event):用于向外部发送通知(观察者模式的基础)
public event Action OnBirthday; // 运算符重载(Operator):自定义预定义运算符的行为
public static Person operator +(Person p1, Person p2)
{return new Person { _name = $"{p1._name} & {p2._name}" };
}// ================= 3. 生命周期与嵌套成员 =================
// 构造函数(Constructor):实例化时调用,负责初始化对象状态
public Person(string name, int age)
{_name = name;Age = age;
}// 终结器/析构函数(Finalizer):GC 回收对象前调用,用于清理非托管资源
~Person()
{Console.WriteLine($"{_name} 正在被垃圾回收器销毁...");
}// 嵌套类型(Nested Type):定义在类内部的类,仅服务于外部类
public class Address
{public string City { get; set; }
}// ================= 4. 两大状态(静态 vs 实例) =================
// 静态成员(Static):属于类本身,所有实例共享,存在静态存储区
public static int TotalPopulation { get; set; } = 0;
}
两大状态维度(静态 vs 实例)
上述所有成员,都可以根据其归属划分为两种截然不同的状态:
- 实例状态(Instance Members)
- 归属:属于具体的“对象(实例)”。
- 内存特征:每次使用
new实例化时,都会在堆(Heap)上分配独立的内存空间。每个对象的实例成员互不影响。 - 访问方式:必须通过具体的对象引用来访问(如
person.Age)。
- 静态状态(Static Members)
- 归属:属于“类本身”,而非任何具体的对象。
- 内存特征:在类加载时分配,存在于静态存储区,全局只有一份副本,被所有实例共享。
- 访问方式:直接通过“类名.成员名”访问(如
Person.TotalPopulation),无需实例化。
类(Class)与对象(Object)的关系
- 对象也叫实例,是类经过”实例化”后得到的内存中的实体
- Formally"instance” is synonymous with“object”-对象和实例是一回事
- "飞机"与”一架飞机”有何区别?天上有(一架)飞机一一必需是实例飞,概念是不能飞的
- 有些类是不能实例化的,比如”数学”(Mathclass),我们不能说”一个数学”
- 依照类,我们可以创建对象,这就是“实例化”
- 现实世界中常称“对象”,程序世界中常称“实例”
- 二者并无太大区别,常常混用,初学者不必迷惑
- 使用new操作符创建类的实例
- 引用变量与实例的关系
- 孩子与气球
- 气球不一定有孩子牵着
- 多个孩子可以使用各自的绳子牵着同一个气球,也可以都通过一根绳子牵着气球
理解:类定义了内存的分配法则(结构蓝图),对象是在堆中“按照”这个法则开辟独立的物理空间完成实例化,并将堆上的“内存地址”赋值给栈中的引用变量。
**1. 类定义了某种内存的分配法则 **
类(Class)在编译后,会生成一份“元数据(Metadata)”,存放在内存的方法区(Metaspace)中。这份元数据就像是一张极其精确的“建筑图纸”,它规定了:
- 这个对象需要多少内存空间(比如一个
int占 4 字节,一个string引用占 8 字节)。 - 字段的排列顺序。
- 包含哪些方法(方法代码只存一份,供所有对象共享)。
所以,类确实定义了内存的分配法则和结构。
2. 对象是在堆中按照类的内存分配法则 **
对象在堆中“遵循/按照”**这个法则,在堆(Heap)上实打实地划出一块连续的物理空间。
- 图纸与房子的区别:就像盖房子,施工队(JVM/C# 运行时)是看着图纸(类元数据)去堆里圈地、盖房子(分配内存)。
- 独立的数据副本:堆里分配出来的这块空间,只存放该对象独有的“实例数据(Instance Data)”。同类型的 100 个对象,会在堆上开辟 100 块独立的内存,但它们都“指向”同一份类元数据。
**3. 并将对应的内存地址“赋值”到声明的栈中 **
- 当你写下
MyClass obj;时,编译器已经在栈上为obj分配了空间(比如 8 字节的指针大小),这属于定义/声明。 - 当执行
obj = new MyClass();时,堆上的房子盖好了,返回了一个堆内存地址(比如0x1A2B)。此时,把这个地址写入栈上obj变量的过程,在底层本质上是一个赋值(Assignment)动作。 - 虽然在宏观语法上我们常说“用 new 来初始化一个对象”,但在严谨的底层逻辑中,这是将堆地址赋给栈变量的过程。
[!IMPORTANT]
- 类加载时:编译器把类的结构蓝图(元数据)放进方法区,不占堆内存。
- 遇到
new时(实例化):运行时系统查阅方法区的蓝图,在堆(Heap)上精确地切出一块内存,把对象的字段数据放进去,并在对象头(Object Header)里留下一个指针,指回方法区的蓝图。- 遇到
=时(赋值):把这块堆内存的起始地址(门牌号),放进栈(Stack)上声明好的引用变量里。
using System.Windows.Forms;namespace ClassAnd
{internal class Program{static void Main(string[] args){Form myForm1;//孩子与气球Form myForm2;Form myForm3;Form myForm4; //孩子不牵气球myForm1 = new Form();//孩子与气球myForm2 = new Form();//$1myForm3 = new Form();new Form();//没有孩子牵气球myForm2 = myForm1; //多个孩子可以使用各自的绳子牵着同一个气球 $1myForm1.Text = "Think and ";myForm2.Text = "Live";myForm1.ShowDialog(); // LivemyForm2.ShowDialog(); // Livebool result1 = myForm1 == myForm2; //在$1中将myForm2中存储的原始内存地址改成myForm1存储的地址。myForm2在$1时创建的内存空间被回收bool result2 = myForm1 == myForm3;Console.WriteLine(result1); //TrueConsole.WriteLine(result2); //False//Console.WriteLine(myForm4);//CS0165:使用了未赋值的局部变量”myForm4”Console.WriteLine("=======================");(new Form()).Text = "匿名对象";//没有真正的实例化,不能正确显示(new Form()).ShowDialog();}}
}
类的成员
1. 数据成员(状态/数据)
数据成员用于存储对象或类的状态信息,是类的“静态骨架”。
- 字段(Fields)
- 定义:在类内部直接声明的变量,是原始的数据存储单元。
- 最佳实践:通常声明为
private(私有),用于内部存储,防止外部随意篡改。例如:private string _name;。
- 常量(Constants)
- 定义:在编译时确定且不可更改的固定值。
- 特点:属于类的静态特征,所有实例共享。例如:
public const string Species = "Homo Sapiens";。
- 属性(Properties)
- 定义:字段的扩展,提供对数据的受控访问(包含
get和set访问器)。 - 核心价值:充当“守门人”,可以在赋值或读取时加入验证逻辑(如防止年龄为负数),完美体现了面向对象的封装特性。例如:
public int Age { get; set; }。
- 定义:字段的扩展,提供对数据的受控访问(包含
- 索引器(Indexers)
- 定义:允许对象像数组一样通过下标(如
[])来访问内部集合数据。 - 用途:为自定义集合类提供便捷的访问语法。例如:
public string this[int index] { get; set; }。
- 定义:允许对象像数组一样通过下标(如
2. 函数成员(行为/操作)
函数成员定义了类可以执行的动作、响应机制以及自定义规则。
- 方法(Methods)
- 定义:包含具体执行逻辑的代码块,用于定义对象的行为。
- 特点:可接收参数并返回值,是类对外暴露的核心功能。例如:
public void Introduce() { ... }。
- 事件(Events)
- 定义:基于委托的通知机制,用于在特定条件满足时向外部发送信号。
- 用途:实现“观察者模式”,解耦对象间的交互(例如按钮点击、状态改变通知)。例如:
public event Action OnBirthday;。
- 运算符重载(Operators)
- 定义:为自定义类型赋予预定义运算符(如
+,-,==)的新含义。 - 特点:必须声明为
public static方法,使自定义对象能像基本数据类型一样参与运算。例如:public static Person operator +(Person p1, Person p2)。
- 定义:为自定义类型赋予预定义运算符(如
3. 生命周期与嵌套成员
这类成员负责管理对象的诞生、消亡以及内部结构的组织。
- 构造函数(Constructors)
- 定义:与类同名、无返回类型的特殊方法,在对象被
new创建时自动调用。 - 职责:负责初始化对象的初始状态(给字段和属性赋初值)。例如:
public Person(string name, int age) { ... }。
- 定义:与类同名、无返回类型的特殊方法,在对象被
- 终结器/析构函数(Finalizers)
- 定义:以
~开头的方法,在对象即将被垃圾回收器(GC)销毁前由运行时自动调用。 - 职责:用于清理非托管资源(如文件句柄、数据库连接)。在现代 C# 开发中极少手动编写,通常由 GC 自动管理。例如:
~Person() { ... }。
- 定义:以
- 嵌套类型(Nested Types)
- 定义:声明在另一个类内部的类、结构体或枚举。
- 用途:用于封装仅服务于外部类的辅助逻辑,隐藏内部实现细节,避免污染全局命名空间。例如:
public class Address { ... }。
[!TIP]
模型类或对象重在属性,如EntityFramework
工具类或对象重在方法,如Math,Console
通知类或对象重在事件,如各种Timer
