目录
一、构造函数和析构函数(对象的初始化和清理)
对象的初始化和清理
生活中我们买的电子产品都基本会有出厂设罩,在某一天我们不用时候也会删除一些自己信息数据保证安全
C++中的面向对象来源于生活,每个对象也都会有初始设置以及对象销毁前的清理数据的设置。
作用
一个对象或者变量没有初始状态,对其使用后果是未知的,
同样的使用完一个对象或变量没有及时清理,也会造成一定的安全问题
功能实现
C++利用构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作对象的初始化和清理工作是编译器强制我们要做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现。
- 构造函数:主要作用于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用
- 析构函数:主要作用于对象销毁前系统自动调用,执行一些清理工作
二、构造函数
语法:类名 () {}
- 构造函数,没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序调用对象的时候会自动调用构造函数,无须手动调用,而且只会调用一次
示例:
#include<iostream>
using namespace std;
class person{
public:// 在公共作用域下在主函数才能访问到
// 构造函数
person()
{
// 在创建一个对象的时候会自动调用一次
cout<<"person 构造函数的调用"<<endl;
}
};
int main ()
{
person p;
return 0;
}
运行结果:
三、析构函数
语法 ~类名(){}
- 析构函数,没有返回值也不写void
- 函数名称与类名相同称
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
示例:
#include<iostream>
using namespace std;
class person{
public:// 在公共作用域下在主函数才能访问到
// 析构函数
~person ()
{
cout<<"析构函数的调用"<<endl;
}
};
void test()
{
person p; // 在栈上的数据,在test执行完毕后就会释放这个对象
}
int main ()
{
person p1; // 在main函数中也会在整个程序中执行完的时候执行
return 0;
}
运行结果:
四、构造函数的分类及调用
两种分类方式:
- 按参数分为:有参构造和无参构造
- 按类型分为:普通构造和拷贝构造
三种调用方式:
- 括号法
- 显示法
- 隐式转换法
- 显示法
示例:
#include<iostream>
using namespace std;
class person {
public:
// 构造函数
person() // 无参
{
cout << "person 的无参数的构造函数调用。" << endl;
}
person(int a) // 有参
{
age = a;
cout << "person 的有参数的构造函数调用。" << endl;
}
// 拷贝构造函数
person(const person& p)
{
// 把另一个person的属性传入进来 +const 防止改变原来的属性
// 将传入的人身上的所有的属性全部拷贝到自己的身上
age = p.age;
cout << "person 的拷贝构造函数调用。" << endl;
}
// 析构函数
~person()
{
cout << "person 的析构函数的调用。" << endl;
}
int age;
};
void test()
{
// 调用
// 1. 括号法
cout << "括号法调用" << endl;
person p1; // 默认构造函数调用
// 注意事项1:默认构造函数的调用不要加() 因为会被认为是一个函数的声明,不会认为在创建对象
person p2(10); // 调用有参构造函数
// 拷贝构造函数的调用
person p3(p2); // 拷贝构造函数的调用
cout << "p2的年龄:" << p2.age << endl;
cout << "p3的年龄:" << p3.age << endl;
cout << endl;
// 2. 显示法
cout << "显示法调用" << endl;
person p4; // 默认构造函数
person p5 = person(10); // 有参构造
person p6 = person(p3); // 拷贝构造
// 右侧相当于匿名对象,当执行结束后系统会立即回收掉匿名对象
// 注意事项2:不要利用拷贝构造函数 初始化 匿名对象
// 编译器会认为 person(p3) === person p3,相当于时对象的声明
cout << endl;
cout << "测试匿名对象" << endl;
person(10);
cout << "aaa" << endl << endl;
// 3. 隐式转换法
cout << "隐式转换法调用" << endl;
person p7 = 10; // 相当于person p3 = person(10);是一种有参构造
person p8 = p7;
cout << endl;
}
int main()
{
test();
return 0;
system("pause");
}
运行结果:
五、拷贝构造函数的调用时机
C++中拷贝构造函数调用的时机通常有三种情况:
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
示例:
#include<iostream>
using namespace std;
// 拷贝函数的三种调用时机
class person {
public:
person()
{
cout << "person 的默认函数构造" << endl;
}
// 有参
person(int in_age)
{
age = in_age;
cout << "有参构造函数" << endl;
}
// 拷贝
person(const person& p)
{
age = p.age;
cout << "拷贝构造函数" << endl;
}
// 析构
~person()
{
cout << "person 的析构函数的调用" << endl;
}
//private:
int age;
};
// 1. 使用一个已经创建完毕的对象来初始化一个新的对象
void test01()
{
person p1(20); // 有参构造
person p2(p1); // 拷贝构造
cout << "p2的年龄为:" << p2.age << endl;
}
// 2.值传递的方式给函数参数传值
void do_work(person p)
{
}
void test02()
{
person p;
do_work(p);
}
// 3. 以值的方式返回局部对象
person do_work2()
{
person p1;
cout << (int*)&p1 << endl;
return p1; // 拷贝一个新的对象用于返回
}
void test03()
{
person p = do_work2(); // 得到的是拷贝的返回值
cout << (int*)&p << endl;
}
int main()
{
test01();
cout << endl;
test02();
cout << endl;
test03();
return 0;
}
运行结果:
六、构造函数调用规则
- 默认情况下,C++编译器至少给一个类添加3个函数
- 1. 默认构造函数(无参,函数体为空)
- 2. 默认析构函数(无参,函数体为空)
- 3. 默认拷贝构造函数,对属性进行值拷贝
- 构造函数调用规则:
- 如果写了有参构造函数,编译器就不再提供默认构造函数,但是依然提供拷贝构造函数
- 如果写了拷贝构造函数,编译器就不再提供默认构造函数和有参构造函数
示例:
#include<iostream>
using namespace std;
class person
{
public:
// 默认
person()
{
cout<<"person 的默认函数构造"<<endl;
}
// 有参
// 如果自己提供了有参构造,编译器就不再提供默认构造
person(int in_age)
{
age = in_age;
cout<<"person 的有参构造函数"<<endl;
}
// 拷贝
// 如果自己不提供,编译器也会提供拷贝函数,值拷贝(两个析构,一个有参)
/*person(const person& p)
{
this->age = p.age;
cout<<"person 的拷贝构造函数"<<endl;
}*/
// 析构
~person()
{
cout<<"person 的析构函数的调用"<<endl;
}
int age;
};
void test01()
{
person p;
p.age = 18;
person p1(p);
cout<<"p1的年龄:"<<p1.age<<endl;
}
int main()
{
test01();
return 0;
}
运行结果:
七、深拷贝与浅拷贝的问题
- 浅拷贝:简单的赋值拷贝操作
- 深拷贝:在堆区重新申请空间,进行拷贝操作
浅拷贝带来的问题是堆区的内存重复释放,利用深拷贝来解决。
示例:
#include<iostream>
using namespace std;
class person
{
public:
// 默认
person()
{
cout<<"person 的默认函数构造"<<endl;
}
// 有参
person(int age, double height)
{
this->age = age;
cout<<"person 的有参构造函数"<<endl;
this->height = new double(height); // new 一个新的指针变量,创建在堆区
}
// 拷贝
person(const person & p)
{
this->age = p.age; // 编译器默认实现
cout<<"person 的拷贝构造函数"<<endl;
// this->age = p.height; // 编译器的 拷贝构造默认实现方式
// 解决浅拷贝带来的问题
// 两个对象的指针分别设立在不同的存储地址
this->height = new double(*p.height); // 利用深拷贝来解决浅拷贝的问题
}
// 析构
~person()
{
// 将我们在堆区开辟的数据做释放操作
if(height !=NULL){
delete height;
height = NULL;
}
cout<<"person 的析构函数的调用"<<endl;
}
int age;
double * height; // 身高数据的地址
// 编译器提供的拷贝构造函数 会做浅拷贝处理 会重复释放
// 身高是定义在堆区的,指针类型的存储的是地址,不能重复释放
};
void test01()
{
person p1(18,1.80);
cout<<"p1的年龄为: "<<p1.age<<endl;
cout<<"p1的身高为:"<<*p1.height<<endl;
person p2(p1);
cout<<"p2的年龄为:"<<p2.age<<endl;
cout<<"p2的身高为:"<<*p2.height<<endl;
}
int main()
{
test01();
return 0;
}
运行结果:
八、初始化列表
作用:C++提供了初始化列表语法,用来初始化属性。
语法 : 构造函数():属性1(值1),属性2(值2)... {}
示例:
#include<iostream>
using namespace std;
class person
{
public:
// 传统初始化操作
/*person(int a ,int b,int c)
{
A = a;
B = b;
C = c;
}*/
// 初始化列表属性,赋值默认值
/*person():A(10),B(20),C(30)
{
}*/
// 设置成变量
person(int a,int b,int c):A(a),B(b),C(c)
{
}
int A;
int B;
int C;
};
void test01()
{
// 每次调用一种
person p(10,20,30);
cout<<"A = "<<p.A<<endl;
cout<<"B = "<<p.B<<endl;
cout<<"C = "<<p.C<<endl;
}
int main()
{
test01();
return 0;
}
运行结果:
九、类对象作为类成员
c++类中的成员是另一个类的对象,称该成员为对象成员
例如:
class A{};
class B
{
A a;
};
B类中有对象A作为成员,A为对象成员
那么,当创建B对象时,A与B的构造和析构顺序ABBA
示例:
#include<iostream>
#include<string>
using namespace std;
// 设计一个手机类
class phone{
public:
// 给手机命名
// 构造函数
phone(string in_name)
{
p_name = in_name;
cout<<"这是phone的构造函数调用"<<endl;
}
~phone()
{
cout<<"phone 析构函数的调用"<<endl;
}
// 手机类型的设计
string p_name;
};
// 设计一个人的类
class person
{
public:
// 获取内容
person(string name,string p_name):my_name(name),my_phone(p_name)
{
// 相当于下边这样,隐式转换法
// phone my_phone = p_name;
cout<<"这是person 的构造函数调用"<<endl;
}
~person()
{
cout<<"person 析构函数的调用"<<endl;
}
// 姓名
string my_name;
// 手机
phone my_phone;
};
// 当其他类作为本类的成员,构造时先构造其他类的对象,再构造自身
// 析构时先析构自身,再析构其他的对象
void test01()
{
person p("张三","苹果MAX");
cout<<p.my_name<<"有一个"<<p.my_phone.p_name<<"手机"<<endl;
}
int main()
{
test01();
return 0;
}
运行结果:
十、静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
1.静态成员分类
静态成员变量:
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
静态成员函数:
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
2.静态成员变量
示例:
#include<iostream>
#include<string>
using namespace std;
class person {
public:
static int m_A; // 静态成员变量
// 类内声明,类外初始化
private:
static int m_B;// 静态成员变量
// 在类外访问不到
};
int person::m_A = 0;
int person::m_B = 200;
// 两种访问方式
void test01()
{
// 1. 通过对象访问
cout << "通过对象访问" << endl;
person p1;
cout << "p1_a = " << p1.m_A << endl;
person p2;
p2.m_A = 200;
cout << "共享数据,p2修改了数据,p1访问就变为p2修改后内容" << endl;
cout << "p1_a = " << p1.m_A << endl;
cout << "p2_a = " << p2.m_A << endl;
// 2. 通过类名访问
cout << "通过类名访问:" << person::m_A << endl;
//并且私有权限类外访问不到
}
int main()
{
test01();
return 0;
}
运行结果:
3.静态成员函数
示例:
#include<iostream>
#include<string>
using namespace std;
class person{
public:
// 静态的成员函数
static void func()
{
m_A = 100;
// m_B = 200; // 静态成员函数不能访问非静态成员变量
// 静态成员函数是每个对象都共享的,调用非静态成员变量时不知道修改的是哪个对象的变量
cout<<"静态函数的调用"<<endl;
}
static int m_A; // 静态成员变量
// 类内声明,类外初始化
int m_B;
private:
static void func2()
{
cout<<"这是private下的调用"<<endl;
}
};
int person::m_A = 0;
// 两种访问方式
void test01()
{
// 1. 通过对象访问
cout << "通过对象访问" << endl;
person p1;
p1.func();
// 2. 通过类名访问
cout<<"通过类名访问:";
person::func();
person::func2();// 类外访问不到私有静态成员函数
}
int main()
{
test01();
return 0;
}
运行结果: