C++精讲:构造函数

构造函数

前言:
构造函数我是们学习c++面向对象非常重要的一门知识点,它种类繁多,相对起来比较难理解。下面是我的一些学习心得,我把它全部分好类,在附加上例子,希望你们一看就懂!

构造函数的作用:

在创建一个新的对象时,自动调用的函数,用来进行“初始化”工作:对这个对象内部的数据成员进行初始化。

构造函数的的特点:

1.自动调用(在创建新的对象时,自动调用);
2.构造函数的函数名和类名相同;
3.构造函数没有返回值类型;
4.可以有多个构造函数(即函数重载形式)。

构造函数的种类:

1.默认构造函数
2.自定义的构造函数(即带参数的默认构造函数)
3.拷贝构造函数
4.赋值构造函数

默认构造函数

没有参数的构造函数,成为默认构造函数。
它又分为两点:合成的默认构造函数和自定义的默认构造函数

一、合成的默认函数

当没有手动定义默认构造函数时,编译器自动为这个类定义一个构造函数。

下面是代码
例:

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

using namespace std;

class Human {
public:
	void print();	// 打印信息
	string get_name();	
	int get_age();
	int get_salary();

private:
	string name;
	int age;
	int salary;
};

void Human::print() {
	cout << "name = " << name << ", age = " << age << ", salary = " << salary << endl;
}

string Human::get_name() {
	return name;
}

int Human::get_age() {
	return age;
}

int Human::get_salary() {
	return salary;
}

int main(void) {
	Human h1;		// 根据Human类,创建了h1对象

	h1.print();		// 把定义的name,age,salary打印出来

	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述

可以发现,string类型的name,没有数据,默认为空;但int类型的age和salary,却存储了庞大的负数。
这是因为默认构造函数给它们做了初始化。

另外:也可以在定义数据是给它们赋初值,这种称为“类内初始值”。

例:

private:
	string name = "张三";	// 类内初始值,C++11 才支持
	int age = 20;
	int salary = 20000;
};

由于我使用的C++编译器是小于11版本的,所以在这里就演示不了了,有兴趣的读者,可以自行操作一下。

小结:
1.如果数据成员使用了“类内初始值”,就使用这个值来初始化数据成员;
2.否则,就使用默认初始化(实际上,不做任何初始化)。

二、自定义的默认构造函数

常被称为“默认构造函数”

定义:
1.在public方法里面定义函数声明;

Human();		// 构造函数名和类名相同

2.在class类外写全函数功能;

Human::Human() {
	name = "张三";
	age = 20;
	salary = 20000;
}

==========================================================

下面是全部代码
例:

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

using namespace std;

class Human {
public:
	Human();		// 自定义的默认构造函数
	void print();	// 打印信息
	string get_name();	
	int get_age();
	int get_salary();

private:
	string name;
	int age;
	int salary;
};
// 赋初值
Human::Human() {
	name = "张三";
	age = 20;
	salary = 20000;
}
void Human::print() {
	cout << "name = " << name << ", age = " << age << ", salary = " << salary << endl;
}

string Human::get_name() {
	return name;
}

int Human::get_age() {
	return age;
}

int Human::get_salary() {
	return salary;
}

int main(void) {
	Human h1;		// 根据Human类,创建了h1对象

	// 自动调用自定义的构造函数赋初值
	h1.print();		// 把定义的name,age,salary打印出来

	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述

说明:如果某数据成员使用了类内初始值,同时又在自定义的构造函数进行了初始化,name以自定义的构造函数中的初始化为准。相当于自定义的构造函数中的初始值,会覆盖对应的“类内初始值”。

注意:
1.只要自定了任何一个构造函数,编译器就不会生成“合成的的默认构造函数”;
2.一般情况下,都应该定义自己的构造函数,不要使用“合成的默认构造函数”。(仅当数据成员全部使用了“类内初始化”,才宜使用“合成的默认构造函数”).

自定义的重载构造函数

也就是带参数的构造函数

定义:
1.在public方法里面定义函数声明;

Human(int age, int salary);

2.在class类外写全函数功能
this是一个特殊的指针,指向这个对象;
也可以给其它没有形参赋值的变量赋值。

Human::Human(int age, int salary) {
	name = "张三";		// 也可以给其它没有形参赋值的变量赋值
	this->age = age;	// this是一个特殊的指针,指向这个对象
	this->salary = salary;
}

==========================================================

下面是代码
例:

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

using namespace std;

class Human {
public:
	Human(int age, int salary);		// 自定义的重载构造函数
	void print();	// 打印信息
	string get_name();	
	int get_age();
	int get_salary();

private:
	string name;
	int age;
	int salary;
};
// 赋初值
Human::Human(int age, int salary) {
	name = "张三";		// 也可以给其它没有形参赋值的变量赋值
	this->age = age;	// this是一个特殊的指针,指向这个对象
	this->salary = salary;
}
void Human::print() {
	cout << "name = " << name << ", age = " << age << ", salary = " << salary << endl;
}

string Human::get_name() {
	return name;
}

int Human::get_age() {
	return age;
}

int Human::get_salary() {
	return salary;
}

int main(void) {
	Human h1(22, 25000);		// 根据Human类,创建了h1对象

	// 自动调用自定义的构造函数赋初值
	h1.print();		// 把定义的name,age,salary打印出来

	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述

注意:自定义的重载函数定义后,系统就不会再用默认的了

拷贝构造函数

一、合成的拷贝构造函数(浅拷贝)

不自定义拷贝构造函数,编译器就会自动生成“合成的拷贝构造函数”

下面是代码:
例:

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

using namespace std;

class Human {
public:
	void test(const char *addr);	// 用于单单给指针变量赋值
	Human(int age, int salary);		// 自定义的重载构造函数
	void print();					// 打印信息
	string get_name();	
	int get_age();
	int get_salary();

private:
	string name;
	int age;
	int salary;
	char *addr;
};
void Human::test(const char *addr) {
	if (!addr) {
		return;
	}
	
	strcpy_s(this->addr, 64, addr); 
}
// 赋初值
Human::Human(int age, int salary) {
	name = "张三";
	this->age = age;		// this是一个特殊的指针,指向这个对象
	this->salary = salary;
	addr = new char[64];
	strcpy(addr, "中国");
}

void Human::print() {
	cout << "name = " << name << ", age = " << age << ", salary = " << salary << ",addr = " << addr << endl;
}

string Human::get_name() {
	return name;
}

int Human::get_age() {
	return age;
}

int Human::get_salary() {
	return salary;
}

int main(void) {
	Human h1(22, 25000);	// 根据Human类,创建了h1对象
	Human h2 = h1;			// 根据Human类,创建了h2对象,同时将h1的值拷贝到h2(自动调用合成的拷贝构造函数)
	Human h3(h1);			// 带括号拷贝和上面哪一种是一样的		

	cout << "h1:";
	h1.print();		// 把定义的name,age,salary打印出来
	cout << "h2:";
	h2.print();		// 把定义的name,age,salary打印出来
	cout << "h3:";
	h3.print();		// 把定义的name,age,salary打印出来

	h1.test("广州");// 把h1的指针变量更改为广州

	cout << "h1 修改后:" << endl;
	cout << "h1:";
	h1.print();		// 把定义的name,age,salary打印出来
	cout << "h2:";
	h2.print();		// 把定义的name,age,salary打印出来
	cout << "h3:";
	h3.print();		// 把定义的name,age,salary打印出来

	system("pause");
	return 0;
}

此代码中,多定义了addr指针变量,且都在自定义的默认构造函数中赋了初值“中国”;

	addr = new char[64];
	strcpy(addr, "中国");

又定义了一个指针赋值的方法:

void Human::test(const char *addr) {
	if (!addr) {
		return;
	}
	
	strcpy_s(this->addr, 64, addr); 
}

在main函数中,根据Human类创建了h1对象,再接着创建了h2和h3对象,将h1的值用自定义的拷贝函数赋值了h2和h3,输出三个对象的值;然后用 test 方法将h1的指针变量值改变为“广州”,再输出三个对象的值。可见,三个对象的指针变量都改变成了“广州”。这就是合成的拷贝构造函数的缺点,当一个对象的指针变量发生变化,其它两个对象的指针变量都会跟着发生变化。这就是“浅拷贝”。

运行结果:
在这里插入图片描述

说明:
合成的拷贝构造函数就是把一个对象的全部内容拷贝到另一个对象中。假如拷贝的内容中有指针变量,那么两个对象的指针变量都会指向同一块内存当中,当内存地址中的内容发生变化,都会涉及两个对象。这就是“合成的拷贝构造函数”的缺点:使用“浅拷贝”

解决方案:在自定义的拷贝构造函数中,使用“深拷贝”

二、自定义的拷贝构造函数(深拷贝)

定义:
1.在public方法里面定义函数声明;

Human(const Human &);	// 必须使用const修饰

2.在class类外写全函数功能

Human::Human(const Human &other) {
	name = other.name;
	age = other.age;
	salary = other.salary;
	
	// 深拷贝	假如有指针就得用深拷贝
	addr = new char[64];		// 先分配一块内存	// 使用宏定义定义64最佳
	strcpy_s(addr, 64, other.addr);	// 在进行内容拷贝
}

下面是代码:
例:

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

using namespace std;

class Human {
public:
	void test(const char *addr);	// 用于单单给指针变量赋值
	Human(const Human &);			// 自定义的拷贝构造函数
	Human(int age, int salary);		// 自定义的重载构造函数
	void print();					// 打印信息
	string get_name();	
	int get_age();
	int get_salary();

private:
	string name;
	int age;
	int salary;
	char *addr;
};

void Human::test(const char *addr) {
	if (!addr) {
		return;
	}
	
	strcpy_s(this->addr, 64, addr); 
}
Human::Human(const Human &other) {
	name = other.name;
	age = other.age;
	salary = other.salary;

	// 深拷贝	假如有指针就得用深拷贝
	addr = new char[64];			// 先分配一块内存	// 使用宏定义定义64最佳
	strcpy_s(addr, 64, other.addr);	// 在进行内容拷贝
}
// 赋初值
Human::Human(int age, int salary) {
	name = "张三";
	this->age = age;
	this->salary = salary;

	addr = new char[64];
	strcpy(addr, "中国");
}

void Human::print() {
	cout << "name = " << name << ", age = " << age << ", salary = " << salary << ",addr = " << addr << endl;
}

string Human::get_name() {
	return name;
}

int Human::get_age() {
	return age;
}

int Human::get_salary() {
	return salary;
}

int main(void) {
	Human h1(22, 25000);	// 根据Human类,创建了h1对象
	Human h2 = h1;			// 根据Human类,创建了h2对象,同时将h1的值拷贝到h2(自动调用合成的拷贝构造函数)
	Human h3(h1);			// 带括号拷贝和上面哪一种是一样的		

	cout << "h1:";
	h1.print();		// 把定义的name,age,salary打印出来
	cout << "h2:";
	h2.print();		// 把定义的name,age,salary打印出来
	cout << "h3:";
	h3.print();		// 把定义的name,age,salary打印出来

	h1.test("广州");// 把h1的指针变量更改为广州

	cout << "h1 修改后:" << endl;
	cout << "h1:";
	h1.print();		// 把定义的name,age,salary打印出来
	cout << "h2:";
	h2.print();		// 把定义的name,age,salary打印出来
	cout << "h3:";
	h3.print();		// 把定义的name,age,salary打印出来

	system("pause");
	return 0;
}

在main函数中,根据Human类创建了h1对象,再接着创建了h2和h3对象,将h1的值用自定义的拷贝函数赋值了h2和h3,输出三个对象的值;然后用 test 方法将h1的指针变量值改变为“广州”,再输出三个对象的值。可见,只有h1的指针变量值改变了,h2和h3的指针变量并没有改变,这就是“深拷贝”的作用。

运行结果:
在这里插入图片描述

三、什么时候会调用拷贝构造函数

1.调用函数时,实参是对象,形参不是引用类型;
如果函数的形参是引用类型,就不会调用拷贝构造函数。

例:

void distance(const Human man) {			
	cout << man.get_name() << "的信息是:";
	man.print();
}

/*
加了引用就不会在调用拷贝构造函数
如:
void distance(const Human &man) {			// 加了引用就不会在调用拷贝构造函数
	cout << man.get_name() << "的信息是:";
	man.test();
}
*/

2.函数的返回值类型是类,而且不是引用类型。

例:

const Human distance2(const Human h1, const Human h2) {	// 加了引用就不会在调用拷贝构造函数
	if (h1.get_age() > h2.get_age()) {
		return h1;
	} else {
		return h2;
	}
}

/* 
 加了引用就不会在调用拷贝构造函数
 如:
 const Human &distance2(const Human &h1, const Human &h2) {	// 加了引用就不会在调用拷贝构造函数
	if (h1.get_age() > h2.get_age()) {
		return h1;
	} else {
		return h2;
	}
}
*/

3.对象数组的初始化列表中,使用对象。

例:

Human h1, h2,  h3, h4;
Human F4[4] = {h1, h2, h3, h4};

赋值构造函数

一、合成的赋值构造函数

如果没有定义赋值构造函数,编译器会自动定义“合成的赋值构造函数”,与其它合成的构造函数一样,是“浅拷贝”(又称为:“位拷贝”)。

例:

和“合成的拷贝函数一样”使用方法一样,只有在赋值的方式有区别:

	Human h1(22, 25000);	// 根据Human类,创建了h1对象
	
	Human h2, h3;			// 先定义
	h2 = h1;				// 再赋值
	h3 = h1;

运行结果:
在这里插入图片描述

二、自定义的赋值构造函数

定义:
1.在public里面定义函数声明:

Human &operator = (const Human &);	// 有返回值
// operator 是一个关键字,必需得写

2.在class类外写全函数功能

Human &Human::operator = (const Human &other) {
	if (this == &other) {	// 检测是不是对自己赋值:比如: h1 = h2
		return *this;
	}

	// 如果有必要,需要先释放自己的内存资源(动态内存)
	// delete addr;
	// addr = new char[64];

	// 深拷贝
	strcpy_s(addr, 64, other.addr);

	// 处理其它成员
	name = other.name;
	age = other.age;
	salary = other.salary;

	// 返回该对象本身的引用,以便做链式连续处理:例如:a = b = c
	return *this;	
}

==========================================================

下面是全部代码:
例:

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

using namespace std;

class Human {
public:
	void test(const char *addr);			// 用于单单给指针变量赋值
	Human &operator=(const Human &other);	// 有返回值类型
	Human(int age, int salary);				// 自定义的重载构造函数
	void print();							// 打印信息
	string get_name();	
	int get_age();
	int get_salary();

private:
	string name;
	int age;
	int salary;
	char *addr;
};
Human &Human::operator = (const Human &other) {
	if (this == &other) {	// 检测是不是对自己赋值:比如: h1 = h2
		return *this;
	}

	// 如果有必要,需要先释放自己的内存资源(动态内存)
	// delete addr;
	// addr = new char[64];

	// 深拷贝
	strcpy_s(addr, 64, other.addr);

	// 处理其它成员
	name = other.name;
	age = other.age;
	salary = other.salary;

	// 返回该对象本身的引用,以便做链式连续处理:例如:a = b = c
	return *this;	
}
void Human::test(const char *addr) {
	if (!addr) {
		return;
	}
	
	strcpy_s(this->addr, 64, addr); 
}


// 赋初值
Human::Human(int age, int salary) {
	name = "张三";
	this->age = age;
	this->salary = salary;

	addr = new char[64];
	strcpy(addr, "中国");
}

void Human::print() {
	cout << "name = " << name << ", age = " << age << ", salary = " << salary << ",addr = " << addr << endl;
}

string Human::get_name() {
	return name;
}

int Human::get_age() {
	return age;
}

int Human::get_salary() {
	return salary;
}

int main(void) {
	Human h1(22, 25000);	// 根据Human类,创建了h1对象
	Human h2(23, 26000);
	Human h3(24, 27000);	// 先创建
	h2 = h1;				// 再赋值
	h3 = h1;

	cout << "h1:";
	h1.print();		// 把定义的name,age,salary打印出来
	cout << "h2:";
	h2.print();		// 把定义的name,age,salary打印出来
	cout << "h3:";
	h3.print();		// 把定义的name,age,salary打印出来

	h1.test("广州");// 把h1的指针变量更改为广州

	cout << "h1 修改后:" << endl;
	cout << "h1:";
	h1.print();		// 把定义的name,age,salary打印出来
	cout << "h2:";
	h2.print();		// 把定义的name,age,salary打印出来
	cout << "h3:";
	h3.print();		// 把定义的name,age,salary打印出来

	system("pause");
	return 0;
}

总结:构造函数虽然难以理解,但是只要分好类,一步步的深入探讨,其实也不算很难。最后,希望这篇文章能帮助到读者。本人也是C++新人,有错误的地方希望帮忙纠正,谢谢!

发布了39 篇原创文章 · 获赞 17 · 访问量 3858

猜你喜欢

转载自blog.csdn.net/cpp_learner/article/details/103335482