[C++]浅谈构造函数

什么是构造函数?

构造函数是一种随着对象创建而自动被调用的函数,主要作用是对对象作初始化。

C++中规定与类同名的成员函数就是构造函数。构造函数应该是一个公有的成员函数,并且构造函数没有返回值类型。

构造函数可以如同一般函数一样被重载。

在C++中,每个类都有且必须有构造函数,如果用户没有自行编写构造函数,编译器会提供一个无参的构造函数,称为默认构造函数。

但这个默认构造函数不做任何初始化工作。而一旦用户编写了构造函数,这个无参的默认构造函数就消失了。如果用户还希望有一个无参的构造函数,必须自行编写。

下面是构造函数的重载的例子:

<span style="font-family:Microsoft YaHei;">#include <iostream>
using namespace std;

class Node
{
public:
	Node();
	//constructors with arguements
	Node(int i, char c = '0');
	Node(int i, char c, Node *p, Node *n);

	int readi() const;
	char readc() const;
	Node * readp() const;
	Node * readn() const;

	bool set(int i);
	bool set(char c);
	bool setp(Node *p);
	bool setn(Node *n);

private:
	int idata;
	char cdata;
	Node *prior;
	Node *next;
};

Node::Node()
{
	cout << "Node constructor is running..."<<endl;
	idata = 0;
	cdata = '0';
	prior = NULL;
	next = NULL;
}

Node::Node(int i, char c)
{
	cout << "Node constructor is running..."<<endl;
	idata = i;
	cdata = c;
	prior = NULL;
	next = NULL;
}

Node::Node(int i, char c, Node *p, Node *n)
{
	cout << "Node constructor is running..."<<endl;
	idata = i;
	cdata = c;
	prior = NULL;
	next = NULL;
}

int Node::readi() const
{
	return idata;
}

char Node::readc() const
{
	return cdata;
}

Node * Node::readp() const
{
	return prior;
}

Node * Node::readn() const
{
	return next;
}

bool Node::set(int i)
{
	idata = i;
	return true;
}

bool Node::set(char c)
{
	cdata = c;
	return true;
}

bool Node::setp(Node *p)
{
	prior = p;
	return true;
}

bool Node::setn(Node *n)
{
	next = n;
	return true;
}
</span>

<span style="font-family:Microsoft YaHei;">#include <iostream>
#include "node.h"
using namespace std;

int main()
{
	Node a; //create an object of Node, call the constructor
	Node b(8);
	Node c(8, 'F', NULL, NULL);
	cout <<a.readi()<<' '<<a.readc() <<endl;
	cout <<b.readi()<<' '<<b.readc() <<endl;
	cout <<c.readi()<<' '<<c.readc() <<endl;
	return 0;
}</span>

结果:

Node constructor is running...
Node constructor is running...
Node constructor is running...
0 0
8 0
8 F

什么是构造函数初始化列表?

构造函数初始化列表是以一个”:“开始,接着以”,“分割的数据成员列表,每个数据后面跟一个放在括号中的初始化表达式。

使用初始化列表时显式的初始化,在没有使用初始化列表的构造函数是隐式的初始化。


构造函数的执行

构造函数的执行可以分为两个阶段,初始化阶段和计算阶段,初始化阶段先于计算阶段。

所有类类型的成员都会在初始化阶段进行初始化,即使该成员没有出现构造函数的初始化列表中。

/*
 * constructor2.cpp
 *
 *  Created on: 2016年2月20日
 *      Author: chenwei
 */
#include <iostream>
using namespace std;

class Test1
{
public:
	Test1():a(0) //constructor with no arguements
	{
		cout << "Constructor Test1" << endl;
	}
	Test1 (const Test1& t1) //copy constructor
	{
		cout << "Copy constructor for Test1" << endl;
		this->a = t1.a;
	}

	Test1& operator = (const Test1& t1)
	{
		cout << "assignment for Test1" <<endl;
		this->a = t1.a;
		return *this;
	}
private:
	int a;
};

class Test2
{
private:
	Test1 test1;
public:
	Test2(Test1 &t1)
	{
		test1 = t1;
	}
};

int main()
{
	Test1 t1;
	Test2 t2(t1);
	return 1;
}
<strong>
</strong>

结果:

Constructor Test1
Constructor Test1
assignment for Test1


第一行对应main函数中Test1 t1, 构造了一个Test1对象

第二行对应输出Test2的构造函数中的代码,用默认构造函数初始化了对象test1,前面两行是所谓的初始化阶段。

第三行对应输出Test1的赋值运算符,用于同类的两个对象之间的赋值,对test1进行复制操作,这是所谓的计算阶段。


使用初始化列表和在构造函数体内进行赋值的含义是什么?二者有什么区别?

对于内置数据类型,复合类型(指针,引用)来说,二者的性能和结果是一样的。

对于用户自定义类型(类类型),结果是相同的,但性能上有很大差别。在上面的例子中,如果改为使用初始化列表,打印输出会不一样:

class Test2
{
private:
	Test1 test1;
public:
	Test2(Test1 &t1):test1(t1) {}
};
结果:

Constructor Test1
Copy constructor for Test1

使用初始化列表少了一次调用默认构造函数的过程,对于数据密集型来说是非常高效的。


成员变量初始化顺序

1. 在使用初始化列表初始化成员变量时,初始化顺序与出现在初始化列表中的顺序无关,只与成员变量被声明的顺序有关。

因为成员变量的初始化顺序是根据变量在内存中的顺序来的,而变量在内存中的顺序是根据变量的声明顺序来的。

<span style="font-family:Microsoft YaHei;">#include <iostream>
using namespace std;
class A
{
private:
	int n1;
	int n2;
public:
	A():n2(0), n1(n2+2) {}

	void Print()
	{
		cout << "n1:" << n1 << ", n2: " << n2 << endl;
	}
};

int main()
{
	A a;
	a.Print();
	return 1;
}
</span>

结果:

n1:4194434, n2: 0

由于n1先于n2被定义,所以会先初始化n1,但是此时n2并没有被初始化,所以n1的结果会是一个随机值。


2. 如果不使用初始化列表,改为在构造函数中初始化,初始化顺序则与构造函数中被声明的顺序一致。

<span style="font-family:Microsoft YaHei;">	A()
	{
		n2 = 0;
		n1 = n2 +2;
	}</span>
结果:

n1:2, n2: 0


3. 类成员在声明时,是不能被初始化的。


4. 类中的static成员变量必须在类外被初始化

static成员变量初始化顺序是,基类的static成员变量先被初始化,然后是派生类的static成员变量,知道所有static成员变量都被初始化。

而由于static变量和全局变量都被放在公共内存区,static变量和全局变量的初始化是不分次序的,可以把static变量看作是有作用域的全局变量。

在一切初始化工作结束后,main函数会被调用。如果某个类的构造函数被执行,那么基类的成员变量会被初始化。然后是派生类的成员变量。

总之,是先静态,后普通,先基类,后派生类。


什么样的成员变量必须通过初始化列表来初始化?

1 const成员,const成员不能通过赋值来初始化,只能通过初始化列表。

2 & 引用类型, 根据引用的定义,它必须在定义时被初始化,并且不能被重新赋值,所以也必须在初始化列表里被初始化。

3 成员类型是没有默认构造函数的类,如果没有提供显式初始化列表,编译器会隐式使用成员类型的默认构造函数,而这时没有默认构造函数,编译器的尝试将会失败。 通过初始化列表可以调用拷贝构造函数而不是默认构造函数来初始化。



猜你喜欢

转载自blog.csdn.net/michellechouu/article/details/50704224