【Java复健指南08】OOP中级03【完结】-Object类和一些练习
前情回顾:https://www.cnblogs.com/DAYceng/category/2227185.html
Object类
equals方法
"=="与equals的区别
"=="是一个比较运算符
- 双等号既可以判断基本类型,又可以判断引用类型
- 判断基本类型时是判断值是否相等(如int i= 10)
- 判断引用类型时是判断地址是否相等,即判定是否为同一个对象
练习题
1.记账软件
面向过程版
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
public class SmallChangeSys {
//化繁为简
//1、先完成显示菜单,并可以选择菜单给出对应提示
//2、完成资金明细
//3、完成收益入账
//4、消费
//5、退出
//6、用户退出时给提示是否要真的退出
//7、金额校验
public static void main(String[] args) {
//定义相关变量
boolean loop = true;
Scanner scanner = new Scanner(System.in);
String key = "";
//资金明细
//可以用数组存;可以使用对象;可以直接用字符串拼接;
String detail = "----------------明细----------------";
//入账
//需要新的变量
double money = 0;
double balance = 0;
Date date = null;
//用于日期格式化
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
//消费
String note = "";
do{
System.out.println("\n================记账菜单================");
System.out.println("\t\t\t\t1 明细");
System.out.println("\t\t\t\t2 入账");
System.out.println("\t\t\t\t3 消费");
System.out.println("\t\t\t\t4 退出");
System.out.print("输入选项1~4: ");
key = scanner.next();
//switch判断
switch (key){
case "1":
System.out.println(detail);
break;
case "2":
System.out.println("收益入账金额:");//输入值校验
money = scanner.nextDouble();
//找不正确的条件,给出提示,直接 break
if(money <= 0){
System.out.println("收益金额需要大于0");
break;
}
balance += money;
//获取当前日期
date = new Date();
detail += "\n收益入账\t+" + money + "\t" + sdf.format(date) + "\t" + + balance;
break;
case "3":
System.out.println("消费金额:");
money = scanner.nextDouble();
//找不正确的条件,给出提示,直接 break
if(money <= 0){
System.out.println("消费金额需要大于0");
break;
}
System.out.println("消费说明:");
note = scanner.next();
balance -= money;
//获取当前日期
date = new Date();
detail += "\n" + note + "\t-" + money + "\t" + sdf.format(date) + "\t" + + balance;
break;
case "4":
//定义一个变量choice,接受输入
//使用while-break,处理接受到的输入是y or n
//退出while循环后再判断输入的是什么
String choice = "";
while (true){
System.out.println("你确定要退出?y/n");
choice = scanner.next();
if("y".equals(choice)||"n".equals(choice)){
break;
}
}
if (choice.equals("y")){
loop = false;
}else {
loop = true;
}
break;
default:
System.out.println("选择有误请重试");
}
}while (loop);
System.out.println("----------------退出软件----------------");
}
}
OOP版
SmallChangeSysOOP,功能类。
实际上就是将之前的各个部分的功能代码封装到类方法中,这样mainMenu()主菜单类只需要去调用本类的方法即可实现所有功能
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;
/*
* 完成记账软件的各个功能
* 将各个功能对应一个方法
* */
public class SmallChangeSysOOP {
//属性
boolean loop = true;
Scanner scanner = new Scanner(System.in);
String key = "";
//资金明细
//可以用数组存;可以使用对象;可以直接用字符串拼接;
String detail = "----------------明细----------------";
//入账
//需要新的变量
double money = 0;
double balance = 0;
Date date = null;
//用于日期格式化
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");
//消费
String note = "";
//先完成显示菜单
public void mainMenu(){
do{
System.out.println("\n================记账菜单OOP================");
System.out.println("\t\t\t\t1 明细");
System.out.println("\t\t\t\t2 入账");
System.out.println("\t\t\t\t3 消费");
System.out.println("\t\t\t\t4 退出");
System.out.print("输入选项1~4: ");
key = scanner.next();
//switch判断
switch (key){
case "1":
this.detail();
break;
case "2":
this.income();
break;
case "3":
this.pay();
break;
case "4":
this.exit();
break;
default:
System.out.println("选择有误请重试");
}
}while (loop);
}
//明细
public void detail(){
System.out.println(detail);
}
//入账
public void income(){
System.out.println("收益入账金额:");//输入值校验
money = scanner.nextDouble();
//找不正确的条件,给出提示,直接 return
if(money <= 0){
System.out.println("收益金额需要大于0");
return;
}
balance += money;
//获取当前日期
date = new Date();
detail += "\n收益入账\t+" + money + "\t" + sdf.format(date) + "\t" + + balance;
}
//消费
public void pay(){
System.out.println("消费金额:");
money = scanner.nextDouble();
//找不正确的条件,给出提示,直接 break
if(money <= 0){
System.out.println("消费金额需要大于0");
return;
}
System.out.println("消费说明:");
note = scanner.next();
balance -= money;
//获取当前日期
date = new Date();
detail += "\n" + note + "\t-" + money + "\t" + sdf.format(date) + "\t" + + balance;
}
//退出
public void exit(){
//定义一个变量choice,接受输入
//使用while-break,处理接受到的输入是y or n
//退出while循环后再判断输入的是什么
String choice = "";
while (true){
System.out.println("你确定要退出?y/n");
choice = scanner.next();
if("y".equals(choice)||"n".equals(choice)){
break;
}
}
if (choice.equals("y")){
loop = false;
}else {
loop = true;
}
}
}
SmallChangeSysApp,调用功能类实现软件功能
/*
* 调用SmallChangeSysOOP对象,显示主菜单即可*/
public class SmallChangeSysApp {
public static void main(String[] args) {
new SmallChangeSysOOP().mainMenu();
}
}
2.类方法复用(扩展银行类的练习)
扩展如下的BankAccount类
class BankAccount{
private double balance ;
public BankAccount(double initialBalance){
this.balance = initialBalance;
}
public void deposit(double amount){
balance += amount;
}
public void withdraw(double amount){
balance -= amount;
}
}
- 要求:
(1)在上面类的基础上扩展新类CheckingAccount对每次存款和取款都收取1美元的手续费
(2)扩展前一个练习的BankAccount类,新类SavingsAccount每个月都有利息产生
(earnMonthlylnterest方法被调用),并且有每月三次免手续费的存款或取款。在earnMonthlylnterest方法中重置交易计数
需求一:
扩展后的新类CheckingAccount,只需充分利用父类方法即可
//在上面类的基础上扩展新类CheckingAccount对每次存款和取款都收取1美元的手续费
public class CheckingAccount extends BankAccount{
//属性
public CheckingAccount(double initialBalance) {
super(initialBalance);
}
@Override
public void deposit(double amount) {//存款
super.deposit(amount - 1);//利用父类的deposit
//继承父类的存款方法deposit,但是每次存款都少记账1¥,相当于扣除手续费
}
@Override
public void withdraw(double amount) {
super.withdraw(amount + 1);//同理,每次取款多扣1¥
}
}
需求二:
扩展后的新类SavingsAccount
/*
* 新类SavingsAccount每个月都有利息产生(earnNonthlyInterest方法被调用),
* 并且有每月三次免手续费的存款或取款。
* 在earnMonthlyInterest方法中重置交易计数
*/
public class SavingsAccount extends BankAccount{
private int count = 3;
private double rate = 0.01;//利率
//正经写项目时,新增方法写在最后
public void earnMonthlylnterest(){//每个月初统计上个月的利息,同时将count重置
count = 3;
super.deposit(getBalance() * rate);//复用父类方法
}
@Override
public void deposit(double amount) {
//判断是否还可以免手续费
if(count > 0){
super.deposit(amount);
}else {
super.deposit(amount - 1);//扣手续费
}
count--;//减去一次机会
}
public SavingsAccount(double initialBalance) {
super(initialBalance);
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public double getRate() {
return rate;
}
public void setRate(double rate) {
this.rate = rate;
}
}
测试类
待拓展的BankAccount类我写在了测试类中,当做父类方法供拓展的子类继承
public class Homework08 {
public static void main(String[] args) {
//// CheckingAccount checkingAccount = new CheckingAccount(1000);
//// System.out.println(checkingAccount.Accountinfo());
//// System.out.println(checkingAccount.deposit(500));
//// System.out.println(checkingAccount.withdraw(100));
// SavingsAccount savingsAccount = new SavingsAccount(1000);
//
// for (int i = 0; i < 5; i++) {
// int date = i+1;
// System.out.println("============第"+date+"月============");
// System.out.println(savingsAccount.Accountinfo());
// System.out.println(savingsAccount.earnMonthlylnterest(savingsAccount.getBalance()));
// System.out.println(savingsAccount.deposit(500));
// System.out.println(savingsAccount.withdraw(100));
// }
// CheckingAccount checkingAccount = new CheckingAccount(1000);
// checkingAccount.deposit(10);//1010-1=1009
// System.out.println(checkingAccount.getBalance());
SavingsAccount savingsAccount = new SavingsAccount(1000);
savingsAccount.deposit(100);
savingsAccount.deposit(100);
savingsAccount.deposit(100);
System.out.println(savingsAccount.getBalance());//1300
savingsAccount.deposit(100);
System.out.println(savingsAccount.getBalance());//1400-1 = 1399
//月初,定时器自动调用earnMonthlylnterest
savingsAccount.earnMonthlylnterest();
System.out.println(savingsAccount.getBalance());//1399+13.99
savingsAccount.withdraw(100);//免手续费,1412.99-100
System.out.println(savingsAccount.getBalance());
savingsAccount.withdraw(100);
savingsAccount.withdraw(100);
System.out.println(savingsAccount.getBalance());//1412.99-200
savingsAccount.deposit(100);//扣手续费
System.out.println(savingsAccount.getBalance());//1211.99
}
}
class BankAccount {
private double balance;
// public int fee = 1;
// Scanner scanner = new Scanner(System.in);
public BankAccount(double initialBalance){
this.balance = initialBalance;
}
public void deposit(double amount){//存款
balance += amount;
}
public void withdraw(double amount){//取出
balance -= amount;
}
public String Accountinfo(){
return "账户当前金额: "+balance;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
3.重写equals方法
编写Doctor类{name, age. job, gender, sal}以及相应的getter()和setter()方法,5个参数的构造器,
重写父类的equals()
方法:public boolean equals(Object obj),并判断测试类中创建的两个对象是否相等。
相等就是判断属性是否相同。
根据题意写出Doctor类
class Doctor{
private String name;
private int age;
private String job;
private char gender;
private double sal;
public Doctor(String name, int age, String job, char gender, double sal) {
this.name = name;
this.age = age;
this.job = job;
this.gender = gender;
this.sal = sal;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
public double getSal() {
return sal;
}
public void setSal(double sal) {
this.sal = sal;
}
@Override//重写equals,所有Object的子类都需要重写以满足特定的判定需求
public boolean equals(Object obj) {
//判断两个比较对象是否相同
if(this == obj){
return true;
}
//判断obj是否为Doctor类型或其子类
if(!(obj instanceof Doctor)){//不是的话
return false;
}
//如果相等就向下转型,因为obj的运行类型是Doctor或者其子类型
//要从需要比较的子类中获取所有属性并进行比较
Doctor doctor = (Doctor) obj;
return this.name.equals(doctor.name)&&this.age ==doctor.age&&
this.gender == doctor.gender&&this.job.equals(doctor.job)&&this.sal==doctor.sal;
}
}
注意,为什么要重写equals类呢?
实际上这是一个很常规的操作,java提供的父类Object中定义的equals仅仅能判断输入的两个对象的地址是否相同,即两个对象是否相同,并不能判断对象的具体类型(详见java源码)
因此开发者需要根据具体判断需求重写equals类,实际上java提供的数据类型在使用equals进行比较时,已经使用了针对相应类型重写的equals类了(例如在比较String时,其使用的equals类就不是Object中定义的equals)
测试类
public class Homework10 {
public static void main(String[] args) {
Doctor doctor1 = new Doctor("jk",20,"牙医",'男',2000);
Doctor doctor2 = new Doctor("jk",20,"牙医",'男',2000);
System.out.println(doctor1.equals(doctor2));//ture
}
}
4.综合练习
题目
打印效果如下
/*
老师的信息:
姓名:王飞
年龄:30
性别:男
工龄:5
我承诺,我会认真教课。
王飞爱玩象棋
--------------------------
学生的信息:
姓名:小明
年龄:15
性别:男
学号:00023102
我承诺,我会好好学习。
小明爱玩足球。
*/
/*
* 案例题目描述;
(1)做一个Student类,Student类有名称(name),性别(sex),年龄(age).学号(stu_id),
* 做合理封装,通过构造器在创建对象时将4个属性赋值。
(2)写一个Teacher类,Teacher类有名称(name),性别(sex),年龄(age),工龄(work_age).
* 做合理封装,通过构造器在创建对象时将4个属性赋值。
(3)抽取一个父类Person类,将共同属性和方法放到Person类
(4)学生需要有学习的方法(study),在方法里写生“我承诺,我会好好学习。”。
(5)教师需要有教学的方法(teach),在方法里写上“我承诺,我会认真教学。“.
(6)学生和教师都有玩的方法(play),学会玩的是足球,老师玩的是象棋,此方法是返回字符串的,
* 分别返回“xx爱玩足球”和“双爱玩象棋”(其中xXx分别代表学生和老师的姓名)。
* 因为玩的方法名称都一样,听以要求此方法定义在父类中,子类实现重写。
* 应当分析出需要打印信息的方法
(7)定义多态数组,里面保存2个学生和2个教师,要求按年龄从高到低排序,
(8)定义方法,形参为Person类型,功能:调用学生的study或教师的teach方法
学生类
没什么好说的,就是按属性-构造器-get/set方法的流程写就行
public class Student extends Person{
//private String name;
//private char gender;
//private int age;
private String stu_id;
public Student(String name, char gender, int age, String stu_id) {
super(name, gender, age);
this.stu_id = stu_id;
}
public String getStu_id() {
return stu_id;
}
public void setStu_id(String stu_id) {
this.stu_id = stu_id;
}
public void study(){
System.out.println(getName()+"说,要好好学习");
}
@Override
public String play() {
return super.play()+"足球";
}
//编写一个输出信息的方法,体现封装
public void printInfo(){
System.out.println("学生信息:");
System.out.println(super.basicInfo());
System.out.println("学号:"+stu_id);
study();
System.out.println(play());
}
@Override
public String toString() {
return "Student{" +
"stu_id='" + stu_id + '\'' +
'}'+super.toString();
}
}
教师类
public class Teacher extends Person{
//private String name;
//private char gender;
//private int age;
private int work_age;
public Teacher(String name, char gender, int age, int work_age) {
super(name, gender, age);
this.work_age = work_age;
}
public int getWork_age() {
return work_age;
}
public void setWork_age(int work_age) {
this.work_age = work_age;
}
public void teach(){
System.out.println(getName()+"说,要认真教学");
}
@Override
public String play() {
return super.play()+"象棋";
}
//输出信息的方法
public void printInfo(){
System.out.println("老师的信息:");
System.out.println(super.basicInfo());
System.out.println("工龄:"+work_age);
teach();
System.out.println(play());
}
@Override
public String toString() {
return "Teacher{" +
"work_age=" + work_age +
'}'+super.toString();
}
}
注意,按正常的逻辑来说,拿到需求后都是先把子类写出来(例如这里的学生和教师类),然后再去将他们共同的属性抽离写出父类(这里是Person)
父类Person
学生和教师类中共有的属性是姓名、年龄和性别,拿出来构成父类
public class Person {
private String name;
private char gender;
private int age;
public Person(String name, char gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//编写play方法,把共有的输出内容写在这里
public String play(){
return name + "爱玩";
}
//返回一个基本信息
/*
姓名:xx
年龄:x
性别:x
**/
public String basicInfo(){
return "姓名: "+name+"\n年龄: "+age+"\n性别: "+gender;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", gender=" + gender +
", age=" + age +
'}';
}
}
细节说明
1、学生的特殊方法是study,老师的是teach,然后他们都有一个爱好类play,只是打印的爱好不同,因此该类可以写在父类中,通过继承去使用,不同类在复用时有自定义内容
2、打印信息肯定需要一个打印类,该类也需要进行抽离,基本信息在父类打印【basicInfo()】,特殊信息在子类通过继承复用的方式打印【printInfo()】
主类
(7)定义多态数组,里面保存2个学生和2个教师,要求按年龄从高到低排序
没什么好说的,排序就用冒泡就行【bubbleSort()】
(8)定义方法test,形参为Person类型,功能:调用学生的study或教师的teach方法
"形参为Person类型"又要调用子类方法,联想到对象类型判断【instanceof】和向下转型
以上两个需求在主类中编写方法实现
public class Homework13 {
public static void main(String[] args) {
//测试学生
Student student = new Student("xm", '男', 15, "00328");
student.printInfo();
//测试老师
Teacher teacher = new Teacher("zf", '男', 30, 15);
System.out.println("--------------------------------------------");
teacher.printInfo();
//定义多态数组,里面保存2个学生和2个老师,要求年龄从高到低
Person[] people = new Person[4];
people[0] = new Student("jk",'女',10,"001");
people[1] = new Student("km",'男',12,"002");
people[2] = new Teacher("SM",'男',35,5);
people[3] = new Teacher("xx",'男',23,1);
//创建对象
Homework13 homework13 = new Homework13();
homework13.bubbleSort(people);
//输出数组
System.out.println("-----------排序后的数组-----------");
for (int i = 0; i < people.length; i++) {
System.out.println(people[i]);
}
//遍历数组,调用test方法
System.out.println("=========");
for (int i = 0; i < people.length; i++) {
homework13.test(people[i]);
}
}
//定义方法,形参为Person类型,功能:调用学生的study和老师的teach方法
//向下转型和类型判断
public void test(Person p){
if(p instanceof Student){
((Student)p).study();
} else if (p instanceof Teacher) {
((Teacher)p).teach();
}else {
System.out.println("do nothing");
}
}
//方法,完成年龄从高到低排序
public void bubbleSort(Person[] people){
Person temp = null;
for (int i = 0; i < people.length-1; i++) {
for (int j = 0; j < people.length-1-i; j++) {
//判断条件
if(people[j].getAge()<people[j+1].getAge()){
temp = people[j];
people[j] = people[j+1];
people[j+1] = temp;
}
}
}
}
}
【Java复健指南08】OOP中级03【完结】-Object类和一些练习的更多相关文章
- 【Java复健指南09】项目练习全解--房屋出租系统
一个基于文本界面的综合练习,主要用于串联和回忆知识点,比较简单 各个界面的设计样式 主菜单 =============房屋出租系统菜单============ 1 新 增 房 源 2 查 找 房 屋 ...
- 【Java复健指南15】链表LinkedList及其说明
链表LinkedList by Java 之前有写过一些记录(引用),但是忘了乱了,现在重新梳理一遍 链表是Java中List接口的一种实现 定义(引用) 链表(linked list)是一种物理存储 ...
- 别样JAVA学习(五)继承上(1.0)Object类equals()
上一节继承下(一)我们进行抽象类.接口以及多态的学习. 接下来大家我们讲点特殊的东西就是object类, 我们一直在说继承,子继承了父,父还有没有父类呢, 为什么这么思考,大家想构造函数的第一行是不是 ...
- 别样JAVA学习(五)继承上(1.1)Object类toString()
接下来说完equals以后,我们学习接下来的toString(), Java又觉得全部对象不光具有比較性, 还能使对象变成字符串被打印. 出现 曾经前面显示的是数组.如今显示的是这个对象所属的类. 紧 ...
- Java基础知识强化26(1):Object类之Object类的概述
1.Object类 类Object是类层次结构的根类,每个类都使用 Object作为超类.所有对象(包括数组)都实现这个类的方法 每个类直接或者间接继承自Object类 2.Object类无参构造 ...
- 【系列】Java多线程初学者指南(1):线程简介
原文地址:http://www.blogjava.net/nokiaguy/archive/2009/nokiaguy/archive/2009/03/archive/2009/03/19/26075 ...
- 菜鸡的Java笔记 Object 类
Object 类 Object类 的主要作用 Object 类的常用方法 内容 虽然有了对象的向上转型,可以解决了参数的统一问题,但是 ...
- java 常用类库:Object类和Objects类
1,Object类: Object类是所有的类,数组,枚举的父类,也就是说,JAVA中允许把任何的对象赋值给Object类(包括基础数据类型),当定义一个类的时候,没有使用extends关键字显示指定 ...
- Java工程师学习指南 中级篇
Java工程师学习指南 中级篇 最近有很多小伙伴来问我,Java小白如何入门,如何安排学习路线,每一步应该怎么走比较好.原本我以为之前的几篇文章已经可以解决大家的问题了,其实不然,因为我写的文章都是站 ...
- Java工程师学习指南(中级篇)
Java工程师学习指南 中级篇 最近有很多小伙伴来问我,Java小白如何入门,如何安排学习路线,每一步应该怎么走比较好.原本我以为之前的几篇文章已经可以解决大家的问题了,其实不然,因为我写的文章都是站 ...
随机推荐
- 部署于K8S集群上面应用性能影响点推测
前言 本人2017年第一次接触K8S. 中间断断续续学习K8S相关的内容. 但是最近一年,几乎没太有学习. 因为之前学习了四五年, 一直以为产品马上要用 结果一直被浇冷水. 去年开始学乖了. 不这么搞 ...
- log4j-漏洞修复
Log4j漏洞修复 修复参考文档:https://www.cert.org.cn/publish/main/9/2021/20211215154225883558274/202112151542258 ...
- 参照DefenseGrid在Unity中实现合理的塔防寻路机制
前言 在一款TD游戏中,最重要的单位就两大类:防御塔(Tower)和敌人单位(Enemy).在处理敌人单位的AI行为时,最基本也是最重要的就是自动寻路.在各式TD游戏中,防御塔的攻击方式以及敌人单位的 ...
- 人均瑞数系列,瑞数 4 代 JS 逆向分析
声明 本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容.敏感网址.数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 本文章未经许 ...
- 1.14 手工插入ShellCode反弹
PE格式是 Windows下最常用的可执行文件格式,理解PE文件格式不仅可以了解操作系统的加载流程,还可以更好的理解操作系统对进程和内存相关的管理知识,而有些技术必须建立在了解PE文件格式的基础上,如 ...
- C/C++ Qt 常用数据结构
Qt 是一个跨平台的图形化类库,常用数据结构就是对C++ STL的二次封装,使其更加易用,如下是经常会用到的一些数据结构和算法,其中包括了QString,QList,QLinkedList,QVect ...
- 由刷题学习 heapq
今日一题是 面试题 17.14. 最小K个数 https://leetcode-cn.com/problems/smallest-k-lcci/ 还好 提示 0 <= len(arr) < ...
- Mygin中间件优化及日志中间件
本篇是mygin的第七篇,参照gin框架,感兴趣的可以从 Mygin第一篇 开始看,Mygin从零开始完全手写,在实现的同时,带你一窥gin框架的核心原理实现. 目的 中间件Middleware优化 ...
- MySQL8.0配置my.cnf
环境 centos7.9 mysql Ver 8.0.32 因为是源码安装的MySQL8.0.32,查了一下MySQL 8.0之后源码中不包含my.cnf文件和my-default.cnf文件了. ...
- 2.4 资源管理器Restorator--《恶意代码分析实战》
Lab01-04.exe 实验内容: 1.将文件上传到http://www.VirusTotal.com 进行分析并查看报告.文件匹配到了已有的反病毒软件特征吗? 2.是否有这个文件被加壳或混淆的任何 ...