首页 > 代码库 > 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