构造函数和析构函数
对象的初始化和清理也是两个非常重要的安全问题
- 一个对象或变量没有初始状态,对其使用后果是未知
- 同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
C++利用构造函数和析构函数解决上述问题,这两个函数会被编译器自动调用,完成对象初始化和清理工作
对象的初始化和清理工作是编译器强制我们做的事情,因此如果我们不提供构造和析构,编译器会提供,编译器提供的构造函数和析构函数是空实现
构造函数:主要作用于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用
析构函数:主要作用于对象销毁前系统自动调用,执行一些清理工作
构造函数语法:类名 () {}
- 构造函数,没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候会自动调用构造,无需手动调用,而且只会调用一次
析构函数语法:~类名 () {}
- 析构函数,没有返回值也不写void
- 函数名称与类名相同,在名称前加上符号 ~
- 析构函数不可以由参数,因此不可以发生重载
- 程序在对象销毁前会自动调用构造,无需手动调用,而且只会调用一次
#include <iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "Person构造函数的调用" << endl;
}
~Person()
{
cout << "Person析构函数的调用" << endl;
}
};
//构造和析构都是必须有的实现,如果我们不提供,编译器会提供一个空实现的构造和析构
void test()
{
Person p; //在栈上的数据,test执行后,释放这个对象
}
int main() {
test(); //会调用一次构造和析构
Person p; //只会调用一次构造,因为程序没结束,对象不会释放
system("pause");
return 0;
}
Person构造函数的调用
Person析构函数的调用
Person构造函数的调用
构造函数的分类及调用
两种分类方式:
- 按参数分类:有参构造和无参构造(默认构造)
- 按类型分类:普通构造和拷贝构造
三种调用方式:
- 括号法
- 显示法
- 隐式转换法
括号法:
#include <iostream>
using namespace std;
class Person
{
public:
//无参构造
Person()
{
cout << "Person无参构造函数的调用" << endl;
}
//有参构造
Person(int a)
{
age = a;
cout << "Person有参构造函数的调用" << endl;
}
//拷贝构造
Person(const Person &p)
{
age = p.age;
cout << "Person拷贝构造函数的调用" << endl;
}
~Person()
{
cout << "Person析构函数的调用" << endl;
}
int age;
};
//调试
void test()
{
//1.括号法
Person p1; //无参构造函数调用
Person p2(10);//有参构造函数调用
Person p3(p2);//拷贝构造函数调用
cout << "p2的年龄为" << p2.age << endl;
cout << "p3的年龄为" << p3.age << endl;
//调用默认构造函数时,不要加括号
//Person p1();编译器会认为是一个函数的声明
}
int main() {
test();
system("pause");
return 0;
}
Person无参构造函数的调用
Person有参构造函数的调用
Person拷贝构造函数的调用
p2的年龄为10
p3的年龄为10
Person析构函数的调用
Person析构函数的调用
Person析构函数的调用
调用默认构造函数时,不要加括号
Person p1();编译器会认为是一个函数的声明
显示法:
#include <iostream>
using namespace std;
class Person
{
public:
//无参构造
Person()
{
cout << "Person无参构造函数的调用" << endl;
}
//有参构造
Person(int a)
{
age = a;
cout << "Person有参构造函数的调用" << endl;
}
//拷贝构造
Person(const Person &p)
{
age = p.age;
cout << "Person拷贝构造函数的调用" << endl;
}
~Person()
{
cout << "Person析构函数的调用" << endl;
}
int age;
};
//调试
void test()
{
//2.显示法
Person p1; //无参构造函数
Person p2 = Person(10); //有参构造函数
Person p3 = Person(p2); //拷贝构造函数
//Person(10); //匿名参数:该行代码执行完后,系统会立马回收掉匿名对象
//不要利用拷贝函数初始化匿名对象
//Person(p3) == Person p3 等于调用了一个默认构造函数
}
int main() {
test();
system("pause");
return 0;
}
Person无参构造函数的调用
Person有参构造函数的调用
Person拷贝构造函数的调用
Person析构函数的调用
Person析构函数的调用
Person析构函数的调用
Person(10); //匿名参数:该行代码执行完后,系统会立马回收掉匿名对象
不要利用拷贝函数初始化匿名对象
Person(p3) == Person p3 等于调用了一个默认构造函数
隐式转换法:
#include <iostream>
using namespace std;
class Person
{
public:
//无参构造
Person()
{
cout << "Person无参构造函数的调用" << endl;
}
//有参构造
Person(int a)
{
age = a;
cout << "Person有参构造函数的调用" << endl;
}
//拷贝构造
Person(const Person &p)
{
age = p.age;
cout << "Person拷贝构造函数的调用" << endl;
}
~Person()
{
cout << "Person析构函数的调用" << endl;
}
int age;
};
//调试
void test()
{
//3.隐式转换法
Person p2 = 10; //有参构造函数 相当于Person p2 = Person(10)
Person p3 = p2; //拷贝构造函数 相当于Person p3 = Person(p2)
}
int main() {
test();
system("pause");
return 0;
}
Person有参构造函数的调用
Person拷贝构造函数的调用
Person析构函数的调用
Person析构函数的调用
拷贝构造函数调用时机
C++拷贝构造函数调用时机通常有三种情况:
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
#include <iostream>
using namespace std;
class Person
{
public:
//无参构造
Person()
{
cout << "Person无参构造函数的调用" << endl;
}
//有参构造
Person(int a)
{
age = a;
cout << "Person有参构造函数的调用" << endl;
}
//拷贝构造
Person(const Person &p)
{
age = p.age;
cout << "Person拷贝构造函数的调用" << endl;
}
~Person()
{
cout << "Person析构函数的调用" << endl;
}
int age;
};
//调试
void test01()
{
Person p1(20);
Person p2(p1);
}
//值传递的方式给函数参数传值
void dowork(Person p)
{
}
void test02()
{
Person p;
dowork(p);
}
//以值方式返回局部对象
Person work()
{
Person p3;
return p3;
}
void test03()
{
Person p = work();
}
int main() {
test01();
test02();
test03();
system("pause");
return 0;
}
Person有参构造函数的调用
Person拷贝构造函数的调用
Person析构函数的调用
Person析构函数的调用
Person无参构造函数的调用
Person拷贝构造函数的调用
Person析构函数的调用
Person析构函数的调用
Person无参构造函数的调用
Person拷贝构造函数的调用
Person析构函数的调用
Person析构函数的调用
构造函数调用规则
默认情况下,C++编译器至少给一个类添加三个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
#include <iostream>
using namespace std;
//默认情况下,C++编译器至少给一个类添加三个函数
//默认构造(空实现)
//默认析构(空实现)
//默认拷贝(值拷贝)
class Person
{
public:
//默认构造
Person()
{
cout << "Person无参构造函数的调用" << endl;
}
//有参构造
Person(int age)
{
Age = age;
cout << "Person有参构造函数的调用" << endl;
}
//拷贝构造
//Person(const Person &p)
//{
// Age = p.Age;
// cout << "Person拷贝构造函数的调用" << endl;
//}
//析构函数
~Person()
{
cout << "Person析构函数的调用" << endl;
}
int Age;
};
//调试
void test01()
{
Person p1;
p1.Age = 18;
Person p2(p1);
cout << "年龄为" << p2.Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
Person无参构造函数的调用
年龄为18
Person析构函数的调用
Person析构函数的调用
将拷贝构造函数部分删除后,编译器会自动提供一个拷贝函数,即Age = p.Age;
起到拷贝函数的值传递作用
构造函数调用规则如下:
- 如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,C++不会再提供其他构造函数
#include <iostream>
using namespace std;
//默认情况下,C++编译器至少给一个类添加三个函数
//默认构造(空实现)
//默认析构(空实现)
//默认拷贝(值拷贝)
class Person
{
public:
//有参构造
Person(int age)
{
Age = age;
cout << "Person有参构造函数的调用" << endl;
}
//析构函数
~Person()
{
cout << "Person析构函数的调用" << endl;
}
int Age;
};
//调试
void test01()
{
Person p(18);
Person p2(p);
cout << "年龄为:" << p2.Age << endl;
}
int main() {
test01();
system("pause");
return 0;
}
Person有参构造函数的调用
年龄为:18
Person析构函数的调用
Person析构函数的调用
我们只定义了一个有参构造函数,因此编译器提供一个拷贝函数
但是不会有默认构造函数,因此在调试部分中只写Person p;会报错
深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
#include <iostream>
using namespace std;
class Person
{
public:
//默认构造
Person()
{
cout << "Person无参构造函数的调用" << endl;
}
//有参构造
Person(int age,int height)
{
Age = age;
Height = new int(height);
cout << "Person有参构造函数的调用" << endl;
}
//自己实现拷贝构造函数 解决浅拷贝带来的问题
Person(const Person& p)
{
cout << "Person拷贝构造函数的调用" << endl;
Age = p.Age;
//Height = p.Height;编译器默认实现就是这行代码
Height = new int(*p.Height);
}
//析构函数
~Person()
{
//析构代码,将堆区开辟数据做释放操作
if (Height != NULL)
{
delete Height;
Height = NULL;
}
cout << "Person析构函数的调用" << endl;
}
int Age;
int* Height;
};
//调试
void test01()
{
Person p1(18,185);
cout << "p1年龄为:" << p1.Age << "身高为:" << *p1.Height << endl;
Person p2(p1);
cout << "p2年龄为:" << p2.Age << "身高为:" << *p2.Height << endl;
}
int main() {
test01();
system("pause");
return 0;
}
Person有参构造函数的调用
p1年龄为:18身高为:185
Person拷贝构造函数的调用
p2年龄为:18身高为:185
Person析构函数的调用
Person析构函数的调用
堆区中的数据需要程序员操作开辟,也需要我们来进行释放操作,因此我们可以在析构函数中实现堆区数据的释放
浅拷贝带来的问题是堆区的内存重复释放,因此我们可以利用深拷贝来再创造一个堆区内存,解决堆区内存重复释放问题
初始化列表
作用:用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)...{ }
#include <iostream>
using namespace std;
class Person
{
public:
Person():A(1),B(2),C(3)
{
}
int A;
int B;
int C;
};
//调试
void test01()
{
Person p;
cout << "A:" << p.A << endl;
cout << "B:" << p.B << endl;
cout << "C:" << p.C << endl;
}
int main() {
test01();
system("pause");
return 0;
}
A:1
B:2
C:3
如何更灵活的赋值?
如下:
#include <iostream>
using namespace std;
class Person
{
public:
Person(int a,int b, int c):A(a),B(b),C(c)
{
}
int A;
int B;
int C;
};
//调试
void test01()
{
Person p(20,30,40);
cout << "A:" << p.A << endl;
cout << "B:" << p.B << endl;
cout << "C:" << p.C << endl;
}
int main() {
test01();
system("pause");
return 0;
}
A:20
B:30
C:40
类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为对象成员
例:
class A{}
class B
{
A a;
}
B类中有对象A作为成员,A为对象成员
那么当创建B对象时,A与B的构造和析构的顺序时谁先谁后?
示例:
#include <iostream>
using namespace std;
#include<string>
class Phone
{
public:
Phone(string pname)
{
PName = pname;
cout << "Phone的构造函数" << endl;
}
~Phone()
{
cout << "Phone的析构函数" << endl;
}
string PName;
};
class Person
{
public:
//Phone P = PName
Person(string name,string pname):Name(name),P(pname)
{
cout << "Person的构造函数" << endl;
}
~Person()
{
cout << "person的析构函数" << endl;
}
string Name;
Phone P;
};
//调试
void test01()
{
Person p("张三","苹果");
cout << p.Name << "有着" << p.P.PName << endl;
}
int main() {
test01();
system("pause");
return 0;
}
Phone的构造函数
Person的构造函数
张三有着苹果
person的析构函数
Phone的析构函数
当其他类对象作为本类成员,构造的时候先构造类对象,再构造自身,析构顺序与构造顺序相反
静态成员-静态成员变量
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
1、静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
2、静态成员函数
- 所有对象共享同一份数据
- 静态成员函数只能访问静态成员函数
静态成员变量
#include<iostream>
using namespace std;
class Person
{
//
public:
static int A;
private:
static int B;
};
int Person::A = 10;
void test01()
{
Person p1;
cout << "p1的A的值为:" << p1.A << endl;
Person p2;
p2.A = 20;
cout << "p1的A的值为:" << p1.A << endl;
//静态成员变量不属于某个对象上,所有对象共享同一份数据
//因此静态成员变量有两种访问方式
//1、通过对象进行访问
cout << "通过对象访问:" << p1.A << endl;
//2、通过类名进行访问
cout << "通过类名访问:" << Person::A << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
p1的A的值为:10
p1的A的值为:20
通过对象访问:20
通过类名访问:20
注意:静态成员变量也有访问权限
静态成员函数
#include<iostream>
using namespace std;
class Person
{
public:
static void show()
{
A = 20;
cout << "静态成员函数的访问" << endl;
}
static int A;
};
int Person::A = 10;
void test01()
{
//两种访问方式
//通过对象访问
Person p;
p.show();
//通过类名访问
Person::show();
}
int main()
{
test01();
system("pause");
return 0;
}
静态成员函数的访问
静态成员函数的访问
注意:静态成员函数也有访问权限
静态成员函数只能访问静态成员变量,因为如果是普通成员变量,它不知道修改哪个对象的属性,而静态成员变量只有一份共享数据