6-C++远征之封装篇[上]-学习笔记

C++远征之封装篇(上)

课程简介

  • 类(抽象概念),对象(真实具体)
  • 配角: 数据成员和成员函数(构成了精彩而完整的类)
  • 构造函数 & 析构函数(描述了对象的生生死死)
  • 对象复制和对象赋值 (使类的定义充满艺术)
  • 对象数组和对象指针 (将应用型态发挥到淋漓尽致)
  • this指针(影子,贯穿大戏始终,却很少崭露头角)

大家的思维模式会从面向过程到面向对象。处理更复杂程序。

学完之后,搞一个迷宫的寻路程序。

类和对象

  • 类(概念,从对象中抽象出的)和对象(具体的事物)

人类忠实的朋友:

mark

狗有自己的狗狗信息,也有它的技能。一条狗就是一个对象,多条狗为了便于管理,为一大群狗建立起表格。

mark

除过表格信息,他们还有共同技能: 叫和跑

从上面的狗狗,我们就可以抽象出一个狗类。

c++类定义

数据成员(属性)、成员函数(方法)

思考: 抽象出来的是否是狗的全部信息

结论:目的不同抽象出的信息不同,我们只抽象出我们自己需要的信息。

mark

通过电视机上的铭牌,我们可以得到它的属性信息。通过旋钮等,我们可以操作电视机。

封装: 选择性暴露(把实现细节封装起来,只暴露给用户他们关心的细节)。

mark

这些信息都在类中进行了定义,如何把想暴露的暴露出去,把想隐藏的隐藏起来呢?

访问限定符: public公共的,protected受保护的,private私有的

mark

内容总结:

  • 什么是类,什么是对象
  • 类的定义: 数据成员,成员函数
  • 类的访问限定符(public private)

C++类对象的定义

对象的实例化

从类中将对象实例化出来,就是计算机根据类这个模板,制造出多个对象的过程。

mark

实例化对象的两种方式:

  • 从栈中实例化
  • 从堆中实例化

从栈中实例化对象

2-2-StackInstantiatedObject/main.cpp

#include <iostream>
#include <stdlib.h>
using namespace std;

class TV
{
   public:
	  char name[20];
	  int type;

	  void changeVol();
	  void power();
};

int main(void)
{
	TV tv;//定义一个对象
	// TV tv[20]; //定义一个对象数组 
	return 0;
}//从栈中实例化对象  会自动回收

注意,务必要在类定义完成之后加上;

从堆中实例化对象.

  • 从堆中实例化的对象。需要我们手动管理new delete
  • 从栈中申请实例化的不需要手动维护。系统会自动回收。
int main(void)
{
	TV *p = new TV(); //在堆中实例化一个对象
	// TV *q = new TV[20]; // 定义一个对象数组

	delete p;
	// delete []q;

	return 0;
}//从堆中实例化对象

new运算符申请出来的内存就是在堆上的了。

实例化出的对象当然不是一个摆设,我们要通过访问对象的各种成员来达成目的。

对象成员的访问

通过不同方式实例化出来的对象,在对象成员,成员函数上的访问方式也不同。

栈实例化出来的对象使用.进行对象成员访问。


int main(void)
{
	TV tv;//定义一个对象
	tv.type = 0;
	tv.changeVol();
	return 0;
}//从栈中实例化对象  自动回收

堆实例化出来的对象使用->进行对象成员访问。

int main(void)
{
	TV *p = new TV();
	p -> type = 0;
	p -> changeVol();

	delete p;
	p = NULL;

	return 0;

}//从堆中实例化对象

当堆中实例化的对象是数组时,代码示例如下:

int main(void)
{
	
	TV *q = new TV[5];
	for (int i = 0; i < 5; ++i)
	{
		p[i] ->type =0;
		p[i] ->changeVol();
	}

	delete []q;
	p = NULL;
	return 0;

}//从堆中实例化对象

代码演示:

定义一个坐标类:包含x,y两个数据成员。分别打印x和打印y成员函数。

类名最好要能看出类的功能

2-2-CoordinateClassStackHeap/main.cpp

#include <stdlib.h>
#include <iostream>
using namespace std;

class Coordinate {
public:
	int x;
	int y;
	void printx(){
		cout << x << endl;
	}
	void printy() {
		cout << y << endl;
	}
};
int main(void)
{
	Coordinate coor;
	coor.x = 10;
	coor.y = 20;
	coor.printx();
	coor.printy();

	Coordinate *p = new Coordinate();
	if (NULL == p) {
		//failed
		return 0;
	}
	p->x = 100;
	p->y = 200;
	p->printx();
	p->printy();

	delete p;
	p = NULL;

	system("pause");
	return 0;
}

mark

申请内存失败情况处理,释放内存,指针置空。

c++初始化String

使用频繁,操作繁琐的数据:

mark

这些都是用的频繁但操作简单的数据类型。

  • char数组:

字符串数组操作函数: (strlen | strcat | strcpy | strcmp | strncpy |strncmp | strstr)

  • 字符串类型string类型

string类型

3-1-stringDemo/main.cpp

#include <stdlib.h>
#include <iostream>
#include <string>
using namespace std;

int main(void)
{
	string name = "mtianyan";
	string hobby("football");
	cout << name << endl;
	cout<<hobby << endl;
	system("pause");
	return 0;
}

mark

注意引入string.h,其命名空间也是std

初始化string对象的多个方式

string s1;        //s1为空串
string s2("ABC"); //用字符串字面值初始化s2;
string s3(s2);    //将s3初始化为s2的一个副本。
string s4(n,'c')  //将s4初始化为字符‘c’的n个副本。3个ccc s4='ccc'

string的常用操作

s.empty() //若s为空串,返回true,否则返回false;
s.size()  //返回s中字符的个数
s[n]      //返回s中位置为n的字符,位置从0开始
s1 + s2   //将两个字符串连接成新串,返回新生成的串
s1 = s2   //把s1的内容替换为s2的副本;
v1 == v2  //判定相等,相等返回true,否则返回false
v1 != v2  //判定不等,不等返回true,否则返回false

通过点的方式,说明s是一个对象

s1+s2 的思维陷阱

非法字符串link

纯字符串连接为非法操作。只有纯字符串和string,以及string与string是合法的。

代码演示

题目

因为要判定输入是不是为空。所以不能简单的使用cin而应该使用getline

3-1-NameStringDemo/main.cpp

#include <stdlib.h>
#include <iostream>
#include <string>
using namespace std;


int main(void)
{

	string name;
	cout << "please input your name:";
	getline(cin, name);
	if (name.empty()){
		cout << "input is null" << endl;
		system("pause");
		return 0;
	}
	if (name == "mtianyan")
	{
		cout << "you are a admin" << endl;

	}
	cout << "hello ," + name << endl;
	cout << "your name length is :" << name.size() << endl;
	cout << "your name frist letter is :" << name[0] << endl;

	system("pause");
	return 0;
}

c++中通过getline获取外界控制台输入(当包含可能输入为空情况)。一般情况还是cin好了。

管理员:

mark

名字为空:

mark

其他普通名字

mark

单元巩固

定义一个Student类,包含名字和年龄两个数据成员,实例化一个Student对象,并打印出其成两个数据成员

3-2-StudentClassString/main.cpp

#include <stdlib.h>
#include <iostream>
#include <string>
using namespace std;

/**
* 定义类:Student
* 数据成员:名字、年龄
*/
class Student
{
public:
	// 定义数据成员名字 m_strName 和年龄 m_iAge
	string m_strName;
	int m_iAge;
};

int main()
{
	// 实例化一个Student对象stu
	Student stu;
	// 设置对象的数据成员
	stu.m_strName = "mtianyan";
	stu.m_iAge = 21;

	// 通过cout打印stu对象的数据成员
	cout << stu.m_strName << " " << stu.m_iAge << endl;
	system("pause");
	return 0;
}

mark

C++属性封装之初识封装

数据的封装

mark

之前我们的用法是如上图所示。但是这样是不符合面向对象的指导思想。

面向对象的基本思想:(以对象为中心,以谁做什么表达程序逻辑)

  • 将所有问题转化为谁做什么,对象在程序中的所有行为,即调用成员函数解决问题。
  • 通过函数封装数据成员 set & get 方法。

mark

封装的好处: 符合面向对象的思想,在set中对于参数条件进行限制(防止数据非法,如年龄1000)

mark

数据只读不写(只读属性):只写get方法,不写set方法。

mark

轮子个数应该只能读,而不能被外界改变设置。

C代码属性封装代码演示

定义一个student类,含有如下信息

  • 姓名:name
  • 性别 :gender
  • 学分(只读) :score
  • 学习:study方法

4-2-StudentEncapsulation/main.cpp

#include <stdlib.h>
#include <iostream>
#include <string>

using namespace std;
class Student {
public:

	string getName() const 
	{
		return m_strName; 
	}

	void setName(string _name) 
	{ 
		m_strName = _name; 
	}

	string getGender()
	{
		return m_strGender;
	}

	void setGender(string val)
	{
		m_strGender = val;
	}

	int getScore() {
		return m_iScore;
	}

	void study(int _score) {
		m_iScore += _score; 
	}

	void initScore() {
		m_iScore = 0;
	}
private:
	string m_strName;
	string m_strGender;
	int m_iScore;

};

int main(void)
{
	Student stu;
	stu.initScore();
	stu.setName("天涯明月笙");
	stu.setGender("男");
	stu.study(5);
	stu.study(3);

	cout << stu.getName() << " " << stu.getGender() << " " << stu.getScore() << endl;
	
	system("pause");
	return 0;
}

mark

不赋初值:

mark

注意,赋初值。后面会学到构造函数,它是专用来初始化的。

单元巩固

定义一个Student类,包含名字一个数据成员,使用get和set函数封装名字这个数据成员。在main函数中通过new实例化对象,并打印其相关函数。

4-3-StudentHeapInstance/main.cpp

#include <stdlib.h>
#include <iostream>
#include <string>
using namespace std;

/**
  * 定义类:Student
  * 数据成员:m_strName
  * 数据成员的封装函数:setName()、getName()
  */
class Student
{
public:
    // 定义数据成员封装函数setName()
    void setName(string _name) 
	{ 
		m_strName = _name; 
	}
    
    
    // 定义数据成员封装函数getName()
    string getName() const 
	{
		return m_strName; 
	}
    
//定义Student类私有数据成员m_strName
private:
	string m_strName;

};

int main()
{
    // 使用new关键字,实例化对象
	Student *str = new Student();
    // 设置对象的数据成员
	str->setName("mtianyan");
    // 使用cout打印对象str的数据成员
    cout << str ->getName() << endl;
    // 将对象str的内存释放,并将其置空
	delete str;
	str = NULL;
	system("pause");
	return 0;
}

mark

类内定义与内联函数

内联函数关键字: inline

inline void fun()
{
	cout << "hello" << endl;
}

mark

  • 普通函数在调用时先找到函数体,执行完函数内容后再返回调用函数的语句;
  • 内联函数inline在编译时将函数体代码和实参替换函数调用语句,执行速度更快,但只局限于简单的函数。

类内定义与类外定义

  • 类内定义是指在类里面定义成员函数的函数体(优先编译为inline)

mark

mark

不会把inline写出来,但是会优先编译为inline

  • 类外定义是指在类的外面先定义好这个函数如何运作,然后再通过类里面声名这个函数。

类外定义分为两种:

  • 同文件类外定义: 成员函数虽然定义在类的外面,但是它与类在同一个文件中定义
  • 分文件类外定义: 成员函数

同文件类外定义例子: Car.cpp

同文件类外定义

为了标识这是属于汽车car的成员函数:car::

同文件类外定义是突击队的话,分文件类外定义就是正规军了。

几乎所有的c++程序,专业点的程序员都会分文件类外定义。

分文件类外定义

一个.h头文件,名称与类名一致。必须包含.h文件,并使用car::

分文件类外定义

类外定义代码演示

要求:

题目要求

5-2-1-OutClassDefine1/main.cpp

#include <stdlib.h>
#include <iostream>
#include <string>

using namespace std;
class Teacher {
public:
	void setName(string name);
	string getName();

	void setGender(std::string val);
	string getGender();

	void setAge(int _age);
	int getAge();

	void teach();
private:
	string m_strName;
	string m_strGender;
	int m_iAge;
};

string Teacher::getName()
{
	return m_strName;
}
void Teacher::setName(string name)
{
	m_strName = name;
}
string Teacher::getGender() 
{
 return m_strGender;
}
void Teacher::setGender(string val) 
{
 m_strGender = val; 
}
int Teacher::getAge() {
	return m_iAge;
}
void Teacher::setAge(int _age) {
	m_iAge = _age;
}
void Teacher::teach() {
	cout << "现在上课" << endl;
};
int main(void)
{
	Teacher teacher;
	teacher.setAge(21);
	teacher.setName("mtianyan");
	teacher.setGender("男");
	cout << teacher.getName() << " " << teacher.getGender() << " " << teacher.getAge() << endl;
	teacher.teach();
	system("pause");
	return 0;
}

mark

分文件类外定义实现。

5-2-2-MultipleFilesOutClassDefine/main.cpp

在解决方案,头文件处右键添加新建项:头文件: Teacher.h
在源文件右键添加新建项:cpp: Teacher.cpp

Teacher.h只存放类的声明

#include <string>
using namespace std;

class Teacher {
public:
	void setName(string name);
	string getName();

	void setGender(std::string val);
	string getGender();

	void setAge(int _age);
	int getAge();

	void teach();
private:
	string m_strName;
	string m_strGender;
	int m_iAge;
};

Teacher.cpp:只存放类外方法的定义

#include "Teacher.h"
#include <iostream>
#include <string>
using namespace std;

string Teacher::getName()
{
	return m_strName;
}
void Teacher::setName(string name)
{
	m_strName = name;
}
string Teacher::getGender() 
{
	return m_strGender;
}
void Teacher::setGender(string val) 
{ 
	m_strGender = val; 
}
int Teacher::getAge() {
	return m_iAge;
}
void Teacher::setAge(int _age) {
	m_iAge = _age;
}
void Teacher::teach() {
	cout << "现在上课" << endl;
};

main.cpp存放类的实例化等以及程序主入口

#include <stdlib.h>
#include <iostream>
#include <string>
#include "Teacher.h"
using namespace std;

int main(void)
{
	Teacher teacher;
	teacher.setAge(21);
	teacher.setName("mtianyanMultiple");
	teacher.setGender("男");
	cout << teacher.getName() << " " << teacher.getGender() << " " << teacher.getAge() << endl;
	teacher.teach();
	system("pause");
	return 0;
}

mark

main.cpp的头文件为:

#include <stdlib.h>
#include <iostream>
#include "Teacher.h"
using namespace std;

Teacher.h的头为

#include <string>
using namespace std;

tercher.cpp的头为:

#include "Teacher.h"
#include <iostream>
#include <string>
using namespace std;

注意:不要把<string>写成<string.h>

c++构造函数讲解

  • 实例化的对象是如何在内存中存储的?
  • 类中的代码又是如何存储的?
  • 数据与代码的关系?

对象的结构

内存分区

mark

  • 栈区 : int x=0; int*p=NULL; 内存由系统管理。
  • 堆区 : int *p = new int[20]; new & delete
  • 全局区: 存贮全局变量及静态变量
  • 常量区: string str = "hello";
  • 代码区: 存贮编译之后的二进制代码

定义一个汽车类,在类被实例化之前,是不会占用栈或者堆内存的。
但是当它实例化出car1 ,car2 ,car3 。每个对象在栈上开辟一段内存,用来存储各自的数据。不同的变量,占据不同的内存。

代码区只有一份代码。

类实例化内存

对象初始化

mark

坦克大战,游戏开始初始化坦克位置。

对象初始化分为两种:

  • 有且仅有一次的初始化
  • 根据条件初始化

对于有且仅有一次的初始化:

  • 思考: 初始化函数如何避免误操作?

如,我们写代码忘记调用了初始化函数,重复调用了初始化函数。

构造函数的规则和特点:

  • 构造函数在对象实例化时被自动调用;只需要将初始化代码写在构造函数内。

构造函数在对象实例化时别调用,且仅被调用一次

  • 构造函数与类同名;
  • 构造函数没有返回值;
  • 构造函数可以被重载;遵循重载函数的规则。
  • 实例化对象时仅用一个构造函数;
  • 用户没有定义构造函数时,编译器会自动生成一个构造函数(这个构造函数中没有做任何事情)。

无参构造函数

class Student
{
public:
	Student(){
		m_strName = "jim";
	} // 与类名相同,无返回值。
private:
	string m_strName;
}

有参构造函数

class Student
{
public:
	Student(string name){
		m_strName = name;
	}
private:
	string m_strName;
}

重载构造函数

class Student
{
public:
	Student(){
		m_strName = "jim";
	}
	Student(string name){
		m_strName = name;
	}// 重载: 参数个数不同,参数类型不同,参数调用顺序不同。
private:
	string m_strName;
}

C++构造函数代码演示

要求

	Teacher();
	Teacher(string name, int age=20);

这样的计算机是可以分辨的,但是如果给name也给默认值。那么将无法通过编译。
提示重载函数的调用不明确。两个不能共存,但是可以单独存在。

6-2-ConstructorFunction

Teacher.h

#include <string>
using namespace std;

class Teacher {
public:
	Teacher();
	Teacher(string name, int age=20);
	void setName(string name);
	string getName();
	void setAge(int _age);
	int getAge();
	void teach();
private:
	string m_strName;
	int m_iAge;
};

teacher.cpp

#include "Teacher.h"
#include <iostream>
#include <string>
using namespace std;

Teacher::Teacher() {
	m_strName = "jim";
	m_iAge = 5;
	cout << "Teacher()" << endl;
}

Teacher::Teacher(string name, int age) {
	m_strName = name;
	m_iAge = age;
	cout << "Teacher(string name, int age)" << endl;
}

string Teacher::getName()
{
	return m_strName;
}
void Teacher::setName(string name)
{
	m_strName = name;
}
int Teacher::getAge() {
	return m_iAge;
}
void Teacher::setAge(int _age) {
	m_iAge = _age;
}
void Teacher::teach() {
	cout << "现在上课" << endl;
};

main.cpp

#include <iostream>
#include <string>
#include "Teacher.h"
using namespace std;

int main(void)
{
	Teacher teacher; //无参数实例化,这里能正常运行是因为我们没有给参数都加默认值。
	Teacher t2("merry", 15);//有参数实例化
	Teacher t3("james");//,构造函数有默认值20
	cout << teacher.getName() << " " << teacher.getAge() << endl;
	cout << t2.getName() << " " << t2.getAge() << endl;
	cout << t3.getName() << " " << t3.getAge() << endl;
	teacher.teach();
	system("pause");
	return 0;
}

mark

构造函数除了重载还可以给参数赋默认值。不调用时,编译可以通过的。

默认构造函数

mark

无论从堆中还是栈中实例化对象,都有一个特点是不用传参数。

mark

这样的构造函数可以像如上图所示定义,本身不带参数。

mark

或带了参数的同时携带着所有参数的默认值。

  • 把实例化时不需要传递参数的构造函数称为默认构造函数。上面的两种(以及下面代码),都是默认构造函数。
Student(){}
Student(string name = "jim");

构造函数初始化列表

class Student
{
public:
	Student():m_strName("jim"),m_iAge(10){} //构造函数初始化列表进行初始化
private:
	string m_strName;
	int m_iAge;

}

构造函数后面使用一个冒号隔开,对于多个数据成员初始化,中间要由逗号隔开。赋值要用括号。

初始化列表的特性

  • 初始化列表先于构造函数执行
  • 初始化列表只能用于构造函数
  • 初始化列表可以同时初始化多个数据成员,并且效率高,速度快。

初始化列表的必要性:

思考: 表面看起来初始化列表的工作是可以由构造函数代劳的,要它还有何用?

下面举例说明。

计算圆,其中pi是一个常量,所以用const修饰。

class Circle
{
public:
	Circle(){m_dPi=3.14} //错误,给常量赋值
private:
	const double m_dPi;
}

无法对于我们的静态成员变量在构造函数中赋初值,解决方案: 在初始化列表中赋初值。

class Circle
{
public:
	Circle():m_dPi(3.14){} // 正确,使用初始化列表
private:
	const double m_dPi;
}

C++初始化列表编码

要求

定义有参默认构造函数,使用初始化列表初始化数据。

6-5-ParameterConstructorFunctionInitList

Teacher.h

#include <string>
using namespace std;

class Teacher {
public:
	Teacher(string name ="hi", int age=1,int m =100); // 有参默认构造函数
	void setName(string name);
	string getName();

	void setAge(int _age);
	int getAge();

	int getMax();
	void setMax(int m_iMax);
private:
	string m_strName;
	int m_iAge;
	const int m_iMax;

};

Teacher.cpp

#include "Teacher.h"
#include <iostream>
#include <string>
using namespace std;

Teacher::Teacher(string name, int age ,int m):m_strName(name),m_iAge(age),m_iMax(m)
{
	cout << "Teacher(string name, int age)" << endl;
}

string Teacher::getName()
{
	return m_strName;
}
void Teacher::setName(string name)
{
	m_strName = name;
}
int Teacher::getAge() {
	return m_iAge;
}
void Teacher::setAge(int _age) {
	m_iAge = _age;
}
int Teacher::getMax() {
	return m_iMax;
}
void Teacher::setMax(int m_iMax) {
	m_iMax = m_iMax;
}

main.cpp

#include <stdlib.h>
#include <iostream>
#include <string>
#include "Teacher.h"
using namespace std;

int main(void)
{
	Teacher teacher("merry",12,150);
	cout << teacher.getName() << " " << teacher.getAge() <<" "<<teacher.getMax()<< endl;
	system("pause");
	return 0;
}

mark

一个类可以没有默认构造函数, 有别的构造函数也可以实例化对象。在构造函数声明的时候加了默认值,就不需要在定义的时候加默认值。

常量如果不在初始化列表中初始化,会提示必须在初始值设定项列表中初始化

c++拷贝构造函数

mark

上图,这是我们定义的Student类

使用时。

int main()
{
	Student stu1;
	Student stu2 =stu1;
	Student stu3(stu1);
	return 0;
}

实例化了三个对象,但上述代码只会执行一次构造函数内的代码。

三次实例化调用了构造函数,但不是我们自己定义的,而是拷贝构造函数。

实例化对象一定会调用构造函数,但是像上面代码这种,将不使用我们定义的构造函数,而使用拷贝构造函数。

拷贝构造函数:

定义格式: 类名(const 类名& 变量名)

Student(){
	m_strName = "jim";
}
Student(const Student& stu){

}

传入一个引用。

  • 如果没有自定义拷贝构造函数则系统自动生成一个默认的拷贝构造函数
  • 当采用直接初始化(stu3(stu1);)或者复制初始化(stu2 =stu1;)实例化对象时系统自动调用拷贝构造函数

构造函数总结:

  • 无参构造函数:一定为默认构造函数

  • 有参构造函数:

      - 参数带默认值。如果参数都带默认值,那么也将是默认构造函数
      - 参数无默认值
    

mark

系统自动生成的函数:

  • 普通构造函数
  • 拷贝构造函数

一旦我们自行定义,系统就不会再生成了。

初始化列表:

  • 普通构造函数
  • 拷贝构造函数

C++拷贝构造函数代码演示

要求

6-8-CopyConstructorFunction

Teacher.h

#include <string>
using namespace std;

class Teacher {
public:
	Teacher(string name ="mtianyan", int age=21,int m =100);
	Teacher(const Teacher& tea); //拷贝构造函数
	void setName(string name);
	string getName();
	void setAge(int _age);
	int getAge();
private:
	string m_strName;
	int m_iAge;
};

teacher.cpp

#include "Teacher.h"
#include <iostream>
#include <string>
using namespace std;

Teacher::Teacher(string name, int age ,int m):m_strName(name),m_iAge(age)
{
	cout << "Teacher(string name, int age)" << endl;
}
Teacher::Teacher(const Teacher& tea) {
	cout << "Teacher(const Teacher &tea)" << endl;
}
string Teacher::getName()
{
	return m_strName;
}
void Teacher::setName(string name)
{
	m_strName = name;
}
int Teacher::getAge() {
	return m_iAge;
}
void Teacher::setAge(int _age) {
	m_iAge = _age;
}

main.cpp

#include <stdlib.h>
#include <iostream>
#include <string>
#include "Teacher.h"
using namespace std;

void test(Teacher t) {

}

int main(void)
{
	Teacher teacher;
	Teacher t2 = teacher;
	Teacher t3(t2);  // 这里无论使用t2还是teacher都只会调用拷贝构造函数
	test(teacher); //函数使用已实例化的对象时调用。
	cout << teacher.getName() << " " << teacher.getAge() << endl;
	system("pause");
	return 0;
}

mark

函数使用已实例化的对象作为参数时就会调用拷贝构造函数。如test()

拷贝构造函数的参数是确定的,不能重载

析构函数

mark

构造函数是对象来到世界的第一声哭泣
析构函数是对象离开世界的最后一声叹息。

析构函数在对象销毁时会自动调用,归还系统资源:

定义格式:

~类名() //不加任何的参数
class Student
{
public:
	Student(){cout << "Student" << endl;}
	~Student(){cout << "~Student" << endl;}
}

析构函数不允许加任何参数。

析构函数的价值:

如果我们在定义数据时使用了指针,使用指针指向了堆中分配的内存(new)。在对象销毁时我们要释放这些内存,释放这些内存最好时机是对象被销毁之前。

class Student
{
public:
	Student(){m_pName = new char[20];}
	~Student(){delete []m_pName;}
private:
	char *m_pName;
}
  • 析构函数没有返回值,没有参数也不能重载
  • 如果没有自定义的析构函数则系统自动生成
  • 析构函数在对象销毁时自动调用

对象的生命历程:

mark

  1. 申请内存
  2. 初始化列表(此时其他的值不确定)
  3. 构造函数
  4. 参与运算
  5. 析构函数
  6. 释放内存

C++析构函数代码演示

要求

按回车后一瞬间可以看到析构函数被调用

6-11-DestructorFunction

Teacher.h

#include <string>
using namespace std;

class Teacher {
public:
	Teacher(string name ="mtianyan", int age=21,int m =100); // 构造
	Teacher(const Teacher &tea); // 拷贝构造
	~Teacher(); // 析构
	void setName(string name);
	string getName();
	void setAge(int _age);
	int getAge();
private:
	string m_strName;
	int m_iAge;

};

Teacher.cpp

#include "Teacher.h"
#include <iostream>
#include <string>
using namespace std;

Teacher::Teacher(string name, int age ,int m):m_strName(name),m_iAge(age)
{
	cout << "Teacher(string name, int age)" << endl;
}
Teacher::Teacher(const Teacher &tea) {
	cout << "Teacher(const Teacher &tea)" << endl;
}
Teacher::~Teacher() {
	cout << "~Teacher()" << endl;
}
string Teacher::getName()
{
	return m_strName;
}
void Teacher::setName(string name)
{
	m_strName = name;
}
int Teacher::getAge() {
	return m_iAge;
}
void Teacher::setAge(int _age) {
	m_iAge = _age;
}

main.cpp

#include <stdlib.h>
#include <iostream>
#include <string>
#include "Teacher.h"
using namespace std;

void test(Teacher t) {

}

int main(void)
{
	Teacher t1;
	Teacher t2(t1);
	Teacher *p = new Teacher();
	delete p;
	p = NULL;
	system("pause");
	return 0;
}

mark

在按下回车的瞬间,可以看到两行输出如下。这是我们t1 t2在销毁时调用析构函数。

~Teacher()

堆栈中的对象在销毁时都会自动调用析构函数。

总结

梳理前面学过的 & 剧透后面的

围绕类与对象展开。

mark

类由成员函数和数据成员组成。担心自己的类与其他人重名,类之上可以定义命名空间。

mark

数据成员:

普通数据成员,(数据类型普通) int, char, char[], string;
初始化列表(const成员);静态数据成员;对象成员

mark

成员函数:

对于数据成员进行封装, 属性封装函数(get,set); 一般功能函数;特殊函数:构造函数(根据参数不同,拷贝构造函数-默认构造函数);析构函数.

mark

成员函数 (参数默认值;函数重载;引用;const;)

对象实例化(堆中实例化,栈中实例化)

mark

对象可以是个引用?对象可以用const修饰么?

  • 成员函数中如何辨识数据成员。
  • 多个对象如何共享数据
  • 对象成员初始化

综合练习:

定义一个Student类,包含名字一个数据成员,定义无参构造函数、有参构造函数、拷贝构造函数、析构函数及对于名字的封装函数,在main函数中实例化Student对象,并访问相关函数,观察运行结果。

7-2-StudentDemo/main.cpp

#include <stdlib.h>
#include <iostream>
#include <string>
using namespace std;
/**
* 定义类:Student
* 数据成员:m_strName
* 无参构造函数:Student()
* 有参构造函数:Student(string _name)
* 拷贝构造函数:Student(const Student& stu)
* 析构函数:~Student()
* 数据成员函数:setName(string _name)、getName()
*/
class Student {
public:
	Student() {
		m_strName = "";
	};
	Student(string _name) {
		m_strName = _name;
	};
	Student(const Student& stu) {

	};
	~Student() {

	};
	void setName(string _name) {
		m_strName = _name;
	};
	string getName() {
		return m_strName;
	};
private:
	string m_strName;
};

int main(void)
{
	// 通过new方式实例化对象*stu
	Student *stu = new Student();
	// 更改对象的数据成员为“mtianyan”
	stu->setName("mtianyan");
	// 打印对象的数据成员
	cout << stu->getName() << endl;
	delete stu;
	stu = NULL;
	system("pause");
	return 0;
}

注意new方式实例化的对象不要忘记delete以及指针置空。

mark

posted @ 2018-07-25 13:47  天涯明月笙  阅读(372)  评论(0编辑  收藏  举报