C#中一些易混淆概念总结(三)--------结构,GC回收,静态成员,静态类

简介:

目录:

【C#小知识】C#中一些易混淆概念总结

【C#小知识】C#中一些易混淆概念总结(二)

---------------------------------------分割线----------------------------------------------

一,C#中结构

在C#中可以使用struct关键字来定义一个结构,级别与类是一致的,写在命名空间下面。

1)结构中可以定义属性,字段,方法和构造函数。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//定义结构
     struct  Point
     {
                                                                                        
         //定义字段
         private  int  x;
         //封装字段
         public  int  X
         {
             get  return  x; }
             set  { x = value; }
         }
                                                                                         
         //定义方法
         public  void  Result()
         {
                                                                                             
         }
         //定义构造函数
         public  Point( int  n)
         {
             this .x = n;
             //Console.WriteLine(n);
         }
     }


那么,声明类与结构的区别有哪些呢?

无论如何,C#编译器都会为结构生成无参数的构造函数;

当我们显式的定义无参数的构造函数,编译时会报错,结果如下:

031544589875991.png

编译器告诉我们,结构不能包含显式的无参数的构造函数

但是这样编写代码时,编译器却不报错,代码如下:

//这里可以调用无参数的构造函数

Point p = new Point();            

Console.WriteLine(p.GetType());

运行结果如下:

031547574246043.png

虽然结构不能显式的声明无参数的构造函数,但是程序员却可以显式的调用结构的无参数的构造函数,说明C#编译器无论如何都会为结构生成无参数的构造函数。

②结构中的字段不能赋初始值;

031605364403942.png

③在结构的构造函数中必须要对结构体的每一个字段赋值;

当我们不声明显式的构造函数时,可以不对成员字段赋值,但是一旦声明了构造函数,就要对所有的成员字段赋值

031558338628941.png

对所有的成员字段赋值,代码如下:

     //定义构造函数

public Point(int n)        

{            

   this.x = n;            

   //Console.WriteLine(n);

}

④在构造函数中对属性赋值不认为对字段赋值,属性不一定去操作字段;

031613540498785.png

所以在构造函数中我们对字段赋初始值的时候,正确的代码应该是

1
2
3
4
5
6
7
8
9
//定义构造函数
         public  Point( int  n)
         {
             //正确的可以对字段赋初始值
             this .x = n;
             //在构造函数中对属性赋值,但是不一定操作字段
             this .X = n;
             //Console.WriteLine(n);
         }


2)结构体的数值类型问题

C#中的结构是值类型,它的对象和成员字段是分配在栈中的,如下图:

031627599561858.png

那么当我们写了如下的代码,内存中发生了什么呢?

1
2
3
4
5
6
//这里可以调用无参数的构造函数
           Point p =  new  Point();
           //为p的属性赋值
           p.X = 100;
           //将p赋值给Point新的对象p1
           Point p1 = p;


Point p1=p发生了什么呢?情况如下:

031641484876969.png

声明结构体对象可以不使用“new”关键字如果不使用“new”关键字声明结构体对象,因为没有调用构造函数,这个时候结构体对象是没有值的。而结构的构造函数必须为结构的所有字段赋值,所以通过"new"关键字创建结构体对象的时候,这个对象被构造函数初始化就有默认的初始值了。实例代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class  Program
     {
         static  void  Main( string [] args)
         {
            //没有办法调用默认的构造函初始化
             Point p;
             Console.WriteLine(p);
             //会调用默认的构造函数对的Point对象初始化
             Point p1 =  new  Point();
             Console.WriteLine(p1);
             Console.ReadKey();
         }
     }
     //定义结构
     struct  Point
     {
         //定义时赋初始值,编译器会报错
         private  int  x;
     }


编译的时候会报错:

031809397683663.png


3)结构体不能使用自动属性

在第一篇文章我写自动属性的时候,反编译源代码,知道自动属性,会生成一个默认字段。而在结构的构造函数中需要对每一个字段赋值,但是编译器不知道这个字段的名字。所以,没有办法使用自动属性。


那么什么时候定义类,什么时候定义结构体呢?

首先我们都知道的是,栈的访问速度相对于堆是比较快的。但是栈的空间相对于堆来说是比较小的。

①当我们要表示一个轻量级的对象,就可以定义结构体,提高访问速度。

②根据传值的影响来选择,当要传递的引用就定义类,当要传递的是“拷贝”就定义结构体。

二,关于GC(.NET的垃圾回收)

1)分配在栈中的空间变量,一旦出了该变量的作用域就会被CLR立即回收;如下代码:

1
2
3
4
5
6
//定义值类型的n当,程序出了main函数后n在栈中占用的空间就会被CLR立即回收
         static  void  Main( string [] args)
         {
             int  n = 5;
             Console.WriteLine(n);
         }

2)分配在堆里面的对象,当没有任何变量的引用时,这个对象就会被标记为垃圾对象,等待垃圾回收器的回收;

GC会定时清理堆空间中的垃圾对象,这个时间频率是程序员无法控制的,是由CLR决定的。所以,当一个对象被标记为垃圾对象的时候,不一定会被立即回收。

3)析构函数

在回收垃圾对象的时候,析构函数被GC自动调用。主要是执行一些清理善后工作。

析构函数没有访问修饰符,不能有你参数,使用“~”来修饰。 如下面的代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class  Program
     {
         //定义值类型的n当,程序出了main函数后n在栈中占用的空间就会被CLR立即回收
         static  void  Main( string [] args)
         {
             int  n = 5;
             OperateFile operate =  new  OperateFile();
             operate.FileWrite();
             //执行完写操作后,会调用该类的析构函数,释放对文件对象的控制
             //Console.WriteLine(n);
         }
     }
     //定义操作硬盘上文件上的类
     class  OperateFile
     {
         //定义写文件的方法
         public  void  FileWrite()
         { }
         //定义调用该类结束后,所要执行的动作
         ~OperateFile()
         {
         //释放对操作文件对象的控制
         }
     }


三,静态成员和实例成员的区别:

静态成员是需要通过static关键字来修饰的,而实例成员不用static关键字修饰。他们区别如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class  Program
     {
         static  void  Main( string [] args)
         {
             //静态成员属于类,可以直接通过“类名.静态成员”的方式访问
             Person.Run();
             //实例成员属于对象,需要通过“对象名.实例成员”来访问
             Person p =  new  Person();
             p.Sing();
         }
     }
     class  Person
     {
         //静态成员变量
         private  static  int  nAge;
         //实例成员变量
         private  string  strName;
         public  static  void  Run()
         {
             Console.WriteLine( "我会奔跑!" );
         }
         public  void  Sing()
         {
             Console.WriteLine( "我会唱歌" );
         }
     }

当类第一次被加载的时候(就是该类第一次被加载到内存当中),该类下面的所有静态的成员都会被加载。实例成员有多少对象,就会创建多少对象。

而静态成员只被加载到静态存储区,只被创建一次,且直到程序退出时才会被释放。

看下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class  Program
     {
         static  void  Main( string [] args)
         {
             Person p =  new  Person();
             Person p1 =  new  Person();
             Person p2 =  new  Person();
         }
     }
                                       
     class  Person
     {
         //静态成员变量
         private  static  int  nAge;
         //实例成员变量
         private  string  strName;
         public  static  void  Run()
         {
             Console.WriteLine( "我会奔跑!" );
         }
         public  void  Sing()
         {
             Console.WriteLine( "我会唱歌" );
         }
     }

那么在内存中发生了什么呢?如下图:

031742396128096.png


由上面显然可知,定义静态的成员是可以影响程序的执行效率的。那么什么时候定义静态的成员变量呢?

①变量需要被共享的时候②方法需要被反复的调用的时候

2)在静态方法中不能直接调用实例成员。

当类第一次被加载的时候,静态成员已经被加载到静态存储区,此时类的对象还有可能能没有创建,所以静态方法中不能调用类成员字段。实例代码如下:

031842376121621.png


this和base关键字都不能在静态方法中使用。

②可以创建类的对象指明对象的成员在静态方法中操作,代码如下:

1
2
3
4
5
6
public  static  void  Run()
        {
            Person p =  new  Person();
            p.strName =  "强子" ;
            Console.WriteLine( "我会奔跑!" );
        }

③在实例成员中肯定可以调用静态方法,因为这个时候静态成员肯定存在,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public  static  void  Run()
         {
             Person p =  new  Person();
             p.strName =  "强子" ;
             Console.WriteLine( "我会奔跑!" );
         }
         public  void  Sing()
         {
             //实例方法被调用的时候,对象实例一定会被创建,所以可以在实例方法中访问实例的字段
             this .strName =  "子强" ;
             strName =  "子强" ;
             //调用静态成员
             Run();
             Console.WriteLine( "我会唱歌" );
         }

静态成员和实例成员的对比:

①生命周期不一样

静态成员只有在程序结束时才会释放,而实例成员没有对象引用时就会释放

②内存中存储的位置不一样

静态成员存放在静态存储区,实例成员在托管堆中。


四,静态类

①静态类被static关键字修饰

//定义两个静态类

staticclass Person    { }  

internalstaticclass Cat    { }


②静态类中只能生命静态的成员变量,否则会报错(因为访问该实例成员的时候,类的对象可能还没有被创建

031856556274126.png

③静态类中不能有实例的构造函数(如果有实例的构造函数,则该静态类能被实例化,都是静态成员,没有实例成员被调用

031859596909959.png


正确的声明方法:

1
2
3
4
5
6
7
8
static  class  Person
     {
         //private int nAge;
         private  static  string  strName;
         static  Person()
         {
         }
     }


④静态类不能被继承,反编译刚才的两个类,结果如下:

031904030341551.png


会发现静态类的本质是一个抽象密封类,所以不能被继承和实例化。所以,静态类的构造函数,不能有访问修饰符


2)那么什么时候声明静态类呢?

如果这个类下面的所有成员的都需要被共享,可以把这个类声明为静态类。

且在一般对象中不能声明静态类型的变量(访问该静态变量时,可能该对象还没有被创建)。

031915217521432.png


3)静态类的构造函数

静态类可以有静态的构造函数(且所有类都可以有静态的构造函数),如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class  Program
     {
         static  void  Main( string [] args)
         {
             Cat c;
             Cat c1 =  new  Cat();
             Console.ReadKey();
         }
     }
     class  Cat
     {
         private  int  n;
         public  string  strName;
         //实例构造函数
         public  Cat()
         {
             Console.WriteLine( "看谁先执行2" );
         }
         //静态构造函数
         static  Cat()
         {
             Console.WriteLine( "看谁先执行1" );
         }
     }

执行结果如下:

031923479246325.png


由此我们可以知道,静态的构造函数会先于实例构造函数执行

//不执行静态构造函数

Cat c;

当我们在Main()函数中添加如下的代码是:

1
2
3
4
5
6
7
8
static  void  Main( string [] args)
         {
             //不执行静态构造函数
             Cat c;
             Cat c1 =  new  Cat();
             Cat c2 =  new  Cat();
             Console.ReadKey();
         }

运行结果如下:

031928535186698.png


说明静态的构造函数只执行了一次。

---------------------------------------------分割线-----------------------------------------------


好吧这次的分享风到此结束。希望对大家对理解C#基础概念知识能有所帮助。








     本文转自yisuowushinian 51CTO博客,原文链接:http://blog.51cto.com/yisuowushinian/1355864,如需转载请自行联系原作者


相关文章
C#的基本语法结构学习案例详解
C#的基本语法结构学习案例详解
71 0
|
2月前
|
存储 安全 编译器
学懂C#编程:属性(Property)的概念定义及使用详解
通过深入理解和使用C#的属性,可以编写更清晰、简洁和高效的代码,为开发高质量的应用程序奠定基础。
116 12
|
9月前
|
C#
C#的基本语法结构学习
【5月更文挑战第17天】C#基础语法包括变量(如`int x = 10`)、常量(`const int MAX_VALUE = 100`)、运算符(如算术和比较运算符)、控制语句(if、for、while等)和函数声明(`int Add(int x, int y) { return x + y; }`)。这些构成C#程序的基本元素。
94 0
|
5月前
|
存储 C# 开发者
C#一分钟浅谈:静态成员与静态类介绍
在C#编程中,`static`关键字用于创建静态字段、方法、属性和构造函数等,这些成员属于类本身而非实例。本文从基本概念入手,详细探讨了静态成员与静态类的特点、应用场景及常见误区。通过示例代码展示了如何使用静态字段和方法,并讨论了静态类的定义及其在工具箱和常量集合中的应用。最后总结了正确使用这些特性的策略,以帮助开发者编写高效、可维护的代码。
100 11
|
5月前
|
C# 数据安全/隐私保护
C# 一分钟浅谈:类与对象的概念理解
【9月更文挑战第2天】本文从零开始详细介绍了C#中的类与对象概念。类作为一种自定义数据类型,定义了对象的属性和方法;对象则是类的实例,拥有独立的状态。通过具体代码示例,如定义 `Person` 类及其实例化过程,帮助读者更好地理解和应用这两个核心概念。此外,还总结了常见的问题及解决方法,为编写高质量的面向对象程序奠定基础。
59 2
|
9月前
|
C#
C#的类和对象的概念学习案例刨析
【5月更文挑战第17天】C#是一种面向对象的语言,以类和对象为核心。类作为对象的模板,定义了属性(如Name, Age)和行为(如Greet)。对象是类的实例,可设置属性值。封装通过访问修饰符隐藏实现细节,如Customer类的私有name字段通过Name属性访问。继承允许新类(如Employee)从现有类(Person)继承并扩展。多态让不同对象(如Circle, Square)共享相同接口(Shape),实现抽象方法Area,提供灵活的代码设计。
84 1
|
9月前
|
存储 C# 开发者
深入了解 C#编程的核心概念
【4月更文挑战第20天】C#是一种强大的面向对象编程语言,关键概念包括:面向对象(类、对象、继承、多态)、封装、数据类型、变量与常量、控制流、异常处理、委托和事件、泛型及集合。掌握这些概念并通过阅读代码、实践编写和学习新技巧行动,是成为熟练C#开发者的基石。了解并运用这些核心概念能提升代码效率、灵活性和可维护性。
94 3
|
9月前
|
SQL 存储 关系型数据库
C# .NET面试系列十:数据库概念知识
#### 1. 为什么要一定要设置主键? 设置主键是数据库设计中的一个重要概念,有几个主要原因: 1、唯一性 ```c# 主键必须保证表中的每一行都有唯一的标识。这样可以避免数据冗余和不一致性。如果没有主键或者主键不唯一,就可能出现数据混乱或错误。 ``` 2、查询性能 ```c# 数据库系统通常会使用主键来加速数据检索。主键通常会被索引,这样可以更快速地找到特定行的数据,提高查询效率。 ``` 3、关联性 ```c# 主键常常用于建立表与表之间的关系。在关系数据库中,一个表的主键通常与其他表中的外键建立关联,这种关系对于数据的一致性和完整性非常重要。 ``` 4、数据完
232 1
C# .NET面试系列十:数据库概念知识
C#基础Ⅱ-注释、快捷键、结构
C#基础Ⅱ-注释、快捷键、结构