本文参考菜鸟教程,仅作笔记用。
构造函数
构造函数(Constructor)是一种特殊的方法,用于在创建对象时进行初始化操作。构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。在面向对象编程中,通常每个类都可以有一个构造函数。构造函数的作用是初始化对象的状态,为对象的属性赋予初始值,以确保对象在创建后处于一个合适的状态。
在继承关系中,子类可以继承父类的属性和方法,但是构造函数却不能直接继承
。这是因为构造函数的特殊性质,它在对象创建时被调用,用于初始化该对象的状态。当子类继承父类时,子类可以使用父类的属性和方法,但是子类的构造函数需要负责初始化子类自己的属性,而不能直接继承父类的构造函数。
通常情况下,如果子类没有定义自己的构造函数,那么会隐式地调用父类的构造函数来初始化子类的实例
。但是如果子类定义了自己的构造函数,那么子类的构造函数就会覆盖父类的构造函数
,这样父类的构造函数就无法被子类直接继承和使用了。
#include <iostream>
using namespace std;
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(); // 这是构造函数
private:
double length;
};
// 成员函数定义,包括构造函数
Line::Line(void)
{
cout << "Object is being created" << endl;
}
void Line::setLength( double len )
{
length = len;
}
double Line::getLength( void )
{
return length;
}
// 程序的主函数
int main( )
{
Line line;
// 设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;
return 0;
}
<< Object is being created
<< Length of line : 6
带参数的构造函数
#include <iostream>
using namespace std;
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(double len); // 这是构造函数
private:
double length;
};
// 成员函数定义,包括构造函数
Line::Line( double len)
{
cout << "Object is being created, length = " << len << endl;
length = len;
}
void Line::setLength( double len )
{
length = len;
}
double Line::getLength( void )
{
return length;
}
// 程序的主函数
int main( )
{
Line line(10.0);
// 获取默认设置的长度
cout << "Length of line : " << line.getLength() <<endl;
// 再次设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;
return 0;
}
<< Object is being created, length = 10
<< Length of line : 10
<< Length of line : 6
使用初始化列表来初始化字段
初始化列表允许我们在构造函数中以更加直接和高效的方式初始化类的成员变量,特别是对于常量或者引用类型的成员变量尤其有用。
使用初始化列表来初始化字段:
Line::Line( double len): length(len)
{
cout << "Object is being created, length = " << len << endl;
}
上面的语法等同于如下语法:
Line::Line( double len)
{
length = len;
cout << "Object is being created, length = " << len << endl;
}
再举个例子
#include <iostream>
#include <string>
class Car {
private:
std::string& owner; // Reference member variable
std::string model;
public:
// Constructor with initialization list
Car(std::string& o, const std::string& m)
: owner(o), model(m) {
// Constructor body (if needed)
}
void displayInfo() const {
std::cout << "Owner: " << owner << ", Model: " << model << std::endl;
}
};
int main() {
std::string alice = "Alice";
std::string bob = "Bob";
// Initializing objects of class Car using initialization list
Car car1(alice, "Toyota");
car1.displayInfo();
Car car2(bob, "Honda");
car2.displayInfo();
return 0;
}
<< Owner: Alice, Model: Toyota
<< Owner: Bob, Model: Honda
析构函数
类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
子类会继承父类的析构函数,但并不是通过继承其实现。当子类的对象销毁时,会先调用子类的析构函数,然后再调用父类的析构函数。这种顺序确保了对象的析构顺序与构造顺序相反。
如果父类有虚析构函数(通过在析构函数前面加上 virtual关键字),那么子类也会继承这个虚析构函数。这是为了实现多态性,确保在使用基类指针指向派生类对象时正确地释放资源。
加上 virtual 关键字确实是声明虚函数的方式,但在析构函数前面加上 virtual关键字有特定的作用。这种做法是为了确保正确的析构函数调用顺序和资源释放。
当你有一个基类指针指向派生类对象时,如果基类的析构函数是虚函数(即声明为virtual),那么在销毁这个对象时,会根据实际对象的类型(基类或派生类)来调用适当的析构函数。这种机制称为动态绑定或者多态性。
具体来说,如果基类的析构函数不声明为虚函数,那么当你通过基类指针删除一个派生类对象时,只会调用基类的析构函数,而不会调用派生类的析构函数。这可能导致派生类对象中的资源(如动态分配的内存)没有被正确释放,从而产生内存泄漏或其他问题。
因此,为了确保在继承体系中正确地释放资源,通常建议在基类的析构函数前面加上 virtual关键字,以启用动态绑定机制。这样,无论通过基类指针如何删除对象,都能正确调用对象的实际类型的析构函数,确保资源的正确释放。
总结一下,加上 virtual 关键字在析构函数中的作用是确保在继承体系中正确地调用对象的析构函数,从而实现多态性和正确的资源管理。
#include <iostream>
using namespace std;
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(); // 这是构造函数声明
~Line(); // 这是析构函数声明
private:
double length;
};
// 成员函数定义,包括构造函数
Line::Line(void)
{
cout << "Object is being created" << endl;
}
Line::~Line(void)
{
cout << "Object is being deleted" << endl;
}
void Line::setLength( double len )
{
length = len;
}
double Line::getLength( void )
{
return length;
}
// 程序的主函数
int main( )
{
Line line;
// 设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;
return 0;
}
<< Object is being created
<< Length of line : 6
<< Object is being deleted
注意
初始化顺序最好要按照变量在类声明的顺序一致
,否则会出现下面的特殊情况:
#include<iostream>
using namespace std;
class Student1 {
public:
int a;
int b;
void fprint(){
cout<<" a = "<<a<<" "<<"b = "<<b<<endl;
}
Student1(int i):b(i),a(b){ } //异常顺序:发现a的值为0 b的值为2 说明初始化仅仅对b有效果,对a没有起到初始化作用
// Student1(int i):a(i),b(a){ } //正常顺序:发现a = b = 2 说明两个变量都是初始化了的
Student1() // 无参构造函数
{
cout << "默认构造函数Student1" << endl ;
}
Student1(const Student1& t1) // 拷贝构造函数
{
cout << "拷贝构造函数Student1" << endl ;
this->a = t1.a ;
}
Student1& operator = (const Student1& t1) // 赋值运算符
{
cout << "赋值函数Student1" << endl ;
this->a = t1.a ;
return *this;
}
};
class Student2
{
public:
Student1 test ;
Student2(Student1 &t1){
test = t1 ;
}
// Student2(Student1 &t1):test(t1){}
};
int main()
{
Student1 A(2); //进入默认构造函数
Student2 B(A); //进入拷贝构造函数
A.fprint(); //输出前面初始化的结果
}
一个类内可以有多个构造函数,可以是一般类型的,也可以是带参数的,相当于重载构造函数,但是析构函数只能有一个
。
class Matrix
{
public:
Matrix(int row, int col); //普通构造函数
Matrix(const Matrix& matrix); //拷贝构造函数
Matrix(); //构造空矩阵的构造函数
void print(void);
~Matrix();
};
总结上述两点注意事项,代码如下:
#include<iostream>
using namespace std;
class Student1 {
public:
int a=0;
int b=0;
void fprint() {
cout << " a = " << a << " " << "b = " << b << "\n"<<endl;
}
Student1()
{
cout << "无参构造函数Student1" << endl;
}
Student1(int i):a(i),b(a)
{
cout << "有参参构造函数Student1" << endl;
}
Student1(const Student1& t1)
{
cout << "拷贝构造函数Student1" << endl;
this->a = t1.a;
this->b = t1.b;
}
Student1& operator = (const Student1& t1) // 重载赋值运算符
{
cout << "赋值函数Student1" << endl;
this->a = t1.a;
this->b = t1.b;
return *this;
}
};
class Student2
{
public:
Student1 test;
Student2(Student1& t1) {
t1.fprint();
cout << "D: ";
test = t1;
}
// Student2(Student1 &t1):test(t1){}
};
int main()
{
cout << "A: ";
Student1 A;
A.fprint();
cout << "B: ";
Student1 B(2);
B.fprint();
cout << "C: ";
Student1 C(B);
C.fprint();
cout << "D: ";
Student2 D(C);
D.test.fprint();
}
/*A: 无参构造函数Student1
a = 0 b = 0
B: 有参参构造函数Student1
a = 2 b = 2
C:拷贝构造函数Student1
a = 2 b = 2
D: 无参构造函数Student1
a = 2 b = 2
D: 赋值函数Student1
a = 2 b = 2