目录
3. 利用递归,写个文件目录遍历,打印出文件名、扩展名、文件大小
5. 重点比较public、protected、private的区别
1. 变量的作用域有哪些?
变量的作用域分别有:局部变量;块级作用域;方法参数作用域;全局变量;静态变量作用域;
下面分别堆这些变量的作用域进行详细讲解:
1.1 局部变量:
局部变量是在方法、构造函数、属性或任何嵌套的代码块内声明的变量。它们的作用域仅限于它们被声明的代码块,通常是大括号 {}
内的内容。一旦控制流离开该代码块,局部变量就会被销毁。这意味着局部变量只在其声明的范围内可见,并且在每次进入此范围时都会重新创建。
namespace DjConsoleApp0527
{
internal class Question
{
void MyMethod1()
{
int localVar = 10; // 局部变量
if (true)
{
Console.WriteLine(localVar); // 可以访问
}
Console.WriteLine(localVar); // 可以访问
}
// 在MyMethod()之外无法访问localVar
Console.WriteLine(localVar)
}
}
程序显示结果将会是:
1.2 块级作用域:
块级作用域指的是在任何由大括号 {}
包围的代码块内部声明的变量。这些变量仅在该代码块内可见,一旦代码块执行完毕,变量就会被销毁。这与局部变量相似,但特别强调了在条件语句(如 if
或 for
循环)中的使用。
void MyMethod2()
{
if (true)
{
int blockScopedVar = 20; // 块级作用域变量
Console.WriteLine(blockScopedVar);
}
Console.WriteLine(blockScopedVar); // 错误:blockScopedVar在此处不可见
}
程序显示结果将会是:
1.3 方法参数作用域
方法参数是在方法签名中声明的变量,用于接收传递给方法的值,它们的作用域限于整个方法体内,即从方法的开始到结束,41方法参数只能在方法内部被访问。
void MyMethod3(int param)
{
Console.WriteLine(param); // 可以访问
}
//Console.WriteLine(param)
//param在此处不可见
只在方法体内生效
1.4 全局变量
在C#中,严格意义上不存在“全局变量”。但是,类的字段(尤其是静态字段)有时被称作全局变量,因为它们在整个类的生命周期中存在,并且在类的任何方法中都可以访问。然而,它们并不是真正意义上的全局,因为它们仍然受限于类的边界。
class MyClass
{
int memberVar = 30; // 成员变量,在整个类中可见
}
1.5 静态变量作用域
静态变量是在类级别上声明的,使用关键字 static;
它们在所有实例共享同一份副本,即所有类的实例访问的是同一个静态变量。静态变量在整个应用程序运行期间存在,直到应用程序终止。它们可以在类的任何静态上下文中访问,包括静态方法和静态构造函数。
class MyClass
{
static int staticVar = 40; // 静态变量,在整个类中可见
}
2. 成员变量和静态变量的区别?
成员变量(Instance Variables):
- 所属范围:成员变量属于每个类的实例(对象),也就是说,每当创建一个新的对象时,每个对象都会拥有自己独立的一份成员变量的副本。
- 存活时间:成员变量的生命周期与对象的生命周期绑定。当对象创建时,成员变量随之创建;当对象被垃圾回收时,成员变量也随之消失。
- 存储位置:成员变量存储在堆内存中,作为对象的一部分。
- 调用方式:成员变量只能通过对象实例来访问和修改。
静态变量(Static Variables):
- 所属范围:静态变量属于类本身,而不是类的任何特定实例。这意味着无论创建了多少个对象,静态变量都只有一个副本,所有实例共享这个副本。
- 存活时间:静态变量的生命周期与类的生命周期绑定。当类加载时,静态变量被创建;当类卸载时,静态变量才消失。
- 存储位置:静态变量存储在方法区(也称为永久代,在Java中)或类的元数据区域(在.NET中),而不是堆内存。
- 调用方式:静态变量可以通过类名直接访问,也可以通过对象实例访问,但推荐使用类名访问,以表明其静态性质。
成员变量属于对象级别,每个对象创建时进行初始化,每个对象实例自己来独立维护这个成员变了;静态变量属于类级别,当类加载时它就会被创建,比对象更早(创建一个工具类,工具类的方法都习惯使用静态)独一份;相比静态变量的性能是高于成本变量;如果复用我们习惯使用静态的,如果各自独立使用,也不需要立即使用,用的时候在创建,我们习惯使用实例变量。
直接代码展示:
这个代码已经加上了static,全局变量只需要去掉static即可。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DjConsoleApp0527
{
internal class StaticVar
{
static public int num;//成员变量
public void count()
{
num++;
}
public int get()
{
return num;
}
}
}
//调用函数
using DjConsoleApp0527;
#region
StaticVar sv = new StaticVar();
sv.count();
sv.count();
sv.count();
Console.WriteLine(sv.get());
StaticVar sv2= new StaticVar();
sv2.count();
sv2.count();
sv2.count();
Console.WriteLine(sv2.get());
结果的比较:
局变量的结果图:
静态变量作用域的结果图:
3. 利用递归,写个文件目录遍历,打印出文件名、扩展名、文件大小
这里做个递归的练习,补充一下C#方法的知识量。
首先,现在使用的项目下添加一个DirectoryTraversa类
然后,根据以下步骤做出这个作业:
- 创建一个递归函数,接受目录路径作为参数。
- 在函数中,使用
DirectoryInfo
和FileInfo
类来获取目录和文件信息。- 使用
GetFiles
和GetDirectories
方法来获取目录下的文件和子目录。- 对每个文件,打印文件名、扩展名和大小。
- 对每个子目录,递归调用该函数。
代码如下:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DjConsoleApp0527
{
internal class DirectoryTraversa
{
public void TraverseDirectory(string Path)
{
/* try
{*/
DirectoryInfo dirInfo = new DirectoryInfo(Path);
if (dirInfo.Exists)
{
// 获取目录下的所有文件
FileInfo[] files = dirInfo.GetFiles();
foreach (FileInfo file in files)
{
Console.WriteLine("文件名{0},拓展名{1},文件大小{2},",file.Name,file.Extension,file.Length);
}
// 获取目录下的所有子目录
DirectoryInfo[] subDirs = dirInfo.GetDirectories();
foreach (DirectoryInfo subDir in subDirs)
{
// 递归调用
TraverseDirectory(subDir.FullName);
}
}
else
{
Console.WriteLine("Directory '{0}' does not exist.",Path);
}
}
/*catch (UnauthorizedAccessException)
{
Console.WriteLine($"Access to directory '{Path}' is denied.");
}
catch (DirectoryNotFoundException)
{
Console.WriteLine($"Directory '{Path}' was not found.");
}
catch (Exception ex)
{
Console.WriteLine($"Error accessing directory '{Path}': {ex.Message}");
}*/
//}
}
}
调用函数:
using DjConsoleApp0527;
DirectoryTraversa directory=new DirectoryTraversa();
string Path= @"D:\NLP\weibo\pachon";
directory.TraverseDirectory(Path);
输出的结果:
可以看出已经实现了这个功能,做作业中我发现:在C#中,异常既可以由系统自动抛出,也可以由程序员显式地抛出,所以我就注释了异常抛出的代码
4. 简述访问修饰符有几种,各有什么不同?
比较一下java和C#的访问修饰符:
- Java:public、protected、默认、private
- C#:public、protected、private、internal、protected internal:
在C#中访问修饰符有:
描述:公共访问。成员可以被任何其他代码访问,无论这些代码是在同一个类、同一个命名空间、同一个程序集还是不同的程序集中。
public(
公开的))
:描述:私有访问。成员仅在其声明所在的类中可见。即使在派生类中也不可访问。
private(
私有的)
:描述:保护访问。成员在其声明所在的类及其派生类中可见。这使得基类的成员可以在派生类中被访问。
protected(
受保护的)
:描述:内部访问。成员仅在包含其声明的程序集内可见。这意味着其他在同一程序集内的类可以访问这些成员,但不同程序集中的类则不能。
internal(
程序集内部的)
:描述:内部保护访问。成员在包含其声明的程序集内以及派生类中可见。这意味着成员既具有
protected internal(
只有本程序内或者继承于该类的类型可以访问)
:protected
的特性也具有internal
的特性。
5. 重点比较public、protected、private的区别
描述: 成员对所有代码开放,无论这些代码位于何处,即在同一个类、同一个包(Java中)、同一个命名空间(C#中)、同一个项目或完全不同的项目中。
public
:描述: 成员对同一类、同一包(Java中)或同一命名空间(C#中)内的代码开放,同时对所有派生类开放,不论这些派生类位于何处。这意味着
protected
:protected
成员可以被子类访问,从而支持继承和多态。描述: 成员仅对声明它的类开放,即只有该类内部的方法可以访问这些成员。
private
:private
成员对类的外部(包括子类)是不可见的,这提供了最高级别的封装和数据保护。
直接上代码:
创建两个类一个BaseClass.cs 基类和DerivedClass.cs 派生类:
BaseClass.cs 基类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DjConsoleApp0527
{
internal class BaseClass
{
public int pc;
protected int pd;
private int pe;
public void pcm()
{
Console.WriteLine("pcm");
}
protected void pdm()
{
Console.WriteLine("pdm");
}
private void pem()
{
Console.WriteLine("pem");
}
}
}
DerivedClass.cs 派生类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DjConsoleApp0527
{
internal class DerivedClass : BaseClass
{
public void accessMember()
{
pc = 10; //直接访问父类的public属性
pd = 20; //直接访问父类的protected属性
//pe = 30;不能直接访问父类的private属性
pcm(); //直接访问父类的public方法
pdm(); //直接访问父类的protected方法
//pem();//不能直接访问父类的private方法
}
}
}
调用:
using DjConsoleApp0527;
DerivedClass dc = new DerivedClass();
dc.accessMember(); //外部类通过派生类去访问基类
dc.pc = 10; //public属性在哪都可以
//dc.pd = 20; //报错:外部类属性不能直接访问,只能子类去访问
dc.pcm(); //public方法在哪都可以
//dc.pdm(); //报错:外部类方法不能直接访问,只能子类去访问
从以上代码应该可以直观的看出它们的不同了吧。