c++面向对象基础知识 Day 3

本文讲解了析构函数的作用及如何使用,同时深入探讨了C++中的继承机制,包括不同类型的派生类及其成员访问权限。

析构函数

析构函数(destructor)与构造函数相反,当对象脱离其作用域时(例如对象所在的函数已调用完毕),系统自动执行析构函数。析构函数往往用来做清理善后的工作(例如在建立对象时用new开辟了一片内存空间,应在退出前在析构函数中用delete释放)。
析构函数名也应与类名相同,只是在函数名前面加一个波浪符~ ,例如~stud(),以区别于构造函数。它不能带任何参数,也没有返回值(包括void类型)。只能有一个析构函数,不能重载。如果用户没有编写析构函数,编译系统会自动生成一个缺省的析构函数,它也不进行任何操作。所以许多简单的类中没有用显式的析构函数。

/*包含构造函数和析构函数的c++程序*/
#include<string.h>
#include<iostream.h>
class stud
{
 private:
 int num;
 char name[];
 char sex;
 public:
 stud(int n,char nam[],char s)
 {
  n=num;
  strcpy(name,nam);
  sex=s;
 }
 ~stud()
 {}
 void display()
 {
 cout<<"num:"<<num<<endl;
 cout<<"name:"<<name<<endl;
 cout<<"sex:"<<sex<<endl;
 }
};
void main()
{
stud stud1(10010,"Wangjun",'M'),stud2(10011,"jianglin",'F')
stud1.display();
stud2.display();
}

现在把类的声明放在main函数之前,它的作用域是全局的。这样做可以使main函数更简练一些。在main函数中定义了两个对象并且给出了初值。然后输出两个学生的数据。
本程序中,成员函数是在类中定义的,如果成员函数的数目很多以及函数的长度很长,类的声明就会占很大的篇幅,不利于阅读程序。可以在类的外面定义成员函数,而在类中只用函数的原型作声明。

//在类的外面定义成员函数
#include<string.h>
#include<iostream.h>
/*如果你用#include<iostream.h>就不需写这句话(旧标准)。但是如果你用#include<iostream>就必须要写using namespace std。*/
class stud
{ 
private:
    int num;
    char name[10];//错写成char name;
    char sex;
public:
    stud(int n,char nam[],char s);
    ~stud();
    void display();
};

stud::stud(int n,char nam[],char s)
{
    num=n;
    strcpy(name,nam);
    sex=s;
}

stud::~stud()
{}

void stud::display()//少写了void
{
    cout<<"num:"<<num<<endl;
    cout<<"name:"<<name<<endl;
    cout<<"sex:"<<sex<<endl;
}

void main()
{
    stud stud1(10010,"Wangjun",'M'),stud2(10011,"chenglin",'F');
    stud1.display();
    stud2.display();
}

在类声明的外部定义函数,必须指定类名,函数首行的形式为
函数类型 类名::函数名(形参表列)

继承与派生

面向对象技术强调软件的可重用性。在c++中可重用性是通过继承这一机制来实现的。因此,继承是C++的一重要组成部分。
前面介绍了类,一个类中包含了若干数据成员和成员函数。每个类的数据成员和成员函数是不相同的。但是有的时候两个类的内容基本相同或有一部分相同。
例如前面声明了学生基本数据的类stud;

class stud
{
  private:
    int num;
    char name;
    char sex;
  public:
    void display()
    {
      cout<<"num:"<<num<<endl;
      cout<<"name:"<<name<<endl;
      cout<<"sex:"<<sex<<endl;
    }
}

如果学校的某个部门除了需要用到学号、姓名、性别以外,还需要用到年龄、地址等信息。当然可以重新声明另外一类:

class stud1
{
  private:
  int num;
  char name;
  char sex;
  int age;
  char addr[20];
  public:
  void display()
  {
    cout<<"num:"<<num<<endl;
    cout<<"name:"<<name<<endl;
    cout<<"sex:"<<sex<<endl;
    cout<<"age:"<<age<<endl;
    cout<<"addr:"<<addr<<endl;
  }
};

可以看到相当一部分是原来已有的。很少人自然会想到能否利用原有声明的类作为基础,再加上新的内容即可,以减少重复的工作量。C++提供的继承机制就是解决这个问题。

在c++中所谓的继承就是在一个已存在的类的基础上建立一个新的类,已存在的类称为基类或父类;新建立的类称为派生类或子类,派生类继承了基类的所以数据成员和成员函数,并增加新的成员。

建立派生类的方法

先通过一个例子说明怎样通过继承来建立派生类
假设已经声明了一个基类stud,在此基础上声明一个派生类student:

class student:public stud
{
 private:
  int age;
  char addr[30];
 public:
  void display()
  {
   cout<<"age:"<<age<<endl;
   cout<<"addr:"<<addr<<endl;
  }
};

仔细观察第一行:
class student:public stud
在class后面的student是新建的类名。冒号后面的stud表示是已存在的基类。在stud之前有一关键字public,用来表示基类stud中的成员在派生类student中的使用权限。基类名前有public的称为“公用派生类”。
定义派生类的一般形式:
class 派生类名:[引用权限] 基类名
{
派生类新增加的数据成员
派生类新增加的成员函数
};
引用权限可以是private和public。不写的话默认是private。
派生类包括基类成员和自己增加的成员,派生类的成员函数在引用派生类自己的数据成员时,按前面介绍过的规则处理(即私有数据成员只能被同一类中的成员函数引用,公用成员可以被外界引用)。而对从基类继承来的成员的引用并不是简答地把基类的私有和公用成员直接作为派生类的私有成员和公用成员,而要根据基类成员的引用权限和派生类声明的引用权限共同决定。

公用派生类
在声明一个派生类的时候将基类的引用权限指定为public的,该类称为基类的公用派生类。
在公用派生类中,基类的公用成员和保护成员仍然称为派生类中的公用成员和保护成员,而基类的私有成员不能被派生类引用,即成为派生类不可访问的成员,只有基类的成员函数可以引用它。基类的成员在公用派生类中的引用权限见下表:

基类私有成员公用成员
公用派生类不可访问的成员公用成员
/*访问基类成员*/
class stud
{
 private:
  int num;
  char name[10];
  char sex;
 public:
  void display()
  {
   cout<<"num:"<<num<<endl;
   cout<<"name:"<<name<<endl;
   cout<<"sex:"<<sex<<endl;
  }
};
class student:public stud
{
 private:
 int age;
 char addr[30];
 public:
 void show()
 {
//cout<<"name:"<<name<<endl;引用基类的私有成员,错误。
  cout<<"age:"<<age<<endl;
  cout<<"addr:"<<addr<<endl;
 }
};

由于基类的私有成员对派生类来说是不可访问的,因此在派生类的show函数中直接引用基类的私有数据成员name是不允许的。可以通过基类的公用成员函数来引用基类的私有数据成员。上面对派生类student的声明可改为

class student:public stud
{
 private:
  int age;
  char addr[30];
 public:
  void show()
  {
   display();//引用基类的公有成员函数允许。
   cout<<"age:"<<age<<endl;
   cout<<"addr:"<<addr<<endl;
  }
};

在派生类成员函数show中引用基类的公用成员函数display,通过display引用基类stud中的私有数据num、name和sex。可以这样写main函数(假设对象a中已有数据);

void main()
{
 student a;//定义一个student派生类的对象a
 ...
 a.show();//输出a对象的5个数据
}

在主函数中如下程序:
a.display();//正确,从基类继承的公用成员函数
a.age=18;//错误。外界不能引用基类的私有成员

私有派生类
在声明一个派生类时,将基类的引用权限指定为private的,该类称为基类的私有派生类。
在私有派生类中,基类的公用成员和保护成员成为派生类的私有成员,基类的私有成员称为派生类的不可访问成员。只有基类的成员函数可以引用它。基类的成员在私有派生类中的引用权限见下表:

基类私有成员公用成员
私有派生类不可访问的成员私有成员

如果派生类首行改为

class student;private stud
{
 private:
 int age;
 char addr[20];
 public:
 void show()
 {
  display();//基类的公有成员函数变成派生类的私有函数
  cout<<"age:"<<age<<endl;
  cout<<"addr:"<<addr<<endl;
 }
};
void main()
{ student a;
  a.display();
  a.age=18;
}

可以看到:
(1)不能通过私有派生类对象引用从基类继承过来的任何成员;
(2)在派生类的成员函数中不能访问基类的私有成员,但可以访问基类的公用成员,由于私有派生类限制太多,一般不常用。

保护成员
由protect声明的成员称为保护成员,不能被外界引用(这点和私有成员类似),但可以被派生类的成员函数引用。

基类私有成员公用成员保护成员
公用派生类不可访问的成员公用成员保护成员
私有派生类不可访问的成员私有成员私有成员

从前面的介绍已知基类的私有成员被派生类(不论是私有派生类还是公用派生类)继承后变为不可访问的成员。如果想在派生类引用基类的成员,可以将基类的成员声明为protected。

/*派生类引用保护成员*/
class stud              //声明基类
{
 protect:                //基类保护成员
  int num;
  char name[10];
  char sex;
 public:                  //基类公用成员
  void display()          //基类成员函数
  {
   cout<<"num:"<<num<<endl;
   cout<<"name:"<<name<<endl;
   cout<<"sex:"<<sex<<endl;
  }
};

class student:public stud  //声明一个公用派生类
{
 private:
 int age;
 char addr[30];
 public:
 void show()
 {
  cout<<"num:"<<num<<endl;  //引用基类的保护成员,合法
  cout<<"name:"<<name<<endl;
  cout<<"sex:"<<sex<<endl;
  cout<<"age:"<<age<<endl;   //引用派生类的私有成员,合法
  cout<<"address:"<<addr<<endl;
 }
};

void main()
{
 student a;  //a是派生类student类的对象
 a.show();    //合法,show是派生类中的公用成员函数
// a.num=10023; 错误,外界不能访问保护成员
}

派生类的构造函数
派生类从基类继承了非私有成员函数和数据成员,但在建立派生类对象时,系统只执行派生类的构造函数,而不会自动执行基类的构造函数。也就是说,基类的构造函数是不能继承的。如果基类的构造函数包含对对变量的初始化,那么在建立派生类对象时,由于没有执行基类的构造函数,因而就会使基类的变量未初始化。所以在设计派生类的构造函数时,不仅要考虑派生类所增加的变量初始化,还应当考虑基类的变量初始化。在执行派生类的构造函数时,应当调用基类的构造函数。

/*派生类的构造函数*/
#include<string.h>
#include<iostream.h>
class stu
{
 protected:
 int num;
 char name[20];
 char sex;

 public:
 stud(int n,char nam[],char s)
 {
  num=n;
  strcpy(name,nam);
  sex=s;
 }
~stud()
{}
};

class student:public stud
{
 private:
 int age;
 char addr[30];
 public:
 student(int n,char nam[],char s,int a,char ad[]):stud(n,nam,s)
 {age=a;
  strcpy(addr,ad);
 }
 void show()
 {
  cout<<"num:"<<num<<endl;
  cout<<"name:"<<name<<endl;
  cout<<"sex:"<<sex<<endl;
 }
~student(){}
};
void main()
{
 student a(10010,"Wangjun",'f',22,"420hust");
 student b(10010,"Chenglin",'m',18,"321hust");
 a.show();
 b.show();
}

请注意派生类构造函数首行的写法:

student(int n,char nam[],char s,int a,char ad[]):stud(n,nam,s)

其一般的形式为
派生类构造函数函数名(参数表列):基类构造函数名(参数表列)
派生类构造函数名后面括号内的参数表列包括参数的类型和参数名。基类构造函数名后面括号内的参数表列只有参数名而不包括参数类型。从基类的声明中可以中可以看到基类构造函数stud有3个参数,派生类构造函数有5个参数,前三个是用来传递给基类构造函数的,后面2个是用来对派生类所增加的变量初始化的。

在上例中也可以将派生类构造函数在类外面定义,而在类的声明中只写函数的声明

student(int n,char nam[],char s,int a,char ad[]);

在类的外面定义派生类构造函数:

student::student(int n,char nam[],char s,int a,char ad[]):stud(n,nam,s)
{
 age=a;
 strcpy(addr,ad);
}

注意:在类中对派生类构造函数作声明时,不包括基类构造函数名和参数表列,只在定义函数时才将它列出。

在建立一个对象时,由派生类构造函数先调用基类构造函数,然后再执行派生类构造函数本身。对上例来说,先初始化num,name,sex,然后再初始化age和addr。
在对象消失时,先执行派生类析构函数,再执行其基类析构函数。

本课题设计了一种利用Matlab平台开发的植物叶片健康状态识别方案,重点融合了色彩与纹理双重特征以实现对叶片病害的自动化判别。该系统构建了直观的图形操作界面,便于用户提交叶片影像并快速获得分析结论。Matlab作为具备高效数值计算与数据处理能力的工具,在图像分析与模式分领域应用广泛,本项目正是借助其功能解决农业病害监测的实际问题。 在色彩特征分析方面,叶片影像的颜色分布常与其生理状态密切相关。通常,健康的叶片呈现绿色,而出现黄化、褐变等异常色彩往往指示病害或虫害的发生。Matlab提供了一系列图像处理函数,例如可通过色彩空间转换与直方图统计来量化颜色属性。通过计算各颜色通道的统计参数(如均值、标准差及主成分等),能够提取具有判别力的色彩特征,从而为不同病害别的区分提供依据。 纹理特征则用于描述叶片表面的微观结构与形态变化,如病斑、皱缩或裂纹等。Matlab中的灰度共生矩阵计算函数可用于提取对比度、均匀性、相关性等纹理指标。此,局部二值模式与Gabor滤波等方法也能从多尺度刻画纹理细节,进一步增强病害识别的鲁棒性。 系统的人机交互界面基于Matlab的图形用户界面开发环境实现。用户可通过该界面上传待检图像,系统将自动执行图像预处理、特征抽取与分判断。采用的分模型包括支持向量机、决策树等机器学习方法,通过对已标注样本的训练,模型能够依据新图像的特征向量预测其所属的病害别。 此课题设计有助于深化对Matlab编程、图像处理技术与模式识别原理的理解。通过完整实现从特征提取到分决策的流程,学生能够将理论知识与实际应用相结合,提升解决复杂工程问题的能力。总体而言,该叶片病害检测系统涵盖了图像分析、特征融合、分算法及界面开发等多个技术环节,为学习与掌握基于Matlab的智能检测技术提供了综合性实践案例。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值