构造函数、析构函数、静态成员
2 对象的初始化和清理
2.1 构造函数和析构函数
对象的初始化和清理是非常重要的安全问题,C++利用构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事情,因此 如果我们不提供构造和析构函数,编译器会提供。,并且编译器提供的构造函数和析构函数是空实现。
构造函数: 创建对象时,为对象的成员属性赋值,构造函数由编译器自动调用,无需手动实现。
析构函数: 对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:类名(){}
(1)构造函数,没有返回值也不写void
(2)函数名称和类名相同
(3)构造函数可以有参数,因此可以发生重载
(4)程序在创建对象是会自动调用构造函数,无需手动调用,而且只会调用一次
构造函数语法:~类名(){}
(1)析构函数,没有返回值也不写void
(2)函数名称和类名相同,在名称前加~符号
(3)析构函数不可以有参数,因此不可以发生重载
(4)程序在销毁对象前会自动调用析构函数,无需手动调用,而且只会调用一次
#include <iostream>
#include <string>
using namespace std;
//对象的初始化和清理
//1.构造函数:进行初始化操作
//2.析构函数:进行清理操作
class Person
{
public:
//1.1 构造函数没有返回值也没有void
//1.2 构造函数的函数名同类名相同
//1.3 构造函数可以有参数,因此可以发生重载
//1.4 程序在创建对象时会自动调用构造函数,而且只会调用一次
Person()
{
cout << "构造函数的调用" << endl;
}
//2.1 析构函数没有返回值也没有void
//2.2 析构函数的函数名与类名同名,但在函数名之前加~
//2.3 析构函数没有参数,不可以重载
//2.4 析构函数在销毁对象前系统调用,并且只会调用一次
~Person()
{
cout << "析构函数的调用" << endl;
}
};
//构造函数和析构函数都是必须有的实现,如果程序员不提供,编译器会提供,并且是空实现
void test01()
{
Person p1;//在栈上的数据,test01()函数执行完后,调用析构函数清理对象
}
int main()
{
test01();
}
2.2 构造函数的分类和调用
两种分类方式:
(1)按参数分:有参构造和无参构造
(2)按类型分:普通构造和拷贝构造
三种调用方式:
(1)括号法
(2)显示法
(3)隐式转换法
#include <iostream>
#include <string>
using namespace std;
//构造函数的作用:创建对象时为对象的成员属性赋值
//1.构造函数的分类
class Person
{
public:
int age;
//1.1 构造函数的分类:有参构造(默认构造函数),无参构造
Person()//无参构造(普通构造)
{
cout << "Person的无参构造函数调用" << endl;
}
Person(int a)//有参构造(普通构造)
{
age = a;
cout << "Person的有参构造函数调用" << endl;
}
//1.2 构造函数的分类:普通构造函数(上述都是普通构造函数),拷贝构造函数
Person(const Person &p)//拷贝构造:把同类的对象的引用传入构造函数,并且该对象不能修改
{
//将传入的对象的所有属性,拷贝到本类
age = p.age;
cout << "Person的拷贝构造函数调用" << endl;
}
};
//2.构造函数的调用
void test01()
{
//2.1 括号法
cout << "括号法" << endl;
Person p1;//默认构造
Person p2(10);//有参构造
Person p3(p2);//拷贝构造
//注意:调用默认构造函数的时候,不要加():Person p1();会认为是函数声明,不会认为是创建对象
//2.2 显示法
cout << "显示法" << endl;
Person p4;
Person p5 = Person(10);//显示调用有参构造
Person p6 = Person(p5);//显示调用拷贝构造
//Person(10)等,是匿名对象;特点是当前行执行结束后,系统回立刻回收掉匿名对象
//注意:不要利用拷贝构造函数初始化匿名对象:编译器认为Person(p3)等价于Person p3,重定义;
//2.3 隐式转换法
cout << "隐式转换法" << endl;
Person p7 = 10;//相当于Person p4=Person(10)
}
int main()
{
test01();
}
2.3 拷贝构造函数调用时机
拷贝构造函数调用时机:
(1)使用一个已经创建完毕的对象来初始化一个新对象
(2)值传递的方式给函数参数传值
(3)以值方式返回局部对象
#include <iostream>
#include <string>
using namespace std;
//拷贝构造函数调用时机
//1.使用一个已经创建完毕的对象来初始化一个新的对象
//2.值传递的方式给函数参数传值
//3.值方式返回局部对象
class Person
{
public:
int age;
Person()//无参构造(普通构造)
{
cout << "Person的无参构造函数调用" << endl;
}
Person(int a)//有参构造(普通构造)
{
age = a;
cout << "Person的有参构造函数调用" << endl;
}
Person(const Person& p)//拷贝构造:把同类的对象的引用传入构造函数,并且该对象不能修改
{
//将传入的对象的所有属性,拷贝到本类
age = p.age;
cout << "Person的拷贝构造函数调用" << endl;
}
};
//1.使用一个已经创建完毕的对象来初始化一个新的对象
void test01()
{
Person p1(10);//有参构造
Person p2(p1);//拷贝构造
}
//2.值传递的方式给函数参数传值
void doWork(Person p)
{
}
void test02()
{
Person p1;
doWork(p1);//实参传递给形参的时候回调用拷贝构造函数:传递过去后会拷贝临时的副本
}
//3.值方式返回局部对象:
Person doWork02()
{
Person p1;
cout << "局部对象对地址" << &p1 << endl;
return p1;
}
void test03()
{
Person p = doWork02();
cout << "局部对象拷贝了副本,副本地址为" << &p << endl;
}
int main()
{
cout << "1.使用一个已经创建完毕的对象来初始化一个新的对象" << endl;
test01();
cout << "2.值传递的方式给函数参数传值" << endl;
test02();
cout << "3.值方式返回局部对象" << endl;
test03();
}
2.4 构造函数调用规则
默认情况下,C++编译器至少给一个类添加3个函数
(1)默认构造函数(无参,函数体为空)
(2)默认析构函数(无参,函数体为空)
(3)默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则:
(1)如果用户定义有参构造函数,C++不在提供默认无参构造函数,但是会提供默认拷贝构造函数
(2)如果用户定义拷贝构造函数,C++不会再提供其他构造函数(类中用户不能只提供拷贝构造)
#include <iostream>
#include <string>
using namespace std;
//构造函数的调用规则
//1.创建一个类时,C++编译器会给每个类都添加至少3个函数
//默认构造:空实现
//默认析构:空实现
//拷贝构造:值拷贝
//2.如果我们写了有参构造,编译器就不会再提供默认构造,依然提供拷贝构造
//3.如果用户定义拷贝构造函数,C++不会再提供其他构造函数
class Person
{
public:
int age;
Person(int a)//有参构造
{
age = a;
cout << "Person的有参构造函数调用" << endl;
}
~Person()
{
cout << "Person的析构函数调用" << endl;
}
};
class Person2
{
public:
int age;
Person2(int a)//有参构造
{
age = a;
cout << "Person的有参构造函数调用" << endl;
}
Person2(const Person2& p)//拷贝构造:把同类的对象的引用传入构造函数,并且该对象不能修改
{
age = p.age;
cout << "Person2的拷贝构造函数调用" << endl;
}
};
//1.创建一个类时,C++编译器会给每个类都添加至少3个函数
void test01()
{
Person p(10);//有参构造
Person p2(p);//调用默认拷贝构造
cout << "p2的年龄" << p2.age << endl;
}
//2.如果程序员写了有参构造,编译器就不会再提供默认构造,依然提供拷贝构造
void test02()
{
//Person p;//默认构造,因为写了有参构造,编译器不再提供默认构造
//p.age = 18;
Person p(18);//有参构造
Person p2(p);//调用默认拷贝构造
cout << "p2的年龄" << p2.age << endl;
}
//3.如果用户定义拷贝构造函数,C++不会再提供其他构造函数
void test03()
{
//Person2 p;//默认构造,因为写了拷贝构造函数构造,编译器不再提供默认构造等其他构造函数
//p.age = 18;
Person2 p(12);
Person2 p2(p);//调用默认拷贝构造
cout << "p2的年龄" << p2.age << endl;
}
int main()
{
cout << "1.创建一个类时,C++编译器会给每个类都添加至少3个函数" << endl;
test01();
cout << "2.如果程序员写了有参构造,编译器就不会再提供默认构造,依然提供拷贝构造" << endl;
test02();
cout << "3.如果用户定义拷贝构造函数,C++不会再提供其他构造函数" << endl;
test03();
}
2.5 深拷贝与浅拷贝
**浅拷贝:(编译器自动提供的拷贝构造函数)**简单的赋值拷贝操作——浅拷贝带来的问题是堆区的内存重复释放,引起错误。
**深拷贝:(用户自定义拷贝构造函数)**在堆区重新申请空间,进行深拷贝操作
如果成员属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的错误.
#include <iostream>
#include <string>
using namespace std;
//深拷贝与浅拷贝
class Person
{
public:
int age;
int* height;
Person()//无参构造
{
cout << "Person的无参构造函数调用" << endl;
}
Person(int a,int h )//有参构造
{
age = a;
height = new int(h);
//new在堆区开辟内存空间,返回一个指针:堆区数据由程序员开辟,程序员释放
cout << "Person的有参构造函数调用" << endl;
}
~Person()//析构函数
{
//程序员手动释放堆区的数据
if (height != NULL) {
delete height;
height = NULL;
}
cout << "Person的析构函数调用" << endl;
}
Person(const Person& p)
{
//height=p.height;//浅拷贝,编译器默认实现的是该代码
cout << "用户定义的拷贝构造函数,实现深拷贝" << endl;
age = p.age;
height = new int(*p.height);//在堆区重新开辟空间,避免浅拷贝的重复释放堆区内存空间出现错误
}
};
void test01()
{
Person p1(18,165);//有参构造
cout << "p1的年龄" << p1.age <<"身高为"<<*p1.height <<endl;
Person p2(p1);//调用拷贝构造(编译器提供的拷贝构造函数——浅拷贝,用户自定义的拷贝构造,实现深拷贝)
cout << "p2的年龄" << p2.age << "身高为" << *p2.height << endl;
}
int main()
{
test01();
}
2.6 初始化列表
作用:C++提供了初始化列表语法,用来初始化属性(构造函数的主要用途也是给属性做初始化操作)
语法:构造函数():属性1(值1),属性2(值2)。。。{}
#include <iostream>
#include <string>
using namespace std;
//深拷贝与浅拷贝
class Person
{
public:
int A;
int B;
int C;
//1.传统初始化操作:利用构造函数
//Person(int a, int b,int c)//有参构造
//{
// A = a;
// B = b;
// C = c;
//}
//2.初始化列表初始化操作
//Person() :A(10), B(20), C(30)
//{
//}
Person(int a,int b,int c) :A(a), B(b), C(c)
{
}
};
void test01()
{
//1.传统初始化操作:利用构造函数
//Person p1(18, 165,16);
//cout << "A=" << p1.A << endl;
//cout << "B=" << p1.B << endl;
//cout << "B=" << p1.B << endl;
//2.初始化列表初始化操作
//Person p;
//cout << "A=" << p.A << endl;
//cout << "B=" << p.B << endl;
//cout << "C=" << p.B << endl;
Person p(18, 165, 16);
cout << "A=" << p.A << endl;
cout << "B=" << p.B << endl;
cout << "C=" << p.B << endl;
}
int main()
{
test01();
}
2.7 类对象作为类成员
C++类中的成员可以是另外一个类的对象,我们称该成员为对象成员
class A{}
class B
{
A a;
}
//构造函数:先调用对象成员的构造,在调用自身类的构造,
//析构函数:先调用自身类的析构,再调用对象成员的析构
#include <iostream>
#include <string>
using namespace std;
//类对象作为类成员
class Phone
{
public:
string m_pname;
Phone()
{
}
Phone(string name)
{
m_pname = name;
cout << "类Phone的有参构造函数" << endl;
}
~Phone()
{
cout << "类Phone的析构函数" << endl;
}
};
class Person
{
public:
string m_name;
Phone m_phone;
//隐式转换,Phone m_phone=phone
Person(string name,string phone):m_name(name),m_phone(phone)
{
cout << "类Person的构造函数" << endl;
}
~Person()
{
cout << "类Person的析构函数" << endl;
}
};
//构造函数:先调用对象成员的构造,在调用自身类的构造,
//析构函数:先调用自身类的析构,再调用对象成员的析构
void test01()
{
Person pe("张三","苹果MAX" );
cout << pe.m_name << "拿着" << pe.m_phone.m_pname << "手机" << endl;
}
int main()
{
test01();
}
2.8 静态成员
定义:静态成员就是在成员变量和成员函数前加上关键字static,成为静态成员
静态成员分为:
(1)静态成员变量
1.1 所有对象共享同一份数据
1.2在编译阶段分配内存
1.3类内声明,类外初始化
1.4访问静态成员变量的方式:1.通过对象访问;2.通过类名访问 类名::静态成员变量
(2)静态成员函数
2.1 所有对象共享同一个函数
2.2 静态成员函数只能访问静态成员变量
2.3访问静态成员函数的方式:1.通过对象访问;2.通过类名访问 类名::静态成员函数()
2.4静态成员函数遵从访问权限(public,static,protrcted),既私有的类外依旧不能访问**
#include <iostream>
#include <string>
using namespace std;
//静态成员函数
//1.所有对象共享一个函数
//2.静态成员函数只能访问静态成员变量
class Person
{
public:
static int m_A;//静态成员变量
int m_B;
static void func()//静态成员函数
{
m_A = 100;//静态成员 函数可以访问静态成员变量
// m_B = 100;//静态成员 函数不可以访问非静态成员变量
cout << "static void func()调用" << endl;
}
};
//访问静态成员函数:
void test01()
{
//1.通过对象访问
Person p;
p.func();
//2.通过类名访问
Person::func();
}
int main()
{
test01();
}