osCommerce购物网站架设全攻略,asp技校网站,门户网站 字体,合肥市住房和城乡建设局目录 对象的初始化和清理1.1 构造函数和析构函数1.2 构造函数的分类及调用1.3 拷贝构造函数调用时机1.4 构造函数调用规则1.5 深拷贝与浅拷贝 对象的初始化和清理 生活中我们买的电子产品都基本会有出厂设置#xff0c;在某一天我们不用时候也会删除一些自己信息数据保证安全。… 目录 对象的初始化和清理1.1 构造函数和析构函数1.2 构造函数的分类及调用1.3 拷贝构造函数调用时机1.4 构造函数调用规则1.5 深拷贝与浅拷贝 对象的初始化和清理 生活中我们买的电子产品都基本会有出厂设置在某一天我们不用时候也会删除一些自己信息数据保证安全。 C中的面向对象来源于生活每个对象也都会有初始设置以及 对象销毁前的清理数据的设置。
1.1 构造函数和析构函数
对象的初始化和清理也是两个非常重要的安全问题。
一个对象或者变量没有初始状态对其使用后果是未知。
同样的使用完一个对象或变量没有及时清理也会造成一定的安全问题。
c利用了构造函数和析构函数解决上述问题这两个函数将会被编译器自动调用完成对象初始化和清理工作。
对象的初始化和清理工作是编译器强制要我们做的事情因此如果我们不提供构造和析构编译器会提供
编译器提供的构造函数和析构函数是空实现。 构造函数主要作用在于创建对象时为对象的成员属性赋值构造函数由编译器自动调用无须手动调用。 析构函数主要作用在于对象销毁前系统自动调用执行一些清理工作。
构造函数语法类名(){} 构造函数没有返回值也不写void 函数名称与类名相同 构造函数可以有参数因此可以发生重载 程序在调用对象时候会自动调用构造无须手动调用,而且只会调用一次
析构函数语法 ~类名(){} 析构函数没有返回值也不写void 函数名称与类名相同,在名称前加上符号 ~ 析构函数不可以有参数因此不可以发生重载 程序在对象销毁前会自动调用析构无须手动调用,而且只会调用一次 示例代码 #includeiostream
using namespace std;
//对象的初始化和清理
//1、构造函数 进行初始化操作
//2、析构函数 进行清理的操作
class Person
{
public://1.1、构造函数//没有返回值 不用void//函数名 与类名称相同//构造函数可以有参数可以发生重载//创建对象的时候构造函数会自动调用且只调用一次Person(){cout Person 构造函数的调用 endl;}//1.2、析构函数//没有返回值 不写void//函数名和类名相同 在名称前加~//析构函数不可以有参数不可以发生重载//对象销毁前 会自动调用析构函数 而且只会调用一次~Person(){cout Person 析构函数的调用 endl;}
};void test01()
{Person P;
}
int main()
{test01();system(pause);return 0;
}运行结果 Person 构造函数的调用
Person 析构函数的调用1.2 构造函数的分类及调用
两种分类方式
1按参数分为 有参构造和无参构造
2按类型分为 普通构造和拷贝构造
三种调用方式
1括号法
2显示法
3隐式转换法 构造函数的分类 #includeiostream
using namespace std;//1构造函数的分类及调用
//分类
//按照参数分类 无参构造(默认构造) 和 有参构造
//按照类型分类 普通构造 和 拷贝构造
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;}//析构函数~Person(){cout Person 析构函数的调用 endl;}
};构造函数的调用 1构造函数的调用括号法
void test01()
{//1、括号法Person p1; //默认构造函数的调用Person p2(10); //有参构造函数的调用Person p3(p2); //拷贝构造函数的调用//注意事项1//调用默认构造函数时候不用加()//因为下面这行代码编译器会认为是一个函数的声明//Person p1();cout p2的年龄为 p2.age endl;cout p3的年龄为 p3.age endl;
}int main()
{test01();system(pause);return 0;
}运行结果 Person 无参构造函数的调用
Person 有参构造函数的调用
Person 拷贝构造函数的调用
p2的年龄为10
p3的年龄为10
Person 析构函数的调用
Person 析构函数的调用
Person 析构函数的调用2构造函数的调用显示法
void test01()
{//2、显示法Person p1;Person p2 Person(10); //有参构造Person p3 Person(p2); //拷贝构造//Person(10); //匿名对象 特点当前行执行结束后系统会立即回收掉匿名对象//注意事项2//不要利用拷贝构造函数 初始化匿名对象 编译器会认为 Person(p3); 等价于 Person p3; 重定义报错。//Person(p3);cout p2的年龄为 p2.age endl;cout p3的年龄为 p3.age endl;
}
int main()
{test01();system(pause);return 0;
}运行结果 Person 无参构造函数的调用
Person 有参构造函数的调用
Person 拷贝构造函数的调用
p2的年龄为10
p3的年龄为10
Person 析构函数的调用
Person 析构函数的调用
Person 析构函数的调用3构造函数的调用
void test01()
{//3、隐式转换法Person p4 10; //相当于 写了 Person(10); Person p5 p4; //拷贝构造cout p4的年龄 p4.age endl;cout p5的年龄 p5.age endl;
}
int main()
{test01();system(pause);return 0;
}运行结果 Person 有参构造函数的调用
Person 拷贝构造函数的调用
p4的年龄10
p5的年龄10
Person 析构函数的调用
Person 析构函数的调用1.3 拷贝构造函数调用时机
C中拷贝构造函数调用时机通常有三种情况。 使用一个已经创建完毕的对象来初始化一个新对象。 值传递的方式给函数参数传值。 以值方式返回局部对象。 示例代码 #includeiostream
using namespace std;//拷贝构造函数调用时机//1、使用一个已经创建完毕的对象来初始化一个新对象//2、值传递的方式给函数参数传值//3、值方式返回局部对象
class Person
{
public:int m_Age;Person(){cout Person默认构造函数的调用 endl;}Person(int age){m_Age age;cout Person有参构造函数的调用 endl;}Person(const Person p){m_Age p.m_Age;cout Person拷贝构造函数的调用 endl;}~Person(){cout Person默认析构函数的调用 endl;}
};1、使用一个已经创建完毕的对象来初始化一个新对象。
//1、使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{Person p1(20);Person p2(p1);cout p2的年龄为 p2.m_Age endl;
}
int main()
{test01();system(pause);return 0;
}运行结果 Person有参构造函数的调用
Person拷贝构造函数的调用
p2的年龄为20
Person默认析构函数的调用
Person默认析构函数的调用2、值传递的方式给函数参数传值
//2、值传递的方式给函数参数传值
//相当于Person p1 p;
void doWork(Person p1)
{}
void test02()
{Person p;doWork(p);
}运行结果 Person默认构造函数的调用
Person拷贝构造函数的调用
Person默认析构函数的调用
Person默认析构函数的调用3、值方式返回局部对象
//3、值方式返回局部对象
Person doWork03()
{Person p1;cout p1的地址为 (int*)p1 endl;return p1;
}
void test03()
{Person p doWork03();cout p的地址为 (int*)p endl;
}运行结果 Person默认构造函数的调用
010FF894
Person拷贝构造函数的调用
Person默认析构函数的调用
010FF98C
Person默认析构函数的调用运行结果VS2022编译器会优化 Person默认构造函数的调用
p1的地址为000000B083EFF5D4
p的地址为000000B083EFF5D4
Person默认析构函数的调用1.4 构造函数调用规则
默认情况下c编译器至少给一个类添加3个函数。
1默认构造函数(无参函数体为空)。
2默认析构函数(无参函数体为空)。
3默认拷贝构造函数对属性进行值拷贝。
构造函数调用规则如下 如果用户定义有参构造函数c不在提供默认无参构造但是会提供默认拷贝构造。 如果用户定义拷贝构造函数c不会再提供其他构造函数。 示例代码 #includeiostream
using namespace std;
//构造函数的调用规则
//1、创建一个类C编译器会给每个类都添加至少三个函数
//默认构造(空实现)
//析构函数(空实现)
//拷贝构造(值拷贝)//2、
// 如果我们写了有参构造函数编译器就不再提供默认构造依然提供拷贝构造
// 如果我们写了拷贝构造函数编译器就不再提供其他普通构造函数了class Person
{
public:int m_Age;Person(){cout Person默认构造函数的调用 endl;}Person(int age){m_Age age;cout Person有参构造函数的调用 endl;}/*Person(const Person p){m_Age p.m_Age;cout Person拷贝构造函数的调用 endl;}*/~Person(){cout Person默认析构函数的调用 endl;}
};void test01()
{Person p;p.m_Age 18;Person p2(p);cout p2的年龄为 p2.m_Age endl;
}int main()
{test01();system(pause);return 0;
}运行结果 Person默认构造函数的调用
p2的年龄为18
Person默认析构函数的调用
Person默认析构函数的调用可以看到我们定义了有参构造函数而我们没有定义拷贝构造函数但是编译器为我们提供了拷贝构造函数将p.m_Age 18值拷贝给了p2。
1.5 深拷贝与浅拷贝
深浅拷贝是面试经典问题也是常见的一个坑。
浅拷贝简单的赋值拷贝操作。
深拷贝在堆区重新申请空间进行拷贝操作。 示例代码 #includeiostream
using namespace std;
//深拷贝与浅拷贝class Person
{
public:int m_Age;int *m_Height; //身高
public:Person() {cout Person的默认构造函数调用 endl;}Person(int age, int height){cout Person的有参构造函数调用 endl;m_Age age;m_Height new int(height);}//自己实现拷贝构造函数解决浅拷贝带来的问题Person(const Person p){cout Person 拷贝构造函数调用 endl;m_Age p.m_Age;//m_Height p.m_Height; 编译器默认实现就是这行代码//深拷贝操作m_Height new int(*p.m_Height);}~Person(){//析构代码将堆区开辟数据做释放操作if (m_Height ! NULL){delete m_Height;m_Height NULL;}cout Person的默认析构函数调用 endl; }
};void test01()
{Person p1(18,160);Person p2(p1);cout p1的年龄为 p1.m_Age 身高为 *p1.m_Height endl;cout p2的年龄为 p2.m_Age 身高为 *p2.m_Height endl;
}int main()
{test01();system(pause);return 0;
}运行结果 Person的有参构造函数调用
Person 拷贝构造函数调用
p1的年龄为18 身高为160
p2的年龄为18 身高为160
Person的默认析构函数调用
Person的默认析构函数调用假如我们没有自行设计拷贝构造函数那么编译器会默认为浅拷贝也就是说首先p2析构内存释放然后p1析构内存释放然而此时对应内存已经释放过了无法释放第二次所以会报错。
浅拷贝的意思就是 p1.m_Height在堆区开辟内存对应的地址为0x0011而拷贝给p2.m_Height它对应的地址也是0x0011。
而深拷贝也就是说 p1.m_Height在堆区开辟内存对应的地址为0x0011而拷贝给p2.m_Heightp2.m_Height开辟了一个新地址0x0022但是0x0011和0x0022的内容都是160。 总结如果属性有在堆区开辟的一定要自己提供拷贝构造函数防止浅拷贝带来的问题。