由于在自己的工作和学习过程中,只查看某个大佬的教程或文章无法满足自己的学习需求和解决遇到的问题,所以自己在追赶大佬们步伐的基础上,又自己总结、整理、汇总了一些资料,方便自己理解和后续回顾,同时也希望给大家带来帮助,所以才写下该篇文章。在本文中,所有参考或引用大佬们文章内容的位置,都附上了原文章链接,您可以直接前往查阅观看。在原文章内容的基础上,若无任何补充内容,同时避免直接大段摘抄大佬们的文章,该情况下也只附上了原文章链接供大家学习。本文旨在总结归纳,并希望给大家提供帮助,未用作任何商用用途。文章内容如有错误之处,望各位大佬指出。如果涉及侵权行为,将会第一时间对文章进行删除。
👉 个人博客主页 👈
📝 一个努力学习的程序猿
🧑👉 本文所在专栏: C# 专栏,欢迎大家前往查看更多内容
专栏内目前所有文章:
🚧 不定期持续更新中
为了更好的说明之后的循环,先总结一下数组内容。对于数组,上篇文章已经有过简单的使用,基础使用如下:
int[] list = new int[] { 1, 2 };
list[0] = 5;
数组用来存储相同数据类型的数据。如果要使用其中的数据,使用索引来访问即可。然后在使用上,我们需要注意以下问题:
(1)初始化数组的时候,必须要给定数组大小。比如可以用这几种方式给定:
方法一:如果此时有确切初始值,那么可以不指定数组大小。
int[] list = new int[] { 1, 2 };
或者
int[] list = { 1, 2 };
方法二:如果此时没有确切初始值,那么必须指定数组大小(此时数组中的所有元素都有初始值,初始值根据不同数据类型决定)。
int[] list = new int[2];
Console.WriteLine("list数组: {0}", list[0]); // list数组: 0
否则报错:
int[] list = new int[];

方法三:如果指定数组大小,那么初始值数量必须与数组大小匹配。
int[] list = new int[2] { 1, 2 };
Console.WriteLine("list数组: {0}", list[0]); // list数组: 1
否则报错:
int[] list = new int[2] { 1, 2, 3 };

(2)通过以上说明已经得知,数组肯定需要给定大小,所以使用时需要注意不要越界,否则报错:
int[] list = { 1, 2 };
list[2] = 3;

(3)如果要使用多维数组:
int[,] list = new int[3,4] {
{0, 1, 2, 3}, // 索引为 0 的行
{4, 5, 6, 7}, // 索引为 1 的行
{8, 9, 10, 11} // 索引为 2 的行
};
Console.WriteLine("list数组: {0}", list[1,1]); // list数组: 5
注意,依然需要和使用一维数组一样,在初始化的时候给定数组大小,在这里不详细举例了。其中 new int[3,4] 意味着这是一个 三行四列 的二维数组,使用的索引还是需要从 0 起。
其他和数组相关的内容,为了避免大篇幅引用,感兴趣的话请您自行前往查阅:
交错数组:
https://www.runoob.com/csharp/csharp-jagged-arrays.html
参数数组:
https://www.runoob.com/csharp/csharp-param-arrays.html
和其他语言一样,最后再总结一下数组常用的属性和方法。
在上篇文章里有提到过,在代码第一行中的 using System; ,就是用来引入 System 命名空间。而这个 System 命名空间中,就有着所有 .net 基础类型和通用类型。而所有数组的基类 Array 也是在 System 命名空间中定义的,Array 类提供了这些用于数组的属性和方法。
常用属性【表格转自菜鸟教程】:
| 属性 | 描述 |
|---|---|
| IsFixedSize | 获取一个值,该值指示数组是否带有固定大小 |
| IsReadOnly | 获取一个值,该值指示数组是否只读 |
| Length | 获取一个 32 位整数,该值表示所有维度的数组中的元素总数 |
| LongLength | 获取一个 64 位整数,该值表示所有维度的数组中的元素总数 |
| Rank | 获取数组的秩(维度) |
代码演示:
using System;
namespace Test
{
class Program
{
static void Main(string[] args)
{
int[] list = { 4, 1, 6 };
// 1、IsFixedSize 获取一个值,该值指示数组是否带有固定大小
Console.WriteLine("{0}", list.IsFixedSize); // True
// 2、IsReadOnly 获取一个值,该值指示数组是否只读
Console.WriteLine("{0}", list.IsReadOnly); // False
// 3、Length 获取一个 32 位整数,该值表示所有维度的数组中的元素总数
Console.WriteLine("{0}", list.Length); // 3
// 4、LongLength 获取一个 64 位整数,该值表示所有维度的数组中的元素总数
Console.WriteLine("{0}", list.LongLength); // 3
// 5、Rank 获取数组的秩(维度)
Console.WriteLine("{0}", list.Rank); // 1
Console.ReadLine();
}
}
}
输出结果:
True
False
3
3
1
常用方法【表格转自菜鸟教程】:
| 方法 | 描述 |
|---|---|
| Clear(Array, Int32, Int32) | 根据元素的数据类型,将 Array 中第一个 Int32 索引开始,其后第二个 Int32 数量的元素,设置为零、false 或 null。括号内参数均为必填项。 |
| Copy(Array, Array, Int32) | 从第一个 Array 的第一个元素开始复制到 Int32 长度的元素到第二个 Array 的第一个元素位置。括号内参数均为必填项。 |
| GetLength(Int32) | 获取一个 32 位整数,该值表示指定 Int32 维度的数组中的元素总数。括号内参数均为必填项。 |
| GetLongLength(Int32) | 获取一个 64 位整数,该值表示指定 Int32 维度的数组中的元素总数。括号内参数均为必填项。 |
| GetLowerBound(Int32) | 获取数组中指定 Int32 维度的下界。括号内参数均为必填项。 |
| GetType | 获取当前实例的类型。 |
| GetUpperBound(Int32) | 获取数组中指定 Int32 维度的上界。括号内参数均为必填项。 |
| GetValue(Int32) | 获取一维数组中 Int32 索引位置的值。括号内参数均为必填项。 |
| IndexOf(Array, Object) | 搜索指定的对象(内容),返回整个一维数组 Array 中第一次出现该对象(内容)的索引。括号内参数均为必填项。 |
| Reverse(Array) | 逆转整个一维数组 Array 中元素的顺序。括号内参数均为必填项。 |
| SetValue(Object, Int32) | 给一维数组中指定 Int32 位置的元素设置 Object 值。括号内参数均为必填项。 |
| Sort(Array) | 使用数组的每个元素的 IComparable 实现来排序整个一维数组中的元素。括号内参数均为必填项。 |
代码演示:
using System;
namespace Test
{
class Program
{
static void Main(string[] args)
{
int[] list = { 4, 1, 6 };
// 6、Clear(Array, Int32, Int32)
Array.Clear(list, 1, 1);
foreach (int i in list) {
Console.WriteLine("{0}", i); // 4 0 6
}
// 7、Copy(Array, Array, Int32)
int[] copyList = new int[3];
Array.Copy(list, copyList, list.Length);
foreach (int i in copyList) {
Console.WriteLine("{0}", i); // 4 0 6
}
// 8、GetLength(Int32)
Console.WriteLine("{0}", list.GetLength(0)); // 3
// 9、GetLongLength(Int32)
Console.WriteLine("{0}", list.GetLongLength(0)); // 3
// 10、GetLowerBound(Int32)
Console.WriteLine("{0}", list.GetLowerBound(0)); // 0
// 11、GetType
Console.WriteLine("{0}", list.GetType()); // System.Int32[]
// 12、GetUpperBound(Int32)
Console.WriteLine("{0}", list.GetUpperBound(0)); // 2
// 13、GetValue(Int32)
Console.WriteLine("{0}", list.GetValue(0)); // 4
// 14、IndexOf(Array, Object)
Console.WriteLine("{0}", Array.IndexOf(list, 4)); // 0
// 15、Reverse(Array)
Array.Reverse(list);
foreach (int i in list) {
Console.WriteLine("{0}", i); // 6 0 4
}
// 16、SetValue(Object, Int32)
list.SetValue(10, 0);
Console.WriteLine("{0}", list[0]); // 10
// 17、Sort(Array)
Array.Sort(list);
foreach (int i in list) {
Console.WriteLine("{0}", i); // 0 4 10
}
Console.ReadLine();
}
}
}
输出结果:
4
0
6
4
0
6
3
3
0
System.Int32[]
2
4
0
6
0
4
10
0
4
10
需要注意的就是,以上一部分方法只能对一维数组使用,且有的操作会直接影响原数组,比如:Clear、Reverse、Sort 等等。如果不想改变原数组,那就可以用 Copy 复制一份。
判断和循环是十分常用的用法,和其他语言很类似,在这里就不过多说明了。展示一下相关使用:
(1)if 语句:
代码:
using System;
namespace Test
{
class Program
{
static void Main(string[] args)
{
int num = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("输入的 num 值是 {0}", num);
if (num >= 10 && num < 20) {
Console.WriteLine("num 大于等于 10,小于 20");
} else if (num < 10) {
Console.WriteLine("num 小于 10");
} else {
Console.WriteLine("num 大于 20");
}
Console.ReadLine();
}
}
}
输出结果:
15
输入的 num 值是 15
num 大于等于 10,小于 20
(2)switch 语句:
代码【选自菜鸟教程】:
using System;
namespace Test
{
class Program
{
static void Main(string[] args)
{
char grade = Convert.ToChar(Console.ReadLine());
switch (grade)
{
case 'A':
Console.WriteLine("很棒!");
break;
case 'B':
case 'C':
Console.WriteLine("做得好");
break;
case 'D':
Console.WriteLine("您通过了");
break;
case 'F':
Console.WriteLine("最好再试一下");
break;
default:
Console.WriteLine("无效的成绩");
break;
}
Console.WriteLine("您的成绩是 {0}", grade);
Console.ReadLine();
}
}
}
输出结果:
B
做得好
您的成绩是 B
(3)for 语句:
代码【选自菜鸟教程】:
using System;
namespace Test
{
class Program
{
static void Main(string[] args)
{
for (int a = 1; a < 5; a++)
{
Console.WriteLine("a 的值: {0}", a);
}
Console.ReadLine();
}
}
}
输出结果:
a 的值: 1
a 的值: 2
a 的值: 3
a 的值: 4
之后的所有循环,依然可以像其他语言一样,使用 break 和 continue。
break 就是跳出所有循环,去执行循环语句的下一行代码;
continue 就是跳出当前的这一次循环,进入循环语句的下一次循环;
(4)foreach 语句:
和其他语言一样,foreach 用来迭代数组或者一个集合对象。
代码【选自菜鸟教程】:
using System;
namespace Test
{
class Program
{
static void Main(string[] args)
{
int[] fibarray = new int[] { 1, 2, 3, 4 };
foreach (int element in fibarray)
{
Console.WriteLine(element);
}
Console.ReadLine();
}
}
}
输出结果:
1
2
3
4
(5)while 语句:
代码【选自菜鸟教程】:
using System;
namespace Test
{
class Program
{
static void Main(string[] args)
{
int a = 15;
while (a < 20)
{
Console.WriteLine("a 的值: {0}", a);
a++;
}
Console.ReadLine();
}
}
}
输出结果:
a 的值: 15
a 的值: 16
a 的值: 17
a 的值: 18
a 的值: 19
(6)do…while 语句:
代码【选自菜鸟教程】:
using System;
namespace Test
{
class Program
{
static void Main(string[] args)
{
int a = 15;
do
{
Console.WriteLine("a 的值: {0}", a);
a++;
} while (a < 20);
Console.ReadLine();
}
}
}
输出结果:
a 的值: 15
a 的值: 16
a 的值: 17
a 的值: 18
a 的值: 19
在上篇文章里,简单说明了字符串类型的使用以及转义字符,简单使用如下:
string a = "hello, world";
string str = @"C:\Windows";
而字符串也有着它自己的属性和方法。
常用属性【表格转自菜鸟教程】:
| 属性 | 描述 |
|---|---|
| Length | 当前 String 对象中的字符数 |
代码演示:
using System;
namespace Test
{
class Program
{
static void Main(string[] args)
{
string str = "CSDN";
Console.WriteLine("{0}", str.Length);
Console.ReadLine();
}
}
}
输出结果:
4
常用方法【表格转自菜鸟教程】:
| 属性 | 描述 |
|---|---|
| Compare(strA, strB, bool) | 比较 strA,strB 两个指定的 string 对象,如果相同返回 0,不同返回 -1。如果不传递 bool 参数或传递 false,那么该方法区分大小写。如果 bool 参数为 true 时,该方法不区分大小写 |
| Concat(str0, str1, …) | 连接多个 string 对象,并返回连接结果 |
| Contains(value) | 返回当前 string 对象是否包含 value 字符或字符串 |
| Copy(str) | 创建一个与指定字符串具有相同值的新 String 对象 |
| EndsWith(value) | 判断当前 string 对象的结尾是否匹配指定的 value 字符或字符串 |
| Equals(str0, str1) | 判断两个 string 对象是否具有相同的值 |
| Format(format, arg0) | 把指定字符串中一个或多个格式项替换为指定对象的字符串表示形式 |
| IndexOf(value) | 返回指定 value 字符或字符串在当前字符串中第一次出现的索引 |
| IsNullOrEmpty(str) | 判断指定的字符串是否为 null 或者的字符串 |
| Replace(oldValue, newValue) | 把当前 string 对象中,所有指定的字符串替换为另一个指定的字符串,并返回新的字符串 |
| ToLower() | 把字符串转换为小写并返回 |
| ToUpper() | 把字符串转换为大写并返回 |
| Trim() | 移除当前 String 对象中的所有前导空白字符和后置空白字符 |
代码演示:
using System;
namespace Test
{
class Program
{
static void Main(string[] args)
{
string str1 = "CSDN1";
string str2 = "CSDN2";
Console.WriteLine("{0}", String.Compare(str1, str2));
Console.WriteLine("{0}", String.Concat(str1, str2));
Console.WriteLine("{0}", str1.Contains("CSDN"));
Console.WriteLine("{0}", String.Copy(str1));
Console.WriteLine("{0}", str1.EndsWith("N1"));
Console.WriteLine("{0}", String.Equals(str1, str2));
Console.WriteLine("{0}", str1.IndexOf("SDN"));
Console.WriteLine("{0}", String.IsNullOrEmpty(str1));
Console.WriteLine("{0}", str2.Replace("SDN2", "SDN3"));
Console.WriteLine("{0}", str2.ToLower());
Console.WriteLine("{0}", str2.ToUpper());
string str3 = " test ";
Console.WriteLine("{0}", str3.Trim());
}
}
}
输出结果:
-1
CSDN1CSDN2
True
CSDN1
True
False
1
False
CSDN3
csdn2
CSDN2
test
更多内容为了避免大篇幅引用,感兴趣的话可以前往查看:
C# 字符串
https://www.runoob.com/csharp/csharp-string.html
枚举类型使用 enum 关键字来声明,枚举的使用如下所示:
enum Days { Sun, Mon, tue, Wed, thu, Fri, Sat };
其中的枚举列表中的每个符号都将会代表一个整数值,如果没有给与初始值的话,那么第一个枚举符号的值是 0,其后的每个枚举符号的值都比前面一个枚举符号大 1。如果给予初始值,那么每个枚举符号就会使用这个初始值。如果部分枚举符号存在初始值,部分枚举符号没有初始值,那么没有初始值的枚举符号的值,都只会比它之前的枚举符号大 1。比如:
using System;
namespace Test
{
class Program
{
enum Day { Sun, Mon, Tue, Wed, Thu, Fri, Sat };
enum Day2 { Sun=7, Mon, Tue, Wed, Thu, Fri=5, Sat };
static void Main(string[] args)
{
int num1 = (int)Day.Sun;
int num2 = (int)Day.Sat;
Console.WriteLine("Sun = {0}", num1);
Console.WriteLine("Sat = {0}", num2);
int num3 = (int)Day2.Sun;
int num4 = (int)Day2.Mon;
int num5 = (int)Day2.Sat;
Console.WriteLine("Sun = {0}", num3);
Console.WriteLine("Mon = {0}", num4);
Console.WriteLine("Sat = {0}", num5);
}
}
}
输出结果:
Sun = 0
Sat = 6
Sun = 7
Mon = 8
Sat = 6
关于枚举的使用情景,可以像下例一样,像是一种开发者所提供的对数组的说明,从而让程序更容易理解。比如您可以这样用:
using System;
namespace Test
{
class Program
{
enum rectangle { width, height, area };
static void Main(string[] args)
{
int[] list = { 3, 4, 12 };
Console.WriteLine("矩形宽度 = {0}", list[(int)rectangle.width]);
Console.WriteLine("矩形高度 = {0}", list[(int)rectangle.height]);
Console.WriteLine("矩形面积 = {0}", list[(int)rectangle.area]);
}
}
}
输出结果:
矩形宽度 = 3
矩形高度 = 4
矩形面积 = 12
如上所示,因为数组本身存放的数据是无法定义具体含义的,比如只看 0 号索引,在无说明的情况下,肯定无法知道它代表着 width。所以如果真想用数组存放这些有意义的数据,那么为了方便他人和自己查看,就可以考虑用枚举。此时利用枚举的特点,通过设置或不设置初始值,来完成类似上面的功能。
说白了,上面的功能就相当于这样:
using System;
namespace Test
{
class Program
{
static void Main(string[] args)
{
int width = 3;
int height = 4;
int area = width * height;
Console.WriteLine("矩形宽度 = {0}", width);
Console.WriteLine("矩形高度 = {0}", height);
Console.WriteLine("矩形面积 = {0}", area);
}
}
}
但是如果有多个矩形的话,不用枚举显然需要定义多个变量。使用枚举就可以少写几行代码。
除此以外,在下篇文章中要详细说的类也可以实现类似枚举的用法(类的详细内容在下篇文章说明),比如:
using System;
namespace Test
{
class Rectangle
{
protected int width, height;
public Rectangle(int w, int h)
{
width = w;
height = h;
}
public int getArea()
{
return width * height;
}
}
class Program
{
static void Main(string[] args)
{
Rectangle r = new Rectangle(3, 4);
Console.WriteLine("面积:{0}", r.getArea());
}
}
}
在面对多个矩形的情况下,只需要实例化多个对象即可。不过相比来说,如果没有复杂需求,就这个功能而言,枚举相对更简易。
想要创建结构体需要用 struct 关键字,结构体很像是在上篇文章中使用的类,它用来存储各种数据类型的相关数据。但是结构体和类还是有些区别的,这些区别在下文说明。首先先看一下结构体该如何使用:
代码演示:
using System;
// 结构不能继承其他的结构或类,也不能作为其他结构或类的基础结构
// 结构可实现一个或多个接口
struct Pet
{
// 结构体中声明的字段无法赋予初值
// 结构成员不能指定为 abstract、virtual 或 protected
public string name;
public int age;
// 结构体中可定义构造函数,但不能定义析构函数
// 但是不能定义无参构造函数。无参构造函数是自动定义的,且不能被改变
// 结构体的构造函数中,必须为结构体所有字段赋值
public Pet(string name, int age) {
this.name = name;
this.age = age;
}
public void setValues(string name, int age) {
this.name = name;
this.age = age;
}
public void display() {
Console.WriteLine("name = {0}", name);
Console.WriteLine("age = {0}", age);
}
}
namespace Test
{
class Program
{
static void Main(string[] args)
{
// 与类不同,结构可以不使用 new 操作符,即可被实例化
Pet pet1;
pet1.name = "测试";
pet1.age = 15;
// 如果不使用 new 操作符,只有在所有的字段都被初始化之后,对象才能被使用
pet1.setValues("狗", 5);
pet1.display();
// 使用 new 操作符创建一个结构对象时,会调用适当的构造函数来创建结构
Pet pet2 = new Pet("猫", 2);
pet2.display();
}
}
}
输出结果:
name = 狗
age = 5
name = 猫
age = 2
其中的相关使用说明在对应位置上写了一些简单注释,在这里详细说一下。
(1)对于结构体来说,它不能继承其他的结构或类,也不能作为其他结构或类的基础结构,但它可以实现一个或多个接口。而对于继承和接口,在这里先不做说明了,在下篇文章将会提到这些内容,在这里可以先记住有这个问题。
(2)结构体中声明的字段无法赋予初值,如果赋值就会报错:
public string name = "测试";

(3)结构体中不能定义析构函数,可定义构造函数,但是不能定义无参构造函数(无参构造函数依然会自动定义,只不过不允许我们修改),且如果定义构造函数,那么它必须为结构体所有字段赋值,否则报错:
public Pet(string name) {
this.name = name;
}

(4)如果不使用 new 操作符实例化对象,那么像构造函数的说明一样,必须得在具体使用对象前,为结构体所有字段赋值,否则报错:
Pet pet1;
pet1.name = "测试";
pet1.setValues("狗", 5);

那么这也就意味着:如果不用构造函数,那么想要使用它的方法前,必须手动为所有字段赋值,即使在这里想用 setValues 赋值,但是 setValues 在没有初始值之前是不会调用成功的。所以,如果能够手动为所有字段赋值,那么就代表着结构体中的所有字段都得至少是 public 才能被外部修改访问。那么这样一来就需要注意是否有些字段不希望外部访问到。
如果有些字段不希望外部访问,那么就需要使用构造函数,而使用构造函数由于(3)展示的问题,那么就必须一次性为所有字段赋值。
最后通过以上使用,总结来说,类和结构会有以下几点区别:
(1)结构体不支持继承,但类无此限制。
(2)结构体中不能声明无参构造函数,不能声明析构函数,且只要声明构造函数,那么它就必须为结构体所有字段赋值,但类无此限制。
(3)结构体中声明的字段无法赋予初值,但类无此限制。
(4)与类不同,结构体可以不使用 new 操作符实例化对象。
因此从目前来看,结构体好像就是类的缩水版,没有多出新功能,但却多了很多限制,那么结构体到底有什么用呢?
首先,它们还有一个更深层次的区别,在上面没有说明,但是这个区别可以从使用上感觉到。你会发现结构体是必须定义初始值的,同时也无法实现继承。这很像在上篇文章中提到过的 值类型和引用类型的区别,也就是值类型是有固定内存空间的,存放在栈中。而引用类型会存放一个内存地址,具体数据都存放在堆中。而这也正是类和结构更深层次的区别。也就是:类是引用类型,结构体是值类型。
那说到这就可以明确类和结构体的使用情景区别了:
类是引用类型,它的值会存储在堆中;而结构体是值类型,它的值会存储在栈中。所以就会导致,使用结构体时,它的访问速度相对类会更快(复杂情况除外)。因此,当我们想要声明一个轻量级对象的时候(比如只有几个成员变量),使用结构体可提高效率,成本也会更低。但是如果真的有复杂或大量逻辑功能,需要写很多成员函数,那么这时候继续使用结构体就有一些得不偿失,不如使用类。具体来说,比如要将声明的复杂的结构体赋值给另一个结构体时,由于值类型直接存储数据,那么性能肯定受限于结构的大小。但如果声明的是类,传递一个内存地址显然更轻松。这样也就产生了其他需要考虑的点。因为结构体是值类型,所以传递给其他对象的是当前对象的拷贝;因为类是引用类型,所以传递给其他对象的是当前对象的引用地址。如果需要考虑这个传参问题,那么还是得根据具体需求来考虑,而不是从效率方面来考虑。
而正是因为结构体的这些特性(简单来说就是效率高),所以通常可以用它来创建新的值类型。最具体的例子就是大学学习 C++ 的时候 或者 写伪代码 或者 学习原生数据结构时,有的例子或老师会让我们用结构体去创建一些数据结构,比如队列、栈、树等等。后来才学习工具库。
由于在自己的工作和学习过程中,只查看某个大佬的教程或文章无法满足自己的学习需求和解决遇到的问题,所以自己在追赶大佬们步伐的基础上,又自己总结、整理、汇总了一些资料,方便自己理解和后续回顾,同时也希望给大家带来帮助,所以才写下该篇文章。在本文中,所有参考或引用大佬们文章内容的位置,都附上了原文章链接,您可以直接前往查阅观看。在原文章内容的基础上,若无任何补充内容,同时避免直接大段摘抄大佬们的文章,该情况下也只附上了原文章链接供大家学习。本文旨在总结归纳,并希望给大家提供帮助,未用作任何商用用途。文章内容如有错误之处,望各位大佬指出。如果涉及侵权行为,将会第一时间对文章进行删除。
👉 个人博客主页 👈
📝 一个努力学习的程序猿
🧑👉 本文所在专栏: C# 专栏,欢迎大家前往查看更多内容
专栏内目前所有文章:
🚧 不定期持续更新中