构造函数
每个类都分别定义了它的对象被初始化的方式,类通过一个或几个特殊成员函数来控制其对象的初始化过程,这些函数叫做构造函数。构造函数的任务是初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数。
构造函数是特殊的成员函数,用来保证每个对象的数据成员具有合适的初始值。
构造函数名字与类名相同,不能指定返回类型(也不能定义返回类型为void),可以有0-n个形参。
在创建类的对象时,编译器就运行一个构造函数。
默认构造函数
当我们没有显式的在声明中添加构造方法时,编译器会默认提供一个空参列表且没有任何行为的默认构造方法,会帮助我们初始化对象,就像下面这样
Student() {}
但是当我们声明了自己的构造方法之后,编译器便不会再提供默认的构造方法了,我们需要自己创建一个默认构造函数,使得对象可以无参数初始化。
当我们想定义一个默认的构造器有两种方式:
1,给已有的构造函数的所有参数提供默认值
Student(std::string name,int chScore=0,int enScore=0,int mathScore=0);
2,定义一个没有参数的函数重载
Student();
注意:声明隐式方式调用空参构造方法不能使用括号,因为这是编译器会将这行代码解释为函数声明,比如:
Student jack;//available
Student jack();//invalidate
创建自己的构造函数
构造函数与普通函数在声明与定义上别无二致,除了不需要声明返回值。在声明中添加如下代码
Student::Student(std::string name, int ch=0, int en=0, int math=0);
在实现中的一般实现方式为
Student::Student(string name, int ch,int en,int math){
setName(name);
setChScore(ch);
setMathScore(math);
setEnScore(en);
show();
}
引用参数与构造函数
如果成员是 const、引用,或者属于某种未提供默认构造函数的类类型,我们必须通过构造函数初始值列表为这些成员提供初值。
std::string & name;
比如我们的 name 属性是引用类型,那么我们需要在每一个构造方法实现
的参数列表后面加上这样的初始化器
Student::Student(std::string &n):name(n){
......
}
Student::Student(std::string &n,int chScore,int enScore,int mathScore):name(n){
......
}
如果我们有多个引用需要初始化,这些构造器只需要使用,
逗号分割,并且除了引用类型,任何类型属性都可以使用这种方式初始化
Class::Constructor(type *t1, type &t2, type t3...):t1(t1),t2(t2),t3(t3)...{}
我们把冒号后出现的部分称为构造函数初始化列表,它负责为新创建的对象的一个或几个数据成员赋初值。构造函数初始值是成员名字的一个列表,每个名字后面紧跟括号括起来的(或者在或括号内的)成员初始值。不同成员的初始化通过逗号分隔开来。
使用构造函数定义对象
当我们声明过一个构造函数之后,我们可以有三种方式去调用它:
1,显式的调用
Student Jack = Student("Jack",99,98,97);
2,隐式的调用
Student Jack("Jack",99,98,97);
3,使用 new 运算符
Student *Jack = new Student("Jack",99,98,97);
上述 3 种方法的关键区别在于内存分配:前两种方式的对象将会被在栈中创建,第3种方式对象将会在堆中被创建。
委托构造函数
C++11 新标准扩展了委托构造函数初始值的功能,使得我们可以定义所谓的委托构造函数。一个委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程,或者说它把它自己的一些(或全部)职责委托给了其他构造函数。
Student():Student("Tony",80,80,80){};
析构函数
析构函数是当对象被销毁时被系统调用的方法,一般用来清空内存。
析构函数的声明只用一种形式,就是~
加上类名
~Student();
析构函数的实现就可以根据需求去自定义
Student::~Student(){
cout << "Destroy a object: " << name << endl;
}
析构函数的执行时机
析构函数的执行与对象的创建位置相关
1,当对象被声明为全局变量,则当整个程序退出时,其析构函数才会被调用。
2,当对象被声明为自动存储类型(局部变量),当其生命周期结束,即其作用域执行完毕时,其析构函数会被调用。
3,当对象被声明为指针变量(使用new创建对象),当执行到delete方法时,其析构函数会被调用。
4,临时对象,这种对象在临时对象完成任务时自动调用其析构函数。如 (Student Sue = Student("Sue",88,51,72);)
const成员函数
有如下代码:
const Student Jack("Jack",99,98,97);
Jack.show();//complier error
因为我们已经将jack对象声明为const类型,所以当我们在调用show()方法时,编译器并不知道其中有没有更改对象内容的方法。所以拒绝编译。如果我们非要这么做的话,可以采用以下方式对show()函数重新定义
void show() const;
示例:
头文件 student.h
//Student.h -- Student class interface
//version 00
#ifndef Student_H_
#define Student_H_
#include <string>
class Student{ //class declaration
private:
std::string name;
int ch;
int en;
int math;
float average;
void count(){
average = (ch + en + math + 0.0F) / 3;
}
public:
Student();//构造函数
Student::Student(std::string name, int ch=0, int en=0, int math=0);
~Student();//析构函数
void setName(std::string name);
void setChScore(int score);
void setEnScore(int score);
void setMathScore(int score);
void show();
};
#endif
student.c
//Student.cpp -- implementing the Student class
//version 00
#include "stdafx.h"
#include "Student.h"
#include <iostream>
using namespace std;
Student::Student(){
cout << "Create a new Student object.\n";
}
Student::Student(string name, int ch,int en,int math){
setName(name);
setChScore(ch);
setMathScore(math);
setEnScore(en);
show();
}
Student::~Student(){
cout << "Destroy a object: " << name << endl;
}
void Student::setChScore(int score){
Student::ch = score; // 在类内部使用 可省略 Student,无需显示作用域
}
void Student::setName(std::string n){
Student::name = n;
}
void Student::setEnScore(int score){
en = score;
}
void Student::setMathScore(int score){
math = score;
}
void Student::show(){
Student::count();
//ios_base::fmtflags orig = cout.setf(ios_base::fixed, ios_base::floatfield);
//std::streamsize prec = cout.precision(1);
cout << name << " 同学的语文成绩为" << ch << "分,数学成绩为" << math << "分,英语成绩为"
<< en << "分,平均成绩" << average << "分" << endl;
//cout.setf(orig, ios_base::floatfield);
}
class.c // 主函数
// class.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "Student.h"
#include <iostream>
#include "stdlib.h"
using namespace std;
void test(){
Student *Jack = new Student();
Jack->setName("Jack");
Jack->setChScore(98);
Jack->setMathScore(100);
Jack->setEnScore(92);
Jack->show();
Student *Lucci = new Student("Lucci",85);
delete Jack;
delete Lucci;
Student Sue;
Sue = Student("Sue", 85, 90, 95); // 创建了一个临时对象,且复制给了对象 Sue
Student jack("Tom", 81, 50, 56);
Student Herry("Herry", 75, 59, 95);
}
int _tmain(int argc, _TCHAR* argv[]){
test();
system("pause");
return 0;
}
运行结果:
结果分析:
我们可以看到输出的第一、二行,表示在堆中开辟一块内存创建了对象 Jack,运行了默认构造函数。
第三行的输出也是在堆中中创建了对象 Lucci,运行了我们自己写的构造函数,语文成绩为我们的实参所改,为85,其他为默认参数。
由于对象 Jack 和 Lucci 都是在堆中开辟内存空间,因此需要我们主动释放内存空间,即 delete。从而进行了对象的析构,对应输出如四五行。
后面默认初始化了对象 Sue,如第六行,随后创建了一个临时对象且拷贝给对象 Sue。这时有了第七行的自制的构造函数输出,拷贝完毕后临时对象解析,有了第八行。九十行用了隐式调用构造函数在栈中创建了对象 Tom 和 Herry。
后续的解析函数的输出顺序遵从栈的先进后出原则。
参考: