开场白
各位程序猿/媛们,今天我们来聊一聊编程世界里的"金钱观"。
你以为只有人类会纠结现金和存款的区别?不不不,C#中的值类型和引用类型每天都在上演这场大戏!
而我们的string同学,表面是个引用类型,背地里却偷偷活成了值类型的样子------像极了在朋友圈立健身人设,实际在家躺平的你。
第一章:当值类型和引用类型去开房(内存分配篇)
已经学习过的数据类型
无符号整型有符号类型 浮点数 特殊类型
//byte b; //sbyte sb; //float f; //bool bo;
//ushort us; //short s; //double d //char a;;
//uint ui; //int i; //decimal //string str;c;
//ulong ul; //long l;
复杂数据类型引用类型:string 数组 类(未学习)
枚举 值类型:其他,结构体
数组
想象值类型和引用类型是两个性格迥异的房客:
-
值类型 :实诚的直男
入住时自带全部家当(数据),直接住进快捷酒店(栈 )。退房时连卫生纸都带走(深拷贝),绝不拖泥带水。例如你有一个数据A,你将其复制给B,那么此时他两就从此天涯陌路人,丝毫关系都莫得了啊,你无论怎么修改B,都绝不会影响到A。
代表嘉宾:int, double, struct -
引用类型 :心机的高富帅
只带一张房卡(内存地址),把行李全扔在豪宅(堆 )里。复制时只给你房卡复印件(浅拷贝),真要去豪宅会发现里面住着前任!对于咱们得引用类型而言呢,他就不像值类型那样绝情,他们在复制的时候,其实是把自己本体也拿过去了,就像是你的银行卡,你可以存钱存上去,你的朋友也可以打钱上去,跑得了和尚跑不了银行卡。钱,始终都是存在银行卡上面的,你可以取出来,假如你和你的朋友穿一条裤子,你把密码告诉了他(你偷偷把银行卡的密码复制给了他)那么他也可以取出来了钱。
代表嘉宾:class, array
示例:
cpp
int a = 10; // 直男值类型:揣着10块钱现金
int b = a; // 克隆人战争,b是a的复制体
b = 20; // 改b不会让a变成土豪
List<int> list1 = new List<int>(); // 引用类型开豪宅
List<int> list2 = list1; // 渣男行为:共享同一套房
list2.Add(42); // 现在list1也被迫拥有42了!
示例:
cpp
//1.使用上的区别
//值类型
int a = 10;
//引用类型
int[] arr = new int[] {1,2,3,4,5 };
//申明了一个b等于a
int b = a;
//申明了一个arr2让其等于之前的arr
int[] arr2 = arr;
Console.WriteLine("a={0},b={1}", a, b);
Console.WriteLine("arr[0]={0},arr2[0]={1}", arr[0], arr2[0]);
b = 20;
arr2[0] = 5;
Console.WriteLine("修改后");
Console.WriteLine("a={0},b={1}", a, b);
Console.WriteLine("arr[0]={0},arr2[0]={1}", arr[0], arr2[0]);
//值类型 在相互赋值时 把内容拷贝给了对方 他变我不变
//引用类型的相互赋值 是 让二者指向同一个值 它变我也变 数组 string 类
//2.为什么有以上区别
//二者的存储的内存区域不同 存储方式不同 故使用上有区别
//值类型 存储在 栈空间 - 系统分配,自动回收,小而快
//引用类型 存储在堆空间 - 手动申请和释放,大而慢
string str = "123";
string str2 = str;
str2 = "321";
Console.WriteLine(str);
Console.ReadKey();
第二章:string的"薛定谔式"生存法则
string同学作为引用类型家族的叛徒,活出了精分现场:
-
表面身份 :根正苗红的引用类型
身份证上写着"System.String",住在堆区豪宅里。
-
实际行为:
- 不可变の强迫症:每次修改都像在墙上贴瓷砖------必须拆了旧墙建新房!
- 假装值类型:复制时宛如克隆人,和原对象老死不相往来。
- **== 运算符的叛变**:其他引用类型用==比地址,string却开始比内涵(值内容)!
//string 的他变我不变
//string非常特殊 它具备 它变我不变
//string 虽然方便 但是频繁的 改变string 重新赋值会产生内存垃圾
cs
string A = "我是原配";
string B = A; // 此时B拿着和A一样的房卡(引用地址)
B = "我是小三"; // 此时A依然坚贞不屈:"我是原配"
表面现象:
看起来像是深拷贝(复制值),B改嫁后A不受影响,仿佛string有「值类型の纯洁」。
真相1: 赋值时它就是个普通引用类型!
B和A最初共享同一块内存(指向堆里的"我是原配"),和所有引用类型一样发的是房卡复印件。
真相2: 一切改变都是「假装努力」!
当B试图修改时,string的不可变性(Immutable) 强迫症发作:
👉 系统在堆里新建豪宅"我是小三"
👉 把B的房卡偷偷换成新地址
👉 而A的房卡还是旧地址,自然不受影响
这就像:
你和朋友合租时,你突然暴富买了别墅搬走,但室友依然住在老破小------不是因为你们分行李了,而是你直接换房了!
- 普通引用类型:共享同一套房,装修直接改原房(浅拷贝)
- string:表面共享房卡,实际变心就换房(不可变+新建对象)
string的「深拷贝错觉」= 引用类型的身子 + 值类型的命 + 不可变的病!
示例:
cs
string s1 = "Hello";
string s2 = s1; // 此时s2是s1的舔狗
s2 = "World"; // 渣男突然自立门户,s1还是单身贵族
string s3 = "Hello";
string s4 = "Hello";
// 此时s3和s4在堆里上演"替身文学",共享同一个"Hello"(拘留池机制)
第三章:当它们去参加《非诚勿扰》(参数传递篇)
-
值类型女嘉宾 :
"我的数据我自己带!"(默认值传递)
牵手时直接克隆一个自己送过去,原版继续在舞台单身。
-
引用类型男嘉宾 :
"我只给你我家钥匙~"(默认引用传递)
女嘉宾拿到钥匙后能随便装修房子,甚至能把家具全卖了!
彩蛋时刻------当ref来搅局:
值类型被ref修饰后,瞬间黑化成"地址传递",开始共享闺房密码!
ref的好朋友out我们后面再说,这其实是在函数里面用到的,来帮助我们将函数内修改的值传递到函数外面去。所以说呢,这也算是个好僚机。当你正苦恼怎么获得女神的微信时候,这个时候女神的室友直接祝你一臂之力,只需要你用ref收买她,她就偷偷的把你的女神的微信号给偷出来了。
小彩蛋:
第一阶段:基础篇(无ref时)
cs
void 追求女神()
{
string 女神微信号 = null;
室友帮忙偷号(女神微信号);
Console.WriteLine(女神微信号); // 输出:null (惨遭失败!)
}
void 室友帮忙偷号(string 微信号)
{
微信号 = "LoveU3000"; // 修改的是局部变量副本
}
剧情解析:
此时室友是「猪队友」,在函数内改了微信号的副本,但原始变量仍是null。就像您托人递情书,结果TA自己抄了一份,把原件扔了。
第二阶段:ref僚机觉醒篇
cs
void 追求女神()
{
string 女神微信号 = null;
室友帮忙偷号(ref 女神微信号); // 递上ref接头暗号
Console.WriteLine(女神微信号); // 输出:"LoveU3000" (成功!)
}
void 室友帮忙偷号(ref string 微信号)
{
微信号 = "LoveU3000"; // 直接修改原变量的内存地址
}
ref的隐藏规则:
- 必须提前和女神搭讪过(变量需先初始化)
- 室友(ref)不是凭空变出微信号,而是修改你们共同关注的聊天窗口
第三阶段:out僚机的叛变形态
cs
void 追求女神()
{
string 女神微信号; // 未初始化!
室友帮忙偷号(out 女神微信号); // out接头更刺激
Console.WriteLine(女神微信号); // 输出:"LoveU3000"
}
void 室友帮忙偷号(out string 微信号)
{
// 必须在这里赋值,否则编译器举起40米大刀!
微信号 = "LoveU3000";
}
out vs ref 核心区别:
- ref:您得先有个目标(变量已初始化),室友助攻修改
- out:您连目标都没有(变量未初始化),室友直接塞个新人给您
暴击总结(配代码注释)
cs
// ref是「改良派」:带着已有方案找人优化
int 存款 = 100;
女神理财顾问(ref 存款); // 存款可能变200或归零
// out是「革命派」:不管三七二十一必须给结果
int 结果;
女神占卜师(out 结果); // 结果必定被赋值
小结:
- ref:女神的闺蜜,需要您先请她喝奶茶(初始化),她才透露情报
- out:女神的宿敌,不需要您付出任何代价,但会强制给您情报(必须赋值)
总结:如何一眼认出它们的真面目?
-
灵魂拷问:"你变了吗?"
- 值类型:我变任我变,关原对象什么事?
- 引用类型:我变就是全家变!
- string:我变就是开分基地!
-
祖传口诀:
值类型在栈上跑,复制就是深拷贝
引用类型堆里笑,地址传递真风骚
string是个两面派,表面引用实则菜(值)
散场彩蛋
下次看到string时请尊称一声"钮祜禄·string",毕竟人家可是从引用类型底层杀出一条血路,活成了白月光般的存在!
(完)今天的学习就到此为止吧,学C++去了!咱们以后再见