类是什么?
在学习类之前,我们要知道一个问题——什么是类?
学习过C语言的同胞们应该知道struct(结构体)这个概念,它是一种对数据和功能的一种包装方式,同样的,类也是一种包装方式。那么他们的区别在哪呢?
这里就不一一列举了,网上有很多的介绍资料,这里给大家放个链接,以供学习了解。
https://blog.csdn.net/weixin_39640298/article/details/84349171
如何使用类?
好了,现在我知道了类与结构体的区别。那么现在,我们就要开始我们的类的学习。
首先,我们要明白类的定义。在C++中,我们用 class 关键字来定义一个类。
为了加深大家记忆,可自行翻译 class 这个单词,翻译出的中文的确会有“类”的意思。
那么类的定义格式就如下:
class MyFirstClass { //定义了一个名叫MyFirstClass的类
}; //注意,这个分号一定要加
定义完了我们的第一个类,可是它什么成员都没有啊?所以我们要添加类成员。
class MyFirstClass {
char *strClassName;
};
我们在类中添加了一个char*型的strClassName,这个变量是用来储存类名的。那么我们来操作一下,看看是否正常运行。
#include<cstdio>
using namespace std;
class MyFirstClass {
char *strClassName;
};
int main() {
MyFirstClass test;
test.strClassName;
return 0;
}
将这段代码编译运行,我们会发现,IDE报了个[Error] 'char* MyFirstClass::strClassName' is private的错误,这是为什么呢?
这里我们就要提到防控属性了,那么在C++中,我们提供了三种属性:
class XXX {
public: //公有属性,也就是不论是何处,都可以调用此属性的成员
private: //私有属性,是对成员的一种保护,除了本类中的成员可以调用外,其他外界的都不
protected: //保护属性,在private的基础上还添加了派生类(子类)可调用的范围
};
【protected 等讲到派生类再说】
而在类中,当没有添加任何属性标签,系统会默认地将其视为私有成员,这就是以上的错误之处,所以我们只要在 “char *strClassName” 前一行添加 “public:” 即可。
以上介绍的是成员变量的情况,那么接下来介绍成员函数的情况,同样的定义类:
class MyFirstClass {
public:
void DisplayClassName();
private:
char *strClassName;
};
我们添加了一个DisplayClassName函数,想用其来输出类名。但类中我们只声明了函数,而没有定义函数的功能。
所以我们就需要为函数添加定义:
class MyFirstClass {
public:
void DisplayClassName() {
printf("%s",strClassName);
}
private:
char *strClassName;
};
我们看一下,如果类中的成员函数过多,而每个函数的定义都在类的内部,是否显得复杂冗余?所以我们就需要在类的外部定义函数,这样成员函数表清晰的列在类中,而定义也清晰的排在外部。
那么这里我们需要引用新的符号——"::"(域操作符),这里直接给大家示范:
class MyFirstClass {
public:
void DisplayClassName();
private:
char *strClassName;
};
void MyFirstClass::DisplayClassName() {
printf("%s",strClassName);
}
定义了以上类,在main函数中调用则有:
MyFirstClass t;
t.DisplayClassName(); //正确
t.strClassName; //错误
可是我们无法更改strClassName的值啊。很简单,专门写一个函数来修改它的值就行了。
class MyFirstClass {
public:
void DisplayClassName();
void EditClassName(char *strName);
private:
char *strClassName;
};
void MyFirstClass::DisplayClassName() {
printf("%s",strClassName);
}
void MyFirstClass::EditClassName(char *strName) {
strClassName=strName;
}
构造函数
那么我们发现,假如,我们定义地类,不需要进行过多次的数值操作,而我们专门写一个数值修改函数,是不是觉得很麻烦?
所以呢,这里我们就引入一个新的名词——构造函数。什么是构造函数?
顾名思义,构造函数就是构思、创造。那它的作用是什么?我们发现,在类中往往存在许多的成员变量,但是因为类在定义的时候,只是一个模板,我们无法进行变量的初始化,所以就用到了构造函数。
首先,构造函数是系统自动调用的,当类中没有定义构造函数,系统会自动调用默认的构造函数。同时要注意,构造函数是在类被实例化后才会调用的。
那既然有默认的,为什么我们还要自己定义呢?就是因为默认构造函数不是我们定义的,我们不知道它的功能,也无法控制它的功能,所以就需要用自定义的构造函数。
那么在定义构造函数的时候,需要注意以下几点:1.构造函数与类名相同;2.构造函数没有类型
于是我们修改成:
class MyFirstClass {
public:
MyFirstClass(char *); //声明构造函数
void DisplayClassName();
void EditClassName(char *strName);
private:
char *strClassName;
};
MyFirstClass::MyFirstClass(char *strName) { //定义构造函数
}
void MyFirstClass::DisplayClassName() {
printf("%s",strClassName);
}
void MyFirstClass::EditClassName(char *strName) {
strClassName=strName;
}
那么我们现在就给它添加功能,比如说:初始化strClassName。但是既然是要初始化,我们当然是要给它一个参数的,所以这里的参数就是strName。
(可能大家有疑惑,为什么楼主我不用string,这个问题是由于,第一、string在Dev-C++上是不会高亮显示的,个人感觉不正式,第二、C++的很多库函数都是用char *作为字符串类型的,为了迎合这些函数)
我们的构造函数已经写完了,于是我们干紧敲进IDE,来看看我们的第一个构造函数的应用,创建一个MyFirstClass实例test,然后编译一下
大家应该都会有一个问题,IDE报错了!
以下是Dev-C++的报错:
这是什么意思呢?
其实不难理解,我们在定义构造函数的时候,发现,构造函数其实是有一个参数的,而我们定义实例是不是没有任何有关参数传入的地方?
所以,这里就又要讲到类的初始化方法了
在类的实例化时,如果我们需要用到构造函数,且构造函数的参数没有默认值的话,我们就需要进行参数的传入,方式是,在实例定义时,实例名后加上一对小括号,然后在括号中写下参数。示例:
int main() {
MyFirstClass test("aa");
test.DisplayClassName();
return 0;
}
这就是一个正确的初始化方式,此时在test实例创建后,其中的strClassName已经被修改成我们传入的参数(即“aa”)。
那么这里很多人会问,函数参数的默认值是什么?
这里粗略地介绍一下,在函数的使用中,有时一个函数中会包括很多的功能,然而,每一次都需要你手动的去传入功能选择的参数什么的,是不是觉得很麻烦?
所以就有了参数默认值这一说法,在日常编程中,我们一般将带有参数默认值(或者叫做缺省值)的参数,写在函数参数表的右侧,具体如下:
//实际上,函数的声明与定义不能同时写缺省值
int fnA(int a,int b=0); //我们一般在函数声明时确定缺省值
//以上函数在调用时,可以只传入一个参数,此时b将会默认为0;总而言之,不带缺省值的参数必须手动传入
好了,我们已经学会了使用构造函数,那么当然的,来写个实例:
用C++类的知识,写出一个带有缺省值构造函数的类,用于记录单个学生的姓名,年龄(重点写出构造函数即可)
析构函数
上边我们知道了,构造函数是用来初始化成员的,那析构函数又是干什么的呢?
想必大家都用过new来申请空间内存,此时,申请的空间内存是在堆空间里的,申请完以后,如果我们不手动用delete来释放空间的话,这个空间就一直存在。
而我们有时候的类成员变量会用到指针,构造时赋予NULL值,然后再new新空间。如上的,我们需要手动释放这个空间,所以就要用到析构函数。析构函数的作用也就是将类中的资源释放。
那么同样的,析构函数也是一个无类型且与类同名的函数,编写的注意事项基本和构造函数一致,析构函数在声明定义时,函数名前要加上一个“~”符号来表示析构函数。但是有一点要注意,一般情况下,我们在释放资源的时候,不需要传入参数,所以析构函数一般无参数。
以下是对我们编写的类的完善:
class MyFirstClass {
public:
MyFirstClass(char *); //声明构造函数
~MyFirstClass(); //声明析构函数
void DisplayClassName();
void EditClassName(char *strName);
private:
char *strClassName;
};
MyFirstClass::MyFirstClass(char *strName) { //定义构造函数
strClassName=strName;
}
MyFirstClass::~MyFirstClass() { //定义析构函数
delete strClassName;
strClassName=NULL;
}
void MyFirstClass::DisplayClassName() {
printf("%s",strClassName);
}
void MyFirstClass::EditClassName(char *strName) {
strClassName=strName;
}
这里要注意,我们一般只对指针变量进行析构,其他的成员变量会随实例的消亡而一起消亡。在析构指针变量时,我们应当先delete指针指向的堆空间,空间释放后,此指针也就指向了一个不存在或者没有被系统分配的内存,所以需要将指针重新赋值为NULL(空地址)。
类的继承与派生
很多人刚刚接触这样一个概念,那么什么是继承与派生?通俗的来讲,继承与派生就相当于父与子。打个比方,A类继承出B类,那么A就是B的父类,B就是A的子类,我们称满足这样一个关系的两个或多个类,父类又称基类,子类又称派生类,即从基类派生出了派生类。
类该如何派生?请看下列代码:
class Vehicle
{
public:
Vehicle();
};
class Bus: Vehicle
{
public:
Bus();
};
基类是Vehicle(交通工具),派生类是Bus。大家仔细阅读代码,想必已经知道了如何继承类了。即在类名后加上冒号,后面跟上基类名。细心地同学会发现上文我加粗了“多个”两个字。事实上,一个派生类可以同时继承于多个基类。只继承于一个类称为单继承,继承于多个类称为多继承,多继承只需要在单继承的格式上在每个基类类名间打上逗号即可。
基类与派生类中的访问权限
首先,这里要强调一点。派生类中的成员如果与基类同名,则会覆盖基类。那么接下来就是基类于派生类中访问权限问题了。其实并不难理解,我们举个例子,你(相当于派生类)和你父亲(相当于基类)。你与你父亲都有可以公开的信息,如身高、体重等(相当于public属性成员)其他人都有权利知道这些信息;而你与你父亲又都有自己的隐私,如QQ密码、日记(相当于private属性成员),别人无权访问;你父亲与你是父子关系,因此有一些东西是只能与你分享的(相当于protected属性)出了你和与你同样身份的人才能访问。
问题就解决了,这就是基类与派生类的访问权限问题。