函数后面跟着圆括号表示参数初始化,比如int i(1)等价于int i=1。
目录
构造函数
了解完【C++】类之后,再来看看类的构造函数。
类的实例就是对象,一个对象如果没有初始值,那不能算对象。所以这时就需要一个函数能在类被实例化时,直接就行初始化,这就是构造函数。构造函数在创建对象时,会被自动调用。
构造函数是类的一种特殊的成员函数:
(1)函数名与类名必须相同;
(2)没有返回值;
(3)构造函数可以被重载(参数列表不同就行)。
#include "pch.h"
#include <iostream>
#include "stdio.h"
class Circle
{
public:
int x, y;
int radius;
Circle()
{
printf("This is the structure of no param.\n");
x = 0;
y = 0;
radius = 1;
}
Circle(int x, int y, int radius)
{
printf("This is the structure with params.\n");
this->radius = radius;
this->x = x;
this->y = y;
}
};
int main()
{
Circle a;
//Circle a();这是错误的,根本没有这种使用方式,它不会调用任何构造函数。
Circle b(1,1,1);
std::cout << "Hello World!\n";
return 0;
}
注意:上面main函数中有一段被我注释,Circle a();这种方式编译会通过,但是没有调用任何构造函数,我不知道为什么可以这样,我很蒙蔽。
默认构造函数
有2中情况属于默认构造函数:
(1)构造函数,没有参数;
(2)构造函数,所有参数都有确定值。
为什么需要默认构造函数?
如果没有默认构造函数,你申明对象数组时就会出错。比如Circle a[4];这种形式就直接报错。
析构函数
如果一个变量和函数跳出了它自身的作用域,那么它就会调用析构函数,然自己所占有的内存被释放。
作用域怎么看?
找这个变量或者函数最近的大括号,跳出这个大括号就会失效,就会自动调用析构函数。
#include "pch.h"
#include <iostream>
#include "stdio.h"
class Circle
{
public:
int x, y;
int radius;
Circle()
{
printf("This is the structure of no param.\n");
x = 0;
y = 0;
radius = 1;
}
~Circle()
{
printf("This is xigou function.\n");
}
};
int main()
{
//对象a跳出这个大括号,就跳出了它的作用域,那么就会自动调用析构函数
{
Circle a;
}
std::cout << "Hello World!\n";
return 0;
}
全局变量的生命周期
全局变量是在主函数中所有程序都结束完成后,最后才会销毁全局变量。具体例子如下:
#include "pch.h"
#include <iostream>
#include "stdio.h"
class Circle
{
public:
~Circle()
{
printf("This is xigou function.\n");
}
};
Circle a;
int main()
{
std::cout << "Hello World!\n";
return 0;
}
/*
//run out
Hello World!
This is xigou function.
*/
.h和.cpp分开写(参考博客)
.h文件
class Circle
{
public:
Circle(); //函数申明
~Circle();
};
.cpp文件
#include "Circle.h"
Circle::Circle(){} //函数实现
Circle::~Circle(){}
再来个学生的例子:
.h文件
#pragma once
#ifndef _DATASTORE_H
#define _DATASTORE_H
struct Student
{
int id;
char name[32];
Student* next;
};
class DataStore
{
public:
DataStore();
~DataStore();
private:
Student m_head;
};
#endif
.cpp文件
#include "pch.h"
#include "DataStore.h"
#include "stdlib.h"
DataStore::DataStore()
{
m_head = {0};
}
DataStore::~DataStore()
{
Student* p = m_head.next;
while(p)
{
Student* next = p->next;
free(p);
p = next;
}
}
编译器默认添加构造和析构函数
如果你没有自己写构造和析构函数,他会自动添加一个简单的构造和析构函数。
Circle::Circle(){}
Circle::~Circle(){}
成员的初始化与析构
考虑成员变量本身就是class类型的情况:
(1)当对象被构造时,成员变量也被构造(成员变量的构造函数被调用);
(2)当对象被析构时,成员变量也被析构(成员变量的析构函数被调用)。
#include "pch.h"
#include <iostream>
#include "stdio.h"
class Other
{
public:
Other()
{
printf("Other: 创建\n");
}
~Other()
{
printf("Other: 销毁\n");
}
};
class Child
{
public:
Child()
{
printf("Child: 创建\n");
}
~Child()
{
printf("Child: 销毁\n");
}
};
class Object
{
public:
Object()
{
printf("Object: 创建\n");
}
~Object()
{
printf("Object: 销毁\n");
}
private:
Child m_child;
Other m_other;
};
int main()
{
{
Object obj;
}
std::cout << "Hello World!\n";
return 0;
}
小结:从结果我们可以得出,你要想创建object对象,必须在object内的所有内容编译成功后才会有一个完整的object。所以需先完成自身的构造函数,然后按照顺序对child和other进行对象的创建,这一系列操作结束后,就完成了对object对象的构建。
在销毁的时候为什么是object先销毁呢?你想如果不告诉计算机你要销毁object了,那么other和child凭什么知道自己被销毁了。好像理解的有问题。
构造和析构的顺序总是相反的。
初始化列表
可以在构造函数后面直接初始化
(1)以冒号引导;
(2)使用小括号来初始化。
class Child
{
public:
Child()
{
printf("Child: 创建\n");
}
Child(int x, int y)
{
printf("Test initial function.");
}
~Child()
{
printf("Child: 销毁\n");
}
};
class Object
{
public:
Object() : m_child(1, 2), x(1), y(2) //这里就是初始化参数列表
{
printf("Object: 创建\n");
}
~Object()
{
printf("Object: 销毁\n");
}
private:
Child m_child;
int x, y;
};
int main()
{
Object obj;
std::cout << "Hello World!\n";
return 0;
}
初始化和赋值有什么异同?
(1)功能上,他们一样;
(2)性能上,初始化相对较好,有时可以节省CPU操作。(暂时不是很明白)
下面这个例子是有问题的,编译器直接提示你Child没有可以用的构造函数。
比如下面的例子,在Object类中创建了Child的对象m_child,但没有给x,y的初始化参数,导致函数不能构造,直接编译报错。因为一个类必须要有构造函数,你不写,编译器会自动帮你加,但你写了,参数没给对,不能进行构造,那就出问题了。
class Child
{
public:
Child(int x, int y)
{
printf("Test initial function.");
}
~Child()
{
printf("Child: 销毁\n");
}
public:
int x, y;
};
class Object
{
public:
Object()
{
printf("Object: 创建\n");
}
//改为下面这种形式就可以了
//Object() : m_child(1,2)
//{
// printf("Object: 创建\n");
//}
~Object()
{
printf("Object: 销毁\n");
}
private:
Child m_child;
int x, y;
};
int main()
{
Object obj;
std::cout << "Hello World!\n";
return 0;
}
有一个比较关键的点:在类中初始化和在主函数中初始化是不一样的,而且他们都只能用这种初始化方式,其他初始化方式都会报错。当然如果你使用指针就可以用new的方式来初始化,比如Child* a = new Child(1,2),这种指针的方式,不管是在类中还是在主函数中都是可以使用的。
在类中初始化:
class Object
{
public:
Object() : m_child(1,2)
{
printf("Object: 创建\n");
}
~Object()
{
printf("Object: 销毁\n");
}
private:
Child m_child;
Other m_other;
int x, y;
};
在主函数中初始化:
int main()
{
Child m_child(1,2);
std::cout << "Hello World!\n";
return 0;
}