【对象的初始化和清理】

  • 生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用时候也会删除一些自己信息数据保证安全
  • C++中的面向对象来源于生活,每个对象也都会有初始设置以及 对象销毁前的清理数据的设置。

构造函数和析构函数

对象的初始化和清理也是两个非常重要的安全问题

​ 一个对象或者变量没有初始状态,对其使用后果是未知

​ 同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题

c++利用了构造函数析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。

对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供

编译器提供的构造函数和析构函数是空实现。

  • 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
  • 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作(例如,销毁堆中开辟的空间)

构造函数语法:类名(){}

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

析构函数语法: ~类名(){}

  1. 析构函数,没有返回值也不写void
  2. 函数名称与类名相同,在名称前加上符号 ~
  3. 析构函数不可以有参数,因此不可以发生重载
  4. 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
class Person
{
public:
//构造函数
Person()
{
cout << "Person的构造函数调用" << endl;
}
//析构函数
~Person()
{
cout << "Person的析构函数调用" << endl;
} }; void test01()
{
Person p;
} int main() { test01(); system("pause"); return 0;
}

构造函数的分类及调用

两种分类方式:

​ 按参数分为: 有参构造和无参构造

​ 按类型分为: 普通构造和拷贝构造

三种调用方式:

  • 括号法
  • 显示法
  • 隐式转换法

示例:

//1、构造函数分类
// 按照参数分类分为 有参和无参构造 无参又称为默认构造函数
// 按照类型分类分为 普通构造和拷贝构造 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;
}; //2、构造函数的调用
//调用无参构造函数
void test01() {
Person p; //调用无参构造函数
} //调用有参的构造函数
void test02() { //2.1 括号法,常用
Person p1(10);
//注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明
//Person p2(); //2.2 显式法
Person p2 = Person(10);
Person p3 = Person(p2);
//Person(10)单独写就是匿名对象 当前行结束之后,马上析构 //2.3 隐式转换法
Person p4 = 10; // Person p4 = Person(10);
Person p5 = p4; // Person p5 = Person(p4); //注意2:不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明
//Person p5(p4);
} int main() { test01();
//test02(); system("pause"); return 0;
}

拷贝构造函数调用时机

C++中拷贝构造函数调用时机通常有三种情况

  • 使用一个已经创建完毕的对象来初始化一个新对象
  • 值传递的方式给函数参数传值
  • 以值方式返回局部对象

示例:

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 test01() { Person man(100); //p对象已经创建完毕
Person newman(man); //调用拷贝构造函数
Person newman2 = man; //拷贝构造 //Person newman3;
//newman3 = man; //不是调用拷贝构造函数,赋值操作
} //2. 值传递的方式给函数参数传值
//相当于Person p1 = p;
void doWork(Person p1) {}
void test02() {
Person p; //无参构造函数
doWork(p);
} //3. 以值方式返回局部对象
Person doWork2()
{
Person p1;
cout << (int *)&p1 << endl;
return p1;
} void test03()
{
Person p = doWork2();
cout << (int *)&p << endl;
} int main() { //test01();
//test02();
test03(); system("pause"); return 0;
}

构造函数调用规则

默认情况下,c++编译器至少给一个类添加3个函数

1.默认构造函数(无参,函数体为空)

2.默认析构函数(无参,函数体为空)

3.默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则如下:

  • 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造(和Java规则类似)

  • 如果用户定义拷贝构造函数,c++不会再提供其他构造函数

示例:

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 test01()
{
Person p1(18);
//如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作
Person p2(p1); cout << "p2的年龄为: " << p2.age << endl;
} void test02()
{
//如果用户提供有参构造,编译器不会提供默认构造,会提供拷贝构造
Person p1; //此时如果用户自己没有提供默认构造,会出错
Person p2(10); //用户提供的有参
Person p3(p2); //此时如果用户没有提供拷贝构造,编译器会提供 //如果用户提供拷贝构造,编译器不会提供其他构造函数
Person p4; //此时如果用户自己没有提供默认构造,会出错
Person p5(10); //此时如果用户自己没有提供有参,会出错
Person p6(p5); //用户自己提供拷贝构造
} int main() { test01(); system("pause"); return 0;
}

深拷贝与浅拷贝

深浅拷贝是面试经典问题,也是常见的一个坑

浅拷贝:简单的赋值拷贝操作

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

示例:

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_height = new int(*p.m_height); } //析构函数
~Person() {
cout << "析构函数!" << endl;
if (m_height != NULL)
{
delete m_height;
}
}
public:
int m_age;
int* m_height;
}; void test01()
{
Person p1(18, 180); 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;
}

总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题

例如上面,test01中的p2对象调用了Person的拷贝构造函数,输入是p1

按照常规处理(即不自己给出拷贝构造函数,用编译器默认的),p1中的属性的地址会全部拷贝给p2一份,也就是p1、p2指向同一个地址中的属性。上述过程即浅拷贝

这样在对象使用完毕,调用析构函数释放资源时,p1会报错,因为p2的析构函数已经将地址释放(后进栈先出栈)

为此,我们需要自己写拷贝构造函数

使用该函数在堆区创建新内存来为p2存放经由p1拷贝得到的数据(内容一致,地址不同)

由此解决之前的问题,上述过程即深拷贝

初始化列表

作用:

C++提供了初始化列表语法,用来初始化属性

语法:构造函数():属性1(值1),属性2(值2)... {}

示例:

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 << "mA:" << m_A << endl;
cout << "mB:" << m_B << endl;
cout << "mC:" << m_C << endl;
}
private:
int m_A;
int m_B;
int m_C;
}; int main() { Person p(1, 2, 3);
p.PrintPerson(); system("pause"); return 0;
}

4.2.7 类对象作为类成员

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

例如:

class A {}
class B
{
A a;
}

B类中有对象A作为成员,A为对象成员

那么当创建B对象时,A与B的构造和析构的顺序是谁先谁后?

示例:

class Phone
{
public:
Phone(string name)
{
m_PhoneName = name;
cout << "Phone构造" << endl;
} ~Phone()
{
cout << "Phone析构" << endl;
} string m_PhoneName; }; class Person
{
public: //初始化列表可以告诉编译器调用哪一个构造函数
Person(string name, string pName) :m_Name(name), m_Phone(pName)
{
cout << "Person构造" << endl;
} ~Person()
{
cout << "Person析构" << endl;
} void playGame()
{
cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 牌手机! " << endl;
} string m_Name;
Phone m_Phone; };
void test01()
{
//当类中成员是其他类对象时,我们称该成员为 对象成员
//构造的顺序是 :先调用对象成员的构造,再调用本类构造
//析构顺序与构造相反
Person p("张三" , "苹果X");
p.playGame(); } int main() { test01(); system("pause"); return 0;
}

静态成员

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

静态成员分为:

静态成员变量

  • 所有对象共享同一份数据
  • 在编译阶段分配内存
  • 类内声明,类外初始化

静态成员函数

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

静态成员变量

class Person
{ public: static int m_A; //静态成员变量 //静态成员变量特点:
//1 在编译阶段分配内存
//2 类内声明,类外初始化
//3 所有对象共享同一份数据 private:
static int m_B; //静态成员变量也是有访问权限的
};
int Person::m_A = 10;
int Person::m_B = 10; void test01()
{
//静态成员变量两种访问方式 //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; //cout << "m_B = " << Person::m_B << endl; //私有权限访问不到
} int main() { test01();
system("pause"); return 0;
}

静态成员函数

class Person
{ public: //静态成员函数特点:
//1 程序共享一个函数
//2 静态成员函数只能访问静态成员变量 static void func()
{
cout << "func调用" << endl;
m_A = 100;
//m_B = 100; //错误,不可以访问非静态成员变量
} static int m_A; //静态成员变量
int m_B; //
private: //静态成员函数也是有访问权限的
static void func2()
{
cout << "func2调用" << endl;
}
};
int Person::m_A = 10; void test01()
{
//静态成员变量两种访问方式 //1、通过对象
Person p1;
p1.func(); //2、通过类名
Person::func(); //Person::func2(); //私有权限访问不到
} int main() { test01();
system("pause"); return 0;
}

【C++ OOP 02 对象的初始化和清理】构造/析构函数、深/浅拷贝、初始化列表以及静态成员的更多相关文章

  1. C++学习(9)—— 对象的初始化及清理

    1. 构造函数和析构函数 对象的初始化和清理是两个非常重要的安全问题 ​ 一个对象或者变量没有初始状态,对其使用后果是未知 ​ 同样的使用完一个对象或者变量,没有及时清理,也会造成一些安全问题   C ...

  2. C++核心编程 4 类和对象-对象的初始化和清理

    构造函数和析构函数 对象的初始化和清理工作是两个非常重要的安全问题,一个对象或者变量没有初始状态,对其使用结果是未知的,同样,使用完一个对象或变量,没有及时清理,也会造成一定的安全问题.C++利用了构 ...

  3. [C++核心编程] 4.2、类和对象-对象的初始化和清理

    文章目录 4.2 对象的初始化和清理 4.2.1 构造函数和析构函数 4.2.2 构造函数的分类及调用 4.2.3 拷贝构造函数调用时机 4.2.4 构造函数调用规则 4.2.5 深拷贝与浅拷贝 4. ...

  4. [THINKING IN JAVA]初始化和清理

    4.5初始化和清理 5.1 构造器初始化 无参:如果一个类没有构造器,那么会有一个默认的无参构造器,并且只能通过无参构造器初始化对象: 带参:传入参数初始化成员变量或者其它初始化操作需要的参数,如果类 ...

  5. Java编程思想——初始化与清理

    PS:最近一直忙于项目开发..所以一直没有写博客..趁着空闲期间来一发.. 学习内容: 1.初始化 2.清理 1.初始化   虽然自己的Java基础还是比较良好的..但是在解读编程思想的时候还是发现了 ...

  6. 图解 & 深入浅出Java初始化与清理:构造器必知必会

    Writer      :BYSocket(泥沙砖瓦浆木匠) 微         博:BYSocket 豆         瓣:BYSocket FaceBook:BYSocket Twitter   ...

  7. 大家一起和snailren学java-(四)初始化与清理

    初始化和清理,是一个生命周期的起始.在java中,对象的初始化和对象的回收是怎样的呢? 在开发c++的时候,有构造器的概念.即对象的创建,首先默认调用构造器进行初始化.在java中也有“构造器”.ja ...

  8. 初读"Thinking in Java"读书笔记之第五章 --- 初始化与清理

    用构造器确保初始化 构造器可以确保每个对象都会得到初始化,Java毁在创建对象时自动调用构造器. 构造器采用与类名相同的名称,因此并不适合"每个方法首字母小写的风格". 构造器默认 ...

  9. Chapter5_初始化与清理_用构造器初始化

    接下来进入第五章,java中初始化和清理的问题,这是两个涉及安全的重要命题.初始化的功能主要是为库中的构件(或者说类中的域)初始化一些值,清理的功能主要是清除程序中不再被需要的元素,防止资源过分被垃圾 ...

  10. ThinkingInJava 学习 之 0000004 初始化与清理

    1. 用构造器确保初始化. 不接受任何参数的构造器叫做默认构造器. Tree tree = new Tree(12); 如果Tree(int)时Tree类的唯一的构造器,那么编译器将不会允许你以其他任 ...

随机推荐

  1. [转帖]使用GCC编译器实测兆芯KX-U6780A的SPEC CPU2006成绩

      https://baijiahao.baidu.com/s?id=1722775453962904303 兆芯KX-U6780A是一款8核2.7GHz的使用x86/AMD64指令集(架构)的国产C ...

  2. [转帖]APIServer dry-run and kubectl diff

    https://kubernetes.io/blog/2019/01/14/apiserver-dry-run-and-kubectl-diff/ Monday, January 14, 2019 A ...

  3. top的简单学习

    获取当前进程的全部线程 jps 获取jvm的进程信息. top -Hp $pid -bn 1 > 1.txt 可以获取当前特定进程的所有子进程. 注意linux与Windows的不太一样. li ...

  4. IIS 实现autoindex的简单方法 能够下载文件等.

    之前使用nginx 的autoindex on 的参数 能够实现了 nginx的 目录浏览查看文件 但是那是linux上面的 windows 上面很多 使用的 其实是 iis的居多 然后看了下 其实也 ...

  5. Beyond Compare 的比较以及导出的简单设置方法

    最近需要对文件进行对比 但是发现对比的工作量比较难搞. 用到了beyond compare 的工具 感觉挺好用的 但是需要注意事项比较多这里记录一下 1.  session setting 里面进行设 ...

  6. 如何从0开始搭建 Vue 组件库

    作者:京东零售 陈艳春 前言: 组件设计是通过对功能及视觉表达中元素的拆解.归纳.重组,并基于可被复用的目的,形成规范化的组件,通过多维度组合来构建整个设计方案,將这些组件整理在一起,便形成组件库.本 ...

  7. TypeScript中typeof的简单介绍

    简单介绍typeof 我们都知道js提供了typeof,用来获取基本数据的类型. 实际上,TS也提供了 typeof 操作符. 可以在 [类型上下文]中进行类型查询. 只能够进行变量或者属性查询. 定 ...

  8. 补齐短板-开源IM项目OpenIM关于初始化/登录/好友接口文档介绍

    OpenIM文档方面的建设一直远远落后于开发, 也经常被开发者诟病,在接下来一周的时间里,我们重点补齐文档,让开发者更轻松接入.由于app sdk底层都是go来实现,所以本文先写一个模板和框架,在接下 ...

  9. 为不同版本python安装pip的正确做法

    想学习Python3,但是暂时又离不开Python2.在Windows上如何让它们共存呢? 目前国内网站经常会让大家把其中一个python.exe改个名字(嗯,我也这样讲过,在此纠正一下),这样区分开 ...

  10. centos7多网口配置同网段IP解决方案

    环境 CentOS Linux release 7.9.2009 (Core) 需求 服务器eth0和eth1配置同网段IP地址.掩码不配网关,同时连接两根网线,对端是两台物理隔离的交换机. 现象 给 ...