C++从0到1的入门级教学(十)——类和对象

文章目录

  • 10 类和对象
    • 10.1 封装
      • 10.1.1 封装的意义
      • 10.1.2 struct和class的区别
      • 10.1.3 成员属性设置为私有
    • 10.2 对象的初始化和清理
      • 10.2.1 构造函数和析构函数
      • 10.2.2 构造函数的分类及调用
      • 10.2.3 关于拷贝构造函数调用时机
      • 10.2.4 构造函数调用规则
      • 10.2.5 深拷贝和浅拷贝
      • 10.2.6 初始化列表
      • 10.2.7 对象成员

10 类和对象

C++面向对象的三大特性:封装、继承、多态

C++认为万事万物皆为对象,对象上有其属性和行为

例如

人可以作为对象,属性有姓名、年龄、身高、体重。。。行为有走、跑、跳、吃饭、唱歌。。。

具有相同性质的对象,我们可以抽象为类,人属于人类,车属于车类。

10.1 封装

10.1.1 封装的意义

封装:封装是C++面向对象三大特性之一,其将属性和行为作为一个整体,表现生活中的事物;并可以将属性和行为加以权限控制

封装意义一:在设计类的时候,属性和行为写在一起,表现事物

语法class 类名{访问权限:属性/行为};

示例1:设计一个圆类

#include <iostream>
using namespace std;//圆周率
const double PI = 3.14;//设计一个圆类,求圆的周长
//圆求周长的公式:2*PI*半径//class 代表设计一个类,类后面紧跟着的就是类的名称
class Circle
{
public://属性:半径int m_r;//行为:获取圆的周长double calculateZC(){return 2 * PI * m_r;}
};int main()
{//通过这个圆类创建具体的圆(对象)Circle c1;//给圆对象的属性进行赋值c1.m_r = 10;cout << "圆的周长为:" << c1.calculateZC() << endl;system("pause");return 0;
}

结果:圆的周长为:62.8

示例二:设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号

#include <iostream>
#include <string>
using namespace std;//设计学生类
class Student
{
public://公共权限//属性string m_Name;//姓名int m_Id;//学号//行为void showStudent(){cout << "姓名:" << m_Name << "学号:" << m_Id << endl;}
};int main()
{//实例化Student s1;s1.m_Name = "张三";s1.m_Id = 1;s1.showStudent();system("pause");return 0;
}

结果:姓名:张三学号:1

对于上面的代码,可以改进一下:

#include <iostream>
#include <string>
using namespace std;//设计学生类
class Student
{
public://公共权限//属性string m_Name;//姓名int m_Id;//学号//行为void showStudent(){cout << "姓名:" << m_Name << "学号:" << m_Id << endl;}void setName(string name){m_Name = name;}void setId(int id){m_Id = id;}
};int main()
{//实例化Student s1;s1.setName("张三");s1.setId(1);s1.showStudent();system("pause");return 0;
}

封装意义二:类在设计时,可以把属性和行为放在不同的权限下,加以控制

访问权限有三种:

英文名中文名说明
public公共成员类内可以访问、类外也可访问
protected保护成员类内可以访问、类外不可访问,但是在继承时,protected的内容可以被子类访问
private私有权限成员类内可以访问、类外不可访问,但是在继承时,private的内容不可以被子类访问

10.1.2 struct和class的区别

在C++中和struct和class唯一的区别就在于默认的访问权限不同;对于struct来说,其默认权限为公共;而对于class来说,其默认权限为私有


10.1.3 成员属性设置为私有

这块知识就和java中的封装对应上了,在java中,我们时常用private对类中的成员属性进行私有化,然后用set去写入,用get去读。

在C++中,我们同样的将所有成员属性设置为私有,这样的好处是可以自己控制读写权限,并且在写入数据的过程中,我们还可以用函数来控制其写入数据的有效性。

如果想体验这个知识点,可以试着做一下下面的案例:

#include <iostream>
#include <string>
using namespace std;//设计人类
class Person 
{
private://姓名string m_Name;//年龄int m_Age;public:void setName(string name){m_Name = name;}void setAge(int age){m_Age = 0;if(age>0 && age < 200)m_Age = age;}string getName(){return m_Name;}int getAge(){return m_Age;}
};int main()
{Person p1;p1.setName("张三");p1.setAge(-1);cout << p1.getName() << endl;cout << p1.getAge() << endl;
}

结果:张三
0


10.2 对象的初始化和清理

生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用时候也会删除一些自己信息数据保证安全,而对于C++来说,C++的面向对象编程也是来源于生活,所以每个对象都会有初始设置以及对象销毁前的清理数据的设置。

10.2.1 构造函数和析构函数

C++利用了构造函数和析构函数解决上面说的初始化和销毁化问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事。因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数。并且两个都是是空实现。

和java实际上很类似,java中也存在构造函数,但是没有析构函数,java如果没有构造方法,那么其自带一个无参构造;如果写了构造方法,那么系统原有自带的无参构造会消失,这也意味着我们写完有参构造后还要再写一个无参构造。

java没有析构函数的原因是,其运行在JVM上,当对象消失,那么JVM会启用垃圾回收机制,回收分配给对象的资源;而C++不会自动回收,因此当对象消失时,需要提供析构函数来回收资源。

构造函数语法为类名(){},需要注意的是,C++中的构造函数没有返回值也不用写void,构造名称和类名相同。构造函数可以有参数,所以可以发生重载,即可以同时拥有空参构造方法有参构造方法。程序在调用对象的时候会自动调用构造,无需手动调用,而且只会调用一次。

对于析构函数来说,其语法为~类名(){},需要注意的是,析构函数同构造函数的特点基本一样,有一点不一样的是析构函数不可以有参数,因此不能写多个析构函数。

在visual studio中,如果你用了系统提示的class创建,那么他会帮你把构造和析构全部写好,如果要写多余的构造自己补充即可。

10.2.2 构造函数的分类及调用

两种分类方式:

按参数分按类型分
有参构造普通构造
无参构造(默认构造)拷贝构造

三种调用方式:

  • 括号法
  • 显示法
  • 隐式转换法

拷贝构造

除了拷贝构造之外,其他的都是普通构造。在学习其他编程语言的过程中,我们并没有听过拷贝构造,其是C++中特有的一种构造方法,通过类名 (const 类名 &对象名){拷贝内容}即可定义拷贝构造,拷贝构造常用语将某一个对象的属性拷贝给另外一个对象。

括号法

括号法很简单,但是学过java的同学可能会混,什么意思呢。

如果是调用无参构造,那么只需类名 对象名,系统会自动调用,而在java中,我们通常是类名 对象名 = new 构造器(),也就是说即使是无参我们也会把括号写上;而在C++中调用无参构造时不能写括号,因为写了括号,C++会误以为你是写了一个函数的声明。

如果是有参构造,只需要类名 对象名(参数)即可。

如果是拷贝构造,只需要类名 对象名(被拷贝的对象)

显式法

如果不用括号法,可以用显式法来调用构造函数,从形式上看,显式法更像java的构造调用。

对于无参构造,显式法和括号法一样。

对于有参构造,显式法格式为:类名 对象名 = 类名(参数)

对于拷贝构造,显式法格式为:类名 对象名 = 类名(被拷贝的对象)

需要注意的是,类名(参数)类似于java学习中的匿名内部类,在C++中被我们称为匿名对象,其特点是当前行执行结束后,系统会立即回收掉匿名对象。

还有一点是:不要利用拷贝函数来初始化一个匿名对象,如:类名(对象名),如果你尝试这么做,编译器会认为其等价于类名 对象名,即对象的声明。

隐式转换法

隐式转换法实际上是显式法的简略版,但是你说简略实际上它还没括号法简略呢,所以不想了解也没啥问题,虽然别人写的代码你可能看不懂哈哈。

对于有参构造:隐式转换法格式为:类名 对象名 = 参数。对于拷贝构造也是同样的原理。

10.2.3 关于拷贝构造函数调用时机

C++中需要拷贝构造函数的情况通常有三种:

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

示例:

#include<iostream>
using namespace std;class Person
{
public:int m_Age;Person(){cout << "Person默认构造函数调用" << endl;}Person(int age){cout << "Person有参构造函数调用" << endl;m_Age = age;}Person(const Person& p){cout << "Person拷贝构造函数调用" << endl;m_Age = p.m_Age;}~Person() {cout << "Person默认析构函数调用" << endl;}};//构造拷贝函数调用时机
//1、使用一个已经创建完毕的对象来初始化一个新对象
void test01() 
{Person p1(20);Person p2(p1);cout << "p2的年龄为:" << p2.m_Age << endl;
}//2、值传递的方式给函数参数传值
void doWork(Person p) 
{}void test02()
{Person p;doWork(p);
}//3、值方式返回局部对象
Person doWork2()
{Person p1;return p1;
}void test03()
{Person p = doWork2();
}int main()
{//test01();//test02();test03();
}

10.2.4 构造函数调用规则

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

  • 默认构造函数,无参,函数体为空
  • 默认析构函数,无参,函数体为空
  • 默认拷贝构造函数,对所有属性值进行拷贝

构造函数调用规则如下:

  • 如果用户定义有参构造函数,C++将不在提供无参构造,但是会提供默认拷贝构造
  • 如果用户定义拷贝构造函数,C++将不会提供其他构造函数

乍一看,上面的规则似乎和Java中的很类似。

如果想要了解上述的知识点,我们可以手动做一下下列的案例:

#include<iostream>
using namespace std;//构造函数的调用规则
/*- 默认构造函数,无参,函数体为空
- 默认析构函数,无参,函数体为空
- 默认拷贝构造函数,对所有属性值进行拷贝*/class Person 
{
public:int m_Age;Person() {cout << "Person的默认构造函数调用" << endl;}Person(int age) {cout << "Person的有参构造函数调用" << endl;}/*person(const person &p) {cout << "person的拷贝构造函数调用" << endl;m_age = p.m_age;}*/~Person() {cout << "Person的默认析构函数调用" << endl;}
};void test01()
{Person p;p.m_Age = 18;Person p2(p);cout << "p2的年龄为:" << p2.m_Age << endl;
}int main() 
{test01();
}

10.2.5 深拷贝和浅拷贝

浅拷贝:简单的赋值拷贝操作

深拷贝:在堆区重新生成空间,然后再进行拷贝

深浅拷贝是面试最常见的问题,需要严加重视。

我们现在来思考一个问题,如果我们想要利用拷贝构造函数在堆区生成一块空间怎么办?一般思路应该如下:

#include <iostream>
using namespace std;class Person
{
public:int m_Age;int* m_Height;Person() {cout << "Person的默认构造函数被调用" << endl;}Person(int age, int height) {cout << "Person的有参构造函数调用" << endl;m_Age = age;m_Height = new int(height);}~Person(){if (m_Height != NULL){delete(m_Height);m_Height = NULL;}cout << "Person的析构函数调用";}private:};void Test01() 
{Person p1(18,160);cout << "p1的年龄为:" << p1.m_Age << endl;Person p2(p1);cout << "p2的年龄为:" << p2.m_Age << endl;
}int main() 
{Test01();
}

经过上面的代码敲试,你会发现这段代码是会报错的。因为这里出现了一个问题——重复释放内存。

我们知道,上述代码的p2是由p1拷贝而来,在执行析构函数时,p2的代码写于p1之后,而析构函数位于栈空间,那么根据栈后入先出的特点,则p2应该首先执行析构函数。

在你没有编写拷贝构造函数时,系统会默认给出一个拷贝构造函数,这个拷贝构造函数执行的是一个浅拷贝的过程,相当于我们平时说的值传递,它的作用是赋值p1对象里所有的值给p2。

当我们执行上面的代码时,p1的析构也给了p2,p1中的m_Height作为在堆区开辟的数据,p2从p1拷贝了m_Height的指针,也就是说,P2.m_Height和P1.m_Height共享这个指针。当p2先执行析构,该指针就被释放掉;而轮到p1执行析构时,没有指针可释放,所以就报错了。编译器写的浅拷贝构造函数如下:

Person(const Person &p)
{cout<<"Person 拷贝构造函数调用"<<endl;m_Age = p.m_Age;m_Height = p.m_Height;
}

既然弄明白了上述的问题,我们要做的,实际上就是要再开辟一块堆区空间,使得p2和p1的m_Height空间不一样。如下所示:

#include <iostream>
using namespace std;class Person
{
public:int m_Age;int* m_Height;Person() {cout << "Person的默认构造函数被调用" << endl;}Person(int age, int height) {cout << "Person的有参构造函数调用" << endl;m_Age = age;m_Height = new int(height);}~Person(){if (m_Height != NULL){delete(m_Height);m_Height = NULL;}cout << "Person的析构函数调用";}Person(const Person& p) {cout << "Person 拷贝构造函数调用" << endl;m_Age = p.m_Age;//深拷贝m_Height = new int(*p.m_Height);}private:};void Test01() 
{Person p1(18,160);cout << "p1的年龄为:" << p1.m_Age << endl;cout << "p1的身高为:" << p1.m_Height << endl;Person p2(p1);cout << "p2的年龄为:" << p2.m_Age << endl;cout << "p2的身高为:" << p2.m_Height << endl;
}int main() 
{Test01();
}

10.2.6 初始化列表

我们知道构造函数实际上就是用于初始化对象,C++提供了初始化列表语法,用于初始化对象中的属性。其语法如下所示:

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

#include <iostream>
using namespace std;//初始化列表
class Person
{
public:int m_A;int m_B;int m_C;//传统初始化方法/*Person(int a, int b, int c){m_A = a;m_B = b;m_C = c;};*///初始化列表/*Person() :m_A(10), m_B(20), m_C(30) {}*///初始化列表Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}
};void test01() 
{//Person p(10, 20, 30);Person p(10,20,30);cout << "m_A=" << p.m_A << endl;cout << "m_B=" << p.m_B << endl;cout << "m_C=" << p.m_C << endl;
}int main()
{test01();
}

10.2.7 对象成员

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

如:

class A{}
class B
{A a
}

由此引发一个问题,当创建B对象时,A和B的构造和析构的顺序谁先谁后。

#include <iostream>
using namespace std;
#include <string>class Phone
{
public://手机品牌名称string m_PName;Phone(string pName) {m_PName = pName;};private:};class Person
{
public://姓名string m_Name;//手机Phone m_Phone;Person(string name, string pName) :m_Name(name), m_Phone(pName) {}private:};void test01()
{Person p("张三", "苹果X");cout << p.m_Name << "拿着" << p.m_Phone.m_PName << endl;
}int main() 
{test01();
} 

执行以上的代码我们可以看出,实际上对象成员是先构造的,而后类对象再构造;而对于析构来说,类对象先析构,而后是对象成员。这就好比搭积木,你要搭个人出来肯定要先搭好胳膊和腿;同理,如果你要拆积木,也肯定是先拆胳膊和腿。

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

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

相关文章

js二级导航

js写二级导航要点 1.ul li 2.js获取元素 3.setInterval(function(),time); 代码如下 1 <style type"text/css">2 ul,li,body{margin:0;padding: 0;}3 #nav{width: 500px;margin: 10px auto;}4 ul li{list-style: none;}5 .clear{clear: both;}6 #n…

关于STM32的两个小问题的总结

一、最近做了一个关于自动转速测试仪的项目&#xff0c;其中用到了STM32的RTC时钟的功能&#xff0c;然后开始写代码&#xff0c;并且成功的跑了起来&#xff0c;于是将自己的板子放到桌面上让它跑了一个晚上看下误差&#xff0c;结果发现经过一晚上&#xff0c;误差并不是很大…

深度学习修炼(六)——神经网络分类问题

文章目录6 分类任务6.1 前置知识6.1.1 分类6.1.2 分类的网络6.2 动手6.2.1 读取数据6.2.2 functional模块6.2.3 继续搭建分类神经网络6.2.4 继续简化6.2.5 训练模型6.3 暂退法6.3.1 重新看待过拟合问题6.3.2 在稳健性中加入扰动6.3.3 暂退法实际的实现6.4 后话6 分类任务 在这…

修改NavigationBar的分根线颜色

[self.navigationController.navigationBar setShadowImage:[Static ColorToImage:[Static colorWithHexString:[UIColor red]]]]; Static 里的几个静态方法 (UIImage *)ColorToImage:(UIColor *)color{CGRect rectCGRectMake(0.0f, 0.0f, 1.0f, 1.0f);UIGraphicsBeginImageCo…

Java IO 之 InputStream源码(2)

Writer&#xff1a;李强强 一、InputStream InputStream是一个抽象类&#xff0c;即表示所有字节输入流实现类的基类。它的作用就是抽象地表示所有从不同数据源产生输入的类&#xff0c;例如常见的FileInputStream、FilterInputStream等。那些数据源呢&#xff1f;比如&#xf…

【CTSC2017】【BZOJ4903】吉夫特 卢卡斯定理 DP

题目描述 给你一个长度为\(n\)的数列\(a\)&#xff0c;求有多少个长度\(\geq 2\)的不上升子序列\(a_{b_1},a_{b_2},\ldots,a_{b_k}\)满足\[ \prod_{i2}^k\binom{a_{b_{i-1}}}{a_{b_i}}\mod 2>0 \]   答案对\({10}^97\)取模。 \(n\leq211985,a_i\leq 233333\) \(\forall i\…

深度学习修炼(七)——卷积神经网络

文章目录7 卷积神经网络7.1 卷积网络和传统网络的区别7.2 卷积7.2.1 卷积过程画大饼7.2.2 图像的不变性7.2.3 互相关运算*(补充)7.2.4 图像颜色通道*(补充)7.2.5 步幅7.2.6 多次卷积7.2.7 边缘填充7.2.8 特征图的大小7.2.9 卷积参数共享7.3 池化7.4 整体网络架构7.5 后话7 卷积…

Java并发编程:ThreadLocal

Java并发编程&#xff1a;深入剖析ThreadLocal ThreadLocal ThreadLocal之我见 Struts2中的设计模式----ThreadLocal模式 ThreadLocal ThreadLocal原理及其实际应用 ThreadLocal 转载于:https://www.cnblogs.com/a198720/articles/4229366.html

用递归形成树结构数据

定义一个树形实体 public class orgTrees{public orgTrees(){this.Children new List<orgTrees>();}public int Id { get; set; }public int FatherId { get; set; }public string Name { get; set; }public int Lever { get; set; }public bool HasChildren { get; se…

网络爬虫(一)——爬虫及其实现

文章目录1.1 爬虫概述1.1.3 网络爬虫和浏览器的区别1.1.2 网络爬虫的定义1.2 requests请求库1.2.1 requests基本概念1.2.2 疫情数据爬取1.2.3 get请求1.2.4 headers请求头1.2.5 Cookies验证1.3 Beautiful Soup解析库1.3.1 安装1.3.2 对象的创建1.3.3 find方法1.3.4 后话1.4 正则…

C# winform 窗体怎么隐藏标题栏,不显示标题栏

//没有标题 this.FormBorderStyle FormBorderStyle.None; //任务栏不显示 this.ShowInTaskbar false;转载于:https://www.cnblogs.com/qq260250932/p/4230472.html

sqL编程篇(三) 游标与存储过程

sql编程2 游标与存储过程 sql编程中的游标的使用&#xff1a;提供的一种对查询的结果集进行逐行处理的一种方式不用游标的处理解决方式&#xff1a;逐行修改工资update salar set 工资‘新工资’ where 雇员号0101 //通过查出雇员号而修改工资过程&#xff1a;1.定义一个游标&a…

python爬虫从入门到精通

第一讲 什么是爬虫 网络蜘蛛&#xff08;Web spider&#xff09;也叫网络爬虫&#xff08;Web crawler&#xff09;&#xff0c;蚂蚁&#xff08;ant&#xff09;&#xff0c;自动检索工具&#xff08;automatic indexer&#xff09;&#xff0c;或者&#xff08;在FOAF软件概念…

Windows五种IO模型性能分析和Linux五种IO模型性能分析

Windows五种IO模型性能分析和Linux五种IO模型性能分析 http://blog.csdn.net/jay900323/article/details/18141217 http://blog.csdn.net/jay900323/article/details/18140847 重叠I/O模型的另外几个优点在于&#xff0c;微软针对重叠I/O模型提供了一些特有的扩展函数。当使用重…

C++从0到1的入门级教学(十一)——友元

文章目录11 友元11.1 全局函数做友元11.2 友元类11.3 成员函数做友元11 友元 让我们引入一个例子来讲述友元是什么。 生活中你的家有客厅&#xff0c;有卧室&#xff0c;客厅所有来的客人都可以进去&#xff0c;但是你的卧室是私有的。对于认识的人来说你可以允许它进去&…

KeyMob:为国内应用开发者管理的广告聚合平台

为什么80%的码农都做不了架构师&#xff1f;>>> 应用开发者在应用中嵌入广告SDK的来源主要包括两种&#xff1a;使用移动广告平台与移动广告聚合平台。国内有多少家提供移动广告管理的平台&#xff1f;据统计&#xff0c;这两个版本&#xff0c;已经有四五十家。虽…

模拟航班查询及预定系统 编写示例

一、建立C#窗体 所需控件&#xff1a; Label标签 Button 按钮 TextBox 文本框 ComboBox 组合框 DATaGridView 数据显示 DateTimePicker 日期表 NumericUpDown 数字选择 二、建立后台数据库 大概需要四张表 1&#xff0c;航空公司表 2&#xff0c;城市信息表 3&#xff0c;航班…

package

package转载于:https://www.cnblogs.com/wangweiabcd/p/4232646.html

数据结构杂谈(七)——串

文章目录7 串7.1 基本知识7.1.1 串的定义:rose:定义:rose:各种概念:rose:字符串和线性表的区别7.1.2 串的抽象类型数据定义7.1.3 串的比较:rose:原理7.2 串的存储结构:rose:7.2.1串的顺序存储:rose:7.2.2 串的链式存储7.3 基本操作:rose:7.3.1 返回子串操作:rose:7.3.2 比较操作…

牛刀小试Oracle之ORACLE 11GR2 RAC安装配置--检测GI软件是否正常(三)

1. 切换至grid用户[rootZracnode1 ~]# su - grid2.查看CRS状态(目前Oracle11GR2官方文档&#xff0c;不建议用如下命令检测了&#xff0c;等我有时间在整理补充)[gridZracnode1 ~]$ crs_stat -tName Type Target State Host ---------------…