类与对象

类与对象

尤其要注意,声明类的时候一定要在最后面加上分号,因为声明类也算是一种语句。语句结束的时候一定要加上分号的。

9.3构造函数

构造函数是对象在被创建时调用的函数,顾名思义,它的作用是帮助创建一个类的实例,即对象。创建对象本质上和创建简单变量没什么区别,都必须占用存储空间,都有名字,只不过对象更复杂,更智能。
它一般用在初始化对象,动态分配内存上。
一般形式如下:

Human (string InputName = "Adam",int InputAge = "25")
{
	//构造函数语句
}

构造函数有默认的,也有重载的(Overloaded)。在一个类的定义中,可以同时存在多个构造函数的定义(或者说实现)。可以有默认构造函数,也可以没有默认构造函数。但是,如果没有构造函数的话,一般要求在实例化对象的同时初始化某些私有变量的值(类似于使用函数时输入的参数)。
比如:

class Human{
	private:
		string Name;
		int Age;
	public:
		Human(){//没有参数
			//构造函数实现
		}
		Human(string HumansName){//需要初始化一个变量
			//构造函数实现
		}
		Human(string HumansName, int HumansAge){//初始化两个
			//构造函数实现
		}
}

类似于这样,三个同名的重载的构造函数,能够实现灵活的参数初始化选项。也就使得对象更容易使用了。

9.4 析构函数

什么是析构函数?与构造函数相反,它的作用是销毁对象,在对象作用域结束时调用。

~Human()

像这样。

...
~Human(){
	cout << "调用析构函数,正在清除..." << endl;
	if(Buffer != NULL)
		delete [] Buffer;
}
...

上面的析构函数的作用是在对象作用域结束时解除Buffer的内存占用。(Buffer是一个char指针,指向一个C风格string数组的开头)。
析构函数有很多用途,上面提到的只是其中一种。
析构函数只能有一个,不能重载。
没有设置它的话,编译器会自动创建一个dummy(伪)析构函数,但这个dummy不释放用new命令动态分配的内存。

9.5 复制构造函数

浅复制与深复制

浅复制指的是直接把对象当参数传递到函数中。这样,函数会创建一个新对象,但是这个新对象的内存地址与作为实参的对象的内存地址是一样的。这本身不会导致程序崩溃。但是,因为有两个对象,会调用两次析构函数,所以会delete两次,但是同一个内存是不能被delete两次的。所以程序会崩溃。

void UseMyString(MyString Input){}		//这是浅复制

相比之下,深复制就安全得多。深复制时,函数会在另外一个内存空间创建一个复制的对象。下面是通过设置一个复制构造函数来实现的。因为函数会创建一个复制的对象,这个新的对象需要一个构造函数。

MyString(const MyString& CopySource)		//这是深复制
{		//const 表示不允许改变原来的对象
}		//符号 & 表示引用
... 
//然后是函数的定义,可见这里的定义与浅复制是是一样的
 void UseMyString(MyString Input){}
 //但不同的是传对象进去时,因为有“复制析构函数”的存在,会创建另外的空间

另外一种深复制的方法是,先实现复制赋值运算符,然后直接令新对象等于就对象。But, that's another story at Chatpet 12.

移动构造函数

???

9.6 构造和析构的其它用途

  1. 通过声明私有的复制构造函数赋值运算符,可以禁止对该对象的复制操作,而无需给这两者提供实现。
  2. 上面的方法可以保证一个对象被创建后就无法复制,但有时候还要确保一个类只能有一个实例,这种类可以称做单例类。它的声明方法是将构造函数(包括默认和复制)、赋值运算符声明为私有的,并且在公有成员里面声明一个静态实例成员。
    如下所示:
#include <iostream>
#include <string>
using namespace std;
class  President{
private:
	President(){};
	President(const President&);
	const President& operator=(const President);
	string Name;
public:
	//受控的实例化
	//下面这条语句声明了这个单例类的“单例”。
	static President& GetInstance(){
		static President OnlyInstance;
		return OnlyInstance;
	}

	string GetName(){
		return Name;
	}

	void SetName(string InputName){
		Name = InputName;
	}
	void ShowName(){
		cout << "总统的名字是" << Name << endl;
	}
};
int main(void)
{
	President& OnlyPresident = President::GetInstance();
	OnlyPresident.SetName("***");
	//被注释掉的是被禁止的复制操作
	//President Second;
	//President* Third = new President;
	//President fourth = OnlyPresident;
	//OnlyPresident = President::GetInstance();
	cout << "The Name of President is: ";
	cout << President::GetInstance().GetName() << endl;
	return 0;
}

这些语句都是在类外面的语句。而因为所有构造函数都被声明在了private里面,所以从main()这里是无法使用这些构造函数的。也就是说,要声明唯一的那个单例类,只能在类President的public里面实现。
所以,我们可以发现,之所以构造函数一般都声明在public里面,就是因为这样才能让main()可以访问它们,通过它们声明对象。到这里,更加理解了类的特性——私有性的含义了。

  1. 禁止在栈(stack)中实例化的类
    通过将析构函数声明为private,可以禁止程序员(我自己—_—)在栈(stack)中创建该类的实例。但是可以在堆(heap)中创建。

未解决的问题:

  1. 什么是堆(heap)?什么是栈(stack)?两者的区别?
    参见CSDN文章:堆和栈的区别(转过无数次的文章)
  2. 什么是内存泄漏?
  3. 仍然不太理解静态函数。

心得一:没有创建对象时,就只能通过类的定义(或实现)直接访问成员函数,比如:

MonsterDB::DestroyInstance(pMyDatabase);

9.7 This指针

this是一个指针,它指向当前对象的地址。一般来说,如果使用一个成员函数调用另一个成员函数,this指针会被隐式地嵌入到所调用的那个成员函数的参数列表中。比如说

class Human{
	private:
		void Talk(string Statement){
		cout << Statement;
		}
	public:
		void InstroduceSelf(){
		Talk("Bla bla");
		}
}

中的Talk("Bla bla"),其实是Talk(this, "Bla bla")
但是,需要注意:调用静态函数时,不会隐式地传递this指针,因为静态函数不与类实例相关联,而由所有实例共享。如果要在静态函数中使用实例变量,则应显式地声明一个形参,让调用者将实参设置为this指针。

9.8 将sizeof()用于类

sizeof()一个是个运算符,它不仅用于之前的数据类型,也同样可以用于确定类这种数据类型所需要的内存(单位:字节),但是只计算数据,而不计算成员函数以及成员函数中定义的局部变量。
sizeof()的结果受字填充(word padding)的影响。比如对象中有一个int,一个bool,一个占用4字节的MyString对象,那么人工计算的话应该是9字节,但是编译时显示了12字节,这说明编译器对bool变量进行了字填充。

9.10 声明友元

可以从外部访问类的私有数据成员和方法——通过友元。使用关键字friend声明友元。将加上了friend关键字的类或函数原型添加到private中,就声明了友元。如:

private:
	string Name;
	int Age;
	//Display()是外部函数。
	friend void DisplayAge(const Human& Person);
	//或者
...
private:
	string Name;
	int Age;
	//Utility是一个外部类
	friend class Utility;-

总结

类封装了成员数据以及使用这些数据的方法,这有助于类对外隐藏它的私有数据和方法,只对外留出一个接口。外部的客体只需了解这个接口的特性即可使用它,而无需知道其内部的细节——这是抽象的方法。自然界目前所知的最小的对象时夸克(虽是理论,但被被广泛认可),由夸克这种数据以某种方法封装成中子或质子,这是一个抽象过程。再由中子和质子经过抽象,成为原子。层次逐渐上升,分子,大分子,到蛋白质等生物大分子,到亚细胞级,到细胞,到器官,最后到人这样一个复杂的主体。
抽象,无处不在。

十分重要的是构造函数析构函数的用法,前者用于创建类,后者用于销毁类,他们通常设置在公有区。复制构造函数利用了构造函数的重载特性,可实现灵活的初始化选项。而析构函数主要用于自动清除内存。
另外,学习了,复制构造函数的概念,需要采取一些措施,防止不好的复制、创建单例类,以及为了保证不在栈中创建存储过大数据的类等。

也了解了类的其它方面的内容,比如this指针,与结构体的相似点和区别,友元。这些不太重要。

posted @ 2017-12-08 23:41  Mars_2030  阅读(357)  评论(0编辑  收藏  举报