C#变量存储(栈和堆),变量作用域,属性,构造函数,修饰符,类,类继承
今日份主要内容:
-
变量存储(栈和堆),变量作用域,属性
-
构造函数,修饰符
-
类,类继承
变量存储(栈和堆)
在C#中,变量存储到两个地方:堆(Heap),栈(Stack),栈和堆是两种内存管理方式。
-
堆的特点:在C#里叫托管堆,受CLR管理,相对安全。用于动态分配内存(引用类型),大小不固定,GC垃圾回收机制自动回收。
-
栈的特点:数据使用之后自动销毁,用的时候可以修改,用完之后就会自动清理掉,它不是被GC清理的,而是由系统自动分配和释放。存放局部变量,对象的引用地址等。用于存储方法中的变量(值类型)。
内存堆栈和数据堆栈
-
堆栈有两个概念:内存堆栈和数据堆栈。内存堆栈就是内存的两个逻辑分区。数据堆栈是一种先进后出的数据结构,主要是说栈区。
内存泄漏
栈和堆存满了会有内存泄露的风险。
-
栈区:
- 算法没写好,函数调用太多了:比如死循环,无限循环的递归调用,会报StackOverflow异常;
- 写的程序有错误,不小心在栈上分配了太多内存,即栈内存溢出问题);
-
堆区:很大(数G),不会爆,未回收可能会造成内存泄漏(C#中有垃圾收集器,不需要手动释放)。
栈和堆的详细区别:
-
栈上存储的数据量少,一般1-2M,堆上存储的数据量大,但是栈区上的数据访问速度比堆快。
-
栈上的空间是有顺序的,是连续的,先进后出。而堆上的空间是无序的,散列的。
值类型数据存储到栈上。值类型包括:基本数据类型(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垃圾回收机制
- 找到内存中的垃圾;
- 回收垃圾,让程序能再次利用这部分空间。
c和c++忘记回收内存就会需要自己分配内存,c#不需要去干预。
参考:
深入理解C#中的堆(Heap)与栈(Stack),一次性全都掌握!_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是一个内置的变量,关键字,用来接收属性更改的值。
- 自动属性的快捷方式:prop===》property属性,propfull完整的属性。
- 属性的默认值,没有赋值的时候,会默认为该类型的默认值。
简写和展开写的区别:
-
完整的属性的写法优势在于:数据校验,可以让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。
- 开发程序集;
- 在其他项目中引用;右键添加引用,选择程序集。
- 在使用的类中加载,using后面跟上程序集的名字,程序集的名字默认为命名空间的名字。
- 右键单击程序集名字,在文件资源管理器中打开文件夹,可以看到生成的程序集dll。
类
类的定义:
-
类文件的命名使用大驼峰。一般会加上一个三斜杠注释,xmal注释。
-
习惯性的定义顺序:
构造函数,属性,方法
类继承
继承是面向对象的编程语言的一项功能,可以方便定义提供特定功能(数据和行为)的基类,定义继承或重写此功能的派生类。
面向对象三大特殊:继承(代码复用,方便维护),封装(代码复用),多态(其中一种表现:重载)
-
基类:父类 A派生B
-
派生类:子类
怎么定义一个继承呢?
public class XXX : AAA
- 每一个类的构造函数不能被继承。子类可以继承基类的属性和方法,但不能继承基类的默认构造函数。
- 继承过来的成员有两种处理结果:不变,变(扩展成员,修改成员,删除成员)。
- 私有的方法不能被子类继承过去,不能打点调用。在子类中可以访问protected成员。
- 保护的方法可以被继承,只能在本类和派生类中被调用,不能在外面被访问。
base基类,想在子类中访问父类成员的时候:
base.XXX; base.asd();
调用类里面的方法,要先实例化类,然后打点调用方法。
所有的对象都有继承链,所有类没有明确继承其他自定义类时,默认继承了Object。
对象子类也是基类的类型。
关于类型转换:
- 引用类型,类,子类可以隐式转换为父类,子类可以强制转换成父类,但只用于引用类型转换的as可以不用写。as转不成功,它不会报错,转为null。
- 在值类型里,小范围的可以隐式转换为大范围的。用小括号()进行强制转换。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)