C# 初步入门(认识、变量、运算符、控制流程)
本文用于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)装箱与拆箱(核心用法)
-
装箱:值类型 → object(栈数据拷贝到堆,返回引用地址)
-
拆箱:object → 值类型(强制转换取出堆内原始数据)
// 装箱
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<T>
Nullable<int> score = null;
3.3.3 核心属性
-
HasValue:bool,判断是否有有效值,true 代表不为 null -
Value:取出存储的实际值,若为 null 直接调用会报错
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 转回值类型(必须显式强制转换)
int b = (int)obj; // 拆箱
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 |
|
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 |
逻辑非!
| A | !A |
|---|---|
| true | false |
| false | true |
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("未成年人");
开发规范建议:即使只有一条语句,也始终书写花括号。
-
优点:代码结构清晰,后续新增代码时不会出现逻辑错误;
-
风险:省略花括号后,新增第二行代码时,会被识别为 if 块外的代码,导致逻辑异常。
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 | 三元只能返回值,不能执行多语句 |
| 逻辑复杂、多层分支 | 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("循环结束");
运行结果:
1
2
3
4
5
循环结束
死循环
循环条件永远为 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 次 |
声明并初始化循环变量 |
|
|
循环条件 |
每次循环开始前判断 |
控制循环是否继续 |
|
|
迭代表达式 |
每次循环体执行完毕后执行 |
修改循环变量,通常自增 / 自减 |
|
代码示例(输出 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 标签; |
跳转到指定标签(慎用) |
希望本文对大家有所帮助,如有问题,欢迎各位博友积极指出!
更多推荐


所有评论(0)