zoukankan      html  css  js  c++  java
  • 【C#小知识】C#中一些易混淆概念总结(三)--------结构,GC回收,静态成员,静态类 分类: C# 2014-02-03 19:33 1238人阅读 评论(1) 收藏

    目录:

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

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

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

    一,C#中结构

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

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

    复制代码
    //定义结构
        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#编译器都会为结构生成无参数的构造函数;

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

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

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

     //这里可以调用无参数的构造函数
                Point p = new Point();
                Console.WriteLine(p.GetType());

    运行结果如下:

     

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

     

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

     

     

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

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

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

         //定义构造函数
            public Point(int n)
            {
                this.x = n;
                //Console.WriteLine(n);
            }

     

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

     

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

    复制代码
             //定义构造函数
            public Point(int n)
            {
                //正确的可以对字段赋初始值
                this.x = n;
    
                //在构造函数中对属性赋值,但是不一定操作字段
                this.X = n;
                //Console.WriteLine(n);
            } 
    复制代码

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

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

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

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

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

     

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

     

    复制代码
    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;
        }
    复制代码

     

    编译的时候会报错:

     

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

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

     

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

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

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

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

     

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

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

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

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

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

    3)析构函数

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

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

    复制代码
    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关键字修饰。他们区别如下代码:

     

    复制代码
    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("我会唱歌");
            }
        }
    复制代码

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

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

    看下面的代码:

    复制代码
    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("我会唱歌");
            }
        }
    复制代码

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

     

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

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

     

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

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

     

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

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

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

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

    复制代码
     public static void Run()
            {
                Person p = new Person();
                p.strName = "强子";
                Console.WriteLine("我会奔跑!");
            }
    
            public void Sing()
            {
                //实例方法被调用的时候,对象实例一定会被创建,所以可以在实例方法中访问实例的字段
                this.strName = "子强";
                strName = "子强";
    
    
                //调用静态成员
                Run();
                Console.WriteLine("我会唱歌");
            }
    复制代码

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

    ①生命周期不一样

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

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

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

     

    四,静态类

    ①静态类被static关键字修饰

     //定义两个静态类
        static class Person
        { }
    
        internal static class Cat
        { }

     

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

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

     

    正确的声明方法:

    复制代码
    
        static class Person
        {
            //private int nAge;
            private static string strName;
    
            static Person()
            { 
            }
        }
    复制代码

     

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

     

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

     

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

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

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

     

    3)静态类的构造函数

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

    复制代码
    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");
            }
    
        }
    复制代码

    执行结果如下:

     

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

                //不执行静态构造函数
                Cat c;

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

    复制代码
    static void Main(string[] args)
            {
                //不执行静态构造函数
                Cat c;
                Cat c1 = new Cat();
                Cat c2 = new Cat();
    
                Console.ReadKey();
            }
    复制代码

    运行结果如下:

     

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

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

     

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



    毕业实习交流群:221376964。你也可以关注我的新浪微博进行交流。

    版权声明:本文为博主原创文章,未经博主允许不得转载。

  • 相关阅读:
    Java虚拟机:十七、互斥同步、锁优化及synchronized和volatile
    Java虚拟机:十六、Java内存模型
    Java虚拟机:十五、运行期优化
    Java虚拟机:十三、Java类加载机制
    Java虚拟机:十四、类加载器
    Java虚拟机:十二、虚拟机性能监控与故障处理工具
    字符集
    发布订阅、事务和脚本
    HyperLogLog和GEO
    Redis 的数据结构
  • 原文地址:https://www.cnblogs.com/yisuowushinian/p/4715618.html
Copyright © 2011-2022 走看看