文章目录
数据类型的区别
栈内存和堆内存
概念
堆(Heap):在c里面叫堆,在c#里面其实叫托管堆。
栈(Stack):就是堆栈,简称栈。
托管堆:
托管堆不同于堆,它是由CLR(公共语言运行库(Common Language Runtime))管理,当堆中满了之后,会自动清理堆中的垃圾。所以,做为.net开发,我们不需要关心内存释放的问题。
栈
栈区的特性:先进后出 入栈的时候是123按顺序入的,那么出栈的时候就是321按顺序出。
当一个栈结束时会把存在栈中的数据依次出栈。
内存堆栈:存在内存中的两个存储区(堆区,栈区)。
栈区:存放函数的参数、局部变量、返回数据等值,由编译器自动释放
堆区:存放着引用类型的对象,由CLR释放
堆
堆是一块内存区域,在堆里可以分配大块的内存用于存储某类型的数据对象。与栈不同,堆里的内存能够以任意顺序存入和移除,除了string 先进先出。虽然程序可以在堆里保存数据,但并不能显式地删除它们。CLR的自动GC(Garbage Collector,垃圾收集器)在判断出程序的代码将不会再访问某数据项时,自动清除无主的堆对象。
值类型和引用类型的区别
- 值类型存取速度快,引用类型存取速度慢。
- 值类型表示实际数据,引用类型表示存储在堆中的数据的引用和地址。
- 值类型都继承自System.VlueTyp,引用类型都继承自System.Object。
- 栈中的内存是自由分配自动释放的,而引用类型会由.NET的GC来回收释放。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _01_数据类型的区别
{
internal class Program
{
static void Main(string[] args)
{
//值类型的变量存储的是值本身(存储到栈中)
//引用类型的变量存储的是数据的内存地址(存储到堆中,变量保存的是内存地址)
//= 运算符在进行赋值操作时,操作的是栈中保存的内容
//= 运算符在进行复值操作时,操作的是栈中保存的内容
int a = 12;
int b = a;
a = 13; //图1,图2详解
Console.WriteLine(a);//13
Console.WriteLine(b);//12
//因为int类型属于值类型,变量存储的是值本身,所以当一个值类型的实例被赋值给另一个变量时,数据的一个完整副本会被创建并分配给新变量。原始变量与新变量彼此独立,互不影响。
Book book1 = new Book();
book1.Name = "隐杀";
Book book2 = book1; //图1,图2详解
book1.Name = "道诡异仙";
Console.WriteLine(book1.Name);//道诡异仙
Console.WriteLine(book2.Name);//道诡异仙
//因为Book类属于引用型,当一个引用类型赋值给另一个变量时,赋值的是引用(内存地址)。这意味着两个变量指向堆上的同一个对象,因此一个变量的改变会影响到另一个。
People p1 = new People() { Name="白小村"};//声明时同时初始化
People p2 = p1;
p2 = new People() { Name = "涂红红" };//虽然在上步骤p1的内存地址复制给了p2,但在这里 new People() { Name = "涂红红" }等于p1新开辟了一块内存空间,不再与p2共用同一块内存空间
Console.WriteLine(p1.Name);//白小村
Console.WriteLine(p2.Name);//涂红红
Peoples laowang=new Peoples() { Name="小王"};
Peoples xiaowang=new Peoples() { Name="老王"};
xiaowang.Father = laowang;
Console.WriteLine($"名字叫做{xiaowang.Name},父亲叫做{xiaowang.Father}");
//string 引用数据类型
//不是一个特别严谨的注释:虽然字符串是引用类型,但是C#中对他进行了特殊处理,可以把他当做基本数据类型使用(后续详解)
string str1 = "白良苗";
string str2 = str1;
str1 = "白灵淼";
Console.WriteLine(str1);//白灵淼
Console.WriteLine(str2);//白良苗
}
}
class Book
{
public double Price { get; set; }
public string Name { get; set; }
}
class People
{
public string Name { get; set; }
}
class Peoples
{
public string Name { get; set; }
//对象的成员也可能是一个对象
public Peoples Father { get; set; }
}
}
特别注意:
在 C# 中,string 类型被视为引用类型。但是,与其他引用类型不同的是,对 string 变量的操作被视为对值类型的操作,因此,在对 string 变量进行赋值或传递到方法时,其实是进行值传递。
这意味着,当您将一个字符串变量传递给一个方法时,该方法会接收该字符串的一个副本,而不是原始字符串的引用。任何对传递的字符串副本的修改都不会影响原始字符串。
值类型和引用类型的区别在于,值类型存储在堆栈中,而引用类型存储在堆中。当您创建一个字符串变量时,它实际上是一个引用类型,但是 .NET 框架会将其优化为值类型,以提高性能。
需要注意的是,字符串是不可变的,即一旦创建就无法更改。因此,在对字符串进行操作时,实际上是创建了一个新的字符串对象,而不是修改原始字符串对象。这也是为什么 C# 中的字符串变量是值传递的原因。
总之,在 C# 中,string 类型是引用类型,但是在对其进行操作时,会被视为值类型,因此是值传递的。
值类型补充-结构体
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _02_值类型补充_结构体
{
internal class Program
{
static void Main(string[] args)
{
//结构体可以让变量存储多个数据(值类型)
//用法和形式与类基本相同,可以理解为: 结构体就是值类型的对象
//结构体的使用:1.结构体的声明2.结构体的创建
//2.结构体的创建
Book book1 = new Book() { Name="隐杀",Price=39.9,Description="杀手穿越"};
Book book2 = new Book() { Name = "道诡异仙", Price = 49.9, Description = "精神病更精神" };
Console.WriteLine($"book1的名字{book1.Name},售价{book1.Price},讲述了{book1.Description}的故事");//隐杀
Console.WriteLine($"book2的名字{book2.Name},售价{book2.Price},讲述了{book2.Description}的故事");//道诡异仙
Book book3 = book1;
book1.Name = "我的26岁女房客";
book1.Description = "白狗过隙,天空之城";
Console.WriteLine($"book1的名字{book1.Name},售价{book1.Price},讲述了{book1.Description}的故事");//我的26岁女房客
Console.WriteLine($"book3的名字{book3.Name},售价{book3.Price},讲述了{book3.Description}的故事");
//还是 隐杀 因为结构体是值类型,所以改变book1的成员变量不会影响到book3
}
}
//1. 结构体的声明(类似于类的声明)
struct Book
{
public string Name;
public double Price;
public string Description;
public int Count;
}
}
可空数据类型
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _03_可空类型数据
{
internal class Program
{
static void Main(string[] args)
{
//值类型能存储的数据只有这个类型的所有数据,例如byte只能存储0到255之间的整数,bool只能存储true和false
byte bt = 255;
int ints = 10086;
bool b1 = true;
//值类型都有默认值
//整数类型的默认值都是0
//char类型的默认值是\0
//bool类型的默认值是False
//结构体默认为空的结构体
//类中成员的默认值是对应数据类型的默认值, 如public string Name { get; set; },的默认值是字符串的默认值即为""
Console.WriteLine(new People().Name);//""
Console.WriteLine(new People().Id);//0
//引用类型除了可以存储对应的类型外 还可以存一个null值
string s = "123";
s = null;
People p1=new People();
p1 = null;
//可空数据类型就是让值类型可以存储null
//在数据类型的后面添加一个 ? 表示它是一个可空数据类型,将会在本来存储的范围上加上一个null值,同时它的默认值就为null
int?i2 = null;
char?c2 = 'a';
//可空数据类型到不空数据类型的转换
//如果一个值为可空类型,我们在运算时,会出现问题,所有需要将可空数据类转化为不可空数据类型
//int num = 10 + i2;
int?i3=null;
//int i4=i3; //报错因为i3可能为null i4不能为null
//可以使用??运算符进行处理
//执行逻辑:如果??前面的值为null 则使用后面的值
int i4=i3 ?? 0;
Console.WriteLine(i4);//0
}
}
class People
{
public string Name { get; set; }
public int Id { get; set; }
}
}
控制台输入的接收
//获取到用户输入的字符,只能获取一个字符
ConsoleKeyInfo s = Console.ReadKey();
//通过ConsoleKeyInfo的Key,获取当前按下的键位
Console.WriteLine($"按下的键是{s.Key}");
//通过ConsoleKeyInfo的KeyChar,获取当前按下的键值
Console.WriteLine($"按下的键是{s.KeyChar}");
//Console.ReadLine()获取用户输入的一行数据,按回车键结束输入
string line = Console.ReadLine();
Console.WriteLine(line);
类型转换
类型转换就是将A类型的变量赋值给B类型的变量
隐式类型转换
隐式转换(自动转换),就是从小范围到大范围的转换
int i1 = 22;
long l1 = i1;
//short s1 = i1; 不允许 因为int存的多
// ulong ul1 = i1; 不允许 因为int能存负数 ulong不能存负数
// ushort us1 = i1; 不允许 因为int存的多 ushort存的少 并且不能存负数
sbyte sbyte2 = 5;
//byte byte2 = sbyte2; 不允许 因为sbyte可以存负数 byte不能存负数
byte by3 = 6;
//sbyte sby3 = by3; 不允许 因为byte最大能存到255 sbyte 只能存到127
ushort us4 = 21; //无符号
int int4 = us4; //可以转成有符号的int
uint uint4 = us4;//可以转为无符号的int
short sh5 = 33; //有符号
int int5 = sh5; //可以转为有符号的int
//uint uint5 = sh5; 不能转换为无符号的uint
// ulong ul5 = sh5; 不能转换为无符号的ulong
/*
* 总结:
* 1.小范围能转大范围,大范围不能转小范围
* 2.同级别之间都不能转换**加粗样式**
* 3.无符号可以转换大范围的有符号和无符号
* 4.有符号可以转换大范围有符号,不能转无符号
*/
//小数的转换
//float < double
float f1 = 2.2f;
double d1 = f1;
//整数可以直接转小数
ulong i6 = 3;
float f2 = i6;
double d2 = i6;
double d4 = 3.3;
//int i7 = d4;// 不允许 因为整型不能保留小数位
//派生类:子类 基类:父类 所有类型的基类都是object
//从派生类转为基类也可以转换
int i8 = 100;
char s= 'a';
object o1=s;
显示类型转换
从大范围到小范围的转换
//从大范围到小范围的转换
uint int1 = 20;
//在被转换的数据前面加上(目标类型)进行显式转换
int int2 = (int)int1;
//强制转换可能会导致数据丢失
uint int3 = 256;
byte byte1 = (byte)int3;
Console.WriteLine(byte1);//0
double dou1 = 22.9;
int in4 =(int)dou1;
Console.WriteLine(in4);
//并不是所有的显式转换都可以转换
string s1 = "吴亦凡";
//char c1 =(char)s1;
char c2 = 'A';
// string s2=(string)c2;
//char类型和int类型之间转换根据的是ASCII码转换的
//https://blog.csdn.net/weixin_45788237/article/details/136460300?spm=1001.2014.3001.5501
int in5 = (int)c2;
Console.WriteLine(in5);//65
int in6 = 67;
char c3 = (char)in6;
Console.WriteLine(c3);//C
字符串和其他类型之间的转换
// ----------其他类型转字符串
int in1 = 123;
double do1 = 12.3;
bool b = true;
string st1 = in1.ToString();
string st2 = do1.ToString();
string st3 = b.ToString();
Console.WriteLine(st1); // 123==> "123"
Console.WriteLine(st2); // 12.3 ==> "12.3"
Console.WriteLine(st3); // true ==> "true"
int aa = 1008611;
Console.WriteLine(aa.ToString());// "1008611"
Console.WriteLine(aa.ToString("F1"));// "1008611.0"
Console.WriteLine(aa.ToString("F2"));// "1008611.00"
//三位分割
Console.WriteLine(aa.ToString("n"));// 1,008,611.00
//加上¥前缀(还会有n的特性)
Console.WriteLine(aa.ToString("c"));// ¥1,008,611.00
//----------字符串转其他类型
int in7 =int.Parse(st1);
double dou2 =double.Parse(st2);
bool b3 = bool.Parse(st3);
int in8 =int.Parse("123");
// in8 = int.Parse("123.0");//报错 因为123.0不是int类型
// in8 = int.Parse("1a");
double ds2 = double.Parse("123.0");
Console.WriteLine(in8);
// 布尔值的转换 不区分大小写 但是单词之间不能有空格
bool b4 = bool.Parse("TRUE");
Console.WriteLine(b4);
Covert
Covert类就是专门进行类型转换的类,Convert类提供的方法可以实现各种数据类型之间的转换
int i1 = 11;
byte by =Convert.ToByte(i1);
short sh = Convert.ToInt16(i1);
//Convert.ToByte() 转byte
// Convert.ToInt16() 转short
//Convert.ToUInt16() 转ushort
//Convert.ToInt32() 转int
//Convert.ToInt64() 转long
//Convert.ToUInt64() 转ulong
Console.WriteLine(by);
练习
-
做一个用户信息录入的功能
让用户输入姓名、性别、爱好、国籍,全部输入完成后输出”你的姓名是xx性别是xx,爱好是xx,国籍是xx“