文章目录
2.基础语法
2.1 基本数据类型
C#语言的数据类型分为值类型
和引用类型
。值类型包括整型、浮点型、字符型、布尔型、结构体类型、枚举类型等;引用类型包括类、接口、数组、字符串等。
从内存存储空间的角度而言,值类型
的值是存放到栈
中的,每次存取值都会在该内存中操作;引用类型
首先会在栈中创建一个引用变量,然后在堆
中创建对象本身,再把这个对象所在内存的首地址赋给引用变量。
2.1.1 整型
整型就是存储整数的类型,按照存储值的范围不同,分成了 byte 类型、short 类型、int 类型、long 类型等,并分别定义了有符号数和无符号数。在C#语言中默认的整型是int类型。
从上面的表中可以看出short、int和long类型所对应的无符号数类型都是在其类型名称前面加上了u字符,只有byte类型比较特殊,它存储一个无符号数,其对应的有符号数则是sbyte。
2.1.2 浮点型
浮点型是指小数类型,分为两种,一种是单精度浮点型,一种是双精度浮点型。
- float:单精度浮点型,占用4个字节,最多保留7位小数。
- double:双精度浮点型,占用8个字节,最多保留16位小数。
在C#语言中默认的浮点型是double类型。如果要使用单精度浮点型,需要在数值后面加上f或F来表示,例如1.23f、1.23F。
- decimal类型:128位高精度十进制数表示法,是一种用于财务计算的专用类型。
decimal d = 12.30M;
2.1.3 字符型
C#提供得字符类型按照国际上公认的标准,采用Unicode字符集,字符长度为16位,能存放一个汉字。
char c='A';
// 使用十六进制转义符对变量赋值
char c='\x0032';
// 使用Unicode表示法对变量赋值
char c='\u0032';
常用的转义字符如下表所示。
2.1.4 布尔类型
布尔类型使用 bool 来声明,它只有两个值,即true和false。
2.1.5 结构体类型
struct 结构体名称
{
//结构体成员
}
可以看到,与C语言中的结构体类似。例子如下。
struct PhoneBook{
public string name;
public string phone;
public string address;
}
//定义结构体类型变量
PhoneBook p1;
//访问结构体的成员变量
p1.name='Mike';
对齐长度:
如果没有显式的指定对齐长度,将以结构中占用空间最大的成员的长度作为对齐长度;如果要显式指定对齐长度,需设置StructLayoutAttribute.Pack,如:[StructLayout(LayoutKind.Sequential,Pack=4)]
。
struct myStruct{
int a;
decimal b; // 可以认为4个int组成,共16字节
double c;
}
// 要想打结构体的长度,首先要在项目属性中勾选“允许不安全代码”,然后语法写法如下
unsafe{
System.Console.WriteLine(sizeof(myStruct));
}
// 按double8字节对齐,故sizeof(myStruct)的大小是32字节
2.1.6 枚举类型
枚举(enum)是为一组在逻辑上密不可分的整数值提供便于记忆的符号。默认情况下,枚举中的每个元素类型都是int型,且第一个元素的值为0,后面元素的值依次增加1;也可以给元素直接赋值。
enum 变量名{
值1,值2,
}
例子如下。
enum WeekDay{
Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday
};
// 定义枚举类型变量
WeekDay day;
2.2 运算符
2.2.1 算术运算符
运算符 | 说 明 |
---|---|
+ | 对两个操作数做加法运算 |
- | 对两个操作数做减法运算 |
* | 对两个操作数做乘法运算 |
/ | 对两个操作数做除法运算 |
% | 对两个操作数做取余运算 |
需要注意的是:
- 当对两个
字符串类型
的值使用 + 运算符,代表的是两个字符串值的连接,例如 “123”+“456” 的结果为 “123456” 。 - 当使用
/
运算符时也要注意操作数的数据类型,如果两个操作数的数据类型都为整数,那么结果相当于取整运算,不包括余数;而两个操作数中如果有一个操作数的数据类型为浮点数,那么结果则是正常的除法运算。
2.2.2 逻辑运算符
在使用逻辑运算符时需要注意逻辑运算符两边的表达式返回的结果都必须是布尔型的。
运算符 | 含义 | 说明 |
---|---|---|
&& | 逻辑与 | 如果运算符两边都为True,则整个表达式为True,否则为False;如果左边操作数为False,则不对右边表达式进行计算,相当于“且”的含义 |
|| | 逻辑或 | 如果运算符两边有一个或两个为True,整个表达式为True,否则为 False;如果左边为True,则不对右边表达式进行计算,相当于“或”的含义 |
! | 逻辑非 | 表示和原来的逻辑相反的逻辑 |
2.2.3 比较运算符
使用比较运算符运算得到的结果是布尔型的值,因此经常将使用比较运算符的表达式用到逻辑运算符的运算中。
运算符 | 说 明 |
---|---|
== | 表示两边表达式运算的结果相等,注意是两个等号 |
!= | 表示两边表达式运算的结果不相等 |
> | 表示左边表达式的值大于右边表达式的值 |
< | 表示左边表达式的值小于右边表达式的值 |
>= | 表示左边表达式的值大于等于右边表达式的值 |
<= | 表示左边表达式的值小于等于右边表达式的值 |
2.2.4 位运算符
运算符 | 说 明 |
---|---|
& | 按位与。两个运算数都为1,则整个表达式为1,否则为0;也可以对布尔型的值进行比较,相当于“与”运算,但不是短路运算 |
| | 按位或。两个运算数都为0,则整个表达式为0,否则为1;也可以对布尔型的值进行比较,相当于“或”运算,但不是短路运算 |
~ | 按位非。当被运算的值为 1 时,运算结果为0;当被运算的值为0时,运算结果为1。该操作符不能用于布尔型。对正整数取反,则在原来的数上加1,然后取负数;对负整数取反,则在原来的数上加1,然后取绝对值 |
^ | 按位异或。只有运算的两位不同结果才为1,否则为0 |
<< | 左移。把运算符左边的操作数向左移动运算符右边指定的位数,右边因移动空出的部分补0 |
>> | 有符号右移。把运算符左边的操作数向右移动运算符右边指定的位数。如果是正值,左侧因移动空出的部分补0;如果是负值,左侧因移动空出的部分补1 |
>>> | 无符号右移。和>>的移动方式一样,只是不管正负,因移动空出的部分都补0 |
比较常用的是左移运算符和右移运算符,左移1位相当于将操作数乘2,右移 位相当于将操作数除2。
2.2.5 三元运算符
三元运算符也被称为条件运算符,用于条件判断。
布尔表达式 ? 表达式1 : 表达式2
其中:
- 布尔表达式:判断条件,它是一个结果为布尔型值的表达式。
- 表达式1:如果布尔表达式的值为True,该三元运算符得到的结果就是表达式1的运算结果。
- 表达式2:如果布尔表达式的值为False,该三元运算符得到的结果就是表达式2的运算结果。
2.2.6 赋值运算符
运算符 | 说 明 |
---|---|
= | x=y,等号右边的值给等号左边的变量,即把变量y的值赋给变量x |
+= | x+=y,等同于 x=x+y |
-= | x-=y,等同于 x=x-y |
*= | x*=y,等同于 x=x * y |
/= | x/=y,等同于 x=x/y |
%= | x%=y,等同于 x=x%y,表示求 x 除以 y 的余数 |
++ | x++或++x,等同于 x=x+1 |
– | x–或--x,等同于 x=x-1 |
2.2.7 运算符的优先级
运算符 | 结合性 |
---|---|
.(点)、()(小括号)、[](中括号) | 从左到右 |
+ (正)、-(负)、++ (自增)、–(自减)、~(按位非)、!(逻辑非) | 从右到左 |
* (乘)、/ (除)、% (取余) | 从左向右 |
+ (加)、-(减) | 从左向右 |
<<、>>、>>> | 从左向右 |
<、<=、>、>= | 从左向右 |
==、!= | 从左向右 |
& | 从左向右 |
| | 从左向右 |
^ | 从左向右 |
&& | 从左向右 |
|| | 从左向右 |
?: | 从右到左 |
=、+=、-=、*=、/=、%=、&=、|=、^=、~=、<<=、>>=、>>>= | 从右到左 |
尽管运算符本身已经有了优先级,但在实际应用中还是建议尽量在复杂的表达式中多用括号来控制优先级,以增强代码的可读性。
2.3 变量
变量(variable)可以理解为存放数据的容器,并且在将值存放到变量中时还要为变量指定数据类型。
定义变量的语法如下:
数据类型 变量名;
int num;
赋值的语法有两种方式,一种是在定义变量的同时直接赋值,一种是先定义变量然后再赋值。
// 1.
数据类型 变量名 = 值;
// 2.
数据类型 变量名;
变量名 = 值;
例子如下:
using System;
class Program
{
static void Main(string[] args)
{
int num1 = 100;
double num2 = 100.123;
bool isFlag = true;
String name;
name = "Hello";
Console.WriteLine("num1= "+ num1);
Console.WriteLine("num2=" + num2);
Console.WriteLine("isFlag=" + isFlag);
Console.WriteLine("name=" + name);
}
}
带有static
修饰符声明的变量称为静态变量,一旦定义赋值后,会一直存在到包含该类的程序运行结束。
2.4 类型推断
类型推断使用关键字var
,使用var
关键字代替实际类型,编译器可以根据变量的初始化值“推断”变量的类型。
// 两者相等
var someNumber = 0;
int someNumber = 0;
类型推断遵循的规则:
- 变量必须初始化;
- 初始化器不能为空;
- 初始化器必须放在表达式中;
- 不能把初始化器设置为一个对象,除非在初始化器中创建了一个新对象。
2.5 常量
与变量不同的是,常量在第一次被赋值后值就不能再改变。定义常量需要使用关键字const
来完成。常量的值必须能在编译时用于计算,不能用从变量中提取的值来初始化常量(除非使用只读字段)。
const 数据类型 常量名 = 值;
使用常量的好处:
- 由于使用易于读取的名称代替了较难读取的数字和字符串,常量使程序变得更易于阅读;
- 常量使程序易于修改;
- 常量更容易避免程序出现错误。
在引用常量时,编译器是将常量的值替换常量,故常量必须在声明时初始化。
2.6 命名规则
- 标识符命名必须以字母或下划线开头;
- 不能把C#关键字用作标识符。
常用的命名方法有两种,一种是Pascal命名法(帕斯卡命名法),另一种是Camel命名法(驼峰命名法)。
Pascal命名法是指每个单词的首字母大写;Camel命名法是指第一个单词小写,从第二个单词开始每个单词的首字母大写。
2.7 条件语句
2.7.1 if语句
- 单一条件的 if 语句
if(布尔表达式)
{
语句块;
}
- 二选一条件的 if 语句
if(布尔表达式)
{
语句块 1;
}else{
语句块 2;
}
- 多选一条件的 if 语句
if(布尔表达式 1)
{
语句块 1;
}else if(布尔表达式 2){
语句块 2;
}
...
else{
语句块 n;
}
2.7.2 switch语句
switch
语句可以实现多分支。
switch(表达式)
{
case 值 1:
语句块 1;
break;
case 值 2:
语句块 2;
break;
...
default:
语句块 n;
break;
}
表达式的结果必须是整型、字符串类型、字符型、布尔型等数据类型。
表达式的值与case后面的值相同,则执行相应的case后面的语句块;如果表达式的值与所有的case语句都不相同,则执行default语句后面的值。
下面的例子实现的功能是根据学生的考试成绩来判断等级。
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("请输入学生考试的成绩(0~100的整数)");
int points = int.Parse(Console.ReadLine());
if(points < 0 || points > 100)
{
points = 0;
}
switch (points / 10)
{
case 10:
case 9:
Console.WriteLine("优秀");
break;
case 8:
Console.WriteLine("良好");
break;
case 7:
case 6:
Console.WriteLine("及格");
break;
default:
Console.WriteLine("不及格");
break;
}
}
}
2.8 循环语句
在循环语句中经常使用break和continue语句,用于控制循环次数。
- break语句:用于中断循环,使循环不再执行。
- continue语句:跳过当前循环中,进入下一次循环。
2.8.1 for循环
for循环是最常用的循环语句,多用于固定次数的循环。
for(表达式1; 表达式2; 表达式3)
{
语句块;
}
其中:
- 表达式1:为循环变量赋初值。
- 表达式2:为循环设置循环条件,通常是布尔表达式。
- 表达式3:用于改变循环变量的大小。
- 语句块;:当满足循环条件时执行。
下面的例子实现的功能是打印九九乘法表。
using System;
class Program
{
static void Main(string[] args)
{
for(int i = 1; i < 10; i++)
{
for(int j = 1; j <= i; j++)
{
Console.Write(i + "x" + j + "=" + i*j + "\t");
}
Console.WriteLine();
}
}
}
2.8.2 while循环
while循环一般适用于不固定次数的循环。
while(布尔表达式)
{
语句块;
}
2.8.3 do while循环
do while循环与while循环很类似,最大的区别是它至少会执行一次。
do
{
语句块;
}while(布尔表达式);
先执行do{}
中语句块的内容,再判断while()中布尔表达式的值是否为True,如果为 True,则继续执行语句块中的内容,否则不执行,因此do while循环中的语句块至少会执行一次。
2.8.4 foreach循环
foreach循环表示收集一个集合中的各元素,in左边的元素是只读元素,不能进行赋值。
foreach(元素 in 集合){
语句块;
}
2.9 预处理指令
预处理指令 | 描述 |
---|---|
#define | 它用于定义一系列成为符号的字符。 |
#undef | 它用于取消定义符号。 |
#if | 它用于测试符号是否为真。 |
#else | 它用于创建复合条件指令,与 #if 一起使用。 |
#elif | 它用于创建复合条件指令。 |
#endif | 指定一个条件指令的结束。 |
#line | 它可以让您修改编译器的行数以及(可选地)输出错误和警告的文件名。 |
#error | 它允许从代码的指定位置生成一个错误。 |
#warning | 它允许从代码的指定位置生成一级警告。 |
#region | 它可以让您在使用 Visual Studio Code Editor 的大纲特性时,指定一个可展开或折叠的代码块。 |
#endregion | 它标识着 #region 块的结束。 |
例1如下。
#define PI
using System;
namespace PreprocessorDAppl
{
class Program
{
static void Main(string[] args)
{
#if (PI)
Console.WriteLine("PI is defined");
#else
Console.WriteLine("PI is not defined");
#endif
Console.ReadKey();
}
}
}
// PI is defined
例2如下。
#define DEBUG
#define C#
using System;
public class TestClass
{
public static void Main()
{
#if (DEBUG && !C#)
Console.WriteLine("DEBUG is defined");
#elif (!DEBUG && C#)
Console.WriteLine("C# is defined");
#elif (DEBUG && C#)
Console.WriteLine("DEBUG and C# are defined");
#else
Console.WriteLine("DEBUG and C# are not defined");
#endif
Console.ReadKey();
}
}
// DEBUG and C# are defined
思考1:为什么变量未初始化不能引用?
C#编译器将没初始化当成错误看待,这样做的目的是强化了安全性,可以防止无意中从其他程序遗留下来的内存中检索垃圾值。
思考2:为什么指针不是类型安全的,而引用是类型安全的?
指针和引用都是存储地址的。引用在设置地址后初始化才能使用;而指针没有这个要求,可能会成为野指针。