C#简介
C#区别于C++与C,他是一门纯面向对象的语言,那么什么是面向对象的语言呢?有跟面向过程有什么区别呢?
面向过程:它是一种编程范式,它将程序视为一系列的过程或函数的集合。
面向对象:它是也是一种常用的编程范式,它将程序设计建立在对象的概念上。那么讲到面向对象就离不开它三大特征了,封装、继承、多态。
什么是封装、继承和多态
1.封装
将对象的状态和行为封装在一起,通过限制对内部数据和方法的直接访问,提供了更好的安全性和灵活性。
封装属性和字段
属性是类,结构体,接口的命名成员,类或者结构体中的成员变量及方法称为域,属性是域的扩展
简单点说:属性就是方法(成员函数),字段就是数据成员。字段如果不赋初值的话系统会给其一个null。
//学生类
class Student
{
private string name;
//属性
public string Name
{
get { return name; }
set { name = value; }
}
//系统能够自己分清给的是那个字段的数据
public int Code
{
get;
set;
}
private int age;
}
2.继承
通过继承机制,一个类可以派生出子类没从而继承父类的属性和方法。继承促进了代码重用和扩展,使得代码更易于维护和扩展。
单继承类(class),多继承接口(interface)
base
1.在派生类中调用基类的构造函数
2.在子类中调用基类的方法
3.注意:静态函数中禁止使用base关键字
//图形基类
class Shape
{
protected int width;
private int length;
protected int Length
{
get { return length; }
set { length = value; }
}
public Shape()
{
Console.WriteLine("父类构造");
}
public void Test2()
{
Console.WriteLine("父类的测试方法");
}
}
//长方形子类
class Rectangle : Shape
{
public int GetArea()
{
return Length * width;
}
public Rectangle()
: base()//在派生类中调用基类的构造函数
{
Console.WriteLine("子类构造");
}
public void Test()
{
base.Test2();//调用基类中的方法
Console.WriteLine("子类的测试方法");
}
//不要再静态方法里使用base
//public static void Test3()
//{
// base.Test2();
//}
}
2.多态
多态允许不同的对象对同一个消息做出不同的响应。通过多态,可以编写出更通用、灵活和可扩展的代码。多态又分为静态多态和动态多态。
什么是静态多态,什么是动态多态
1.静态多态
函数重载:参数类型不同,参数的个数不同,不能重载只有返回值类型不同的函数
运算符重载:operator
C#中可重载的运算符:
+ - * / %
++ -- ! ~
== > < >= <= !=
//静态多态
class TestData
{
//函数重载
public int Add(int a, int b)
{
return a + b;
}
public double Add(double a, double b)
{
return a + b;
}
//这不是函数重载,因为其只有返回值类型不同
public float Add(int a, int b)
{
return a + b;
}
public int num;
//运算符重载
public static TestData operator +(TestData t1, TestData t2)
{
TestData t = new TestData();
t.num = t1.num + t2.num;
return t;
}
}
不能重载的运算符
= -> . new sizeof ?: += -= *= /= %=
2.动态多态
虚函数,抽象类 关键字abstract (抽象类中可以定义普通的成员方法)
1.不能创建抽象类的实例
2.抽象类中可以声明抽象函数和非抽象函数,但是非抽象类不能声明抽象函数
//动态多态
//抽象类 关键字abstract
abstract class Animal
{
public abstract void Eat();
public virtual void Test()
{
Console.WriteLine("抽象类中的普通函数");
}
}
class Lion : Animal
{
//public abstract void TestMethod();
public override void Eat()
{
Console.WriteLine("子类的吃");
}
public override void Test()
{
base.Test();
Console.WriteLine("重写父类中的test函数");
}
}
C#的访问权限
C#不同与C++,它有四种访问权限:
private:只能在类中访问
public:公有的,在任意程序集(一个项目)中都能访问
protected:只能在当前程序集的类中和子类中访问
internal:只能在当前程序集类中和类外访问
protected internal:protected or internal,既可以在当前程序集的子类和类中访问,也可以只在当前程序集的类中和类外访问
既然提到了程序集,那什么是程序集呢?
程序集:类型元数据,程序集元数据,IL代码,资源
元数据:元数据一般就是指描述自身的数据
程序集元数据:包含程序集的扳本信息,安全信息,签名等
类型元数据:记录程序集引用了哪些类,用户自定义了哪些类,字段,数据类型等一系列信息
IL代码:c#->IL代码->保存到程序集中,被CLR加载,通过JIT调用BCL->机器码,在CPU运行
资源:图片,声音,视频等
C#的变量
值类型和引用类型
值类型:值类型的变量直接存储数据。值类型包括: int float byte long short double char bool struct
引用类型:引用类型的变量持有的是数据引用(地址),数据是存储在堆区的。引用类型包括:class string 委托 事件
值类型与引用类型区别:
1.值类型数据存储在栈区,引用类型的数据存储在堆区
2.值类型存取速度快,引用类型存取速度慢
3.值类型表示的是实际的数据,引用类型表示的指向存储在堆区的地址
4.值类型继承自system.valuetype,引用类型继承自system.object
5.栈区内存是自动释放,堆区内存由GC回收
什么是GC(垃圾回收机制)
在编写程序时,会产生很多的数据 比如:string 变量,这些数据都存储在堆区里,如果不合理的管理他们,就会内存溢出导致程序崩溃
C#内置了自动垃圾回收GC,在编写代码时可以不需要担心内存溢出的问题 变量失去引用后 GC会帮我们自动回收,但不包括数据流,和一些数据库的连接,这就需要我们手动的释放资源
GC是一种自动化内存管理机制,负责在程序运行时跟踪并管理对象的内存使用情况,当对象不再被引用时,自动从内存中删除这些对象,并释放内存
GC的作用:让内存的利用率更高。
特点:
1.自动管理
2.分代回收 代龄 高效,朝生夕灭,只有少部分的对象会长时间存在
3.不确定性 GC.Collect()
4.gc会影响程序的性能
5.gc只回收堆区内存,栈区内存是使用后自动释放
C#中的循环
循环语句
while do...while for foreach
foreach的语法形式:
foreach(var item in collection)
{
}
foreach的优缺点:
优点:
1.foreach语法简洁,多维数组遍历只需要一行代码,for循序需要给初始值,步长,结束值,foreach不需要,会自动遍历集合中的所有值
2.foreach的效率比for循环高,foreach遍历速度比for要快
3.不需要关心数组的起始值
缺点:
1.会造成额外gc开销
2.foreach是只读类型
因此在知道循环的长度时,尽量用for循环而不用foreach
class Program
{
static void Main(string[] args)
{
//C++中定义数组
//int a[10]={1,2,};
//C#中数组的定义形式
int[] a1 = { 1, 2, 3 };
int[] a2 = new int[3] { 1, 2, 3 };
int[] a3 = new int[] { 1, 2, 3 };
int[,] a4 = new int[,] { { 1, 2 }, { 3, 4 } };
int[,] a5 = new int[2, 2] { { 1, 2 }, { 3, 4 } };
//for循环的定义形式 a1.Length能的到a1的长度
for (int i = 0; i < a1.Length; i++)
{
Console.WriteLine(a1[i]);
a1[i] = 6;
}
for (int i = 0; i < a4.GetLength(0); i++)
{
for (int j = 0; j < a4.GetLength(1); j++)
{
Console.WriteLine(a4[i, j]);
}
}
//foreach循环的定义形式
foreach (int item in a1)
{
Console.WriteLine(item);
}
foreach (int item in a4)
{
Console.WriteLine(item);
}
}
}
C#中的函数
函数中的参数
如果我们想C++那样传递引用类型,C#中提供了专门的关键字ref与out
相同点:都是址传递,执行方法后,原来的值会改变
不同点:
1.使用ref时,传入参数必须要初始化,而使用out时参数可以不初始化
2.ref的参数可以不赋值,但是out参数必须要赋值
class Program
{
static void Main(string[] args)
{
Test t = new Test();
int a = 1, b = 2;
//Console.WriteLine("a={0},b={1}", a, b);
//t.TestOut(out a, out b);
//Console.WriteLine("a={0},b={1}", a, b);
t.TestRef(ref a, ref b);
Console.WriteLine("a={0},b={1}", a, b);
}
}
class Test
{
//out的蚕食必须在函数体中完成初始化
public void TestOut(out int a, out int b)
{
a = 10;
b = 20;
}
//传入的参数必须已经初始化
public void TestRef(ref int a, ref int b)
{
//a = 30;
//b = 40;
}
}
override与new关键字
override:重写基类中的方法(virtual,abstract)
new:
1.创建对象
2.在子类中使用new关键字,修饰定义与父类中同名的函数,叫覆盖,覆盖不会改变父类的方法功能
class Program
{
static void Main(string[] args)
{
A aaa = new A();
aaa.Test();
B bbb = new B();
bbb.Test();
A aaaaaa = new B();
aaaaaa.Test();
}
}
class A
{
public virtual void Test()
{
Console.WriteLine("a类的测试函数");
}
}
class B : A
{
//public override void Test() //当用子类创建父类时, A aaaaaa = new B();调用子类的函数
//{
// Console.WriteLine("b类的测试函数");
//}
//当用子类创建父类时, A aaaaaa = new B();覆盖是不会改变父类的功能,仍然调用父类的函数
public new void Test()
{
Console.WriteLine("b类的测试函数");
}
}