当前位置: 首页 > news >正文

C++核心编程学习--对象特性--对象的初始化和清理

构造函数和析构函数

构造函数用于初始化
析构函数用于清理
编译器会自动调用这两个函数,完成对象初始化和清理工作。
如果程序员不提供构造和析构,编译器会提供,但是两个函数是空实现。
构造函数:主要用于创建对象时,为对象的成员属性赋值。
析构函数:主要用于对象销毁时系统自动调用,执行一些清理工作。

构造函数的语法: 类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序在调用对象时候回自动调用构造,无需手动调用,且只会调用一次。

析构函数的语法: ~类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同加上~
3.构造函数不可以有参数,因此不可以发生重载
4.程序在销毁对象时候回自动调用析构,无需手动调用,且只会调用一次。

#include<iostream>
using namespace std;
#include<string>class Person{
public:Person() {cout << "person的构造函数调用" << endl;
}~Person() {cout << "person的析构函数调用" << endl;}private:string name;int age;
};int main() {Person stj;system("pause");return 0;
}

构造函数的分类和调用

两种分类:
有参构造(默认构造)和无参构造
按类型分为:
普通构造和拷贝构造
将对象的属性全部拷贝。

#include <iostream>
using namespace std;// 构造函数按参数分类为: 1.无参构造函数 2.有参构造函数
// 按类型分类为: 1.默认构造函数 2.拷贝构造函数
class Person {
public:// 无参(默认)构造函数Person() {cout << "无参构造函数" << endl;}// 有参构造函数Person(int a) {age = a;cout << "有参构造函数" << endl;}// 拷贝构造函数Person(const Person& p) {  // Person(const Person& P)// 将传入的对象的所有属性值拷贝到当前对象中age = p.age;cout << "拷贝构造函数" << endl;}// 析构函数~Person() {cout << "析构函数" << endl;}
public:int age;
};// 构造函数的调用
// 调用无参构造函数
void test1() {Person p;
}// 调用有参构造函数
void test2() {// 1.括号法:常用Person p0;      // 调用无参(默认)构造函数Person p1(10);   // 调用有参构造函数Person p2(p1);      //  调用拷贝构造函数// 注意1:调用无参构造函数不能带括号,例如:Person p2(),因为编译器会认为这是一个函数声明,不会认为在创建对象// 测试调用是否成功cout << "p1的年龄为:" << p1.age << endl;cout << "p2的年龄为:" << p2.age << endl;// 2.显式法Person p3 = Person(10);     // 调用有参构造函数Person p4 = Person(p3);     // 调用拷贝构造函数// 注意2:Person(10)可以放在等号右边,但是单独写是匿名对象,匿名对象的特点是,当前行执行结束后,匿名对象就销毁了// 注意3: 不要利用拷贝构造函数初始化匿名对象,错误写法:Person(p3); 编译器会认为Person(p3) == Person p3 重定义了// 3.隐式转换法Person p5 = 10;     // 等价于 Person p4 = Person(10);Person p6 = p5;     // 拷贝构造// 注意:不能利用拷贝构造函数初始化匿名对象,因为匿名对象没有名字,编译器会认为是对象声明。错误写法:Person p5(p4)
}int main()
{test1();cout << "-------------" << endl;test2();return 0;
}

拷贝函数的调用时机
1.使用一个已经创建完毕的对象来初始化一个新对象
2.值传递的方式给函数传参数
3.值方式返回局部对象
如果自己没有定义拷贝构造函数,在上述操作中,编译器会自动创建一个拷贝构造函数,实现临时对象的创建。相当于函数中的临时副本、临时变量。

#include <iostream>
using namespace std;class Person {
public:Person() {cout << "无参构造函数!" << endl;mAge = 0;}Person(int age) {cout << "有参构造函数!" << endl;mAge = age;}Person(const Person& p) {cout << "拷贝构造函数!" << endl;mAge = p.mAge;}//析构函数在释放内存之前调用~Person() {cout << "析构函数!" << endl;}
public:int mAge;
};// 1.使用一个已经初始化的对象来初始化一个对象
void test1() {Person man(100);    // 创建一个对象Person newman(man);     // 调用拷贝构造函数Person newman2 = man;   //  调用拷贝构造函数
}// 2.值传递的方式给函数参数传值    这里在dowork中修改某个属性,原P中的属性不会改变,因为是临时拷贝
void doWork(Person p){}
void test2() {Person p;doWork(p);
}// 3.值方式返回局部对象
Person doWork2() {Person p1;return p1;
}
void test3() {Person p = doWork2();
}
int main() {test1();test2();test3();return 0;
}

构造函数的调用规则
默认情况下,C++编译器至少给一个类添加三个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
如果已经自定义了某种构造函数,比如说有参的,那么编译器就不会提供相应的构造函数了,再进行无参构造就会报错。但是会默认提供拷贝构造函数
如果只自定义了拷贝构造函数,编译器也不会自动创建无参和有参构造函数。

#include <iostream>
using namespace std;class Person {
public://无参(默认)构造函数Person() {cout << "无参构造函数!" << endl;}//有参构造函数Person(int a) {age = a;cout << "有参构造函数!" << endl;}//拷贝构造函数Person (const Person &p) {age = p.age;cout << "拷贝构造函数!" << endl;}// 析构函数~Person() {cout << "析构函数!" << endl;}public:int age;
};void test1() {Person p1(18);// 如果不写拷贝构造,编译器会自动添加拷贝构造函数,并做浅拷贝操作Person p2 = p1;cout << "p2的年龄为:" << p2.age << endl;
}void test2() {// 如果用户提供有参构造,编译器不会提供默认构造函数,会提出拷贝构造Person p1;      //此时如果用户没有提供无参构造,编译器会报错Person p2(10);      // 此时如果用户没有提供有参构造,编译器会报错Person p3(p2);      //  此时如果用户没有提供拷贝构造,编译器会提供// 如果用户提供拷贝构造,编译器不会提供其他构造函数Person p4;      // 此时如果用户没有提供无参构造,编译器会报错Person p5(10);     //  此时如果用户没有提供有参构造,编译器会报错Person p6(p5);      // 此时如果用户没有提供拷贝构造,编译器会报错
}
int main() {test1();test2();system("pause");return 0;
}

深拷贝和浅拷贝

浅拷贝:简单的赋值操作
如果使用默认的拷贝函数,会浅拷贝,逐字节的将对象拷贝到另一个对象上(相当于直接赋值)
如果已经在栈上创建了两个对象,也就是局部函数中创建的对象,并且执行默认的拷贝构造函数,那么会进行浅拷贝,这时如果在堆区开辟数据,那么就会把堆区指针直接拷贝过去,最终析构的时候由于(栈区:先进后出,先创建的后释放)会逐个释放,释放两次堆区的数据,所以导致堆区重复释放。
所以需要自定义的拷贝构造函数,拷贝构造函数直接申请一块新的堆区内存,用于存放与拷贝内容相同的数据,这样在析构的时候就不会重复释放同一个堆区的内存了。

深拷贝:在堆区重新申请空间,进行拷贝操作

#include <iostream>
using namespace std;class Person {
public://无参(默认)构造函数Person() {cout << "无参构造函数!" << endl;}//有参构造函数Person(int age, int height) {cout << "有参构造函数!" << endl;m_age = age;m_height = new int(height);   //创建在堆区,用指针接收}// 拷贝构造函数Person (const Person& p) {cout << "拷贝构造函数!" << endl;m_age = p.m_age;//默认实现的代码//m_age  = p.m_age  这里直接是浅拷贝m_height = new int(*p.m_height);}// 析构函数  将堆区数据进行释放~Person() {cout << "析构函数!" << endl;// 何时需要自己写析构函数:平时直接使用编译器默认提供的析构函数,但是涉及到堆区数据,则需要自己写析构函数来释放堆区数据if (m_height != NULL) { // 这里就会走两个不同堆区的释放delete m_height;m_height = NULL;    // 防止野指针出现,进行置空操作}}public:int m_age;int* m_height;
};void test01() {Person p1(18, 180);Person p2(p1);cout << "p1的年龄:" << p1.m_age << ",p1的身高:" << *p1.m_height << endl;cout << "p2的年龄:" << p2.m_age << ",p2的身高:" << *p2.m_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) {//	m_A = a;//	m_B = b;//	m_C = c;// }//初始化列表方式初始化,与传统方式初始化不同,更方便Person(int a, int b, int c):m_A(a), m_B(b), m_C(c){}void printPerson() {cout << "m_A:" << m_A << endl;cout << "m_B:" << m_B << endl;cout << "m_C:" << m_C << endl;}private:int m_A;int m_B;int m_C;
};
int main() {// 初始化列表Person p(1,2,3);p.printPerson();return 0;
}

类对象作为类成员

C++类中的成员可以是另一个类的对象,我们成该成员为对象成员

class A {}
class B {A a;
}

那么创建B对象时,A与B的构造和析构顺序是谁先谁后
我猜 构造的时候先B再A 析构 的时候先A再B 错
先构造类中的其他的类的对象,先有零件才能出来整体。
析构翻过来,先析构本类再析构类内的对象

#include <iostream>
using namespace std;class Phone {
public:Phone(string name) {m_Phonename = name;cout << "Phone构造" << endl;}~ Phone() {cout << "Phone析构" << endl;}string m_Phonename;
};class Person {
public:string m_name;Phone m_phone;//初始化列表可以告诉编译器调用哪一个构造函数 这里phonename是string类型 m_phone(phonename)=== Phone m_phone =  phonename (隐式转换法)Person (string name, string phonename): m_name(name), m_phone(phonename) {cout << "Person构造" << endl;}~ Person() {cout << "Person析构" << endl;}void playGame() {cout << m_name << "使用" << m_phone.m_Phonename << "打游戏" << endl;}
};void test01() {Person p("jennie", "samsung");p.playGame();
}
int main() {test01();return 0;
}

静态成员

静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员。静态成员分为:

静态成员变量

  • 所有的对象共享同一份数据
  • (同一个类创建出的所有对象都共享,也就是其中一个对象修改了这个属性,其他的对象属性也跟着修改)
  • 在编译阶段分配内存
  • 类内声明类外初始化 必须有初始值

静态成员函数

  • 所有的对象共享一个函数
  • 静态成员函数只能访问静态成员变量

静态成员变量 不属于某个对象,所有的对象都共享同一份数据
因此,静态成员变量有两种访问方式
1.通过对象进行访问
2.通过类名进行访问

#include <iostream>
using namespace std;// 静态成员变量
class Person {
public:static int m_A;private:static int m_B;     // 静态成员变量也是有访问权限的
};
// 静态成员变量初始化
int Person::m_A = 10;
int Person::m_B = 20;void test01() {// 静态成员变量不属于某个对象。所有对象都共享同一份数据,因此有2种访问方式:// 1.通过对象Person p1;p1.m_A = 100;cout << "p1.m_A = " << p1.m_A << endl;Person p2;p2.m_A = 200;cout << "p1.m_A = " << p1.m_A << endl;  // 共享同一份数据cout << "p2.m_A = " << p2.m_A << endl;// 2.通过类名 访问静态成员变量cout << "m_A = " << Person::m_A << endl;}int main() {test01();return 0;
}

静态成员函数,静态成员函数不可以访问非静态的成员变量,因为静态函数每个对象都是共用的一个,而非静态变量每个对象都有一个,因此不知道是访问的哪个,因此不能访问。
私有的静态成员函数也无法被类外访问。

#include <iostream>
using namespace std;class Person {
public:static int m_A;int m_B;static void func() {cout << "func调用 " << endl;m_A = 100;// m_B = 90;   // 错误,静态成员函数不能访问非静态成员变量}private:static void func2() {cout << "func2调用 " << endl;}
};
int Person::m_A = 10;
// int Person::m_B = 100;   // 错误,非静态成员变量不能在类外定义、初始化,也不能通过类名访问void test01() {// 静态成员函数2种调用方式// 1.  通过对象访问Person p;p.func();// 2. 通过类名访问Person::func();// Person::func2();     // 私有静态成员函数不能通过类名访问
}
int main() {test01();return 0;
}
http://www.dtcms.com/a/296744.html

相关文章:

  • MCU(微控制器)中的高电平与低电平?
  • 基于LiteOS的OTA组件实现对终端固件的差分升级和全量升级
  • Rust与YOLO目标检测实战
  • 【redis其它面试问题】
  • 【OD机试】矩阵匹配
  • JavaScript高级特性与优化全解析
  • JManus Plan-Act模式:如何对用户的需求进行规划和执行
  • 【第五节】列表渲染
  • p5.js 椭圆的用法:从基础到创意应用
  • Java 实现 B/S 架构详解:从基础到实战,彻底掌握浏览器/服务器编程
  • 北京-4年功能测试2年空窗-报培训班学测开-第五十九天-模拟面试前
  • 前端学习日记(十二)
  • MongoDB常用场景
  • jax study notes[19]
  • 【Kubernetes】通过 YAML 创建 nginx Pod 并验证,流程总结
  • Python编程进阶知识之第五课处理数据(matplotlib)
  • rust流程控制
  • Code Composer Studio:CCS 设置代码折叠
  • 20.OSPF路由协议·单区域
  • 枚举右,维护左高级篇
  • [明道云] -基础入门1- 什么是明道云 HAP 平台?
  • 【基础篇一】Python Web开发的演进历程(CGI → WSGI → ASGI)
  • 100条SQL语句分类精讲:从基础到进阶的实操指南
  • Matplotlib详细教程(基础介绍,参数调整,绘图教程)
  • 支付宝小程序 SEO 优化指南:从流量获取到商业转化
  • 【Linux】常用命令(一)
  • LockscreenCredential 类方法详解
  • 机器学习入门与经典knn算法表文解析
  • 模型的存储、加载和部署
  • 天邑TY1613_S905L3SB_安卓9-高安版和非高安版-线刷固件包