C#变量存储(栈和堆),变量作用域,属性,构造函数,修饰符,类,类继承

今日份主要内容:

  1. 变量存储(栈和堆),变量作用域,属性

  2. 构造函数,修饰符

  3. 类,类继承

变量存储(栈和堆)

在C#中,变量存储到两个地方:(Heap),(Stack),栈和堆是两种内存管理方式。

  • 堆的特点:在C#里叫托管堆,受CLR管理,相对安全。用于动态分配内存(引用类型),大小不固定,GC垃圾回收机制自动回收。

  • 栈的特点:数据使用之后自动销毁,用的时候可以修改,用完之后就会自动清理掉,它不是被GC清理的,而是由系统自动分配和释放。存放局部变量,对象的引用地址等。用于存储方法中的变量(值类型)。

    内存堆栈和数据堆栈

  • 堆栈有两个概念:内存堆栈和数据堆栈。内存堆栈就是内存的两个逻辑分区。数据堆栈是一种先进后出的数据结构,主要是说栈区。

    内存泄漏

    栈和堆存满了会有内存泄露的风险。

  • 栈区:

    1. 算法没写好,函数调用太多了:比如死循环,无限循环的递归调用,会报StackOverflow异常;
    2. 写的程序有错误,不小心在栈上分配了太多内存,即栈内存溢出问题);
  • 堆区:很大(数G),不会爆,未回收可能会造成内存泄漏(C#中有垃圾收集器,不需要手动释放)。

栈和堆的详细区别:

  1. 栈上存储的数据量少,一般1-2M,堆上存储的数据量大,但是栈区上的数据访问速度比堆快。

  2. 栈上的空间是有顺序的,是连续的,先进后出。而堆上的空间是无序的,散列的。

    值类型数据存储到上。值类型包括:基本数据类型(int、long、float、char、bool)、枚举类型(enum)、结构类型(struct)。

    引用类型的数据存储到上。引用类型包括:类(基类Systekm.Object、字符串string、自定义类class)、接口(interface),数组(int[]\string[])。

    引用类型会把数据的值存在堆上,而在栈存储的仅仅是个地址。栈上的地址就相当于指针,也可称引用或指针。

代码说明:

string str = "hello";

  • str地址存在栈上,hello存在堆上。栈区的内存是有顺序的,堆上的内存是碎片化的。栈上存的地址和堆上的值有联系,通过栈上分配的空间存的地址可以去堆上去找到某个值。

  • 堆上的值进行修改时,原来的旧值就会变成游离值。这个"hello"依靠GC垃圾回收机制进行回收,.NET框架特有的GC垃圾回收机制,所以不用担心内存满了之后的内存泄漏问题。

 // string虽然是引用类型,在栈上分配一个地址,在堆上分配一个值。
 
 // 默认同一个值在堆上只占一个空间,指向的是同一个hello。理论上修改str1会影响str2,但实际上不是这样的。
 
 // 为什么修改了str1不会影响str2?
 
 // string比较特殊,str1和str2会使用常量池。
 
 string str1 = "hello";
 
 string str2 = "hello";
  
 Console.WriteLine("str1的值:" + str1 + ",str2的值:" + str2);
 
 str1 = "HELLO";
 
 // 在修改str1之前,str1和str2同时指向堆上的“hello”空间。修改了str1后,给str1在堆重新分配了新的空间“HELLO”,而原来的空间给str2自己单独使用了。
 
 Console.WriteLine("str1的值:" + str1 + ",str2的值:" + str2);
  • 当其中一个改变时,它就会在常量池里新建一个,修改了str1后,给str1在堆重新分配了一个新的空的“HELLO”。常量池就是为了节省堆区的空间。

​ 堆上的变量,长期不使用,或者赋值为null的时候,GC就会回收它。

str1 = null;

str2  = ""//空字符串

​ 一个字符串里什么都不写就是空的意思。

​ null不分配空间,而“”分配了空间,只不过这个空间里什么都没放,等一会儿不会被GC回收。

GC垃圾回收机制

  1. 找到内存中的垃圾;
  2. 回收垃圾,让程序能再次利用这部分空间。

c和c++忘记回收内存就会需要自己分配内存,c#不需要去干预。

参考:

深入理解C#中的堆(Heap)与栈(Stack),一次性全都掌握!_c# 堆栈-CSDN博客

C# 堆和栈_c#堆栈-CSDN博客

一文搞懂七种基本的GC垃圾回收算法 - 知乎 (zhihu.com)

变量作用域

​ 两种作用域:局部作用域(靠大括号{}控制的) 成员作用域(靠修饰符来控制的)

  • 局部作用域是在方法内,语句内,或者方法签名(参数列表)中定义的变量的可访问区域。

  • 成员作用城是在类或结构体中定义的变量的可访问区域。

    方法里面都有形参,形参的作用域跟函数的作用域一样。在花括号内可以使用,在外面不可以使用。

    提醒:if,while的花括号没有作用域的作用,只是一个范围,类的花括号有作用域的效果。

    语句块for的作用域也不是函数的作用域,在花括号里面,跟函数的局部变量不一样。

    C#方法签名是方法声明的一部分,包括方法名、返回类型以及参数列表。每个方法签名在类内部必须是唯一的,这保证了方法的重载功能。

	 internal class Program
    {
        static void Main(string[] args)
        {
            int a = 10; // 局部的作用域,a只能在Main方法中使用,超出此方法范围无效。

            Student s1 = new Student(1, "张三"); 				// 调用有参构造函数
            
            Student s2 = new Student()
            {
                Id = 2, 
                Name = "李四"
            }; 
            // 调用无参构造函数

            s2.Id = 11;
            Console.WriteLine(s2.Id);

            Console.ReadKey();
        }

        // 形参的作用域:函数的作用域
        static void Mehtod1(int x, int y )
        {
            int a = 10; // 此a和Main中的a不在同一个作用域中。

            // 语句块的作用域
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(i);
            }
            //Console.WriteLine(i);
        }
    }
}

​ 方法可以是私有的,也可以是公开的。方法如果是类内部自己使用,就可以定义成私有。

属性

 // 属性,一般公开的
 //public int Id { get; set; }  // 自动属性,get和set访问器是自动的。简写,语法糖。

 // 完整的属性的写法
 private int _id;// 字段,一般是私有,建议小写, 或带下划线,如:_id。字段的private去掉也可以,默认是私有的。
 public int Id
 {
     get { return _id*2; }  // set访问器获取值时执行
     set {
         if (value > 10)
         {
             throw new Exception("id不能设置10以上");
         }
         _id = value;
    	 }  // set访问器设置值时执行。value为内置的变量,关键字,用来接收属性更改的值。
 }

简写的写法就是语法糖,get在获取值的时候执行,取Id值的时候执行;set在设置值,也就是赋值的时候执行。

value是一个内置的变量,关键字,用来接收属性更改的值。

  1. 自动属性的快捷方式:prop===》property属性,propfull完整的属性。
  2. 属性的默认值,没有赋值的时候,会默认为该类型的默认值。

简写和展开写的区别:

  • 完整的属性的写法优势在于:数据校验,可以让value限定在一个范围内,抛出异常。还可以进行格式化,转换一下数据输出的格式“¥”+id。设置默认值,只需要给私有字段进行赋值 id=100。

  • 自主属性的写法优势在于简洁,不需要借助私有字段就可以实现属性。

构造函数

​ 构造函数,名称必须和类名一样,没有返回值。如果类不明确创建构造函数,则默认有一个无参数的构造函数。实例化的时候初始化,第一次赋值。

​ 构造函数可以有多个,有无参构造函数和有参构造函数之分。

​ 方法或构造函数中的默认值注意细节:从前后往设置第一参数默认值时,后面都必须有默认值,从后往前设置最后一个参数默认值时,前面不必有默认值。不能间隔。

​ 即:可选参数必须出现在所有必选参数后。

​ 构造函数在创建对象时执行。Student s = new Student();

 public class Student
 {
     public Student()
     {
     
	 }

	 public Student(int id,string name)
 	{
     	// 把构造函数传递的参数,赋值给属性。
    	 this.Id = id;  
    	 Name = name;
 	}
 }

​ this就是当前类实例化的对象。形参的命名风格一般用小驼峰,如果id重名了,就要用this进行区分。

  • 有参构造函数通过构造函数形参进行初始化,无参构造函数通过{}给属性初始化,同时使用时通过形参传递的优先。
  • 调用有参数的构造函数时有默认值的可以不用填写参数。
	new Student(1,"张三",true,18,new DateTime(2024,7,22));

	new Student(){ Id=3, Name="张三", Sex=true, Age=20, Birthday=new DateTime(2024,5,22)};

修饰符

​ 修饰符可以用来类、属性,字段,方法等元素(对象)上。

​ 修饰符:private私有, protect 保护的, public 公开的, internal 内部的

  • public 任何地方,类体内部或外部都能使用,继承的子类中也可以使用。

  • private 私有的,只能在类体内部使用。

  • protect 保护的,在本类中使用,派生类中能使用。

  • internal 内部的:内部(程序集内部,.exe或.dll)

    范围:public>internal>protected>private

    修饰符可以组合,如:public static, 但public internal这样组合不对。

    一个类默认为internal,类成员默认为private。

  1. 开发程序集;
  2. 在其他项目中引用;右键添加引用,选择程序集。
  3. 在使用的类中加载,using后面跟上程序集的名字,程序集的名字默认为命名空间的名字。
  4. 右键单击程序集名字,在文件资源管理器中打开文件夹,可以看到生成的程序集dll。

类的定义:

  • 类文件的命名使用大驼峰。一般会加上一个三斜杠注释,xmal注释。

  • 习惯性的定义顺序:

    构造函数,属性,方法

类继承

​ 继承是面向对象的编程语言的一项功能,可以方便定义提供特定功能(数据和行为)的基类,定义继承或重写此功能的派生类。

​ 面向对象三大特殊:继承(代码复用,方便维护),封装(代码复用),多态(其中一种表现:重载)

  • 基类:父类 A派生B

  • 派生类:子类

​ 怎么定义一个继承呢?

​ public class XXX : AAA

  • 每一个类的构造函数不能被继承。子类可以继承基类的属性和方法,但不能继承基类的默认构造函数。
  • 继承过来的成员有两种处理结果:不变,变(扩展成员,修改成员,删除成员)。
  • 私有的方法不能被子类继承过去,不能打点调用。在子类中可以访问protected成员。
  • 保护的方法可以被继承,只能在本类和派生类中被调用,不能在外面被访问。

​ base基类,想在子类中访问父类成员的时候:

base.XXX; base.asd();

​ 调用类里面的方法,要先实例化类,然后打点调用方法。

​ 所有的对象都有继承链,所有类没有明确继承其他自定义类时,默认继承了Object。

​ 对象子类也是基类的类型。

​ 关于类型转换:

  • 引用类型,类,子类可以隐式转换为父类,子类可以强制转换成父类,但只用于引用类型转换的as可以不用写。as转不成功,它不会报错,转为null。
  • 在值类型里,小范围的可以隐式转换为大范围的。用小括号()进行强制转换。
posted @   海域  阅读(174)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示