C#基础-4-循环与封装

前文:

循环

循环语句允许多次执行一个语句或语句组。

while语句

用法:

while(condition)
{
   statement(s);
}

在这里,statement(s)​ 可以是一个单独的语句,也可以是几个语句组成的代码块。condition​ 可以是任意的表达式,当为 任意非零值时都为真

当条件为真时执行循环,直到当条件为假时,程序流才将继续执行循环的下一条语句,例如:

int a = 10;

// while 循环执行
while (a < 15)
{
	Console.WriteLine($"a 的值: {a}");
	a++;
}

输出:

a 的值: 10
a 的值: 11
a 的值: 12
a 的值: 13
a 的值: 14

for-foreach语句

用法:

for ( init; condition; increment )
{
   statement(s);
}

释义:

  1. init​ 会首先被执行,且只会执行一次,这一步允许声明并初始化任何 循环控制变量,也可以不在这里写任何语句,但必须要有一个分号
  2. 接下来,会判断 condition​ ,如果为真,则执行循环主体 statement(s)​ ;如果为假,则不执行循环主体,跳出循环
  3. 在执行完 for 循环主体后,控制流会跳回上面的 increment​ 语句,该语句允许更新循环控制变量,同样该语句可以留空,但必须要有一个分号
  4. 条件再次被判断,如果为真,则执行循环,这个过程会不断重复(循环主体,然后增加步值,再然后重新判断条件),直到条件变为假时循环终止

例如:

for (int a = 10; a < 15; a = a + 1)
{
	Console.WriteLine($"a 的值: {a}");
}

输出:

a 的值: 10
a 的值: 11
a 的值: 12
a 的值: 13
a 的值: 14

C# 也支持 foreach 循环,使用 foreach 可以迭代数组或者一个集合对象,用来遍历集合类型,例如数组、列表、字典等,相当于一个简化版的 for 循环,用法:

foreach (var item in collection)
{
    // 循环
}

collection​ 是要遍历的集合,item​ 是当前遍历到的元素,示例:

// 创建一个整数数组 fibarray
int[] fibarray = new int[] { 0, 1, 1, 2, 3, 5, 8, 13 };

// 打印 fibarray 数组中的每个元素 element
foreach (int element in fibarray)
{
	Console.WriteLine(element);
}
Console.WriteLine();	// 输出空用来占位

// 上述的循环用 for 语句来实现,用 fibarray.Length 来获取数组的长度
for (int i = 0; i < fibarray.Length; i++)
{
	Console.WriteLine(fibarray[i]);
}
Console.WriteLine();

// 设置集合中元素的计算器,手动获取数组的长度
int count = 0;
foreach (int element in fibarray)
{
	count += 1;
	Console.WriteLine($"Element #{count}: {element}");
}
Console.WriteLine($"Number of elements in the array: {count}");

输出:

0
1
1
2
3
5
8
13

0
1
1
2
3
5
8
13

Element #1: 0
Element #2: 1
Element #3: 1
Element #4: 2
Element #5: 3
Element #6: 5
Element #7: 8
Element #8: 13
Number of elements in the array: 8

示例 foreach 来遍历一个列表:

// 创建一个字符串列表
List<string> myStrings = new List<string>();

// 向列表添加一些字符串元素
myStrings.Add("Google");
myStrings.Add("Runoob");
myStrings.Add("Taobao");

// 使用 foreach 循环遍历列表中的元素
foreach (string s in myStrings)
{
	Console.WriteLine(s);
}

输出:

Google
Runoob
Taobao

再看一个示例:

// 定义一个变量 count 存放数组长度
int count;
// 读取用户输入的数字,赋值给 count
Console.WriteLine("输入要登记的学生数:");
count = int.Parse(Console.ReadLine());
// 创建一个字符串数组 names,数组长度为变量 count
string[]names = new string[count];
// 通过 for 循环让用户输入数组 names 的所有元素
for (int i = 0; i < names.Length; i++)
{
	int num = i + 1;
	Console.WriteLine($"请输入第{num}个学生的姓名");
	names[i] = Console.ReadLine();
}
// 通过 foreach 遍历输出数组 names 中每一个元素
Console.WriteLine("已登记的学生如下");
foreach (string name in names)
{
	Console.WriteLine($"{name}");
}

仅是示例,实际业务中需要考虑输入为空,输入类型不匹配等情况。

输出:

foreach 在遍历数组方面的简便性是 for 语句无可比拟的,再看个复杂示例:

// 定义一个 2 行 2 列 2 纵深的 3 维数组 a
int[,,] a = new int[2, 2, 2] { {{ 1, 2 }, { 3,4}},{{ 5, 6 }, { 7,8}} };
// 用 Array.GetLength(n) 得到数组 [0,1,,,n] 上的维数的元素数,0 代表行,1 列,n 代表此数组是 n+1 维
for (int i = 0; i < a.GetLength (0) ;i++ )
{
    for (int j = 0; j < a.GetLength(1); j++)
    {
		// 2 代表得到纵深上的元素数,如果数组有 n 维就得写 n 个 for 循环
        for (int z = 0; z < a.GetLength(2);z++ )
        {
            Console.WriteLine(a[i,j,z]);
        }
    }
}

如果使用 foreach 则:

int[,,] a = new int[2, 2, 2] { {{ 1, 2 }, { 3,4}},{{ 5, 6 }, { 7,8}} };
foreach (int i in a)
{
    Console .WriteLine (i);
}

do-while语句

不像 for 和 while 是在循环头部测试循环条件,do while​ 循环是在循环的尾部检查它的条件。do while​ 循环与 while​ 循环类似,但是 do while​ 循环会确保 至少执行一次循环。用法:

do
{
   statement(s);
} while ( condition );

注意,条件表达式 condition​ 出现在循环的尾部,所以循环中的 statement(s)​ 会在条件被测试之前 至少执行一次。如果条件为真,控制流会跳转回上面的 do​ ,然后重新执行循环中的 statement(s)​ ,这个过程会不断重复,直到给定条件变为假为止。示例:

int a = 10;

// do 循环执行
do
{
	Console.WriteLine($"a 的值: {a}");
	a = a + 1;
} while (a < 15);

输出:

a 的值: 10
a 的值: 11
a 的值: 12
a 的值: 13
a 的值: 14

控制语句

循环控制语句有两个:break​ 和 continue​ 。

break​ 语句有以下两种用法:

  • 当 break 语句出现在一个循环内时,循环会立即终止,且程序流将继续执行紧接着循环的下一条语句
  • 终止 switch 语句中的一个 case

如果是嵌套循环(即一个循环内嵌套另一个循环),break 语句会停止执行最内层的循环,然后开始执行该块之后的下一行代码

示例:

int a = 10;

while (a < 20)
{
	Console.WriteLine($"a 的值: {a}");
	a++;
	if (a > 13)
	{
		// 使用 break 语句终止循环
		break;
	}
}

输出:

a 的值: 10
a 的值: 11
a 的值: 12
a 的值: 13

本来循环应该输出到 a 的值为 19,但当 a 为 13 时,执行 a++ 后为 14,触发 break 语句,并没有输出 14。

continue 语句有点像 break 语句,但它不是强迫终止,continue 会跳过当前循环中的代码,强迫开始下一次循环。对于 for 循环,continue 语句会导致执行 条件测试和循环增量 部分。对于 while 和 do while 循环,continue 语句会导致程序控制 回到条件测试 上。

示例:

int a = 13;

do
{
	if (a == 15)
	{
		a = a + 1; // a = 16
		continue; // 直接不执行下列的输出语句和 a++ 语句,跳到 while 语句检查条件
	}
	Console.WriteLine($"a 的值:{a}");
	a++;
} while (a < 18);

输出:

a 的值: 13
a 的值: 14
a 的值: 16
a 的值: 17

循环嵌套

可以在任何类型的循环内嵌套其他任何类型的循环。比如,一个 for 循环可以嵌套在一个 while 循环内,反之亦然。示例:

// 输出 2-10 的质数
int i, j;

for (i = 2; i < 10; i++)
{
	for (j = 2; j <= (i / j); j++)
	{
		if ((i % j) == 0)
		{
			break; // 如果找到,则不是质数
		}
	}
	if (j > (i / j))
	{
		Console.WriteLine($"{i} 是质数");
	}
}

输出:

2 是质数
3 是质数
5 是质数
7 是质数

无限循环

如果条件永远不为假,则循环将变成无限循环,for 循环在传统意义上可用于实现无限循环:

for ( ; ; )
{
	Console.WriteLine("Hey! I am Trapped");
}

封装

封装 被定义为"把一个或多个项目封闭在一个物理的或者逻辑的包中"。在面向对象程序设计方法论中,封装是为了防止对实现细节的访问。

抽象和封装是面向对象程序设计的相关特性。抽象允许相关信息可视化,封装则使开发者实现所需级别的抽象

C# 封装根据具体的需要,设置使用者的访问权限,并通过 访问修饰符 来实现。

一个 访问修饰符 定义了一个类成员的范围和可见性。C# 支持的访问修饰符如下所示:

  • public:所有对象都可以访问;
  • private:对象本身在对象内部可以访问;
  • protected:只有该类对象及其子类对象可以访问
  • internal:同一个程序集的对象可以访问;
  • protected internal:访问限于当前程序集或派生自包含类的类型。

Public

Public 访问修饰符允许一个类将其成员变量和成员函数暴露给其他的函数和对象,任何公有成员可以被外部的类访问示例:

using System;

namespace RectangleApplication
{
    class Rectangle
    {
        // 成员变量
        public double Length;
        public double Width;

        public double GetArea()
        {
            return Length * Width;
        }
        public void Display()
        {
            Console.WriteLine($"长度: {Length}");
            Console.WriteLine($"宽度: {Width}");
            Console.WriteLine($"面积: {GetArea()}");
        }
    } // Rectangle 结束

    class ExecuteRectangle
    {
        static void Main(string[] args)
        {
            Rectangle r = new Rectangle();
            r.Length = 4.5;
            r.Width = 3.5;
            r.Display();
            // Console.ReadLine();
        }
    }
}

输出:

长度: 4.5
宽度: 3.5
面积: 15.75

成员变量 length 和 width 被声明为 public,所以它们可以被函数 Main()​ 使用 Rectangle 类的实例 r 访问,成员函数 Display()​ 和 GetArea()​ 可以直接访问这些变量。成员函数 Display()​ 也被声明为 public,所以它也能被 Main()​ 使用 Rectangle 类的实例 r 访问。

至于为什么要新建实例 r,Main()​ 函数必须是静态的,静态方法中不能调用非静态方法,除非通过创建类的实例实例来调用。详情参见:C++和C#调用不同目录其他文件中的函数或方法

Private

Private 访问修饰符允许一个类将其成员变量和成员函数对 其他的 函数和对象进行 隐藏。只有 同一个类 中的函数可以访问它的 私有成员。即使是类的实例也不能访问它的私有成员。示例:

using System;

namespace RectangleApplication
{
    class Rectangle
    {
        // 私有成员变量
        private double _length;
        private double _width;

        // 公有方法,用于从用户输入获取矩形的长度和宽度,需要被 Main() 函数调用
        public void AcceptDetails()
        {
            Console.WriteLine("请输入长度:");
            _length = Convert.ToDouble(Console.ReadLine());
            Console.WriteLine("请输入宽度:");
            _width = Convert.ToDouble(Console.ReadLine());
        }

        // 私有方法,用于计算矩形的面积,不需要被 Main() 函数调用
        private double GetArea()
        {
            return _length * _width;
        }

        // 公有方法,用于显示矩形的属性和面积,需要被 Main() 函数调用
        public void Display()
        {
            Console.WriteLine($"长度: {_length}");
            Console.WriteLine($"宽度: {_width}");
            Console.WriteLine($"面积: {GetArea()}");
        }
    }  

    class ExecuteRectangle
    {
        static void Main(string[] args)
        {
            // 创建 Rectangle 类的实例
            Rectangle r = new Rectangle();
            // 通过公有方法 AcceptDetails() 从用户输入获取矩形的长度和宽度
            r.AcceptDetails();
            // 通过公有方法 Display() 显示矩形的属性和面积
            r.Display();
            // Console.ReadLine();
        }
    }
}

输出:

lengthwidth 被声明为私有成员变量,以防止直接在类的外部访问和修改。AcceptDetails 方法允许用户输入矩形的长度和宽度,这是通过公有方法来操作私有变量的一个例子。GetArea 方法用于计算矩形的面积,而 Display 方法用于显示矩形的属性和面积。在 ExecuteRectangle 类中,通过创建 Rectangle 类的实例,然后调用其公有方法,来执行操作。这样,主程序无法直接访问和修改矩形的长度和宽度,而是通过类提供的公有接口来进行操作,实现了封装。

Protected

Protected 访问修饰符允许子类访问它的基类的成员变量和成员函数,这样有助于实现继承,在 继承 的章节详细讨论这个。

protected 访问修饰符用于控制类成员的访问权限。具体来说,protected 修饰符允许类的成员在以下情况下被访问:

  1. 类内部:类的成员可以在类内部的任何地方访问。
  2. 派生类:类的成员可以在任何派生类(子类)中访问,即使这些派生类位于不同的程序集中。

示例:

public class BaseClass
{
    protected int protectedField = 42;

    protected void ProtectedMethod()
    {
        Console.WriteLine("This is a protected method.");
    }
}

public class DerivedClass : BaseClass
{
    public void AccessProtectedMembers()
    {
        // 可以访问基类的 protected 成员
        Console.WriteLine(protectedField);
        ProtectedMethod();
    }
}

public class OtherClass
{
    public void TryAccessProtectedMembers()
    {
        BaseClass baseClass = new BaseClass();
        
        // 以下代码会编译错误,因为 protected 成员不能在类外部访问
        // Console.WriteLine(baseClass.protectedField);
        // baseClass.ProtectedMethod();
    }
}

关键点:

  • 类内部访问:protected 成员可以在声明它们的类内部自由访问。
  • 派生类访问:protected 成员可以在任何派生类中访问,无论这些派生类是否在同一个程序集中。
  • 类外部访问:protected 成员不能在类的外部访问,即使是通过类的实例。

protected 访问修饰符在C#中用于限制类成员的访问范围,使其只能在类内部和派生类中访问。这种访问控制机制有助于实现封装和继承的概念。

模型:DeepSeek-V3,2025 03-04 15:34

Internal

Internal 访问修饰符允许一个类将其成员变量和成员函数暴露给当前程序中的其他函数和对象。换句话说,带有 internal 访问修饰符的任何成员可以被定义在该成员所定义的应用程序内的任何类或方法访问。

示例:

using System;

namespace RectangleApplication
{
    class Rectangle
    {
        //成员变量
        internal double length;
        internal double width;
        
        double GetArea()
        {
            return length * width;
        }
       public void Display()
        {
            Console.WriteLine("长度: {0}", length);
            Console.WriteLine("宽度: {0}", width);
            Console.WriteLine("面积: {0}", GetArea());
        }
    }  
    class ExecuteRectangle
    {
        static void Main(string[] args)
        {
            Rectangle r = new Rectangle();
            // 在类 ExecuteRectangle 中访问类 Rectangle 的成员变量
            r.length = 4.5;
            r.width = 3.5;
            r.Display();
            Console.ReadLine();
        }
    }
}

输出:

长度: 4.5
宽度: 3.5
面积: 15.75

在这个示例中,注意成员函数 GetArea() 声明的时候不带有任何访问修饰符,如果没有指定访问修饰符,则使用类成员的 默认访问修饰符,即为 private