C++编程:进阶阶段—4.2对象

目录

 

4.2 对象特征

4.2.1 构造函数和析构函数

4.2.2 构造函数的分类

4.2.3 拷贝函数调用时机

4.2.4 构造函数调用规则

4.2.5 深拷贝与浅拷贝

4.2.6 初始化列表

4.2.7 类对象作为类成员

4.2.8 静态成员

4.2.9 成员变量和成员函数的存储

4.2.10 this指针

4.2.11 空指针访问成员函数

4.2.12 const修饰成员函数


4.2 对象特征

对象的初始化和清理:C++中,每个对象都有初始设置和对象销毁前的清理数据的设置。

4.2.1 构造函数和析构函数

C++中利用构造函数和析构函数对对象进行初始化和清理。这两个函数会被编译器自动调用,完成对象的初始化和清理。如果程序员不提供构造和析构,编译器会提供构造函数和析构函数,但是是空的。

构造函数:创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。

语法:类名(){}

1.构造函数,没有返回值也不写void;

2.函数名与类名相同;

3.构造函数可以有参数,因此可以发生重载;

4.程序在调用对象时会自动调用构造,无须手动调用,且只调用一次。

析构函数:对象销毁前系统自动调用,执行一些清理工作。

语法:~类名(){}

1.析构函数,没有返回值也不写void;

2.函数名与类名相同,在函数名前加上符号~;

3.析构函数不可以有函数,因此不可以发生重载;

4.程序在对象销毁前会自动调用析构,无须手动调用,且只调用一次。

代码如下:

#include <iostream>
using namespace std;class Person
{
public://构造函数 初始化对象Person(){cout<<"Person构造函数的调用"<<endl;}//析构函数 销毁/清理对象~Person(){cout<<"Person析构函数的调用"<<endl;}
};void test01()
{Person p;//栈上的数据,该函数执行完后,p这个对象会被释放
}int main()
{//对象的初始化和清理test01();Person p;system("pause");return 0;
}

输出如下:

4.2.2 构造函数的分类

按参数分:有参构造、无参构造;

按类型分:普通构造、拷贝构造。

调用方式:括号法、显示法、隐式转换法。

代码如下:

#include <iostream>
using namespace std;//构造函数发分类及调用
class Person
{
public://无参(默认)构造Person(){cout<<"Person的无参构造函数的调用"<<endl;}//有参构造Person(int a){age=a;cout<<"Person的有参构造函数的调用"<<endl;}//拷贝构造函数(将Person p的属性拷贝过来)Person(const Person &p){age=p.age;cout<<"Person的拷贝构造函数的调用"<<endl;}~Person(){cout<<"Person析构函数的调用"<<endl;}private:int age;
};void test01()
{//调用:括号法cout<<"括号法调用构造函数:"<<endl;Person p1;//默认构造函数调用,不用加括号,编译器会认为Person p1();是一个函数声明。Person p2(21);//有参构造函数调用Person p3(p2);//拷贝构造函数调用//调用:显示法cout<<"显示法调用构造函数:"<<endl;Person p4;Person p5=Person(21);//有参构造Person P6=Person(p5);//拷贝构造Person(21);//表示一个匿名对象,在等式左边的P2就是给他取的名字,匿名对象执行后会立即回收。cout<<"匿名对象清理后执行了这句代码"<<endl;//PS:不要用拷贝构造 初始化匿名对象,编译器会认为Person(p3);是一个对象声明Person p3;//Person(p3);//调用:隐式转换法cout<<"显示法调用构造函数:"<<endl;Person p7=21;//有参构造Person p8=p7;//拷贝构造
}int main()
{test01();return 0;
}

输出如下:

4.2.3 拷贝函数调用时机

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

代码如下:

#include <iostream>
using namespace std;//拷贝构造函数调用时机
class Person
{
public://无参(默认)构造Person(){cout<<"Person的无参构造函数的调用"<<endl;}//有参构造Person(int a){age=a;cout<<"Person的有参构造函数的调用"<<endl;}//拷贝构造函数Person(const Person &p){age=p.age;cout<<"Person的拷贝构造函数的调用"<<endl;}~Person(){cout<<"Person析构函数的调用"<<endl;}int age;
};//使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{cout<<"test01函数调用"<<endl;Person p1(21);Person p2(p1);cout<<"p2的年龄为:"<<p2.age<<endl;
}//值传递的方式给函数参数传值
void doWork(Person p)
{}void test02()
{cout<<"test02函数调用"<<endl;Person p;doWork(p);//这里传入的p和dowork中的p不一样
}//以值方式返回局部对象
Person doWork2()
{Person p1;cout<<"p1的地址为:"<<(long long)&p1<<endl;return Person(p1);//直接返回p1则不会调用拷贝函数,因为编译器自动做了优化(可以看到p1和p的地址一样)
}void test03()
{cout<<"test03函数调用"<<endl;Person p=doWork2();cout<<"p的地址为:"<<(long long)&p<<endl;
}int main()
{test01();test02();test03();return 0;
}

输出如下:

4.2.4 构造函数调用规则

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

  1. 1.默认构造函数(无参,函数体为空)
  2. 2.默认析构函数(无参,函数体为空)
  3. 3.默认拷贝构造函数,对属性进行值拷贝

调用规则:

  • 如果用户定义了有参构造函数,则编译器不提供默认无参构造,但会提供默认拷贝构造
  • 如果用户定义了拷贝构造函数,则编译器不再提供其他构造函数

代码如下:

#include <iostream>
using namespace std;class Person
{
public:// Person()// {//     cout<<"person的默认构造函数"<<endl;// }Person(int a){age=a;cout<<"Person的有参构造函数的调用"<<endl;}// Person(const Person &p)// {//     age=p.age;//     cout<<"Person的拷贝构造函数的调用"<<endl;// }~Person(){cout<<"Person析构函数的调用"<<endl;}int age;
};// void test01()
// {
//     Person p;
//     p.age=18;//     Person p2(p);
//     cout<<"p2的年龄为:"<<p2.age<<endl;
// }void test02()
{Person p(28);Person p2(p);cout<<"p2的年龄为:"<<p2.age<<endl;
}int main()
{//test01();test02();return 0;
}

输出如下:用户定义了拷贝构造函数

输出如下:用户没有定义拷贝构造函数

错误示例:用户定义了有参构造,但没有定义无参(默认)构造,则编译器也不会提供默认构造,此时调用默认构造则会报错。

输出如下:用户只定义了有参构造,则编译器依然或提供拷贝构造

4.2.5 深拷贝与浅拷贝

浅拷贝:编译器提供的拷贝函数,简单的赋值拷贝操作;

缺点:容易导致堆区的重复释放,利用深拷贝解决。

深拷贝:在堆区重新申请空间,进行拷贝操作,而不是与被拷贝的指针指向相同的空间。

PS:如果属性有在堆区开辟的,一定要自己定义拷贝构造函数,防止浅拷贝中出现的问题。

代码如下:

#include <iostream>
using namespace std;class Person
{
public:Person(){cout<<"person的默认构造函数"<<endl;}Person(int a,int h){age=a;height=new int(h);cout<<"Person的有参构造函数的调用"<<endl;}//自己实现拷贝构造函数,解决浅拷贝的问题Person(const Person &p){age=p.age;height=p.height;//编译器写的(浅拷贝)height= new int(*p.height);//深拷贝操作,另外开辟空间cout<<"Person的拷贝构造函数的调用"<<endl;}~Person(){//析构的作用,将堆区new的数据手动释放if(height!=NULL)//若指针不为空,则需要释放{delete height;//P2先释放,完了之后P也需要释放,但两个对象的指针操作的是同一个堆区中的地址,造成重复释放的非法操作,因此会报错height=NULL;//防止野指针出现,将指针置空}cout<<"Person析构函数的调用"<<endl;}int age;int * height;
};void test01()
{Person p(28,160);Person p2(p);cout<<"p2的年龄为:"<<p2.age<<" 身高为:"<<*p2.height<<endl;
}int main()
{test01();return 0;
}

输出如下:

4.2.6 初始化列表

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

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

代码如下:

#include <iostream>
using namespace std;class Person
{
public://传统初始化操作// Person(int a,int b,int c)// {//     A=a;//     B=b;//     C=c;// }//初始化列表赋初值//Person():A(1),B(2),C(3){}Person(int a,int b,int c):A(a),B(b),C(c){}int A;int B;int C;
};void test01()
{//Person p(10,20,30);//传统赋值Person p(1,2,3);//列表赋值cout<<"A="<<p.A<<endl;cout<<"B="<<p.B<<endl;cout<<"C="<<p.C<<endl;}int main()
{test01();return 0;
}

输出如下:

4.2.7 类对象作为类成员

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

注意对象作为成员时,两种对象的构造和析构函数的顺序。(先构造其他类,再构造本类,先析构本类,再析构其他类)

代码如下:

#include <iostream>
using namespace std;
#include <string>//对象成员
class Phone
{
public://手机品牌string PName;Phone(string pname){cout<<"Phone的构造函数的调用"<<endl;PName=pname;}~Phone(){cout<<"Phone析构函数的调用"<<endl;}
};class Person
{
public://P(pname)相当于Phone P=pname; 隐式转换法Person(string name,string pname):Name(name),P(pname){cout<<"Person的构造函数的调用"<<endl;}~Person(){cout<<"Person析构函数的调用"<<endl;}string Name;Phone P;
};void test01()
{Person p("张三","iPhone18");cout<<p.Name<<"拿着"<<p.P.PName<<endl;
}int main()
{test01();return 0;
}

输出如下:

4.2.8 静态成员

静态成员是指在成员变量和成员函数卡加static关键字,静态成员都有三种访问权限。

静态成员变量:

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

静态成员函数

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

代码如下:

#include <iostream>
using namespace std;
#include <string>//静态成员
class Person
{
public://静态成员变量static int A;//类内声明int B;//静态成员函数static void func(){A=44;//B=22;//静态成员函数访问非静态成员变量,报错,无法区分是哪个对象的Bcout<<"静态成员函数调用"<<endl;}
};//类外初始化
int Person::A=100;void test01()
{Person p;cout<<p.A<<endl;Person p2;p2.A=200;//所有对象共享同一份数据,因此有两种访问方式:通过对象访问;通过类名访问cout<<p.A<<endl;cout<<Person::A<<endl;
}void test02()
{//两种访问方式:通过对象访问;通过类名访问Person p;p.func();Person::func();cout<<p.A<<endl;
}int main()
{test01();test02();return 0;
}

输出如下:

错误示例:静态成员函数访问非静态成员变量

4.2.9 成员变量和成员函数的存储

类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上的。

代码如下:

#include <iostream>
using namespace std;
#include <string>//静态成员
class Person1
{};class Person2
{int A;//非静态成员变量 
};class Person3
{int A;static int B;//静态成员变量
};int Person3::B=9;class Person4
{int A;static int B;void func(){}//非静态成员函数
};class Person5
{int A;static int B;void func(){}//非静态成员函数static void func2(){};
};void test01()
{Person1 p1;//空对象占用内存为1,为了区分空对象占内存的位置,每个空对象有一个唯一的地址cout<<"size of p1="<<sizeof(p1)<<endl;Person2 p2;//有非静态成员变量,占4字节  属于类的对象上的数据cout<<"size of p2="<<sizeof(p2)<<endl;Person3 p3;//有静态成员变量  不属于类的对象上的数据cout<<"size of p3="<<sizeof(p3)<<endl;Person4 p4;//非静态成员函数  不属于类的对象上的数据cout<<"size of p4="<<sizeof(p4)<<endl;Person5 p5;//静态成员函数  不属于类的对象上的数据cout<<"size of p5="<<sizeof(p5)<<endl;
}int main()
{test01();return 0;
}

输出如下:

4.2.10 this指针

每一个非静态成员函数只会产生一个函数实例,所有同类中的多个对象会公用一块代码。

C++提供this指针来指向被调用的成员函数所属的对象。this指针是隐含每一个非静态成员函数内的一种指针,不需要定义,直接使用即可。

用途:

  • 当形参和成员变量同名时,用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可使用return *this。

PS:用Person&定义返回值类型,是因为可以一直对同一个空间操作,用Person定义返回值类型表示值返回,会复制一份新的数据(按照本体p2创建了新的数据,而不是返回的p2本体),调用了拷贝构造函数。

代码如下:

#include <iostream>
using namespace std;
#include <string>class Person
{
public:int age;Person(int age){//age=age;//报错//this指针指向被调用的成员函数所属的对象p1this->age=age;}
//用Person&定义返回值类型,是因为可以一直对同一个空间操作,用Person定义返回值类型表示值返回,会复制一份新的数据(按照本体p2创建了新的数据,而不是返回的p2本体),调用了拷贝构造函数Person& PersonAddAge(Person &p){this->age+=p.age;//this指向p2的指针,*p2指向p2本体return *this;}
};//解决名称冲突
void test01()
{Person p1(18);cout<<p1.age<<endl;
}//用*this 返回对象本身
void test02()
{Person p1(31);Person p2(31);p2.PersonAddAge(p1);cout<<p2.age<<endl;p2.PersonAddAge(p1).PersonAddAge(p1);//用this*返回才能链式追加cout<<p2.age<<endl;}int main()
{test01();test02();return 0;
}

输出如下:

错误示例:名称冲突,形参和属性名相同时,不能输出正确结果

4.2.11 空指针访问成员函数

C++中空指针可以调用成员函数,但需要注意有没有用this指针。如果用到this指针,需要加以判断保证代码的健壮性。

代码如下:

#include <iostream>
using namespace std;//空指针调用成员函数
class Person
{
public:void showClassName(){cout<<"this is person class"<<endl;}void showPersonAge(){if(this==NULL){return;}//传入指针为空,报错  在前面加一个空指针的判断cout<<"age="<<this->age<<endl;}int age;
};void test01()
{Person *p=NULL;p->showClassName();p->showPersonAge();
}int main()
{test01();return 0;
}

输出如下:

错误示例:用空指针访问属性,图中age,默认是this->age,而访问时用的空指针,this为空所以不能指向正确的对象的属性。

4.2.12 const修饰成员函数

常函数:

  • 成员函数后加const后称为常函数;
  • 常函数内不可用修改成员属性;
  • 成员属性声明时加关键词mutable后,在常函数中依然可以修改

常对象:

  • 声明对象前加const称该对象为常对象;
  • 常对象不允许修改指针指向的值;

  • 常对象只能调用常函数

代码如下:

#include <iostream>
using namespace std;//常函数
class Person
{
public://this指针的本质是一个指针常量Person * const this 指针的指向是不可修改的//后面加的const相当于const Person * const this,使this指向的值也不可修改void showPerson() const{this->b=99;//this=NULL;//this的指针指向不能修改cout<<"this is person class"<<endl;}Person(){}//不写默认构造函数会报错实例化的常对象没有初始化void func(){}int age;mutable int b;
};void test01()
{Person p;p.showPerson();
}void test02()
{const Person p;//p.age=99;//报错 常对象不允许修改指针指向的值p.b=88;p.showPerson();//p.func();//报错 常对象不能调用非常函数
}int main()
{test01();test02();return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/897499.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MySQL_04】数据库基本操作(用户管理--配置文件--远程连接--数据库信息查看、创建、删除)

文章目录 一、MySQL 用户管理1.1 用户管理1.11 mysql.user表详解1.12 添加用户1.13 修改用户权限1.14 删除用户1.15 密码问题 二、MySQL 配置文件2.1 配置文件位置2.2 配置文件结构2.3 常用配置参数 三、MySQL远程连接四、数据库的查看、创建、删除4.1 查看数据库4.2 创建、删除…

配置 Thunderbird 以使用 outlook 邮箱

配置 Thunderbird 以使用 outlook 邮箱 thunder bird 作为邮件客户端非常好用&#xff0c;不用每次登录邮箱网页端查看邮件&#xff0c;直接打开配置好的 thunder bird 即可免登录查看邮件。 0. 什么是 Thunder Bird ? https://www.thunderbird.net/zh-CN/ Thunderbird 创立…

边缘计算的业务种类划分

Pcdn的业务可以根据不同的分类标准来划分 一、按线路类型划分 汇聚模式&#xff1a;一个地方有多条线路&#xff0c;业务种类较多。通常使用X86或X99主板组装的服务器&#xff0c;或各品牌的准系统服务器。收益通常比单线模式更高。 单线模式&#xff1a;一个地方只有一条线路&…

服务器数据恢复—raid5阵列中硬盘出现坏道的数据恢复流程

服务器故障情况&#xff1a; 某公司一台服务器中有一组多块硬盘组成的磁盘阵列。磁盘阵列中有2块硬盘出现故障离线&#xff0c;服务器崩溃&#xff0c;上层数据丢失。 硬件检测&#xff1a; 硬件工程师对客户服务器内的所有硬盘进行物理故障检测&#xff0c;最终确认这2块硬盘…

Linux:多线程(三.POSIX信号量、生产消费模型、线程池)

目录 1. 生产者消费者模型 1.1 阻塞队列(BlockingQueue) 1.2 一个实际应用的例子 2. POSIX信号量 2.1 引入 2.2 回顾加深理解信号量 2.3 信号量的操作接口 3. 基于循环队列的生产消费模型 3.1 循环队列 3.2 整个项目 4. 线程池 4.1 概念 4.2 线程池实现 1. 生产者…

关于前后端整合和打包成exe文件的个人的总结和思考

前言 感觉有很多东西&#xff0c;不知道写什么&#xff0c;随便写点吧。 正文 前后端合并 就不说怎么开发的&#xff0c;就说点个人感觉重要的东西。 前端用ReactViteaxios随便写一个demo&#xff0c;用于CRUD。 后端用Django REST Framework。 设置前端打包 import { …

Android15 Camera框架中的StatusTracker

StatusTracker介绍 StatusTracker是Android15 Camera框架中用来协调Camera3各组件之间状态转换的类。 StatusTracker线程名&#xff1a;std::string("C3Dev-") mId "-Status" Camera3 StatusTracker工作原理 StatusTracker实现批处理&#xff08;状态…

利用OpenResty拦截SQL注入

需求 客户的一个老项目被相关部门检测不安全&#xff0c;报告为sql注入。不想改代码&#xff0c;改项目&#xff0c;所以想到利用nginx去做一些数据校验拦截。也就是前端传一些用于sql注入的非法字符或者数据库的关键字这些&#xff0c;都给拦截掉&#xff0c;从而实现拦截sql…

警惕AI神话破灭:深度解析大模型缺陷与禁用场景指南

摘要 当前AI大模型虽展现强大能力&#xff0c;但其本质缺陷可能引发系统性风险。本文从认知鸿沟、数据困境、伦理雷区、技术瓶颈四大维度剖析大模型局限性&#xff0c;揭示医疗诊断、法律决策等8类禁用场景&#xff0c;提出可信AI建设框架与用户防护策略。通过理论分析与实操案…

颠覆语言认知的革命!神经概率语言模型如何突破人类思维边界?

颠覆语言认知的革命&#xff01;神经概率语言模型如何突破人类思维边界&#xff1f; 一、传统模型的世纪困境&#xff1a;当n-gram遇上"月光族难题" 令人震惊的案例&#xff1a;2012年Google语音识别系统将 用户说&#xff1a;“我要还信用卡” 系统识别&#xff…

【Linux】详谈 基础I/O

目录 一、理解文件 狭义的理解&#xff1a; 广义理解&#xff1a; 文件操作的归类认知 系统角度 二、系统文件I/O 2.1 标志位的传递 系统级接口open ​编辑 open返回值 写入文件 读文件 三、文件描述符 3.1&#xff08;0 & 1 & 2&#xff09; 3.2 文件描…

超分之DeSRA

Desra: detect and delete the artifacts of gan-based real-world super-resolution models.DeSRA&#xff1a;检测并消除基于GAN的真实世界超分辨率模型中的伪影Xie L, Wang X, Chen X, et al.arXiv preprint arXiv:2307.02457, 2023. 摘要 背景&#xff1a; GAN-SR模型虽然…

Vue3 Pinia 符合直觉的Vue.js状态管理库

Pinia 符合直觉的Vue.js状态管理库 什么时候使用Pinia 当两个关系非常远的组件&#xff0c;要传递参数时使用Pinia组件的公共参数使用Pinia

Web Worker如何在本地使用

首先了解一下什么是Web Worker Web Worker 是一种在后台线程中运行 JavaScript 的机制&#xff0c;允许你在不阻塞主线程的情况下执行耗时的任务。这对于保持网页的响应性和流畅性非常重要&#xff0c;特别是在需要进行复杂计算或大量数据处理时。 主要特点 多线程&#xff1…

Javaweb后端文件上传@value注解

文件本地存储磁盘 阿里云oss准备工作 阿里云oss入门程序 要重启一下idea&#xff0c;上面有cmd 阿里云oss案例集成 优化 用spring中的value注解

MAC-禁止百度网盘自动升级更新

通过终端禁用更新服务(推荐)​ 此方法直接移除百度网盘的自动更新组件,无需修改系统文件。 ​步骤: ​1.关闭百度网盘后台进程 按下 Command + Space → 输入「活动监视器」→ 搜索 BaiduNetdisk 或 UpdateAgent → 结束相关进程。 ​2.删除自动更新配置文件 打开终端…

数据结构:有序表的插入

本文是我编写的针对计算机专业考研复习《数据结构》所用资料内容选刊。主要目的在于向复习这门课程的同学说明&#xff0c;此类问题不仅仅使用顺序表&#xff0c;也可以使用链表。并且&#xff0c;在复习中&#xff0c;两种数据结构都要掌握。 若线性表中的数据元素相互之间可以…

DeepSeek大语言模型下几个常用术语

昨天刷B站看到复旦赵斌老师说的一句话“科幻电影里在人脑中植入芯片或许在当下无法实现&#xff0c;但当下可以借助AI人工智能实现人类第二脑”&#xff08;大概是这个意思&#xff09; &#x1f49e;更多内容&#xff0c;可关注公众号“ 一名程序媛 ”&#xff0c;我们一起从 …

Html5学习教程,从入门到精通, HTML5超链接应用的详细语法知识点和案例代码(18)

HTML5超链接应用的详细语法知识点和案例代码 超链接&#xff08;Hyperlink&#xff09;&#xff0c;也称为跃点链接&#xff0c;是互联网和文档编辑中的一种重要概念。 超链接的定义 超链接是指从一个网页指向一个目标的连接关系&#xff0c;这个目标可以是另一个网页&#…

MYSQL之创建数据库和表

创建数据库db_ck &#xff08;下面的创建是最好的创建方法&#xff0c;如果数据库存在也不会报错&#xff0c;并且指定使用utf8mb4&#xff09; show databases命令可以查看所有的数据库名&#xff0c;可以找到刚刚创建的db_ck数据库 使用该数据库时&#xff0c;发现里面没有…