目录

  • 16. this指针和常函数
    • 16.1 this指针
      • 必须显式使用this指针的场景
    • 16.2 常函数
      • mutable关键字
      • 常函数的调用规则
      • Q:常函数与同名非常函数可以构成关系吗?
  • 17. 析构函数
    • 17.1 析构函数调用时机
    • 17.2 缺省析构函数
    • 17.3 有成员子对象的对象的构造函数、析构函数调用时机
  • 18. 拷贝构造函数和拷贝赋值
    • 18.1 拷贝构造函数
      • 浅拷贝构造函数、深拷贝构造函数
    • 18.2 拷贝赋值
      • 类数据类型对象拷贝赋值机制

16. this指针和常函数

16.1 this指针

定义:类的构造函数和成员函数都隐藏了一个该类数据类型的指针参数

class giraffe{giraffe(void){}//实际形式:giraffe(giraffe *this){}void func(const string &name){}//实际形式:void func(const string &name, giraffe *this){}
};

this指针特点:

  1. 在构造函数或者成员中访问该类的其他成员,都是通过this指针完成的
  2. 对于普通成员函数,this指针就指向调用该函数的对象
  3. 对于构造函数,this指针指向正在被构造的对象
#include <iostream>
using namespace std;#ifndef THIS
class animal{public:animal(const char *name, int weight):m_str_name(name), m_i_weight(weight){cout << m_str_name << endl;cout << m_i_weight << endl;}   void func(void){cout << m_str_name << " is " << m_i_weight << " kg " << endl;}
private:            string m_str_name;int m_i_weight;
};
#endif              #ifdef THIS
class animal{
public:             animal(const char *name, int weight/* ,animal *this*/):m_str_name(name), m_i_weight(weight){//对于构造函数,this指针指向正在被构造的对象cout << this -> m_str_name << endl;//类成员函数访问内部成员,实质是通过this指针访问的cout << this -> m_i_weight << endl;cout << "animal::animal(const char *,int)中的this = " << this << endl;}               void func(void/*animal * this*/){//对于普通成员函数,this指针就指向调用该函数的对象cout << this -> m_str_name << " is " << this -> m_i_weight << " kg " << endl;cout << "animal::func()中的this = " << this << endl;}               private:            string m_str_name;int m_i_weight;
};
#endif              int main(void){     animal giraffe("giraffe",65);cout << "&giraffe:" << &giraffe << endl;giraffe.func();//实质类似调用func(&giraffe); return 0;
}

编译输出结果:

Desktop Linraffe$ g++ constructor.cpp -o constructor//编译隐藏this指针版本
Desktop Linraffe$ constructor
giraffe
65
&giraffe:0x7ffeeb8b7968
giraffe is 65 kg
-------------------
Desktop Linraffe$ g++ constructor.cpp -o constructor -D THIS//编译显示this指针版本
Desktop Linraffe$ constructor
giraffe
65
animal::animal(const char *,int)中的this = 0x7ffee9487968
&giraffe:0x7ffee9487968
giraffe is 65 kg
animal::func()中的this = 0x7ffee9487968

必须显式使用this指针的场景

  1. 区分作用域,当函数的参数和成员变量的名字相同时,需要使用this指针区分两者
#include <iostream>
using namespace std;class animal{public:animal(const string name, int weight){/* name = name;weight = weight;试图初始化成员变量,但是形式参数名和成员变量名冲突*///通过this指针区分作用域,解决同名冲突this -> name = name;this -> weight = weight;}void func(void){cout << name << " is " << weight << " kg " << endl;                     }
private:                         string name;                 int weight;
};                               int main(void){                  animal giraffe("giraffe",65);giraffe.func();//实质类似调用func(&giraffe);return 0;
}
  1. 从成员函数中返回调用对象的自身(返回自引用)
#include <iostream>
using namespace std;class A{public:A(int num = 0){m_i_num = num;}void print(void){cout << m_i_num << endl;}A &add(void){// A &add(A *this)m_i_num++;return *this;//返回自引用}private:int m_i_num;
};int main(void){A A_a;//以无参构造创建对象,此时A_a.m_i_num == 0;A_a.print();A_a.add().add().add().add();//add返回子引用,相当于返回调用对象自身,此时A_a.m_i_num == 4;A_a.print();return 0;
}
  1. 从类的内部销毁对象自身,在堆区创建对象后要销毁对象,并且希望直接在类的内部就能实现销毁的操作(自销毁)时,需要用到this指针
#include <iostream>
using namespace std;class A{public:A(void){}void destory(){//自销毁成员函数cout << "自销毁" << endl;delete this;}
};int main(void){A *A_p = new A;//在堆区创建一个A类对象A_p -> destory();//和delele A_p;作用相同   return 0;
}

16.2 常函数

定义:在一个普通的成员函数的形参表后面加const关键字,这个函数就称为常函数。

常函数形式:

返沪类型 函数名(形参表)const{函数体}
如:void *func(const string &name)const {...}

常函数特点:
常函数中this指针是一个常指针,即,不能在常函数中修改成员变量的值,常函数可以防止函数以外修改对象的成员变量值

#include <iostream>
using namespace std;class A{public:    A(int num = 517){m_i_num = num;}
#ifdef CONSTvoid print(void)const{//定义常函数cout << m_i_num++ << endl;//试图打印成员函数值的同时修改成员函数的值}
/*上面的函数可以理解为以下形式:void printf(const A *this){//常函数的this指针用const修饰cout << ++(this -> m_i_num) << endl;//显然,const修饰的this指针指向的对象存储单元中的内容不可修改}                                        //因此,会报错
*/
#endifvoid print(void){//函数试图打印成员函数值的同时修改成员函数的,对于一个打印函数显然不合理cout << m_i_num++ << endl;}   private:int m_i_num;
};int main()
{A A_a;A_a.print();A_a.print();return 0;
}

编译执行结果:

Desktop Linraffe$ g++ constfunc.cpp -o constfunc//编译非常函数版本
Desktop Linraffe$ constfunc
517
518
--------------------
Desktop Linraffe$ g++ constfunc.cpp -o constfunc -D CONST//编译常函数版本
constfunc.cpp:11:24: error: cannot assign to non-static data member within const member function 'print'cout << m_i_num++ << endl;//试图打印成员函数值的同时修改成员函数的值~~~~~~~^
constfunc.cpp:10:10: note: member function 'A::print' is declared const herevoid print(void)const{//定义常函数~~~~~^~~~~~~~~~~~~~~~
1 error generated.

注意:

只有不对成员变量值作出修改的成员函数才适合被定义为常函数;类似构造函数、析构函数等会对成员变量值作出修改的函数都不能声明为常函数

mutable关键字

根据定义,常函数不能修改成员变量的值,但是如果存在一个场景必须在常函数中对某个成员变量值作出修改,该如何解决?引入mutable关键字

用法:
在希望可以在常函数中修改值的成员变量声明处最前面加上mutable关键字

class A{public:A(int num = 109):m_i_num(num){}void func(void)const{//常函数m_i_num = 517;//m_i_num是mutable修饰的成员变量在常函数中允许被修改}
private:mutable int m_i_num;//使用mutable修饰的成员变量在常函数中允许被修改
}

修改上一节的代码:

#include <iostream>
using namespace std;class A{public:A(int num = 517):m_i_num(num){}   void print(void)const{//定义常函数cout << m_i_num++ << endl;//试图打印成员函数值的同时修改成员函数的值         }
private: mutable int m_i_num;
};       int main()
{                           A A_a;                  A_a.print();            A_a.print();return 0;
}

编译执行结果:

Desktop Linraffe$ constfunc
517
518

以上,说明mutable关键字修饰的成员变量在常函数中允许被修改

常函数的调用规则

#include <iostream>
using namespace std;class A{public://void func_nornal(A *this){...}void func_normal(void){cout << "A::func_normal" << endl;}   //void func_const(const A *this){...}void func_const (void)const{cout << "A::func_const" << endl;}
};int main()
{const A cA_a;//定义A类常对象A nA_b;//定义A类非常对象//常对象调用常函数cA_a.func_const();//func_const(&cA_a);实参赋值给形式参数过程:const A *this = &cA_a;完全匹配//常对象调用非常函数cA_a.func_normal();//func_normal(&cA_a);实参赋值给形式参数过程:A *this = &cA_a;将常对象赋值给非常对象,扩大了形式参数的权限,不安全,会报错                                     //非常对象调用常函数nA_b.func_const();//func_const(&nA_b);实参赋值给形式参数过程:const A *this = &nA_b;将非常对象赋值给常对象,缩小了形式参数的权限,安全//非常对象对用非常函数nA_b.func_normal();//func_normal(&nA_b);实参赋值给形式参数过程:A *this = &nA_b;将非常对象赋值给非常对象,完全匹配,安全return 0;
}

编译输出结果:

Desktop Linraffe$ g++ constfunc.cpp -o constfunc
constfunc.cpp:24:5: error: 'this' argument to member function 'func_normal' has type 'const A', but function is not marked constcA_a.func_normal();//func_normal(&cA_a);实参赋值给形式参数过程:A *this = &cA_a;将常对象赋值给非常对象,扩大了形式参数的权限,不安全,会报错^~~~
constfunc.cpp:7:10: note: 'func_normal' declared herevoid func_normal(void){^
1 error generated.

和预期的一样,编译cA_a.func_normal();时报错

总结以上代码:
非常对象既可以调用常函数也可以调用非常函数;
常对象只能调用调用常函数,不能调用非常函数;
// 通过数据安全性记忆

Q:常函数与同名非常函数可以构成关系吗?

class A{public://void func_nornal(A *this){...}void func(void){}   //void func_const(const A *this){...}void func(void)const{}
};

按照本专题10.1.1对函数重载关系条件的定义,以上代码中两个成员函数满足1)相同作用域 2)函数同名,但是不满足3)形式参数表不同,按理来说不能构成承载关系,但是实际编译时,编译器会把以上两个函数的形式参数表替换为以下形式:

void func_nornal(A *this){...}
void func_const(const A *this){...}

此时,满足了条件3)不同形式参数表
因此,常函数与同名非常函数可以构成关系,且常对象调用常版本,非常对象调用非常版本

17. 析构函数

析构函数形式:

class 类名{~类名(void){函数体}//析构函数
};

析构函数特点:

  1. 析构函数是类中特殊的成员函数
  2. 没有返回类型,也没有参数,不能被重载(因为没有参数,缺少构成函数重载的条件),一个类只能有一个析构函数,痛构造函数一样,一个类数据的对象被销毁时必然会调用析构函数,也仅能调用一次
  3. 主要负责清理对象在构造是所分配的动态资源
#include <iostream>
using namespace std;class Integer{public:Integer(int num):m_pi_num(new int(num)){cout << "Integer::Integer(int)" << endl;}//在堆区开辟空间/*等价于:Integer(int num){m_pi_num = new int(num);}*/~Integer(void){cout << "Integer::~Integer(void)" << endl;delete m_pi_num;//释放堆区内存m_pi_num = NULL;};private:int * m_pi_num;
};int main(void){Integer i(517); return 0;
}

编译执行结果:

Desktop Linraffe$ deconstructor
Integer::Integer(int)
Integer::~Integer(void)

以上,在进程退出前析构函数被自动调用,并释放堆区内存

17.1 析构函数调用时机

  1. 对于栈对象,析构函数被 ‘}’ (作用域终止运算符) 调用
#include <iostream>
using namespace std;class A{public:~A(void){cout << "Integer::~Integer(void)" << endl;};
};int main(void){{A A_a;//在栈区创建A类对象} //'}'为作用域终止运算符,A_a的析构函数应该被作用域终止运算符调用cout << "还未执行return" << endl;return 0;
}

编译执行结果:

Desktop Linraffe$ deconstructor
Integer::~Integer(void)
还未执行return

以上输出验证了栈区对象析构函数是被‘}’,而不是return

  1. 对于栈对象,析构函数被delete/delete[]运算符调用
#include <iostream>
using namespace std;class A{public:~A(void){cout << "Integer::~Integer(void)" << endl;};
};int main(void){A * pA = new A;delete pA;pA = NULL;cout << "还未执行return" << endl;return 0;
}

编译输出结果:

Desktop Linraffe$ deconstructor
Integer::~Integer(void)
还未执行return

以上输出结果验证了堆区对象析构函数是被delete/delete[]调用的

  1. 全局对象,析构函数在进程结束前被调用

17.2 缺省析构函数

定义:如果一个类没有显式定义析构函数,那么编译器为该类提供缺省的析构函数

缺省析构函数特点: //对比缺省构造函数

  1. 对于内置类型成员变量,什么都不做
  2. 对于类数据类型成员变量(成员子对象),调用对应类的析构函数

17.3 有成员子对象的对象的构造函数、析构函数调用时机

#include <iostream>
using namespace std;class A{public:A(void){cout << "A::A(void)" << endl;}~A(void){cout << "A::~A(void)" << endl;};
};class B{//B类没有显式定义无参构造函数和析构函数//编译器会提供缺省的无参构造和析构函数
public:B(void){cout << "B::B(void)" << endl;}~B(void){cout << "B::~B(void)" << endl;}A m_A_a;
};int main(void){B B_b;//创建B类对象B_b时,会先以无参形式构造成员子对象m_A_a;return 0;
}//执行作用域终止运算符时会调用B的析构函数析构B_b对象//之后再调用A类的析构函数析构B_b的成员子对象m_A_a

编译执行结果:

A::A(void)
B::B(void)
B::~B(void)
A::~A(void)

以上,可以看到,对于含有类数据类型成员变量(成员对象)的对象的创建与销毁过程,调用构造、析构函数的时机为:

  1. 调用成员子对象的构造函数
  2. 调用自身的构造函数
  3. 调用自身的析构函数
  4. 调用成员子对象的析构函数

就像拼装高达一样,每个零件都拼好了,最后才拼成一个完成的塑料小人;只有你确定要拆掉这个完整的塑料小人,之后再一步步把每个部件拆掉(符合逻辑,可以这么记忆

笔记:Linux环境C++语言复习(4)相关推荐

  1. 笔记:Linux环境C语言复习(16)// 网络

    网络 1. 网络相关概念 1.1 协议与网络分层 1.1.1 OSI模型与网际协议族 1.1.2 TCP/IP协议族分层 1.2 封装 1.3 以太网帧 1.4 分用 1.5 端口号 1.6 IP首部 ...

  2. linux环境c语言编程 蔡晋,Linux环境C语言编程

    Linux环境C语言编程第1讲linux系统环境介绍 Linux环境C语言编程第2讲命令行解析+环境变量+gcc基本参数 Linux环境C语言编程第3讲共享库.gdb的使用 Linux环境C语言编程第 ...

  3. linux环境C语言操作数据库

    在实际应用中,我们不可能在命令行登录进数据库进行数据的查询.插入等操作,用户一般是使用一个界面良好的应用程序软件来对数据进行管理.为了方便应用程序的开发,MySQL提供了多种编程语言(C.perl.p ...

  4. linux环境c语言课程设计,linux环境下c语言编程课程设计

    linux环境下c语言编程课程设计 (14页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 19.90 积分 1/14LINUX操作系统教程课程设计题目算术 ...

  5. 【C++笔记】1. C语言复习

    1. C语言复习 1.1 基础部分 C99之后可以用const int来表示常量,初始化后不能再被赋值. 整数和整数运算只会得到整数.当有浮点数参与运算的时候,就会变成浮点数. 整数用int(输入输出 ...

  6. linux环境c语言实现who,C语言编程实现Linux命令——who

    C语言编程实现Linux命令--who 实践分析过程 who命令是查询当前登录的每个用户,它的输出包括用户名.终端类型.登录日期及远程主机,在Linux系统中输入who命令输出如下: 我们先man一下 ...

  7. Java笔记-Linux环境中因编码问题导致中文String解析有问题

    背景:各个现场搭建的Linux的环境不相同,遇到了莫名其妙的问题,发现带有中文的字符串解析有问题. 通过测试,发现是编码问题,在此记录下. 指定为utf-8编码运行此jar包: java -Dfile ...

  8. linux查看python环境_运维笔记linux环境提示python: command not found hello

    场景描述: 新部署的容器环境,终端执行python命令,提示没有该命令. 从报错异常可以看出,可能是python环境未安装. 分析思路: 检查python路径: 方式一:type -a python ...

  9. Linux环境C语言开发基础

    C语言是一门面向过程的计算机编程语言,与C++.C#.Java等面向对象编程语言有所不同.C语言的设计目标是提供一种能以简易的方式编译.处理低级存储器.仅产生少量的机器码以及不需要任何运行环境支持便能 ...

最新文章

  1. 字典求最小值经典案例 heapq.nsmallest() 与min()
  2. 自然语言处理----处理原始文本
  3. 锁、C#中Monitor和Lock以及区别
  4. MYSQL错误: ERROR 1205: Lock wait timeout exceeded(处理MYSQL锁等待)解决办法
  5. 微信能远程控制电脑吗_神器分享:用微信就能远程控制电脑,这款神器有些厉害...
  6. 解决手动运行脚本执行正常而放入crontab后不正常的方法
  7. 【Kafka】kafka命令kafka-console-consumer.sh
  8. kafka : CommitFailedException already rebalanced and assigned max.poll.records
  9. CSS中超链接样式的书写顺序
  10. SHELL(bash)脚本编程四:其他扩展
  11. 深度学习caffe:权值初始化
  12. 摘录 | WAREZ无形帝国
  13. 基于FPGA的CIC滤波器设计(1)
  14. iPhone模拟器部分操作
  15. 《Graphene-SGX: A Practical Library OS for UnmodifiedApplications on SGX (ATC‘17)》笔记
  16. lombok 基础注解之 @Cleanup
  17. 第一章 核磁共振的物理学基础
  18. [18调剂]上海海洋大学2018年硕士研究生调剂政策与规则
  19. javascript,jQuery,vue的区别
  20. 不用下载就能在线P图,这款工具分享给你

热门文章

  1. 计算机网络安全管理的开题报告,计算机网络安全的应用论文开题报告
  2. 新手必备的网络营销工具
  3. javascript中访问数据库
  4. pytorch的安装-中科大源
  5. 开发创造性思维的基本方法
  6. 如何本地化部署ChatGPT
  7. 让未登录的用户跳转到登录页
  8. html能做到什么效果,什么是HTML标签?HTML标签有什么作用?
  9. 怎样提取一个数的十位个位百位千位
  10. 懒人必备公式快速插入word(latexocr+TyporaMathtype)保姆集教程