C#基础知识

C#基础

一、C#数据类型

1.元组

1.元组的介绍

  1. 元组在C# 7.0及以后版本才支持
  2. 用于处理各种数据类型的组合

2.元组类型的定义及使用

//第一种定义方式
(double,int) value1 = (4.5,3);	//定义了一个元组变量
(double,int,float) value2 = (4.5,3,0.5f);	//定义了一个元组变量
Console.WriteLine(Value2.Item1);	//输出元组类型变量的值
Console.WriteLine(Value2.Item2);
Console.WriteLine(Value2.Item3);

//第二种定义方式
(double vd,int vi,float vf) value3 = (4.5,3,0.5f);	//定义了一个元组变量 直接写明变量名称
Console.WriteLine(Value2.vd);	//输出元组类型变量的值
Console.WriteLine(Value2.vi);
Console.WriteLine(Value2.vf);

//第三种定义方式,通过隐式类型var
var value4 = (vd:4.5,vi:3,vf:0.5f);
Console.WriteLine(Value2.vd);	//输出元组类型变量的值
Console.WriteLine(Value2.vi);
Console.WriteLine(Value2.vf);

//第4种定义方式,也是通过隐式类型var
var (v1,v2)=(1,2.4f);
Console.WriteLine(v1);	//输出元组类型变量的值
Console.WriteLine(v2);

2.Object和动态类型

1.基本的介绍

  1. Objetc:在C#中,所有类型都是直接或间接从System.Object继承的,所以可以将任何变量赋值给Object类型的变量
  2. dynamic:动态类型,在编译时不做检查。在运行时确定类型
  3. Var:隐式类型,通过赋值语句来确认类型,但是确认后不可再修改,编译时确定

1.定义和使用

Object obj = 123456;	
obj="c#";
Console.WriteLine(obj);	//可以将任意类型的变量赋值给Object

dynamic dyc = "c#";
dyc = 123456;
Console.WriteLine(dyc);	//可以赋值不同类型的变量

var i = 12;
//i="c#";	这个语句编译不通过,i通过赋值语句确认了是int类型,就不可以再将string类型的值赋值给i

3.类型默认值

  • 任何引用类型:null
  • 内置整数和浮点数类型:0
  • bool:false
  • char:‘\0’
  • enum:0
  • strcut:所有值类型字段为默认值,所有引用类型字段为null

4.作用域冲突问题

1.内层作用域的变量会覆盖外层作用域的同名变量

2.示例

//这样能正常编译
internal class Program
{

    static int Temp = 1000;

    static void Method()
    {
        Console.WriteLine(Temp);
    }
}
//这样会报错
internal class Program
{

    static int Temp = 1000;

    static void Method()
    {
        Console.WriteLine(Temp);
        int Temp = 100;				//会提示在Temp变量声明之前无法使用,由于在本地作用域声明了和外层作用域同名的变量,隐藏了外层的Pragram.Temp变量。
    }
}
//这样会成功编译,但输出的值是100
internal class Program
{

    static int Temp = 1000;

    static void Method()
    {
    	int Temp = 100;	
        Console.WriteLine(Temp);		
    }
}

二、C#运算符

1.关系运算符

在C#中,== 和 equals 都是用来比较两个对象是否相等的方法,但它们之间有一些关键的区别。

  1. 运算符 ==:
    == 是C#中的比较运算符,用于引用类型的比较。
    当使用 == 运算符来比较两个引用类型对象时,它会检查两个引用是否指向内存中的同一个对象,即它们是否完全相同。
    换句话说,它比较的是对象的引用(是不是指向一个地址),而不是它们的内容。
  2. 方法 equals:
    equals 是一个方法,通常用于比较两个对象的内容是否相等。
object str1 = "abc";
object str2 = "ab";
str2 = str2 + "c";
bool result1 = str1==str2;				//值是false,因为不是指向同一个引用
bool result2 = object.Equals(str1,str2);//值是true,因为内容相等

object str1 = "abc";
object str2 = "abc";
bool result1 = str1==str2;				//值是true,因为指向同一个引用  c#中 相同的字符串共用一个堆区内存,这里两个"abc"其实同一个堆区内存

object str1 = "abc";
object str2 = "ab";
str2 = "ab" + "c";
bool result1 = str1==str2;				//值也是true,因为指向同一个引用

bool result3 = object.ReferenceEquals(str1,str2);	//比较对象的引用

2.算术运算符

  • 算术运算符计算的时候,要考虑溢出等问题
  • 取余运算符%常用场景是限定值的范围,比如输入值取余100,取值范围就在0-100。例如平时的随机数

3.字符串的操作

  1. 子字符串的截取
string str = "Hello-world";
string subStr = str.Substring(0,2); //从第0个字符开始截取2个字符
  1. 字符串的分隔
string ip = "192.168.1.209";
string[] ipArr = ip.Split('.');     //按','分隔字符,返回一个字符串数组
  1. 字符串的格式化
string strIp = string.Format("{0}+{1}+{2}+{3}", ipArr[0], ipArr[1], ipArr[2], ipArr[3]);    //用{0}之类的进行占位

string strIp2 = $"{ipArr[0]}+{ipArr[1]}+{ipArr[2]}+{ipArr[2]}";         //在字符串前加$符号,后续字符串内的可以直接用{}传入对应要显示的字符串变量
  1. 字符串取消转义
string str3 = @"D:\Tools\";			//加@符号,后续字符串不会做特殊处理,只显示原本内容

5 字符串的Join方法:串联字符串数组的所有元素,其中在每个元素之间使用指定的分隔符

List<string> list = new List<string>();
list.Add("12");
list.Add("34");
string str = string.Join(",", list);	//将list中所有字符串,按“,”号分隔串联

三、C#方法

1.out和ref传值

  • 两个都是地址传递
  • out传值前,变量可以没初始化。传入的时候,传入的参数必须带out修饰符
  • ref传值前,变量必须有值。传入的时候,传入的参数必须带ref修饰符
{
   int a1 = 10;
   int a2 = 20;
   int result;
   Program program = new Program();
   program.Add_Out(a1, a2,out result); //可以不用初始化,但必须带out修饰符传入 由于Add不是静态函数,所以需要声明Add所在类Program的变量,通过变量去调用
   //int a3;
   //program.Add_Ref(a1, a2, ref a3);//传入的ref变量必须有值,带ref修饰符传入。这里会报错
   int a4 = 0;
   program.Add_Ref(a1, a2, ref a4);    //这样是正常的;

   Console.WriteLine(a4);
   Console.ReadLine();

}

void Add_Out(int a,int b,out int result)
{
   result = a + b;
}

void Add_Ref(int a, int b, ref int result)
{
   result = a + b;
}

2.访问修饰符

  • public
  • private
  • protected
  • internal:同一程序集中的任何代码都可以访问该类型或成员
  • protected internal:该类型或成员可由对其进行声明的程序集或另一个程序集中的派生class中的任何代码访问
  • private protected:该类型或成员可以通过从class派生的类型访问,这些类型在其包含程序集中进行声明

一个方法没写访问修饰符,默认为private
一个类没有指定访问修饰符,默认为internal

四、C#类

1.属性

namespace ConsoleApp2
{
    internal class Program
    {
        static void Main(string[] args)
        {
            MyClass myClass = new MyClass();
            /*myClass.MyProperty_4 = 10;*/  //会报错,只能类内的方法访问
        }

    }

    public class MyClass
    {
        private int myVar;          //变量,为私有的

        public int MyProperty       //属性,是一种语法糖,目前MyProperty是可读可写
        {
            get;
            set;
        }

        public int MyProperty_2       //属性,是一种语法糖,目前MyProperty是只读
        {
            get;
        }
        //public int MyProperty_3       //属性,是一种语法糖,不允许没有get访问器,因为从操作系统的概念上来讲,设置一个变量却不读取他,将没有意义
        //{
        //    set;
        //}
        public int MyProperty_4       //属性,表示可以在类外读取,却只能在类内设定
        {
            get;
            private set;
        }

        void Add()
        {
            MyProperty_4 = 4;
        }
    }
}

2.构造函数

  • 继承:this() 对应的构造函数
namespace ConsoleApp2
{
    internal class Program
    {
        static void Main(string[] args)
        {
            MyClass myClass = new MyClass();		//由于继承原因,虽然调用的public MyClass(),但会先调用一次public MyClass(int a)
            MyClass myClass2 = new MyClass(1,2);	//同上
            Console.WriteLine(myClass.MyProperty);
            Console.ReadKey();
        }

    }

    public class MyClass
    {
        private int myVar;

        public int MyProperty
        {
            get { return myVar; }
            set { myVar = value; }
        }

		/*
		这里空参的构造函数继承了传入一个int型参数的构造函数
		在上面实例化类的时候,因为没有传参,所以调用的是这个无参构造。但由于有继承,会先调用public MyClass(int a)这个构造函数,然后继续调用public MyClass(),最终结果为100
		*/
        public MyClass():this(0)	
        {
            myVar = 100;
        }

        public MyClass(int a)
        {
            myVar = a;
        }
        
		public MyClass(int a)
        {
            myVar = a;
        }
		/*
		这里继承了public MyClass(int a),调用的时候也会优先调用public MyClass(int a),再调用自身
		*/
        public MyClass(int a, int b) : this(a)
        {
            
        }
    }
}

3.类与结构体

1.类与结构体的差异

  1. 结构体是值类型 在栈区分配内存,类是引用类型,在堆区分配内存
  2. 结构体 也可以有属性和方法。结构体有隐藏的无参构造函数,而且无法重写。任何时候都会存在
  3. 结构体的new方法不是生成一个新的对象,而是在用构造函数给变量赋值。
  4. 类的new方法是生成一个新的对象,在堆区内存
  5. 结构体是值类型在栈区存储,速度较快,数据量较小的数据类型适合结构体
  6. 类里面定义的值类型的变量在new一个新对象的时候其实是存放在堆内存的

2.结构体的陷阱

  • 当创建一个类,再在类中定义一个结构体。直接调用类的对象去修改结构体的属性,会提示“无法修改“…”的返回值,因为它不是变量”。
public struct MyStruct
{
    private int myVar;

    public int MyProperty
    {
        get { return myVar; }
        set { myVar = value; }
    }
}

public class MyClass
{
    private int myVar;

    public int MyProperty
    {
        get { return myVar; }
        set { myVar = value; }
    }

    private MyStruct myStruct1;

    public MyStruct MyStruct1
    {
        get { return myStruct1; }
        set { myStruct1 = value; }
    }
}

internal class Program
{
    static void Main(string[] args)
    {
        MyClass myClass = new MyClass();
        myClass.MyStruct1.MyProperty(10);	//会报错提示“无法修改“...”的返回值,因为它不是变量”。
        //因为myClass是引用类型,MyStruct是值类型,这里实际会调用
        public MyStruct MyStruct1
		{
		get { return MyProperty; }
		}
		//但由于结构体是值传递的,返回的是栈上的一个临时的备份。无法赋值到本身去,所以没有意义,编译器从源头就禁用了这个操作
		//如果直接创建Mystruct这个结构体变量
		MyStruct myStruct = new MyStruct();
		myStruct.MyProperty = 20;	//这样是有效的,相当于直接给栈区的内存赋值,类似于 int a;a =10;
		//前面在类中赋值结构体的属性,类似于创建了一个函数,将a进行值传递返回,由于是值传递,不会修改本身的值
    }

}

2.类(引用类型)传入函数算引用传入

  1. 函数内部对形参的值进行修改,可以改变外部实参的值,因为引用类型在传参时,是将变量的地址给了形参,当形参被修改时,意味着其地址所指向的堆空间中的值被修改了,此时外部实参也指向的该地址所指向的堆空间,因此间接的对外部实参的值进行改变
  2. 而string类型比较特殊,string类型是引用类型,但是在函数内部是无法修改外部实参的值的,因为string类型的变量在被赋予新值的时候,不会将原值擦去而填上新的值,而是会在堆中重新开辟空间,在空间中放入新的值,从而变量的地址也会改变,变成新空间的地址
internal class Program
{
     static void Main(string[] args)
     {
         MyClass myClass = new MyClass();
         SetA(myClass);				//在函数内修改,外面的对象值会修改,相当于引用传递
         string str = "12";
         SetString(ref str);		//在函数内修改,外面的对象的值不会修改,因为string类型不定长,修改内容会新开辟堆区内存,是一个新的变量了
     }

     public static void SetA(MyClass myClass)
     {
         myClass.MyProperty = 1000;
     }

     public static void SetString(ref string str)
     {
         str = "234";
     }

 }
public class MyClass
{
    private int myVar;

    public int MyProperty
    {
        get { return myVar; }
        set { myVar = value; }
    }
}

五、封装,继承,多态

1.抽象类

  1. 抽象类是在类前面加上abstract修饰符,抽象类的定义和正常的类一致。但抽象类不可以实例化,常用作父类
//抽象类的定义
public abstract class Person
{
    private string name;

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

}
  1. 一个子类只能有一个父类,但是可以继承多个接口

2.多态

  1. 在父类中的方法前增加virtual,表示该方法为虚方法。可以在子类中重写
  2. 在子类的方法前加new修饰符可以隐藏父类的方法。
  3. 如果是重写虚方法,用父类引用子类的实例,也可以调用子类重写的方法,但是如果是隐藏的话,父类引用子类的实例无法调用子类的方法
  4. 在方法前加abstract是抽象方法,抽象方法不能有方法主体,而且必须在抽象类中
  5. 抽象方法也可以用override修饰在子类重写,但是必须在子类重写抽象方法,不然无法编译
namespace ConsoleApp2
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Dog dog = new Dog();
            dog.Speak();        //输出"汪汪汪_重写虚方法"
            dog.Speak2();       //输出"汪汪汪_隐藏父类方法"
            dog.Speak3();       //输出"汪汪汪_重写抽象方法"
            Animal An = new Dog();
            An.Speak();         //输出"汪汪汪_重写虚方法"
            An.Speak2();        //输出叫声      如果是重写虚方法,用父类引用子类的实例,也可以调用子类重写的方法,但是如果是隐藏的话,父类引用子类的实例无法调用子类的方法
            An.Speak3();        //输出"汪汪汪_重写抽象方法"
            Console.ReadLine();

        }


    }

    public abstract class Animal
    {
        private string name;

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        public virtual void Speak()         //虚方法,用virtual修饰,可以在子类重写
        {
            Console.WriteLine("叫声");
        }

        public void Speak2()                //普通方法
        {
            Console.WriteLine("叫声");
        }

        public abstract void Speak3();

    }

    public class Dog : Animal
    {
        public override void Speak()        //重写虚方法
        {
            Console.WriteLine("汪汪汪_重写虚方法");
        }

        public new void Speak2()            //隐藏了父类同名的方法。
        {
            Console.WriteLine("汪汪汪_隐藏父类方法");
        }

        public override void Speak3()
        {
            Console.WriteLine("汪汪汪_重写抽象方法");
        }
    }
    
}

五、异常处理

1.异常捕获

  • 通常用try,catch,finally,throw这几个关键字来实现
  • Exception是所有异常类的基类
catch(System.IndexOutOfRangeException ex)	//如果是下标溢出相关异常才会进入到这个异常捕获当中
{

}
catch (Exception ex)	//如果没有catch能够对应的上错误,会由Exception捕获
{

}

2.抛出异常

  1. 通过throw关键字抛出异常
static void Main(string[] args)
{
    while (true) 
    { 
        try
        {
            int a;
            string value = Console.ReadLine();
            bool result = Int32.TryParse(value, out a);
            if(result)
            {
                Console.WriteLine(a);
            }
            else
            {
                throw new Exception("不可输入非数字");	//如果输入的值无法转换成int32类型,则throw Exception类型的错误,内容为"不可输入非数字"
            }

        }
        catch(System.IndexOutOfRangeException ex)
        {

        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);

        }
    }

}
  • 也可以自己创建异常类
internal class ValueErrorException : Exception	//自己创建异常类,继承自Exception
{
     public ValueErrorException(string msg) :base(msg)	//自定义有参的构造函数,通过传入的参数设定Message
     {

     }
 }

internal class Program
{
    static void Main(string[] args)
    {
        while (true) 
        { 
            try
            {
                int a;
                string value = Console.ReadLine();
                bool result = Int32.TryParse(value, out a);
                if(result)
                {
                    Console.WriteLine(a);
                }
                else
                {
                    throw new ValueErrorException("不可输入非数字");	//抛出自定义的异常类
                }

            }
            catch(ValueErrorException ex)		//专门捕获自定义的异常类
            {
                Console.WriteLine(ex.Message);
            }
            catch (Exception ex)
            {  

            }
        }

    }
}
  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值