本文用于C#学习引导入门,运用的编译器是visual studio2022,主要涉及认识C#、C#的变量、C#的运算符、C#的流程控制;通过文字解析和相关代码示例进行讲解,简洁明了初步理解C#,对C#有个初步认识,可以做到上手写代码。
一、对C#的认识
官方来讲:C#(读作 C Sharp)是微软推出的面向对象、类型安全、现代化编程语言,专门搭配 .NET 平台使用。他是一门编程语言,与C语言、C++、JAVA类似,主要用于程序员与计算机通过编译器等进行交流,类似人类与计算机进行交流的翻译官。C#的主要用途有:
- 控制台程序 简单工具、脚本、后台处理程序。
- Windows 桌面软件 WinForms、WPF,电脑桌面客户端(管理系统、工具软件)。
- 网站 & Web 后端 ASP.NET Core,企业后台接口、网站、管理平台,跨 Windows/Linux 部署。
- 游戏开发(最热门用途) 搭配 Unity 引擎,90% 手游、独立游戏、主机游戏都用 C#。
- 移动端 App MAUI / Xamarin,一套代码同时开发安卓、苹果手机软件。
- 物联网、嵌入式、桌面原生程序 .NET Native AOT 可编译成无依赖独立程序,适合硬件设备。
- 微服务、云服务、异步高并发项目 自带 Task 异步、完善线程与内存管理,企业后端主流选型。
这些内容大家简单看看就好,对于面向对象、类型安全、现代化编程语言的理解后续学习会进行探讨,现在在这里进行一个简单的赘述:
面向对象→一切皆对象:基础类型 int、string 本质也是类;支持类、继承、多态、接口、抽象类、封装。
类型安全→变量必须指定类型,编译阶段就检查类型错误,不会运行时突然崩溃; 不允许随便强制转换无关类型,减少内存越界、非法访问。
现代化编程语言→语法简洁干练、功能强大等。
二、认识第一个C#程序
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- namespace Task01
- {
- internal class Program
- {
- static void Main(string[] args)
- {
- Console.WriteLine("Hello C#");
- }
- }
- }
复制代码
除了Console.WriteLine("Hello C#");是我们自己写的,其余内容都是编译器自动生成的,namespace Task01的Task01是我们在创建项目的项目名称;大家在这里只需要认识到static void Main(string[] args) 是C# 控制台唯一入口,程序启动自动执行;其余代码后续学习理解即可,Console.WriteLine("Hello C#")的作用是打印Hello C#,运行效果如下:
三、变量与数据类型
以在校大学生信息存储场景为例,学生包含学号、姓名、性别、血型、年龄等多项个人信息。若要让计算机保存、记录这类信息,就需要借助变量,由此可得:变量是编程语言中用于承载各类数据信息的载体。
学生各项信息具备明显差异化特征:如学号为数字串 2390004362、姓名为文本 “小帅”、年龄为整数 18、性别为文字 “男”、血型为字符 “O”。各类数据在组成形式、字符长度、取值规则上均存在区别,因此需要划分不同的数据类型进行区分存储。计算机依靠变量、对应数据类型以及赋予变量的变量值(如年龄对应的数值 18),完成对各类业务数据的存储与管理。
3.1变量
3.1.1变量的三要素
变量需要有变量名、数据类型、变量值,例如小帅的年龄18岁表示为:int age=18;int是数据类型,age是变量名(我们自己取的),18是变量值。
3.1.2变量的命名规范
(一)硬性强制规则
(1)只能由字母、数字、下划线_组成 不能出现空格、中文、特殊符号(#¥%&@之类),比如学生姓名、stu-name、stu#age全是违规,电脑直接不认。
(2)不能以数字开头 18age不行,纯数字放开头程序直接罢工,换成age18、stu_age才合规。
(3)严格区分大小写 Name和name是两个完全不搭边的变量,别手滑大小写混用,找 bug 能找到秃头。
(4)禁止使用编程语言关键字 像int、if、for、class这类系统自带词不能当变量名,相当于抢系统饭碗,程序直接炸。
(二)行业通用可读性规范
(1)见名知意,拒绝玄学命名 千万别用a、b、x这种抽象代号,过两天自己都忘了存的啥。存学生年龄就写stu_age,学号就写stu_id,一目了然。
(2)主流两种命名风格
- 下划线命名:单词之间用下划线隔开,stu_blood_type、stu_sex
- 小驼峰命名:首个单词小写,后面单词首字母大写,stuBloodType、stuName
(3)不要过长、不要堆砌单词 不用写一长串废话the_age_of_student_in_class_one,简洁stu_age足够,太长看着费眼。
(4)常量全大写,普通变量小写 固定不变的数据(比如学校代码)写成大写SCHOOL_CODE,普通存储信息的变量统一小写,一眼区分常量和变量。
(5)尽量使用英文,少用拼音 尽量用标准英文单词,别整xingming、nianling这种拼音,写大型项目会显得很业余;实在词汇匮乏再简短拼音凑合。
3.2变量的数据类型
C#的数据类型分为两类:值类型和引用类型
3.1.1值类型
值类型主要包括:整数类型、浮点类型、字符类型、布尔类型。
(1)整数类型:主要是存贮整数,例如年龄18表示为:int age=18;int 表示为整数类型。整形类型的分类如下:
| 数据类型 | 占用字节 | 取值范围 | 是否常用 |
|---|
| sbyte | 1 字节 | -128 ~ 127 | 极少用,底层硬件开发 |
| byte | 1 字节 | 0 ~ 255 | 常用,存储字节、颜色、状态码 |
| short | 2 字节 | -32768 ~ 32767 | 较少用,节省空间的小整数 |
| ushort | 2 字节 | 0 ~ 65535 | 极少用 |
| int | 4 字节 | -2147483648 ~ 2147483647 | 最常用,普通数字、学号、年龄等 |
| uint | 4 字节 | 0 ~ 4294967295 | 偶尔使用,仅存非负大数 |
| long | 8 字节 | -9223372036854775808 ~ 9223372036854775807 | 常用,超大数值、订单号、时间戳 |
| ulong | 8 字节 | 0 ~ 18446744073709551615 | 很少用 |
(2)浮点类型:主要是存储小数,例如:微信余额float balance=100.99f;float表示浮点数类型。浮点数类型分类如下:
| 数据类型 | 占用字节 | 有效精度 | 取值范围 | 是否常用 |
|---|
| float | 4 字节 | 7 位有效数字 | ±1.5×10⁻⁴⁵ ~ ±3.4×10³⁸ | 偶尔使用,图形、游戏、性能优先场景 |
| double | 8 字节 | 15~16 位有效数字 | ±5.0×10⁻³²⁴ ~ ±1.7×10³⁰⁸ | 最常用,普通小数、计算、价格、坐标默认使用 |
| decimal | 16 字节 | 28~29 位有效数字 | ±1.0×10⁻²⁸ ~ ±7.9×10²⁸ | 常用,金融、金额、财务高精度计算 |
(3)字符类型:主要存储字符,例如:人的血型char blood_type='A';char表示浮点数类型,浮点数类型分类如下:
| 数据类型 | 占用字节 | 取值范围 | 代码示例 |
|---|
| char | 2 字节 | 0 ~ 65535(Unicode U+0000 ~ U+FFFF) | char gender = '男';char bloodType = 'O'; |
(4)布尔类型:主要存储一个结果的正误,例如:判断结果bool result=ture;bool表示布尔类型,布尔类型的结果只有ture和flase;
| 数据类型 | 占用字节 | 取值范围 | 代码示例 |
|---|
| bool | 1 字节 | 仅两个值:true(真)、false(假) | bool isStudent = true;bool isMale = false; |
3.1.2引用类型
值类型(int、char、bool、decimal 等):数据直接存放在栈内存,变量里装的就是真实数据本身; 引用类型:真实数据存放在堆内存,栈中变量只存放堆内存的地址(引用地址),通过地址去堆里找到真实内容。
C# 主要引用类型分类:string 字符串类型、object 对象类型(所有类型的基类)、自定义类(class)、接口 interface、数组 Array、委托 delegate、事件 event、装箱后的值类型;我们这里简单讲解一下string类型和object类型;
(一)string类型
(1)基础特性:属于引用类型,但行为有特殊性;底层存储一串 Unicode 字符,使用双引号包裹;不可变(只读):一旦赋值无法修改,拼接、替换字符串时会在堆中新建字符串,旧字符串等待 GC 回收。
(2)占用和使用
- // 直接赋值,无需new
- string name = "小帅";
- string id = "2390004362";
- // 字符串拼接,会生成新字符串
- string info = name + "学号:" + id;
复制代码
(二)object类型
(1)定位:C# 所有类型的根父类
不管是值类型(int、bool)还是引用类型(string、自定义类),全部隐式继承 object,是所有数据的统一顶层类型。
(2)装箱与拆箱(核心用法)
- // 装箱
- int age = 18;
- object obj = age;
- // 拆箱
- int newAge = (int)obj;
复制代码
3.2var 隐式类型推断
3.2.1var 是什么
var 是隐式类型推断关键字,编译器会根据右侧赋值的内容,自动推断出变量真实数据类型,不用手动写完整类型名。 本质:变量依然是强类型,类型在编译阶段就确定,不是动态类型。
- // 显式写法
- int age = 18;
- string name = "小帅";
- // var隐式推断
- var age = 18; // 编译器自动识别为 int
- var name = "小帅"; // 编译器自动识别为 string
复制代码
var可以自动识别数据类型。
3.2.2var 使用硬性规则
(1)声明时必须立刻赋值,不能只定义不赋值
- //错误用法
- var num; // 报错,无法推断类型
- num = 10;
- //正确用法
- var num=10;
复制代码
(2)不能赋值为 null(单独 null 无法推断类型)
- var str = null; // 报错,编译器不知道是什么引用类型
- // 如需初始null,搭配强制转换
- var str = (string)null;
复制代码
(3)同一变量不能多次更换不同类型
类型编译时固定,后续不能存别的类型数据:
- var data = 10;
- data = "abc"; // 报错,data已经是int类型
复制代码
(4)不能用于方法参数、返回值、类字段
类的成员变量不能用 var;方法参数、返回值不能写 var; var仅能用于方法内部局部变量。
- // 错误:类字段
- class Student
- {
- var id = 202601; // 编译报错
- }
复制代码
(5)搭配匿名类型时必须用 var
匿名类没有自定义类型名,只能依靠 var 接收:
- var stu = new { Id = 2390004362, Name = "小帅" };
复制代码
对于(4)、(5)点不完全理解的话,不碍事后续学习后即可。
3.2.3适用场景
(1)类型名很长,简化代码(集合、泛型)
- // 冗长显式写法
- List<int> list = new List<int>();
- // 简洁var写法
- var list = new List<int>();
复制代码
(2)接收匿名类型对象(唯一选择)
(3)循环遍历集合
- foreach(var item in list)
- {
- }
复制代码
3.3可空数据类型
3.3.1 作用
值类型(int、double、bool、char 等)默认不能为 null,但业务中存在 “无数据” 场景(比如学生成绩未录入、身高未知),可空类型允许值类型变量存储 null。
3.3.2 两种写法
1.简写(推荐):值类型?
- int? score = null;
- double? height = 172.5;
- bool? sex = null;
复制代码
2.完整写法:Nullable
- Nullable<int> score = null;
复制代码
3.3.3 核心属性
- int? num = null;
- if (num.HasValue)
- {
- Console.WriteLine(num.Value);
- }
复制代码
3.3.4空合并运算符??
简化 null 判断,为空时使用默认值。
- int? score = null;
- int s = score ?? 0; // score为null则赋值0
复制代码
3.3.5使用限制
仅支持值类型,string、自定义类等引用类型本身就可以存 null,不需要加?。
3.4类型转换
分为隐式转换、显式强制转换、工具类转换三类:
3.4.1 隐式转换
自动转换,无丢失精度,不用写代码;小范围类型 → 大范围类型,编译器自动完成,不会报错 规则:存储空间小 → 存储空间大
- int a = 100;
- double b = a; // int自动转double,隐式转换
- short s = 50;
- int i = s;
复制代码
不可隐式:double 转 int、long 转 int、带符号转无符号等(存在精度 / 范围丢失)
3.4.2显式强制转换
格式:(目标类型)变量 适用于大范围转小范围、精度更高转更低;手动强制,可能丢失数据。
- double d = 99.99;
- int num = (int)d; // 结果99,小数直接截断丢失
复制代码
风险:数值超出目标类型范围会溢出,数据错乱。
3.4.3Convert 工具类转换
处理 string 与各类数值转换,常用方法: Convert.ToInt32()、Convert.ToDouble()、Convert.ToChar()、Convert.ToBool();通用,字符串互转首选。
- string str = "18";
- int age = Convert.ToInt32(str);
复制代码
3.4.4int.Parse / int.TryParse
Parse:只能用于字符串数字和整形之间转换,转换失败直接抛异常;下面代码的意思是将最后转换的结果存储到a或者b变量。
- int a = int.Parse("123");
- int b=int.Parse("abc");
复制代码
运行结果如下:
TryParse:只能用于字符串数字和整形之间转换,安全转换,不会报错,返回 bool 判断是否成功。格式 bool 返回结果=int.TryParse(需要转换的数据,out 转换成的数据);如果返回结果为flase,则转换成的数据赋值为0.
- //第一种flase情况
- string input01= "abc";
- int res01;
- bool result01 = int.TryParse(input01, out res01);
- //第二种ture情况
- string input02 = "123";
- int res02;
- bool result02 = int.TryParse(input02, out res02);
复制代码
运行结果如下:
3.4.5 装箱与拆箱
装箱:值类型转 object(隐式)
- int a = 10;
- object obj = a; // 装箱
复制代码
拆箱:object 转回值类型(必须显式强制转换)
3.5常量 const
3.5.1定义
常量是运行期间值永远不能修改的变量,使用 const 修饰,编译时就确定值。
3.5.2使用规则
- 声明时必须直接赋值,后续不能修改;
- 只能是值类型、string,不能是引用类型(除 string);
- 属于静态,可直接 类名.常量名 访问;
- 不能用 var 定义常量。
- // 正确
- const string SCHOOL_NAME = "厦门大学";
- const int MAX_AGE = 120;
- // 错误
- const double num; // 未赋值
- MAX_AGE = 99; // 常量二次赋值,报错
复制代码
3.5.3 命名规范
常量统一全大写,下划线分隔单词,区分普通变量。
3.5.4const 与 readonly 简单区分
const:编译期常量,必须声明赋值,全局静态;
readonly:运行时常量,可在构造函数赋值,实例 / 静态都可用。
四、运算符与表达式
4.1 运算符基础概念
4.1.1 通俗类比
数据相当于原材料(面粉、鸡蛋、学生年龄、分数),运算符是加工数据的动作(搅拌、烘烤、加减对比);计算机依靠运算符处理数据,完成计算、判断、赋值等功能。
4.1.2 专业定义
运算符:告诉编译器执行数学、逻辑操作的专用符号。
- int a = 10 + 5;
- // 操作数:10、5
- // 运算符:+
- // 完整表达式:10 + 5
- // 运算结果:15
复制代码
4.2 算术运算符
4.2.1 算术运算符清单
| 运算符 | 作用 | 代码示例 | 运算结果 |
|---|
| + | 加法 | 10 + 5 | 15 |
| - | 减法 | 10 - 5 | 5 |
| * | 乘法 | 10 * 5 | 50 |
| / | 除法 | 10 / 5 | 2 |
| % | 取余(求余数) | 10 % 3 | 1 |
4.2.2 除法两大核心陷阱
陷阱 1:整数除法直接舍弃小数
两个 int 整数相除,只会保留整数部分,小数直接截断,不四舍五入。
- int res1 = 10 / 3; // 输出3,不是3.333
- int res2 = 5 / 2; // 输出2,不是2.5
复制代码
解决方案:保证至少一个操作数为浮点型
- double r1 = 10.0 / 3; // 3.3333333
- double r2 = 10 / 3.0; // 3.3333333
- double r3 = (double)10 / 3; // 强制转换后计算
复制代码
陷阱 2:除数不能为 0
整数除以 0 会触发运行时异常 DivideByZeroException,程序直接崩溃。
- int a = 10;
- int b = 0;
- // int c = a / b; // 禁止编写,运行报错
复制代码
4.2.3 取余运算符 %
% 仅支持整数运算,计算两数相除后的余数。
(1)场景 1:判断数字奇偶
数字 % 2 结果为 0 → 偶数;结果为 1 → 奇数
- int num = 7;
- int rem = num % 2; // rem=1,奇数
复制代码
(2)场景 2:提取数字个位、十位
- int num = 12345;
- int u_place = num % 10; // 个位5
- int t_place= (num / 10) % 10; // 十位4
复制代码
(3)场景 3:判断能否整除(闰年判断)
- int year = 2024;
- bool div4 = year % 4 == 0; // 能被4整除 true
- bool div100 = year % 100 == 0;
- bool div400 = year % 400 == 0;
复制代码
(4)场景 4:时间单位换算(分钟转小时分钟)
- int totalMin = 134;
- int hour = totalMin / 60; // 2小时
- int min = totalMin % 60; // 剩余14分钟
复制代码
4.2.4 算术运算符优先级
数学规则:先乘除取余,后加减;括号优先级最高,可强制改变运算顺序。
- int r1 = 5 + 3 * 4; // 5+12=17
- int r2 = (5 + 3) * 4; // 8*4=32
复制代码
开发建议:不确定优先级一律加括号,代码可读性更高。
4.3 赋值运算符
赋值运算符作用:将右侧计算结果存入左侧变量。
4.3.1 基础赋值 =
-
= 不是数学等于,是赋值;
-
执行顺序:从右向左;
-
左侧只能是变量,不能是常量、数字、表达式;
-
支持链式赋值。
- // 声明后赋值
- int age;
- age = 18;
- // 声明并赋值
- int score = 95;
- // 链式赋值(从右往左)
- int a, b, c;
- a = b = c = 10; // c=10 → b=10 → a=10
复制代码
4.3.2 复合赋值运算符
简化 “变量自身运算再赋值” 的写法
| 运算符 | 示例 | 等价完整写法 |
|---|
| += | a += 5 | a = a + 5 |
| -= | a -= 3 | a = a - 3 |
| *= | a *= 2 | a = a * 2 |
| /= | a /= 2 | a = a / 2 |
| %= | a %= 3 | a = a % 3 |
- int score = 100;
- score += 10; // 110
- score -= 20; // 90
- score *= 2; // 180
- score /= 3; // 60
- score %= 7; // 4
复制代码
4.3.3 自增、自减运算符
专门用于变量 + 1、变量 - 1,分前置和后置两种,核心区别:先运算还是先取值
| 写法 | 名称 | 等价代码 | 执行逻辑 |
|---|
| a++ | 后置自增 | a = a + 1 | 先使用变量原值,运算结束后再加 1 |
| ++a | 前置自增 | a = a + 1 | 变量先 + 1,再使用新值 |
| a-- | 后置自减 | a = a - 1 | 先取值,后减 1 |
| --a | 前置自减 | a = a - 1 | 先减 1,后取值 |
后置 ++ 演示
- int a = 5;
- int b = a++;
- // 执行流程:b=5 → a = a+1
- // 结果:a=6,b=5
复制代码
前置 ++ 演示
- int x = 5;
- int y = ++x;
- // 执行流程:x=x+1 → y=6
- // 结果:x=6,y=6
复制代码
4.4 关系运算符
作用:对比两个数据的大小、相等关系,运算结果固定为 bool 类型(true/false)
| 运算符 | 含义 | 示例 | 结果 |
|---|
| == | 等于 | 5 == 5 | true |
| != | 不等于 | 5 != 3 | true |
| > | 大于 | 5 > 3 | true |
| < | 小于 | 5 < 3 | false |
| >= | 大于等于 | 5 >= 5 | true |
| <= | 小于等于 | 5 <= 3 | false |
- int age = 18;
- bool isAdult = age >= 18; // true
- double score = 85.5;
- bool pass = score >= 60; // true
复制代码
4.4.1 两大高频陷阱
陷阱 1:混淆赋值 = 和等于 ==
- int num = 5;
- bool right = num == 10; // 正确,比较
- // bool wrong = num = 10; // 错误,=是赋值,不是比较
复制代码
陷阱 2:浮点数不能直接用 == 判断相等
二进制浮点存储存在精度误差,0.1 + 0.2 不等于精确 0.3 解决方案:判断两数差值小于极小值
- double a = 0.1 + 0.2;
- double b = 0.3;
- double eps = 0.0000001;
- bool equal = Math.Abs(a - b) < eps;
复制代码
4.5 逻辑运算符与短路求值
作用:组合多个关系条件,结果依旧是 bool。
4.5.1 运算符说明
| 运算符 | 名称 | 规则 | 类比 |
|---|
| && | 逻辑与 | 全部条件 true,结果才 true | 串联开关,全部打开才通电 |
| || | 逻辑或 | 任意一个 true,结果 true | 任意一个 true,结果 true |
| ! | 逻辑非 | 取反,true 变 false,false 变 true | 反向开关 |
- int age = 20;
- bool hasId = true;
- bool canBar = age >= 18 && hasId; // 与,true
- bool weekend = true;
- bool holiday = false;
- bool sleepLate = weekend || holiday; // 或,true
- bool rain = false;
- bool goodWeather = !rain; // 非,true
复制代码
4.5.2 真值表
逻辑与 &&
| A | B | A && B |
|---|
| true | true | true |
| true | false | false |
| false | true | false |
| false | false | false |
逻辑或 ||
| A | B | A || B |
|---|
| true | true | true |
| true | false | true |
| false | true | true |
| false | false | false |
逻辑非!
4.5.3 短路求值(核心重点)
C# && 和 || 具备短路特性:仅靠左侧条件就能确定整体结果时,右侧代码完全不执行。
-
&& 左侧为 false → 整体必 false,右侧跳过;
-
|| 左侧为 true → 整体必 true,右侧跳过。
安全判空经典用法
- string name = null;
- // 左侧name != null 为false,右侧不会执行,不会空指针报错
- bool hasText = name != null && name.Length > 0;
复制代码
防止除零崩溃
- int a = 5, b = 0;
- // b!=0 为false,a/b不会执行,无报错
- bool res = (b != 0) && (a / b > 2);
复制代码
4.6 三元条件运算符
4.6.1 语法格式
条件表达式 ? 条件成立返回值 : 条件不成立返回值 逻辑:条件为 true,执行冒号前;false 执行冒号后。
4.6.2 常用场景
场景 1:简单二选一赋值
- int age = 20;
- string tip = age >= 18 ? "成年人" : "未成年人";
- //tip的结果是 “成年人”
复制代码
场景 2:输出内嵌使用
- int num = 7;
- Console.WriteLine($"{num}是{(num%2==0 ? "偶数" : "奇数")}");
- //结果打印 7是奇数
复制代码
场景 3:取最大值、最小值
- int a = 10, b = 20;
- int max = a > b ? a : b;
- int min = a < b ? a : b;
复制代码
场景 4:空值默认文本
- string userName = null;
- string show = userName != null ? userName : "游客";
复制代码
4.6.3 使用规范建议
适合:简单单层二选一、字符串插值内嵌
禁止:多层嵌套复杂逻辑(可读性极差,改用 if 分支)
禁止:需要执行多行代码(三元只能返回单个值)
4.7 Null 合并运算符
4.7.1 ?? 运算符
作用:左边不为 null 取左边,左边为 null 取右边,简化可空类型判断
- int? age = null;
- // 传统三元写法
- int r1 = age.HasValue ? age.Value : 18;
- // ??简化写法
- int r2 = age ?? 18;
复制代码
支持链式调用:
- string input = null;
- string cfg = null;
- string res = input ?? cfg ?? "默认文字";
复制代码
4.7.2 ??= C
#8.0 新增,左边变量为 null 时,才将右侧值赋值给变量;不为 null 则不执行。
- string name = null;
- name ??= "默认姓名"; // name为null,赋值成功
- name = "张三";
- name ??= "默认姓名"; // name不为null,无变化
复制代码
4.8 运算符优先级
-
后缀自增 / 自减 a++ a--
-
前缀自增 / 自减 ++a --a
-
乘除取模 * / %
-
加减 + -
-
大小关系 < <= > >=
-
相等判断 == !=
-
逻辑与 &&
-
逻辑或 ||
-
null 合并 ??
-
三元条件 ?:
-
赋值运算符 = += -= *= /= %= ??=
记忆技巧:不用死记,不确定优先级全部加括号。
五、流程控制
5.1 流程控制基础概述
5.1.1 流程控制的核心概念
通俗理解:生活中处处存在判断与重复:下雨带伞、堵车绕路、一日三餐重复进行。程序同理,不会永远从上到下直线执行,需要根据条件选择路径、根据需求重复执行代码。 流程控制就像程序的 “交通指挥系统”,指挥代码按指定的顺序、分支、次数执行。
正式定义:流程控制是通过特定语法语句,控制程序代码的执行顺序与执行路径的机制。没有流程控制的程序只能顺序执行,无法处理复杂业务逻辑;借助流程控制,程序可以实现条件判断、重复执行、分支跳转等复杂功能。
5.1.2 程序三大执行结构
所有程序逻辑都可以拆解为三种基础结构的组合:
| 结构类型 | 执行规则 | 生活类比 | 典型应用 |
|---|
| 顺序结构 | 代码从上到下逐行依次执行,无分支、无重复 | 按菜谱步骤做菜,一步接一步 | 变量声明、简单计算、顺序输出 |
| 选择结构 | 根据条件判断结果,选择不同代码路径执行 | 十字路口红绿灯,绿灯直行红灯停 | 成绩评级、权限判断、状态校验 |
| 循环结构 | 满足条件时,重复执行同一段代码 | 操场跑圈,跑完指定圈数停止 | 遍历数据、重复输入、批量计算 |
5.2 选择结构
选择结构也叫分支结构,核心是 “满足条件才执行”,根据条件数量与分支数量分为单分支、双分支、多分支三类。
5.2.1 if 单分支语句
语法格式
- if (布尔条件表达式)
- {
- // 条件为 true 时执行的代码块
- }
复制代码
执行逻辑
- 计算 if 括号内的表达式,得到 true 或 false;
- 结果为 true:执行花括号内的代码;
- 结果为 false:跳过整个 if 代码块,继续执行后续代码。
代码示例
- int score = 85;
- if (score >= 60)
- {
- Console.WriteLine("恭喜你,考试及格了!");
- }
- Console.WriteLine("程序执行结束");
复制代码
运行结果:
适用场景
只需要处理 “条件成立” 的情况,条件不成立时无需任何操作。
5.2.2 if-else 双分支语句
语法格式
- if (布尔条件表达式)
- {
- // 条件为 true 执行
- }
- else
- {
- // 条件为 false 执行
- }
复制代码
执行逻辑
条件成立走 if 分支,不成立走 else 分支,两条路径二选一,必然执行其中一个。
代码示例
- int age = 15;
- if (age >= 18)
- {
- Console.WriteLine("你已经成年了");
- }
- else
- {
- Console.WriteLine("你还是未成年人");
- }
复制代码
运行结果:
适用场景
典型的二选一逻辑,例如及格 / 不及格、成年 / 未成年、登录成功 / 登录失败。
5.2.3 if-else if-else 多分支语句
语法格式
- if (条件1)
- {
- // 条件1成立执行
- }
- else if (条件2)
- {
- // 条件1不成立 且 条件2成立执行
- }
- else if (条件3)
- {
- // 条件1、2都不成立 且 条件3成立执行
- }
- else
- {
- // 所有条件都不成立执行
- }
复制代码
执行逻辑
从上到下依次判断每个条件:
-
遇到第一个成立的条件,执行对应代码块,执行完毕后直接跳出整个分支结构;
-
所有条件都不成立,执行最后的 else 分支;
-
条件顺序至关重要,一旦前面的条件命中,后面的条件不再判断。
代码示例(成绩五级评级)
- int score = 85;
- if (score >= 90)
- {
- Console.WriteLine("优秀");
- }
- else if (score >= 80)
- {
- Console.WriteLine("良好");
- }
- else if (score >= 70)
- {
- Console.WriteLine("中等");
- }
- else if (score >= 60)
- {
- Console.WriteLine("及格");
- }
- else
- {
- Console.WriteLine("不及格,继续努力");
- }
复制代码
运行结果:
注意:85 同时满足>=80和>=70,但因为>=80写在前面,优先命中,不会再判断后续条件。
5.2.4 if 语句使用注意事项与常见陷阱
1. 花括号省略问题
如果 if / else 后只有一条语句,可以省略花括号。
- int age = 20;
- if (age >= 18)
- Console.WriteLine("成年人");
- else
- Console.WriteLine("未成年人");
复制代码
开发规范建议:即使只有一条语句,也始终书写花括号。
2. 悬空 else 问题
else 永远与离它最近的、未匹配的 if 进行配对,和代码缩进无关。
- int x = 5;
- int y = 10;
- if (x > 0)
- if (y > 0)
- Console.WriteLine("x和y都大于0");
- else
- Console.WriteLine("y不大于0");
复制代码
上述代码中,else 实际匹配内层的 if(y>0),而非外层 if(x>0),最终打印:x和y都大于0。 解决方案:始终使用花括号明确层级,避免歧义。
3. 赋值与等于混淆
= 是赋值运算符,== 是相等比较运算符,二者完全不同。
- int num = 5;
- // 错误写法:= 是赋值,不是比较,C#中不允许int值直接作为if条件
- // if (num = 10) { }
- // 正确写法
- if (num == 10)
- {
- Console.WriteLine("num等于10");
- }
复制代码
5.2.5 三元表达式与 if-else 的选型对比
两种写法代码对比
- int age = 20;
- string result1;
- // if-else 写法(语句,无返回值)
- if (age >= 18)
- {
- result1 = "成年人";
- }
- else
- {
- result1 = "未成年人";
- }
- // 三元表达式写法(表达式,有返回值)
- string result2 = age >= 18 ? "成年人" : "未成年人";
复制代码
本质区别
选型对照表
| 场景 | 推荐方案 | 原因 |
|---|
| 给单个变量赋值 | 三元表达式 | 代码简洁,一行完成 |
| 需要执行多条语句 | if-else | 三元只能返回值,不能执行多语句 |
| 逻辑复杂、多层分支 | if-else | 可读性强,易维护 |
| 字符串插值内嵌使用 | 三元表达式 | 可直接嵌入字符串,无需额外变量 |
内嵌示例:
- int score = 85;
- Console.WriteLine($"考试结果:{(score >= 60 ? "及格" : "不及格")}");
复制代码
5.2.6 switch 多路分支语句
switch 专门用于 “一个变量匹配多个固定值” 的多分支场景,比多层 if-else 结构更清晰。
语法格式
- switch (待匹配的表达式)
- {
- case 常量值1:
- // 匹配值1时执行的代码
- break;
- case 常量值2:
- // 匹配值2时执行的代码
- break;
- default:
- // 所有case都不匹配时执行
- break;
- }
复制代码
执行逻辑
-
计算 switch 括号内表达式的值;
-
从上到下依次与 case 后的常量值对比;
-
匹配成功则执行对应 case 内的代码,遇到 break 跳出整个 switch;
-
全部不匹配则执行 default 分支。
代码示例(星期查询)
- Console.Write("请输入星期几(1-7):");
- int day = int.Parse(Console.ReadLine());
- switch (day)
- {
- case 1:
- Console.WriteLine("星期一:新的一周开始啦!");
- break;
- case 2:
- Console.WriteLine("星期二:加油!");
- break;
- case 3:
- Console.WriteLine("星期三:小周末");
- break;
- case 4:
- Console.WriteLine("星期四:快周末了");
- break;
- case 5:
- Console.WriteLine("星期五:明天就周末了!");
- break;
- case 6:
- Console.WriteLine("星期六:休息日");
- break;
- case 7:
- Console.WriteLine("星期日:明天又要上班了");
- break;
- default:
- Console.WriteLine("输入错误,请输入1-7之间的数字");
- break;
- }
复制代码
5.2.7 switch 穿透规则与语法规范
核心规则
C# 不允许有代码的 case 自动向下穿透,每个带代码的 case 必须以 break、return 等跳转语句结尾,否则编译报错。
合法穿透:空 case 共享代码
多个空 case 可以堆叠,共享同一段执行代码,实现 “多值匹配同一逻辑”。
- int score = 85;
- int scoreLevel = score / 10;
- switch (scoreLevel)
- {
- case 10:
- case 9:
- Console.WriteLine("优秀"); // 90-100分共用
- break;
- case 8:
- case 7:
- Console.WriteLine("良好"); // 70-89分共用
- break;
- case 6:
- Console.WriteLine("及格");
- break;
- default:
- Console.WriteLine("不及格");
- break;
- }
复制代码
语法要求
-
case 后必须是常量,不能是变量或范围表达式;
-
default 分支可选,不是必须书写;
-
支持匹配的类型:整数、字符、字符串、枚举等。
5.2.8 if 与 switch 的场景选型指南
| 场景 | 推荐方案 | 原因 |
|---|
| 条件复杂(范围判断、多条件组合) | if | if 支持任意布尔表达式,灵活度最高 |
| 条件简单(单个变量匹配固定离散值) | switch | 结构清晰,分支多时执行效率更高 |
| 分支数量 2-3 个 | if | 写法简单,无需额外语法 |
| 分支数量大于 5 个 | switch | 层级清晰,维护成本低 |
5.3 循环结构
循环结构用于重复执行同一段代码,避免重复书写,提升代码复用性。C# 提供 while、do-while、for 三种基础循环,以及循环控制语句。
5.3.1 while 循环:先判断后执行
语法格式
- while (布尔循环条件)
- {
- // 循环体:条件为true时重复执行
- }
复制代码
执行逻辑
-
判断循环条件,得到 true/false;
-
条件为 true:执行循环体,执行完毕后回到第 1 步再次判断;
-
条件为 false:结束循环,执行循环后续代码。
代码示例(输出 1 到 5)
- int i = 1;
- while (i <= 5)
- {
- Console.WriteLine(i);
- i++; // 循环变量自增,必须修改条件,否则会死循环
- }
- Console.WriteLine("循环结束");
复制代码
运行结果:
死循环
循环条件永远为 true,程序无限执行无法退出,称为死循环。
- // 死循环示例,实际开发禁止
- while (true)
- {
- Console.WriteLine("无限循环中...");
- }
复制代码
可通过 break 语句主动退出死循环:
- int i = 1;
- while (true)
- {
- Console.WriteLine($"第{i}次循环");
- if (i >= 5)
- {
- break; // 满足条件时跳出循环
- }
- i++;
- }
复制代码
5.3.2 do-while 循环:先执行后判断
语法格式
- do
- {
- // 循环体
- } while (循环条件); // 注意末尾必须有分号
复制代码
执行逻辑
-
先执行一次循环体;
-
再判断循环条件,为 true 则继续循环,为 false 则结束。 核心特点:循环体至少执行一次。
代码示例(猜数字简化版)
- Random random = new Random();
- int secretNumber = random.Next(1, 11);
- int guess;
- do
- {
- Console.Write("猜一个数字(1-10):");
- guess = int.Parse(Console.ReadLine());
-
- if (guess > secretNumber)
- Console.WriteLine("猜大了");
- else if (guess < secretNumber)
- Console.WriteLine("猜小了");
-
- } while (guess != secretNumber);
- Console.WriteLine("恭喜你猜对了!");
复制代码
5.3.3 while 与 do-while 核心区别对比
| 对比项 | while 循环 | do-while 循环 |
|---|
| 判断时机 | 先判断条件,后执行循环体 | 先执行循环体,后判断条件 |
| 最少执行次数 | 0 次(条件一开始不成立则一次都不执行) | 1 次(无论条件如何,至少执行一次) |
| 语法特点 | while 后无分号 | while 条件后必须加分号 |
| 适用场景 | 可能一次都不需要执行的场景 | 至少执行一次的场景,如菜单、用户输入校验 |
5.3.4 for 循环:计数型循环
for 循环是最常用的循环,专门用于已知循环次数的场景,语法上将初始化、条件、迭代集中在一起,结构清晰。
语法格式
- for (初始化表达式; 循环条件; 迭代表达式)
- {
- // 循环体
- }
复制代码
三部分说明
| 组成部分 | 执行时机 | 作用 | 示例 |
|---|
| 初始化表达式 | 循环开始前执行,仅执行 1 次 | 声明并初始化循环变量 | int i = 1 |
| 循环条件 | 每次循环开始前判断 | 控制循环是否继续 | i <= 10 |
| 迭代表达式 | 每次循环体执行完毕后执行 | 修改循环变量,通常自增 / 自减 | i++ |
代码示例(输出 1 到 10)
- for (int i = 1; i <= 10; i++)
- {
- Console.WriteLine(i);
- }
复制代码
5.3.5 for 循环执行流程分步拆解
以 for (int i = 1; i <= 3; i++) 为例,完整执行步骤:
- 初始化:执行 int i = 1,循环变量 i 初始化为 1,仅执行一次;
- 条件判断:判断 1 <= 3,结果为 true;
- 执行循环体:输出第 1 次循环;
- 迭代更新:执行 i++,i 变为 2;
- 条件判断:判断 2 <= 3,结果为 true;
- 执行循环体:输出第 2 次循环;
- 迭代更新:执行 i++,i 变为 3;
- 条件判断:判断 3 <= 3,结果为 true;
- 执行循环体:输出第 3 次循环;
- 迭代更新:执行 i++,i 变为 4;
- 条件判断:判断 4 <= 3,结果为 false,循环结束。
5.3.6 for 循环的多种变体用法
(1)倒序循环
- // 从10输出到1
- for (int i = 10; i >= 1; i--)
- {
- Console.WriteLine(i);
- }
复制代码
(2)自定义步长
- // 输出10以内的偶数
- for (int i = 2; i <= 10; i += 2)
- {
- Console.WriteLine(i);
- }
复制代码
(3)多循环变量
- for (int i = 1, j = 10; i <= 5; i++, j--)
- {
- Console.WriteLine($"i={i}, j={j}");
- }
复制代码
(4)省略写法
for 循环的三个部分都可以省略,但分号必须保留。
- // 等同于 while(true) 死循环
- for (;;)
- {
- Console.WriteLine("死循环");
- }
复制代码
5.3.7 循环控制语句:break、continue、return
(1)break:跳出当前循环
作用:立即终止当前所在的整个循环,执行循环后续代码。
- for (int i = 1; i <= 10; i++)
- {
- if (i == 5)
- {
- Console.WriteLine("找到5了,停止循环");
- break; // 直接结束整个for循环
- }
- Console.WriteLine($"当前数字:{i}");
- }
复制代码
注意:嵌套循环中,break 只能跳出它所在的那一层循环。
(2)continue:跳过本次循环
作用:结束当前这一次循环,直接进入下一次循环判断,不执行本次循环剩余代码。
- for (int i = 1; i <= 10; i++)
- {
- if (i % 4 == 0)
- {
- continue; // 遇到4的倍数,跳过本次输出
- }
- Console.WriteLine(i);
- }
复制代码
运行结果: 输出 1、2、3、5、6、7、9、10,跳过 4 和 8。
(3)return:退出整个方法
作用:直接终止当前方法的执行,不仅仅跳出循环,方法内后续所有代码都不再执行。
- static void PrintNumbers()
- {
- for (int i = 1; i <= 10; i++)
- {
- if (i == 5)
- {
- Console.WriteLine("遇到5,方法结束");
- return; // 退出整个方法
- }
- Console.WriteLine(i);
- }
- // 这行代码永远不会执行
- Console.WriteLine("循环结束");
- }
复制代码
5.3.8 goto 跳转语句(了解即可)
语法与作用
goto 可以直接跳转到代码中指定标签的位置,属于无条件跳转语句。
- goto 标签名;
- // 被跳过的代码
- 标签名:
- // 跳转后执行的代码
复制代码
典型场景:跳出多层嵌套循环
- // 寻找第一个乘积大于50的数对
- for (int i = 1; i <= 10; i++)
- {
- for (int j = 1; j <= 10; j++)
- {
- int product = i * j;
- if (product > 50)
- {
- Console.WriteLine($"找到:{i}×{j}={product}");
- goto Found; // 直接跳出两层循环
- }
- }
- }
- Found:
- Console.WriteLine("查找结束");
复制代码
使用规范
-
99% 场景不推荐使用:goto 会破坏代码结构,降低可读性,几乎都可以用 break、continue、return 替代;
-
唯一推荐场景:跳出深层嵌套循环(3 层及以上);
-
禁止用法:不能跳进循环内部,不能跳过变量初始化。
5.3.9 循环嵌套与多层循环执行逻辑
一个循环内部包含另一个循环,称为循环嵌套。外层循环执行一次,内层循环完整执行一轮。
(1)示例 1:九九乘法表
- for (int i = 1; i <= 9; i++) // 外层:控制行数
- {
- for (int j = 1; j <= i; j++) // 内层:控制每行的列数
- {
- Console.Write($"{j}×{i}={i * j}\t");
- }
- Console.WriteLine(); // 每行结束换行
- }
复制代码
(2)示例 2:直角三角形星号图案
- int rows = 5;
- for (int i = 1; i <= rows; i++) // 外层:行数
- {
- for (int j = 1; j <= i; j++) // 内层:每行的*数量
- {
- Console.Write("*");
- }
- Console.WriteLine();
- }
复制代码
运行结果:
执行规律
- 外层循环控制 “行”,内层循环控制 “列”;
- 外层循环执行 1 次,内层循环从头到尾执行完整一轮;
- 总执行次数 = 外层次数 × 内层次数。
六、选择循环知识点掌握清单与速查表
6.1 知识点掌握清单
| 类别 | 具体内容 | 掌握星级 |
|---|
| if 分支语句 | if 单分支、if-else 双分支、多分支 if | ⭐⭐⭐⭐⭐ |
| switch 分支语句 | 基础语法、穿透规则、default 用法 | ⭐⭐⭐⭐ |
| while 循环 | 基础语法、死循环、break/continue | ⭐⭐⭐⭐⭐ |
| do-while 循环 | 语法、与 while 的核心区别 | ⭐⭐⭐⭐ |
| for 循环 | 语法、执行流程、变体用法 | ⭐⭐⭐⭐⭐ |
| 循环嵌套 | 多层循环逻辑、图案打印、九九乘法表 | ⭐⭐⭐⭐ |
| 跳转语句 | break、continue、return 的区别与用法 | ⭐⭐⭐⭐⭐ |
| goto 语句 | 基础语法、使用场景与规范 | ⭐⭐ |
| switch 表达式 | C# 8.0 + 新语法、模式匹配 | ⭐⭐⭐⭐ |
6.2语法速查表
分支语句
| 语法 | 说明 |
|---|
| if (条件) { } | 单分支 |
| if (条件) { } else { } | 双分支 |
| if (条件1) { } else if (条件2) { } else { } | 多分支 |
| switch(表达式) { case 值: 代码; break; default: 代码; break; } | switch 多路分支 |
循环语句
| 语法 | 说明 |
|---|
| while (条件) { } | 先判断后执行 |
| do { } while (条件); | 先执行后判断,至少一次 |
| for (初始化; 条件; 迭代) { } | 计数型循环 |
跳转语句
| 语句 | 作用 |
|---|
| break; | 跳出当前循环 /switch |
| continue; | 结束本次循环,进入下一轮 |
| return; | 退出当前方法 |
| goto 标签; | 跳转到指定标签(慎用) |
希望本文对大家有所帮助,如有问题,欢迎各位博友积极指出!