我在学习 c# 的构造函数,你看我的表述是否正确,首先我们创建一个类,这个类里面会有变量,在这个类函数外面我们可以调用,给这个类函数赋值,这个时候我们对其中一个变量的值去修改也是可以成功的,这个时候我们就要对这两个变量进行变量的构造,这个时候当我们构造数据了以后,就不能直接通过直接对变量赋值去修改这个数据了
你的表述梳理 & 纠错
先把你原话翻译成人话:
创建一个类,类里有变量;在类外面可以调用这个类、给类的变量赋值,也能修改变量值。用构造函数之后,就不能在外部直接赋值修改变量了。
结论:后半句不完全正确,有概念混淆,我帮你拆开讲清楚。
1、没有构造函数的时候
C# 如果你不写任何构造函数 ,编译器会自动给你一个无参默认构造函数。
class Person
{
// 公开字段
public string name;
public int age;
}
// 外部使用
Person p = new Person(); // 调用编译器默认给的无参构造
p.name = "张三"; // ✅ 外部直接赋值修改
p.age = 20;
p.age = 25; // ✅ 外部再次修改,完全可以
关键点:之所以外面能直接改,是因为字段标记了
public,不是因为有没有构造函数。
2、写了构造函数会发生什么
构造函数作用:创建对象的时候,给成员做初始化赋值 ,它本身不会禁止外部修改变量。
class Person
{
public string name;
public int age;
// 手写有参构造函数
public Person(string n, int a)
{
name = n;
age = a;
}
}
// 创建对象时必须走构造函数初始化
Person p = new Person("张三",20);
p.age = 99; // ✅!依然可以外部直接修改!
👉 重点:就算写了构造函数,只要字段是 public,外面照样改。构造函数只负责「对象出生那一刻初始化」,不锁死变量。
3、什么时候外部不能直接修改?
不是构造函数干的,是访问修饰符 + 属性(get/set):
-
把字段改成
private,外部就不能直接访问; -
或者属性只写
get,去掉set,变成只读。class Person
{
// private 私有字段,类外面看不见
private string name;
private int age;public Person(string n, int a) { name = n; age = a; } // 只读属性:只能读,外部不能赋值 public string Name => name; public int Age => age;}
Person p = new Person("张三",20);
Console.WriteLine(p.Age);
// p.Age = 100; // ❌编译报错,不能修改
4、修正你的完整正确表述
我们创建一个类,类中定义成员变量。如果成员是公开的,在类外部实例化对象之后,可以读取、直接给这些变量赋值、修改值。
构造函数的作用:在 new 创建对象的瞬间,给成员做初始化。构造函数本身不会阻止外部修改变量。
如果想要外部不能直接修改数据:需要把成员私有化,配合只读属性限制,而不是靠构造函数。构造只是初始化,不等于锁死数据。
容易踩坑的误区总结
- ❌错误:写了构造函数,外部就不能改字段 ✅正确:构造函数 = 对象初始化工具;能不能改看访问权限(public/private)和 set 访问器。
- ❌错误:不写构造函数,类就没有构造函数 ✅正确:不手写,C# 编译器自动生成无参构造;一旦手写任意构造,编译器不再提供默认无参构造。
如果你愿意,我可以写两段极简对比代码,一段可修改,一段初始化后只读,方便你跑一遍加深理解。
那我初始化和不初始化有什么区别呢
C#:初始化 vs 不初始化
先分清两个概念:
- 初始化 :对象刚诞生(
new的那一刻)就给变量一个确定的值 - 不初始化:创建完对象,字段用默认值,之后你再手动赋值
构造函数就是做初始化最常用的手段。
1、不初始化(靠默认值,之后手动赋值)
C# 所有字段都有默认值:
-
数值类型
int→0 -
string→null -
引用类型 →
nullclass Student
{
public string name;
public int age;
}// new 出来,调用默认无参构造,没有做初始化
Student s = new Student();Console.WriteLine(s.name); // null
Console.WriteLine(s.age); // 0// 创建完对象以后,再手动赋值
s.name = "小明";
s.age = 18;
特点:
- 对象刚创建出来,字段是系统给的默认值,可能是
null、0; - 允许创建出一个 "半成品对象",你后面再补数据;
- 风险:忘记手动赋值,就拿着
null/0去用,容易出 bug。
2、用构造函数初始化(new 的同时就把数据给好)
class Student
{
public string name;
public int age;
// 构造函数:new 的瞬间完成初始化
public Student(string n, int a)
{
name = n;
age = a;
}
}
// new 的时候必须传入参数,对象一诞生就有有效数据
Student s = new Student("小明", 18);
Console.WriteLine(s.name); // 小明
Console.WriteLine(s.age); // 18
特点:
-
✅对象一被创建出来,就具备有效业务数据,不会出现
null、0这种无效半成品; -
⚠️你不能写出
Student s = new Student();,手写了有参构造,编译器不再赠送无参构造; -
⚠️仅仅只是初始化! 只要字段是
public,创建完依旧可以改:s.age = 99; // 依旧可以修改,初始化只是出生那一刻赋值,不是永久锁死
核心对比表格
表格
| 不初始化(默认构造,后续手动赋值) | 构造函数初始化 | |
|---|---|---|
| 对象刚 new 出来的值 | 系统默认值:0、null | 你传入的业务有效值 |
| 创建对象 | new Student() |
new Student("小明",18) |
| 是否能外部修改 | public 字段可以改 | public 字段照样可以改 |
| 风险 | 容易忘记赋值,拿到 null,报错 | 强制创建时提供数据,避免半成品 |
| 作用时机 | new 之后,由程序员手动赋值 | new 对象这一瞬间完成赋值 |
通俗大白话比喻
- 不初始化:生个孩子,先让他出生,名字、年龄以后慢慢填。有可能忘记填,孩子没名字。
- 构造函数初始化 :生孩子的时候,出生那一刻就把名字年龄全部定好。但是出生之后依旧可以改名改年龄(public 字段)。
关键点再次强调: 初始化 ≠ 只读、不可修改。初始化只管控 "出生那一刻";能不能后续修改,看访问修饰符和 set。
拓展:字段直接初始化(不写构造函数也能初始化)
除了构造函数,还可以直接给字段写初始值,这也是初始化:
class Student
{
public string name = "未知"; // 字段初始化
public int age = 0;
}
new 对象的时候,还没跑构造函数,就先把这两个值填进去。
什么时候选哪种?
- 有些对象必须要有数据才能正常工作,不能允许半成品 → 用有参构造强制初始化。
- 有些对象可以先创建,后面慢慢填充数据 → 使用无参构造,后续手动赋值。
如果你想,我可以写一段小例子演示:不初始化拿到 null 引发报错,对比初始化之后规避这个问题。
就是加了构造函数,到创建实例,就必须对这个实例赋值的意思吗
不完全是,只有手写了【有参构造函数】才会强制你 new 的时候传参数;如果你写的是无参构造,依然不用赋值。
情况 1:写了有参构造函数
public class Person
{
public string Name;
public int Age;
// 有参构造
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
这时:
Person p = new Person(); // ❌报错!不能这样写
Person p = new Person("李四",22); // ✅必须传入参数,创建实例的时候就要给值
这就是你理解的:创建实例时必须赋值。 原因:一旦手写任意构造函数,C# 就不再自动生成默认无参构造。
⚠️但是!只是 new 这一步必须给初始值,实例创建完成之后,p.Name = "王五" 照样可以修改,构造函数只管出生瞬间。
情况 2:写的是无参构造函数
public class Person
{
public string Name;
public int Age;
// 无参构造,没有参数
public Person()
{
Name = "默认名字";
Age = 10;
}
}
Person p = new Person(); // ✅不用传参数,直接创建实例
这里也有构造函数,但创建实例不需要外部传值,构造函数内部自己给了初始值。
情况 3:完全不写任何构造函数
编译器偷偷给你生成一个看不见的无参默认构造函数
public class Person
{
public string Name;
public int Age;
}
Person p = new Person(); // ✅可以直接new,字段拿到默认null、0
总结一句话
- ✅只有【有参构造函数】,才强制你在
new创建实例的时候传入数值; - ✅无参构造(手写 / 编译器默认),new 的时候不需要传值;
- ❗强制的只是创建时的初始化 ,不等于对象以后不能改;能不能后续修改看字段是不是
public。
补充小细节
如果你既想要:new 的时候可以不传参,又支持 new 的时候传参,可以重载构造函数,写两个构造:
public class Person
{
public string Name;
public int Age;
//无参
public Person()
{
}
//有参
public Person(string name,int age)
{
Name = name;
Age = age;
}
}
//两种都能用
Person p1 = new Person();
Person p2 = new Person("张三",18);
那无参构造和完全不写,其实是一样的啊
表面看效果很像,但底层不是一回事,我把区别讲透。
1、完全不写任何构造函数
class Person
{
public string Name;
public int Age;
}
编译器自动帮你生成一个隐藏的无参构造函数。 等价于编译器偷偷插入这段代码(你源码看不见):
public Person()
{
// 里面什么代码都没有
}
使用:
Person p = new Person();
对象字段得到系统默认值:Name=null,Age=0。
2、自己手写一个无参构造函数
class Person
{
public string Name;
public int Age;
// 自己写的无参构造
public Person()
{
Name = "无名氏"; // 这里可以写自己的初始化逻辑!
Age = 1;
}
}
Person p = new Person();
对象出来:Name="无名氏",Age=1。
核心区别
① 编译器自动生成的无参构造(不写构造函数)
- 函数体里面是空的,什么都不做;
- 只会让字段拿到 C# 系统默认值(null、0、false);
- 只要你手写任意一个构造(不管有参无参),这个自动生成的就直接消失!
② 手写无参构造
- 你可以在大括号
{ }里面写代码,做自定义初始化; 比如给字段设置默认业务值、打印日志、做计算; - 它是实实在在写在类里的构造,不会自动消失。
👉 只有当手写无参构造里面完全空着什么代码都不写的时候,运行效果才和 "完全不写构造函数" 看起来几乎一样。
//手写空的无参构造
public Person()
{
//啥也不干
}
此时运行效果 ≈ 不写构造函数。但身份上:一个是你手写,一个是编译器偷偷生成。
一个非常关键的坑(考试、写代码高频踩坑)
看这个例子:写了一个有参构造,不再写无参
class Person
{
public string Name;
public int Age;
//只写有参构造
public Person(string n,int a)
{
Name = n;
Age = a;
}
}
❌ Person p = new Person(); 直接编译报错!
原因:只要你手写了任意构造函数(有参),编译器就不再给你生成那个默认无参构造。 哪怕你想要无参,必须自己手动敲出来。
如果你完全不写任何构造函数,编译器才会送你无参版本。
简短总结
- 不写构造函数:编译器赠送空无参构造,内部啥逻辑都没有。
- 手写空无参构造:代码是你写的,啥逻辑也没有,运行表现同上。
- 手写无参构造里面写代码:new 的时候就执行你写的初始化逻辑,这就和 "不写构造函数" 不一样。
- 只要手写任意构造函数,编译器赠送的默认无参构造就没了。
记忆小口诀
不写构造,编译器送你空无参; 只要手写任意构造,赠送福利直接取消。
要不要我给你出 2 行小思考题,检验下你是不是吃透这个点?
今天 19:28
就是无参里面可有个值,而不写就是里面有个默认值
对,你理解方向对,我帮你把这句话精准修正下:
手写无参构造函数:你可以在函数体内自己指定初始值 (想设什么就设什么)。 完全不写构造函数:编译器生成空构造,不会帮你设置业务值,只给 C# 系统默认值(int=0,string=null)。
例子 1:手写无参,自己赋值
public Person()
{
Name = "匿名"; // ✅我自己规定的初始值
Age = 18;
}
new 出来直接:Name="匿名",Age=18。
例子 2:完全不写构造函数
编译器生成空构造,函数里面一行代码都没有。
//编译器偷偷生成,内部空
public Person(){}
new 出来:Name=null,Age=0。这是系统类型默认值,不是你写的。
容易混淆的点分清
- 手写无参构造可以是空的
public Person(){},里面什么都不写。 此时效果就和 "不写构造函数"表现一模一样,出来也是 null、0。
区别只是:一个是你手写,一个编译器生成;一旦你写了有参构造,编译器版本会消失,手写的不会消失。
- "不写构造函数" 本身不会主动设置任何业务值,仅仅是 C# 语法规则:所有类字段一定会被赋予系统默认值。
大白话总结
- ✍️手写无参构造:出生的时候,由你来决定给什么初始值。
- 🤖不写构造函数:出生的时候,编译器什么都不干,C# 系统自动给类型默认值(0/null)。
补充:字段直接写
public string Name="匿名";这种,属于字段初始化,比构造函数更早执行,又是另外一套机制。