Skip to main content

任务五 构造函数

一、构造函数的由来

我们先从一个熟悉的场景说起。假设我们定义了一个"学生类",包含姓名、年龄这些属性:

class Student
{
public string Name { get; set; }
public int Age { get; set; }
public string StudentId { get; set; }
}

当我们创建学生对象时,通常会这样写:

Student student = new Student();
student.Name = "张三";
student.Age = 18;
student.StudentId = "2023001";

这段代码没问题,但如果我们每次创建学生对象都要重复这几步赋值操作,不仅麻烦,还可能因为忘记给某个重要属性赋值而导致错误。比如,如果忘记设置学号,这个学生对象就是不完整的。

有没有办法在创建对象的同时,就完成这些必要的初始化工作呢?当然有,这就是构造函数的作用!

二、构造函数是什么

在C#中,构造函数是一种特殊的方法,专门用于对象的初始化(如设置初始值、分配资源等)。它的名字和类名完全相同,而且没有返回值。当我们用new关键字创建对象时,构造函数会自动执行,帮我们完成对象的初始化工作。

👉 定义:构造函数是一种与类同名的特殊方法,在创建对象时自动执行,用于初始化对象的状态。

  • 返回值:没有返回值(连void都不能写)。
  • 作用
    • ✅ 给对象的字段赋初始值
    • ✅ 执行一些初始化逻辑(比如显示欢迎信息、加载数据)
    • ✅ 确保对象在使用前处于“正确状态”在创建对象时自动执行,完成对象的初始化工作(如给字段/属性赋初始值、建立数据库连接等)。
  • 触发时机:每次用new关键字创建类的实例时,都会调用对应的构造函数。

示例:

class Cat
{
public string Name;
public int Age;

// 构造函数
public Cat()
{
Name = "未命名";
Age = 1;
Console.WriteLine("一只小猫诞生了!");
}
}

class Program
{
static void Main()
{
Cat cat1 = new Cat(); // 构造函数自动执行
Console.WriteLine($"猫的名字:{cat1.Name},年龄:{cat1.Age}");
}
}

输出:

一只小猫诞生了!
猫的名字:未命名,年龄:1

💡 解释: 当执行 new Cat() 时,C# 自动调用 Cat() 这个构造函数,用于“初始化”对象。

我们来给Student类添加一个构造函数:

class Student
{
public string Name { get; set; }
public int Age { get; set; }
public string StudentId { get; set; }

// 构造函数
public Student(string name, int age, string studentId)
{
Name = name;
Age = age;
StudentId = studentId;
}
}

现在,当我们创建学生对象时,就可以在创建的同时完成赋值:

Student student = new Student("张三", 18, "2023001");

这样是不是简洁多了?而且通过构造函数,我们可以强制要求创建对象时必须提供必要的信息,避免了忘记赋值的问题。

构造函数的语法特点**

特点说明
名字必须与类名相同(区分大小写)
没有返回类型不写 voidint
自动执行new 创建对象时会自动调用
可重载可以有多个构造函数(参数不同)

三、默认构造函数

可能有同学会问,如果我没有手动定义构造函数,之前的代码为什么还能正常工作呢?那是因为当一个类中没有定义任何构造函数时,C#编译器会自动为我们生成一个无参数的默认构造函数。它什么都不做,但保证对象能被正常创建。就像这样:

public class Student
{
public string Name { get; set; }
public int Age { get; set; }

// 编译器自动生成的默认构造函数(隐式存在)
// public Student() { }
}

// 使用默认构造函数创建对象
Student stu = new Student(); // 调用默认构造函数
stu.Name = "张三";
stu.Age = 18;

四、自定义构造函数

当需要在创建对象时就初始化数据(如强制设置姓名、年龄),可以显式定义构造函数。

重载原则:构造函数名必须与类名相同。

1. 无参数构造函数(手动定义)

如果手动定义了构造函数,编译器就不会再生成默认构造函数。如需保留无参构造,需手动声明:

public class Student
{
public string Name { get; set; }
public int Age { get; set; }

// 手动定义无参数构造函数
public Student()
{
// 初始化逻辑:如设置默认值
Name = "未知姓名";
Age = 0;
}
}

// 创建对象时自动调用无参构造函数
Student stu = new Student();
Console.WriteLine(stu.Name); // 输出:未知姓名(构造函数中设置的默认值)

2. 带参数的构造函数

最常用的构造函数形式,用于在创建对象时直接传入初始值:

public class Student
{
public string Name { get; set; }
public int Age { get; set; }

// 带参数的构造函数:强制传入姓名和年龄
public Student(string name, int age)
{
// 用参数初始化属性
Name = name;
Age = age;
}
}

// 创建对象时必须传入参数(与构造函数匹配)
Student stu = new Student("张三", 18); // 调用带参构造函数
Console.WriteLine($"{stu.Name}{stu.Age}岁"); // 输出:张三,18岁

优势:确保对象创建时就有有效的初始值,避免“未初始化就使用”的问题。

3.修饰符

构造函数还可以和访问修饰符配合使用。比如,我们可以把构造函数设为private,这样就只能在类内部创建对象,这在一些特殊设计模式中非常有用。

class Singleton
{
// 私有构造函数
private Singleton()
{
}

// 在类内部创建唯一实例
public static Singleton Instance = new Singleton();
}

五、什么时候可以不写构造函数

这个问题核心要理解C#的默认构造函数机制——编译器会自动为类生成无参构造函数,满足基础初始化需求时,就可以不手动写。

核心结论

当你的类满足以下任一条件时,可以不手动编写构造函数

  1. 类的所有属性/字段只需要使用C#默认值(如string默认nullint默认0、引用类型默认null),无需自定义初始化逻辑;
  2. 类仅作为“数据容器”(如简单的DTO类),属性通过对象初始化器(new Class { Prop = Value })赋值,无需提前初始化;
  3. 类是静态类(static class),无法实例化,因此不需要构造函数。

分场景详解(附代码示例)

场景1:类的成员使用默认值即可(最常见)

C#编译器会为没有任何手动构造函数的类,自动生成一个无参的默认构造函数,这个自动生成的构造函数会把所有成员初始化为C#默认值:

  • 值类型:int→0、bool→false、double→0.0;
  • 引用类型:string→null、自定义类→null。
using System;

// 不写构造函数,编译器自动生成默认无参构造函数
class Person
{
// 自动属性,无手动初始化
public string Name { get; set; }
public int Age { get; set; }
public bool IsStudent { get; set; }
}

class Program
{
static void Main()
{
// 使用编译器自动生成的构造函数实例化
Person p = new Person();

// 输出默认值:null、0、false
Console.WriteLine(p.Name); // null
Console.WriteLine(p.Age); // 0
Console.WriteLine(p.IsStudent);// false
}
}

这种场景下,因为只需要默认值,完全不需要手动写构造函数。

场景2:通过对象初始化器赋值(替代构造函数)

即使成员需要自定义值,但如果通过对象初始化器{ Prop = Value })赋值,也可以不写构造函数,这是C#简化初始化的常用方式:

using System;

// 无手动构造函数
class Book
{
public string Title { get; set; }
public double Price { get; set; }
}

class Program
{
static void Main()
{
// 用对象初始化器赋值,替代构造函数的初始化逻辑
Book b = new Book
{
Title = "C#入门",
Price = 59.9
};

Console.WriteLine(b.Title); // 输出:C#入门
Console.WriteLine(b.Price); // 输出:59.9
}
}

这种场景下,初始化逻辑由外部赋值完成,类内部无需写构造函数。

场景3:静态类(无需实例化)

静态类(static class)的所有成员都是静态的,无法被实例化(不能new 静态类()),因此完全不需要构造函数,写了反而会编译报错:

using System;

// 静态类,不写构造函数(也不能写)
static class Tool
{
// 静态方法,直接通过类名调用
public static int Add(int a, int b)
{
return a + b;
}
}

class Program
{
static void Main()
{
// 直接调用静态方法,无需实例化
int result = Tool.Add(1, 2);
Console.WriteLine(result); // 输出:3
}
}

反例:这些情况必须手动写构造函数

如果不满足上述条件,就必须手动写构造函数,否则无法实现需求:

  1. 需要自定义成员的初始值(如之前Animal类要把Name初始化为“未命名”,Age初始化为3);
  2. 需要带参数的构造函数(如public Person(string name) { Name = name; });
  3. 类有继承关系,子类需要调用父类的带参构造函数(base(参数));
  4. 实例化时需要执行额外逻辑(如初始化数据库连接、读取配置)。

总结

  1. 可以不写构造函数的核心条件:类无需自定义初始化逻辑,成员用默认值或通过对象初始化器赋值,或类是静态类;
  2. 编译器自动生成的构造函数:仅在类无任何手动构造函数时生效,且只有无参版本;
  3. 关键提醒:如果手动写了任意构造函数(哪怕是带参的),编译器就不会再生成默认无参构造函数,此时如果需要无参初始化,必须手动补充。

简单记:只要不需要“提前给成员赋非默认值”“执行初始化逻辑”,就可以不写构造函数,让编译器帮你生成。

六、构造函数的重载

和普通方法一样,构造函数也支持重载(同一个类中定义多个同名构造函数,通过参数的数量、类型或顺序区分),满足不同的初始化需求:

public class Student
{
public string Name { get; set; }
public int Age { get; set; }
public string Major { get; set; }

// 构造函数1:无参数(设置默认值)
public Student()
{
Name = "未知";
Age = 0;
Major = "未分配";
}

// 构造函数2:仅传入姓名
public Student(string name)
{
Name = name;
Age = 0;
Major = "未分配";
}

// 构造函数3:传入姓名、年龄、专业
public Student(string name, int age, string major)
{
Name = name;
Age = age;
Major = major;
}
}

// 调用不同的构造函数
Student stu1 = new Student(); // 用构造函数1
Student stu2 = new Student("李四"); // 用构造函数2
Student stu3 = new Student("王五", 20, "计算机"); // 用构造函数3

重载原则:构造函数名必须与类名相同,参数列表必须不同(数量、类型、顺序至少有一个不同)。

但是,一旦我们手动定义了构造函数,这个默认的无参数构造函数就会消失。如果还想像以前那样创建对象,就需要手动添加一个无参数构造函数:

class Student
{
// 其他成员...

// 无参数构造函数
public Student()
{
// 可以在这里设置一些默认值
StudentId = "未知";
}

// 带参数的构造函数
public Student(string name, int age, string studentId)
{
Name = name;
Age = age;
StudentId = studentId;
}
}

这种一个类中定义多个构造函数的情况,叫做构造函数的重载,它体现了面向对象的多态特性。

七、构造函数的使用场景

  1. 强制初始化:确保对象创建时必须提供关键信息(如IdName等)。
    例:User类必须传入用户名才能创建对象,避免无意义的空对象。

  2. 设置默认值:为属性设置合理的初始值(如年龄默认为0,状态默认为“正常”)。

  3. 资源初始化:在创建对象时分配资源(如打开文件流、建立数据库连接)。

    public class FileHandler
    {
    private FileStream _stream;

    // 构造函数中打开文件
    public FileHandler(string filePath)
    {
    _stream = File.Open(filePath, FileMode.Open);
    }
    }

八、总结

核心要点总结

  1. 构造函数与类同名,无返回值,用于对象初始化。
  2. 未手动定义时,编译器自动生成无参默认构造函数。
  3. 支持重载,可通过this关键字复用初始化逻辑。
  4. 每次用new创建对象时,必然调用对应的构造函数。

掌握构造函数是面向对象编程的基础,它能保证对象在使用前处于“就绪”状态,是封装思想的重要体现。

总结一下,构造函数是类中用于初始化对象的特殊方法,它具有以下特点:

  1. 构造函数的名字和类名相同,没有返回值
  2. 创建对象时会自动调用
  3. 主要作用是初始化对象的属性和状态
  4. 可以重载,实现多种初始化方式
  5. 可以使用访问修饰符控制访问权限

掌握构造函数的使用,能让我们的代码更加简洁、安全,也更符合面向对象的设计思想。下节课,我们将继续学习类的其他特性,大家课后可以尝试给之前定义的类添加构造函数,体验一下它的便利之处。

关键词含义
构造函数创建对象时自动执行的方法
名称与类名相同,没有返回值
作用初始化对象
类型无参构造、带参构造
特点可以重载、可用 this() 互相调用

练习题

第1题:最简单的构造函数

要求: 创建一个名为 Student 的类,定义一个无参数构造函数,在构造函数中输出 “一个学生对象被创建!”。

目标: 理解构造函数的基本定义与自动调用特性。


第2题:带参数的构造函数

要求:Student 类中添加 nameage 字段,通过构造函数为它们赋值,并在控制台输出它们的值。

目标: 掌握带参数构造函数的写法和使用方法。


第3题:多个对象实例

要求: 使用上题的 Student 类,创建 3 个学生对象,分别传入不同的姓名和年龄,观察输出结果。

目标: 理解不同实例拥有各自独立的数据。


第4题:字段与属性结合

要求:nameage 改为属性(NameAge),并在构造函数中通过属性赋值。

目标: 理解构造函数与属性的配合使用。


第5题:默认值构造函数

要求: 创建一个 Book 类,包含 TitlePrice 两个属性。 定义一个无参构造函数,为其设置默认值(例如:“未命名”, 0)。

目标: 掌握构造函数初始化默认值的技巧。


第6题:构造函数重载

要求: 创建 Car 类:

  • 一个无参构造函数(输出“默认构造函数被调用”)
  • 一个带参数构造函数(设置车名和价格)

创建两个对象,分别调用这两个构造函数。

目标: 理解构造函数的重载机制


第7题:在构造函数中调用方法

要求: 创建一个 Dog 类,包含 name 字段。 构造函数中为 name 赋值并调用 Bark() 方法(输出“汪汪!”)。

目标: 理解构造函数中可执行初始化逻辑或方法调用。


第8题:this关键字的使用

要求: 创建一个 Person 类,带参数构造函数中使用 this 关键字区分同名的字段与参数。

目标: 掌握 this 关键字在构造函数中的作用。


第9题:构造函数之间的相互调用

要求: 创建一个 Rectangle 类,包含:

  • 一个无参构造函数(默认长宽为 1)
  • 一个带两个参数的构造函数 无参构造函数调用带参构造函数。

目标: 学习使用 this(...) 调用其他构造函数。


第10题:构造函数与对象初始化器对比

要求: 创建 Phone 类,包含品牌和价格两个属性。 使用构造函数创建一个对象,再使用对象初始化器 {} 创建另一个对象,对比两者区别。

目标: 理解构造函数与对象初始化器在对象创建时的不同用法。


练习题答案

第1题:最简单的构造函数

using System;

class Student
{
public Student()
{
Console.WriteLine("一个学生对象被创建!");
}
}

class Program
{
static void Main()
{
Student stu1 = new Student();
}
}

运行结果:

一个学生对象被创建!

要点: 构造函数名称与类名相同,无返回类型,在创建对象时自动执行。


第2题:带参数的构造函数

using System;

class Student
{
public string name;
public int age;

public Student(string n, int a)
{
name = n;
age = a;
Console.WriteLine($"学生姓名:{name},年龄:{age}");
}
}

class Program
{
static void Main()
{
Student stu1 = new Student("小明", 18);
}
}

运行结果:

学生姓名:小明,年龄:18

要点: 带参数的构造函数用于在创建对象时初始化字段或属性。


第3题:多个对象实例

using System;

class Student
{
public string name;
public int age;

public Student(string n, int a)
{
name = n;
age = a;
Console.WriteLine($"学生姓名:{name},年龄:{age}");
}
}

class Program
{
static void Main()
{
Student s1 = new Student("小明", 18);
Student s2 = new Student("小红", 19);
Student s3 = new Student("小刚", 20);
}
}

运行结果:

学生姓名:小明,年龄:18
学生姓名:小红,年龄:19
学生姓名:小刚,年龄:20

要点: 每个对象都有自己独立的数据空间,构造函数在每次创建对象时都会执行。


第4题:字段与属性结合

using System;

class Student
{
public string Name { get; set; }
public int Age { get; set; }

public Student(string name, int age)
{
Name = name;
Age = age;
Console.WriteLine($"姓名:{Name}, 年龄:{Age}");
}
}

class Program
{
static void Main()
{
Student stu = new Student("小李", 17);
}
}

运行结果:

姓名:小李, 年龄:17

要点: 构造函数可以通过属性来初始化对象的公开数据。


第5题:默认值构造函数

using System;

class Book
{
public string Title { get; set; }
public double Price { get; set; }

public Book()
{
Title = "未命名";
Price = 0;
}
}

class Program
{
static void Main()
{
Book b = new Book();
Console.WriteLine($"书名:{b.Title},价格:{b.Price}");
}
}

运行结果:

书名:未命名,价格:0

要点: 无参构造函数可以为对象设置默认状态。


第6题:构造函数重载

using System;

class Car
{
public string Name { get; set; }
public double Price { get; set; }

public Car()
{
Console.WriteLine("默认构造函数被调用");
}

public Car(string name, double price)
{
Name = name;
Price = price;
Console.WriteLine($"车型:{Name},价格:{Price}");
}
}

class Program
{
static void Main()
{
Car c1 = new Car();
Car c2 = new Car("特斯拉", 299999);
}
}

运行结果:

默认构造函数被调用
车型:特斯拉,价格:299999

要点: 构造函数可以重载,根据参数不同选择不同版本。


第7题:构造函数中调用方法

using System;

class Dog
{
public string name;

public Dog(string n)
{
name = n;
Bark();
}

void Bark()
{
Console.WriteLine($"{name}:汪汪!");
}
}

class Program
{
static void Main()
{
Dog d = new Dog("旺财");
}
}

运行结果:

旺财:汪汪!

要点: 构造函数中可以调用类的其他方法,完成初始化逻辑。


第8题:this关键字的使用

using System;

class Person
{
private string name;

public Person(string name)
{
this.name = name; // 使用this区分字段和参数
}

public void Show()
{
Console.WriteLine($"姓名:{name}");
}
}

class Program
{
static void Main()
{
Person p = new Person("小华");
p.Show();
}
}

运行结果:

姓名:小华

要点: this 用于区分当前对象的字段和同名的构造函数参数。


第9题:构造函数之间的相互调用

using System;

class Rectangle
{
public int Width { get; set; }
public int Height { get; set; }

public Rectangle() : this(1, 1)
{
Console.WriteLine("无参构造函数被调用");
}

public Rectangle(int width, int height)
{
Width = width;
Height = height;
Console.WriteLine($"带参构造函数被调用:{Width}x{Height}");
}
}

class Program
{
static void Main()
{
Rectangle r1 = new Rectangle();
}
}

运行结果:

带参构造函数被调用:1x1
无参构造函数被调用

要点: 可以使用 this(...) 调用同类中的其他构造函数,避免重复代码。


第10题:构造函数与对象初始化器对比

using System;

class Phone
{
public string Brand { get; set; }
public double Price { get; set; }

public Phone(string brand, double price)
{
Brand = brand;
Price = price;
}

public Phone() { } // 空构造函数
}

class Program
{
static void Main()
{
// 使用构造函数
Phone p1 = new Phone("iPhone", 6999);
Console.WriteLine($"构造函数创建:{p1.Brand}, {p1.Price}");

// 使用对象初始化器
Phone p2 = new Phone { Brand = "小米", Price = 2999 };
Console.WriteLine($"对象初始化器创建:{p2.Brand}, {p2.Price}");
}
}

运行结果:

构造函数创建:iPhone, 6999
对象初始化器创建:小米, 2999

要点: 对象初始化器 {} 适合简化赋值操作;构造函数适合执行逻辑初始化。

完全理解你的感受!很多初学者练习题要么太简单(比如只让写一个构造函数打印一句话),要么脱离实际。下面给你3道循序渐进、真正锻炼思维的构造函数练习题,每道题都有明确的输入输出示例,并且覆盖了笔试面试中的高频考点。


练习题 1:图书类(基础:构造函数重载 + 默认值)

题目描述
创建一个 Book 类,包含三个私有字段:

  • title(标题,字符串)
  • author(作者,字符串)
  • price(价格,浮点数)

要求

  1. 编写两个构造函数:
    • 无参构造函数:标题设为 "未知书名",作者设为 "佚名",价格设为 0.0
    • 带参构造函数:接收标题、作者、价格三个参数,用它们初始化字段。
  2. 编写一个 ShowInfo() 方法,输出书的信息,格式如:《红楼梦》曹雪芹 ¥59.8
  3. Main 中分别使用两个构造函数创建对象,并调用 ShowInfo()

示例输出

《未知书名》佚名 ¥0.0
《三体》刘慈欣 ¥68.0

考察点

  • 构造函数重载
  • this 的隐式使用(区分字段和参数)
  • 默认值的设置

练习题 2:学生类(中级:this 调用 + 参数校验)

题目描述
创建一个 Student 类,包含:

  • name(姓名,string)
  • age(年龄,int,要求 0~120)
  • score(成绩,double,0~100)

要求

  1. 提供三个构造函数:
    • 无参构造函数:调用 “只传姓名” 的构造函数,姓名设为 "新生"
    • 构造函数 Student(string name):调用 Student(name, 18, 0)(年龄默认18,成绩默认0)。
    • 构造函数 Student(string name, int age, double score):直接初始化所有字段。在内部必须校验年龄和成绩的有效性(无效则 age 设为18,score 设为0)。
  2. 编写 Show() 方法输出学生信息。
  3. Main 中测试以下三种创建方式:
    • new Student()
    • new Student("李明")
    • new Student("王芳", 25, 95.5)
    • 故意给非法年龄(比如 new Student("坏蛋", 200, 80)),观察输出。

示例输出

姓名:新生, 年龄:18, 成绩:0
姓名:李明, 年龄:18, 成绩:0
姓名:王芳, 年龄:25, 成绩:95.5
姓名:坏蛋, 年龄:18, 成绩:80 (年龄被修正)

考察点

  • 构造函数链(this(...) 调用)
  • 参数校验与防御性编程
  • 理解构造函数的执行顺序

练习题 3:计数器类(进阶:静态构造函数 + 复制构造函数)

题目描述
实现一个 Counter 类,用于记录计数器的当前值,并附加一个全局最大限制。

要求

  1. 实例字段:_currentValue(int,当前计数)。
  2. 静态只读字段:MaxLimit(int,所有计数器允许的最大值)。
    • 该值通过静态构造函数初始化:从配置读取(为了模拟,直接写死在静态构造函数中为 100),且不可更改。
  3. 实例构造函数:
    • Counter():将当前值初始化为 0
    • Counter(int initialValue):如果 initialValue > MaxLimit 则抛出 ArgumentOutOfRangeException,否则赋值。
  4. 复制构造函数Counter(Counter other),用 other 的当前值初始化新计数器。
  5. 方法:
    • Increment():当前值加1,不能超过 MaxLimit,否则抛出异常。
    • GetValue() 返回当前值。
  6. Main 中演示:
    • 输出 Counter.MaxLimit
    • 创建两个计数器,其中一个通过复制另一个得到
    • 验证修改复制后的计数器不影响原计数器

示例输出(假设 MaxLimit = 100):

最大允许值: 100
原始计数器: 0 -> 递增后: 1
复制得到的计数器: 1 (独立的对象)
尝试创建超过限制的计数器抛出异常: 参数 initialValue 不能大于 100

考察点

  • 静态构造函数的作用与调用时机
  • 复制构造函数的理解(深拷贝/浅拷贝概念入门)
  • 异常的基本处理
  • 静态成员与实例成员的区别

为什么这三道题是“经典”?

题目核心练习目标常见错误陷阱
图书类构造函数重载 + 默认值忘记在无参构造函数中初始化字段;参数名和字段名冲突
学生类this 调用链 + 参数校验不注意调用顺序导致字段重复赋值;忘记校验边界值
计数器类静态构造 + 复制构造 + 业务限制误以为复制构造是引用复制;静态构造被多次调用

做完这三道题,你将彻底掌握:

  • 构造函数的各种形态(默认、有参、重载、链式调用、静态、复制)
  • 对象初始化时的常见设计模式
  • 简单的健壮性处理

你可以先把每个题目自己动手写出来,遇到卡壳的地方再对照我提供的提示(见下方)。写完再看标准参考答案,进步会非常快!

👉 点击查看提示(不建议一开始就看)

题1提示

  • 字段命名建议用 _titletitle,但参数同名时必须用 this.title = title
  • 无参构造方法体内可以直接调用带参构造并传入默认值,但为了练习,建议手动赋值。

题2提示

  • 构造函数链写法:
    public Student(string name) : this(name, 18, 0) { }
  • 校验逻辑放在最完整的构造函数中(被其他构造最终调用)。

题3提示

  • 静态构造函数语法:static Counter() { MaxLimit = 100; }
  • 复制构造函数:public Counter(Counter other) { _currentValue = other._currentValue; }
  • 记得判断初始值有效性时使用 if (initialValue > MaxLimit) throw ...

动手试试吧!如果你写完需要我帮你检查或者提供参考答案,随时告诉我。

下面是三道练习题的标准答案,每道题都包含完整的类实现和 Main 方法演示。建议你写完自己的代码后再对照参考,效果最好。


答案 1:图书类(构造函数重载 + 默认值)

using System;

public class Book
{
// 私有字段
private string title;
private string author;
private double price;

// 无参构造函数:设置默认值
public Book()
{
title = "未知书名";
author = "佚名";
price = 0.0;
}

// 带参构造函数
public Book(string title, string author, double price)
{
this.title = title; // this 区分字段和参数
this.author = author;
this.price = price;
}

// 显示信息的方法
public void ShowInfo()
{
Console.WriteLine($"《{title}{author}{price}");
}
}

class Program
{
static void Main()
{
Book book1 = new Book(); // 使用无参构造
Book book2 = new Book("三体", "刘慈欣", 68.0); // 使用带参构造

book1.ShowInfo();
book2.ShowInfo();
}
}

输出

《未知书名》佚名 ¥0
《三体》刘慈欣 ¥68

答案 2:学生类(this 调用 + 参数校验)

using System;

public class Student
{
private string name;
private int age;
private double score;

// 无参构造函数:调用单参数构造,姓名设为"新生"
public Student() : this("新生")
{
}

// 单参数构造函数:调用三参数构造,年龄默认18,成绩默认0
public Student(string name) : this(name, 18, 0)
{
}

// 完整构造函数:包含参数校验
public Student(string name, int age, double score)
{
this.name = name;

// 校验年龄 (0~120)
this.age = (age >= 0 && age <= 120) ? age : 18;

// 校验成绩 (0~100)
this.score = (score >= 0 && score <= 100) ? score : 0;
}

public void Show()
{
Console.WriteLine($"姓名:{name}, 年龄:{age}, 成绩:{score}");
}
}

class Program
{
static void Main()
{
Student s1 = new Student(); // 无参
Student s2 = new Student("李明"); // 单参
Student s3 = new Student("王芳", 25, 95.5); // 完整参数
Student s4 = new Student("坏蛋", 200, 80); // 年龄非法

s1.Show();
s2.Show();
s3.Show();
s4.Show();
}
}

输出

姓名:新生, 年龄:18, 成绩:0
姓名:李明, 年龄:18, 成绩:0
姓名:王芳, 年龄:25, 成绩:95.5
姓名:坏蛋, 年龄:18, 成绩:80

答案 3:计数器类(静态构造函数 + 复制构造函数)

using System;

public class Counter
{
// 实例字段
private int _currentValue;

// 静态只读字段
public static readonly int MaxLimit;

// 静态构造函数:初始化 MaxLimit(只执行一次)
static Counter()
{
// 模拟从配置读取,这里直接写死为 100
MaxLimit = 100;
}

// 实例构造函数:初始值为 0
public Counter()
{
_currentValue = 0;
}

// 实例构造函数:带初始值,并验证
public Counter(int initialValue)
{
if (initialValue > MaxLimit)
throw new ArgumentOutOfRangeException(nameof(initialValue),
$"参数 initialValue 不能大于 {MaxLimit}");
_currentValue = initialValue;
}

// 复制构造函数
public Counter(Counter other)
{
if (other == null)
throw new ArgumentNullException(nameof(other));
_currentValue = other._currentValue;
}

// 增加计数
public void Increment()
{
if (_currentValue + 1 > MaxLimit)
throw new InvalidOperationException($"当前值已达上限 {MaxLimit},无法递增");
_currentValue++;
}

// 获取当前值
public int GetValue()
{
return _currentValue;
}
}

class Program
{
static void Main()
{
// 输出静态成员
Console.WriteLine($"最大允许值: {Counter.MaxLimit}");

// 创建两个计数器
Counter c1 = new Counter(); // 值为 0
c1.Increment(); // 变为 1
Console.WriteLine($"原始计数器: {c1.GetValue()}");

// 复制构造函数
Counter c2 = new Counter(c1);
Console.WriteLine($"复制得到的计数器: {c2.GetValue()} (独立的对象)");

// 修改 c2 不影响 c1
c2.Increment(); // c2 变为 2
Console.WriteLine($"c2 递增后: {c2.GetValue()}, c1 仍然是: {c1.GetValue()}");

// 测试超出限制的异常
try
{
Counter c3 = new Counter(150);
}
catch (ArgumentOutOfRangeException ex)
{
Console.WriteLine($"尝试创建超过限制的计数器抛出异常: {ex.Message}");
}

// 测试递增到超限
Counter c4 = new Counter(Counter.MaxLimit);
try
{
c4.Increment();
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"尝试递增超过限制: {ex.Message}");
}
}
}

输出示例(可能因异常消息文本略有不同):

最大允许值: 100
原始计数器: 1
复制得到的计数器: 1 (独立的对象)
c2 递增后: 2, c1 仍然是: 1
尝试创建超过限制的计数器抛出异常: 参数 initialValue 不能大于 100 (Parameter 'initialValue')
尝试递增超过限制: 当前值已达上限 100,无法递增

总结

这三道题覆盖了:

  • 基础的重载和默认值
  • 构造函数链(this 调用)
  • 参数校验与防御性编程
  • 静态构造函数的执行时机
  • 复制构造函数(值类型拷贝)

认真完成并理解这些代码,你对 C# 构造函数的掌握会更扎实。如果有任何疑问(例如为什么用 readonly 而不是 const,或者复制构造函数中的深拷贝问题),欢迎继续提问!