C++构造函数
构造函数是一种特殊类型的方法,在每次实例化对象时运行
实例化
例1
class Entity
{
public:
float X, Y;
void Print()
{
std::cout << X << ", " << Y << std::endl;
}
};
int main()
{
Entity e;
e.Print();
std::cin.get();
}
得到0, 4.56996e-039
- 当实例化Entity,并给他们分配内存时,并没有初始化那个内存,即我们得到了那个内存原来存在的东西
- 我们希望默认初始化为0
手动打印X和Y的话,则会报错没有初始化局部变量:
std::cout << e.X << e.Y << std::endl;
所以,我们需要一种方法,当构造Entity实例化的时候,想把X和Y初始化为0
初始化函数
例3
#include <iostream>
class Entity
{
public:
float X, Y;
void Init()
{
X = 0.0f;
Y = 0.0f;
}
void Print()
{
std::cout << X << ", " << Y << std::endl;
}
};
int main()
{
Entity e;
e.Init();
std::cout << e.X << ", " << e.Y << std::endl;
e.Print();
std::cin.get();
}
然而,这种方法每次都需要调用初始化函数Init(),有没有一种方法,创建对象的同时就可以对其进行初始化
构造函数
构造函数是一种特殊类型的方法,每次构造对象时都会调用的方法
例4
#include <iostream>
class Entity
{
public:
float X, Y;
Entity()
{
X = 0.0f;
Y = 0.0f;
}
void Print()
{
std::cout << X << ", " << Y << std::endl;
}
};
int main()
{
Entity e;
std::cout << e.X << ", " << e.Y << std::endl;
e.Print();
std::cin.get();
}
实际上,不需要指定构造函数,默认就会有一个构造函数,但这个构造函数其实什么都没做,类似这样:
Entity()
{
}
带参数的构造函数
例5
#include <iostream>
class Entity
{
public:
float X, Y;
Entity()
{
X = 0.0f;
Y = 0.0f;
}
Entity(float x, float y)
{
X = x;
Y = y;
}
void Print()
{
std::cout << X << ", " << Y << std::endl;
}
};
int main()
{
Entity e(10.0f, 5.0f);
e.Print();
std::cin.get();
}
- 如果不实例化对象,将不会运行构造函数,所以如果使用一个类的静态方法,将不会运行构造函数
- 当用new关键字并创建一个对象实例,也会调用构造函数
删除构造函数
例6
class Log
{
public:
static void Write()
{
}
};
如果只想以这种方式使用类:
int main()
{
Log::Write();
}
而不想实例化对象:
int main()
{
Log l;
}
- 可以通过private来隐藏构造函数
class Log
{
private:
Log() {}
public:
static void Write()
{
}
};
- 也可以用delete:
class Log
{
public:
Log() = delete;
static void Write()
{
}
};
C++析构函数
- 在销毁对象时运行,只要对象要被销毁,析构函数就会调用
- 构造函数的功能通常是设置变量或者做任何需要的初始化
- 析构函数的功能则是卸载变量,并清理使用过的内存
- 析构函数同时适用于栈和堆分配的对象,如果使用new分配一个堆对象,当调用delete时,析构函数会被调用;而如果只是一个栈对象,当作用域结束时,栈对象就被删除
析构函数
例7
class Entity
{
public:
float X, Y;
// 构造函数
Entity()
{
X = 0.0f;
Y = 0.0f;
std::cout << "Created Entity" << std::endl;
}
Entity(float x, float y)
{
X = x;
Y = y;
}
// 析构函数
~Entity()
{
std::cout << "Destroyed Entity!" << std::endl;
}
void Print()
{
std::cout << X << ", " << Y << std::endl;
}
};
void Function()
{
Entity e;
e.Print();
}
int main()
{
Function();
std::cin.get();
}
在函数作用域结束时,由于对象是在栈上创建的,所以当函数作用域结束时,对象就被销毁
为什么需要析构函数
- 因为如果在构造函数中调用了特定的初始化代码,需要在析构函数中,卸载或销毁所有东西,不然会造成内存泄漏
- 如果是堆上分配的对象,则需要手动清理堆上的内存
C++继承
- 继承允许我们有一个相互关联的类的层次结构
- 继承允许我们有一个包含公共功能的基类
- 继承允许从那个基类中分离出来,从最初的父类中创建子类
- 类、继承如此有用是因为可以帮助我们避免代码重复
- 可以把类之间的所有公共功能放在一个父类,然后从基类(父类)创建(派生)一些类,稍微改变下功能,或者引入全新的功能
继承
例7
class Entity
{
public:
float X, Y;
void Move(float xa, float ya)
{
X += xa;
Y += ya;
}
};
class Player
{
public:
const char* Name;
float X, Y;
void Move(float xa, float ya)
{
X += xa;
Y += ya;
}
};
Player有许多重复的代码,但跟Entity又有点不同,可以使用继承:
class Player: public Entity
{
public:
const char* Name;
void PrintName()
{
std::cout << Name << std::endl;
}
};
int main()
{
Player player;
player.Move(5, 5);
player.X = 5;
std::cin.get();
}
- 任何在Entity类中不是私有的东西,实际上都可以被Player访问
- 继承可以扩展现有类,并为基类提供新功能的一种方式
- 打印父类的大小:
std::cout << sizeof(Entity) << std::endl;
- 打印子类的大小:
std::cout << sizeof(Player) << std::endl;
- 如果Player没有扩展Enity,则Player只有const char * Name,只有四字节的内存
- 如果Player扩展了Entity,所以Player有4+4+4=12字节的内存
- 类的大小和内存可以发生变化,如果开始重写函数和Player类,那么需要维护V表(虚函数表),也就是要额外的内存
多态
- 多态是一个单一类型,但有多个类型的意思
- Player不仅是Player类型,也是一个Entity
- 意味着我们可以在任何想要使用Entity的地方使用Player;
- 父类是子类的子集
重载
- 可以改变父类或基类的行为
- 重写一个方法,并给新的代码来代替父类方法的运行