C++学习:4拷贝友元

Posted 想文艺一点的程序员

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++学习:4拷贝友元相关的知识,希望对你有一定的参考价值。

拷贝构造

1、拷贝构造函数

也是一种构造函数,只是比较特殊

问题:

什么时候调用构造函数? 创建一个对象的时候进行调用。

什么时候调用拷贝构造函数? 当利用已存在的对象创建一个新对象时类似于复制粘贴,生成一个新文件

  • 拷贝构造函数的格式是固定的,接收一个const引用作为参数


理解拷贝:类似于复制粘贴,生成一个新文件, 所以我们希望 新文件和旧文件的内容是一样的

  • 当我们去掉拷贝构造函数,看看会发生什么。

  • 当我们写了拷贝构造函数,但是里面是空的,会发生什么。

结果:并不会对成员变量进行拷贝

  • 在拷贝构造函数当中,添加拷贝代码

反问:这情况下,我们写拷贝构造函数有用吗?

  • 如果我们想拷贝所有成员变量,这样写根本没有必要。
  • 因为不写,默认也会拷贝所有的成员变量。

我们有个性化需求的时候,可以写:

  • 只希望部分成员变量进行拷贝操作。

2、调用父类的拷贝构造函数

怎么调用父类的拷贝构造函数?

  • 如果我们是想要拷贝全部成员变量,那么就没有必要调用父类的拷贝构造函数。
Student s1(18,90);
Student s2(s2); // 这样会默认拷贝所有变量

注意点:

拷贝构造函数: 拷贝 + 构造 的时候被调用:

  • 拷贝:赋值操作
  • 构造:产生一个新的对象


注意点:

  • 在初始化列表当中,通过调用父类构造函数,进行初始化。

  • 同理:子类拷贝构造函数,也应该调用父类拷贝构造函数
  • 可以发现是:父类指针指向子类对象

如果子类拷贝构造函数没有调用父类的拷贝构造函数,就只能给 m_score 赋值,不能给 m_age 进行赋值。

1、可以发现 m_score 并没有赋值,但是 m_age 变为了 0,而不是 0xcc

2、因为 子类的构造函数会默认调用 父类的无参构造函数。 同理 子类的析构函数 也会默认调用 父类无参的析构函数。

3、 Person (int age = 0) : 因为有默认参数,所以不传入参数也可以,所以也算是无参的构造函数


3、浅拷贝

编译器默认的提供的拷贝是浅拷贝(shallow copy) , 我们分析浅拷贝是为了更好的分析深拷贝

回顾一下C语言当中的字符串,(字符串的本质其实就是字符数组

const char *name = "bwm"; // 本质是一个常量
char name2[] = {'b','w','m'};

当我们类当中有一个指针变量的时候:然后调用构造函数

分析对象的内存模型

  • 栈空间指向堆空间:Car *c1 = new Car(100,name);
  • 堆空间指向栈空间:Car.name = name

注意:堆空间指向栈空间非常危险!!!

  • 因为栈空间的释放,是系统自动回收内存的,我们无法控制
  • 栈空间被回收的时候,而我们堆空间仍然会指向它,那么就非常危险。(堆空间的指针为野指针


怎么解决:堆空间指向栈空间的问题?

堆空间指向堆空间

步骤:

1、先取消初始化列表的指针初始化,让将其放到构造函数当中初始化。

2、进行栈空间到堆空间的拷贝

3、当 c1 释放的时候,我们也要释放 m_name 的堆空间。

这时候就体现出 析构函数的作用了

当 delete(c1) 的时候,自动调用 Car 的析构函数,我们将 delete(m_name)放到析构函数当中,就可以达到我们想要的目的。

变量图:

name:指向栈空间

m_name :指向堆空间


第二种情况:


前面都是铺垫,我们现在来分析浅拷贝

浅拷贝:不用写拷贝构造函数,直接进行拷贝。

分析浅拷贝的特点:

  • 将一个对象中所有成员变量的值拷贝到另一个对象。

造成问题一:指针也是直接进行拷贝, c2 和 c1 共用一个堆空间。(当 c1 的名字改变的时候,c2 的名字也会被改变)

造成的问题2:多次 free。可能我们看起来多次 free 好像没有什么问题,但是有些平台,多次 free 程序就会崩掉


4、深拷贝

解决办法:使用深拷贝,即自己写一个拷贝构造函数,从而产生一个新的内存空间。

优化:


5、总结拷贝构造函数

回过头来看:我们什么时候需要自己写一个 拷贝构造函数呢

情况1:如果class 当中都是 int 、double 这些简单的数据类型。

我们就没必要自己写拷贝构造函数,直接进行浅拷贝也不会出错。

情况2:class 当中我们如果含有 指针类型

  • 浅拷贝:会造成堆空间的多次 free,以及多个对象共用一个堆空间。

这时候我们才真正需要自己去实现一个拷贝构造函数


一些细节

1、对象参数和返回值

使用对象类型作为函数的参数或者返回值,可能会产生一些不必要的中间对象

这个就是传值传递。

改进:我们利用传址传递。(传递一个地址就可以达到两种效果

  • 输出型参数:const
  • 输出性参数:非const


对象作为返回值:多了一个 对象的创建

分析为什么?

  • test 函数结束之后,car对象就会销毁,而我们还想将 car 拿到 main 函数里面用
  • 所以就会将 test函数当中 car,拷贝到 mian函数的栈空间

改进:编译器进行优化,只进行了一次拷贝构造


2、匿名对象、临时对象

匿名对象:没有变量名、没有被指针指向的对象,用完后马上调用析构

匿名对象作为参数,传给一个函数:


3、隐式构造

C++中存在隐式构造的现象:某些情况下,会隐式调用单参数的构造函数

可以知道,隐式构造有时候是不好的,可以通过关键字explicit禁止掉隐式构造

我再来体会一下构造函数:

修改:

分析为什么可以这么操作:

其实 Car c1 = 20 等价于 Car c1(20); 它默认调用了 单参数的构造函数

接着看:

作为返回值:


将关键字explicit 添加在构造函数前面, 因为这样的写法,代码可读性比较差


4、编译器自动生成的构造函数

一个错误的广为传之的观点:编译器会为每一个类生成一个无参的空的构造函数。

直接分析汇编:

如果没有呢?

总结:只有特定情况下,才会生成 空的无参的构造函数。

反过来思考,编译器为什么会生成 空的无参的构造函数? 有什么用呢哪些情况下会生成 空的无参的构造函数?

1、成员变量在声明的同时进行了初始化

2、有定义虚函数 ,会对对象有什么影响呢?

  • 有虚函数,对象前面的前 4 个字节就必须存放虚表地址


3、虚继承了其他类,虚继承对对象有什么影响呢?

  • 对象的前四个字节,又会有一个虚表地址


4、包含对象类型的成员,且这个成员有构造函数(编译器生成或自定义)

  • 因为生成 Car 对象,就会生成 Person 对象。
  • 生成给 Person 对象的时候就会调用 Person 类的构造函数
  • 所以会给 Car 生成一个构造函数,并且在其中调用 Person 类的构造函数。


5、父类有构造函数(编译器生成或自定义)

  • 同理,继承的时候,在 子类的构造函数当中会默认调用父类的构造函数
  • 所以必须 先得给子类生成一个构造函数

总结一下:
对象创建后,需要做一些额外操作时(比如内存操作、函数调用),编译器一般都会为其自动生成无参的构造函数。


友元

首先明确友元分为两部分:友元函数友元类

典型的封装:

缺点:假设 add 的函数调用非常频繁,那么开销就会非常大每调用一次add ,就会调用 4 次api接口

改进:

  • 要求:我们不能破坏原来的封装性。
  • Point 的成员变量,必须还是 private, 保持原来的封装性。

办法:友元函数


1、友元函数

  • 如果将函数 add(非成员函数)声明为类 point 的友元函数,那么函数 add 就能直接访问类point 对象的所有成员


2、友元类

如果将类A声明为类C的友元类,那么类A的所有成员函数都能直接访问类C对象的所有成员

设定友元类:

如果将类 Math 声明为类 Point 的友元类,那么类Math 的所有成员函数,都能直接访问类Point对象的所有成员。

总结:

友元破坏了面向对象的封装性,但在某些频繁访问成员变量的地方可以提高性能


3、内部类

含义:如果将类A定义在类C的内部,那么类A就是一个内部类(嵌套类)

特点:

1、支持public、 protected、 private权限, 可以控制这个类的访问权限

2、成员函数可以直接访问其外部类对象的所有成员(反过来则不行)

3、成员函数可以直接不带类名、对象名访问其外部类的static成员

4、不会影响外部类的内存布局

  • 仅仅是访问权限变了。

5、内部类 – 声明和实现分离


4、局部类

在一个函数内部定义的类,称为局部类

局部类的特性:

  • 作用域仅限于所在的函数内部

  • 所有的成员必须定义在类内部,即声明和实现必须放到类里面。

  • 不允许定义static成员变量,因为 static成员变量 必须放到类外初始化,与上面矛盾。

  • 成员函数不能直接访问函数的局部变量(static变量除外)


以上是关于C++学习:4拷贝友元的主要内容,如果未能解决你的问题,请参考以下文章

嵌入式c++学习全局 友元

C++入门不能重载为友元函数的4个运算符(=, ->, [ ], ( ))

C++学习摘要之六:友元函数与友元类

Sun Studio C++ 中模板类的模板友元失败

——友元函数 | 友元类 | 内部类 | 匿名对象 | 关于拷贝对象时一些编译器优化

c++面向对象高级编程(上)