最近在做一些需要用到 C# 开发的项目,许久未碰有些生疏,因此决定把语言的基础内容先都快速过一遍。为方便查阅,挑选最精简的部分整理于该博客。
【说明】
- 本系列的目标是整理出十个以内的博客(传送门),适合快速入门。目前更新到:精简 C# 入门(五)
- 本系列不适用于完全的编程初学者(未接触任何计算机语言),这一类读者建议仔细学习【参考资料】中的视频资源
【参考资料】
1.b站:【01_C#入门到精通】新手强烈推荐:C#开发课程,一整套课程
文章目录
#1 类的引用
引用系统类:
系统类一般都位于名字类似为 System.XXX
的命名空间,因此只需直接在代码开头添加:
using System.XXX;
引用自定义的类:
例如想要在 PlaneChess 项目中使用 ConsoleApp2 中定义的 Pet 类(下图红框):
- 右键单击
引用
,选择添加引用
- 在弹窗中勾选
ConsoleApp2
,点击确定
- 使用 using 添加
using ConsoleApp2
,然后就能调用其中的Pet
类了。
注意!这个该类必须是由public
修饰的,即允许外界访问
#2 字符串 string
作者在 精简 C# 入门(一)已经对各类基础数据类型进行了简短的概括,包括字符串。但由于字符串存在典型性与特殊性,因此在这里补充对其更细致的介绍。
2.1 字符串的不可变性 & StringBuilder
的使用
字符串为引用类型,其值储存在堆,地址储存在栈
这一块的内容详见 精简 C# 入门(一)Section #5
值相同的字符串指向同一个堆地址
如下图,字符串 s1
, s2
均指向同一个 “张三”,不存在第二个 “张三”
字符串的不可变性
当给字符串重新赋值时,原初值并没有销毁,而是在堆中重新开辟一块区域储存。如下图中字符串 s
赋新值 “孙权” 后,其初值 “张三” 并未在堆中被销毁。
不同于栈空间垃圾内存的立即销毁,像这样未被销毁的堆空间垃圾,只有当程序结束时才会被处理。因此为了防止程序运行时内存占用过高,要避免大量的对字符串的赋值操作
使用 StringBuilder
避免对字符串的操作占用过量内存
示例:将 1-100 的整数串成一个字符串
// 直接用 string:占用内存且耗时
string s = "";
for (int i = 0: i < 100; i++)
{
s += i;
}
// 使用 StringBuilder:不占内存且高效
StringBuilder sb = new StringBuilder();
for (int i = 0: i < 100; i++)
{
sb.Append(i);
}
string s = sb.ToString();
2.2 修改字符串
字符串相当于一个 char 类型的只读数组
我们可以使用 字符串名[index]
的方式来访问该位置的字符,但无法修改。
使用 char[]
与 string
的相互转换修改字符串中的字符
虽然字符串可以看作只读 char[]
,但是仍然可以通过一些列操作来修改其某个位置的字符,实现 “曲线救国”。示例:
string str = "Hello World!"
// 首先将其转化为 char[]
char[] chs = str.ToCharArray();
// 相当于删去 "Hello" 末尾的 "o"
chs[4] = "";
// 再转回字符串
str = new string(chs);
>>>
"Hell World!"
2.3 字符串的相关方法
以下代码中 []
里的内容表示该方法的返回值类型:
[double] str.Length; // 长度
[string] str.ToUpper(); // 转成大写
[string] str.ToLower(); // 转成小写
[bool] str.Equals(str1); // 判断两个字符串是否相同
// 该函数还有一个枚举类型的可选参数,具体作用百度
str.Equals(str1, StringComparison.XXX)
[bool] str.Contains(substr); // 判断字符串中是否包含 substr
[bool] str.StartsWith(substr); // 判断字符串是否以 substr 开头
[bool] str.EndsWith(substr); // 判断字符串是否以 substr 结尾
[bool] string.IsNullorEmpty(str); // 判断字符串是否为 null or ""
[int] str.IndexOf(substr); // 找 substr 第一次出现的 index
str.IndexOf(substr, startIndex) // 从 startIndex 开始找
[int] str.LastIndexOf(substr); // 找 substr 最后一次出现的 index
[string] str.Trim(); // 去掉字符串开头与结尾的空格
[string] str.TrimStart(); // 去开头空格
[string] str.TrimEnd(); // 去结尾空格
[string] str.Replace(substr1, substr1); // 将子串 substr1 替换为 substr2
[string] str.Substring(startIndex, length); // 从 startIndex 位置截取长度为 length 的子串
应用1:使用 LastIndexOf
, Substring
提取地址中文件名
string path = @"C:\file1\file2\1.txt":
int index = path.LastIndexOf("\\"); // "\\" 表示 "\"
string fileName = path.Substring(index+1);
>>>
"1.txt"
分割字符串
[string[]] str.Split(chs, StringSplitOptions.None/RemoveEmptyEntries)
chs
为char[]
类型,储存作为分割依据的字符StringSplitOptions.None/RemoveEmptyEntries
是一个枚举类型的可选参数
None
表示不删除分割后的空值""
RemoveEmptyEntries
表示删除
示例:
string str = "a b + c";
char[] chs = { ' ', '+' };
string[] splits = str.Split(chs, StringSplitOptions.RemoveEmptyEntries);
>>>
{ "a", "b", "c" }
string[] splits = str.Split(chs, StringSplitOptions.None);
>>>
{ "a", "b", "", "", "", "c" }
拼接字符串
[string] string.Split(connection, str);
示例:
string[] strs = { "a", "b", "c" };
string strJoint = string.Join(" | ", strs);
>>>
"a | b | c"
#3 文件操作之 File
类
File
类是一个静态的系统类,这里介绍一些其中的方法:
读取文件的每一行
using System.IO;
using System.Text;
[string[]] File.ReadAllLines(path, Encoding.XXX);
path
为 string 类型的路径,
Encoding.XXX
为解码方式,常见有 Encoding.Default, Encoding.UTF8 等
#4 继承
继承主要是类的继承,通过子类继承父类的形式,使得子类能够沿用父类当中的字段、属性与方法(只能继承 public 的),从而降低代码冗余。
示例,子类 Cat
继承父类 Animal
:
public class Animal
{
private int _age;
public int Age { get => _age; set => _age = value; }
}
public class Cat: Animal
{
// 代码块
}
继承具有传递性,即子类的子类能够继承父类中的字段、属性与方法。
子类不继承父类的构造函数。但是可以通过在子类的构造函数前添加 :base([参数列表])
来实现构造函数的继承,示例:
public class Animal
{
public Animal(int age)
{
this.Age = age;
}
private int _age;
public int Age { get => _age; set => _age = value; }
}
public class Cat: Animal
{
public Cat(int age, string name)
: base(age)
{
this.Name = name;
}
private string _name;
public string Name { get => _name; set => _name = value; }
}
如果在子类中需要写一个与父类中的同名的成员,建议为其添加一个关键字 new
(不加也不会报错,但是加了就看的比较清楚),示例:
public class Animal
{
public void Shout()
{
Console.WriteLine("发出了叫声");
}
}
public class Cat: Animal
{
public new void Shout()
{
Console.WriteLine("喵!");
}
}
#5 里式转换
核心含义:
- 子类可以赋值给父类
- 如果父类对象中装的是子类对象,那么可已将这个父类强转为子类对象
子类可以赋值给父类
依旧以上个代码片段中的 Animal
, Cat
类为例:
Cat cat = new Cat();
Animal animal1 = cat;
Animal animal2 = new Cat();
作用:当一个地方需要某类的对象时,可以使用其子类的对象替代
如果父类对象中装的是子类对象,那么可已将这个父类强转为子类对象
示例:
Aniaml animal = new Cat(); // 父类对象中装的是子类对象
Cat cat = (Cat)animal; // 强转
#6 protected
访问修饰符
private
: 仅限于类的内部访问
public
: 不限访问
protected
: 仅限于类内部及其子类访问
#7 ArrayList 泛型集合
注意:ArrayList 由于在读写的过程中存在类型转换(装箱/拆箱),导致其读写速度较为缓慢,因此一般更多地会 使用 List<> 代替 ArrayList,详见 Section 8
using System.Collections;
ArrayList list = new ArrayList();
集合的概念类似于数组,但是不同于数组的长度固定、元素类型单一,集合没有这些限制。集合也有缺陷,由于任何添加进集合的元素都会被自动转换为 object
类型,因此在之后调用这些元素的时候,需要使用强制类型转换,示例:
list.Add(1);
list.Add(2);
int sum = (int)list[0] + (int)list[1];
类方法:以下代码中 obj
表示任意类型的元素
list.Count; // 查看长度
list.Capacity; // 查看集合容量
// 数组容量只能等于 0, 4, 8, 16, 32... 依据集合长度而发生变化
// 例如 count=4 时 capacity=4, cout=5 时 capacity=8
list.Add(obj); // 添加元素
list.AddRange(数组、集合); // 依次添加数组或集合中的每个元素
list.Clear(); // 清空集合
list.Remove(obj); // 根据值来删除元素
list.RemoveAt(index); // 根据 index 删除元素
list.RemoveRange(start, end); // 移除一定范围的元素
list.Reverse(); // 反转
list.Insert(index, obj); // 插入元素
list.InsertRange(index, 数组、集合);
[bool] list.Contain(obj); // 判断是否包含
#8 List<> 泛型集合
创建对象
List<数据类型> 对象名 = new List<数据类型>();
List<> 泛型集合的大部分类方法同 ArrayList,详见 Section 7。除此以外,泛型集合也有一些独有的类方法:
List<int> list = new List<int>();
[int[]] list.ToArray(); // 将 List<> 转换为数组
// 当然也可以将数组转换为 List<>
[List<int>] array.ToList();
#9 HashTable 键值对集合
注意:HashTable 由于在读写的过程中存在类型转换(装箱/拆箱),导致其读写速度较为缓慢,因此一般更多地会 使用 Dictionary 代替 HashTable,详见 Section 10
类似 Python 中的字典,键必须唯一,值可以重复
using System.Collections;
HashTable ht = new HashTable();
类方法:
ht.Count; // 长度
ht.Add(键, 值); // 添加方法 1
ht[键] = 值; // 添加方法 2
// 这里的键、值可以是任意类型的元素,类似于 Python 中的 dict[key] = value;
ht.Clear(); // 清空
ht.Remove(键); // 根据键来移除键值对
[value] ht[key]; // 根据键找值
[collection] ht.Keys; // 返回所有键
[collection] ht.Values; // 返回所有值
// 读取键、值时,无法使用 ht.Keys[index] 这样的形式
// 但可以使用 foreach (var item in ht.Keys) { } 来读取
[bool] ht.ContainsKey(键); // 判断是否包含某个键
[bool] ht.COntainsValue(值); // 判断是否包含某个值
#10 Dictionary 键值对集合
创建对象
Dictionary<键类型, 值类型> dict = new Dictionary<键类型, 值类型>();
Dictionary 键值对集合的大部分类方法同 HashTable,详见 Section 9。
读取 Dictionary 中的键值对的两种方法
Dictionary<int, string> dict = new Dictionary<int, string>();
// 方法 1
foreach (var key in dict.Keys)
{
Console.WriteLine("{0}, {1}", key, dict[key]);
}
// 方法 2
foreach (KeyValuePair<int, string> kv in dict)
{
Console.WriteLine("{0}, {1}", kv.Key, kv.Value);
}