本文用于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 异步、完善线程与内存管理,企业后端主流选型。

这些内容大家简单看看就好,对于面向对象、类型安全、现代化编程语言的理解后续学习会进行探讨,现在在这里进行一个简单的赘述:

面向对象→一切皆对象:基础类型 intstring 本质也是类;支持类、继承、多态、接口、抽象类、封装。

类型安全→变量必须指定类型,编译阶段就检查类型错误,不会运行时突然崩溃; 不允许随便强制转换无关类型,减少内存越界、非法访问。

现代化编程语言→语法简洁干练、功能强大等。

二、认识第一个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-namestu#age全是违规,电脑直接不认。

(2)不能以数字开头  18age不行,纯数字放开头程序直接罢工,换成age18stu_age才合规。

(3)严格区分大小写  Namename是两个完全不搭边的变量,别手滑大小写混用,找 bug 能找到秃头。

(4)禁止使用编程语言关键字intifforclass这类系统自带词不能当变量名,相当于抢系统饭碗,程序直接炸。

(二)行业通用可读性规范

(1)见名知意,拒绝玄学命名 千万别用abx这种抽象代号,过两天自己都忘了存的啥。存学生年龄就写stu_age,学号就写stu_id,一目了然。

(2)主流两种命名风格

  • 下划线命名:单词之间用下划线隔开,stu_blood_typestu_sex
  • 小驼峰命名:首个单词小写,后面单词首字母大写,stuBloodTypestuName​​​​​​

(3)不要过长、不要堆砌单词 不用写一长串废话the_age_of_student_in_class_one,简洁stu_age足够,太长看着费眼。

(4)常量全大写,普通变量小写 固定不变的数据(比如学校代码)写成大写SCHOOL_CODE,普通存储信息的变量统一小写,一眼区分常量和变量。

(5)尽量使用英文,少用拼音 尽量用标准英文单词,别整xingmingnianling这种拼音,写大型项目会显得很业余;实在词汇匮乏再简短拼音凑合。

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使用规则

  1. 声明时必须直接赋值,后续不能修改;
  2. 只能是值类型、string,不能是引用类型(除 string);
  3. 属于静态,可直接 类名.常量名 访问;
  4. 不能用 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 基础赋值 =

  1. = 不是数学等于,是赋值

  2. 执行顺序:从右向左

  3. 左侧只能是变量,不能是常量、数字、表达式;

  4. 支持链式赋值。

// 声明后赋值
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

逻辑非!

A !A
true false
false true

4.5.3 短路求值(核心重点)

C# &&|| 具备短路特性:仅靠左侧条件就能确定整体结果时,右侧代码完全不执行

  1. && 左侧为 false → 整体必 false,右侧跳过;

  2. || 左侧为 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 ??=

#8.0 新增,左边变量为 null 时,才将右侧值赋值给变量;不为 null 则不执行。

string name = null;
name ??= "默认姓名"; // name为null,赋值成功
name = "张三";
name ??= "默认姓名"; // name不为null,无变化

4.8 运算符优先级

  1. 后缀自增 / 自减 a++ a--

  2. 前缀自增 / 自减 ++a --a

  3. 乘除取模 * / %

  4. 加减 + -

  5. 大小关系 < <= > >=

  6. 相等判断 == !=

  7. 逻辑与 &&

  8. 逻辑或 ||

  9. null 合并 ??

  10. 三元条件 ?:

  11. 赋值运算符 = += -= *= /= %= ??=

记忆技巧:不用死记,不确定优先级全部加括号。

五、流程控制

5.1 流程控制基础概述

5.1.1 流程控制的核心概念

通俗理解:生活中处处存在判断与重复:下雨带伞、堵车绕路、一日三餐重复进行。程序同理,不会永远从上到下直线执行,需要根据条件选择路径、根据需求重复执行代码。 流程控制就像程序的 “交通指挥系统”,指挥代码按指定的顺序、分支、次数执行。

正式定义:流程控制是通过特定语法语句,控制程序代码的执行顺序与执行路径的机制。没有流程控制的程序只能顺序执行,无法处理复杂业务逻辑;借助流程控制,程序可以实现条件判断、重复执行、分支跳转等复杂功能。

5.1.2 程序三大执行结构

所有程序逻辑都可以拆解为三种基础结构的组合:

结构类型 执行规则 生活类比 典型应用
顺序结构 代码从上到下逐行依次执行,无分支、无重复 按菜谱步骤做菜,一步接一步 变量声明、简单计算、顺序输出
选择结构 根据条件判断结果,选择不同代码路径执行 十字路口红绿灯,绿灯直行红灯停 成绩评级、权限判断、状态校验
循环结构 满足条件时,重复执行同一段代码 操场跑圈,跑完指定圈数停止 遍历数据、重复输入、批量计算

5.2 选择结构

选择结构也叫分支结构,核心是 “满足条件才执行”,根据条件数量与分支数量分为单分支、双分支、多分支三类。

5.2.1 if 单分支语句

语法格式

if (布尔条件表达式)
{
    // 条件为 true 时执行的代码块
}

执行逻辑

  1. 计算 if 括号内的表达式,得到 truefalse
  2. 结果为 true:执行花括号内的代码;
  3. 结果为 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
{
    // 所有条件都不成立执行
}

执行逻辑

从上到下依次判断每个条件:

  1. 遇到第一个成立的条件,执行对应代码块,执行完毕后直接跳出整个分支结构;

  2. 所有条件都不成立,执行最后的 else 分支;

  3. 条件顺序至关重要,一旦前面的条件命中,后面的条件不再判断。

代码示例(成绩五级评级)

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;
}

执行逻辑

  1. 计算 switch 括号内表达式的值;

  2. 从上到下依次与 case 后的常量值对比;

  3. 匹配成功则执行对应 case 内的代码,遇到 break 跳出整个 switch

  4. 全部不匹配则执行 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 必须以 breakreturn 等跳转语句结尾,否则编译报错。

合法穿透:空 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;
}

语法要求

  1. case 后必须是常量,不能是变量或范围表达式;

  2. default 分支可选,不是必须书写;

  3. 支持匹配的类型:整数、字符、字符串、枚举等。

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时重复执行
}

执行逻辑

  1. 判断循环条件,得到 true/false;

  2. 条件为 true:执行循环体,执行完毕后回到第 1 步再次判断;

  3. 条件为 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 (循环条件); // 注意末尾必须有分号

执行逻辑

  1. 先执行一次循环体;

  2. 再判断循环条件,为 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++) 为例,完整执行步骤:

  1. 初始化:执行 int i = 1,循环变量 i 初始化为 1,仅执行一次;
  2. 条件判断:判断 1 <= 3,结果为 true;
  3. 执行循环体:输出第 1 次循环;
  4. 迭代更新:执行 i++,i 变为 2;
  5. 条件判断:判断 2 <= 3,结果为 true;
  6. 执行循环体:输出第 2 次循环;
  7. 迭代更新:执行 i++,i 变为 3;
  8. 条件判断:判断 3 <= 3,结果为 true;
  9. 执行循环体:输出第 3 次循环;
  10. 迭代更新:执行 i++,i 变为 4;
  11. 条件判断:判断 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("查找结束");

使用规范

  1. 99% 场景不推荐使用:goto 会破坏代码结构,降低可读性,几乎都可以用 break、continue、return 替代;

  2. 唯一推荐场景:跳出深层嵌套循环(3 层及以上);

  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 标签; 跳转到指定标签(慎用)

希望本文对大家有所帮助,如有问题,欢迎各位博友积极指出!

更多推荐