C#数据类型入门:从变量声明到类型转换与WinForms实战
简介:围绕 C# 强类型语言特性整理的实验文档,面向刚接触 .NET 编程的初学者以及需要完成课程实验的学生,聚焦数据类型分类与变量声明使用这一基础环节。文档以一次完整的 Windows 窗体实验为主线,先梳理 byte、sbyte、short、int、long 等整型,float、double、decimal 等浮点型,以及 char、bool、string 的取值范围与精度差异,再通过 FormLoad 初始化 ComboBox、在 SelectedIndexChanged 中按 switch 分支调用 sizeof 与 MaxValue/MinValue 的写法,把抽象的内存占用与取值边界变成界面上可切换、可对比的直观数据。资源包仅含 1 个 doc 文件,约 1.21MB,篇幅集中,便于打印或随堂对照。已有 353 人学习下载。读者可据此掌握变量声明与初始化的规范写法,理解各类型的存储字节数与极值,减少实际编码中选型不当引发的溢出与精度问题。
1. 一个让新手发怵的问题:为什么数字还要分类型
刚开始学 C# 的人,十个里有八个会在第一节课卡住:明明写个数字,为什么还要挑int还是double?这个问题的答案就是数据类型。说白一点,数据类型是给内存里的数据提前定好一个“盒子规格”——能装多大的数、带不带小数、占几个字节、取值范围到哪儿,都得在声明变量那一刻说清楚。C# 是强类型语言,你不写类型,编译器直接不让你过,这不是刁难,是为了让后面的运算不出莫名其妙的错。这份内容面向的是刚装好 VS 2008、连窗体控件都没拖过几次的零基础同学:先讲清 13 个常用类型长什么样,再带你写第一个控制台小程序,最后做一个能点、能跑、能自己验证答案的窗体程序。学完你至少能判断一件事——这段代码该用哪个类型,而不是凭感觉随便挑一个。
2. 数据类型的分类:13 个常用类型先装进脑子
打开 VS 2008 新建一个 C# 项目,你还没开始写任何逻辑,编译器就已经在等你交代类型了。这一章不讲深奥的内存模型,只做一件事:把 C# 里最常用的那批类型,按“整型、浮点、字符、布尔、文本”分好类,让你看到int、float、decimal这些词时不再发懵。
2.1 为什么 C# 非要你先说清楚类型
想象你去寄快递,工作人员第一句话一定问:寄什么、多大、要不要保价。因为盒子拿大了浪费,拿小了装不下。数据类型干的就是这件事。你声明一个变量,等于向内存申请一块空间,类型决定了这块空间多大、里面允许放什么。
C# 把“必须说清楚类型”写进了语言规则里,这叫强类型。好处很直接:编译器能在你按下运行之前,就告诉你“这里塞不进去”。比如你拿一个byte变量去装 300,编译阶段或者运行阶段就会拦下来,而不是像某些弱类型语言那样,悄悄给你一个错误的结果,让你排查半天。
入门阶段最该建立的一个直觉是:类型选小了会溢出,选大了浪费空间,选错类别会算出错误答案。举个真实的场景,用double去算钱,0.1 + 0.2算出来可能是0.30000000000000004,账目对不上,你还以为是哪里逻辑写错了。这类问题在网上被戏称为“玄学”,其实根子就在类型上。
那怎么知道每种类型占多大、能装多少?C# 给了两个现成的工具:sizeof(类型)返回这个类型占用的字节数,类型.MaxValue和类型.MinValue返回它的上下限。这两个东西是后面做那个窗体程序的核心,先记住它们的名字就够了。
2.2 整型家族:byte 到 ulong,先分清有符号和无符号
整型就是“只能装整数”的类型,一共 8 个,按字节数从 1 排到 8。细分下来只有两个维度:占几个字节,以及带不带负号。
带负号的叫有符号整型,前缀是s(signed):sbyte、short、int、long。不带负号、只能装 0 和正数的叫无符号整型,前缀是u(unsigned):byte、ushort、uint、ulong。
| 类型 | 字节数 | 大致范围 | 新手常见用途 |
|---|---|---|---|
byte | 1 | 0 ~ 255 | 图像像素值、年龄、状态码 |
sbyte | 1 | -128 ~ 127 | 少见,某些底层数据 |
short | 2 | -32768 ~ 32767 | 少见,节省空间的场合 |
ushort | 2 | 0 ~ 65535 | 端口号一类的小正数 |
int | 4 | 约 -21 亿 ~ 21 亿 | 最常用的整数,默认选它 |
uint | 4 | 0 ~ 约 42 亿 | 明确不会为负的计数 |
long | 8 | 约 -9.2e18 ~ 9.2e18 | 时间戳、超大计数 |
ulong | 8 | 0 ~ 约 1.8e19 | 极少用,极大正数 |
这张表不用背,但有两个点必须记住。第一,int是整数的默认类型,你随手写一个100,编译器认为它就是int。第二,byte最大只有 255,这个数字在后面的实验里会反复出现,因为稍不注意就会踩到上限。
为什么要有这么多整型?答案是空间和范围的取舍。byte只占 1 个字节,long要 8 个字节,同样的数据量下内存占用差 8 倍。日常写业务代码,int能覆盖九成场景,剩下的交给long和byte就够了,sbyte、ushort、ulong这些更多是在特定场景里出现。
2.3 浮点与 decimal:带小数的三个选手怎么选
只要涉及小数,就轮到float、double、decimal出场了。这三个类型的区别,新手最容易搞混,我用一张表先摆清楚。
| 类型 | 字节数 | 有效数字 | 字面量写法 | 适合场景 |
|---|---|---|---|---|
float | 4 | 约 7 位 | 3.14f,必须带 f | 图形、游戏里对精度要求不高的量 |
double | 8 | 约 15 ~ 17 位 | 3.14,默认就是它 | 科学计算、一般小数运算 |
decimal | 16 | 约 28 ~ 29 位 | 3.14m,必须带 m | 金额、财务、需要精确小数的场合 |
三个类型里,double是默认的浮点类型,你写3.14不加后缀,编译器就当它是double。想写float必须加f,想写decimal必须加m,不加就报错,这是新手最常遇到的第一个编译错误。
那为什么算钱要用decimal?因为float和double在计算机内部是用二进制近似表示小数的,有些十进制小数根本没法用二进制精确表达。0.1就是典型例子,存进去的时候就已经有一点点误差了,累加多次之后误差会被放大。decimal用的是另一种表示方式,牺牲了运算速度换来十进制下的精确,所以金额计算必须用它。
提示:入门阶段记一句就够——带小数的一律先想
double,只要跟钱有关就换成decimal,float除非明确要求否则先别碰。
2.4 char、bool、string:单引号、真假值和文本
剩下三个类型,各有各的脾气。
char表示单个字符,占 2 个字节,用的是 Unicode 编码,所以中文、日文、各种符号都能装进去。写法上必须用单引号包起来,比如'A'、'中'、'3'。注意'3'是字符,3是整数,两者不是一回事。
bool只有两个值:true和false,占 1 个字节。C# 里不能用 0 和 1 代替,这一点和 C 语言不同,if (1)这种写法在 C# 里直接编译不过。新手从别的语言转过来,最容易在这儿卡一下。
string用来存文本,用双引号包起来,比如"你好"。严格来说它属于引用类型,不在那 13 个基本类型里,但你每天都在用,绕不开。它可以装任意长度的文本,也可以和别的类型拼接,后面写输出语句时基本离不开它。
单引号和双引号的区别,我见过的翻车案例不止一次:把"A"写成'A'赋给string,或者把'A'写成"A"赋给char,编译器都会报类型不匹配。写的时候多看一眼引号,能省不少时间。
2.5 值类型与引用类型:一句话先分清
上面讲的整型、浮点、char、bool都属于值类型,string属于引用类型。入门阶段不需要深挖背后的内存布局,先建立一个粗略的直觉就行。
值类型赋值时,是把数据本身复制一份。你写int a = 5; int b = a;,之后改b不影响a,因为它们是两个独立的盒子。引用类型赋值时,复制的是“指向同一份数据的线索”,两个变量指向同一段文本,改其中一个,另一个看到的也会变。
这个区别在入门阶段影响不大,但在你开始传参数、改对象属性的时候会突然冒出来。现在记住“值类型存数据本身,引用类型存指向数据的地址”这句话,等以后遇到“我明明只改了一个变量,另一个怎么也跟着变了”这类问题时,再回来翻这一节。
3. 变量怎么用:从一行 int age = 25; 开始
类型是盒子的规格,变量就是你给这个盒子起的名字。这一章把声明、命名、常量这几件事讲透,最后用一个能直接跑的控制台程序,把前面学的类型全部用一遍。
3.1 声明、赋值、声明并赋值
变量的使用有三种写法,新手最好按顺序都试一遍。
// 写法一:先声明,后赋值 int age; age = 25; // 写法二:声明的同时赋值,最常用 int count = 10; // 写法三:一行声明多个同类型变量 int a = 1, b = 2, c = 3;第一种写法要注意:声明了但没赋值,这个变量就是“未初始化”状态,C# 不允许你直接读它的值,编译会报“使用了未赋值的局部变量”。这是好事,避免你拿到一个莫名其妙的内存残留值。第二种写法最省事,日常写代码基本都是这种。第三种在需要一组同类变量时方便,但一多就不好读,建议控制在三个以内。
赋值之后,变量的值是可以改的,只要新值还在这个类型的范围内。age = 26;没问题,age = 300;就会出问题,因为int装不下 300 吗?不,int装得下,是我举错例子了——byte装不下 300。这也说明一件事:改值之前,心里要清楚这个类型的上限在哪。
注意:变量的类型一旦声明就不能改。
int age = 25;之后你想把它变成string,只能重新声明一个新变量,不能就地转换类型。
3.2 命名规则:编译器不让你乱起名
变量名不是想叫什么叫什么,C# 有几条硬规则:
- 必须以字母、下划线或
@开头,不能以数字开头,所以1name是错的,name1是对的。 - 只能包含字母、数字和下划线,不能有空格、减号、小数点。
- 不能和 C# 的关键字重名,比如
int、class、if这些词不能当变量名。如果非要用,前面加@,写成@int,但强烈不建议。 - 区分大小写,
age和Age是两个不同的变量。
除了硬规则,还有几条软习惯,早点养成受益很久。私有变量和局部变量用小驼峰,首字母小写,后面每个单词首字母大写,比如maxValue、userName。方法名和类名用大驼峰,首字母大写。名字要有意义,a、b、temp这种名字在代码超过二十行之后基本等于没起名。
我第一次写窗体程序的时候,控件全用默认的textBox1、textBox2,结果代码写到一半完全分不清哪个框是显示最大值的。后来血泪经验告诉我,控件一拖上去第一件事就是改名,改成tbMax、tbMin、tbSize这种一眼能看懂的。
3.3 const 常量和 var:什么时候用
有两个关键字入门阶段就该认识。
const用来声明常量,一旦赋值就不能再改,而且必须在声明的时候就把值写上。
const double Pi = 3.14159; // Pi = 3.14; // 这行会编译报错,常量不允许二次赋值什么时候用常量?当某个值在整个程序里固定不变、又反复出现的时候。比如税率、最大重试次数、固定的提示文本。用常量代替散落各处的魔法数字,改的时候只改一处,能少踩很多坑。
var是隐式类型,声明的时候不用写类型,编译器根据右边的值自动推断。
var count = 10; // 推断为 int var price = 9.9; // 推断为 double var name = "新手"; // 推断为 string注意var不是“随便什么类型都行”,它一旦推断出类型就固定了,count = "abc";照样报错。新手容易把var当成弱类型的万能钥匙,其实它只是少写几个字母。入门阶段我建议少用,先把类型写全,等对类型有感觉了再用。
3.4 第一个能跑的例子:控制台打印 6 个变量
讲了这么多概念,该动手了。这个例子不涉及窗体,纯控制台,用来验证你对类型的直觉。
在 VS 2008 里新建一个“控制台应用程序”项目,把默认生成的代码替换成下面这段。
using System; class Program { static void Main() { // 每种类型声明一个变量,值都在合法范围内 byte b = 200; // 1 字节,0~255 int age = 25; // 4 字节,最常用的整数 double price = 9.9; // 8 字节,默认浮点类型 char grade = 'A'; // 2 字节,单引号包一个字符 bool isReady = true; // 1 字节,只能是 true 或 false string name = "新手"; // 文本,双引号 // 依次打印变量的值 Console.WriteLine(b); Console.WriteLine(age); Console.WriteLine(price); Console.WriteLine(grade); Console.WriteLine(isReady); Console.WriteLine(name); // 打印类型信息:字节数、最大值、最小值 Console.WriteLine(sizeof(int)); // 4 Console.WriteLine(int.MaxValue); // 2147483647 Console.WriteLine(int.MinValue); // -2147483648 Console.WriteLine(sizeof(decimal)); // 16 Console.ReadLine(); // 让窗口停住,等你按回车 } }逐行解释一下关键部分。前六行是六种类型的声明加赋值,每一行都遵循“类型 名字 = 值;”的格式,byte后面跟的是整数,char后面是单引号,string后面是双引号,bool后面只能是true或false。
中间六行Console.WriteLine(...)是输出语句,把变量的值打到控制台窗口里,每个值占一行。
最后四行是验证类型信息的。sizeof(int)返回int占用的字节数,结果是 4。int.MaxValue和int.MinValue分别返回int能表示的最大值和最小值,这两个数字在后面的窗体实验里会以文本框的形式显示出来,现在先在控制台里看一眼,混个脸熟。sizeof(decimal)返回 16,顺便感受一下为什么说decimal占空间。
按 F5 运行,你应该看到八行输出。如果某一行报错,九成是这几种情况:浮点数没加后缀、char用了双引号、bool写了 1 或 0。对照报错信息改一下,这个坎过了,类型的基本用法就通了。
4. 第一个能跑的实战:用 WinForms 做一个类型信息查询器
控制台只能看,窗体程序能点。这一章带你从零做一个“类型信息查询器”:左边下拉框选一个类型,右边三个文本框立刻显示它的字节数、最大值、最小值。做完这个,你对 13 个整型和浮点类型的印象会比看十遍表格都深。
4.1 建工程、铺控件、给控件改名字
打开 VS 2008,选择“文件 → 新建 → 项目”,模板选“Windows 窗体应用程序”,随便起个名字,确定。你会看到一个空白的窗体,工具箱在左边。
从工具箱里往窗体上拖这些控件:
- 4 个
Label,用来写说明文字,比如“数据类型”“字节数”“最大值”“最小值”。 - 1 个
ComboBox,下拉框,用来选类型。 - 3 个
TextBox,文本框,分别显示字节数、最大值、最小值。
拖完之后,最关键的一步来了:改名字。选中一个控件,在右下角的属性窗口里找到(Name)这一项,按下面的规则改:
| 控件 | 改成 | 作用 |
|---|---|---|
| ComboBox | cmbDataType | 选类型 |
| 第一个 TextBox | tbSize | 显示字节数 |
| 第二个 TextBox | tbMax | 显示最大值 |
| 第三个 TextBox | tbMin | 显示最小值 |
剩下的 4 个 Label 可以改成lblType、lblSize、lblMax、lblMin,也可以不改,因为它们不参与代码逻辑。但 TextBox 和 ComboBox 必须改,否则后面写代码时你根本分不清textBox1和textBox2谁是谁。
顺手把三个 TextBox 的ReadOnly属性设成True,这样运行时只能看不能改,避免误操作。再调整一下控件位置,让界面看起来别太挤。
4.2 Form_Load:把 13 个类型塞进下拉框
窗体一打开就要把下拉框填满,这个动作放在Form_Load事件里。双击窗体空白处,VS 会自动生成Form1_Load方法,把代码填进去。
private void Form1_Load(object sender, EventArgs e) { // 先清空,防止重复加载时出现重复项 cmbDataType.Items.Clear(); // 把 13 个常用类型名放进字符串数组 string[] strType = { "byte", "sbyte", "short", "ushort", "int", "uint", "long", "ulong", "float", "double", "decimal", "char", "bool" }; // 一次性把数组里的所有项加进下拉框 cmbDataType.Items.AddRange(strType); }这段代码做了三件事。Items.Clear()是清空下拉框,虽然窗体第一次加载时本来就是空的,但写上更安全,万一以后要重新加载不会出现两份重复项。string[] strType是一个字符串数组,把 13 个类型名当成文本存起来。Items.AddRange(strType)把整个数组一次性灌进下拉框。
这里有个细节值得停一下:数组里存的是字符串,不是类型本身。也就是说,下拉框里显示的"int"只是一个文本标签,程序并不知道它代表哪个类型。后面要靠switch语句,根据这段文本来决定显示哪个类型的信息。这是入门阶段一种很直白的做法,等学到枚举,可以把它替换掉,那是后面的事。
运行一下,点开下拉框,应该能看到 13 个选项。如果只有几个或者一个都没有,检查一下数组是不是写漏了,或者AddRange是不是写成了Add——Add一次只能加一个,传数组进去会变成加一个奇怪的字符串。
4.3 下拉框变化:把字节数、最大值、最小值填进去
现在要做的,是让用户在下拉框里选一个类型时,三个文本框立刻更新。这个动作绑定在SelectedIndexChanged事件上。
选中cmbDataType,在属性窗口里点那个闪电图标(事件列表),找到SelectedIndexChanged,双击,VS 会自动跳到代码位置。
private void cmbDataType_SelectedIndexChanged(object sender, EventArgs e) { // 防止窗体初始化阶段选中项为空导致空引用 if (cmbDataType.SelectedItem == null) return; string strSel = cmbDataType.SelectedItem.ToString(); if (strSel.Length < 1) return; switch (strSel) { case "byte": tbSize.Text = sizeof(byte).ToString(); // 1 tbMax.Text = byte.MaxValue.ToString(); // 255 tbMin.Text = byte.MinValue.ToString(); // 0 break; case "sbyte": tbSize.Text = sizeof(sbyte).ToString(); // 1 tbMax.Text = sbyte.MaxValue.ToString(); // 127 tbMin.Text = sbyte.MinValue.ToString(); // -128 break; case "int": tbSize.Text = sizeof(int).ToString(); // 4 tbMax.Text = int.MaxValue.ToString(); // 2147483647 tbMin.Text = int.MinValue.ToString(); // -2147483648 break; case "decimal": tbSize.Text = sizeof(decimal).ToString(); // 16 tbMax.Text = decimal.MaxValue.ToString(); tbMin.Text = decimal.MinValue.ToString(); break; case "char": tbSize.Text = sizeof(char).ToString(); // 2 // char 的最大最小值是不可见字符,转成整数才好显示 tbMax.Text = Convert.ToInt32(char.MaxValue).ToString(); // 65535 tbMin.Text = Convert.ToInt32(char.MinValue).ToString(); // 0 break; case "bool": tbSize.Text = sizeof(bool).ToString(); // 1 tbMax.Text = bool.TrueString; // True tbMin.Text = bool.FalseString; // False break; // 其余类型(short、ushort、long、ulong、float、double) // 照着上面的格式补全即可,把类型名换掉就行 } }这段代码的逻辑很直白:先拿到当前选中的字符串,然后用switch逐项比对,命中哪个就填哪一组值。
几个地方要解释清楚。开头的if (cmbDataType.SelectedItem == null) return;是防御性写法。窗体刚加载、还没选中任何项的时候,SelectedItem是空的,直接调ToString()会抛空引用异常。这一行加上,能挡掉一类很烦的报错。
sizeof(类型)返回字节数,是个整数,得用.ToString()转成字符串,才能赋给TextBox.Text。类型.MaxValue和类型.MinValue返回的是该类型的极值,同样要转字符串。
char这个分支要特别说明。char.MaxValue确实存在,但它对应的是 Unicode 里的一个不可见字符,直接ToString()塞进文本框,你看到的是空白,还以为是代码没生效。所以这里用Convert.ToInt32()把它转成数字,65535 和 0,一眼就能看懂。
bool更特殊,它没有MaxValue和MinValue,只有bool.TrueString(值是"True")和bool.FalseString(值是"False")。这两个属性返回的是字符串,直接赋给文本框就行,不需要再转换。
剩下的 6 个类型我没有全部写出来,留给你照着补。别嫌麻烦,手敲一遍这 13 个分支,你对sizeof、MaxValue、MinValue这三个东西的印象会比抄一遍深得多。
4.4 编译运行,用已知答案对拍
按 F5 运行,下拉框里选byte,三个文本框应该分别显示1、255、0。选int,显示4、2147483647、-2147483648。选decimal,字节数是16,最小值是一长串负数。
怎么确认自己写对了?用控制台版本对拍。第 3 章那个程序里已经打印过sizeof(int)、int.MaxValue、int.MinValue,拿窗体显示的结果和它比一比,一致就说明逻辑没问题。
这一步别跳过。我见过不少人代码写完直接交,结果tbMax和tbMin的位置填反了,显示出来的最小值比最大值还大,自己都没发现。跑起来看一眼,比盯着代码检查快十倍。
再补一个自测技巧:把每个类型的字节数加起来验证。byte、sbyte、char、bool各 1 个字节(char是 2 个字节,注意),short、ushort各 2 个,int、uint、float各 4 个,long、ulong、double各 8 个,decimal是 16 个。如果你做出来char显示 1,那就是把sizeof(char)和sizeof(byte)搞混了。
4.5 再加一个窗体:类型转换候选表
第一个窗体做完,你已经能查类型信息了。第二个窗体做的是另一件事:选了源类型,再选一个目标类型,看看这两个类型之间能不能转。
在解决方案里右键,添加一个新的 Windows 窗体。在新窗体上放两个ComboBox(cmbType和cmbConvertType)、两个RadioButton(一个表示隐式转换,一个表示显式转换)、一个TextBox(tbInput)、一个Button(btnConvert)、还有一个TextBox(tbOutput)。
先讲清楚什么叫隐式转换。简单说,小范围类型往大范围类型赋值,编译器自动帮你转,不用写额外代码,这叫隐式转换。比如byte转int,255 在int的范围里稳稳装得下,编译器放心。反过来,int转byte就得你自己动手,这叫显式转换,后面再讲。
所以第二个窗体的逻辑是:在类开头定义一组数组,每个数组记录“这个类型能隐式转换成哪些类型”。
// 这些数组写在窗体类的开头,方法外面 string[] impbyte = { "short", "ushort", "int", "uint", "long", "ulong", "float", "double", "decimal" }; string[] impsbyte = { "short", "int", "long", "float", "double", "decimal" }; string[] impshort = { "int", "long", "float", "double", "decimal" }; string[] impushort = { "int", "uint", "long", "ulong", "float", "double", "decimal" }; string[] impint = { "long", "float", "double", "decimal" }; string[] impuint = { "long", "ulong", "float", "double", "decimal" }; string[] implong = { "float", "double", "decimal" }; string[] impulong = { "float", "double", "decimal" }; string[] impchar = { "ushort", "int", "uint", "long", "ulong", "float", "double", "decimal" };看impbyte这一行,意思是byte可以隐式转成short、ushort、int、uint、long、ulong、float、double、decimal。规律很清楚:byte范围是 0 到 255,只要目标类型的范围能完全覆盖它,就能隐式转。sbyte因为有负数,不能转byte和ushort,所以impsbyte里少了这两个。
然后是cmbType的SelectedIndexChanged事件,根据选中的源类型,把对应的数组灌进第二个下拉框。
private void cmbType_SelectedIndexChanged(object sender, EventArgs e) { if (cmbType.SelectedItem == null) return; string strtemp = cmbType.SelectedItem.ToString(); cmbConvertType.Items.Clear(); switch (strtemp) { case "byte": cmbConvertType.Items.AddRange(impbyte); break; case "sbyte": cmbConvertType.Items.AddRange(impsbyte); break; case "int": cmbConvertType.Items.AddRange(impint); break; // 其余类型照此格式补全 default: break; } }逻辑和第一个窗体完全一样:拿到选中项,switch分流,往第二个下拉框里加数据。区别在于这次加的是“能转成谁”,而不是“这个类型的信息”。
最后是转换按钮的代码,这里先写一部分,把byte这一支走通。
private void btnConvert_Click(object sender, EventArgs e) { string strtemp = cmbType.SelectedItem.ToString(); if (strtemp == "byte") { // 先用 long 接收输入,long 的范围比 byte 大得多 long i = long.Parse(tbInput.Text); // 判断输入值是否超出 byte 的范围 if (i > byte.MaxValue || i < byte.MinValue) { MessageBox.Show("输入的数字超出了 byte 的范围"); return; // 超范围直接返回,不往下走 } byte b = byte.Parse(tbInput.Text); if (cmbConvertType.SelectedItem.ToString() == "short") { short r = b; // byte 隐式转 short,直接赋值 tbOutput.Text = r.ToString(); } else if (cmbConvertType.SelectedItem.ToString() == "ushort") { ushort r = b; // byte 隐式转 ushort,直接赋值 tbOutput.Text = r.ToString(); } } }这段代码里有个设计值得琢磨:为什么先用long.Parse读输入,再用范围判断,最后才byte.Parse?
因为如果直接byte.Parse("300"),程序会抛一个溢出异常,窗体直接崩掉。先转成long,long装 300 毫无压力,然后拿它跟byte.MaxValue比,超了就弹个提示框,用户可以重新输入,程序不会挂。这个套路在实际开发里很常见,叫做“先接住,再校验,最后才转换”。
if (cmbConvertType.SelectedItem.ToString() == "short")这一行是在判断用户选的目标类型。byte b赋值给short r的时候,你什么都没写,编译器自动就转了,这就是隐式转换的具体体现。当你把 13 个源类型的分支都补完,这个窗体就成了一台“转换关系查询机”,选什么类型,就告诉你它能隐式转成谁。
5. 避坑与常见问题:这 5 个地方新手最容易翻车
这一章不长,但每条都是真实会卡住人的地方。遇到问题先来这里对一下,大概率能省下半小时。
5.1 选了下拉框没反应,三个框一直是空的
现象:程序能跑,下拉框也能点开,但不管选哪一项,右边的文本框一点变化都没有。
原因:SelectedIndexChanged事件没绑上。很多新手双击了窗体,把代码写进了Form_Load,或者双击了下拉框但写进了Click事件,代码本身没错,就是没被触发。
解决:回到设计视图,选中cmbDataType,在属性窗口点闪电图标,找到SelectedIndexChanged,确认右边有方法名。如果空着,双击一下重新生成,再把之前的代码粘进去。顺手在方法第一行加一句MessageBox.Show("触发了");,运行选一下,弹框出来就说明绑定成功,看完记得删掉。
5.2 下拉框里有个选项,选了却什么都没显示
现象:下拉框列表里明明有 13 项,选前面几项正常,选到某一项时三个文本框全空。
原因:strType数组里的拼写和switch里的case对不上。原文的实验代码里就出现过这个问题,数组里写成了"unint",case里写的是"uint",字符串比对失败,直接跳出switch,没有任何分支命中,文本框就保持空白。
解决:把数组里的 13 个类型名和switch里的 13 个case逐字对照一遍。这种错误编译器不会报,因为两边都是合法的字符串,只能靠肉眼或者运行时观察发现。养成习惯:这类靠字符串匹配的逻辑,写完一定要每一项都点一遍。
5.3 输入 300 转 byte,程序直接崩了
现象:在第二个窗体的输入框里填 300,点转换按钮,弹出一个未处理异常的窗口,程序直接退出。
原因:byte.Parse("300")在运行时发现 300 超出byte的范围,抛出了溢出异常。如果没有try-catch或者前置判断,异常没人接,程序就崩了。
解决:照 4.5 节的写法,先用范围更大的类型接住输入,判断范围之后再做转换。另一种做法是用byte.TryParse,它不会抛异常,而是返回一个布尔值告诉你转换成没成功,转换结果通过out参数带出来。两种写法都行,入门阶段先用范围判断这种,逻辑最直观。
5.4 改了控件名字,代码里全是红波浪线
现象:把textBox1改名成tbSize之后,原来能跑的代码突然一片红,提示“当前上下文中不存在名称 textBox1”。
原因:改的是控件的(Name)属性,但代码里还写着旧名字。VS 不会自动帮你全改,尤其是你手写的那部分代码。
解决:改完控件名之后,立刻在代码里搜一遍旧名字,全部替换。VS 有全局替换功能(Ctrl+H),比一个个找快。更好的习惯是拖控件的时候就改名,别等代码写完再改,那时候要改的地方就多了。
5.5 用 double 算钱,算出来的数差了几分
现象:两个金额相加,结果显示19.990000000000002这种尾巴,四舍五入之后跟手工算的对不上。
原因:double用二进制近似表示小数,某些十进制小数无法精确表达,误差在多次运算后累积放大。
解决:只要涉及金额,一律用decimal,字面量后面加m。decimal a = 9.99m; decimal b = 10.00m;这样算出来是精确的19.99。代价是运算速度慢一些、占 16 个字节,但对账目来说这点代价完全值得。
6. 下一步学什么:把转换补完,再用枚举替掉字符串数组
第一个窗体和第二个窗体跑通之后,你已经摸到了 C# 类型系统的门槛。接下来该往哪走,我给一条具体的路线,都是能立刻上手的。
先把第二个窗体的btnConvert_Click补完整。4.5 节只写了byte这一支,而且只处理了转short和ushort两种情况。你需要把 13 个源类型都补上,每一种再覆盖它所有能转的目标类型。补的过程中,你会自然遇到显式转换和Convert类的区别。
long big = 300; byte small = (byte)big; // 显式强制转换,300 超出范围,结果被截断 int n = Convert.ToInt32("123"); // Convert 类,字符串转整数 int m = int.Parse("123"); // Parse,效果类似,但转换失败时行为不同(byte)big这种写法叫强制转换,你在括号里写目标类型,编译器照做,但超出范围时不会报错,会给你一个截断后的结果,这叫“翻车不留痕”,最难查。Convert.ToInt32和int.Parse在失败时会抛异常,用哪个看场景:确定数据没问题就用Parse,不确定就用TryParse。
补完转换之后,去做实验里留的第二个作业:用枚举替换字符串数组。现在你的switch是靠"byte"、"int"这些字符串来匹配的,拼错一个字母就静默失败。改成枚举之后,编译器会帮你检查拼写,写错了直接报错,这就是枚举的价值。
enum DataType { Byte, SByte, Short, UShort, Int, UInt, Long, ULong, Float, Double, Decimal, Char, Bool } // 用枚举之后,switch 里写的是 DataType.Byte,不再是字符串再往后,可以看看TryParse系列方法、装箱和拆箱、string和StringBuilder的区别。这些内容不用急着一次学完,先把手上这两个窗体做扎实。
我自己有个习惯,是从踩坑里养出来的:每次写完一个涉及类型转换的方法,都强制拿边界值跑一遍——最小值、最大值、比最大值大 1、比最小值小 1,四个用例跑完才敢说这段代码没问题。这个习惯帮我挡掉过不少上线后才发现的溢出问题。希望帮到你。
本文还有配套的精品资源,点击获取