上一期说到类的声明,这一期我们来讨论类的其他特性
上期回顾
#include<iostream>
class Student
{
int scr;
int goal;
public:
void set(int a,int b){
scr = a;
goal =b;
}
void show(){
std::cout<< scr <<"\n"<< goal;
}
};
int main(){
Student abc;
abc.set(40,60);
abc.show();
return 0;
}
这里列出上期代码,我们继续从这里开始
我们声明了Student类,那怎么使用呢?
Students name;
直接在类名后接上对象的名称即可,但是要注意,类只是一个模版,对象则是一个一个独立的单位
Student name1,name2;
name1.show();
name2.show();
所以我们可以声明两个不一样的对象,以Student类为模版,注意类的所有成员函数都是需要声明成对象才能使用
Student name;
name.show();//allowed
Student.show();//unallowed
类设计的进阶
我们知道,一个类是包括如下元素的
类的声明,包括私有的数据,函数,公有的数据,函数
而就保护数据完整性考虑下,我们通常把数据成员列为私有,而操作数据的函数列为公有
class classname
{
private:
//数据成员
public:
//类的成员函数
};//不要遗忘声明的分号
但仔细考虑最开始的那一段代码,如果我们把类成员的所有定义放入类的声明中,那会使得类的声明极为冗长,那我们是否可以像函数声明那样,只在头文件里提供声明,而在一个独立文件中提供定义呢?
当然可以!
class Student
{
int scr;
int goal;
public:
void set(int a,int b);
void show();
};
#include<Student.cpp>
我们把这一段声明放入Student.h的文件中
Student.cpp
include<iostream>
void Student::set(int a,int b){
scr = a;
goal =b;
}
void Student::show(){
std::cout<< scr <<"\n"<< goal;
}
我们把函数定义放入Student.cpp中,我们会发现, 函数名字前多了Studnet::,这是什么呢?和解析作用域标识符::其实是一样的,不同的是每一个类都有一个对应的类作用域,就和std::cout标识cout位于std命名空间,Studnet::标识show()位于Student类中。
use.cpp
#include"Student.h"
int main()
{
Student abc;
abc.set(40,60);
abc.show();
}
首先,我们创建了一个名为abc的对象,并使用了abc的成员函数,那怎么使用呢?很简单,编译use.cpp就好了,对于关联多文件可以参见多文件关联
类的构造函数和析构函数
C++强大的一个重要原因,便是它种类繁多的工具,构造和析构函数便是其中一个
那他们是什么呢?
如果您接触过Python,那么便不会对其感到陌生
def __init__(self):
xxx
xxx
该函数允许进行初始化,同样C++的构造函数也是同样的功能
针对这个我们对上述代码进行改写
#include<iostream>
class Student
{
int scr;
int goal;
public:
void set(int a,int b){
scr = a;
goal =b;
}
void show(){
std::cout<< scr <<"\n"<< goal;
}
Stock(){
scr = 40;
goal = 60;
}
Stock(int m_scr,int m_goal){
scr = m_scr;
goal = m_goal;
}
};
int main(){
Student abc;
abc.set(40,60);
abc.show();
return 0;
}
对于一个类的构造函数,其名字便是类名,如果要在独立文件中提供定义,则其标识为Student::Student()
需要注意的是,构造和析构函数都不能有返回值(void也不被允许,因为void其实有着返回值,即是没有返回值的返回),所以函数名没有返回标识符
我们会注意到有着两个Stock函数,其中一个可以接受两个参数,这是怎么回事呢?
这就不得不提到创建对象时,构造函数做了什么
构造函数做了什么?
我们知道创建对象的方法
Student abc;
Student abc = Stock(40,60);
Student abc(40,60);
Student *abc = new Stock(40,60);
那么,其中哪一项是正确的呢?
答案可能出乎你的意料,都是正确的。
但后面三个仅仅适用于有接受两个int参数的非默认构造函数的类才有效
是的,没错,构造函数分为默认和非默认,默认构造函数没有任何参数,对于一个没有显性声明构造函数的类,编译器会自动隐性创建一个构造函数,类似这样
Student(){
}
其默认为空,但需要注意的是,只有没有显性声明过任何构造函数,编译器才会提供默认构造函数,如果一个类没有默认构造函数,那么编译器会拒绝这种操作,所以在声明非默认构造函数后,需要再次声明默认构造函数
那这样有什么用处呢?
为了保证这两种对象创建方式都有效
Student abc;
Student abc(40,60);
需要注意的是有()进行声明时,调用的是非默认构造函数,但如果是这样的
Student abc();
abc并不是一个对象,而是一个返回Stock类的函数
再谈谈析构函数
如今主流的解释性语言,都没有类似析构函数的处理方式,其中很重要的原因是每个解释型语言都有着一套十分完整的垃圾回收机制,而C++是需要程序员自己进行内存管理,所以如果在类中使用动态内存分配,就会产生十分严重的内存泄漏问题,所以需要析构函数在创建的对象被摧毁时,释放占用的内存
那析构函数是怎么工作的呢?
如果没有主动声明,那么编译器会提供一个默认析构函数,内容因每一个对象不同有所变化,同样,如果有主动声明,那么编译器不会提供默认析构函数。所以,需要注意的是,如果你对类里的动态数据结构不胜了解,请不要随意声明析构函数,不然会引起严重的后果
对于析构函数,声明一般是这样的
~Student(){
}
析构函数只需要在类名前加~就可以了
const成员函数
我们知道,类其实属于一种数据结构,同样它可以这样声明
const Student abc(40,60);
abc.show();
但需要注意,第二行的操作,会被编译器拒绝,因为无法保证show()不会修改原本的数据,所以我们要在开始声明的时候,把声明改成如下形式
void show() const{
std::cout<< scr <<"\n"<< goal;
}
我们需要在函数声明的末尾加上const
尾声
非常感谢观看,这篇文章花去了我很多精力,加之最近成绩不甚理想,情绪低落,但是我还是会继续更新下去的,下一期我们会讨论类的高级特性--this指针!