C++复习之路七——类

类的主要思想就是数据抽象和封装。

一、定义一个类

我们首先来看一个例子:

class Test  // 类名
{
   public:  //访问说明符,public之后的所有成员可以在整个程序内被访问。
   void print() // 成员函数, 打印出成员变量的值。
  {
     cout << m_a << endl;
  }
   private: //访问说明符, private之后的成员只能被类的成员函数访问,但是不能被使用该类的代码访问
   int m_a; //类的成员变量
};

通过上面的例子我们就可以知道类是如何定义的。

我们除了可以通过class 来定义一个类,还可以通过struct来定义一个类。struct 在c语言中是用来定义一个结构体的。

struct和class定义的类的区别:

struct:用struct定义的类,在第一个访问说明符出现之前的成员都是public

class:用class定义的类,在第一个访问说明符出现之前的成员都是private

其他都是一样的!

类的组成

类由类名,访问说明符,成员变量和成员函数构成,还是看第一个例子,我们可以很清晰的知道类的组成。m_a是类的成员变量,print()函数是类的成员函数,类名是Test。

二、构造函数

构造函数也是类的成员函数,构造函数的名字和类名相同,但是没有返回值,其他的都是和普通的函数一样的,所以构造函数是可以重载的。

我们也来看一个有构造函数的例子:

class Test
{
public:
	Test();//无参构造函数
	Test(int a, int b);//带两个参数的构造函数
	void printT();
private:
	int m_a;
	int m_b;
};

类里面的Test()函数就是构造函数,这两个构造函数的形参不一样所以是重载函数。如果对重载函数有什么不理解的可以去看一下我的这篇博客。C++复习之路六——函数, 这个里面有对函数重载的介绍。

构造函数的作用

构造函数主要用来初始化类对象的数据成员。

1、默认构造函数

如果我们没有显示的定义一个构造函数,编译器会为我们隐式的定义一个构造函数用来对类的对象进行初始化工作。相反如果我们显示的定义了一个构造函数,编译器就不会给我们定义默认的构造函数。

建议:我们在定义一个类的时候最好定义一个构造函数,因为我们不能确定编译器给我们生成的默认构造函数是否符合我们的需求,也许默认构造函数会执行错误的操作。

2、拷贝构造函数

拷贝构造函数也是构造函数的一种,但是也是比较难理解的一种。所以使用c++的朋友一定要注意拷贝构造函数的使用。

拷贝构造函数的声明,以上面的Test类为例。

Test(const Test& p); // 拷贝构造函数

【注意】:拷贝构造函数的第一个参数必须是类本身的引用类型

拷贝构造函数调用的几种场景。

①一个类的对象使用等号初始化另一个类的对象的时候。(拷贝初始化)

Test t1(1, 2);
Test t2 = t1;

t1是一个Test类的对象,第二条语句定义了另一个Test对象t2并且用t1初始化这个对象,这个时候就调用了拷贝构造函数。(完整的代码会在最后贴出来,希望能够帮助读者更好的理解)

一个类对象使用括号来初始化另一个类对象的时候。(直接初始化)

Test t1(1, 2);
Test t3(t1);

第二条语句是用t1对象初始化t3对象,这个时候也会调用拷贝构造函数。

③将一个类的对象作为实参传递给一个非引用类型的形参。

void diaoyong(Test obj) //参数为一个非引用类型的Test类的形参
{
	cout << "传递Test类函数执行了" << endl;
}
Test t1(1, 2);//定义一个Test类的对象。
diaoyong(t1);//将实参传递给形参。

在我们调用diaoyong()函数的时候,形参将会拷贝实参的值,这个时候就会调用拷贝构造函数。

④从一个返回类型为非引用类型的函数返回一个对象

Test fanhui()
{
	Test t4(3, 5); 
        return t4;  
}
上面这个例子在fanhui()函数中定义了一个Test t4,然后返回这个对象,在 返回的过程中会产生一个匿名对象,并且用这个t4对象来初始化这个匿名对象。所以这个时候就会调用拷贝构造函数。然后函数结束就会把t4这个对象析构掉!
三、析构函数

析构函数的作用

释放对象使用的资源,并销毁对象的非static成员。

析构函数是类的一个成员函数,名字右波浪号(~)和类名构成。

~Test();//析构函数的声明

【注意】:析构函数没有返回值,也不接受参数,所以析构函数不能够重载

析构函数是编译器自动调用的,在以下几种情况会自动调用:

1、变量在离开其作用域时被销毁。

2、当一个对象被销毁时,其成员被销毁。

3、容器(无论是标准库容器还是数组)被销毁时,其元素被销毁。

4、对于动态分配的对象,当对指向它的指针应用delete运算符时被销毁。

5、对于临时对象,当创建它的完整表达式结束时被销毁。

四、类对象的定义

我们就用之前的Test 类来说明。

class Test
{
public:
	Test();//无参构造函数
	Test(int a, int b);//带两个参数的构造函数
	Test(const Test& p); // 拷贝构造函数
	~Test();//析构函数
private:
	int m_a;
	int m_b;
};

如果我们想定义一个无参的对象,我们可以这样定义:

Test t1;//定义了一个无参的对象

也许有人会这样写,Test t1(); 注意这样写是不正确的,虽然我们在无参构造函数声明的时候写了(), 但是如果定义的时候也加上了(),编译器会报这样的错误:“Test t1(void)”: 未调用原型函数(是否是有意用变量定义的?)。

定义带两个参数的对象。

Test t1(1, 2);//定义了带两个参数的对象

这样我们的构造函数会用两个参数去初始化Test类里面的成员变量。

当然我们还可以这样来定义一个对象

Test t1(1, 2);
Test t2 = t1;

这样是用t1对象初始化t2对象,这个时候就会调用拷贝构造函数。

最后就是本篇博客的完整代码,以上的所有代码都是从这个程序选出来的。如果有什么不懂的地方可以在评论里面问我。

/*
*这断代码展示了何时调用构造函数,以及构造函数的重载
*还展示了四种调用拷贝构造函数的情形
*还有何时调用析构函数。
*如果想要更清楚的了解何时调用构造函数,以及析构函数,可以加上断点,不断的调试
*相信经过调试都会一清二楚的。
*/

#include <iostream>

using namespace std;

class Test
{
public:
	Test();//无参构造函数
	Test(int a, int b);//带两个参数的构造函数
	Test(const Test& p); // 拷贝构造函数
	~Test();//析构函数
public:
	void printT();

private:
	int m_a;
	int m_b;
};

Test::Test()
{
	cout << "无参构造函数" << endl;
}

Test::Test(int a, int b)
{
	cout << "两个参数的构造函数被调用了" << endl;
	m_a = a;
	m_b = b;
}

Test::~Test()
{
	cout << "析构函数被调用了" << endl;
}

Test::Test(const Test& p)
{
	cout << "调用了拷贝构造函数" << endl;
	m_a = p.m_a;
	m_b = p.m_b;
}

void Test::printT()
{
	cout << "m_a = " << m_a << endl;
	cout << "m_b = " << m_b << endl;
}

void diaoyong(Test obj)
{
	cout << "传递Test类函数执行了" << endl;
}

//这个函数返回的时候回生成一个临时变量(匿名对象),用t4初始化这个匿名对象会调用拷贝
//构造函数
Test fanhui()
{
	Test t4(3, 5);  
	t4.printT();   
	return t4;  
}
void fun() //这个函数的作用就是能够完整的展示对象的声明周期。(主要是用来看析构函数是否被调用)
{
	Test t1(1, 2);
	diaoyong(t1);// 这个函数在把实参拷贝给形参的时候也会调用拷贝构造函数。
	t1.printT();
	Test t2 = t1;//这个时候会调用拷贝构造函数。
	t2.printT();
	cout << "t3开始了" << endl;
	Test t3(t2); // 这样也会调用拷贝构造函数。
	t3.printT();
	 Test t5 = fanhui();
}

int main()
{
	fun();

	system("pause");
	return 0;
}


猜你喜欢

转载自blog.csdn.net/y____xiang/article/details/79982233