百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

C#基础 值类型和引用类型

bigegpt 2024-08-09 11:06 2 浏览


1、值类型,引用类型,拆,装箱,常用的引用类型,值类型。

栈:一种先进后出(后进先出)的存储数据的结构体

堆:一块连续的,自由的存储空间。

值类型:变量直接保存其数据。

引用类型:变量保存其数据的引用(地址),而不是具体的数据。

C#的值类型包括:结构体(数值类型,bool型,用户定义的结构体),枚举,可空类型。

C#的引用类型包括:数组,用户定义的类、接口、委托,object,字符串。

数组的元素,不管是引用类型还是值类型,都存储在托管堆上。

引用类型在栈中存储一个引用,其实际的存储位置位于托管堆。为了方便,本文简称引用类型部署在托管推上。

值类型总是分配在它声明的地方:作为字段时,跟随其所属的变量(实例)存储;作为局部变量时,存储在栈上。

值类型在内存管理方面具有更好的效率,并且不支持多态,适合用作存储数据的载体;引用类型支持多态,适合用于定义应用程序的行为。

应该尽可能地将值类型实现为具有常量性和原子性的类型。

应该尽可能地确保0为值类型的有效状态。

应该尽可能地减少装箱和拆箱。

装箱:把值类型转换成引用类型的过程。把一个值类型赋值给一个引用类型

拆箱:把引用类型转换成值类型的过程。把一个引用类型赋值给一个值类型(需要用装箱的数据类型转换)

object obj;

short i = 123;

obj = i;//装箱操作

int j = (short )obj;//拆箱操作,需要用装箱的数据类型转换

Console.WriteLine(obj);

1. 隐式转换和强制转换。

隐式转换:第一种,把小的赋值给大的。第二种,把派生类(子类)赋值给基类,也会发生隐式转换。第三种、把某一类的对象赋值给实现该类的接口时候发生隐式转换。

强制转换:大的赋值给小的时候强制转换。拆箱的时候发生强制转换。

2. 装箱和拆箱的内部原理

装箱 是值类型到 object 类型或到此值类型所实现的任何接口类型的隐式转换。对值类型装箱会在堆中分配一个对象实例,并将该值复制到新的对象中。


拆箱(取消装箱)是从 object 类型到值类型或从接口类型到实现该接口的值类型的显式转换。取消装箱操作包括:

检查对象实例,确保它是给定值类型的一个装箱值。(拆箱后没有转成原类型,编译时不会出错,但运行会出错,所以一定要确保这一点。用GetType().ToString()判断时一定要使用类型全称,如:System.String 而不要用String。)

将该值从实例复制到值类型变量中。

值类型举例:

namespace ConsoleApplication1

int i = 0; 执行,i入栈。

ChangeAge(i);方法入栈,执行方法i = 10;

当i = 10执行完后出栈。此时方法执行完,也出栈。

栈中留下i=0;

所以输出结果为0

内存图解如下:


{

class Program

{

static void ChangeAge(int i)

{

i = 10;

}

static void Main(string[] args)

{

int i = 0;

ChangeAge(i);

Console.WriteLine(i);//输出结果为0

}

}

}


引用类型举例:

namespace ConsoleApplication1

Student student=new Student();

在堆中分配一块内存,并将地址赋值到栈中。

student.Age=0;

将堆中的age赋值为0;

Change(student);方法入栈并将student内存地址传到方法内。

student.Age = 10;将内存地址上对应的对象的Age改变为10;

然后出栈,方法出栈,再执行 Console.WriteLine(student.Age);输出Age的结果时,Age的值变为10;


{

class Student

{

public int Age;

}

class Program

{

static void Change(Student student)

{

student.Age = 10;

}

static void Main(string[] args)

{

Student student=new Student();

student.Age=0;

Change(student);

Console.WriteLine(student.Age);//输出结果为10

}

}

}


C#支持两个预定义的引用类型


1.object类型

许多编程语言和类结构都提供了根类型,层次结构中的其他对象都从它派生而来。C#和.NET也不例外。在C#中,object类型就是最终的父类型,所有内在和用户定义的类型都从它派生而来。这是C#的一个重要特性,它把C#与VB和C++区分开来,但其行为与Java中的非常类似。所有的类型都隐含地最终派生于System.Object类,这样,object类型就可以用于两个目的:

可以使用object引用绑定任何特定子类型的对象。例如,使用object类型把堆栈中的一个值对象装箱,再移动到堆中。对象引用也可以用于反射,此时,必须有代码来处理未知的特定类型对象。这类似于C++中的void指针或VB中的Variant数据类型。

object类型执行许多基本的一般用途的方法,包括Equals()、GetHashCode()、GetType()和ToString()。用户定义的类可能需要使用一种面向对象技术--重写,提供其中一些方法的替代执行方法。例如,重写ToString()时,要给类提供一个方法,该方法可以提供类本身的字符串表示。如果类中没有提供这些方法的实现,编译器就会在对象中选择这些实现,它们在类中的执行不一定正确。

2.string类型

有C和C++字符串不过是一个字符数组,因此客户机程序员需要做很多工作,才能把一个字符串复制到另一个字符串上,或者连接两个字符串。实际上,对于一般的C++程序员来说,执行包装了这些操作细节的字符串类是一个非常头痛的耗时过程。VB可以使用string类型,Java中的String类在许多方面都类似于C#字符串。

C#有string关键字,翻译为.NET类时,它就是System.String。

string是一个引用类型,String对象保留在堆中,而不是堆栈中。因此当把一个字符串变量赋予另一个字符串时,会得到对内存中同一个字符串的两个引用,但是String与引用类型在常见的操作上有区别。字符串时不可以改变的,修改其中一个字符串,就会创建一个全新的String对象,另一个字符串不发生变化。例:

输出结果:

s1 is a String

s2 is a String

s1 is now another string

s2 is new a String

改变s1的值对s2没有影响,这与我们期待的引用类型正好相反。当用值 a String 初始化s1时,就在堆上分配了一个新的String对象。在初始化s2时,引用也指向这个对象。所以s2的值也是a string,但是当改变s1的值时,并不会替换原来的值,堆上为新分配一个对象。S2变量仍指向原来的对象,所以它的值没有改变


class Program

{

static void Main(string[] args)

{

string s1 = "a String";

string s2 = s1;

Console.WriteLine("s1 is " + s1);

Console.WriteLine("s2 is "+s2);

s1 = "another string";

Console.WriteLine("s1 is now "+s1);

Console.WriteLine("s2 is new "+s2);

}

}


关于引用类型需要注意的地方。举例:

开始bb=alist,此时bb为null,bb指向alist的内存地址,bb=new List<int>(),bb指向内存的地址改变了,不再和alist是同一个地址,

所以bb无论怎么改alist依然为空。


一、class Program

{

static void Main(string[] args)

{

List<int> alist = null;

List<int> bb = alist;

bb = new List<int>();

bb.Add(1);

if (alist == null)

{

Console.WriteLine("a list is null");

}

else

{

Console.WriteLine("a list is not null");//结果a list is null

}

}

}

二、class Program

{

public class Student

{

public Dictionary<int, string> dicValue;

public void AddDic(Dictionary<int, string> dicValue)

{

if (dicValue == null)

{

dicValue = new Dictionary<int, string>();

}

输出结果:发现程序一直为null;

分析原因如下:

在AddDic方法中重新new了一次导致内存地址的指向发生了改变disValue已经和原来的内存地址指向不同了。解决方法:

第一种方法:声明dicValue的时候将他初始化。

第二种方法:在AddDic中的dicValue参数前加上out或ref,重新将地址指给原理的dicValue。


for (int i = 0; i < 10; i++)

{

dicValue.Add(i, "ss" + i);

}

}

}

static void Main(string[] args)

{

Student s = new Student();

s.dicValue = null;

s.AddDic(s.dicValue);

Console.WriteLine(s.dicValue.Count);

}

} [C#]

一个关于null赋值的问题

class Program

{

static void Main(string[] args)

{

Children children = new Children();

SetInstanceNull(children);

if (children == null)

{

Console.WriteLine("children is null");

}

else

{

Console.WriteLine(children.inter);

}

}

static void SetInstanceNull(Children childrenParam)

{

childrenParam.inter = 10;

childrenParam = null;

}

}

class Children

{

public int inter = 0;

}

程序输出结果:10

问题解析:我们知道方法参数如果是引用类型的话,则方法调用时,将把实例对象的地址传递给方法参数,这样在被调用方法中就可以通过实例对象的地址来操作实例对象的数据。故在SetInstanceNull方法中我们能将children实例中inter成员的值改更为10。然而childrenParam = null语句却没有使children为null,而仅仅是把childrenParam值为null。有人说children和chilrenParam是两个不同的变量,所以才有这样的结果。的确,这种原因的产生是因为他们是两个不同的变量导致的,但为什么不同呢?如果我们用object.ReferenceEquals方法去验证两个变量的相等性的话,我们会发现结果是相等的。那这个相等一定表示这两个变量相同吗?答案是否定的。在C#里面,当初始化一个类的时候,系统将使所有的引用引用类型参数引用为空,当遇到实例化一个类的时候,例如:new Children(),系统会在堆上分配一个内存空间存放Children实例,并将该地址返回给引用参数children。这种其实就是指针了。这样引用参数children与刚才实例化的Children实例就建立了一一映射关系。当调用方法SetInstanceNull时,系统将children参数的引用复制给childrenParam参数。这样在SetInstanceNull方法里面就可以操作刚才实例化的Children实例。所以Children实例中的inter成员能够被更改。childrenParam = null中语句只影响到childrenParam而没有影响到children给了我们一点提示,那就是将引用类型参数赋值为null其实是切断参数与实例之间的联系,当没有任何参数与该实例有联系的时候,该实例就会被垃圾回收器给回收。

相关推荐

【Docker 新手入门指南】第十章:Dockerfile

Dockerfile是Docker镜像构建的核心配置文件,通过预定义的指令集实现镜像的自动化构建。以下从核心概念、指令详解、最佳实践三方面展开说明,帮助你系统掌握Dockerfile的使用逻...

Windows下最简单的ESP8266_ROTS_ESP-IDF环境搭建与腾讯云SDK编译

前言其实也没啥可说的,只是我感觉ESP-IDF对新手来说很不友好,很容易踩坑,尤其是对业余DIY爱好者搭建环境非常困难,即使有官方文档,或者网上的其他文档,但是还是很容易踩坑,多研究,记住两点就行了,...

python虚拟环境迁移(python虚拟环境conda)

主机A的虚拟环境向主机B迁移。前提条件:主机A和主机B已经安装了virtualenv1.主机A操作如下虚拟环境目录:venv进入虚拟环境:sourcevenv/bin/active(1)记录虚拟环...

Python爬虫进阶教程(二):线程、协程

简介线程线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合和堆栈共同组成。线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能...

基于网络安全的Docker逃逸(docker)

如何判断当前机器是否为Docker容器环境Metasploit中的checkcontainer模块、(判断是否为虚拟机,checkvm模块)搭配学习教程1.检查根目录下是否存在.dockerenv文...

Python编程语言被纳入浙江高考,小学生都开始学了

今年9月份开始的新学期,浙江省三到九年级信息技术课将同步替换新教材。其中,新初二将新增Python编程课程内容。新高一信息技术编程语言由VB替换为Python,大数据、人工智能、程序设计与算法按照教材...

CentOS 7下安装Python 3.10的完整过程

1.安装相应的编译工具yum-ygroupinstall"Developmenttools"yum-yinstallzlib-develbzip2-develope...

如何在Ubuntu 20.04上部署Odoo 14

Odoo是世界上最受欢迎的多合一商务软件。它提供了一系列业务应用程序,包括CRM,网站,电子商务,计费,会计,制造,仓库,项目管理,库存等等,所有这些都无缝集成在一起。Odoo可以通过几种不同的方式进...

Ubuntu 系统安装 PyTorch 全流程指南

当前环境:Ubuntu22.04,显卡为GeForceRTX3080Ti1、下载显卡驱动驱动网站:https://www.nvidia.com/en-us/drivers/根据自己的显卡型号和...

spark+python环境搭建(python 环境搭建)

最近项目需要用到spark大数据相关技术,周末有空spark环境搭起来...目标spark,python运行环境部署在linux服务器个人通过vscode开发通过远程python解释器执行代码准备...

centos7.9安装最新python-3.11.1(centos安装python环境)

centos7.9安装最新python-3.11.1centos7.9默认安装的是python-2.7.5版本,安全扫描时会有很多漏洞,比如:Python命令注入漏洞(CVE-2015-2010...

Linux系统下,五大步骤安装Python

一、下载Python包网上教程大多是通过官方地址进行下载Python的,但由于国内网络环境问题,会导致下载很慢,所以这里建议通过国内镜像进行下载例如:淘宝镜像http://npm.taobao.or...

centos7上安装python3(centos7安装python3.7.2一键脚本)

centos7上默认安装的是python2,要使用python3则需要自行下载源码编译安装。1.安装依赖yum-ygroupinstall"Developmenttools"...

利用本地数据通过微调方式训练 本地DeepSeek-R1 蒸馏模型

网络上相应的教程基本都基于LLaMA-Factory进行,本文章主要顺着相应的教程一步步实现大模型的微调和训练。训练环境:可自行定义,mac、linux或者window之类的均可以,本文以ma...

【法器篇】天啦噜,库崩了没备份(天啦噜是什么意思?)

背景数据库没有做备份,一天突然由于断电或其他原因导致无法启动了,且设置了innodb_force_recovery=6都无法启动,里面的数据怎么才能恢复出来?本例采用解析建表语句+表空间传输的方式进行...