C# .NET面试系列一:基础语法

2024-03-07 10:35:16 浏览数 (1)

基础语法

1. 字符串中 string str = null 和 string str = "" 和 string str = string.Empty 的区别?

string str=null:

代码语言:c#复制
这表示字符串变量str被初始化为null,即它不引用任何对象。这与空字符串是不同的,因为空字符串是一个具有零长度的字符串对象,不会分配内存空间

string.Empty:

代码语言:c#复制
这表示字符串变量str被初始化为空字符串,即一个具有零长度的字符串对象。这与null是不同的,因为str引用了一个空字符串对象

string str="" :

代码语言:c#复制
这也是将字符串变量初始化为空字符串,但使用了string.Empty常量。string.Empty实际上是一个表示空字符串的常量字段,它与""是等价的。这只是一种更可读的方式来表示空字符串,并分配一个空字符串的内存空间
2. byte b = 'a'; byte c = 1; byte d = 'ab'; byte e = '啊'; byte g = 256; 这些变量有些错误是错在哪里?

在给定的代码中,有一些错误:

byte b = 'a';

代码语言:c#复制
这行代码会引发编译错误。虽然字符'a'可以被隐式地转换为整数,但是将其赋值给byte类型时,需要确保其值在byte类型的范围内(0 到 255)。字符'a'的ASCII码值为97,这是一个在byte范围内的值,所以可以修改为 byte b = (byte)'a'; 来解决问题。

byte d = 'ab';

代码语言:c#复制
这行代码也会引发编译错误。将字符串赋值给byte类型是不允许的,需要修改为合法的赋值方式。

byte e = '啊';

代码语言:c#复制
这行代码同样会引发编译错误。字符 '啊' 的 Unicode 值大于 byte 类型的范围,因此需要修改为合法的赋值方式。

byte g = 256;

代码语言:c#复制
这行代码会引发编译错误。byte 类型的范围是 0 到 255,因此不能将 256 赋值给 byte 类型。需要将值修改为在 byte 范围内的值。

以下是修改后的可能代码:

代码语言:csharp复制
byte b = (byte)'a';
byte c = 1;
byte d = 0; // 修正为合法的赋值方式
byte e = 0; // 修正为合法的赋值方式
byte g = 255; // 修改为在 byte 范围内的值

请注意,在处理字符时,需要确保其值在 byte 类型的范围内。如果要将字符串赋值给 byte 类型,可能需要使用 Convert 类或其他适当的转换方法。

3. string 和 StringBuilder 的区别,两者性能的比较

① 不可变性:

代码语言:c#复制
string 是不可变的。一旦创建了一个字符串对象,就不能更改其内容。对字符串进行修改实际上是创建一个新的字符串对象。

StringBuilder 是可变的。它允许对字符串进行动态的、原地的修改,而不必每次都创建新的对象。

② 性能比较:

代码语言:c#复制
当需要频繁对字符串进行修改时,StringBuilder 通常比直接使用 string 更高效。这是因为 StringBuilder 允许原地修改,而不必每次都创建新的字符串对象,从而减少了内存开销。

对于简单的字符串拼接,如果只涉及少量操作,性能差异可能并不明显。但在大量拼接或修改的情况下,StringBuilder 的性能优势会更为明显。

③ 内存分配:

代码语言:c#复制
在使用 string 进行字符串拼接时,每次拼接都会创建一个新的字符串对象,这可能导致频繁的内存分配和垃圾回收。

StringBuilder 的内部实现使用可变的字符数组,避免了频繁的内存分配,从而提高了性能。

④ 线程安全性:

代码语言:c#复制
string 是不可变的,因此是线程安全的。多个线程可以同时读取一个字符串对象而不会引起问题。

StringBuilder 不是线程安全的。如果多个线程同时尝试修改同一个 StringBuilder 实例,可能会引发竞态条件。如果在多线程环境中需要对字符串进行修改,应该考虑使用 StringBuilder 的同步方法或采取其他线程安全的措施。

综上所述,选择使用 string 还是 StringBuilder 取决于具体的使用场景。如果字符串是不变的且不需要频繁修改,使用 string 是合适的。如果需要频繁进行字符串的拼接或修改,尤其是在循环中,使用 StringBuilder 可以提高性能。
4. 什么是扩展方法?

扩展方法(Extension Methods)是C#中一种特殊的静态方法,它允许你向现有的类添加新的方法,而无需修改原始类的定义。扩展方法通常用于向.NET框架中的类型添加功能,甚至是无法修改的封闭源代码的类。

要创建扩展方法,需要满足以下条件:

1)扩展方法必须是一个静态方法。

2)扩展方法必须包含一个关键字 this 作为其第一个参数,该参数指定了该方法应用于的类型。这个参数是要扩展的类型的实例。

下面是一个简单的扩展方法的例子,假设我们想为 string 类型添加一个反转字符串的方法:

代码语言:csharp复制
public static class StringExtensions
{
  public static string Reverse(this string input)
  {
      char[] charArray = input.ToCharArray();
      Array.Reverse(charArray);
      return new string(charArray);
  }
}

在上面的例子中,StringExtensions 类是一个静态类,而 Reverse 方法是一个扩展方法。通过使用 this 关键字,我们将 Reverse 方法关联到 string 类型上。

使用扩展方法的示例:

代码语言:csharp复制
string original = "Hello";
string reversed = original.Reverse();
Console.WriteLine(reversed); // 输出:olleH

这里 Reverse 方法就好像是 string 类的一个原生方法一样。需要注意,扩展方法只是在语法上的扩展,它并没有真正修改原始类的定义。

5. byte a =255;a =5; a 的值是多少?

在C#中,当你对byte类型的变量进行算术运算,而结果超过了byte类型能够表示的最大值(即255)时,就会发生溢出。溢出的行为取决于它发生的上下文。

代码语言:c#复制
byte a = 255;
a  = 5;

变量a的初始值是255,当你给它加上5时,结果是260。然而,byte类型只能表示0到255的值,因此发生了溢出。

在C#中,默认情况下,对于溢出的处理方式是在相同类型的变量上进行赋值时,会将溢出的部分进行包装(wrap around)。这意味着如果超过了byte的最大值,它会回到最小值。因此,在你的例子中,结果是:

代码语言:c#复制
260 % 256 = 4

因此,a最终的值将为4。

为了避免由于溢出而导致意外行为,你可以使用checked关键字来启用溢出检查。例如:

代码语言:c#复制
checked
{
    byte a = 255;
    a  = 5; // 这会抛出 System.OverflowException 异常
}

通过使用checked,如果发生溢出,就会抛出异常,允许你在代码中明确处理这种情况。

6. 什么是装箱和拆箱?

装箱(Boxing)和拆箱(Unboxing)是与值类型和引用类型之间转换相关的概念。

装箱(Boxing):

装箱是将值类型(例如 int, char, double 等)转换为对应的引用类型(通常是 object 类型或接口类型)的过程。

代码语言:c#复制
当将值类型装箱时,会在堆内存中创建一个对象,将值类型的值复制到这个对象中,并返回对象的引用。

装箱发生在需要将值类型赋值给引用类型的情况下,或者当值类型需要作为参数传递给接受 object 类型的方法时。

示例:

代码语言:c#复制
int intValue = 42;
object boxedValue = intValue; // 装箱发生在这里

拆箱(Unboxing):

拆箱是将装箱过的引用类型转换回原始值类型的过程。

代码语言:c#复制
当需要从引用类型中获取值类型的值时,需要进行拆箱。拆箱将引用类型中的值复制到一个新的值类型变量中。

拆箱发生在将装箱后的对象赋值给值类型变量的情况下,或者当从 object 类型中获取值类型的值时。

示例:

代码语言:csharp复制
object boxedValue = 42;
int intValue = (int)boxedValue; // 拆箱发生在这里

需要注意的是,装箱和拆箱都涉及到数据的复制,因此可能会带来一些性能开销。在高性能要求的情况下,可以考虑避免不必要的装箱和拆箱操作。 C# 中的泛型和 Nullable 类型等机制有助于减少这些操作的需求。

7. 值类型和引用类型的区别

值类型的例子包括整数类型(如 int、float、double)、字符类型、枚举类型和结构体等。

引用类型的例子包括类、接口、委托和数组等。

代码语言:c#复制
内存位置: 值类型数据存储在栈上,引用类型的引用存储在栈上,但实际数据存储在堆上。

复制行为: 值类型的赋值是对实际数据的复制(默认是0),而引用类型的赋值是对引用的复制(默认是null)。

传递方式: 值类型在传递时是按值传递,引用类型在传递时是按引用传递。但需要注意,引用类型的引用本身也是值,传递引用时是按引用的值传递。

生存期: 值类型的生存期通常与其声明的作用域相同,而引用类型的生存期可能会更长,直到没有任何引用指向它时才会被垃圾回收。

在使用值类型和引用类型时,需要根据具体的需求和性能考虑选择合适的类型。值类型通常更轻量,而引用类型更灵活,具有动态分配内存的能力。

8. new 关键字的作用?

实例化对象:

使用 new 关键字可以创建一个类的实例,即对象。通过调用类的构造函数来初始化对象,并返回对新创建对象的引用。

代码语言:c#复制
MyClass myObject = new MyClass();

方法的重写(Override):

在派生类中,new 关键字可以用于隐藏基类中的成员,尤其是在派生类中重新定义一个与基类中的成员同名的成员。这被称为方法的重写或隐藏。

代码语言:c#复制
class BaseClass
{
  public void Display()
  {
      Console.WriteLine("BaseClass Display");
  }
}
class DerivedClass : BaseClass
{
  public new void Display()
  {
      Console.WriteLine("DerivedClass Display");
  }
}

隐藏字段或属性:

在派生类中使用 new 关键字也可以隐藏基类中的字段或属性。这样,在派生类中可以定义一个与基类中同名但不同类型的字段或属性。

代码语言:csharp复制
class BaseClass
{
  public int Number = 42;
}
class DerivedClass : BaseClass
{
  public new string Number = "Hello";
}

在泛型类型中的实例化:

在泛型类型中,new 关键字用于创建具体类型的实例。在泛型类型参数需要具体类型时,可以使用 new 来创建实例。

代码语言:c#复制
public class MyGenericClass<T> where T : new()
{
  public T CreateInstance()
  {
      return new T();
  }
}

总的来说,new 关键字在C#中用于创建对象、方法的重写、字段或属性的隐藏以及在泛型类型中实例化。其具体行为取决于它在代码中的上下文。

9. int? 和 int 有什么区别?
代码语言:c#复制
int? 为可空类型,默认值是null,它允许具有正常整数值或者 null。在需要表示缺失或未知值的情况下,可以使用 int?

int 是值类型,不允许为 null,默认值是0,它总是有一个具体的整数值

int?是通过int装箱为引用类型实现
10. C# 中的委托是什么?
代码语言:c#复制
委托(Delegate)是一种类型,它表示对一个或多个方法的引用。委托可以看作是函数指针的一种类型安全的封装,它允许将方法作为参数传递给其他方法,或者将方法赋值给委托变量。

.net中有很多内置的委托类型,如Action和Func,它们分别用于表示无返回值的方法和有返回值的方法。这些内置委托类型在泛型和非泛型形式中都可用

作用:提高方法的扩展性
11. 用最有效的方法算出 2 乘以 8 等于几?

采用位运算符

代码语言:csharp复制
在二进制中,左移运算符 `<<` 是将二进制数向左移动指定的位数,而乘以2的n次方的效果可以通过左移n位来实现。

因为8等于2的三次方,所以2乘以8可以通过将2左移3位来实现。这就是为什么 `2 << 3` 的结果是16。

在二进制中,左移运算符 `<<` 是将二进制数向左移动指定的位数。对于整数2,它的二进制表示是 `0000 0010`。

如果我们将2左移3位,即 `2 << 3`,那么这个操作将会将整数2的二进制数 `0000 0010` 向左移动3位,得到 `0001 0000`。

而 `0001 0000` 对应的十进制数就是16。

所以,`2 << 3` 确实等同于2乘以8,或者说是2的3次方。这是因为左移3位就是将原数乘以2的n次方,其中n是左移的位数。

或者直接乘法运算符

代码语言:c#复制
int result = 2 * 8;
12. const 和 readonly 有什么区别?

都用于定义常量。主要有以下区别:

代码语言:c#复制
1、初始化位置不同。const必须在声明的同时赋值;readonly既可以在声明处赋值,也可以在静态构造方法(必须是静态构造方法,普通构造方法不行)里赋值。

2、修饰对象不同。const即可以修饰类的字段,也可以修饰局部变量;readonly只能修饰类的字段

3、const是编译时常量,在编译时确定该值;readonly是运行时常量,在运行时确定该值。

4、const默认是静态的;而readonly如果设置成静态需要显示声明

5、修饰引用类型时不同,const只能修饰string或值为null的其他引用类型;readonly可以是任何类型。
13. 现有一个整数 number,请写一个方法判断这个整数是否是 2 的 N 次方

取模运算: 用number%2==0

位运算:可以通过位运算来判断一个整数是否是2的N次方。如果一个整数是2的N次方,那么它的二进制表示中只有一个1,例如,2、4、8、16等。

代码语言:c#复制
public class Solution
{
  public bool IsPowerOfTwo(int number)
  {
      // 判断是否为正数且只有一个1
      return number > 0 && (number & (number - 1)) == 0;
  }
}

这个方法的核心是使用位运算 (number & (number - 1)) == 0。如果一个数是2的N次方,那么它的二进制表示中只有一个1,而且将这个数减去1后,所有的低位都会变为1,高位不变。因此,它们的按位与操作结果应该为0。

14. CTS、CLS、CLR 分别作何解释

CTS(Common Type System):

代码语言:csharp复制
CTS 是.NET平台中所有编程语言都必须遵循的规范,它定义了一组公共的数据类型和规则,以确保不同语言之间的互操作性。这意味着不同的编程语言可以使用相同的数据类型,从而实现相互通信和交互。

CLS(Common Language Specification):

代码语言:csharp复制
CLS 是定义在CTS上的一组规范,目的是确保.NET编程语言之间的互操作性。CLS规范包含一组规则,要求支持CLS的编程语言应该使用CTS定义的类型,并且必须遵循一些规则,以便其他语言也能够使用这些类型。如果一个程序集符合CLS,那么它可以被任何CLS兼容的语言使用。

CLR(Common Language Runtime):

代码语言:csharp复制
CLR 是.NET平台的执行环境,负责管理和执行.NET程序。它提供了许多关键的服务,包括内存管理、垃圾回收、线程管理、安全性、代码访问安全性等。CLR还负责将中间语言(IL,Intermediate Language)编译成本地机器代码,并执行程序集中的方法。CLR是.NET平台的核心组件,为不同语言提供了一个公共的执行环境。

总的来说,CTS定义了.NET平台中的数据类型和规则,CLS确保编程语言之间的互操作性,而CLR则负责运行和管理.NET程序。这三者共同构成了.NET平台的基础,使得不同语言能够在相同的运行时环境中协同工作。

15. 在 .net 中,配件的意思是?

"配件"(Assembly)通常指代一个可执行文件或一个代码库,其中包含有关.NET应用程序的信息。配件是.NET的基本构建单元,它可以包含一个或多个模块,而每个模块可以包含一个或多个类型(类、接口等)。

代码语言:csharp复制
可执行文件、程序集信息、模块、程序集清单、部署和版本控制、GAC(Global Assembly Cache)
16. 分析下面代码,a、b 的值是多少?
代码语言:csharp复制
string strTmp = "a1某某某";
int a = System.Text.Encoding.Default.GetBytes(strTmp).Length;
int b = strTmp.Length;

分析:在UTF-8编码下,每个中文字符通常占用3个字节,而每个英文字符和数字占用1个字节。

代码语言:csharp复制
a = 11(1 1 3*3), b = 5
17. Strings = new String(“xyz”); 创建了几个 String Object?

这段代码实际上会创建一个字符串对象,其中每个字符都是从提供的字符串中复制的,但是因为 string 对象本身是不可变的,所以这样的使用方式并不常见。

通常,我们直接使用字符串字面量或通过其他方法创建字符串,而不需要使用 new string 构造函数。例如:

代码语言:c#复制
string s = "xyz"; // 使用字符串字面量

或者如果你有字符数组,可以使用:

代码语言:c#复制
char[] charArray = { 'x', 'y', 'z' };
string s = new string(charArray);
18. 静态成员和非静态成员的区别

实例化:

代码语言:csharp复制
静态成员属于类,而不属于类的实例。可以通过类名直接访问静态成员,而不需要创建类的实例。
非静态成员属于类的实例。要访问非静态成员,需要先创建类的实例,然后通过实例来访问成员。

内存分配:

代码语言:csharp复制
静态成员在程序启动时就分配内存,并且在程序结束时释放。它们的生命周期与应用程序的生命周期相同。
非静态成员在创建类的实例时分配内存,并在实例被销毁时释放。它们的生命周期与实例的生命周期相同。

访问方式:

代码语言:csharp复制
静态成员可以通过类名直接访问,也可以通过实例访问。但强烈建议使用类名来访问静态成员,以明确它们的静态性。
非静态成员只能通过实例访问。

this 关键字:

代码语言:csharp复制
静态成员中不能使用 this 关键字,因为它们不属于实例。
非静态成员中可以使用 this 关键字引用当前实例。

使用场景:

代码语言:csharp复制
静态成员通常用于表示与整个类相关的数据或功能,例如共享的计数器、工厂方法等。
非静态成员通常用于表示实例特有的数据或功能,每个实例都有独立的值。

示例:

代码语言:c#复制
public class MyClass
{
  public static int StaticMember = 42; // 静态成员
  public int InstanceMember = 10;   // 非静态成员
  public static void StaticMethod()
  {
      // 静态方法
      // 不能使用 this 关键字
      Console.WriteLine("Static method");
  }
  public void InstanceMethod()
  {
      // 非静态方法
      // 可以使用 this 关键字
      Console.WriteLine("Instance method");
  }
}

总的来说,静态成员与类关联,非静态成员与类的实例关联。选择使用静态或非静态成员取决于成员的用途和数据的共享需求。

19. c# 可否对内存直接操作

C#在<u>unsafe</u>模式下可以使用指针对内存进行操作, 但在托管模式下不可以使用指针,C#NET默认不运行带指针的,需要设置下,选择项目右键->属性->生成->常规->“不安全代码-允许适应"unsafe”关键字编译的代码,打勾->保存

代码语言:c#复制
class Program
{
  static unsafe void Main()
  {
      int value = 42;
      // 使用 unsafe 关键字和指针来直接修改内存
      int* pointer = &value;
      *pointer = 99;
      Console.WriteLine(value); // 输出: 99
  }
}

需要注意的是,使用指针直接操作内存时存在一些潜在的风险和安全性问题。因此,除非在特殊情况下确实需要对内存进行底层操作,否则应该避免使用 unsafe 关键字。对于普通的应用程序和开发任务,推荐使用C#的高级特性(垃圾回收机制和类型安全)和标准库来进行内存管理,以确保代码的安全性和可维护性。

20. short s1 = 1; s1 = s1 1; 有什么错? short s1 = 1; s1 = 1; 有什么错?
代码语言:csharp复制
s1 = s1   1; 会引起编译错误,需要显式类型转换。
s1  = 1; 不会引起编译错误,编译器会自动进行类型转换
21. 什么是强类型,什么是弱类型?哪种更好些?为什么?

强类型(Strongly Typed):

代码语言:csharp复制
强类型语言在编译时或运行时对变量的类型进行严格检查。即使在进行简单的操作时,也要确保变量的类型是一致的,否则会引发类型错误。
C#、Java、C   等是强类型语言的代表。

弱类型(Weakly Typed):

代码语言:csharp复制
弱类型语言对变量的类型检查较为宽松,允许在一定程度上进行自动类型转换。在弱类型语言中,同一个变量可以在不同的上下文中被赋予不同的类型。
JavaScript、Python 等是弱类型语言的代表。

哪种更好一些?为什么?

代码语言:csharp复制
这取决于具体的应用场景和个人偏好,没有一种类型系统能够满足所有需求。以下是一些考虑因素:

类型安全性:
强类型语言在编译时或运行时能够提供更高的类型安全性,可以在很早的阶段捕获类型错误,减少潜在的运行时错误。

代码可读性和维护性:
强类型语言通常更易读懂,因为类型信息对于理解代码非常重要。类型信息使得代码更加自文档化,提高了代码的可维护性。

开发效率:
弱类型语言可能在某些情况下具有更大的灵活性,允许更快地编写和测试代码。这可以提高开发效率,但也增加了在运行时发现错误的风险。

安全性:
强类型语言通常在类型检查方面更为严格,有助于防止一些常见的安全漏洞,如类型转换错误。

总体来说,强类型语言在大型项目和对类型安全性要求较高的场景中通常更受青睐。但在某些情况下,弱类型语言的灵活性可能更适用于快速原型开发或某些领域,因此没有绝对的优劣之分,而是要根据具体的需求和团队背景进行选择。

22. using 关键字的作用

命名空间引用:

代码语言:c#复制
// using 用于引入命名空间,以便在代码中使用其中定义的类型而不需要使用完全限定的类型名。
using System;
namespace MyNamespace
{
  class MyClass
  {
      static void Main()
      {
          Console.WriteLine("Hello, World!");
      }
  }
}

别名引用:

代码语言:c#复制
// using 还可以用于为类型或命名空间创建别名,以解决命名冲突或简化类型名的使用。
using MyAlias = MyNamespace.MyClass;
namespace AnotherNamespace
{
  class AnotherClass
  {
      MyAlias myObject = new MyAlias();
  }
}

资源管理(IDisposable 接口):

代码语言:c#复制
// using 语句还用于资源管理,特别是实现了 IDisposable 接口的类型。在 using 块中创建的对象会在块结束时自动调用 Dispose 方法,以确保资源被正确释放。
using (MyDisposableObject myObject = new MyDisposableObject())
{
    // 使用 myObject
} // 在这里,myObject 的 Dispose 方法被调用

总的来说,using 关键字用于<u>引入命名空间、创建别名和资源管理</u>。在不同的上下文中,它提供了一种方便和简洁的方式来管理代码中的命名空间、类型和资源。

23. ref 和 out 有什么区别

ref 和 out 都是在C#中用于参数传递的关键字,它们有一些区别,主要体现在以下几个方面:

初始值:

代码语言:c#复制
ref 关键字要求在传递给方法之前必须先为变量赋初值,即在方法调用前必须对变量进行初始化。
out 关键字不要求在传递给方法之前为变量赋初值,但在方法内部必须确保在使用该参数之前赋值。
// ref 的示例
int x = 10;
MyMethod(ref x); // 必须在调用前为 x 赋值
// out 的示例
int y;
MyMethod(out y); // 不需要在调用前为 y 赋值

方法内部对参数的要求:

代码语言:c#复制
ref 关键字在方法内部不要求对变量重新赋值,但可以在方法内对其进行修改。
out 关键字在方法内部要求对变量重新赋值,因为方法内部不能使用未赋值的 out 参数。
// ref 的示例
void MyMethod(ref int a)
{
  // 可以修改 a 的值
  a = a   1;
}
// out 的示例
void MyMethod(out int b)
{
  // 必须在方法内部为 b 赋值
  b = 42;
}

方法调用时的要求:

代码语言:c#复制
在方法调用时,ref 和 out 关键字都要求在实参和形参上都使用相同的修饰符。
// ref 的示例
int x = 10;
MyMethod(ref x);
// out 的示例
int y;
MyMethod(out y);

总的来说,ref 和 out 都允许将参数的引用传递给方法,但它们在对初始值和在方法内部的要求上有所不同。选择使用哪个关键字取决于具体的需求和设计。通常,如果方法需要从参数中获取值并可能对其进行修改,可以使用 ref;如果方法只需要返回值,并且不关心参数的初始值,可以使用 out。

24. a.Equals(b) 和 a == b 一样吗?

a.Equals(b):

代码语言:c#复制
Equals 方法是从 System.Object 类继承而来的,因此对于所有类型都是可用的。
默认情况下,Equals 方法执行的是引用比较,即检查两个对象是否引用同一个内存位置。子类可以重写 Equals 方法以提供自定义的相等性比较。

object a = new object();
object b = a;
bool result = a.Equals(b); // 引用比较

a == b:

代码语言:c#复制
== 操作符的行为取决于具体的类型。对于引用类型,== 执行的是引用比较,与 Object.ReferenceEquals 方法的行为相同。
对于值类型,== 操作符通常执行值比较,即比较两个对象的值是否相等。但某些值类型可以通过重写 == 操作符来改变这种行为。

object a = new object();
object b = a;
bool result = (a == b); // 引用比较

需要注意的是,对于自定义类型,如果没有重写 Equals 方法和 == 操作符,它们将默认执行引用比较,即比较对象的引用是否相同。

在一些常见的值类型(如 int、double 等)和字符串类型上,== 操作符通常执行值比较,而不是引用比较。但对于自定义类型,特别是引用类型,最好重写 Equals 方法以提供有意义的相等性比较。

25. 下面这段代码求值
代码语言:c#复制
class Class1
{
    internal static int count = 0;
    static Class1()
    {
        count  ;
    }
    public Class1()
    {
        count  ;
    }
}
Class1 o1 = new Class1();
Class1 o2 = new Class1();

o1.count的值是多少?

代码语言:csharp复制
在这个示例中,`count` 是一个静态字段,它被所有类实例共享。在静态构造函数 `static Class1()` 中,`count` 被增加了一次。此外,在每个对象的构造函数 `public Class1()` 中,`count` 又被增加了一次。

因为你创建了两个 `Class1` 对象 `o1` 和 `o2`,所以静态构造函数 `static Class1()` 会在类的第一个实例被创建时调用,而普通构造函数 `public Class1()` 在每个对象创建时都会调用。

因此,在创建 `o1` 的时候,`count` 增加了两次(一次来自静态构造函数,一次来自普通构造函数)。而在创建 `o2` 的时候,静态构造函数不会再被调用,只有普通构造函数会增加 `count`,所以 

o1.count 的值是 2,o2.count 的值是 3

26. 关于构造函数说法正确的是哪个?

a) 构造函数可以声明返回类型。

b) 构造函数不可以用private修饰

c) 构造函数必须与类名相同

d) 构造函数不能带参数

代码语言:c#复制
答案:c
构造函数的名称必须与包含它的类的名称完全相同。这是构造函数的标准命名规则。其他选项是不正确的

a) 构造函数不可以声明返回类型。构造函数没有返回类型,甚至不能声明 void。
b) 构造函数可以使用 private 修饰符。例如,私有构造函数常用于实现单例模式或工厂模式。
d) 构造函数可以带参数。带参数的构造函数允许在创建对象时传递初始值,以便对对象进行初始化。
27. Math.Round(11.5) 等于多少? Math.Round(-11.5) 等于多少?
代码语言:c#复制
在C#中,Math.Round 方法用于将浮点数舍入到最接近的整数。对于包含 .5 的情况,它遵循一种特定的规则,称为"银行家舍入"规则。
Math.Round(11.5) = 12
Math.Round(-11.5) = -12
28. &和 && 的区别

& 是按位与运算符,同时也可用于逻辑与操作,但不会短路。

&& 是逻辑与运算符,具有短路的特性。

代码语言:c#复制
& 运算符:
& 是按位与运算符,用于对整数类型的相应位执行位与操作。
在逻辑上,& 也可用于执行逻辑与操作,但与 && 不同,& 会对两侧的操作数都进行求值,而不会短路。
例如,if (condition1 & condition2),无论 condition1 是否为 false,condition2 都会被求值。
&& 运算符:
&& 是逻辑与运算符,用于执行逻辑与操作。
&& 具有短路的特性,即如果第一个条件为 false,则不会对第二个条件进行求值。
例如,if (condition1 && condition2),如果 condition1 为 false,则不会执行 condition2 的求值。
示例:
int a = 5;
int b = 10;

// 使用 & 进行按位与运算
int result1 = a & b; // 结果是 0b0000 (0)

// 使用 && 进行逻辑与运算
bool condition1 = (a > 0) && (b > 0); // 结果是 true

// 使用 & 进行逻辑与运算,不会短路
bool condition2 = (a > 0) & (b > 0); // 结果是 true,即使第一个条件为 false,仍然会对第二个条件求值
29. i 和 i 有什么区别?

i 和 i 都是用于递增变量的操作符,它们的区别在于它们返回的值和执行顺序。

i (后增量):

代码语言:c#复制
i   表示使用变量的当前值,然后再将变量递增。
返回的值是变量的当前值,然后变量会递增。
后增量表示先使用当前值再递增。

int i = 5;
int result = i  ; // result的值是5,i的值变为6

i(前增量):

代码语言:c#复制
  i 表示先将变量递增,然后再使用递增后的值。
返回的值是递增后的变量值。
前增量表示先递增再使用。

int i = 5;
int result =   i; // result的值是6,i的值也变为6

在实际使用中,这两种形式的选择通常取决于具体的需求。如果你希望先使用当前值再递增,可以使用后增量(i )。如果你希望先递增再使用递增后的值,可以使用前增量( i)

30. as 和 is 的区别

在C#中,as 和 is 是用于处理类型转换和类型检查的两个不同的运算符。

as 运算符:

代码语言:c#复制
as 运算符用于将对象转换为指定类型,如果转换失败则返回 null,而不会引发异常。
通常用于在不确定对象类型时进行类型转换,如果转换成功,得到一个非空值,否则得到 null。

object obj = "Hello";
string str = obj as string;
if (str != null)
{
  // 转换成功
  Console.WriteLine("Conversion successful: "   str);
}
else
{
  // 转换失败
  Console.WriteLine("Conversion failed");
}

is 运算符:

代码语言:c#复制
is 运算符用于检查对象是否是指定类型的实例,返回一个布尔值。
不执行实际的类型转换,只是检查对象的类型。

object obj = "Hello";
if (obj is string)
{
  // 是字符串类型
  Console.WriteLine("Object is a string");
}
else
{
  // 不是字符串类型
  Console.WriteLine("Object is not a string");
}

总的来说,as 用于尝试进行类型转换,而 is 用于检查对象是否是指定类型的实例,而不进行实际的类型转换。使用它们时需要根据具体的需求来选择。

本系列文章题目摘自网络,答案重新梳理

0 人点赞