第二层:对象的初始化和清理

简介: 第二层:对象的初始化和清理

前情回顾


上回说到,我踏入C++古塔,在第一层中了解到了面向对象的第一大特性——封装,通过努力,我成功获得封装的力量,并且上到了第二层…


🚄上章地址:第一层:封装


对象特性——对象的初始化和清理


踏入第二层当中,那道声音再次飘来:“挑战者,这层的任务需要你掌握对象特性中的初始化和清理,祝你好运…”一块石板再次出现在我的眼前


构造函数和析构函数


什么是构造函数和析构函数


在C++中,对象在被调用和销毁的时候,会默认调用两个函数,在创建对象的时候,会调用构造函数,在销毁的时候会调用析构函数,它们在一个对象中,只会被调用一次,所以构造函数和析构函数是必须存在的,当程序员本身不提供任何的构造和析构函数的时候,编译器会提供默认构造函数


构造函数


构造函数的语法

构造函数没有返回值,也不用写void

构造函数的函数名与类名相同

构造函数可以有参数,可以发生函数重载1

基本语法了解,那构造函数就可以尝试的去写一下。


class A
{
public:
  A()
  {
  cout << "A的构造函数" << endl;
  }
  string a;
};
int main()
{
  A c;
}


0a2653c851af460fa595bd959398a8f1.png


构造函数的分类

按照参数分类

按照参数可以分为:无参构造函数(默认构造函数)和有参构造函数,它们的区别在于函数有无参数。


按照类型分类

按照类型可以分为:普通构造函数和拷贝构造函数。


拷贝构造函数

上面提到了拷贝构造函数,那拷贝构造函数是什么?从表面来看,在构造的同时拷贝,那拷贝什么?数据吗?拷贝到哪里去呢?这里我们先将拷贝构造函数的语法写罗列出来。


类名(const 类名 引用2)

可以看到,它的参数是我们的对象,那它的作用大致就可以猜到了,就是将对象的数据拷贝到调用拷贝构造函数的对象上面去,可以用代码来试验一下。


#include<string>
#include<iostream>
using namespace std;
class A
{
public:
  A()
  {
  cout << "A的构造函数" << endl;
  }
  A(A &c)
  {
  b = c.b;
  }
  int b;
};
int main()
{
  A c;
  cin >> c.b;
  A b(c);
  cout<< b.b << endl;
}


0a2653c851af460fa595bd959398a8f1.png

0a2653c851af460fa595bd959398a8f1.png

可以看到c里面的内容拷贝到了b里面。


拷贝函数的调用时机

通常在三种情况下调用我们的拷贝函数:


1.使用一个已经创建完毕的对象来初始化一个新对象,可以参考上面

2.值传递的方式给函数参数传值

3.以值的方式返回局部变量

对于第二点,可以看下方这张图来理解

0a2653c851af460fa595bd959398a8f1.png

int main函数内部调用test1函数,函数参数为类,在调用函数时候,会调用拷贝构造函数,然后在回去调用test1函数内部。

而第三点,是因为函数中,当返回的值是我们的对象时,因为出了函数体,局部变量销毁,所以函数内对象会进行销毁,这个时候就会发生拷贝,将函数内的对象数据重新拷贝到一块新的空间内,进行返回。


深拷贝和浅拷贝

浅拷贝是进行简单的拷贝操作,只会进行值拷贝

深拷贝是在堆区重新申请空间,进行拷贝

那为什么会出现深拷贝和浅拷贝呢?当类内的属性有指针的时候,这个时候发生拷贝,会拷贝的是地址。

0a2653c851af460fa595bd959398a8f1.png

因为指针内存放数据之前,我们要使用new3函数进行开辟空间,这个时候我们就需要在析构函数内使用delete4释放空间,但是因为两个对象都同时指向了同一块空间,就会释放两个同一块空间的地址,对堆区进行了重复释放,那这个时候想要解决,就需要程序员自己设计出拷贝构造函数,进行深拷贝,对指针的拷贝用new操作符重新开辟一块空间。

深拷贝


class A
{
public:
  A()
  {
  cout << "A的构造函数" << endl;
  }
  //浅拷贝
  A(A &c)
  {
  b=c.b;
  d=c.d;
  }
  //深拷贝
  A(A &c)
  {
  cout << "拷贝构造函数" << endl;
  b = c.b;
  d = new int(*c.d);
  }
  int b;
  int* d;
};


构造函数的调用方法

括号法

括号法在上面我们进行拷贝构造函数调用时已经用过,就是在对象后面加括号,括号内是参数,但是这个时候要注意!对于无参构造函数而言,是在后面加括号的,这样编译器会认为这一个函数声明

0a2653c851af460fa595bd959398a8f1.png

在编译器眼中,加括号的就会认为是这样。


显示法

显示法是将我们的构造函数当赋值一样,书写方式如下:


类名 对象名 = 类名(参数);


函数名(参数)叫做匿名对象,特点是当执行结束后,系统会立即回收匿名对象。

注意!:不要利用拷贝构造函数去初始化匿名对象,这个时候编译器会认为这是一个函数声明。


隐式转换法

隐式转换法是将我们的显示法中等号右侧的类名省略,书写方式如下:


类名 对象名 =参数;


构造函数规则

在默认情况下,C++编译器至少会给一个类添加三个函数:


1.默认构造函数(无参)(内部空实现)

2.默认析构函数(内部空实现)

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


构造函数规则如下:


如果程序员定义有参构造函数,C++不在提供默认构造函数(无参),但是会提供默认拷贝构造函数,如果程序员定义拷贝构造函数,C++则不会提供任何构造函数。


析构函数


析构函数的语法

没有返回值,也不需要写void

函数名和类名相同,但是需要在名称前加~

析构函数不可以有参数,不能发生重载

class A
{
public:
  A()
  {
  cout << "A的构造函数" << endl;
  }
  //析构函数
  ~A()
  {
  }
  string a;
};
int main()
{
  A c;
}


析构函数的作用

当类内调用new操作符开辟空间时,需要在析构函数内部进行释放空间的操作,用delete操作符,最好在将指向new操作符开辟的空间的指针,置空,防止野指针。


初始化列表


作用


对类内的属性进行初始化操作


语法


构造函数():属性1(初始值),属性2(初始值)…


class man
{
public:
  //构造函数,初始化列表
  man() :_name("狗蛋"), age(18), car("鬼火")
  {
  }
  //属性
  string _name;//名字
  int age;//年龄
  string car;//汽车
};


这是最基础的用法,还可以和有参构造函数进行结合:


#include<string>
#include<iostream>
using namespace std;
class man
{
public:
  //构造函数,初始化列表
  man(string a, int b, string c) :_name(a), age(b), car(c)
  {
  }
  //属性
  string _name;//名字
  int age;//年龄
  string car;//汽车
};
int main()
{
  man m1("狗蛋", 18, "鬼火");
  return 0;
}


也可以这样去进行初始化。


类对象作为类成员


在C++中,类中的成员可以是另一个类的对象,这个时候称这个成员为对象成员:


class A
{
};
class B
{
  A a;
};


上述代码中,A a就是对象成员,在我们创建B类的对象时,A a为对象成员,要先有成员才能有这个类,所以会先调用A的构造函数,在调用B的构造函数,但是在解析的时候时相反的,先析构B,在析构A。


静态成员


静态成员变量


静态成员变量,是将我们的成员变量放入到静态区,需要在成员的类型前面加static关键字。


静态成员变量特点

1.因为放入到了静态区,所以所有对象是共享同一份数据的,不属于任何的一个对象了

2.会在编译阶段就分配内存,相当于在运行之前就已经分配好内存了,分配在全局区

3.类内声明,类外初始化,比如有一个初始值才可以用,或者就无法访问到那片内存空间。

对于静态成员变量的初始化我们可以这样:


class A
{
  static int a;
}
int A::a=//初始值;


也是因为上述特点,静态成员不属于任何对象,所有有了两种访问方式:


1.通过对象进行访问,访问方式:对象名.成员名

2.通过类名进行访问,访问方式:类名:: 成员名

静态成员变量也是遵循访问权限的。


静态成员函数


访问方式

与静态成员变量的访问方式是一样的。


注意事项

1.静态成员函数可以访问静态成员变量

2.静态成员函数不可以访问非静态的成员变量

3.放访问非静态的时候,这个时候静态成员函数是不知道修改的是哪个对象的,如果修改,它会把所有对象的这个属性进行修改

静态成员函数同样遵循访问权限。


掌握对象初始化和清理,步入第三层


随着面前石板的倒下,第三层的楼梯显现在了我的眼前…


本章知识点(图片形式)


0a2653c851af460fa595bd959398a8f1.png


😘预知后事如何,关注新专栏,和我一起征服C++这座巨塔

🚀专栏:C++爬塔日记

🙉都看到这里了,留下你们的👍点赞+⭐收藏+📋评论吧🙉


出现的陌生名词解释


看到函数重载,可能有些人就懵了,这里解释一下,在C++中,函数名是可以重复的,这个时候参数不同,或者参数的顺序不一样,都是可以的,但是如果光返回类型不同,参数相同,则不能发生函数重载,编译器会报错。 ↩︎


引用,换一种说法就是取别名,比如,我有一个同学,他叫做李鑫,那我们可能会给他取个外号叫做李三金,当说到这个外号的时候,我们会想到李鑫这个人,所以引用是不会在去内存当中重新开辟一块空间的,它和它引用的变量是用了同一块空间,它的书写形式为:

数据类型 &变量名=要引用的变量名;

还要注意,对于引用来说,我们不能对其进行第二次改变,如果aa是a的别名,那aa就不能在改变成别的变量的别名了,对于引用来说,引用就等于我们的指针加const,,把const加在我们的解引用右侧就等于我们的引用。 ↩︎


new操作符是C++用于内存开辟的函数,它的书写形式为:

指针=new 和指针同类型 (这里可以直接赋值);

指针=new 和指针同类型 [开辟多少个];

上面第一种为开辟一个变量空间,第二个为开辟数组空间。 ↩︎


delete操作符,用于对new申请下来的空间进行空间释放,书写形式:

delete(指针); ↩︎


相关文章
|
5天前
LabVIEW使用调用节点与通过引用调用节点调用VI时的差别
LabVIEW使用调用节点与通过引用调用节点调用VI时的差别
|
5天前
|
编译器 数据安全/隐私保护 C++
【类与对象】封装&对象的初始化及清理
【类与对象】封装&对象的初始化及清理
|
5天前
new 一个对象的过程中发生了什么
new 一个对象的过程中发生了什么
12 2
|
5天前
|
C++
35对象的动态建立和释放
35对象的动态建立和释放
12 1
|
5天前
|
安全 编译器 C++
C++类与对象【对象的初始化和清理】
C++类与对象【对象的初始化和清理】
C++类与对象【对象的初始化和清理】
|
5天前
合并对象的几种方法
合并对象的几种方法
30 5
|
11月前
|
Java 编译器
对象的构造及初始化
对象的构造及初始化
60 0
|
12月前
如何在把创建临时变量的前提下交换两个数(直接上代码)
如何在把创建临时变量的前提下交换两个数(直接上代码)
|
Java
JAVA数组批量设值(初始化)的办法
JAVA数组批量设值(初始化)的办法
126 0