文章目录
3. 类和对象
3.1 面向对象简介
面向对象(Object-OrientedProgramming, OOP)是指在编程时将任何事物都看成是一个对象来描述。对象包括属性和方法,属性是指对象固有的特征,方法则是对象的行为。
面向对象语言的三大特征分别是封装、继承、多态。
- 封装:封装的好处就是能让用户只关心对象的用法而不用关心对象的实现,在为用户的访问提供了便利的同时也提高了程序的安全性。
- 继承:继承关系主要体现在类之间的继承,这样既能减少开发时的代码量又方便了程序的复用。
- 多态:多态是通过类的继承或接口的实现来体现的,多态给程序带来的最大好处与继承类似,即提高了程序的复用性和可移植性。
3.2 类的定义(class)
类包含成员,成员可以是静态或实例成员,静态成员属于类,实例成员属于对象。静态成员使用static修饰符声明。
class
是定义类的关键字。
类的访问修饰符 修饰符 类名
{
类的成员
}
- 类的访问修饰符:用于设定对类的访问限制,包括
public
、internal
或者不写。用internal
或者不写时代表只能在当前项目中访问类;public
则代表可以在任何项目中访问类。 - 修饰符:修饰符是对类本身特点的描述,包括
abstract
、sealed
和static
。abstract
是抽象的意思,使用它修饰符的类不能被实例化;sealed
修饰的类是密封类,不能被继承;static
修饰的类是静态类,不能被实例化。 - 类名:类名用于描述类的功能,因此在定义类名时最好是具有实际意义,这样方便用户理解类中描述的内容。在同一个命名空间下类名必须是唯一的。
- 类的成员:在类中能定义的元素,主要包括字段、属性、方法。
3.3 访问修饰符、修饰符
类中的成员包括字段、属性、方法。每个类成员在定义时需要指定访问修饰符、修饰符。
类中成员的访问修饰符有 4 个,具体用法如下。
- public:成员可以被任何代码访问。
- private:成员仅能被同一个类中的代码访问,如果在类成员前未使用任何访问修饰 符,则默认为private。
- internal:成员仅能被同一个项目中的代码访问。
- protected:成员只能由类或派生类中的代码访问。
字段的定义与前面介绍的变量和常量的定义类似,只是在变量或常量前面可以加上访问修饰符、修饰符。在修饰字段时通常用两个修饰符,即readonly
(只读)和static
(静态的)。
访问修饰符 修饰符 数据类型 字段名;
- 使用
readonly
修饰字段意味着只能读取该字段的值而不能给字段赋值。 - 使用
static
修饰的字段是静态字段,可以直接通过类名访问该字段。常量不能使用 static 修饰符修饰。
在这里访问修饰符和修饰符都是可以省略的,并且访问修饰符和修饰符的位置也可以互换,但从编码习惯上来说通常将访问修饰符放到修饰符的前面。此外,在类中定义字段时字段名是唯一的。
class Student
{
private int id; //定义私有的整型字段id
public readonly string name; //定义公有的只读字符串类型字段name
internal static int age; //定义内部的静态的整型字段age
private const string major = "计算机"; //定义私有的字符串类型常量major
}
字段在类中定义完成后,在类加载时,会自动为字段赋值,不同数据类型的字段默认值不同, 如下表所示。
数据类型 | 默认值 |
---|---|
整数类型 | 0 |
浮点型 | 0 |
字符串类型 | 空值 |
字符型 | a |
布尔型 | False |
其他引用类型 | 空值 |
3.3 方法的定义
访问修饰符 修饰符 返回值类型 方法名(参数列表)
{
语句块;
}
- 访问修饰符:
所有类成员访问修饰符都可以使用,如果省略访问修饰符,默认是 private。
- 修饰符:
在定义方法时修饰符包括virtual(虚拟的)、abstract(抽象的)、override(重写的)、static(静态的)、sealed(密封的)。override是在类之间继承时使用的。
- 返回值类型:
用于在调用方法后得到返回结果,返回值可以是任意的数据类型,如果指定了返回值类型,必须使用 return 关键字返回一个与之类型匹配的值。如果没有指定返回值类型,必须使用 void 关键字表示没有返回值。
- 方法名:
对方法所实现功能的描述。方法名的命名是以 Pascal 命名法为规范的。
- 参数列表:
在方法中允许有 0 到多个参数,如果没有指定参数也要保留参数列表的小括号。参数的定义形式是数据类型 参数名
,如果使用多个参数,多个参数之间需要用逗号隔开。
using System;
class Compute
{
//加法
public double Add(double num1, double num2)
{
return num1 + num2;
}
//减法
public double Minus(double num1, double num2)
{
return num1 - num2;
}
//乘法
public double Multiply(double num1, double num2)
{
return num1 * num2;
}
//除法
public double Divide(double num1, double num2)
{
return num1 / num2;
}
}
class Program
{
static void Main(string[] args)
{
Compute c = new Compute();
double ans = c.Add(1, 2);
Console.WriteLine("计算结果为:" + ans);
}
}
// 计算结果为:3
命名参数:变量名:值
,目的是使调用更加清晰,可以调换参数位置而不影响结果。
可选参数:方法参数可以是可选的,但必须提供默认值。
// 定义方法
public void TestMethod(int n , int opt1 = 11, int opt2=22; int opt3 = 33){}
// 调用方法
TestMethod(1, opt3:4 );
在上面的这个例子中,n的取值为1,opt1和opt2的取值为默认值,opt3的取值为4。
3.4 get和set访问器
属性经常与字段连用,并提供了get
访问器和set
访问器,分别用于获取或设置字段的值。字段最好把声明为private,使用属性来访问字段。
public 数据类型 属性名
{
get
{
获取属性的语句块;
return 值;
}
set
{
设置属性得到语句块;
}
}
- get{}:用于获取属性的值,使用return关键字返回一个与属性数据类型相兼容的值。如果省略该访问器,则不能在其他类中获取私有类型的字段值,因此也称为只写属性。
- set{}:用于设置字段的值,这里需要使用一个特殊的值
value
,用它给字段赋值。如果省略该访问器,无法在其他类中给字段赋值,因此也称为只读属性。
C#允许给属性的get和set访问器设置不同的访问修饰符
,所以属性可以有公有的get访问器和私有的或受保护的set访问器。
using System;
class Book
{
private int id;
private string name="C#学习教程";
private double price;
//设置图书编号属性
public int Id
{
get
{
return id;
}
set
{
id = value;
}
}
//设置图书名称属性
public string Name
{
get
{
return name;
}
}
//设置图书价格属性
public double Price
{
get
{
return price;
}
set
{
if (value >= 0)
{
price = value;
}
else
{
// 不符合实际情况的取值统一设为0
price = 0;
}
}
}
public void ouput()
{
Console.WriteLine("Id:" + Id);
Console.WriteLine("Name:" + Name);
Console.WriteLine("Price:" + Price);
}
}
class Program
{
static void Main(string[] args)
{
Book book = new Book();
book.Id = 1;
book.Price = -3;
book.ouput();
Console.WriteLine("**************************");
book.Price = 50;
book.ouput();
}
}
在上面的例子中,我们第一次设置价格为负数,但是不符合实际情况,故最后取值为0。
在C#中,我们还可以简化属性的定义。使用该方式创建的属性,就不能在属性设置中验证属性的有效性
。
public 数据类型 属性名{get;set;}
简化后图书类中的属性设置的代码如下。
public int Id{get; set;}
public string Name{get; set;}
public double Price{get; set;}
3.5 调用类成员
创建类实例。
类对象名 = new 类名();
调用类成员。
对象名.类的成员
using System;
class Book
{
public int Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public void PrintMsg()
{
Console.WriteLine("图书编号:" + Id);
Console.WriteLine("图书名称:" + Name);
Console.WriteLine("图书价格:" + Price);
}
}
class Program
{
static void Main(string[] args)
{
Book book = new Book();
//为属性赋值
book.Id = 1;
book.Name = "C#语言学习";
book.Price = 50;
book.PrintMsg();
}
}
3.6 构造函数和析构函数
3.6.1 构造函数
类名 对象名 = new 类名()
,在实例化类对象时,类名()
的形式调用的是类的构造方法,构造方法的名字是与类的名称相同的。
访问修饰符 类名 (参数列表)
{
语句块;
}
3.6.2 析构函数
构造函数是在创建类的对象时执行的,而析构函数则是在垃圾回收、释放资源时使用的。
~类名()
{
语句块;
}
在析构方法中不带任何参数,它实际上是保证在程序中会调用垃圾回收方法Finalize()。
3.6.3 例子
using System;
class User
{
// 构造函数
public User(string name, string password, string tel)
{
// this关键字
this.Name = name;
this.Password = password;
this.Tel = tel;
Console.WriteLine("调用了构造方法");
}
public string Name { get; set; }
public string Password { get; set; }
public string Tel { get; set; }
public void PrintMsg()
{
Console.WriteLine("用户名:" + this.Name);
Console.WriteLine("密 码:" + this.Password);
Console.WriteLine("手机号:" + this.Tel);
}
// 析构函数
~User()
{
Console.WriteLine("调用了析构方法");
}
}
class Program
{
static void Main(string[] args)
{
User user = new User("小明","123456","13131351111");
user.PrintMsg();
}
}
3.7 方法重载
函数方法名称相同,参数列表不同。参数列表不同主要体现在参数个数或参数的数据类型不同。在调用重载的方法时是根据所传递参数的不同判断调用的是哪个方法。
using System;
class SayHello
{
// 构造函数重载
public SayHello()
{
Console.WriteLine("Welcome");
}
public SayHello(string name)
{
Console.WriteLine("Welcome " + name);
}
public SayHello(string name, int age)
{
Console.WriteLine("Welcome " + name + "," + age);
}
}
class Program
{
static void Main(string[] args)
{
SayHello say1 = new SayHello();
SayHello say2 = new SayHello("小明");
SayHello say3 = new SayHello("张三", 20);
}
}
3.8 方法的参数
方法中的参数分为实际参数和形式参数,实际参数被称为实参,是在调用方法时传递的参数;形式参数被称为形参,是在方法定义中所写的参数。
方法中的参数除了定义数据类型外,还可以定义引用参数和输出参数。引用参数使用ref
关键字定义,输出参数使用out
关键字定义。
引用参数在方法中使用时必须为其值,并且必须是由变量赋予的值,不能是常量或表达式。
using System;
class RefClass
{
public bool Judge(ref int num)
{
if (num % 5 == 0)
{
return true;
}
return false;
}
}
class Program
{
static void Main(string[] args)
{
RefClass refClass = new RefClass();
int a = 20;
bool result = refClass.Judge(ref a);
Console.WriteLine("验证结果是:" + result);
}
}
// 验证结果是:True
输出参数相当于返回值,即在方法调用完成后可以将返回的结果存放到输出参数中。需要注意的是,在使用输出参数时,必须在方法调用完成前为输出参数赋值。输出参数多用于一个方法需要返回多个值的情况。
using System;
class OutClass
{
public void Judge(int num, out bool result)
{
if (num % 5 == 0)
{
result = true;
}
else
{
result = false;
}
}
}
class Program
{
static void Main(string[] args)
{
OutClass outClass = new OutClass();
// 输出参数rs
bool rs;
outClass.Judge(20, out rs);
Console.WriteLine("验证结果是:" + rs);
}
}
// 验证结果是:True
3.9 lambda表达式
Lambda表达式给编写程序带来了很多的便利
访问修饰符 修饰符 返回值类型 方法名(参数列表) => 表达式;
如果在方法定义中定义了返回值类型,在表达式中不必使用 return 关键字,只需要计算值即可。这种形式只能用在方法中只有一条语句的情况下,方便方法的书写。
using System;
class LambdaClass
{
// 计算结果,然后返回
public static int Add1(int a, int b) => a + b;
// 计算结果并输出,()不能少,不然就当作是字符串拼接
public static void Add2(int a, int b) => Console.WriteLine("计算结果为"+ (a + b));
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("计算结果为" + LambdaClass.Add1(100, 200));
LambdaClass.Add2(100, 200);
}
}
// 计算结果为300
// 计算结果为300
3.10 部分类
一个类可以由多个部分类构成,定义部分类的语法形式如下。
访问修饰符 修饰符 partial class 类名{……}
using System;
class test
{
public partial class Course
{
public int Id { get; set; }
public string Name { get; set; }
public double Points { get; set; }
}
public partial class Course
{
public void PrintCoures()
{
Console.WriteLine("课程编号:" + Id);
Console.WriteLine("课程名称:" + Name);
Console.WriteLine("课程学分:" + Points);
}
}
static void Main(string[] args)
{
Course course = new Course();
course.Id = 1001;
course.Name = "C#部分类";
course.Points = 3;
course.PrintCoures();
}
}
// 课程编号:1001
// 课程名称:C#部分类
// 课程学分:3
3.11 String类
字符串是比较常用的一种数据类型,是引用类型的一种,对象被分配在堆上。
注意:C#中字符串是不可改变的,修改一个字符串,就会创建一个全新的string对象。
// 字符串使用反斜杠\进行转义
string filepath=“c:\\temp\\test.cs”
// 加@符号不会解释转义字符
string filepath=@“c:\temp\test.cs”
// 使用$标记字符串插值,用于格式化输出
string s1=“this is a string”;
WriteLine($“s1 = {s1}”);
属性或方法名 | 作用 |
---|---|
Length | 获取字符串的长度,即字符串中字符的个数 |
IndexOf | 返回整数,得到指定的字符串在原字符串中第一次出现的位置,不存在则返回-1 |
LastlndexOf | 返回整数,得到指定的字符串在原字符串中最后一次出现的位置,不存在则返回-1 |
Starts With | 返回布尔型的值,判断某个字符串是否以指定的字符串开头 |
EndsWith | 返回布尔型的值,判断某个字符串是否以指定的字符串结尾 |
ToLower | 返回一个新的字符串,将字符串中的大写字母转换成小写字母 |
ToUpper | 返回一个新的字符串,将字符串中的小写字母转换成大写字母 |
Trim | 返回一个新的字符串,不带任何参数时表示将原字符串中前后的空格删除。 参数为字符数组时表示将原字符串中含有的字符数组中的字符删除 |
Remove | 返回一个新的字符串,将字符串中指定位置的字符串移除 |
TrimStart | 返回一个新的字符串,将字符串中左侧的空格删除 |
TrimEnd | 返回一个新的字符串,将字符串中右侧的空格删除 |
PadLeft | 返回一个新的字符串,从字符串的左侧填充空格达到指定的字符串长度 |
PadRight | 返回一个新的字符串,从字符串的右侧填充空格达到指定的字符串长度 |
Split | 返回一个字符串类型的数组,根据指定的字符数组或者字符串数组中的字符 或字符串作为条件拆分字符串 |
Replace | 返回一个新的字符串,用于将指定字符串替换给原字符串中指定的字符串 |
Substring | 返回一个新的字符串,用于截取指定的字符串 |
Insert | 返回一个新的字符串,将一个字符串插入到另一个字符串中指定索引的位置 |
Concat | 返回一个新的字符串,将多个字符串合并成一个字符串 |
例子如下。
using System;
class Program
{
static void Main(string[] args)
{
Console.Write("请输入字符串:");
string str = Console.ReadLine();
Console.WriteLine("字符串的长度为:" + str.Length);
if (str.IndexOf("@") != -1)
{
Console.WriteLine("字符串中含有@,其出现的位置是{0}", str.IndexOf("@") + 1);
// 将原字符串中的@替换为@@
str = str.Replace("@", "@@");
Console.WriteLine("替换后的字符串为:" + str);
}
else
{
Console.WriteLine("字符串中不含有@");
}
str = str.ToUpper();
Console.WriteLine("小写字母转换成大写字母:" + str);
}
}
3.12 Console类
Console类主要用于控制台输入输出。
方法 | 描述 |
---|---|
Write | 向控制台输出内容后不换行 |
WriteLine | 向控制台输出内容后换行 |
Read | 从控制台上读取一个字符 |
ReadLine | 从控制台上读取一行字符 |
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("请输入学生姓名:");
string name = Console.ReadLine();
Console.WriteLine("请输入所在学校:");
string school = Console.ReadLine();
// Console.Write(格式化字符串, 输出项, 输出项2);
// 格式化字符串中使用{索引号}的形式,索引号从 0 开始。输出项1填充{0}位置的内容,依此类推。
Console.WriteLine("{0}同学在{1}学习", name, school);
}
}
3.13 Math类
Math类主要用于一些与数学相关的计算。
方法 | 描述 |
---|---|
Abs | 取绝对值 |
Ceiling | 返回大于或等于指定的双精度浮点数的最小整数值 |
Floor | 返回小于或等于指定的双精度浮点数的最大整数值 |
Equals | 返回指定的对象实例是否相等 |
Max | 返回两个数中较大数的值 |
Min | 返回两个数中较小数的值 |
Sqrt | 返回指定数字的平方根 |
Round | 返回四舍五入后的值 |
// 输出两个值中的最大值和最小值
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("请输入第一个数:");
double num1 = Double.Parse(Console.ReadLine());
Console.WriteLine("请输入第二个数:");
double num2 = Double.Parse(Console.ReadLine());
Console.WriteLine("两个数中较大的数为{0}", Math.Max(num1, num2));
Console.WriteLine("两个数中较小的数为{0}", Math.Min(num1, num2));
}
}
3.14 Random类
Random类是一个产生伪随机数字的类,它的构造函数有两种。
// 根据触发那刻的系统时间做为种子,来产生一个随机数字
New Random();
// 自定义触发的种子
New Random(Int32);
因此如果计算机运行速度很快,如果触发 Randm 函数间隔时间很短,就有可能造成产生一样的随机数,因为伪随机的数字,在 Random 的内部产生机制中还是有一定规律的,并非是真正意义上的完全随机。
方法 | 描述 |
---|---|
Next() | 每次产生一个不同的随机正整数 |
Next(int max Value) | 产生一个比 max Value 小的正整数 |
Next(int min Value,int max Value) | 产生一个 minValue~maxValue 的正整数,但不包含 maxValue |
NextDouble() | 产生一个0.0~1.0的浮点数 |
NextBytes(byte[] buffer) | 用随机数填充指定字节数的数组 |
using System;
class Program
{
static void Main(string[] args)
{
Random rd = new Random();
Console.WriteLine("产生一个10以内的数:{0}", rd.Next(0, 10));
Console.WriteLine("产生一个0到1之间的浮点数:{0}", rd.NextDouble());
// 字节数组
byte[] b = new byte[5];
rd.NextBytes(b);
Console.WriteLine("产生的byte类型的值为:");
foreach(byte i in b)
{
Console.Write(i + " ");
}
Console.WriteLine();
}
}
3.15 DateTime类
DateTime类用于表示时间,所表示的范围是从0001年1月1日0点到9999年12月31日24点。
DateTime类中有静态属性Now,获取当前的日期和时间可以用DateTime.Now
。
方法 | 描述 |
---|---|
Date | 获取实例的日期部分 |
Day | 获取该实例所表示的日期是一个月的第几天 |
DayOfWeek | 获取该实例所表示的日期是一周的星期几 |
DayOfYear | 获取该实例所表示的日期是一年的第几天 |
Add(Timespan value) | 在指定的日期实例上添加时间间隔值 value |
AddDays(double value) | 在指定的日期实例上添加指定天数 value |
AddHours(double value) | 在指定的日期实例上添加指定的小时数 value |
AddMinutes(double value) | 在指定的日期实例上添加指定的分钟数 value |
AddSeconds(double value) | 在指定的日期实例上添加指定的秒数 value |
AddMonths(int value) | 在指定的日期实例上添加指定的月份 value |
AddYears (int value) | 在指定的日期实例上添加指定的年份 value |
using System;
class Program
{
static void Main(string[] args)
{
DateTime dt = DateTime.Now;
Console.WriteLine("当前日期为:{0}", dt);
Console.WriteLine("当前时本月的第{0}天", dt.Day);
Console.WriteLine("当前是:{0}", dt.DayOfWeek);
Console.WriteLine("当前是本年度第{0}天", dt.DayOfYear);
Console.WriteLine("30 天后的日期是{0}", dt.AddDays(30));
}
}
using System;
class Program
{
static void Main(string[] args)
{
DateTime dt1 = DateTime.Now;
DateTime dt2 = new DateTime(2022, 1, 1);
//两个日期的差可由时间间隔类TimeSpan的对象来存放。该类允许表示的时间间隔范围是0到64位整数。
TimeSpan ts = dt1 - dt2;
Console.WriteLine("间隔的天数为{0}天", ts.Days);
}
}
思考1:为什么类中要将字段设置为private,而使用属性访问字段?有什么好处?
使用属性可以在设置值的同时校验值的有效性;属性还可以控制读写权限。