C#类和对象,值类型与引用类型
类与对象
模块1:类与对象的核心定义(重中之重)
1. 类(Class)
// 定义:类是对象的模板/蓝图,是具有相同属性(特征)和方法(行为)的事物的集合,是抽象的概念。
// 通俗理解:类就像“人类”这个统称,它规定了人类都有“年龄、性别”这些特征,有“吃饭、睡觉”这些行为,但不特指某一个人。
// 举例:类 = 人类、汽车类、手机类(都是抽象的,没有具体实体)
2. 对象(Object)
// 定义:对象是类的实例化产物,是具体存在的个体,拥有类中定义的所有属性和方法。
// 通俗理解:对象是“类”这个模板造出来的具体东西,每个对象都是独立的。
// 举例:对象 = 张三(人类的实例)、李四(人类的实例)、我的手机(手机类的实例)
3. 类与对象的核心关系
// 类 → 模板,对象 → 模板造出来的具体事物;一个类可以创建无数个对象,每个对象独立存在。
// 类比:类 = 房子设计图,对象 = 按照设计图盖出来的每一套房子(每套房子独立,可装修不同风格)
模块2:类与对象的核心作用
1. 类的作用
// ① 定义属性(事物的特征,如年龄、姓名)、字段(类内部存储数据的变量)、方法(事物的行为,如吃饭、学习);
// ② 作为“模板”,供我们创建具体的对象,统一规范所有对象的属性和方法,避免重复定义。
2. 对象的作用
// ① 给类中定义的属性、字段赋值(每个对象的属性值可以不同,比如张三10岁,李四20岁);
// ② 调用类中定义的方法,实现具体的功能(比如对象调用“吃饭”方法)。
模块3:类的定义规范(代码实操重点)
// 语法格式: 修饰符 class 类名 { 类的内容(字段、属性、方法) }
修饰符 class 类名
{
// 类的内部内容(字段、属性、方法)
}
1. 类的基本语法
2. 类的修饰符(重点,必记)
// 修饰符作用:控制类的访问范围,决定这个类能在哪些地方被使用
-
public:公共的、公开的,最常用,可在整个项目的任何地方访问(比如Student类用public,才能在Main方法中使用);
-
private:私有的,只能在当前类内部访问,不能用于定义类(定义类用private会导致无法创建对象);
-
internal:内部的,只能在当前项目中访问(默认修饰符,不写修饰符时,默认是internal);
-
protected:受保护的,只能在当前类和它的子类中访问(用于继承场景,暂时用不到)。
3. 类名的命名规范(必守)
// 必须遵循大驼峰命名法:首字母大写,后续每个单词的首字母也大写,不能用拼音、数字开头。
// 正确示例:Student(学生类)、Person(人类)、Book(书籍类);错误示例:student、stu、学生类。
模块4:类的内部组成(字段 vs 属性,重点区分)
类的内部主要包含2类核心内容:字段(私有)和属性(公有),二者配合使用,实现“数据隐藏+对外访问”。
1. 字段(Field)
// 语法: 访问修饰符 数据类型 字段名;
private int _age; // 这就是字段
-
访问修饰符:一般用private(私有),只能在当前类内部使用,对外隐藏(不让外部直接修改);
-
命名规范:前面加下划线“_”,后面单词首字母小写(小驼峰),比如 _age、_name;
-
作用:在类内部存储数据,是属性的“底层存储容器”,属性本质是访问这个字段。
2. 属性(Property)
// 语法: 访问修饰符 数据类型 属性名 { get; set; }
public int Age { get; set; } // 这就是属性
public string Name { get; set; }
-
访问修饰符:一般用public(公有),供外部(比如Main方法、其他类)访问和修改;
-
命名规范:首字母大写(大驼峰),和字段对应,比如字段 _age → 属性 Age;
-
核心作用:对外提供访问/修改字段的接口,隐藏内部字段的细节(比如可以在get/set中加判断,限制输入);
-
get/set访问器:get(获取属性值)、set(设置属性值),二者都有才能读写,缺一不可(比如只写get就是只读属性)。
3. 字段与属性的关系(必懂)
// 外部(比如Main方法)不能直接访问字段(private修饰),只能通过public属性访问;
// 比如:不能直接操作 _age,只能通过 Age 属性(zhangsan.Age = 10)来修改字段的值。
模块5:对象的创建与使用(实操重点,每一步都要会)
// 核心语法: 类名 对象名 = new 类名(); (new关键字是关键,用于创建对象,开辟内存空间)
方式1:基本创建 + 逐一对属性赋值(最基础,最常用)
// 步骤1:创建对象(new关键字不能少)
Student zhangsan = new Student();
// 步骤2:给对象的属性赋值(语法:对象名.属性名 = 值)
zhangsan.Name = "张三"; // 给Name属性赋值
zhangsan.Age = 10; // 给Age属性赋值
zhangsan.Sex = "男"; // 给Sex属性赋值
// 步骤3:访问对象的属性(语法:对象名.属性名)
Console.WriteLine(zhangsan.Age); // 输出:10(获取张三的年龄)
// 细节:每次new一个对象,都是一个独立的个体,修改一个对象的属性,不影响其他对象。
方式2:创建对象时,直接赋值(对象初始化器,简洁高效)
// 语法: 类名 对象名 = new 类名() { 属性1=值1, 属性2=值2, ... };
Student lisi = new Student() { Name = "李四", Age = 20, Sex = "女" };
// 直接访问属性
Console.WriteLine(lisi.Name); // 输出:李四
// 细节:大括号内的属性顺序可以任意,只要属性名和类中定义的一致即可,末尾不加逗号。
方式3:循环创建多个对象(批量创建,重点)
// 步骤1:定义一个字符串,用于拼接所有对象的信息(方便最后打印)
string s = "";
// 步骤2:循环n次,创建n个对象(这里循环10次,创建10个Student对象)
for (int i = 0; i < 10; i++)
{
// 每次循环,new一个新的Student对象(每个对象独立)
Student s1 = new Student();
// 给当前对象的属性动态赋值(根据循环变量i变化)
s1.Name = "吴亦凡" + i + "号"; // 姓名:吴亦凡0号、吴亦凡1号...
s1.Age = i * 10; // 年龄:0、10、20...90
s1.Sex = i % 2 == 0 ? "男" : "女"; // 偶数=男,奇数=女
// 拼接当前对象的信息(\n表示换行,方便后续打印)
s += s1.Name + ":" + s1.Age + ":" + s1.Sex + "\n";
}
// 步骤3:打印所有对象的信息
Console.WriteLine(s);
// 细节:循环中每次new Student(),都会创建一个全新的对象,属性值互不影响;如果不new,所有对象会指向同一个内存,属性值会被覆盖。
模块6:完整可运行代码(整合所有知识点)
using System;
// 1. 定义Student类(public修饰,供Main方法访问)
public class Student
{
// 字段(私有,类内部使用)
private int _age;
// 属性(公有,外部访问)
public int Age { get; set; }
public string Name { get; set; }
public string Sex { get; set; }
}
class Program
{
static void Main(string[] args)
{
// 方式1:基本创建+逐一赋值
Student zhangsan = new Student();
zhangsan.Name = "张三";
zhangsan.Age = 10;
zhangsan.Sex = "男";
Console.WriteLine("方式1:" + zhangsan.Name + ":" + zhangsan.Age);
// 方式2:对象初始化器赋值
Student lisi = new Student() { Name = "李四", Age = 20, Sex = "女" };
Console.WriteLine("方式2:" + lisi.Name + ":" + lisi.Age);
// 方式3:循环创建多个对象
string s = "";
for (int i = 0; i < 10; i++)
{
Student s1 = new Student();
s1.Name = "吴亦凡" + i + "号";
s1.Age = i * 10;
s1.Sex = i % 2 == 0 ? "男" : "女";
s += s1.Name + ":" + s1.Age + ":" + s1.Sex + "\n";
}
Console.WriteLine("方式3:\n" + s);
}
}
模块7:常见易错点(重点提醒)
-
1. 忘记写new关键字:创建对象必须写 new 类名(),否则会报错(比如 Student zhangsan; 只是声明,没有创建对象);
-
2. 类名、属性名大小写错误:C#区分大小写,比如 zhangsan.name 会报错,必须写 zhangsan.Name;
-
3. 字段和属性混淆:外部不能直接访问private字段(比如 zhangsan._age 会报错),只能访问public属性;
-
4. 循环创建对象时,忘记在循环内new:如果把 Student s1 = new Student(); 写在循环外,所有对象会共用一个内存,属性值会被覆盖。
值类型与引用类型
模块1:值类型(Value Type)—— 存储值本身,赋值即拷贝
1.1 值类型核心定义(原注释逐句拆解)
原注释原文://值类型:变量存储值本身,存在栈空间中,在赋值过程中,把值本身复制拷贝一份,把拷贝一份给新变量了,在去修改其中一个时候 ,另外一个不会受影响
核心定义(逐词拆解,必懂):
-
变量存储值本身:值类型的变量,直接存储“具体的数据”,而不是数据的地址。比如int a = 10; 变量a直接存储“10”这个数值,不是存储10的内存地址。
-
存在栈空间中:值类型的变量和它存储的值,都存储在栈空间(栈空间是内存中一块连续的区域,读写速度快,由系统自动分配和释放,生命周期短)。
-
赋值过程=拷贝值:当把一个值类型变量赋值给另一个值类型变量时,会复制一份原变量的具体值,把这份拷贝的值赋值给新变量,原变量和新变量是完全独立的两个变量,互不关联。
-
修改一个,另一个不受影响:因为两个变量存储的是“各自的副本值”,不是同一个值,所以修改其中一个变量的值,不会影响另一个变量的原值。
1.2 常见值类型(原注释完整细化)
原注释原文://值类型:int float double bool char long uint sbyte ushort ulong 枚举 结构体类型
值类型分为两大类,每类重点说明,贴合代码使用场景:
-
① 基本值类型(最常用,代码中高频出现):
-
int:整数类型(比如代码中的int a = 10; 存储整数,范围:-2147483648 ~ 2147483647);
-
float:单精度浮点型(存储小数,比如3.14f,后缀f不能省略,精度较低);
-
double:双精度浮点型(存储小数,比如3.14,精度比float高,默认小数类型);
-
bool:布尔类型(只有两个值:true(真)、false(假),用于判断);
-
char:字符类型(存储单个字符,用单引号包裹,比如char c = 'A';);
-
long:长整数类型(存储范围比int大,比如long num = 10000000000L; 后缀L可省略);
-
uint、sbyte、ushort、ulong:都是整数类型的变体(uint是无符号int,只能存正数;sbyte是字节型,范围更小),日常使用较少,了解即可。
-
-
② 自定义值类型:
-
枚举(enum):自定义的枚举类型,比如enum Gender { 男, 女 }; 用于表示固定的一组选项;
-
结构体(struct):自定义的结构体类型,比如struct Point { public int X; public int Y; }; 用于封装少量相关数据,本质是值类型。
-
1.3 原代码实操演示(逐行拆解,含运行原理)
// 代码原文,逐行拆解
int a = 10; // 1. 定义int类型(值类型)变量a,赋值10
int b = a; // 2. 赋值过程:拷贝a的值(10),赋值给b,b是a的副本
a = 30; // 3. 修改a的值为30,只影响a本身,不影响b
Console.WriteLine(b); // 4. 打印b的值,结果是10(b还是拷贝的原a的值)
逐行拆解(含内存原理,关键重点):
-
第1行:
int a = 10;-
系统在栈空间中,开辟一块内存,命名为变量a,直接存储“10”这个具体的值;
-
此时栈空间:a → 10(a直接指向值,没有内存地址的概念)。
-
-
第2行:
int b = a;(核心赋值过程)-
系统在栈空间中,再开辟一块新的内存,命名为变量b;
-
把变量a存储的具体值(10),完整拷贝一份,赋值给变量b;
-
此时栈空间:a → 10,b → 10(a和b是两块独立的内存,存储相同的副本值,互不关联)。
-
-
第3行:
a = 30;-
只修改变量a对应的栈内存中的值,把10改成30;
-
变量b对应的栈内存,依然存储的是之前拷贝的10,没有任何变化;
-
此时栈空间:a → 30,b → 10。
-
-
第4行:
Console.WriteLine(b);-
打印变量b存储的值,因为b没有被修改,依然是10,所以最终输出结果:10。
-
关键总结:值类型赋值 = 拷贝值,两个变量独立,修改互不影响(核心考点)。
模块2:引用类型(Reference Type)—— 存储地址,赋值即拷贝地址
2.1 引用类型核心定义(原注释逐句拆解)
原注释原文://引用类型:变量存储的对象的引用,或者说变量保存是内存地址,存储在堆空间, 在赋值过程中把内存地址复制一份,新变量和之前变量内存地址一样,这俩个变量是指向同一个内存地址,再去修改其中一个另一个会被修改。
核心定义(逐词拆解,必懂):
-
变量存储的是对象的引用/内存地址:引用类型的变量,不直接存储具体的值,而是存储“对象在内存中的地址”(相当于“门牌号”),通过这个地址,才能找到真正的对象。
-
对象存在堆空间中:引用类型的对象本身(比如new Book()创建的对象)存储在堆空间(堆空间是内存中一块不连续的区域,读写速度较慢,由垃圾回收机制自动释放,生命周期长);而引用类型的变量(比如b1、b2),依然存储在栈空间中,存储的是堆空间中对象的地址。
-
赋值过程=拷贝地址:当把一个引用类型变量赋值给另一个引用类型变量时,会复制一份原变量存储的内存地址,把这份地址拷贝给新变量;此时,原变量和新变量存储的地址完全一样,都指向堆空间中的同一个对象。
-
修改一个,另一个会被修改:因为两个变量指向同一个对象(同一个堆内存地址),无论通过哪个变量修改对象的属性,都是在修改同一个对象的内容,所以另一个变量访问该对象时,会看到修改后的值。
2.2 常见引用类型(原注释完整细化)
原注释原文://引用类型:string(特殊的引用类型)、class 、数组、集合 Random等
重点说明每个类型,贴合代码使用场景,尤其是string的特殊性:
-
① string(字符串类型):特殊的引用类型
-
特殊原因:string虽然是引用类型,但它具有“不可变性”——当你修改string变量的值时,系统不会修改原对象,而是重新创建一个新的string对象,赋值新的值,原对象不变;
-
举例:string s = "abc"; s = "def"; 此时不是修改原“abc”对象,而是创建一个新的“def”对象,s重新指向新对象的地址,原“abc”对象会被垃圾回收。
-
-
② class(类):最常用的引用类型,比如原代码中的Book类,通过new Book()创建的对象,就是引用类型(核心重点,贴合原代码);
-
③ 数组:比如int[] arr = new int[3]; 数组对象存储在堆空间,arr变量存储数组的地址;
-
④ 集合:比如List<int> list = new List<int>(); 集合对象存储在堆空间,list变量存储集合的地址;
-
⑤ Random:随机数类,比如Random r = new Random(); r是引用类型变量,存储Random对象的地址。
2.3 原代码实操演示(逐行拆解,含内存原理)
// 先定义Book类(class,引用类型)
public class Book
{
public string Name { get; set; } // 书籍名称属性
}
// 主方法中的代码,逐行拆解
// 创建对象(引用类型)
Book b1 = new Book(); // 1. 创建Book对象,b1存储对象的地址
b1.Name = "明朝那些事"; // 2. 通过b1的地址,修改对象的Name属性
Book b2 = b1; // 3. 赋值过程:拷贝b1存储的地址,b2和b1指向同一个对象
b2.Name = "清朝"; // 4. 通过b2的地址,修改同一个对象的Name属性
Console.WriteLine(b1.Name); // 5. 打印b1指向的对象的Name,结果是“清朝”
逐行拆解(含内存原理,关键重点,贴合原代码):
-
第一步:定义Book类
-
public class Book { public string Name { get; set; } }:Book是类,属于引用类型,创建的Book对象会存储在堆空间;
-
Name是Book类的公共属性,用于存储书籍名称,通过对象的属性可以修改对象的内容。
-
-
第1行:
Book b1 = new Book();(核心:创建引用类型对象)-
new Book():创建一个Book对象,系统在堆空间中开辟一块内存,存储这个Book对象(此时对象的Name属性默认是null,空值);
-
Book b1:定义引用类型变量b1,存储在栈空间中;
-
赋值操作:把堆空间中Book对象的“内存地址”(比如地址:0x123456),赋值给栈空间中的b1;
-
此时内存状态:栈空间b1 → 0x123456(地址),堆空间0x123456 → Book对象(Name=null)。
-
-
第2行:
b1.Name = "明朝那些事";-
b1存储的是Book对象的地址(0x123456),系统通过这个地址,找到堆空间中的Book对象;
-
修改该对象的Name属性,把“明朝那些事”赋值给Name;
-
此时内存状态:堆空间0x123456 → Book对象(Name="明朝那些事"),b1依然指向这个地址。
-
-
第3行:
Book b2 = b1;(核心:引用类型赋值)-
定义引用类型变量b2,存储在栈空间中;
-
赋值操作:把b1存储的内存地址(0x123456),完整拷贝一份,赋值给b2;
-
此时内存状态:栈空间b1 → 0x123456,栈空间b2 → 0x123456;b1和b2存储同一个地址,指向堆空间中的同一个Book对象。
-
-
第4行:
b2.Name = "清朝";-
b2存储的地址是0x123456,系统通过这个地址,找到堆空间中的同一个Book对象;
-
修改该对象的Name属性,把“清朝”赋值给Name(覆盖之前的“明朝那些事”);
-
此时内存状态:堆空间0x123456 → Book对象(Name="清朝"),b1和b2依然指向这个地址。
-
-
第5行:
Console.WriteLine(b1.Name);-
b1存储的地址是0x123456,系统通过这个地址,找到堆空间中的Book对象;
-
获取该对象的Name属性值(此时已经被b2修改为“清朝”),所以最终输出结果:清朝。
-
关键总结:引用类型赋值 = 拷贝地址,两个变量指向同一个对象,修改一个,另一个会跟着变(核心考点,贴合原代码)。
模块3:值类型与引用类型核心区别(对比总结,考试高频)
|
对比维度 |
值类型 |
引用类型 |
|---|---|---|
|
存储内容 |
直接存储具体的值(比如10、true) |
存储对象的内存地址(不是值本身) |
|
存储位置 |
变量和值都在栈空间 |
变量在栈空间,对象在堆空间 |
|
赋值规则 |
赋值 = 拷贝具体的值,生成副本 |
赋值 = 拷贝内存地址,指向同一个对象 |
|
修改影响 |
修改一个变量,另一个不受影响(副本独立) |
修改一个变量的对象,另一个变量的对象也会变(指向同一个对象) |
|
默认值 |
数值类型默认0(int默认0),bool默认false,char默认'\0' |
默认值为null(表示不指向任何对象) |
|
常见类型 |
int、float、double、bool、char、枚举、结构体 |
class(Book类)、string、数组、集合、Random |
|
原代码示例 |
int a=10; int b=a; a=30; b还是10 |
Book b1=new Book(); Book b2=b1; b2.Name="清朝"; b1.Name也为清朝 |
模块4:常见易错点汇总(考试/作业高频,贴合原代码)
-
1. 混淆值类型和引用类型的存储位置:误以为引用类型的变量也存在堆空间,实际引用类型的变量在栈空间,对象在堆空间;值类型的变量和值都在栈空间。
-
2. 误以为引用类型赋值是拷贝对象:引用类型赋值只拷贝“地址”,不拷贝对象本身,两个变量依然指向同一个对象,修改属性会相互影响(原代码核心易错点)。
-
3. 忽略string的特殊性:string是引用类型,但具有不可变性,修改string变量的值,会创建新对象,不会影响原对象(和原代码中Book类的引用类型不同)。
-
4. 引用类型变量赋值为null:比如Book b3 = null; 表示b3不指向任何堆空间的对象,此时访问b3.Name会报“空引用异常”(不能访问null的属性)。
-
5. 值类型赋值后修改原变量:误以为修改原变量会影响副本,实际值类型赋值后,两个变量是独立副本,修改互不影响(原代码中a=30,b依然是10)。
-
6. 混淆类(class)和结构体(struct)的类型:class是引用类型,struct是值类型,不要把结构体当成引用类型使用。
模块5:完整可运行代码(可直接复制运行,对照学习)
using System; // 必须添加,否则Console.WriteLine会报错
// 定义Book类(class,引用类型)
public class Book
{
public string Name { get; set; } // 书籍名称属性
}
class Program
{
static void Main(string[] args)
{
// 一、值类型演示(原代码)
Console.WriteLine("=== 值类型演示 ===");
int a = 10;
int b = a; // 拷贝a的值,b是副本
a = 30; // 修改a,不影响b
Console.WriteLine("b的值:" + b); // 输出:10
// 二、引用类型演示(原代码)
Console.WriteLine("\n=== 引用类型演示 ===");
Book b1 = new Book();
b1.Name = "明朝那些事";
Book b2 = b1; // 拷贝b1的地址,b1和b2指向同一个对象
b2.Name = "清朝"; // 修改b2指向的对象,b1指向的对象也会变
Console.WriteLine("b1.Name的值:" + b1.Name); // 输出:清朝
// 三、string特殊引用类型演示(补充,帮助理解)
Console.WriteLine("\n=== string特殊引用类型演示 ===");
string s1 = "abc";
string s2 = s1; // 拷贝s1的地址,指向同一个对象
s2 = "def"; // string不可变,创建新对象,s2指向新地址
Console.WriteLine("s1的值:" + s1); // 输出:abc(原对象未被修改)
}
}
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐
所有评论(0)