全面细致图文并茂的C++学习笔记类与对象之“封装”等知识总结

 

前言:

        本文是作者在学习C++类与对象的过程中总结归纳的,写成博客既是一种对自己的提醒,更是分享给更多的伙伴学习交流。如若本文有些许漏洞抑或是差错,欢迎批评指正,也欢迎补充一起交流学习。本文7600余字,配合目录适用更佳,相信会对你有所帮助。话不多说,首先看看下面的思维导图,它囊括了本文的大部分内容,让我们有一个更好的思维框架。(如下图)

目录

 

前言:

1.1面向对象程序设计的基本特点

1.1.1抽象

 1.1.2封装

1.1.3继承

 1.1.4多态

 1.2类和对象

1.2.1类的定义

1.2.2类的访问控制

1.2.3对象

1.2.4类的成员函数

1.成员函数实现

2.成员函数中调用目的对象

3.带默认形参值的成员函数

4.内联函数

1.3构造函数和析构函数

1.3.1构造函数:

1.3.2默认构造和析构函数

 1.3.4构造函数的分类和调用

 1.3.5 拷贝构造函数调用时机

1.3.6 构造函数调用规则

1.3.7补充函数

1.委托构造函数

2.移动构造函数

 3. default 、delete函数

1.4类的组合

1.4.1组合

1.4.2前向引用声明

 4.5初始化列表的补充

 4.6结构体

总结:


类与对象思维导图

63f596209e904fcf92d53e83c811abf2.png


相信你已经有一个初步了解了,继续往下看吧

1.1面向对象程序设计的基本特点

1.1.1抽象

 抽象是对具体问题(对象)进行概括,抽出一类对象的公共性质并加以描述的过程
 抽象包含两个方面:数据抽象和行为抽象(功能抽象和代码抽象)

int hour,minute,second//数据抽象
showtTme();setTime();//功能抽象

 1.1.2封装

封装就是将已经抽象得到的数据和功能相结合,形成一个有机的整体(类),其中的数据和函数都是类的成员.


为什么要封装?


通过封装使一小部分陈成员充当类与外部的接口,而将其他成员隐蔽起来,这样就达到了对成员访问权限的合理控制,使不同类之间的影响降到最低,进而增强数据的安全性和简化程序编写工作


1.1.3继承

> 其使得一般概念中的属性和行为可以被特殊概念共享,摆脱重复分析、重复开发的困境*。C++
> 语言中提供了类的继承机制,允许程序员在保持原有类特性的基础上,进行更具体、更详细
> 的说明。通过类的这种层次结构,可以很好地反映出特殊概念与一般概念的关系。


 1.1.4多态

> 从广义上说,多态性是指一段程序能够处理多种类型对象的能力。在C++语言中.这
> 种多态性可以通过强制多态、重载多态、类型参数化多态,包含多态4种形式来实现


 1.2类和对象

1.2.1类的定义

//以时钟为例
class Clock										//class关键字,类名
{												//边界
public:											//外部接口
    void setTime(int newH,int newM,int newS);	//行为,代码成员
    void showTime();							//行为,代码成员
private:										//特定的访问权限
    int m_Hour,m_Minute,m_Second;				//属性,数据成员
}												//边界

1.2.2类的访问控制

532dddd7e0094057b112387f681c4cd3.png


1.2.3对象

> 类实际上一种抽象机制.它描述了一类事物的共同属性和行为。
>
> 在C++语言中,类的对象就是该类的某一特定实体(也称实例)。
> 假如将整个公司的雇员看作一个类,那么每一个雇员就是该类的个特定实体.也就是一个对象

声明一个对象和声明一个一般变量相同:类名 + 对象名

Clock my clock	//声明了一个时钟变量

注意:注意对象所占据的内存空间只是用于存放数据成员,函数成员不会在每个对象中存储副本.每个函数的代码在内存中只占据一份空间


1.2.4类的成员函数

与普通函数不同,实现成员函数时要指明类的名称。

1.成员函数实现

/*
返回值类型  类名 :: 函数成员名(参数表)
{
	函数体
}
*/
void Clock::setTime(int newH,int newM,int newS)
{
    hour = newH;
    minute = newM;
    second = newS;
}
void Clock::showTime()
{
    cout<<hour<<":"<<minute<<":"<<second<<endl;
}

2.成员函数中调用目的对象

> 成员函数调用中的目的对象调用一个成员函数与调用普通函数的差异在于.需要使用"."操作符指出调用所针对的对象.这一对象在本次调用中称为目的对象.例如使用“myClock.showTime()”调用showTime函数时.myClock就是这一调用过程中的目的对象

注意:注意在类的成员函数中,既可以访问目的对象的私有成员,又可以访问当前类的其他对象的私有成员

3.带默认形参值的成员函数

类成员函数也可以像普通函数一样有默认形参值,但**默认值要写在类的定义中**

class Clock
{
 public:
    void setTime(int newH = 0, int newM = 0,int newS = 0);
    ……
}

4.内联函数

> 内联成员函数我们知道.函数的调用过程要消耗一些内存资源和运行时间来传递参数和返回值,要记录调用时的状态.以便保证调用完成后能够正确地返回并继续执行。如果有的函数成员需要被频繁调用,而且代码比较简单的,这个函数也可以定义为内联函数(inline function)

注意:内联函数会增长编译后的代码长度,要权衡利弊慎重选择

//1.隐式声明,将函数体直接放在类体里
class Clock
{
 public:
    //隐式声明
    void setTime(int newH = 0, int newM = 0,int newS = 0);
    ……
}
//2.显式声明:在函数体实现时,在函数返回值类型前面加上inline
inline void Clock::showTime()
{
    //::为域符
    ……
}

介绍了这么多,来一个完整程序复习一下

#include<iostream>

usinq namespace stdi
//时钟类的定义
class Clocki
//外部接口,公有成员雨数
pwblic:

void setPime(int newH=0, int newM=0, int news = 0),
veldshowTime()
//私有数据成员
private:

int hour, minute,second;
//时钟类成员雨数的具体实现
void Clock:;setTime(int newH, int newN, int news) {
hour=newH
minute-newM;
second-newS;
inline void cloek::showTlme() (
cout<<hour<<";"<<minute<<";"<csecondx<endl;
//主雨数

int main() {

//定义对象myClock
Clock myClockx

cout<<"First time set and output:"<<end1;
//设置时间为默认值
myClock.setTime()

//显示时间
myClock.showTime()
cout<<"Second time set and output:"<<endl;
//设置时间为 8: 30: 30
myClock.setrime(8, 30, 30)7

//显示时间
myClock.showTime();
return 0

(呼呼呼,不容易,上面我们学习了类和对象的具体定义以=以及成员函数的基本介绍,稍作喘息,我们进入具体函数的介绍)


1.3构造函数和析构函数

记住在定义对象的时候进行的数据成员设置,为对象的初始(对象的创建过程-->调用构造和析构等函数)

1.3.1构造函数:

  • ·进行初始化.就意味着在为变量分配内存单元的同时在其中写入了变量的初始值
  • ·当遇到对象定义时,程序会向操作系统申请一定的内存空间用于存放新建的对象

构造函数的作用就是在对象被创建时利用特定的值构造对象.将对象初始化为一个特定的状态。构造函数也是类的一个成员函数,除了具有一般成员函数的特征外,还有一些特
殊的性质:构造函数的函数名与类名相同,而且没有返回值;构造函数通常被声明为公有函数


1.3.2默认构造和析构函数

默认构造函数语法:`类名( ){ }

  •  构造函数,没有返回值也不写void
  • 函数名称与类名相同
  • 构造函数可以有参数,因此可以发生重载
  •  程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次

析构函数语法: `~类名 () { }`

  • 析构函数,没有返回值也不写void
  • 函数名称与类名相同,在名称前加上符号  ~
  • 析构函数不可以有参数,因此不可以发生重载
  • 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次

看个例子:

class Person
{

public:
	//构造函数
	Person()

	{
		cout << "Person的构造函数调用" << endl;
	}
    //析构函数
	~Person()
	{
		cout << "Person的析构函数调用" << endl;
	}
};

void test01()
{
	Person p;

}
int main() {
	test01();

	system("pause");
	return 0;
}

 1.3.4构造函数的分类和调用

*两种分类方式:*

  • ​    *按参数分为: 有参构造和无参构造*
  • ​    *按类型分为: 普通构造和拷贝构造*

*三种调用方式:*

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

看下图例子:

16d2e40c249c477f8ca7bdbb305f6379.png


 1.3.5 拷贝构造函数调用时机

C++中拷贝构造函数调用时机通常有三种情况

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

示例:

#include<iostream>

using namespace std;

class Person
{
public:
	Person()
	{
		cout << "默认构造函数的调用" << endl;
	}
	Person(int age)
	{
		m_age = age;
		cout << "有参函数的调用" << endl;
	}
	Person(const Person& p)
	{
		m_age = p.m_age;
		cout << "拷贝函数的调用" << endl;
	}
	~Person()
	{
		cout << "析构函数的调用" << endl;
	}
	int m_age;
};

//2.值传递的方式给函数参数传值
//值传递其实就是要拷贝副本,拷贝函数会执行
void doWork(Person p)
{
}
//3.值方式返回局部变量
Person doWork2()
{
	Person p3;
	//值返回也是拷贝
	cout << (int*)&p3 << endl;
	return p3;
}
void test03()
{
	Person p = doWork2();
	cout << (int*)&p << endl;
}
void test02()
{	//	实参传递给形参有拷贝
	Person p;
	doWork(p);
}
void test01()
{
	Person p1(20);
	Person p2(p1);
	cout << "p2的年龄是: " << p2.m_age << endl;
}
int main() {
	//test01();
	//test02();
	test03();
	return 0;
}

运行结果:

f0b495acd5f943b98d64fb8d8795bab4.png


1.3.6 构造函数调用规则

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

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

构造函数调用规则如下:

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

示例:

class Person {
public:
	//无参(默认)构造函数
	Person() {
		cout << "无参构造函数!" << endl;
	}
	//有参构造函数
	Person(int a) {
		age = a;
		cout << "有参构造函数!" << endl;
	}
	//拷贝构造函数
	Person(const Person& p) {
		age = p.age;
		cout << "拷贝构造函数!" << endl;
	}
	//析构函数
	~Person() {
		cout << "析构函数!" << endl;
	}
public:
	int age;
};
void test01()
{
	Person p1(18);
	//如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作
	Person p2(p1);

	cout << "p2的年龄为: " << p2.age << endl;
}

void test02()
{
	//如果用户提供有参构造,编译器不会提供默认构造,会提供拷贝构造
	Person p1; //此时如果用户自己没有提供默认构造,会出错
	Person p2(10); //用户提供的有参
	Person p3(p2); //此时如果用户没有提供拷贝构造,编译器会提供
	//如果用户提供拷贝构造,编译器不会提供其他构造函数
	Person p4; //此时如果用户自己没有提供默认构造,会出错
	Person p5(10); //此时如果用户自己没有提供有参,会出错
	Person p6(p5); //用户自己提供拷贝构造
}
int main() {
	test01();
	system("pause");
	return 0;
}

运行结果:

307c6d4be5d141dbbc8362fd5659b59f.png


1.3.7补充函数

1.委托构造函数

> 一个委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程.也就是说它把自身的一些(或者全部)职责委托给了其他构造函数我们使用委托构造函数重写Clock类的构造函数.其形式如下:

//构造函数
Clock(int newH, int newM, int newS) {
hour=newH;
minute=newN;

second=newS;
//构造函数
Clock() :Clock(0, 0,0) {}
/*可以看到第二个构造函数委托给了第一个构造函数来完成数据成员的初始化。当一个构造
函数委托给另一个构造函数时,受委托的构造函数的初始值列表和函数体依次执行,然后控
制权才会交还给委托者函数。*/

2.移动构造函数

参考一下书籍:

fc59ed5fc6db49a6aabeaf9e4b7a455c.png


 3. default 、delete函数

> 默认构造函数、复制构造函数和移动构造函数.这些概念让人眼花缭乱.在定义一个新类时.用户可能只是希望简单地使用.不希望花太多精力在复制控制优化性能上.C++11标准提供了defaul和delete两个关键字来简化构造函数的定义与使用。使用=delault可显示要求编译器自动生成默认或复制构造函数。

举个栗子:

class MyStr {
public:
string sr
//默认合成的无参构造函数
MyStr()=default;
//有参构造函数
NyStr(string _s) : s(std::move(_s)) {};
//默认合成的复制构造函数
MyStr(MyStr ssstr)=default;
//默认合成的析构函数
~MyStr()=default;
};

还有一个:

class MyStr {
public:
string s;
//默认合成的无参物造函数
MyStr()=default;
//有参构造函数
MyStr(string_s) : s(std::move(_s)) ();
//删除复制构造函数
NyStr(NyStr ssstr)=deleter
//默认合成的析构函数
~MyStr()=default;
};

1.4类的组合

1.4.1组合

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

例如:

class A {}
class B
{
    A a;
}

B类中有对象A作为成员,A为对象成员

拓展一下概念

类的组合描述的就是一个类内嵌其他类的对象作为成员的情况.它们之间的关系是一
种包含与被包含的关系。

例如,用一个类来描述计算机系统.首先可以把它分解为硬件和软件.而硬件包含中央处理单元(Central Processing Unit.CPU)、存储器、输人设备和输出设备.软件可以包括系统软件和应用软件。这些部分每一个都可以进一步分解,用类的观点来描述.它就是一个类的组合。图4-3给出了它们的相互关系。对于稍微复杂的问题都可以使用组合来描述.这比较符合逐步求精的思维规律

当创建类的对象时.如果这个类具有内嵌对象成员,那么各个内嵌对象将被自动创建
因为部件对象是复杂对象的一部分。因此.在创建对象时既要对本类的基本类型数据成员
进行初始化.又要对内嵌对象成员进行初始化
。这时,理解这些对象的构造函数被调用的顺
序就很重要

76da2bd339ca4133a037d7fbfa3094ef.png


顺序:

  •  调用内嵌对象的构造函数.调用顺序按照内嵌对象在组合类的定义中出现的次序
  • 内嵌对象在构造函数的初始化列表中出现的顺序与内嵌对象构造函数的调用顺序无关。
  • 执行本类构造函数的函数体

注意:

​    **析构函数的调用执行顺序和构造函数刚好相反**


1.4.2前向引用声明

看个例子了解一下即可:

//A类的定义
class A{
//外部接口
Public:
//以B类对象b为形参的成员雨数
void f(B b);
//这里将引起编译错误,因为“B"为未知符号
//B类的定义
class B{
//外部接口
public:
//以A类对象a为形参的成员雨数
void g(A a);
};

为了解决这个问题,我们需要用到前向引用声明(看下面代码)

class B; //前向引用声明 <----------很有效
//A类的定义
class A{
//外部接口
Public:
//以B类对象b为形参的成员雨数
void f(B b);
//这里将引起编译错误,因为“B"为未知符号
//B类的定义
class B{
//外部接口
public:
//以A类对象a为形参的成员雨数
void g(A a);
};

 4.5初始化列表的补充

作用:

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

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

示例:

class Person {
public:

	传统方式初始化
	//Person(int a, int b, int c) {
	//	m_A = a;
	//	m_B = b;
	//	m_C = c;
	//}

	//初始化列表方式初始化 
	Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}
	void PrintPerson() {
		cout << "mA:" << m_A << endl;
		cout << "mB:" << m_B << endl;
		cout << "mC:" << m_C << endl;
	}
private:
	int m_A;
	int m_B;
	int m_C;
};

int main() {

	Person p(1, 2, 3);
	p.PrintPerson();


	system("pause");

	return 0;
}

        


看到这里的小伙伴很不容易,马上就到最后一节了,坚持就是胜利!

 4.6结构体

首先说明类与结构体的一大不同:

**Class的默认访问类型是私有类型的,而结构体是public的。**

在C++中 struct和class唯一的**区别**就在于 **默认的访问权限不同**

区别:

  • struct 默认权限为公共
  • class   默认权限为私有
class C1
{
	int  m_A; //默认是私有权限
};
struct C2
{
	int m_A;  //默认是公共权限
};
int main() {

	C1 c1;
	c1.m_A = 10; //错误,访问权限是私有

	C2 c2;
	c2.m_A = 10; //正确,访问权限是公共

	system("pause");

	return 0;
}

总结:

好了,关于C++类与对象中的封装等知识点以及就介绍到这里。寥寥7000字,远不能介绍完C++的类与对象的知识,当然也受限制于作者的水平,欢迎批评指正,以前交流学习进步!

猜你喜欢

转载自blog.csdn.net/m0_73421035/article/details/130009533