可供参考的别人的学习笔记:https://blog.csdn.net/Jacey_cai/article/details/122848272
文章目录
一、课程简介、C#语言简介、开发环境准备
- 怎样编写程序和程序语言的选择?
编辑->编译->调试->发布 - 编程的学习路径?
纵向:语言->类库->框架
横向:命令行程序,桌面程序,设备(平板/手机)程序,Web(网站/服务)程序,游戏… - 开发环境与学习资料
下载Visual Studio
学习资料为:
- 下载离线MSDN文档
- C#语言定义文档(Language Specification)
- 推荐书籍:C# 5.0 In A Nutshell
- 学习原则
- 从感观到原理
- 从使用别人的到创建自己的
- 必需要亲自动手
- 必需学以致用、紧跟实际工作
- 追求实用,不搞学院派
二、初识各类应用程序
1.console :控制台应用(.NET Framework)
2.WPF:WPF应用程序,是新的Windows Forms
3.Windows Forms :Windows窗体应用(无后缀)
4.ASP.NET Web Forms(Old):ASP.NET Web应用程序(.NET Framework)->Web Forms
5.ASP.NET MVC(Model-View-Contoller),ASP.NET Web应用程序(.NET Framework)->MVC,是新的ASP.NET Web Forms,它可以让程序员把不同种类的代码分开放到不同文件里面
6.WCF:WCF服务应用程序,纯网络服务
7.Wiindows Store Application:给平板电脑写,已凉
8.Windows Phone Application:已凉
9.Cloud(Windows Azure)
10.WF(Workflow Foundation):工作流
三、初识类与名称空间
- 剖析Hello,World程序
- 初识类(class)与名称空间(namespace)
- 类库的引用
- DLL引用(黑盒引用)
- 项目引用(白盒引用)
- 依赖关系
- 排除错误
剖析Hello,World程序
类构成程序的主体
名称空间以树型结构组织类(和其他类型)
- 例如Button和Path类
在VS里,类是青色
那么应该如何查看类属于哪个命名空间呢?
- 查看help View索引
- 智能标记,可以用快捷键Alt+Enter+.
防止冲突可以使用权限命名
类库的引用
- 类库引用是使用名称空间的物理基础
- 不同技术类型的项目会默认引用不同的类库
- 在VS里面对象浏览器,快捷键是Ctrl+Alt+J
- DLL引用(黑盒引用,无源代码)
- 如何引用别人的dll,以及如何添加类库,使用对象浏览器和help viewer
- NuGet简介,可以很好的引用用到的类库
- 项目引用(白盒引用,有源代码)
一个项目可以被多个solution使用,叫做项目重用
断点+debug+F11
创建类库并引用类库。
依赖关系
- 类(或对象)之间的耦合关系
- 优秀的程序员追求“高聚合,低耦合”
- 教学程序往往会违反这个原则
- UML(通用建模语言)类图
四、类、对象、类成员简介
- 类(class)是现实世界事物的模型
- 类与对象的关系
- 什么时候叫“对象”,什么时候叫“”实例
- 引用变量与实例的关系
- 类的三大成员
- 属性(Property)
- 方法(Method)
- 事件(Event)
- 类的静态成员与实例成员
- 关于“绑定”(Binding)
类(class)是现实世界事物的模型
- 类是对现实世界事物进行抽象所得到的结果
- 事物包括“物质”(实体)与“运动”(逻辑)
- 建模是一个去伪存真、由表及里的过程
类与对象的关系
孩子是引用变量,气球是实例,多个孩子通过一根绳子牵着气球用到ref
类的三大成员
以属性为侧重点的类,以方法为侧重点的类,以事件为侧重点的类,默写。
//重在事件的代码演示
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace WpfApp4
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DispatcherTimer timer = new DispatcherTimer();//实例化
timer.Interval = TimeSpan.FromSeconds(1);//给属性值,1s钟生成一个timespan
timer.Tick += Timer_Tick;//挂接事件处理器
timer.Start();//方法调用
}
private void Timer_Tick(object sender, EventArgs e)//事件处理器
{
this.timeTextBox.Text = DateTime.Now.ToString();
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
}
}
}
https://www.bilibili.com/video/BV13b411b7Ht/?p=4&spm_id_from=pageDriver&vd_source=04d791790ccb453dc9e7113022339213
要慎用事件机制
鼠标移动到类上,按下F1键,就会自动打开MSDN文档,显示当前类的信息
如果想看这个类在什么分支上,可以点一下内容同步
输入cw+Tab+Tab就可以打出Console.WriteLine,快捷键。
静态成员与实例成员
-
静态(Static)成员在语义上表示他是“类的成员”
-
实例(非静态)成员在语义表示他是“对象的成员”
在MSDN里面如果发现某个属性下面加了一的大写的S,就说明他是一个静态的属性。
-
绑定(Binding)指的是编译器如果把一个成员与类或对象关联起来
比如在编写console类和Form类的时候,进行编译时,编译器知道把某个成员是跟这个类关联起来,还是跟这个类的对象关联起来,叫做Binding。早绑定指的是编译器在编译这个类的时候就已经知道这个成员是隶属于这个类还是隶属于这个类的对象,晚绑定是编译器不管这个事情,当程序运行起来之后,才由程序员决定这个方法属于这个类还是这个类的对象,有晚绑定功能的语言一般叫做动态语言,比如JavaScript- 不可小觑的"."操作符——成员访问
五、语言基本元素概览、初识类型、变量和方法、算法简介
构成C#语言的基本元素
- 关键字(Keyword)
- 操作符(Operator)
- 标识符(Identifier)
- 标点符号
- 文本
- 注释与空白
5.1C#语言基本元素
标记,就是对于编译器来说有意义的记号,也就是编译器能识别出来。
自动取消空格
快捷键Ctrl+K、D
5.2 类型、变量和方法
var能推断赋值的类型,一般都是使用明确的
方法的声明和调用
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
Calculator c = new Calculator();
int x = c.Add(2, 3);
Console.WriteLine(x);
string str = c.Today();
Console.WriteLine(str);
c.PrintSum(4, 6);
}
}
class Calculator
{
public int Add(int a, int b)//有数据输入输出
{
int result = a + b;
return result;
}
public string Today()//无数据输入有输出
{
int day = DateTime.Now.Day;
return day.ToString();
}
public void PrintSum(int a, int b)//有数据输入无输出
{
int result = a + b;
Console.WriteLine(result);
}
}
}
5.3算法简介
- 循环初体验
- 递归初体验
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
Calculator c = new Calculator();
c.PrintXTo1(10);
c.PrintXTo1d(3);
}
}
class Calculator
{
public void PrintXTo1(int x)//循环
{
for (int i = x; i > 0; i--)
{
Console.WriteLine(i);
}
}
public void PrintXTo1d(int x)//调用递归
{
if (x == 1)
{
Console.WriteLine(x);
}
else
{
Console.WriteLine(x);
PrintXTo1(x - 1);
}
}
}
}
- 计算1到100的和
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
Calculator c = new Calculator();
int result = c.SumFrom1ToX(100);
Console.WriteLine(result);
}
}
class Calculator
{
//public int SumFrom1ToX(int x)//循环
//{
// int result = 0;
// for (int i = 1; i <= x; i++)
// {
// result = result + i;
// }
// return result;
//}
//public int SumFrom1ToX(int x)//递归
//{
// if (x == 1)
// {
// return 1;
// }
// else
// {
// int result = x + SumFrom1ToX(x - 1);
// return result;
// }
//}
public int SumFrom1ToX(int x)
{
int result = (1 + x) * x / 2;
return result;
}
}
}
批量注释快捷键Ctrl+K+C,接触注释是Ctrl+K+U
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp5
{
class Program
{
public const int MAX_VALUE = 70;
static void Main(string[] args)
{
Calculator nu = new Calculator();
int levels = 0;
Console.Write($"输入汉诺塔层数(1~{MAX_VALUE}):");
levels = int.Parse(Console.ReadLine());
if (levels > 0 && levels < MAX_VALUE)
{
nu.Hanoi(levels, 'a', 'b', 'c');
nu.ReportNum();
Console.ReadKey();
}
else
{
Console.WriteLine("输入范围错误");
Console.ReadKey();
}
}
}
class Calculator
{
public static int num = 0;
public void Hanoi(int n, char a, char b, char c)
{
if (n == 1)
{
Console.WriteLine($"{a}--->{c}");
num++;
return;
}
else
{
Hanoi(n - 1, 'a', 'c', 'b');
Hanoi(1, 'a', 'b', 'c');
Hanoi(n - 1, 'b', 'a', 'c');
}
}
public void ReportNum()
{
Console.WriteLine($"一共移动了{num}次");
}
}
}
六、详解类型变量与对象-上
- 什么是类型(Type)
- 数据结构就是类型的延申
- 类型在C#语言中的作用
- C#语言的类型系统
- 变量、对象与内存
6.1什么是类型
冯诺依曼系统(计算机组成原理课程,存储器对应内存 ,硬盘是扩展存储器,固态SSD硬盘更快)
有些编程语言是强类型编程语言,有些是弱类型编程语言
C#语言是强类型语言
int x;
x=100L;//会报错,保护类型正确
x=100;
if(x=200)//报错,C语言中不报错
//vs创建C语言项目:https://blog.csdn.net/weixin_58396509/article/details/125949295
C#语言提供了一种机制,动态类型,用来模仿弱类型
dynamic myVar = 100;
Console.WriteLine(myVar);
myVar = "Mr.Okey!";
Console.WriteLine(myVar);
使用var定义变量时有以下四个特点:
- 必须在定义时初始化。也就是必须是var s = “abcd”形式,而不能是如下形式:
var s;
s = “abcd”; - 一但初始化完成,就不能再给变量赋与初始化值类型不同的值了。
- var要求是局部变量。
- 使用var定义变量和object不同,它在效率上和使用强类型方式定义变量完全一样。
动态调试对程序员:反射技能
查看这个类里都有什么方法
程序在不运行时放在硬盘中,运行时装载到内存中,程序静态指的是没有运行的时候,动态是运行起来的时候。程序一旦装载到内存开始运行之后,他把内存分为两个区域来对待,一个区域叫做栈(Stack),一个区域叫做堆。
栈的作用是给方法调用来用的,函数调用用到的是栈;栈比较小,一般只有1~2M,如果装的东西特别多会爆掉,一是算法没写好,函数调用太多了,二是程序写的有错误,不小心在栈上分配了太多的内存,有一个著名的网站,叫做https://stackoverflow.com/,stackoverflow作为全球最大的技术问答网站,里面解答了很多计算机编程的问题。
堆是用来存储对象的,实例就放在堆里面;堆比较大,能达到1个G,所以能放很多对象,堆很大不会爆掉,但是在堆里面分配对象,分配了很多,但是在最后忘了回收这些对象,会造成内存的浪费,也就是内存泄漏,C语言中泄露就泄露了,C#中有垃圾收集器,所以没有手动释放内存一说。
递不归,栈就爆掉了
C#里面也是有指针的,但是不能随便用
也可以这样写
还需要在:项目->属性->生成->允许不安全代码
一个程序从硬盘加载到内存就形成了一个进程
//WPF程序,演示内存堆的占用和释放
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApp5
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
List<Window> winList;
private void Button1_Click(object sender, RoutedEventArgs e)
{
winList = new List<Window>();
for (int i = 0; i < 15000; i++)
{
Window w = new Window();//因为Window的实例占的内存比较多
winList.Add(w);
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
}
private void Button2_Click(object sender, RoutedEventArgs e)
{
winList.Clear();
}
}
}
七、详解类型变量与对象-下
7.1C#语言类型系统
光标移动到类名,按F12打开类定义。值类型是存储在内存的栈里面,因为值类型的大小确定,切语句运行完就释放,引用类型是存在堆里面。
第一组关键字:object和string这两个类太常用了,所以C#吸收为关键字了;class、Interface、delegate这三个关键字是用来定义自己的数据类型的。
第二组关键字代表值类型,蓝色关键字是真正的数据类型。
第三组关键字中var和dynamic是用来声明变量的。
基本数据类型也叫内建数据类型。int、short等都是结构体定义。
7.2变量、对象与内存
什么是变量
局部变量指在方法体和函数体中声明的变量。
字段是属性的雏形,如果裸露字段,那么就有可能会给成员变量赋一个不合理的值,比如age=-1。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ConsoleApp7
{
class Program
{
static void Main(string[] args)
{
Student.Amount;
Student stu = new Student();//实例变量
int[] array = new int[100];//声明一个长度为100的整型数组,分出400个字节
}
}
class Student
{
public static int Amount;//静态变量是隶属于这个类的,不是隶属于类的实例的
public int Age;
public string Name;
public double Add(double a, double b)//a,b就叫做值参数变量,也叫做形参
//public double Add(ref double a, double b),如果在参数前面加一个ref就是引用参数变量,加一个out为输出参数变量
{
double result = a + b;//result为局部变量
return result;
}
}
}
值变量的类型
值类型没有实例,所谓的实例和变量合二为一,意思是:
int x = new int();与int x;的效果相同
引用类型的变量与实例
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ConsoleApp7
{
class Program
{
static void Main(string[] args)
{
Student Stu;//引用类型变量所存放的是引用类型实例在堆中的地址
Stu = new Student();//在堆里分配4+2=6个字节内存
}
}
class Student
{
uint ID;
ushort Score;
}
}
局部变量在栈(stack)上分配内存
实例是分配在堆上的,类里面的成员变量,也叫字段,他不是局部变量,而是随着类的实例化而在堆上分配内存,再把分配的地址交还给引用实例变量
变量的默认值
在C#中,局部变量如果没有赋值的话,是不能通过编译的,但是其他类里面的成员变量,默认值0
常量
const修饰符,常量,初始化器也是不能省略的,const int x = 100;
装箱和拆箱
装箱和拆箱本质上就是值类型和引用类型之间的转换
装箱:当我们的计算机发现,这个object类型变量obj所要引用的这个值,他不是堆上的实例,而是一个栈上的值类型的时候,他会先把值类型的值copy一下,在对上面找一块可以存储的空间,把这个空间变成一个对象,再把这个对应的起始是地址转化为2进制,存储到obj这个变量所对应的内存空间里面来,从而构成obj这个引用变量对堆上实例的引用,而这个实例封装着我们的一个整数,就叫做装箱。装箱会损失程序的性能。简单说就是:装箱是把栈上的值类型的值封装成一个object类型的实例放在堆上;拆箱就是把堆上object实例的值按照要求拆成目标数据类型,存储到栈上去;装箱拆箱会损失程序的性能。
namespace ConsoleApp7
{
class Program
{
static void Main(string[] args)
{
int x = 100;
object obj;
obj= x; //叫做装箱,obj叫做引用类型,但内存中分配四个字节的存储单元
int y = (int)obj;//叫做拆箱
}
}
拆箱:
八、方法的定义、调用与调试-上
两本推荐书籍(有难度)
- 方法的由来
- 方法的定义与调用
- 构造器(一种特殊的方法)
- 方法的重载
- 如何对方法进行debug
- 方法的调用与栈
8.1方法的由来
Visual C+±>Empty Project
在文件夹shift+右击能快速点出PowerShell命令行界面
当一个函数以类的成员的身份出现的时候就成为方法,这就是为什么方法有一个别名叫做成员函数。
C#中类的定义和声明是放在一起的,C++是分开的。
8.2方法的定义与调用
方法的定义
方法的定义参见C#语言规范。
- return-type:返回值
- member-name:方法名
- formal-parameter-list-opt :形式参数列表
- attributes-opt :特性/特征,比较高级的内容
- method-modifiers:方法修饰符,或修饰符的组合
- partial-opt:代表这个方法可以分为多个部分去写
- type-parameter-list-opt:类型参数,当我们的方法是一个泛型方法时会用到
- type-parameter-constraints-clauses-opt:约束列表,用来约束类型参数的
方法命名采用帕斯卡命名法,需要所有首字母大写;变量命名一般采用驼峰法,也就是首字母小写,其余单词首字母大写。
静态方法是程序运行的时候会直接加载到内存里面,全部static的话会影响程序性能。
方法的调用
在方法名后面加一对圆括号(不能省略),在括号里面写入必要的实际参数(argument),这对圆括号在C#里面叫做方法调用操作符。声明时参数是形参(parameter),调用时参数是实际参数(argument)
九、方法的定义、调用与调试-下
8.3构造器(一种特殊的方法)
构造函数译为构造器,成员函数译为方法,本质都是函数。
小技巧:输入ctor,再按两下tab即可自动生成构造函数的代码片段
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ConsoleApp7
{
class Program
{
static void Main(string[] args)
{
Student stu = new Student(2,"Mr.okey");//这个括号实际上就是在调用构造器
Console.WriteLine(stu.ID);//值为0,说明已经调用了构造器
Console.WriteLine(stu.Name);
Console.WriteLine("**************");
Student stu2 = new Student();
Console.WriteLine(stu2.ID);
Console.WriteLine(stu2.Name);
}
}
class Student//当你声明一个类后,你又没有为他准备构造器的话,编译器会为他准备一个默认的构造器
{
public Student()//构造器,没有返回值
{
this.ID = 1;
this.Name = "No name";
}
public Student(int initId, string initName)//带参数构造器
{
this.ID = initId;
this.Name = initName;
}
public int ID;//创建两个公开字段
public string Name;
}
}
下面是构造器的内存原理:
注意栈的内存分配的时候是由比较高的地址向比较低的地址分配,直至溢出,先进后出。一个整形默认的话都是0,字符串类型都是0的话是Null值。
8.4方法的重载(Overload)
打一个左半括号,可以上下选择不同的重载类型
public int Add(int a, int b)
{
return a + b;
}
public double Add(double a, double b)
{
return a + b;
}public int Add(int a, int b, int c)
{
return a + b + c;
}
public int Add<T>(int a, int b)//<T>是泛型,叫做类型参数,未来会参与到方法里面来,与C++的模板相似
{
T t;
return a + b;
}
public int Add(ref int a,int b)//ref代表传递引用参数,与C++中&类似,out为输出参数
{
return a + b;
}
public int Add(out int a,int b)//out为输出参数,参数的种类不同,可以重载
{
return a + b;
}
8.5如何对方法进行debug
第二条观察方法调用的时的call Stack,可以观察方法的调用逻辑
F11为逐语句,F10逐过程,可以快速的大范围的定位,如果调试已知某些方法正确,那么为了节省时间,可以用F10跳过正确部分。F10和F11交替使用进行调试。Shift+F11跳出,可以快速返回当前方法的调用处,这样比在call stack中点击,再在调用这个方法的位置处设置断点,要快很多。
8.6方法的调用与栈*
- 方法调用时栈内存的分配
- 对stack frame的分析(一个方法的stack frame指的是一个方法在被调用时候,他在栈内存当中的布局),调用者为caller,被调用者为callee
如图,在Main中会调用这个方法,需要传进两个参数到栈,那么这两个参数在栈中归谁管呢?在和方面C#和C++保持一致,均为归主调者管,也就是归caller管,也就是Main管,在压入栈时先压左边的,再压右边的。谁调用谁负责把参数压入到栈里边。
- 对stack frame的分析(一个方法的stack frame指的是一个方法在被调用时候,他在栈内存当中的布局),调用者为caller,被调用者为callee
详细的方法调用时栈的存储情况见视频末尾:https://www.bilibili.com/video/BV13b411b7Ht/?p=9&spm_id_from=pageDriver&vd_source=04d791790ccb453dc9e7113022339213
返回值一般存放在cpu的寄存器里面,可以理解为内嵌在cpu当中非常快速的内存,当寄存器存不下这个值时,也会在栈上开辟空间,返回参数后,相应的stack frame就被清空了,传递进来的参数也就被清空了。
十、十一、十二、操作符详解
- 操作符概览
- 操作符的本质
- 操作符的优先级和同级操作符的运算顺序
- 各类操作符的示例
操作符、表达式和语句都是为方法服务的,操作符和操作数组成表达式,表达式后面加分号组成语句,语句就是用来组成方法体的,来组成方法的算法和逻辑的。
10.1操作符概览
从上往下优先级依次降低,同级运算符优先级按照数学中从左到右的顺序,但是最后一行赋值是从右到左运算。
10.2操作符的本质
为了说明C#中操作符就是函数的简记法,此处与C++类似(C++中叫做符号的重载)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp8
{
class Program
{
static void Main(string[] args)
{
Person person1 = new Person();
Person person2 = new Person();
person1.Name = "Deer";
person2.Name = "Deer's wife";
List<Person> nation = person1 + person2;
foreach (var p in nation)
{
Console.WriteLine(p.Name);
}
}
}
class Person
{
public string Name;
public static List<Person> operator+(Person p1, Person p2)
{
List<Person> people = new List<Person>();
people.Add(p1);
people.Add(p2);
for (int i = 0; i < 11; i++)
{
Person child = new Person();
child.Name = p1.Name + "&" + p2.Name + "'s child";
people.Add(child);
}
return people;
}
}
}
10.3操作符的优先级和同级操作符的运算顺序
10.4各类操作符的示例
基本操作符
基本表达式:最基础的,不能够在进行拆分的表达式,基本运算符就是用来参与基本表达式的运算符。
表达式就是表达一定算数意图的式子。
- x.y:成员访问运算符,功能为:
- 以访问外层命名空间中的子集名称空间
- 可以访问名称空间中的类型
- 可以访问类型中的静态成员
- 访问对象的成员
System.IO.File.Create("D:\\HelloWorld.txt");//1,2,3
Form myForm = new Form();
myForm.Text = "Hello,World!";//4
myForm.ShowDialog();//4
- f(x):方法调用操作符,就是那一对圆括号:
委托可以把方法作为参数在其它类中使用,类似于函数指针;
抽象类是对类的抽象,泛型是对参数的抽象,委托是对方法的抽象
- a[x]:元素访问运算符,[ ]访问集合中的元素,里面是索引,索引不一定都是整数,常用访问数组和字典的元素
数组索引:
namespace ConsoleApp9
{
class Program
{
static void Main(string[] args)
{
//声明数组的三种方法:
int[] myIntArray1 = new int[10];
int[] myIntArray2 = new int[] { 1, 2, 3, 4, 5 };//这对花括号叫做初始化器,不叫构造器
int[] myIntArray3 = new int[5] { 1, 2, 3, 4, 5 };
//访问数组中的元素
Console.WriteLine(myIntArray3[0]);
Console.WriteLine(myIntArray3[myIntArray3.Length-1]);
}
}
}
字典索引:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp9
{
class Program
{
static void Main(string[] args)
{
//泛型类不是一个完整的类,他需要和其他的类型组合在一起
//string作为索引的类型,Student作为值的类型
Dictionary<string, Student> stuDic = new Dictionary<string, Student>();
for (int i = 0; i < 100; i++)
{
Student stu = new Student();
stu.Name = "s_" + i.ToString();
stu.Score = 100+i;
stuDic.Add(stu.Name,stu);
}
Student number6 = stuDic["s_6"];//字典索引
Console.WriteLine(number6.Score);
}
}
class Student
{
public string Name;
public int Score;
}
}
- x++,x–:自增、自减运算符:
先赋值,后自加
y=x++;
//相当于:
y=x;
x=x+1;
- typeof操作符:功能是帮助我们查看一个类型的内部结构,术语叫做Metadata,源数据(包含着这个类型的基本信息:包括名字,属于哪个名称空间,父类是谁,包含多少个方法、属性、事件)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp10
{
class Program
{
static void Main(string[] args)
{
Type t = typeof(int);
Console.WriteLine(t.Namespace);
Console.WriteLine(t.FullName);
Console.WriteLine(t.Name);
int c = t.GetMethods().Length;
foreach (var mi in t.GetMethods())
{
Console.WriteLine(mi.Name);//打印出类中所有方法的名字
}
Console.WriteLine(c);
}
}
}
- default操作符:作用是帮助我们获取一个类型的默认值,着重三种数据类型:结构体类型(值类型)、引用类型、枚举类型(值类型)。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ConsoleApp10
{
class Program
{
static void Main(string[] args)
{
int x = default(int);//结构体类型,内存块都写0,值为0
Console.WriteLine(x);
Form myForm = default(Form);//引用类型,内存块都写0,就是空值
Console.WriteLine(myForm == null);
Level level = default(Level);
Console.WriteLine(level);//枚举类型,注意枚举类型中若无0对应时,默认输出0,而不是枚举的
}
enum Level//枚举类型在声明时会和整数值对应起来,Low=0
//在设定枚举类型时最好给一个o值,因为默认输出0对应
{
Low,//也可以显式赋值:Low=1
Mid,
High
}
}
}
- new操作符:帮助我们在内存当中创建一个类型的实例,并且立刻调用这个实例的实例构造器。
(var关键字是帮我们声明隐式类型的变量,帮我们推断出隐式类型的变量类型,声明完后可以更改数值,但是不能再更改数据类型。),如果在new操作符的左边有赋值符号的话,那么new操作符会把自己拿到的实例的内存地址通过赋值操作符负责交给引用这个实例的变量,这是new操作符的主要功能。他的附加功能为,除了能够调用实例的构造器之外,还能调用这个实例的初始化器。
static void Main(string[] args)
{
/Form myForm = new Form();//()为调用实例构造器
myForm.Text = "Hello";
myForm.ShowDialog();
//下面{ }内为调用初始化器
Form myForm = new Form() { Text = "Hello",FormBorderStyle = FormBorderStyle.SizableToolWindow };//取消最大化最小化按钮
myForm.ShowDialog();
}
有时候,某些实例只使用一次,也可以这么写:
new Form() { Text = "Hello" }.ShowDialog();
下面有一个问题:是不是创建实例都要用new运算符呢?
不是的,比如:
int x = 100;//int是一个结构体
string name = "Tim";//string是一个类,name是一个实例,这里创建实例没有用new
string name = new string("Tim");//与上面语句作用一样,一般不这么用
string这个类型用起来和int是一样的格式,这是因为微软在创建C#语言的时候为了同意这些基本类型的使用体验,故意把这个new操作符给隐藏起来了,在用string时就像int一样用就可以了,就简化了,这叫做语法糖衣,同样的还有创建数组的时候:
int[] myArray = new int[10];//创建int类型的实例的时候,这里不用在后面加(),因为它的构造器比较特殊
//也可以用这样的语法:
int[] myArray = {1, 2, 3, 4};//这里我们也没有用到new操作符,这也是一个语法糖衣
另外一个new操作符的特殊且有意思的用法:为匿名类型创建对象
namespace ConsoleApp11
{
class Program
{
static void Main(string[] args)
{
Form myForm = new Form() { Text = "Hello"};//new操作符后面跟一个数据类型,这是针对非匿名类型这么用
var person = new { Name = "Mr.Okey", Age = 34 };//记住这种new操作符和ver组合的使用方法,是为匿名类型创建对象,并且用隐式类型变量来引用实例。
Console.WriteLine(person.Name);
Console.WriteLine(person.Age);
Console.WriteLine(person.GetType().Name);
}
}
}
编译结果为:
其实这是一种合理的“偷懒”办法,而且比较鼓励使用var来声明变量。
记住这种new操作符和ver组合的使用方法,是为匿名类型创建对象,并且用隐式类型变量来引用实例。
在使用new运算符时一定要小心,一旦在一个类里面调用了类运算符,你正在编写的这个类型和你创建实例的这个类型之间就构成了十分紧密的耦合。(new操作符功能强大,但是不能乱用,在编写大型程序的时候,为了避免new操作符造成的紧耦合,我们有一种依赖注入的设计模式去使用)。
上述为new作为操作符时的用法。
- 下面介绍new关键字作为其他用途(非操作符)时的用法:
用作修饰符,子类对父类的方法进行隐藏
namespace ConsoleApp11
{
class Program
{
static void Main(string[] args)
{
Student stu = new Student();
stu.Report();
CsStudent csStu = new CsStudent();
csStu.Report();
}
}
class Student
{
public void Report()
{
Console.WriteLine("I'm a student");
}
}
class CsStudent : Student//继承自父类Student
{
new public void Report()//这里new为子类对父类的方法进行隐藏,这里new不叫操作符,叫做修饰符
{
Console.WriteLine("I'm CS student");
}
}
}
- cheaked和unchecked是一对:用来检查一个值在内存中是不是有溢出,C#是强类型语言,任何一个变量他在内存当中都是有数据类型的,而数据类型的一个重要作用就是标识这种数据类型的实例在内存当中能够占多大的空间,那么一个值在内存中所占空间的大小决定了这个值能够表达的范围,一旦超出就发生溢出,cheaked这个关键字就是用来检查这个值有没有溢出,而unckeaked就是高速编译器你不用去检查这个值有没有溢出。C#语言默认采用unchecked模式
namespace ConsoleApp12
{
class Program
{
static void Main(string[] args)
{
uint x = uint.MaxValue;
Console.WriteLine(x);
string binStr = Convert.ToString(x, 2);
Console.WriteLine(binStr);
uint y = x + 1;//会变成0
try
{
uint y2 = checked(x + 1);//程序会产生溢出异常
Console.WriteLine(y);
}
catch (OverflowException ex)//用来抓住产生的这个异常
{
Console.WriteLine("There's overflow");
}
}
}
}
- checked关键字除了操作符形式的用法外,还有上下文用法:
namespace ConsoleApp12
{
class Program
{
static void Main(string[] args)
{
uint x = uint.MaxValue;
Console.WriteLine(x);
string binStr = Convert.ToString(x, 2);
Console.WriteLine(binStr);
checked//checked后面会有一个语句块,在这个语句块的上下文范围之内,所有溢出都是会被检测到的,unchecked就是不检查
{
try
{
uint y = x + 1;
Console.WriteLine(y);
}
catch (OverflowException ex)
{
Console.WriteLine("There's overflow");
}
}
}
}
}
- delegate关键字:它最广泛的用途不是当操作符用,而是用来声明一种叫做委托的数据类型,现在研究把delegate当作操作符使用。现在delegate当作操作符用的地方已经很少了,因为C#出现了一种新的技术,叫做λ表达式,此技术就是用来取代delegate当作操作符的应用场景。
- sizeof操作符:用来获取一个对象在内存中所占字节数这个尺寸,注意:
- 只能用来获取基本数据类型他们的实例在内存当中所占的字节数,基本数据类型就是C#关键字里面那些数据类型(见下图中蓝色关键字,除了object和string,因为sizeof只能用来获取结构体数据类型的实例在内存当中的字节数)
- 在非默认的情况下,我们可以使用sizeof去获取自定义的结构体类型的实例在内存中占的字节数
,但是需要把他放在不安全的上下文中。
namespace ConsoleApp12
{
class Program
{
static void Main(string[] args)
{
int x = sizeof(decimal);
Console.WriteLine(x);
unsafe//还需要在项目-项目属性-build-允许unsafe
{
int y = sizeof(Student);
Console.WriteLine(y);//y=16,因为内存对齐
}
}
}
struct Student
{
int Id;//4
long Score;//8
}
}
- ->操作符:C#是有真正的指针的,因为直接操作内存,所以要放在不安全的上下文中,在C#里有严格的规定,像指针操作、取地址操作、以及用指针去访问成员操作,之能用来去操作结构体类型,不能用来去操作引用类型。
namespace ConsoleApp12
{
class Program
{
static void Main(string[] args)
{
unsafe
{
Student stu;
stu.ID = 1;
stu.Score = 99;
Student* pstu = &stu;
pstu->Score = 100;
Console.WriteLine(stu.Score);//凡是通过stu.去访问的都是直接访问,而用pstu->去访问的都是通过指针的间接访问
}
}
}
struct Student
{
public int ID;//4
public long Score;//8
}
}
一元操作符
只要有一个操作数跟在操作符后面即可,也叫一目操作符
- ** *符号和取地址符号&**:
Student stu;
stu.ID = 1;
stu.Score = 99;
Student* pstu = &stu;//
pstu->Score = 100;
(*pstu).Score = 101;//
Console.WriteLine(stu.Score);
- +、-、!、~正负非反操作符:
取反操作符~:对一个数在二进制级别上进行按位取反
namespace ConsoleApp12
{
class Program
{
static void Main(string[] args)
{
int x = 100;
int y = -x;//注意正负界限不对称
Console.WriteLine(y);
string xStr = Convert.ToString(x, 2).PadLeft(32, '0');
Console.WriteLine(xStr);
int x2 = 12345678;
int y2 = ~x2;
string x2Str = Convert.ToString(x2, 2).PadLeft(32, '0');//32位显示,不满则用0左侧补齐
string y2Str = Convert.ToString(y2, 2).PadLeft(32, '0');
Console.WriteLine(x2Str);
Console.WriteLine(y2Str);
}
}
}
namespace ConsoleApp12
{
class Program
{
static void Main(string[] args)
{
Student stu = new Student(null);
Console.WriteLine(stu.Name);
}
}
class Student
{
public Student(string initName)
{
if (!string.IsNullOrEmpty(initName))//如果非空,!操作符在现实的应用
{
this.Name = initName;
}
else
{
throw new ArgumentException("initName cannot be null or empty.");//否则抛出异常
}
}
public string Name;
}
}
- ++x,–x操作符:
class Program
{
static void Main(string[] args)
{
int x = 100;
int y = ++x;
Console.WriteLine(x);//101
Console.WriteLine(y);//101
}
}
- await操作符:需要学到异步操作,才能看懂。
- (T)x强制类型转换操作符:T为某种数据类型
什么是类型转换:
class Program
{
static void Main(string[] args)
{
string str1 = Console.ReadLine();
string str2 = Console.ReadLine();
int x = Convert.ToInt32(str1);
int y = Convert.ToInt32(str2);
Console.WriteLine(str1 + str2);
Console.WriteLine(x+y);
}
}
隐式类型转换:
C#定义文档:
不丢失精度的转换:
int x = int.MaxValue;
long y = x;
子类向父类的转换:
namespace ConsoleApp13
{
class Program
{
static void Main(string[] args)
{
Teacher t = new Teacher();
//C#语言规定,当你试图拿一个引用变量去访问它所引用的实例的成员的时候,
//这个时候只能访问到这个变量的类型,它所具有的成员。是你这个变量的类
//型,而不是它所引用的实例的类型
Human h = t;
h.Think();
Animal a = h;
a.Eat();
}
}
class Animal
{
public void Eat()
{
Console.WriteLine("Eating...");
}
}
class Human : Animal
{
public void Think()
{
Console.WriteLine("Who I am?");
}
}
class Teacher:Human
{
public void Teach()
{
Console.WriteLine("I teach programming");
}
}
}
显示类型转换:
class Program
{
static void Main(string[] args)
{
Console.WriteLine(ushort.MaxValue);
uint x = 65536;
ushort y = (ushort)x;//32位的数据塞到16位的空间里面,只能舍弃高16位,y=0
Console.WriteLine(y);
}
}
string类到数值类的转换需要用工具–convert类实现
int x = Convert.ToInt32("32");
Console.WriteLine(x);
数值类到string类的转换有两种方法:
-
用convert类的静态方法转换
-
调用数值类型数据的ToString方法(一般应用在想让一个文本框显示一个数值的时候,WPF)
所有的数据类型都属于object类型的子类,object类型就带有四种方法,所以所有的数据类型都有这四种方法:
实际上,在目标数据类型存在着Parse方法,但是它有个缺陷,就是只能解析格式正确的字符串数据类型
为了让程序看起来更加友好,目标数据类型往往准备了另外一个方法TryParse方法
它通过输出类型参数来输出转换结果,等学到输出类型参数时再深入。
操作符本质是方法的简记法,对于类型转换操作符也是这样,其背后的秘密:(与C++中的操作符重载一样)
namespace ConsoleApp13
{
class Program
{
static void Main(string[] args)
{
Stone stone = new Stone();
stone.Age = 5000;
Monkey wukongSun = (Monkey)stone;
//隐式类型转换写法:
//Monkey wukongSun = stone;
Console.WriteLine(wukongSun.Age);
}
}
class Stone
{
public int Age;
//显示类型转换用explicit,隐式类型转换用implicit
public static explicit operator Monkey(Stone stone)
{
Monkey m = new Monkey();
m.Age = stone.Age / 500;
return m;
}
}
class Monkey
{
public int Age;
}
}
乘法、加法操作符
务必留意**“数值提升”**
乘法分为:整数乘法、浮点乘法、小数乘法。见C#标准文档7.8节。
double*int,结果为double类型,这就是数据提升。
浮点除法没有除数不能为0的限制,结果为无穷,如果想用正无穷大和负无穷大:
double a = double.PositiveInfinity;//正无穷大
double b = double.NegativeInfinity;//负无穷大
加法还有枚举加法、字符串加法、委托加法。加减也会有类型提升。
具体参见C#标准文档。
移位操作符
<<、 >>,Ctrl+D快捷键:快速复制一行
static void Main(string[] args)
{
int x = 7;
int y = x << 1;
string strX = Convert.ToString(x, 2).PadLeft(32, '0');
string strY = Convert.ToString(y, 2).PadLeft(32, '0');
Console.WriteLine(strX);
Console.WriteLine(strY);
}
在没有溢出的情况下,左移1就是乘2,左移2就是乘2再乘2,右移就是除2。注意:左移时补进来的都是0,右移时最高位,当为正数时补进来的为0,为负数时补进来的为1;
关系和类型检测
- <、 >、 <=、 >=
所有关系运算符的运算结果只有布尔类型的。关系操作符除了可以比较数值类型还可以比较字符类型,因为char类型是归类于整数类型的。
static void Main(string[] args)
{
char char1 = 'a';
char char2 = 'A';
ushort u1= (ushort)char1;
ushort u2 = (ushort)char2;
Console.WriteLine(u1);
Console.WriteLine(u2);
}
字符串不能够比大小,只能够用来比相等不相等
static void Main(string[] args)
{
string str1 = "abc";
string str2 = "Abc";
Console.WriteLine(str1.ToLower() == str2.ToLower());
string.Compare();//也可以调用这个方法去比较,他会有一个返回整数值,如果是0就是相等,如果是负值就是第一个小于第二个,如果是正值就是第一个大于第二个。
}
- is、as
is操作符:用来检验一个对象是不是某个类型的对象,返回一个bool值
t is Teacher/Human/Animal(Human和Animal都是Teacher的父类),is的结果也是true。
任何一个类都是object的派生。只能说儿子像爸爸,不能说爸爸像儿子。
爸爸转儿子需要显式类型转换。
as操作符:如果o是Teacher类型,那么就把这个实例的地址给t
逻辑“与”&、逻辑XOR异或^、逻辑OR或|
因为他们都是操作数据的二进制结构,所以也叫做位与、位异或、位或
static void Main(string[] args)
{
int x = 7;
int y = 28;
int z = x & y;
string str1 = Convert.ToString(x, 2).PadLeft(32, '0');
string str2 = Convert.ToString(y, 2).PadLeft(32, '0');
string str3 = Convert.ToString(z, 2).PadLeft(32, '0');
Console.WriteLine(str1);
Console.WriteLine(str2);
Console.WriteLine(str3);
}
条件与&&、条件或||
&&和||用来操作bool值 ,条件与和条件或有短路效应
static void Main(string[] args)
{
int x = 3;
int y = 4;
int a = 3;
if (x>y && a++>3)
{
Console.WriteLine("Hello");
}
Console.WriteLine(a);
}
&&左边为假,就不再执行右面,||左面为真就不再执行右面
null合并操作符??
static void Main(string[] args)
{
//int x;
//x = null;会报错
// Nullable<int> x = null;//可空int
//因为可空类型很常见,所以将以用关键字替代
int? x = null;
x = 100;
Console.WriteLine(x);
Console.WriteLine(x.HasValue);
int y = x ?? 1;
Console.WriteLine(y);//若x为null,就给y赋值1
}
条件操作符?:
条件操作符是所有操作符中唯一一个可以接收三个操作数的操作符,本质上就是if-else分支的简写。
static void Main(string[] args)
{
int x = 80;
string str = string.Empty;
str = (x >= 80) ? "Pass" : "Failed";//条件成立则等于左边,不成立则等于右边,往往把条件括起来以提高可读性。
}
赋值和lambda表达式
*=、 =、 /=、 %=、 +=、 -=、 <<=、 >>=、 &=、 ^=、 |=、 =>
lambda表达式先不讲
x+=1就是x=x+1
x<<=2就是x=x<<2
赋值运算符的运算顺序为从右到左
int x = 5;
int y = 6;
int z = 7;
int a = x += y *=z;//a为47