写在最前

  • 这篇笔记对应的是唐老狮的C#四部曲──C#基础课程地址

一、复杂数据类型

复杂数据类型概述

  1. 特点
    1. 一般是多个数据(变量)集合在一起构成的数据。
    2. 一般可以自己取名字,可以自定义的数据(变量)。
  2. 类型:
    1. 枚举:整形常量的集合,可以自定义
    2. 数组:任意变量类型的顺序存储的数据
    3. 结构体:任意变量的数据集合,可以自定义

枚举

  1. 概念:被命名的整形常量的集合,一般用于表示状态、类型。
  2. 声明枚举和声明枚举变量是两个概念。
    1. 声明枚举:创建一个自定义的枚举类型。
    2. 声明枚举变量:使用声明的自定义枚举类型,创建一个枚举变量。
  3. 声明枚举
1
2
3
4
5
6
7
8
9
//在namespace语句块中声明(常用)
//也可在class或者struct语句块中声明(不常用)
//不能在函数语句块中声明!!
//枚举名,以E或者E_开头,作为我们的命名规范
enum E_自定义枚举名
{
//自定义枚举项名1,第一个默认为0,下面的依次累加
//自定义枚举项名2,
}
  1. 声明枚举变量
1
2
3
//在函数语句块中
//自定义的枚举类型 变量名 = 默认值;(自定义的枚举类型.枚举项)
//switch语句常和枚举搭配使用
  1. 枚举的类型转换
    1. 枚举和int转换:
      1. 枚举转int:()强转
      2. int转枚举:隐式转换
    2. 枚举和sring互转:
      1. 枚举转string:.Tostring(); 转换的是枚举项名
      2. string转枚举:(枚举类型)Enum.Parse(typeof(枚举类型),“枚举项名”)

数组

  1. 数组是存储一组相同类型数据的集合
  2. 数组分为一维、多维、交错数组
  3. 一维数组就简称为数组

一维数组

  1. 数组的声明
1
2
3
4
5
6
7
8
9
10
//变量类型[] 数组名;只是声明了一个数组,并未初始化
int[] arr1;
//变量类型[] 数组名 = new 变量类型[数组的长度];这种方式初始化后,其中的值都是对应类型的默认值。
int[] arr2 = new int[5];
//变量类型[] 数组名 = new 变量类型[数组的长度]{内容1,内容2,内容3,……};
int[] arr3 = new int[5]{1,2,3,4,5};
//变量类型[] 数组名 = new 变量类型[]{内容1,内容2,内容3,……};
int[] arr4 = new int[]{1,2,3,4,5};
//变量类型[] 数组名 = {内容1,内容2,内容3,……};
int[] arr5 = {1,2,3,4,5};
  1. 数组的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int[] array = {1,2,3,4,5};
//1. 数组的长度
//数组变量名.Length
//2. 获取数组中的元素
//数组变量名[index],数组的index从0开始递增,不能越界0~Length-1
//3. 修改数组中的元素
//数组变量名[index] = 目标值;
//4. 遍历数组
//常用for循环
for(int i = 0; i < array.Length; i++)
{
//code
}
//5. 增加数组的元素
//数组初始化之后是不可以添加新元素的,可以通过新建一个更大数组并拿到之前的数组数据实现
//6. 删除数组中的元素
//数组初始化之后是不可以直接删除元素的,可以通过新建一个更小数组并拿到之前的数组数据实现
//7. 查找数组中的元素
//遍历数组,判断并返回

二维数组

  1. 二维数组的声明
1
2
3
4
5
6
7
8
9
10
//变量类型[,] 二维数组变量名;
int[,] arr1;
//变量类型[,] 二维数组变量名 = new 变量类型[行,列];
int[,] arr2 = new int[3,3];
//变量类型[,] 二维数组变量名 = new 变量类型[行,列]{{0行内容1,0行内容2,0行内容3}{1行内容1,1行内容2,1行内容3}……};
int[,] arr3 = new int[3,3]{{1,2,3}{4,5,6}{7,8,9}};
//变量类型[,] 二维数组变量名 = new 变量类型[,]{{0行内容1,0行内容2,0行内容3}{1行内容1,1行内容2,1行内容3}……};
int[,] arr4 = new int[,]{{1,2,3}{4,5,6}{7,8,9}};
//变量类型[,] 二维数组变量名 = {{0行内容1,0行内容2,0行内容3}{1行内容1,1行内容2,1行内容3}……};
int[,] arr4 = {{1,2,3}{4,5,6}{7,8,9}};
  1. 二维数组的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//1. 二维数组的长度
//数组变量名.GetLength(),括号内为0是行长度,1是列长度
//2. 获取二维数组的元素
//数组变量名[行,列]
//3. 修改二维数组中的元素
//数组变量名[x,y] = 目标值;
//4. 遍历二维数组
//嵌套for循环
//5. 增加二维数组的元素
//数组初始化之后是不可以添加新元素的,可以通过新建一个更大数组并拿到之前的数组数据实现
//6. 删除二维数组中的元素
//数组初始化之后是不可以直接删除元素的,可以通过新建一个更小数组并拿到之前的数组数据实现
//7. 查找数组中的元素
//遍历数组,判断并返回

交错数组

  1. 交错数组的声明
1
2
3
4
5
6
7
8
9
10
//变量类型[][] 交错数组名;
int[][] arr1;
//变量类型[][] 交错数组名 = new 变量类型[index][];第二个括号内不能填写
int[][] arr2 = new int[3][];
//变量类型[][] 交错数组名 = new 变量类型[index][]{{一维数组1},{一维数组2},……};
int[][] arr3 = new int[3][]{new int[]{1,2,3},new int[]{1,2},new int[]
//变量类型[][] 交错数组名 = new 变量类型[][]{{一维数组1},{一维数组2},……};
int[][] arr4 = new int[][]{new int[]{1,2,3},new int[]{1,2},new int[]{1}};
//变量类型[][] 交错数组名 = {{一维数组1},{一维数组2},……};
int[][] arr5 = {new int[]{1,2,3},new int[]{1,2},new int[]{1}}
  1. 交错数组的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//1. 交错数组的长度
//行:数组变量名.GetLength(0),括号内为0是行长度
//列:数组变量名[index].Length,index为具体的行号
//2. 获取交错数组的元素
//数组变量名[行][列]
//3. 修改交错数组中的元素
//数组变量名[x][y] = 目标值;
//4. 遍历交错数组
//嵌套for循环
//5. 增加交错数组的元素
//同样是搬家方式
//6. 删除交错数组中的元素
//同样是搬家方式
//7. 查找数组中的元素
//遍历数组,判断并返回

二、值类型和引用类型

分类

  1. 引用类型
    string, 数组, 类
  2. 值类型
    sbyte, short, int, long, byte, ushort, uint, ulong, float, double, decimal, bool, char, 枚举, 结构体

区别

  1. 表现
    1. 值类型,在相互赋值时,把内容拷贝给了对方,它变我不变。
    2. 引用类型的相互赋值是让两者指向同一个值,它变我也变。
  2. 原因
    1. 值类型存储在栈空间,系统分配,自动回收,小而快。
    2. 引用类型存储在堆空间,手动申请和释放,大而慢。

特殊的引用类型

  1. string 是非常特殊的引用类型,它的特征是它变我不变。
  2. 频繁地改变string重新赋值会产生内存垃圾。

三、函数

函数基础

  1. 基本概念
    1. 定义:具有名称的代码块,可以使用名称来使用代码块
    2. 作用:封装代码、提升代码复用率、抽象行为
  2. 位置
    1. class语句块中
    2. struct语句块中
  3. 语法
1
2
3
4
5
6
7
8
9
10
11
static 返回类型 函数名(参数类型 参数名1,参数类型参数名2,……) 
{
//函数代码逻辑
//有返回值才返回
return 返回值;
}
//static 表示静态
//返回类型可以是14种简单变量类型+复杂变量类型,如果没有返回值,使用 void
//函数名使用帕斯卡命名法,每个单词首字母大写
//参数类型可以是任意类型,可以有0~n个。参数命名使用驼峰命名法
//return 可以不执行后面的代码,直接返回到函数外部
  1. 使用
1
2
3
4
5
6
7
8
9
10
11
12
class Myclass
{
static int Addition(int a , int b)
{
return a+b
}
static void Main(string[] args)
{

Console.WriteLine(Addition(2,5));
}
}

ref & out

  1. 使用
    1. 函数参数的修饰符
    2. 当传入值类型参数在内部修改时,或者引用类型参数在内部重新声明时,让外部值同时变化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//ref 和 out 的使用方法一模一样
class Myclass
{
static void ChangeValue(ref int i)
{
i = 1;
}
static void ChangeArray(ref int[] arr)
{
arr = new int[]{1,2,3};
}
static void Main(string[] args)
{
int a = 3;
ChangeValue(ref a);
Console.WriteLine(a);
int[] arr1 = {0,0,0};
ChangeArray(ref arr1);
Console.WriteLine(arr1[0]);
}
}
  1. 区别
    1. ref 传入的变量必须初始化,out 不用。
    2. out 传入的变量必须在内部赋值,ref 不用。

变长参数和默认值

  1. 变长参数
    1. params 数组类型 数组名;
    2. 后面必须是数组,数组可以是任意类型。
    3. params一定是最后一组参数且最多只有一个,前面可以有任意个参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Myclass
{
static int Addition(params int[] array)
{
int sum = 0;
for(int i = 0 , i < array.Length , i++ )
{
sum += array[i];
}
return sum;
}
static void Main(string[] args)
{
Console.WriteLine(Addition(1,2,3,4,5));
}
}
  1. 默认参数
    1. 作用:当函数没有传入参数,则会使用默认参数。
    2. 如果要混用可选参数,必须写在普通参数后面。
1
2
3
4
5
6
7
8
9
10
11
class Myclass
{
static void Speak(string zhaoHu = "我无话可说")
{
Console.WriteLine(zhaoHu)
}
static void Main(string[] args)
{
Speak();
}
}

函数重载

  1. 基本概念
    1. 同一语句块中(class或struct)函数名相同:⑴参数数量不同⑵参数数量相同,类型或顺序不同。
    2. 作用:⑴减少函数名数量,避免命名空间污染⑵提升程序的可读性。
  2. 实例
    1. 重载和返回值类型无关,只和参数类型、数量、顺序有关。
    2. 调用时,程序会自动根据传入参数类型判断使用哪一个重载。
    3. ref 和 out 相当于改变了变量类型,所以可以用在重载中,但是不能同时修饰,会被认为是一样的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Myclass
{
static int Sum(int a , int b)
{
return a + b;
}
static float Sum(float a , float b)
{
return a + b;
}
static void Main(string[] args)
{
Sum(1,2)
}
}

递归函数

  1. 函数里面调用同名函数
  2. 一个正确的递归函数需要:⑴必须有结束调用的条件⑵条件必须能够达到停止的目的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Myclass
{
static void Print(int a)
{
if (a == 1)
{
return;
}
Console.WriteLine(a)
Print(a-1)
}
//逻辑运算符短路打印,很巧妙的方法
static bool Fun(int a)
{
Console.WriteLine(a)
return a == 100 || Fun(a + 1)
}
static void Main(string[] args)
{
Print(10);
}
}

四、复杂数据类型──结构体

  1. 基本概念
    1. 自定义变量类型
    2. 数据和函数的集合
    3. 可以声明各种变量和方法
    4. 用来表现存在关系的数据集合
  2. 基本语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//写在namespace里面
//自定义结构体名:帕斯卡命名法
namespace MySpace
{
struct 自定义结构体名
{
//第一部分:变量
//结构体中声明的变量不能初始化
//变量类型可以是任何类型,可以是结构体,但是不能是自己的结构体
public int age;
public bool sex;
public string name;
//第二部分:构造函数(可选)
public 自定义结构体名(int age, bool sex, string name)
{
//this关键字,代表自己
this.age = age;
this sex = sex;
this name = name;
}
//第三部分:函数
//表现数据结构的行为
public void Speak()
{
Console.WriteLine("我的名字是{0}",name)
}
}
}
  1. 访问修饰符
    1. Public 公共的,可以被外部访问
    2. private 私有的,只能内部使用
    3. 默认是私有的
  2. 构造函数
    1. 没有返回值
    2. 函数名必须和结构体名相同
    3. 必须要有参数
    4. 如果声明了构造函数,必须对其中堆所有变量数据初始化

五、排序初探

冒泡排序

  1. 两两相邻,不停比较,不停交换,比较n轮
  2. 代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//优化,用标识来判断是否已经是排序最终形态
bool isSort = false;
int[] arr = new int[]{5,2,6,4,8,1};
int n = arr.Length;
//比较n轮
for(int i = 0, i < n , i++)
{
isSort = false;
//两两相邻,不停比较
for(int j = 0, j < n-1-i, i++)
{
isSort = true;
//交换
if(arr[j] > arr[j+1])
{
int tmp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = tmp;
}
}
if(!isSort)
{
break;
}
}

选择排序

  1. 新建中间商,依次比较,找出极值,放入目标位置,比较n轮
  2. 代码实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

int[] arr = new int[]{5,2,6,4,8,1};
int n = arr.Length;
for(int i = 0, i < n ,i++)
{
//声明中间商,记录索引,默认第一个为极值
int index = 0;
//依次比较
for(int j = 1, j < n - i, j++)
{
//找出极值
if(arr[index] < arr[j])
{
index = j;
}
}
if(index != n-1-i)
{
int tmp = arr[index];
arr[index] = arr[n-1-i];
arr[n-1-i] = tmp;
}
}