C#入门01:开胃菜-语法基础

输入和输出

    //控制台输入整数和字符串
    int age = int.Parse(Console.ReadLine());
    string name = Console.ReadLine();

    //控制台输入浮点数
    double salary = double.Parse(Console.ReadLine());
    double addSalary = Convert.ToDouble(Console.ReadLine());

    //控制台格式化输出
    Console.WriteLine("这是第一个C#控制台程序");
    Console.WriteLine("Your name is {0}, age is {1}, and salary is {2:f2}, addSalary is {3}",
        name, age, salary, addSalary.ToString());

    //格式化到字符串
    int age = 32;
    double salary = 12000.59;
    String name = "zhangsan";
    String outstr = String.Format("name={0}, age={1}, salary={2:f1}", name, age, salary);
    Console.WriteLine(outstr);

    //输出日期和时间,DateTime.Today只获取日期
    Console.WriteLine("{0:D} {0:t}", DateTime.Now);
    DateTime dt = new DateTime(2017, 4, 1, 13, 16, 32, 108);
    dt.ToString("y yy yyy yyyy");//17 17 2017 2017
    dt.ToString("M MM MMM MMMM");//4  04 四月 四月
    dt.ToString("d dd ddd dddd");//1  01 周六 星期六
    dt.ToString("t tt");//下 下午
    dt.ToString("H HH");//13 13
    dt.ToString("h hh");//1  01
    dt.ToString("m mm");//16 16
    dt.ToString("s ss");//32 32

数据类型

C#中的数据类型包含值类型(int,double,float,string等),引用类型(object,dynamic和string三种内置的引用类型,以及class等)和指针类型(C#中不建议使用指针类型)。

**如何区别值类型和引用类型? **
值类型:可以直接赋值的通常是值类型,比如int,float等,String类型除外,它是特殊的引用类型;
引用类型:通常是对象,比如从Object派生的对象,可以设置为null,还需要使用new操作符来申请内存空间构造对象;比如自己实现的class,系统定义的class等

类型检查
sizeof()判断类型的大小,判断值类型有效,由于在C#中引用类型类似C++中的引用,不存储内存空间,因此不能用sizeof()计算引用类型的空间大小;
typeof()返回数据的类型

内置的引用类型

    //对象(Object)类型,装箱就是这里将值类型转换为引用类型,反之就是拆箱
    object obj1 = 100;
    object obj2 = 100.5;
    Console.WriteLine("obj1 is {0}, obj2 is {1}", obj1.GetType().ToString(), obj2.GetType().ToString());

    //动态(Dynamic)类型,可以是值类型,也可以是引用自定义class类型
    dynamic obj3 = 100;
    dynamic obj4 = 100.5;
    Console.WriteLine("obj3 is {0}, obj4 is {1}", obj3.GetType().ToString(), obj4.GetType().ToString());

    //字符串类型,使用@将转义字符(\)当作普通字符对待
    string str1 = @"C:\Windows";
    string str2 = "C:\\Windows";
    if (str1 == str2)
    {
        Console.WriteLine("他们相等");
    }

动态类型与对象类型相似,但是对象类型变量的类型检查是在编译时发生的,而动态类型变量的类型检查是在运行时发生的。

用户自定义引用类型
用户或系统实现的class、interface 或 delegate等;

类型转换

显示和隐式转换
等同于C++的操作

    int v = 10;    
    double d = v;        //隐式转换
    int v2 = (int)d;      //显式转换

AS操作(引用类型转换)

    //子类到基类的转换,两种方法都可以
    //第一种是强制转换,在编译期间会进行判断
    //第二种转换在失败时base b会为空,不抛出异常
    base b = subclass;
    base b = subclass as base;

    //基类到派生类的转换:
    //C++:subclass* sub = dynamic_cast<base>(base);
    //if (sub != nullptr) 需要做一下判断是否转换成功
    Rectangle rect2 = sh as Rectangle;
    if (rect2 != null)
    {
        //需要做一下判断,转换失败rect2为空,但不会抛出异常
    }

使用AS操作符转换,但是AS只能用于引用类型和可为空的类型。使用as有很多好处,当无法进行类型转换时,会将对象赋值为NULL,避免类型转换时报错或是出异常。C#抛出异常在进行捕获异常并进行处理是很消耗资源的,如果只是将对象赋值为NULL的话是几乎不消耗资源的(消耗很小的资源)。

装箱和拆箱(值类型和引用类型转换)

    object obj1 = 100;
    object obj2 = 100.5;
    Console.WriteLine("obj1 is {0}, obj2 is {1}", obj1.GetType().ToString(), obj2.GetType().ToString());

装箱和拆箱在值类型和引用类型之间架起了一座桥梁,使得任何 value-type 的值都可以转换为 object 类型的值,反过来转换也可以。 装箱:装箱是指将一个值类型的数据隐式地转换成一个对象类型(object)的数据。执行装箱操作时不可避免的要在堆上申请内存空间,并将堆栈上的值类型数据复制到申请的堆内存空间上,这肯定是要消耗内存和cpu资源的。注意:在执行装箱转换时,也可以使用显式转换。 拆箱:拆箱是指将一个对象类型的数据显式地转换成一个值类型数据。拆箱过程是装箱的逆过程,是将存储在堆上的引用类型值转换为值类型并赋给值类型变量。拆箱操作分为两步:一是检查对象实例,确保它是给定值类型的一个装箱值;而是将该值从实例复制到值类型变量中。装箱和拆箱都是要消耗内存和cpu资源的,也就造成效率降低,所以要尽量避免使用。

可空类型

    //num1不能为空值,但是可以使用?符号来定义可空值类型
    double? num1 = null;
    double? num2 = 3.14157;
    double num3;
    // num1 如果为空值则返回 5.34
    num3 = num1 ?? 5.34; 
    Console.WriteLine("num3 的值: {0}", num3);
    num3 = num2 ?? 5.34;
    Console.WriteLine("num3 的值: {0}", num3);
    Console.ReadLine();

一维数组

    //声明数组
    int[] age;
    double[] balance;

    //初始化数组并赋值
    age = new int[5] { 1, 2, 3, 4, 5 };
    //也可以不指定长度:age = new int[] { 1, 2, 3, 4, 5 };

    //初始化数组使用默认值0
    balance = new double[10];

    //访问数组元素
    int val = age[3];

    //遍历数组元素
    foreach (int v in age)
    {
        Console.WriteLine("{0}", v);
    }

多维数组

    //声明数组
    int[,] age;
    double[,] balance;

    //初始化数组并赋值
    age = new int[2,3] { { 1, 2, 3 }, { 4, 5, 6 } };
    //也可以不指定长度:age = new int[,] { { 1, 2, 3 }, { 4, 5, 6 } };

    //初始化数组使用默认值0
    balance = new double[5, 10];

    //访问数组元素
    int val = age[0,1];

    //遍历数组元素
    foreach (int v in age)
    {
        Console.WriteLine("{0}", v);
    }

交错数组

交错数组是数组的数组,不同于多维数组。多维数组本质上元素都是一个数据类型,而交错数组的元素本质上是另外一个数组。交错数组和C++的多维数组很类似,声明和使用看起来都一样,要特别注意!

    //声明数组
    int[][] scores;

    //初始化数组
    scores = new int[5][];
    for (int i = 0; i < scores.Length; i++)
    {
        //数组元素是一个有4个元素的数组
        scores[i] = new int[4] { i * i + 1, i * i + 2, i * i + 3, i * i + 4 };
    }
    //也可以直接初始化:scores = new int[2][] { new int[] { 92, 93, 94 }, new int[] { 85, 66, 87, 88 } };

    //访问数组元素
    int val = scores[0][2];

    //遍历数组元素
    for (int i = 0; i < 5; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            Console.WriteLine("a[{0}][{1}] = {2}", i, j, scores[i][j]);
        }
    }

Array 类

Array 类是 C# 中所有数组的基类,它是在 System 命名空间中定义。
Array 类提供了各种用于数组的属性和方法。
Array类是一个抽象类,不能被实例化。

结构体Struct

    struct Books
    {
        public string title;
        public string author;
    };

    Books b;
    b.title = "Eleven";
    b.author = "ZhangSan";
    Console.WriteLine("{0}, {1}", b.title, b.author);

在 C# 中,结构体是值类型数据结构。它使得一个单一变量可以存储各种数据类型的相关数据。

C# 中的结构有以下特点:

  • 结构可带有方法、字段、索引、属性、运算符方法和事件。
  • 结构可定义构造函数,但不能定义析构函数。但是,您不能为结构定义默认的构造函数。默认的构造函数是自动定义的,且不能被改变。
  • 与类不同,结构不能继承其他的结构或类。
  • 结构不能作为其他结构或类的基础结构。
  • 结构可实现一个或多个接口。
  • 结构成员不能指定为 abstract、virtual 或 protected。
  • 当您使用 New 操作符创建一个结构对象时,会调用适当的构造函数来创建结构。与类不同,结构可以不使用 New 操作符即可被实例化。
  • 如果不使用 New 操作符,只有在所有的字段都被初始化之后,字段才被赋值,对象才被使用。

类和结构有以下几个基本的不同点:

  • 类是引用类型,结构是值类型。
  • 结构不支持继承。
  • 结构不能声明默认的构造函数。

枚举Enum

C# 枚举是值类型。换句话说,枚举包含自己的值,且不能继承或传递继承。

    //默认从0开始,依次加1
    enum Days { Sun, Mon, tue, Wed, thu, Fri, Sat };

    //指定从10开始,中间Fri指定为20,Sat就是21
    enum Days2 { Sun = 10, Mon, tue, Wed, thu, Fri=20, Sat };

    //和C++不一样,这里会打印字符串Sun,Fri,Sat
    Console.WriteLine("{0}, {1}, {2}", Days.Sun, Days.Fri, Days.Sat);
    Console.WriteLine("{0}, {1}, {2}", Days2.Sun, Days2.Fri, Days2.Sat);

    //强制将枚举转换为int,才会得到和C++一样的结果
    Console.WriteLine("{0}, {1}, {2}", (int)Days.Sun, (int)Days.Fri, (int)Days.Sat);
    Console.WriteLine("{0}, {1}, {2}", (int)Days2.Sun, (int)Days2.Fri, (int)Days2.Sat);

参数传递

和C++一样,C#也有值传递和引用传递,但是比C++多一个参数传出功能。

    //引用传递使用ref而不是&
    public void swap(ref int x, ref int y)
    {
        int temp = x;
        x = y;
        y = temp;
    }

    //输出参数传递,对于未初始化的参数获取很有用
    public void GetValues(out int a, out int b)
    {
        Console.WriteLine("请输入第一个值: ");
        a = Convert.ToInt32(Console.ReadLine());
        Console.WriteLine("请输入第二个值: ");
        b = Convert.ToInt32(Console.ReadLine());
    }

    int x = 100;
    int y = 200;
    MyClass n = new MyClass();

    n.swap(ref x, ref y);
    Console.WriteLine("在交换之后,x 的值: {0}", x);
    Console.WriteLine("在交换之后,y 的值: {0}", y);

    int a;
    int b;
    n.GetValues(out a, out b);
    Console.WriteLine("a={0}, b={1}", a, b);
    //这里不能使用swap,因为a,b都没有初始化
    //n.swap(ref a, ref b);

C#的引用传递可以使用值传递也可以显式使用引用传递,效果是一样的。
C#将Object obj2 = obj1当成对象引用,而不是创建新的对象,创建新对象都使用new操作符。而C++对象引用必须强制使用&符号,比如:Object& obj2 = obj1。这点和C++有很大区别!

    class MyData
    {
        public int val = 0;
        double v2 = 100.3232;

        //通过传递引用改变数据
        static public void ChangeValue1(ref MyData d)
        {
            d.val = 60;
        }

        //通过传递值改变数据,因为这个值是一个引用类型,因此可以改变数据
        //如果参数是值类型,如int,float等就不可以
        static public void ChangeValue2(MyData d)
        {
            d.val = 100;
        }
    }

    //使用new创建对象
    MyData v = new MyData();
    MyData.ChangeValue1(ref v);
    Console.WriteLine("v.val={0}", v.val);
    MyData.ChangeValue2(v);
    Console.WriteLine("new v.val={0}", v.val);

C#数组也是对象,因此参数传递中的数组等同于引用传递:

    //也可以static void init(ref int[] array)这样声明  
    static void init(int[] array)  
    {
        int index = 0;
        int size = array.Length;
        for (int i=0; i<size; i++)
        {
            array[i] = ++index;
        }
    }

    //创建一个10个元素长度的数组
    int[] array = new int[10];

    //使用参数传递初始化数组,等同于Program.init(ref array);   
    Program.init(array);

    //打印数组元素
    foreach (int i in array)
    {
        Console.Write("{0} ", i);
    }

可变参数(参数数组)

类似于C/C++的可变参数(不定长参数),C# 通过使用参数数组来实现不可知个数的参数传递。
1.带 params 关键字的参数类型必须是一维数组,不能使用在多维数组上;
2.不允许和 ref、out 同时使用;
3.带 params 关键字的参数必须是最后一个参数,并且在方法声明中只允许一个 params 关键字。
4.不能仅使用 params 来使用重载方法。
5.没有 params 关键字的方法的优先级高于带有params关键字的方法的优先级

    //参数数组只能传递int类型
    public static void UseParams(params int[] list)
    {
        for (int i = 0; i < list.Length; i++)
        {
            Console.Write(list[i] + " ");
        }
        Console.WriteLine();
    }

    //可以传递任意object类型
    public static void UseParams2(params object[] list)
    {
        for (int i = 0; i < list.Length; i++)
        {
            Console.Write(list[i] + " ");
        }
        Console.WriteLine();
    }

    UseParams(1, 2, 3, 4);
    UseParams2(1, 'a', "Apple");
    UseParams2();   //不填入参数

    //和UseParams(1, 2, 3, 4);等价
    int[] myIntArray = { 5, 6, 7, 8, 9, };
    UseParams(myIntArray);

    object[] myObjArray = { 1, 'b', "boom", "app" };
    UseParams2(myObjArray);

    //输出为"System.Int32[]",这里把数组看作为一个object类型了
    UseParams2(myIntArray); 

Const和Readonly

静态常量
所谓静态常量就是在编译期间会对变量进行解析,再将常量的值替换成初始化的值。
动态常量
所谓动态常量就是编译期间会将变量标记只读常量,而不用常量的值代替,这样在声明时可以不初始化,可以延迟到构造函数初始化。

const修饰的常量是上述中的第一种,即静态常量,而readonly是上述中第二种即动态常量。他们的区别可以从静态常量和动态常量的特性来说明:

  • const修饰的常量在声明时必须初始化值;readonly修饰的常量可以不初始化值,且可以延迟到构造函数。
  • cons修饰的常量在编译期间会被解析,并将常量的值替换成初始化的值;而readonly延迟到运行的时候。
  • const修饰的常量注重的是效率;readonly修饰的常量注重灵活。
  • const修饰的常量没有内存消耗;readonly因为需要保存常量,所以有内存消耗。
  • const只能修饰基元类型、枚举类、或者字符串类型;readonly却没有这个限制。

猜你喜欢

转载自my.oschina.net/u/3489228/blog/1790147