在C语言基础上的C++(深入理解类和对象)

 1:构造函数

1:为什么使用构造函数

由于类的封装性,一般来说,数据成员是不能被外界访问的,所以对象的数据成员的初始化工作就给共有函数来完成了。如果定义了构造函数,那么只要对象一建立,就可以自动调用构造函数,来完成对象的初始化。

2:构造函数的特点

1:函数名和类名相同
2:没有返回类型
3:对象创建时自动调用
4:可以带参数
5:可以重载

3:构造函数的种类

构造函数只有两种情况,带参数的和不带参数的构造函数。

class student {public:student()//构造函数不带参数{cout << "请输入学号,姓名,成绩" << endl;cin >> no>>name>>score;}student(int n,const char na[],double s)//构造函数带参数{no = n;strcpy(name,na);score = s;}void display(){cout << "学号:" << no << endl;cout << "姓名:" << name << endl;cout << "成绩:" << score << endl;}
private:int no;char name[20];double score;
};

如果想要区分上面的构造函数是如何使用的我们可以在主函数里面创造两个对象来观察一下。

int main()
{student s1(10,"de",88.8);student s2;s1.display();s2.display;return 0;
}

注意s2这个对象创建使用第一个构造函数,不带参数,不能带括号,如果带上括号,会被编译器理解为函数声明。

其中带参数的构造函数也可以使用另外一种方法来对数据初始化,那就是初始化列表

格式如下

类名(参数): 数据成员1(初始化值),数据成员2(初始化值)....

{函数体};

如代码所示(函数体里面无内容)

class Data {
public:Data(int y, int m, int d) :year(y), month(m), day(d)//初始化列表{}void display(){cout << year << "-" << month << "-" << day << endl;}
private:int year, month, day;
};

2:析构函数(主要释放有new动态开辟的空间)

1:析构函数的特点

1:析构函数名是类名在前面加上~取反运算符
2:析构函数没有返回值,没有参数,不能重载
3:当对象生命周期结束后自动调用,主要目的是释放对象所占内存

2:默认析构函数

每一个类都必须要有一个析构函数,如果一个类没有析构函数,系统会自动生成一个公有的析构函数,即默认析构函数,它的定义格式如下

~类名()

{       }

 默认析构函数无法回收由new分配的空间,如果对象的成员有使用new分配空间,就必须自己构造析构函数,显示使用delete来释放内存。

代码示例如下

class Array {
public:Array(int);void sort();void show();~Array();
private:int* a;int n;
};
Array::Array(int nn)
{int i;n = nn;a = new int[n];cout << "请输入" << n << "个整数" << endl;for (int i = 0; i < n; i++){cin >> a[i];}
}
void Array::sort()
{for (int i = 0; i < n; i++){for (int j = 0; j < n - i - 1; j++){if (a[j] > a[j + 1]){a[j] ^= a[j + 1];a[j + 1] ^= a[j];a[j] ^= a[j + 1];}}}
}
void Array::show()
{for (int i = 0; i < n; i++){cout << a[i] << " ";}cout << endl;
}
Array::~Array()
{delete[] a;//释放数组内存
}int main()
{Array mya(10);cout << "排序前" << endl;mya.show();cout << "排序后" << endl;mya.sort();mya.show();return 0;
}

我们定义一个构造函数来分配空间,sort函数进行排序,show函数进行打印,最后一个析构函数释放构造函数分配的空间。

3:复制构造函数

我们复制一个整型很简单,但是我们复制类这个变量也简单吗?

1:类对象的复制

class Data {
public:Data(int y, int m, int d) :year(y), month(m), day(d){}Data(const Data &d)//复制函数{year = d.year;month = d.month;day = d.day;}void display(){cout << year << "-" << month << "-" << day << endl;}
private:int year, month, day;
};int main()
{Data d1(2024, 12, 29);d1.display();//Data d2(d1);Data d2=d1;d2.display();return 0;
}

上面主函数里面两种d2写法都可以

复制构造函数的格式如下

类名(const 类名 & 引用对象名)

{复制函数体}

2:复制构造函数注意事项

1:复制构造函数也是一种构造函数,因此函数名和类名相同,并且没有返回类型
2:只有一个参数,它必须是本类类型的一个引用但并不限制为const,一般普通地会加上const修饰,让参数无法改变,以免在调用此函数时被不小心改变
3:是通过参数传进来的对象来初始化另一个对象,简单来说就是用一个对象初始化另一个对象。

3:复制构造函数的调用时机

1:对象通过另一个对象进行初始化
2:对象以值传递的方式传入函数参数
3:对象以值传递的方式从函数返回
class Data {
public:Data(int y, int m, int d) :year(y), month(m), day(d)//初始化列表{}Data(const Data &d){year = d.year;month = d.month;day = d.day;}void display(){cout << year << "-" << month << "-" << day << endl;}
private:int year, month, day;
};
Data fun()
{Data tmp(0, 0, 0);cout << "coping" << endl;return tmp;
}

我们定义一个返回类型为Data的fun函数返回一个tmp变量。

4:深复制和浅复制

在默认复制构造函数里面,编译器会自动生成代码,将老对象的成员值一一赋值给新对象成员。这是浅复制,这种复制是默认复制方法,也就是将对象里面的数据成员进行简单的赋值。大多数情况下浅复制是够用的,但是当成员函数里面出现动态成员,浅复制就不够用了。例如下面代码

#include<iostream>
using namespace std;
class student {
public:student( int n, const char* na, int s){no = n;name = new char[strlen(na) + 1];strcpy(name, na);score = s;}~student(){if (name != NULL)delete[]name;}
private:int no;char* name;int score;
};int main()
{student s1(1, "weiren", 99);student s2(s1);return 0;
}

当我们运行代码的时候,报错了,这是因为当我构造s2的时候,因为没有定义拷贝构造函数,只是将对象成员赋值,也就导致s2的成员name 和s1的成员name指向了同一块空间,而在析构函数销毁这俩个对象时,将同一块空间销毁了两次,所有导致报错解决方式就是,使用深复制。

如下更改实例

#include<iostream>
using namespace std;
class student {
public:student( int n, const char* na, int s){no = n;name = new char[strlen(na) + 1];strcpy(name, na);score = s;}student(const student& s){no = s.no;score = s.score;name = new char[strlen(s.name) + 1];strcpy(name, s.name);}~student(){if (name != NULL)delete[]name;}
private:int no;char* name;int score;
};int main()
{student s1(1, "weiren", 99);student s2(s1);return 0;
}

4:对象指针,对象引用和对象数组

指针和引用没什么好讲的,因为也就是把类型改成了类这个变量。

1:对象指针

其实和struct定义的指针一样,格式就是

类名 * 对象指针名 = 初值;

2:对象引用

格式

类名 & 对象引用名 = 对象名;

3:对象数组(重点)

格式

类名 数组名[整型常量表达式];

如下我定义一个对象数组

class student {
public:student(int n,const char na[],double s){no = n;strcpy(name, na);score = s;}student(){cout << "请输入学号,姓名,成绩" << endl;cin >> no >> name >> score;}void set(int n,const char na[],double s){no = n;strcpy(name, na);score = s;}void display(){cout << "学号" << no << endl;cout << "姓名" << name << endl;cout << "成绩" << score << endl;}private:int no;char name[20];double score;
};int main()
{student* pp[80];for (int i = 0; i < 80; i++){pp[i]=new student(i,"weiren",i+1);}for (int i = 0; i < 80; i++){pp[i]->display();delete pp[i];}
}

4:对象数组指针

只需要把代码改成这样就行了

int main()
{student* pp[80];student* (*p)[80] = &pp;for (int i = 0; i < 80; i++){(*p)[i]=new student(i,"weiren",i+1);}for (int i = 0; i < 80; i++){(*p)[i]->display();delete pp[i];}
return 0;
}

p时对象数组的指针。

5:常对象和常成员

1:常成员函数

使用const说明的成员函数我没成为常成员函数

定义格式如下

返回类型 成员函数名(参数列表) const

{函数体}

说明

1:const放在函数后面,他是函数类型的一部分,在说明函数和定义函数时都要有const

2:常对象只能调用常成员函数

3:设计常函数的好处是,让使用者知道这个函数不会改变对象成员的值

4:有无const可以进行函数重载

观察下面const的函数重载

class point {
private:int x, y;
public:point(int xx, int yy) :x(xx), y(yy) {};void show(){cout << x << " " << y << endl;}void show()const{cout << "const:" << x << " " << y << endl;}
};
int main()
{point p1(66, 77);const point p2(88, 99);p1.show();p2.show();
}

p1会调用没有const的函数,p2反之。

2:常数据成员

使用const说明的数据成员成为常数据成员

常数据成员必须初始化,而且不能改变。常数据成员的初始化只能通过构造函数的初始化列表实现。

看下面代码

class point {
private:const int x, y;//常成员
public:point(int xx, int yy) :x(xx), y(yy) {};//只能使用初始化列表void show(){cout << x << " " << y << endl;}void show()const{cout << "const:" << x << " " << y << endl;}
};
int main()
{point p1(66, 77);const point p2(88, 99);p1.show();p2.show();
}

这个时候x和y的值就不会改变了,因为他们是常成员,但是常成员是不会调用const重载的show函数,因为const重载的show函数只会去找定义对象时是否有const,也就是找常对象。

 3:指向对象的常指针

格式

类名 *const 指针变量名 =对象地址;

看下列代码

class point {
private:int x, y;
public:point(int xx, int yy) :x(xx), y(yy) {};void move(int xo, int yo){x += xo;y += yo;}void show(){cout << "(" << x << "," << y << ")" << endl;}
};int main()
{point p1(88,99), p2(1, 2);point* ptr1 = &p1;ptr1 = &p2;point* const ptr2 = &p1;p1.move(1,1);ptr2->show();return 0;
}

不用const修饰我们可以让ptr1先指向p1然后指向p2,但是使用const修饰就会让ptr2只能指向最初的p1,如果修改会报错。

4:指向常对象的指针

格式

const 类名 * 指针名;

如下代码

我没定义const point* pp来储存p1的地址成功了,也就是使用指针储存了常对象,常对象的类型是const point所以只能使用const point*来储存地址。以此可看无法使用point类型的指针来储存,const point类型的p2.

5:对象的常引用

class point {
private:int x, y;
public:point(int xx, int yy) :x(xx), y(yy) {  };void move(int xo, int yo){x += xo;y += yo;}void show()const{cout << "const(" << x << "," << y << ")" << endl;}
};
void f1(const point& cpr)
{cpr.show();
}void f2(point &rp)
{rp.show();rp.move(4, 5);
}int main()
{point p1(66,77), p2(88,99);const point cp(44,55);point& rp = p1;const point& crp = p2;f1(p1);	f1(cp);	f1(rp);	f1(crp);	f2(p1); f2(rp); f2(p2);return 0;
}

解释一下我们看f1函数的形参,是一个const point类型的常引用,实参也就可以是一般对象,常对象,一般引用和常引用。

但是我们再看f2函数的形参是一个point类型的引用,实参不能是常对象或者常引用。但是我们注意看最后的f2函数调用,我们调用了p2,虽然crp是const point的类型是p2的引用,但是p2本质上还是一个point的一般对象。所以f2依然可以接受p2传递的参数。

6:动态创建对象和释放对象

直接看代码吧

class point {
private:int x, y;
public:point() :x(0), y(0) {};point(int xx,int yy):x(xx),y(yy){}void show(){cout << x << "," << y << endl;}
};int main()
{point* points = new point[10];for (int i = 0; i < 10; ++i) {points[i] = point(i, i);}delete [10]points;return 0;
}

我们使用points来接受new开辟的10个point类型的对象空间。

然后使用for循环对其赋值。

最后手动释放掉。

7:对象的生存期(使用调试功能观察)

上程序

#include<iostream>
using namespace std;
class A {char str[20];
public:A(const char s[]) { strcpy(str, s);cout << str << "A->";}~A(){cout << str << "~A->";}
};class B {char str[20];
public:B(const char s[]){strcpy(str, s);cout << str << "B->";}~B(){cout << str << "~B->";}
};
void fun()
{A a("fun");static B b("fun");
}
A a("global");
int main()
{B b("main");fun();fun();return 0;
}

使用逐语句,重点观察static修饰的B和main函数局部变量的B和全局变量A的析构函数调用时机。

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

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

相关文章

ESP32_H2-ESP32_H2(IDF)学习系列-安装官方组件

1、 在VS Code项目工程中添加IDF组件注册表中的组件十分便捷。您只需按下“CtrlShiftP”快捷键快速进入命令面板&#xff0c;或者通过菜单栏的“查看”选项&#xff0c;选择“命令面板”来打开它。随后&#xff0c;在命令面板中输入“ESP-IDF: Show Component Registry”即可展…

【UE5】UnrealEngine源码构建2:windows构建unreal engine 5.3.2

参考大神知乎的文章:UE5 小白也能看懂的源码编译指南 据说会耗费400G的空间。 代码本身并不大,可能是依赖特别多,毕竟看起来UE啥都能干,核心还是c++的, 【UE5】UnrealEngine源码构建1:tag为5.3.2源码clone 本着好奇+ 学习的态度,想着也许有机会能更为深入的熟悉UE的机制…

[Qt] 常用控件 | QWidget | “表白程序2.0”

目录 一、控件概述 控件体系的发展阶段&#xff1a; 二、QWidget 核心属性 核心属性概览&#xff1a; 1、enabled 2、Geometry 实例 1: 控制按钮的位置 实例 2: 表白 程序 i、Window Frame 的影响 ii、API 设计理念 iii、Geometry 和 FrameGeometry 的区别 &#xf…

laravel部署到云服务器上,除了首页之外,区域页面找不到路由

laravel部署到云服务器上&#xff0c;除了首页之外&#xff0c;区域页面找不到路由&#xff0c;都是报404错误 解决方法&#xff1a; &#xff08;注&#xff1a;本人服务器使用宝塔面板&#xff09; 打开宝塔面板&#xff0c;找到该站点->配置文件 在下方增加如下代码 …

git注意事项

提交代码的备注 feat : 开发 新增功能 fix: 修复 git相关 1. git安装及全局用户设置 Git安装 npm install git -ggit修改用户名邮箱密码 git config --global --replace-all user.name "要修改的用户名" git config --global --replace-all user.email"要修改…

Agent系列:AppAgent v2-屏幕智能Agent(详解版)

引言 简介 方法 Agent 框架 Agent 交互 探索阶段 部署阶段 文档生成 高级功能 实验结果 总结 局限性 未来工作 1. 引言 大语言模型&#xff08;LLM&#xff09;如 ChatGPT 和 GPT-4 显著提升了自然语言处理能力&#xff0c;并且推动了智能体在自主决策中的应用。…

flink cdc oceanbase

接上文&#xff1a;一文说清flink从编码到部署上线 环境&#xff1a;①操作系统&#xff1a;阿里龙蜥 7.9&#xff08;平替CentOS7.9&#xff09;&#xff1b;②CPU&#xff1a;x86&#xff1b;③用户&#xff1a;root。 预研初衷&#xff1a;现在很多项目有国产化的要求&#…

Docker 安装与配置 Nginx

摘要 1、本文全面介绍了如何在 Docker 环境中安装和配置 Nginx 容器。 2、文中详细解释了如何设置 HTTPS 安全连接及配置 Nginx 以实现前后端分离的代理服务。 2、同时&#xff0c;探讨了通过 IP 和域名两种方式访问 Nginx 服务的具体配置方法 3、此外&#xff0c;文章还涵…

C语言格式输出

1.转换字符说明&#xff1a; 2.常用的打印格式&#xff1a; 在 C 语言中&#xff0c;格式输出主要依靠 printf 函数来实现。以下是一些 C 语言格式输出的代码举例及相关说明。 printf("%2d"&#xff0c;123)&#xff0c;因为输出的部分有三位数&#xff0c;但是要求…

yolov5核查数据标注漏报和误报

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、误报二、漏报三、源码总结 前言 本文主要用于记录数据标注和模型预测之间的漏报和误报思想及其源码 提示&#xff1a;以下是本篇文章正文内容&#xff0c;…

Word如何插入图片并移动到某个位置

Word如何插入图片并移动到某一个位置 新建word→插入→图片 选择合适的位置→选择图片→打开 点击图片→布局选项→选择文字环绕下的任意一个→固定在页面上 点击图片就可以将图片移动到任意位置

【prometheus】【blackbox_exporter】grafna导入blackbox_exporter看板配置

1、进入到grafana看板&#xff0c;选择合适的看板模版 地址&#xff1a;https://grafana.com/grafana/dashboards/ 在搜索框中输入 blackbox_exporter,找到合适的模版&#xff0c;如下图所示&#xff1a; 2、点击并下载对应看板JSON数据 3、在grafana的页面进行导入操作 3.1…

微服务面试题:分布式事务和服务监控

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

wx015基于springboot+vue+uniapp的经济新闻资讯的设计与实现

开发语言&#xff1a;Java框架&#xff1a;springbootuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#…

获取用户详细信息-ThreadLocal优化

Thread全局接口可用&#xff0c;不用再重复编写。所以为了代码的复用&#xff0c;使用Thread。把之前的内容&#xff08;函数的参数和map与username&#xff09;注释掉&#xff0c;换为Thread传过来的内容&#xff08;map与username&#xff09;。 因为Thread需要在拦截器里面…

【论文阅读笔记】IceNet算法与代码 | 低照度图像增强 | IEEE | 2021.12.25

目录 1 导言 2 相关工作 A 传统方法 B 基于CNN的方法 C 交互方式 3 算法 A 交互对比度增强 1)Gamma estimation 2)颜色恢复 3)个性化初始η B 损失函数 1)交互式亮度控制损失 2)熵损失 3)平滑损失 4)总损失 C 实现细节 4 实验 5 IceNet环境配置和运行 1 下载…

git环境配置用户与秘钥

git环境配置用户与秘钥 git环境配置git配置用户名与邮箱git配置秘钥 git环境配置 已经安装git后环境配置 git配置用户名与邮箱 查看git版本 git -v查看git配置环境 git config --global --list第一次未配置时会报无法找到配置文件 全局配置git用户名 git config --glob…

logback日志框架源码分析

目录 (一)入口:slf4j选择日志框架 (二)日志框架初始化 (1)logback的3种配置方式 a、BasicConfigurator默认配置 b、SPI方式配置的Configurator实现类 c、通过配置文件初始化 (2)xml配置文件初始化 (三)Logger的创建 (四)打印日志 本文源码基于:logback版…

SpringBoot整合篇 05、Springboot整合Redission

文章目录 前言Redission详细配置步骤pom依赖application.yaml配置类CacheConfigEnvironmentContext RedissionController单测 前言 本篇博客是SpringBoot整合Redission&#xff0c;若文章中出现相关问题&#xff0c;请指出&#xff01; 所有博客文件目录索引&#xff1a;博客…

从0入门自主空中机器人-4-【PX4与Gazebo入门】

前言: 从上一篇的文章 从0入门自主空中机器人-3-【环境与常用软件安装】 | MGodmonkeyの世界 中我们的机载电脑已经安装了系统和常用的软件&#xff0c;这一篇文章中我们入门一下无人机常用的开源飞控PX4&#xff0c;以及ROS中无人机的仿真 1. PX4的安装 1.1 PX4固件代码的下载…