C++ 学习心得 之 类

默认分类 | 2020-04-09 21:39:49 | 阅读 1179 次 | 评论(4)

类是什么?

      在学习类之前,我们要知道一个问题——什么是类?

学习过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++的报错:

$7X9`I}RJ56]WCC[$}G3U4I.png (上传于2020-04-17 21:02:55)
$7X9`I}RJ56]WCC[$}G3U4I.png

这是什么意思呢?

      其实不难理解,我们在定义构造函数的时候,发现,构造函数其实是有一个参数的,而我们定义实例是不是没有任何有关参数传入的地方?

      所以,这里就又要讲到类的初始化方法了

在类的实例化时,如果我们需要用到构造函数,且构造函数的参数没有默认值的话,我们就需要进行参数的传入,方式是,在实例定义时,实例名后加上一对小括号,然后在括号中写下参数。示例:

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(空地址)。


【第四次更新,以后本文会停止较长时间更新。。。】

【非常抱歉,由于我的技术原因,这里暂时只能写这么多了,希望大家不要介意!】


-------------------------------- 作者在 2020-04-17 21:06:53 补充以下内容 --------------------------------

文章中的图片附件如果看不清,可以拖到新的标签页查看哦!
文章评论,共4条
Image
1楼: 刺简吗俄 发表于 2020-04-15 02:18   回复
Image
2楼: 斜始助台 发表于 2020-04-17 19:53   回复
Avatar
3楼: 雪影辰风 发表于 2020-04-17 21:05   回复
以下是引用斜始助台在2020-04-17 19:53的发言1
不错,希望续更。
谢谢资瓷,不过这里要给大家道个歉,因为我的学业问题,更新周期有点长,每次的更新量也会很少,请大家谅解,谢谢!
Avatar
4楼: 小白时光机 发表于 2020-04-19 02:11   回复
v支持
游客请输入验证码
浏览1179次
文章分类
文章归档
阅读排行
最新评论
  • 小白时光机:v支持
  • 雪影辰风:谢谢资瓷,不过这里要给大家道个歉,因为我的学业问题,更新周期有点长,每次的更新量也会很少,请大...