C#学习笔记——数据类型篇

C#学习笔记——(五)

  • 一、数据类型
    • 1、数据类型
      • 1>类型分类
      • 2>类型归属
    • 2、内存分配
      • 1>内存
      • 2>分配
    • 3、局部变量
      • 1>值类型与引用类型
      • 2>垃圾回收器
    • 4、成员变量
      • 1>值类型与引用类型
    • 5、应用
      • 1>比较
      • 2>赋值
      • 3>传参
    • 6、拆装箱
      • 1>装箱 box
      • 2>拆箱 unbox
    • 7、string
      • 1>特性
      • 2>常用方法
      • 3>StringBulid类
    • 8、枚举
      • 1>简单枚举
      • 2>标志枚举(Flags)
      • 3>按位运算符
      • 4>数据类型转换
    • 9、结构
      • 1>什么是结构
      • 2>定义结构体
        • 定义结构体
        • 构造函数
  • 二、值传递和引用传递

一、数据类型

C#学习笔记——数据类型篇_第1张图片

1、数据类型

1>类型分类

  • 通用类型系统CTS(Common Type System)是 .NET 框架中的一个组成部分,为所有面向 .NET 框架的语言定义了数据类型的规则;
  • 值类型:存储数据本身
  • 引用类型:存储数据的引用(内存地址)

2>类型归属

C#学习笔记——数据类型篇_第2张图片
C#学习笔记——数据类型篇_第3张图片

2、内存分配

1>内存

  • 是CPU与其他外部存储器交换数据的桥梁;
  • 用于存储正在执行的程序与数据,即数据必须加载到内存才能被CPU处理;
  • 通常开发人员表达的"内存"都是指内存条。

2>分配

  • 程序运行时,CLR将申请的内存空间从逻辑上进行划分;
  • 栈区:
    –空间小(1MB),读取速度快
    用于存储正在执行的方法,分配的空间兼做栈帧。栈帧中存储方法的参数以及变量等数据。方法执行完毕后,对应的栈帧将被清除。
  • 堆区:
    –空间大,读取速度慢
    用于存储引用类型的数据。

3、局部变量

  • 定义在方法内部的变量
  • 特点:
    –没有默认值,必须自行设定初始值,否则不能使用
    –方法被调用时,存在栈中,方法调用结束时从栈中清除

1>值类型与引用类型

  • 值类型:
    声明在栈中,数据存储在栈中

  • 引用类型:
    声明在栈中,数据存储在堆中,栈中存储该数据的引用

  • 理解:
    1、因为方法执行在栈中,所以在方法中声明的变量都在栈中
    2、因为值类型直接存储数据,所以数据存储在栈中
    3、又因为引用类型存储数据的引用,所以数据在堆中,栈中存储数据的内存地址

2>垃圾回收器

  • GC(Garbage Collection)是CLR中一种针对托管堆自动回收释放内存的服务;
  • GC线程从栈中的引用开始跟踪,从而判定哪些内存是正在使用的,若GC无法跟踪到某一块堆内存,那么认为这块内存不再使用,即为可回收的。

4、成员变量

  • 定义在类中方法外的变量
  • 特点:
    –具有默认值
    –所在类被实例化后,存在堆中,对象被回收时,成员变量从堆中清除
    –可以与局部变量重名

1>值类型与引用类型

  • 值类型:
    声明在中,数据存储在
  • 引用类型:
    声明在中,数据存储在的另一块空间

5、应用

1>比较

		static void Main()
        {

            int num01 = 1, num02 = 1;
            bool r1 = num01 == num02;//true,因为值类型存储的是数据,所以比较的时数据

            int[] arr01 = new int[] { 1 }, arr02 = new int[] { 1 };
            bool r2 = arr01 == arr02;//false,因为引用类型存储的时数据的引用,所以比较的是存储的引用
            bool r3 = arr01[0]== arr02[0];//ture
        }

2>赋值

		static void Main()
        {
            // 值类型                                   引用类型
            //int bool char                        string  Array

            //a在栈中   1在栈中
            int a = 1;
            int b = a;
            a = 2;
            Console.WriteLine(b);//?

            //arr在栈中存储数组对象的引用(内存地址)   1在堆中
            int[] arr = new int[] { 1 };
            int[] arr2 = arr;
            //arr2[0] = 2;//修改的是堆中的数据
            arr = new int[]{2};//修改的是栈中存储的引用
            Console.WriteLine(arr2[0]);//?

            string s1 = "男";
            string s2 = s1;
            s1 = "女";//修改的是栈中存储的引用
            //s1[0]='女';//堆中的文字  不能修改
            Console.WriteLine(s2);//?

            // o1 在栈中   数据1 在堆中
            object o1 = 1;
            object o2 = o1;//o2得到的是数据1 的地址
            o1 = 2;//修改的是栈中o1存储的引用
            Console.WriteLine(o2);//?

        }

C#学习笔记——数据类型篇_第4张图片

C#学习笔记——数据类型篇_第5张图片
C#学习笔记——数据类型篇_第6张图片
C#学习笔记——数据类型篇_第7张图片

3>传参

		static void Main()
        {
            int a = 1;
            int[] arr = new int[] { 1 };
            Fun1(a,arr);//实参将 数据1/数组引用  赋值给形参
            Console.WriteLine(a);
            Console.WriteLine(arr[0]);
        }

        private static void Fun1(int a,int[] arr)
        {
            a = 2;
            //arr[0] = 2;
            arr = new int[] { 2 };
        }

C#学习笔记——数据类型篇_第8张图片
C#学习笔记——数据类型篇_第9张图片
C#学习笔记——数据类型篇_第10张图片

6、拆装箱

1>装箱 box

  • 值类型隐式转换为object类型(值类型转换为引用类型)或由此值类型实现的任何接口类型的过程;
  • 内部机制:
    1.在堆中开辟内存地址
    2.将值类型的数据复制到堆中
    3.返回堆中新分配对象的地址C#学习笔记——数据类型篇_第11张图片

2>拆箱 unbox

  • 从 object 类型到值类型(引用类型转换为值类型)或从接口类型到实现该接口的值类型的显式转换;
  • 内部机制:
    1.判断给定类型是否是装箱时的类型
    2.返回已装箱实例中属于原值类型字段的地址

C#学习笔记——数据类型篇_第12张图片

        static void Main()
        {
            int a = 1;
            //int b = a;

            //装箱操作:"比较"消耗性能("最")
            object o = a;

            //拆箱操作:"比较"消耗性能
            int b = (int)o;

			int num = 100;
            string str01 = num.ToString();//没装

            string str02 = "" + num;//装箱
            //string str02 = string.Concat("",num)  //int ==>object

        }
        //形参object类型,实参传递值类型,则装箱
        //可以通过 重载、泛型避免。
  • 注意:
    ---- 发生装箱和拆箱:两种类型必须存在继承关系

7、string

  • 所有的数据类型都能通过Tostring()转换成string类型

1>特性

  • 字符串常量具备字符串池特性
    1.字符串常量在创建前,首先在字符串池中查找是否存在相同文本。如果存在,则直接返回该对象引用;如果不存在,则开辟空间存储。
    2.作用:提高内存利用率
  • 字符串具有不可变性
    字符串常量一旦具有内存,就不得再次改变。因为如果在原位置改变会使其他对象内存被破坏,导致内存泄漏 。当遇到字符串变量引用新值时,会在内存中新建一个字符串,将该字符串地址交由该变量引用。
        static void Main()
        {
            string s1 = "八戒";
            string s2 = "八戒";//同一个字符串

            string s3 = new string(new char[] { '八', '戒' });//将字符数组转换为字符串
            string s4 = new string(new char[] { '八', '戒' });

            bool r1 = object.ReferenceEquals(s3, s4);

            //字符串池
            //字符串的不可变性

            //重新开辟空间 存储新字符串,再替换栈中引用
            s1 = "悟空";
            //将文本“八戒”改为“悟空”
            Console.WriteLine(s1);

            //每次修改,都是重新开辟新的空间存储数据,替换栈中引用
            object o1 = 1;
            o1 = 2.0;
            o1 = "ok";
            o1 = true;

            //创建一个计时器,用来记录程序运行的时间
            Stopwatch sw = new StopWatch();
            string strNumber = "";
            for (int i = 0; i < 10; i++)
            {
                //         ""  + "0"
                //         "0"+ "1"  每次拼接产生新的对象  替换引用 (原有的数据产生1次垃圾)
                strNumber = strNumber + i.ToString();
            }
            sw.Stop();//计时结束
            Console.WriteLine(sw.Elapsed);

            //可变字符串  一次开辟可以容纳10个字符大小的空间
            //优点:可以在原有空间修改字符串,避免产生垃圾
            //适用性:频繁对字符串操作(增加 替换 移除)
            StringBuilder builder = new StringBuilder(10);
            for (int i = 0; i < 10; i++)
            {
                builder.Append(i);
            }
            builder.Append("是哒");
            string result = builder.ToString();

            builder.Insert(0,"builder");
            //builder.Replace();
            //builder.Remove();

            s1 = s1.Insert(0, "s1");
        }

C#学习笔记——数据类型篇_第13张图片

2>常用方法

  • ToCharArray(将字符串转为字符数组)
  • Insert(在指定索引位置插入字符串)
  • Contains(检查字符串中是否包含某个字符)
  • ToLower(将字符串全部转为小写字母)
  • ToUpper(将字符串全部转为大写字母)
  • IndexOf(返回某个字符第一次出现的索引)-----LastIndexOf(返回某个字符串最后一次出现的位置)
  • Substring(从指定索引开始复制一个子字符串)
  • Trim(移除字符串两侧的空白字符或其他预定义字符)-----TrimEnd、TrimStart(移除最后、移除最前的空白字或其他预定义字符)
  • Split(以某些字符为分隔符将字符串分割成字符串数组)
  • Replace(替换字符串中的某个字符)
  • Join(将字符串数组中的字符串进行拼接,中间以某个字符分隔开)
  • new string(char[] 字符数组名)(能够将字符数组转换为字符串)
  • Length(获得当前字符串中字符的个数)
  • 字符串1.Equals(字符串2,StringComparison.OrdinalIgnoreCase)(忽略大小写,比较两个字符串是否相等)
  • StartsWith(判断字符串是否以子字符串开始)-----EndsWith(判断字符串是否以子字符串结束)
  • Compare(比较两个字符串)
  • Copy(创建一个与指定String具有相同值得String的新实例)
  • PadLeft(右对齐此实例中的字符,在左边用空格或指定的字符填充以达到指定的总长度) PadRight(左对齐,右边填充)
  • Remove(删除指定个数的字符)
    C#学习笔记——数据类型篇_第14张图片
    C#学习笔记——数据类型篇_第15张图片
    C#学习笔记——数据类型篇_第16张图片
    C#学习笔记——数据类型篇_第17张图片
    具体实现可在菜鸟教程中找到: 传送门.
		static void Main()
        {
            string s1 = " sda Hisc as ";
            char[] s2 = s1.ToCharArray(0, 5);
            foreach (var item in s2)
            {
                Console.WriteLine(item);
            }

            string s3 = s1.Insert(0, "abs");

            bool r1 = s1.Contains('d');

            string s4 = s1.ToLower();

            string s5 = s1.ToUpper();

            int index = s1.IndexOf('i');

            string s6 = s1.Substring(5);

            string s7 = s1.Trim();

            string[] s8 = s1.Split(new char[] { 'a' });

            string s9 = s1.Replace('a', 'b');

            string s10 = string.Join(' ', new string[] { "dsa", "ewq", "sxc" });
        }
  • 额外方法----ReferenceEquals: 用于比较两者是否具有相同的引用,它对于值类型对象的比较永远返回false;对于两个null的比较永远返回true。

3>StringBulid类

  • 与String类不同,当修改StringBuilder类的实例时,修改的是字符串本身,而不是它的一个复制。
    C#学习笔记——数据类型篇_第18张图片
  • C#字符串的处理 String和StringBuilder: 传送门.

8、枚举

  • 将枚举声明到命名空间的下面,类的外面,表示这个命名空间下,所有的类都可以使用这个枚举
  • 我们可以将一个枚举类型的变量跟int类型和string类型相互转换
    ---- 枚举类型默认是跟int类型相互兼容的,所以可以通过强制类型转换的语法进行转换。当转换一个枚举中没有的值时,不会抛出异常,而是直接将数字显示出来。
    ---- 枚举同样也可以跟string类型相互转换,如果将枚举类型转化成string类型,则直接调用Tostring()。如果将字符串转换成枚举类型则需要下面这样一行代码:
               ~~~~~~~~~~           (需要转换的枚举类型)Enum.Parse(typeof(要转换的枚举类型),“要转换的字符串”);
    如果转换的字符串是数字,则就算枚举值没有,也不会抛出异常。如果转换的字符串是文本,如果枚举中没有,则会抛出异常。

1>简单枚举

  • 列举某种数据的所有取值
  • 作用:增强代码的可读性,限定取值
  • 语法:enum 名字{值1、值2、值3、值4……}
  • 枚举元素默认为int,准许使用枚举类型有byte、sbyte、short、ushort、int、uint、long或ulong
  • 每个枚举元素都有枚举值。默认情况下,第一个枚举的值为0,后面每个枚举的值依次递增1,可以修改值,后面枚举的值依次递增

2>标志枚举(Flags)

  • 当想要选择多个枚举值时,可采用标志枚举
  • 指明[Flags]特性修饰,以指示可以将枚举作为位域(即一组标志)处理
  • 任意多个枚举值做 |(位或) 运算的结果不能与其他枚举值相同,即枚举中各标志的值应该是以 2 的幂来赋值,即:1、2、4、8、16、32……
namespace Day07
{
    [Flags] //修饰符
    enum PersonStyle
    {
        //普通枚举
        //tall=0,                                //0000000000
        //rich=1,                                //0000000001
        //handsome =2,                           //0000000010
        //white =3,                              //0000000011
        //beauty = 4                             //0000000100

        //标志枚举
        tall = 1,                                //0000000001
        rich = 2,                                //0000000010
        handsome = 4,                            //0000000100
        white = 8,                               //0000001000
        beauty = 16                              //0000010000
    }


    /*
     选择多个枚举值
    运算符  |(按位或):两个对应的二进制位中有一个为1,结果位为1
    tall  |  rich  ==>   0000000000 | 0000000001 ==>  0000000001

    条件:
    1.任意多个枚举值做 | 运算 的 结果不能与其他枚举值相同(值以2的N次方递增)
    2.定义枚举时,使用[Flags]特性修饰

    判断标志枚举 是否包含指定枚举值
    运算符  &(按位与):两个对应的二进制位中都为1,结果位为1
    0000000011 & 0000000001  ==> 0000000001    
     */
}

3>按位运算符

  • 运算符 |(按位或):两个对应的二进制为中有一个为1,结果位为1
    tall | rich ==> 0000000000 | 0000000001 ==> 0000000001
  • 运算符 &(按位与):两个对应的二进制为中都为1,结果位为1
    0000000011 & 0000000001 ==> 0000000001

4>数据类型转换

static void Main(string[] args)
 {
     //PrintPresonStyle(PersonStyle.tall);
     //00000000001 | 0000010000 ==>  0000010001
     //PrintPresonStyle(PersonStyle.tall | PersonStyle.beauty);

     //数据类型转换
     //int ==> Enum
     PersonStyle style01 = (PersonStyle)2;
     //PrintPresonStyle((PersonStyle)2);

     //Enum ==> int
     int enumNumber =(int) (PersonStyle.beauty | PersonStyle.handsome);

     //string ==> Enum
     //"beauty"

     PersonStyle style02 =(PersonStyle) Enum.Parse(typeof(PersonStyle), "beauty");

     // Enum ==> string
     string strEnum = PersonStyle.handsome.ToString();

 }

private static void PrintPresonStyle(PersonStyle style)
 {
     if((style & PersonStyle.tall)==PersonStyle.tall)
         Console.WriteLine("大个子");
    else if ((style & PersonStyle.rich)== PersonStyle.rich)
         Console.WriteLine("土豪");
    else if ((style & PersonStyle.handsome) != 0)
         Console.WriteLine("靓仔");
    else if ((style & PersonStyle.white)== PersonStyle.white)
         Console.WriteLine("白净");
    else if ((style & PersonStyle.beauty) != 0)
         Console.WriteLine("美女");
 }

9、结构

  • 可以帮助我们一次性声明多个不同类型的变量
  • 语法:
    [public] struct 结构名
    {
          ~~~~~      成员;//字段
    }
    变量在程序运行期间只能存储一个值,而字段可以存储多个值。(为了区别于变量,字段前一般都加下划线)

1>什么是结构

  • 定义:用于封装小型相关变量的值类型。与类语法相似,都可以包含数据成员和方法成员。但结构属于值类型,类属于引用类型
  • 适用性:
    表示点、颜色等轻量级对象,如创建存储1000个点的数组,如果使用类,将为每个对象分配更多内存,使用结构可以节约资源

2>定义结构体

定义结构体
  • 使用struct关键字定义
  • 除非字段被声明为 const 或 static ,否则无法初始化
  • 结构不能被继承,但可以实现接口
  private static int a = 1;
  private const int b = 1;
  private int rIndex = 0;//报错:结构中不能有实例字段初始化设定项
构造函数
  • 结构总会包含无参数构造函数
  • 构造函数中必须初始化所有字段
struct  Direction
{
	private int rIndex;
	
	public int RIndex
	{
	    get
	    {
	        return this.rIndex;
	    }
	    set
	    { 
	        this.rIndex = value; 
	    }
	}
	
	public int CIndex { get; set; }
	
	//因为结构体自带无参数构造函数,所以不能包含无参数构造函数
	public Direction()//报错:结构不能包含显式的无参数构造函数
	{
	}
	
	public Direction (int rIndex,int cIndex):this()//没有:this()好像也没报错,可能是高版本的原因
	{//构造函数中,必须先为所有字段赋值
	    //:this()  有参数构造函数 先调用无参数构造函数,为自动属性的字段赋值
	    this.rIndex = rIndex;
	    this.CIndex = cIndex;
	}
}

二、值传递和引用传递

  • 值传递在复制的时候,传递的是这个值的本身
  • 引用类型在复制的时候,传递的是对这个对象的引用

你可能感兴趣的:(C#,unity,c#)