C++面向对象程序设计 - 构造函数

时间:2024-04-14 10:24:03

        C++提供了构造函数来处理对象的初始化,构造函数是一种特殊的成员函数,与其他成员函数不同,它不需要用户来调用,而是在建立对象时自动执行。构造函数名称必须与类同名,而不能由用户任意命名,以便编译系统能识别它把它作为构造函数处理。它不具体任何类型,也不返回任何值。构造函数的功能由用户定义,可根据初始化的要求设计函数体和函数参数。而且如果用户未定义构造函数,C++系统会自动生成一个构造函数,只是这个构造函数体是空的。

        

一、默认构造函数

        用户未定义构造函数系统会自动生成一个构造函数,这个构造函数的函数体是空的,也没有参数,不执行初始化操作。代码如下:

#include <iostream>
#include <string>
using namespace std;

class Student{
	private:
		int age;
		string name;
		string sex;
	public:
		void display(){
			cout <<"name:" <<name <<", age:" <<age <<", sex:" <<sex <<endl;
		}
};

int main(){
	Student s;
	s.display();
	return 0;
}

执行效果如下:

二、自定义构造函数

        构造函数没有返回值,也不需要在定义构造函数时声明类型。构造函数是在建立对象时由系统自动执行的,而且只执行一次。这里创建一个无参构造函数,代码如下:

#include <iostream>
#include <string>
using namespace std;

class Student{
	private:
		int age;
		string name;
		string sex;
	public:	
		Student(){
			age = 0;
			name = "test";
			sex = "male";
		}
		void display(){
			cout <<"name:" <<name <<", age:" <<age <<", sex:" <<sex <<endl;
		}
};

int main(){
	Student s;
	s.display();
	return 0;
}

        此时参数已在构造函数中初始化,执行效果如下:

三、构造函数的重载

        在一个类中可以定义多个构造函数,以便对类对象提供不同的初始化的方法供用户选择。这些构造函数名称相同,而参数的个数或参数类型不同,这称为构造函数的重载。代码如下:

#include <iostream>
#include <string>
using namespace std;

class Student{
	private:
		int age;
		string name;
		string sex;
	public:	
		// 不带参数构造函数
		Student(){
			age = 0;
			name = "test";
			sex = "male";
		}
		// 带参的构造函数
		Student(int a, string n, string s){
			age = a;
			name = n;
			sex = s;
		}
		void display(){
			cout <<"name:" <<name <<", age:" <<age <<", sex:" <<sex <<endl;
		}
};

int main(){
	Student s, s2(10, "jack", "male");
	s.display();
	s2.display();
	return 0;
}

        运行结果如下:

四、参数初始化表

        C++提供了一种特殊的初始化数据成员方法(参数初始化表来实现对数据成员的初始化),这种方法不在函数体内实现数据的初始化,而在函数首部实现。代码如下:

#include <iostream>
#include <string>
using namespace std;

class Student{
	private:
		int age;
		string name;
		string sex;
	public:	
		// 不带参数构造函数
		Student(){
			age = 0;
			name = "test";
			sex = "male";
		}
		// 带参的构造函数,并且使用参数初始化表对数据成员的初始化
		Student(int a, string n, string s) : age(a), name(n), sex(s){}
		
		void display(){
			cout <<"name:" <<name <<", age:" <<age <<", sex:" <<sex <<endl;
		}
};

int main(){
	Student s, s2(10, "jack", "male");
	s.display();
	s2.display();
	return 0;
}

        这种写法方便、简练,尤其当需要初始化的数据成员较多时更显其优势。运行结果如下:

五、类体外定义构造函数

        除在类内定义构造函数,也可以在类外定义构造函数,要类名加上两个冒号“::”域限定符来实现。代码如下:

#include <iostream>
#include <string>
using namespace std;

class Student{
	private:
		int age;
		string name;
		string sex;
	public:	
		// 声明不带参数构造函数
		Student();
		// 带参的构造函数,并且使用参数初始化表对数据成员的初始化
		Student(int, string, string);
		
		void display(){
			cout <<"name:" <<name <<", age:" <<age <<", sex:" <<sex <<endl;
		}
};

int main(){
	Student s, s2(10, "jack", "male");
	s.display();
	s2.display();
	return 0;
}

// 类体外定义无参构造函数
Student::Student(){
		age = 0;
		name = "test";
		sex = "male";
}
// 在类体外定义带参构造函数
Student::Student(int a, string n, string s) : age(a), name(n), sex(s){}

        运行结果如下:

六、构造函数添加默认参数

        在成员函数中可以使用有默认的参数,构造函数也可以使用包含默认参数的构造函数。代码如下:

Student::Student(int a = 15, string n = "Tom", string s = "female") : age(a), name(n), sex(s){}

        如果构造函数全部参数都指定了默认值,则在定义对象时可以给一个或几个实参,也可以不给出实参;但应该执行哪一个构造函数,会出现歧义,系统无法识别编译时会报错([Error] no matching function for call to 'Student::Student()')。这块操作对程序员来说,开发难度会加大,所以不建议使用。

七、析构函数

        析构函数是C++中的一个特殊的成员函数,它的作用与构造函数相反,它的名字是类名前面加一个“~”符号。在以C++中“~”是位取反运算符,所它是与构造函数作用相反的函数。当对象的生命期结束时,会自动执行析构函数。在以下几种情况会执行析构函数:

  1. 在一个函数中定义一个对象,当这个函数被调用结束时,对象应该释放,在对象释放前自动执行析构函数。
  2. static局部对象在函数调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用static局部对象的析构函数。
  3. 定义了一个全局对象,则在程序的流程离开其作用域时,调用该全局对象的析构函数。
  4. 如果用new运算符动态地建立一个对象,当用delete运算符释放该对象时,先调用该对象的析构函数。

以上几种方法演示代码如下:

#include <iostream>
#include <string>
using namespace std;

class Student{
	private:
		int age;
		string name;
		string sex;
	public:	
		// 声明不带参数构造函数
		Student();
		// 带参的构造函数,并且使用参数初始化表对数据成员的初始化
		Student(int, string, string);
		// 定义析构函数
		~Student(){
			cout <<"Destructor called:" <<name <<endl;
		}
		
		void display(){
			cout <<"name:" <<name <<", age:" <<age <<", sex:" <<sex <<endl;
		}
};

// 对象在函数内执行
void show_student(){
	Student t1(15, "Lily", "femail");
	t1.display();
}

int main(){
    // 在函数中定义一个对象执行后,执行析构函数
	show_student();
    // 当对象定义全局(如main函数结束),执行析构函数
	Student s;
	s.display();
	// 当static局部对象在main函数结束后,执行析构函数
	static Student s2(10, "jack", "male");
	s2.display();
	// 当用new运算符动态地建立一个对象
	Student *p = new Student(12, "John", "male");
	p->display();
	return 0;
}

// 类体外定义无参构造函数
Student::Student(){
	age = 0;
	name = "test";
	sex = "male";
}
// 在类体外定义带参构造函数
Student::Student(int a = 15, string n = "Tom", string s = "female") : age(a), name(n), sex(s){}

        执行后支行结果:

        可以看出,首先show_student()函数执行完后,其内部t1对象已结束并执行析构函数;然后定义全局对象和static局部对象,在main函数执行结束后,也相继执行析构函数;但是始终未见用new运算符动态建立的对象执行析构函数,这是因为没用delete运算符释放对象,现在咱们加再运行看下结果,代码如下:

Student *p = new Student(12, "John", "male");
p->display();
delete p;

        运行结果如下:

        此时用new建立的对象也执行了析构函数,从这也可以看出,John在test、jack之前执行析构函数,恰好说明了定义在全局对象和static局部对象是在main函数执行结束后,执行的析构函数。

c++构造函数与其他开发语言相比,不仅有很多相似之处,也有独特之处。相似之处有其命名中与类同名且无返回类型,自动调用与初始化,可重载性等外,还可以在类体外定义构造函数,参数初始化表,析构函数等。