首页 > 代码库 > 15-01-11 C# 面向对象 10

15-01-11 C# 面向对象 10

类中的成员,如果不加访问修饰符,默认是private;

私有的,只能在当前类的内部访问;

理论上是给每个私有的字段配备一个公有的属性;

静态函数中只能访问静态成员;静态类中只能有静态成员;非静态函数既可以访问静态成员,也可以访问非静态成员,非静态类中既可以有静态成员,也可以有非静态成员;

this的作用1.当前类的对象,是为了把字段(属性)和局部变量区分开来;字段和属性是和方法平级的,而局部变量是在方法里面的;

namespace(命名空间),用于解决类重名问题,可以看做类的文件夹;

命名空间里面有很多很类;命名空间其实就是相当于项目;

如果我们要使用一个新类,但是这个新类不存在上面的命名空间里,这个时候我们首先要引用这个类的命名空间;

类是属于命名空间(项目)的,如果没有引用相应的命名空间的话,我们就不能使用这个类;

如何引用类的命名空间,

1.把光标放到类的名称下,会看到左下角有个蓝色的小块块,光标移过来点开,就可以引用命名空间

2.把光标放到类的名称下,alt+shift+F10;

3.把命名空间记住,直接在上面打出来;

如果在自己的两个项目中,我要在02项目中引用01项目,

第一步,添加引用;在02的项目文件的引用里,右键,添加引用,默认选的是项目,选中引用的项目,点确定,这个时候发现在02项目的引用中有了01项目;(系统自带的少了这一步)

第二步,在上面引用命名空间,

在C#中数据类型确切的分为值类型和引用类型, 区别 1.值类型和引用类型,在内存上存储的地方不一样,2.在传递值类型和传递引用类型的时候,传递的方式不一样,值类型我们称之为值传递,引用类型我们称之为引用传递

在内存中我们人为了得分成了5块,我们程序员常用的就是堆,栈,静态存储区域,静态存储区域用来存储静态成员,堆栈就是用来存储我们这些数据类型的数据

值类型:int,double,bool,char,decimal,struct,enum

引用类型:string,自定义类,数组

值类型的值是存储在内存中的栈上的,引用类型的值是存储在内存的堆中,

int number = 10; 10存在栈中,栈内存空间的名字是number 

string s = "123"; "123"存在堆中,s在栈中,但是名字为s的栈空间中,存的是"123"在堆中的地址;

Person p = new Person(); p.Name = "张三"; new Person()存在堆中,new Person()才是一个对象,p在栈中,但是名字为p的栈空间中,存的是new Person()在堆中的地址;Name = "张三"也在堆中

值类型的值是存在内存中的栈中,引用类型的值存在内存中的堆中,但是引用类型也在栈上开辟了空间,存的是这个值在堆中的引用(地址);

字符串区别于别的类型有一个很特别的性质,叫

1.字符串的不可变性, int n1 = 10; ni = 20;    在堆中的10变成了20;老值10,不存在了; string s1 = "张三"; s1 = "李四"; 当你给字符串重新赋值之后,老值"张三"并没有销毁,而是重新开辟一块堆空间存储新值;此时s1在栈中的地址由原来的"张三"的地址变成了"李四"的地址;此时张三还在内存中 因此对字符串循环赋值的话,会在堆内存中产生大量的垃圾; 当程序结束后,GC扫描整个内存,如果发现有的空间没有被指向,则立即把它销毁;

string s1 = "张三"; string s2 = "张三";    理论上在堆中有2块空间存储"张三",在栈中有两块空间s1,s2分别指向各自的"张三";但事实不是这样的,事实上在堆中只开辟了一块空间,叫做"张三" ,s1,s2同时都指向张三;s1,s2在堆中的地址是一样的,在这时候s1="123" 原来的"张三"是不会变的,因为字符串的不可变性,因此会在堆空间中重新开辟一块空间存储"123";

如何看变量在内存中的地址,生成运行,调试菜单,窗口,即时里面,在即时窗口里面输入&变量名,回车就能看到;有两个地址,上面那个是栈中的地址,下面那个是堆中的地址

2.我们可以将字符串看做是char类型的只读数组; 一个string类型可以看做是很多很多char类型组成,很多很多的char类型可以看做是一个数组;

只读的意思是只可以访问,但不可以改变;

string s = "abcdefg";既然可以把s当做一个数组来处理,那就意味着可以通过索引来访问其中的某一个元素; Console.WriteLine(s[0]);  //成功输出a

如果要把第一个字符a改变为b;首先要想办法访问到第一个字符; s[0] = ‘b‘;//报错,因为string是char类型的只读数组;

如果非要将字符串的第一个字符变‘b‘;

1.首先将字符串转换为char类型的数组;把它转换为一个真正的数组,那么既可读,又可写了;

char[] chs = s.ToCharArray(); chs[0] = ‘b‘;

2.将字符数组转化为字符串;

s = new string(chs);

ToCharArray()将字符串转化为char[]数组; new String(char[] chs)能够将char[]转化为字符串;

当程序中要对字符串进行大量的重新赋值,拼接等操作的时候,如果用string,在堆内存中会产生大量的垃圾 因此我们可以用StringBuilder这个类;

StringBuilder sb = new StringBuilder();

记录程序运行的时间,我们呢可以用StopWatch这个类;

StopWatch sw = new StopWatch();//创建一个计时器;

sw.Start();  //开始计时;

sw.Stop(); //结束计时;

sw.Elapsed 获取测量所用的时间;

str += i;字符串的追加; sb.Append(i);追加;

为什么string追加速度这么慢,因为它每次都要在内存中开辟空间;stringbuilder没有开空间; 虽然中间用的是stringbuilder但最后还是把它转化string, sb.ToString();

一切类型都可以通过ToString()转化为字符串类型;

str.Length;  调用字符串的属性得到字符串的长度;

str.ToUpper();  将字符串转化为大写形式;

str.ToLower();  将字符串转化为小写形式;

str.Equals(str1,StringComparison.OrdnialIgnoreCase); 忽略大小写的比较两个字符串;

char[] chs = {‘_‘,‘+‘,‘-‘,‘*‘,‘ ‘}; 

str.split(chs,StringSplitOptions.RemoveEmptyEntries);在str里把字符数组里有的字符给去掉,返回值是string[];

两个小黄块就是枚举类型

str.split(new char[]{‘_‘,‘+‘,‘-‘,‘*‘,‘ ‘},StringSplitOptions.RemoveEmptyEntries); //等效的;

str.Contains("老赵");判断字符串里有没有老赵;

str.Replace("老赵","**"); 将字符串中的老赵替换为**;

Substring和Split在字符串中经常会用到这两个方法; str.Substring(1,2); 1是要截取开始的索引,2是要截取的长度;

str.StartsWith("今天");判断字符串是不是以"今天"开始;

str.EndsWith("今天");判断字符串是不是以"今天"结尾;

str.IndexOf(‘天‘); 拿到‘天‘在字符串中首次出现的索引;返回值int; 如果找不到就返回-1;

str.IndexOf(‘天‘,2); 从第二个索引开始找天首次出现的索引,返回值int;  如果找不到就返回-1;

str.LastIndexOf(‘天‘);找天在字符串中最后一次出现的索引;

string path = @"c:\a\b\c........\射雕英雄传.wav";

int idndex = path.LastIndexOf("\\");

path = path.SubString(index+1);  //获取文件名;

str.Trim();//去掉字符串的前面和后面的空格

str.TrimStart();//去掉字符串的前面的空格

str.TrimEnd();//去掉字符串的后面的空格

string.IsNullOrEmpty(str);判断字符串是不是空值或者null;

string[] str ={"张三","李四","王五","赵六","田七"};

string strNew = string.join("|",str);  //得到张三|李四|王五|赵六|田七

string strNew = string.join("|","张三","李四","王五","赵六","田七");也可以这样写,因为前面有params可变参数;

string在运行(赋值,拼接)的时候会产生新的实例,而StringBuilder不会产生新的实例

File类是一个静态类;

File.ReadAllLines(path,Encoding.Default) 

//path是文件的路径;读取文件中的所有的行,返回类型是string[];Encoding.Default用来解决乱码;

要改变一个字符串,除了重新赋值之外,可以先把它变成一个字符数组,在通过循环或者调用方法改变字符数组,再把这个字符数组转化为字符串;

.net类库提供的字符串方法还是很强大的;

封装就是把一个功能写成一个方法调用,具体这个功能怎么实现的不必知道,就像调用.net类库提供的方法一样;

每写一个类应该新建一个类文件去写;

public class Student:Person

{  

    private int _id;   

    public int Id   

   {     

     get{return _id;}     

     set{_id = value;}   

    }       

    public void Study()   

   { 

     Console.WriteLine("学习");  

    } 

   //public Student(string name, int age,char gender,int id)   

   //{   

   //this.Name = name;   

   //this.Age = age;   

   //this.Gender = gender;   

   //this.Id = id;   

   //  }                                      

  //这些代码里的大部分在父类里已经写过了,如果用父类无参的构造函数,一行都省不了;因此要调用父类有参的构造函数                                          

  //这样一来,构造函数也执行了,对象也创建了,同时,三行代码也不需要写了。     

    public Student(string name, int age,char gender,int id)   //运行到这里的时候,跳到父类的构造函数;   

    :base(name,age,gender)   

    {       

      this.Id = id;   

    }                                                             

    public new  void Test()    

    {       

       Console.WriteLine("测试1");    

   }

}

      public class Teacher:Person

    {   

       public Teacher(string name,ing age,char gender,int salary):base(name,age,gender)    

       {       

         this.Salary =salary;   

       }

        private int _salary;  

        public int Salary   

      {     

        get{return _salary;}   

       set{_salary = value;}   

       }

       public void Teach()   

       {      

         Console.WriteLine("教学");

       }                       

         public  new void Test()    

        {      

          Console.WriteLine("测试2");

        }   

}

 

public class Driver:Person

 {   

      private string _driveTime;   

      public string DriveTime   

      {    

         get{return _driveTime;}    

         set{_driveTime = value;}

       }

       public void Drive()   

       {     

          Console.WriteLine("开车");   

        }        

       public Driver(string name,ing age,char gender,int driveTime)

      :base(name,age,gender)   

       {     

            this.DriveTime =driveTime;  

        }

         public  new void Test()  

       {      

            Console.WriteLine("测试3");   

       }  

      }

      构造函数可以解决对象初始化时候的代码冗余,

     :this可以解决构造函数重载的时候的冗余代码;

     继承是用来解决类与类之间的代码冗余;把几个类中共重复的成员单独的拿出来封装成一个类,作为这几个类的父类;

      public class Person

     {   

      private string _name;   

      public string Name   

     {     

       get{return _name;}     

       set{_name = value;}  

      }     

      private int _age;   

      public int Age   

     {     

       get{return _age;}     

       set{__age = value;}   

     }      

     private string _gender;   

     public string Gender   

     {     

        get{return _gender;}     

        set{_gender = value;}   

     }

   public void CHLSS()   

   {     

      Console.WriteLine("吃喝拉撒睡");   

    }

   public Person(string name,int age,char gender)   

    {       

      this.Name = name;       

      this.Age = age;       

       this.Gender = gender;  

     }       

     // public Person()   

    // {

    //   }

     public void Test()    

     {      

        Console.WriteLine("测试");  

      }

}

 智能提示的正方体是方法的意思;

子类既可以由父类的成员,也可以有自己的成员;

我们可能会在一些类中,写一些重复的成员,我们可以将一些重复的成员,单独的封装到一个类中,作为这些类的父类

Student,Teacher,Drive  子类(派生类)  

Person                        父类(基类)

我们看子类在父类那里继承过来了什么,就看子类对象能访问到父类的什么;能访问到的表示继承过来了;

上面那个例子,子类继承了父类的属性和方法;子类没有继承父类的私有字段;

1.继承的单根性,一个子类只能有一个父类;

2.继承的传递性,A继承B,B继承C   A不仅能访问B的成员,A也能访问C的成员;

查看类图:可以看出各个类的关系;右键项目名,视图,查看类图;

如果子类继承了父类的构造函数,那么创建子类对象的时候,应该会调用到父类的构造函数;

子类没有继承父类的构造函数;但是子类创建对象的时候,先会执行父类的构造函数,执行完父类的构造函数,再来执行自己的构造函数, 执行父类的构造函数就是创建了一个父类对象,

当我们创建子类对象的时候,首先会在子类的内部创建一个父类的对象出来,这样才能用到父类的属性和方法;不然子类对象里面没有父类的对象,怎么调用父类的属性和方法;

因此父类里写了一个有参的构造函数后,父类无参的构造函数就被干掉了,就不能创建父类的对象出来了(因为子类创建对象的时候,调用的是父类无参的构造函数); 如果父类中重新写了一个有参数的构造函数之后,那个无参的就被干掉了,子类就调用不到了,所以子类会报错,

解决办法

1.在父类中重新写一个无参数的构造函数; //一般不去这么做;

2.在子类中显示调用父类的构造函数,关键字:base()

ctrl+K+D代码对齐;

本来子类调用的是父类无参的构造函数,现在写了个有参的父类的构造函数,原来的无参的父类构造函数被干掉了,因此调不到原来的无参的构造函数了, 所以去调用有参的构造函数,使用关键字:base();

Student s = new Student("学生",18,‘男‘,101);

Object类是所有类的基类;如果你没有让一个类去继承另外一个类,那么这个类默认就继承于Object;

在C#中所有类都直接或者间接继承Object类;

如果子类的成员名称和父类的成员名称一样,会把从父类继承过来的一样名字的成员隐藏掉;隐藏的后果是调用不到父类的那个重名的成员了;  

public  new void Test()把父类中重名的成员隐藏掉了;加了new不报警告错误了;

new 1.创建对象 2.隐藏从父类那里继承过来的同名成员

隐藏的后果,子类调用不到父类的被隐藏的成员;

因此,在继承的时候,尽量不要把子类的成员名字写的和父类的成员名字一样;

Object也是引用类型,因为它是类;

 

15-01-11 C# 面向对象 10