《第六章》——深入理解类

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34536551/article/details/83817545


静态成员的生命期


●  只有在实例创建之后才产生实例成员,在实例销毁后实例成员就不存在了。

● 但静态成员,即使类没有实例,也存在静态成员,并且可以访问。(在C++中不可以这样)

namespace Ch05Ex03
{
    class D
    {
        static public int Mem2;
    }
    class Program
    {
        static void Main(string[] args)
        {
            D.Mem2 = 5; //通过类名直接为公有静态数据成员赋值, 但是如果该成员是私有,就不可以这样赋值
            WriteLine(D.Mem2);
            ReadKey();
        }
    }
}

输出结果为:
5

注意: 静态成员即使没有类的实例也存在。如果该静态字段有初始化语句,那么会在使用该类的任何静态成员之前初始化该字段, 但没必要在程序执行的开始就初始化。


静态成员函数


● 如同静态字段,静态成员函数独立于任何类实例。 即使没有类的实例,仍然可以调用静态方法。(C++中不可以这样)

● 注意:类成员函数(无论是static成员函数或非static成员函数)都可以直接访问static数据成员,static 成员函数仅能访问static 数据成员,不能访问非static的数据成员,也不能访问非static 的成员函数。

namespace Ch05Ex03
{
    class D
    {

        static public int Mem2;
        public int aa = 10; //在类定义中初始化一个非静态的公有成员
        static public void PrintVal() //静态成员函数访问静态成员数据
        {
            WriteLine(Mem2);
            //WriteLine(aa); 静态成员不可以访问非静态数据成员
        }
        public void getVal()
        {
            WriteLine(aa);  // 输出aa的值
             WriteLine(Mem2); //非静态函数可以访问静态的数据成员
        }
    }
  
    class Program
    {
        static void Main(string[] args)
        {
            D.Mem2 = 5;
            D.PrintVal();

            D dd = new D(); // 创建该类的对象
            dd.getVal(); 
            ReadKey();
        }
    }
}

输出结果为:
5
10
5

在C++中可以声明常量为静态的常量成员数据, 而在C#中却不可以。

与C++不同,在C#中没有全局常量。 每个常量都必须声明在类型内。


常量与静态量


●  注意: 常量成员即使没有类的实例也可以使用。 与真正的静态量不同, 常量没有自己的存储位置, 而是在编译时被编译器替换。 跟C++中的 #define 类似。

看一个C++的程序:

class D
{

public:
	const double PI = 3.14;
	D(decltype(PI) aa):PI(aa)
	{
		
	}
};

int main()
{
	D dd(22.3);
	cout << dd.PI << endl;
	system("pause");
	return 0;
}

看一个C#程序:

namespace Ch05Ex03
{
    class D
    {
        public const double PI = 3.14;
       
    }
  
    class Program
    {
        static void Main(string[] args)
        {
            WriteLine(D.PI);   //访问静态数据成员
            ReadKey();
        }
    }
}

在C#中常量成字段现得像一个静态量, 但不能将常量字段声明成 static:

static const double PI = 3.14; //错误

但是在C++中可以。


属性


●   属性指的是一组两个匹配的、称为访问器的方法:

set 访问器为属性赋值
get 访问器为属性获取值

属性和字段不同的在于: 属性属于一个成员函数:

它不为数据存储分配内存

它执行代码

不过它也有跟字段类似的地方,比如:

它是命名的类成员。

它有类型

它可以被赋值和读取。

它类似于这样:

        int MyValue
        {
            set { }
            get
            {
                return 0;
            }
        }

注意: 访问器不能被直接调用。

●   set访问器总是:

拥有一个单独的、隐式的值参,名称为value, 与属性的类型相同。

set访问器返回类型为void。

●  get访问器总是:

没有参数。

拥有一个与属性类型相同的返回类型。

访问器的重点如下:

get 访问器的所有执行路径都必须包含一条return 语句,返回一个属性类型的值。

访问器 set 和 get 可以任何顺序声明, 并且只能有这两个方法,不能有其他的。
 

要想不定义属性的某个访问器,可以忽略该访问器的声明。

两个访问器中至少有一个必须定义, 否则编译器会产生错误信息。


使用属性


●  写入和读取属性的访问器时,会被隐式调用:

要写入一个属性,在赋值语句的左边使用属性的名称。

要读取一个属性,把属性的名称用在表达式中。

注意: 不能显式地调用访问器,会出现编译错误。

namespace Ch05Ex03
{
    class D
    {
        private  double name = 3.14;  // 为字段分配内存
        public double MyValue   // 属性不分配内存
        {
            set
            {
                name = value; //返回类型为void
            }
            get
            {
                return name;  // 返回类型是属性的类型,属性的类型也要跟字段的数据类型一致
            }
        }
    }
  
    class Program
    {
        static void Main(string[] args)
        {
            D myD = new D();
            myD.MyValue = 5.666;  //给访问器中的字段赋值
            WriteLine(myD.MyValue);  //输出赋值后的字段的值
            ReadKey();
        }
    }
}

●  我们经常将类中的字段声明private以封装该字段, 然后声明一个public的属性来控制从类的外部对该字段的访问。

和属性关联的字段称为后备字段或后备存储。

● 属性和后备字段需要注意的问题有:

一种约定是两个名称使用相同的内容, 但字段使用Camel 大小写, 属性使用 Pascal 大小写。

另一种是字段使用Camel 大小写,并以下划线开始,属性使用 Pascal 大小写。

         private  double nameField = 3.14; //第一种约定
        public double NameFild
        {
            set
            {
                nameField = value;
            }
            get
            {
                return nameField;
            }
        }
 private  double _nameField = 3.14;  // 第二种约定
        public double NameFild
        {
            set
            {
                _nameField = value;
            }
            get
            {
                return _nameField;
            }
        }

●    属性访问器不仅仅只可以对关联的字段传入传出数据, 还可以执行任何计算,或者不执行任何计算。但是get访问器必须返回一个属性类型的值。

namespace Ch05Ex03
{
    class D
    {
        private  double _nameField = 3.14;
        public double NameFild
        {
            set
            {
                _nameField = value > 100 ? 100 : value;  //执行计算
            }
            get
            {
                return _nameField;
            }
        }
    }
  
    class Program
    {
        static void Main(string[] args)
        {
            D myD = new D();
            myD.NameFild = 200;  //给访问器中的字段赋值
            WriteLine(myD.NameFild);  //输出赋值后的字段的值
            ReadKey();
        }
    }
}


自动实现属性


●  自动实现属性: 允许只声明属性而不声明后备字段。 编译器会为你创建隐藏的后备字段, 并且自动挂接到get 和 set 访问器上。

自动实现属性的要点如下:

不声明后备字段—— 编译器根据属性的类型分配存储。

不能提供访问器的方法体—— 它们必须被简单地声明为分号。 get 相当于简单的内存读, set 相当于简单的内存写。

除非通过访问器,否则不能访问后备字段。 因为不能用其他的方法访问它, 所以实现只读和只写属性没有意义, 因此必须同时提供读写访问器。

namespace Ch05Ex03
{
    class D
    {
     
        public double NameFild  //分配内存
        {
            set;get;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            D myD = new D();
            myD.NameFild = 200;  //给访问器中的字段赋值
            WriteLine(myD.NameFild);  //输出赋值后的字段的值
            ReadKey();
        }
    }
}


静态属性


●  属性也可以声明为 static, 静态属性的访问器和所有静态成员一样,具有以下特点:

不能访问类的实例成员——可以访问类的静态实例成员, 但是它们可以在非静态方法中被访问。

不管类是否有实例,它们都是存在。

当从类的外部访问时,必需使用类名引用,而不是实例名。


静态构造函数


● 我们在使用类中的静态成员时,先要初始化这些成员。 在声明静态成员时、可以同时提供一个初始值。 但有时候我们需要执行更复杂的初始化操作, 这应该怎么办呢?

● 我们可以把构造函数声明成static的, 一般来说, 静态的构造函数初始化类的静态字段。

● 我们在使用类中的静态成员时,先要初始化这些成员。 在声明静态成员时、可以同时提供一个初始值。 但有时候我们需要执行更复杂的初始化操作, 这应该怎么办呢?

● 我们可以把构造函数声明成static的, 一般来说, 静态的构造函数初始化类的静态字段。

关于静态构造函数应注意的有:

●  那么一个类只能有一个静态构造函数,而且不能带参数、没有返回值。  也不能有访问修饰符(比如private)。 而且声明时使用static 关键字作为前辍。

● 那么一个类既可以有静态构造函数也可以有实例构造函数。

 跟静态成员函数类似, 静态构造函数不能访问所在类的实例成员, 所以也不能使用this访问器。

● 不能在程序中显式调用静态构造函数,系统会自动调用它们,在:

只要一个类有静态构造函数,当我们创建第一个类实例时,都会调用静态构造函数、

只要一个类有静态构造函数,类的任何静态成员被引用之前。

在这两种情况下,会首先调用静态构造函数, 之后实例化类或访问静态成员。应注意的是: 其实我们创建了多个个类实例, 其静态构造函数都只调用一次。

下面看一个程序代码:

namespace Ch05Ex03
{
    class RandomNum
    {
        static int RandomKey;
        int aa;
        int bb;
        static RandomNum()
        {
            RandomKey = 15;
            WriteLine("静态构造函数被调用!");
        }
        public RandomNum(int _aa,int _bb)
        {
            aa = _aa;
            bb = _bb;
            WriteLine("\n实例构造函数被调用!");
        }
        public void Show()
        {
            WriteLine($"输出RandomKey的静态成员的值:{RandomKey}");
            WriteLine($"输出aa的静态成员的值:{aa}");
            WriteLine($"输出bb的静态成员的值:{bb}");
            WriteLine();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            RandomNum a = new RandomNum(2,3);
            RandomNum b = new RandomNum(5,6);
            a.Show();
            b.Show();
            ReadKey();
        }
    }
}
输出是结果为:

静态构造函数被调用!

实例构造函数被调用!

实例构造函数被调用!
输出RandomKey的静态成员的值:15
输出aa的值:2
输出bb的的值:3

输出RandomKey的静态成员的值:15
输出aa的值:5
输出bb的的值:6

在该程序中, 我们首先创建了该类的两个实例,分别为它填了两个实参, 我可以看到输出结果,不管创建多少个实例,  静态构造函数只调用只一次, 其次就是调用创建该实例的对应的实例构造函数, 上面的程序调用的是带两个形参的实例构造函数。 调用顺序为你创建实例的顺序。

那么每一个实例都有一个静态成员的值,它被所有类实例所共享。

猜你喜欢

转载自blog.csdn.net/qq_34536551/article/details/83817545