参考:

1.https://blog.csdn.net/cherrydreamsover/article/details/81942293

2.https://www.cnblogs.com/jiqing9006/p/9348832.html

3.https://blog.csdn.net/WJ_SHI/article/details/81782643

我们都知道C++有三大特性:封装、继承、多态,之前我总结过继承的知识点,现在来总结一下封装的相关知识!

一、什么是封装?

封装:隐藏对象的属性和实现细节,仅对外公开接口和对象进行交互,将数据和操作数据的方法进行有机结合。

说明:
  函数是封装的一种形式:函数所执行的细节行为被封装在函数本身这个更大的实体中,被封装的元素隐藏了它们的实现细节–可以调用一个函数但是不能够访问函数所执行的语句。

访问限定符

(1)public(共有)
(2)protected(保护)
(3)private(私有)

说明:
  (1)public成员可以在类外直接访问。
  (2)protected和private成员在类外(在此将这两种限定符都可以看成是私有的,在继承出区别)不能够访问。
  (3)它们的作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。
  (4)class的默认访问权限是private,而struct为public型(因为struct要兼容C)。C++常用的是class
  (5)类外:即脱离了类的作用域或者说访问时不在类的成员函数中。

因此C++的封装和函数有一些不同,它能够更加灵活的满足不同对象调用的需求,因此封装相当于是class+访问限定符

注意:访问限定符本质上是给编译器使用的,数据放在内存中是没有任何限制的

来段代码看看:

 1 class CDate
2 {
3 public :
4
5 void SetDate(int iYear = 1990, int iMonth = 1, int iDay = 1)
6 {
7 _iYear = iYear;
8 _iMonth = iMonth;
9 _iDay = iDay;
10 }
11
12 void PrintDate()
13 {
14 cout << _iYear << "-" << _iMonth << "-" << _iDay << endl;
15 }
16
17 private:
18 int _iYear;
19 int _iMonth;
20
21 public:
22 int _iDay;
23 };
24 int main()
25 {
26 CDate d;
27 d.SetDate(2016, 3, 2);
28 d.PrintDate();
29 // d._iYear = 2016; // 私有成员变量不能再类外直接访问
30 // d._iMonth = 3;
31 d._iDay = 2;
32 return 0;
33 }

二、类的作用域

局部作用域
全局作用域
类作用域
名字空间作用域

(1) 在类体外定义成员,需要使用 :: 作用域解析符指明成员属于哪个类域。
  (2) 在类的作用域外,只能够通过对象或指针借助成员访问操作符.和->来访问类成员,跟在访问操作符后面的名字必须在相关联类的作用域中。
  (3)成员变量在类中具有全局作用域。

 1 // ConsoleApplication6.cpp : 定义控制台应用程序的入口点。
2 //
3
4 #include "stdafx.h"
5 #include "iostream"
6 using namespace std; //(全局作用域)名字空间作用域
7
8
9 namespace NewSpace
10 {
11 int iValue = 10; // (括号内)名字空间作用域
12 }
13
14 int iValue = 20; // 全局作用域
15
16 class CTest
17 {
18 public:
19 void FunTest(int iValue)
20 {
21 iValue = iValue; //(函数)局部作用域
22 }
23
24 void Print()
25 {
26 cout << iValue << endl;
27 }
28
29
30 private:
31 int iValue; // (类作用域)
32
33 };
34
35
36
37 int main()
38 {
39 CTest test;
40 test.FunTest(30); // 类作用域中函数局部作用域值改为30
41 cout << iValue << endl; // 输出全局变量作用域值 20
42 cout << NewSpace::iValue << endl; // 输出名字作用域 10
43 test.Print(); // 输出类作用域定义的变量值,但未被初始化
44 system("pause");
45 return 0;
46 }

三、类的实例化

举一个例子我们来体会一下类的实例化的过程:

类–>实例化–>对象
图纸–>建造–>别墅

类是抽象的,只是限定了类中有哪些成员,定义了类并没有分配实际的内存来存储它。

CDate d; // 类实例化
//用类类型创建对象的过程
//一个类可以实例化出多个对象,实例化123

四、类对象模型

类中的成员和对象在类中布局格式:

说明:
  (1)可以用sizeof来求一个非空类的大小;
  (2)空类的大小为一个字节,但是如果该空类变成非空类,例如类中有一个int,则该类的大小计算时,不在计算原来的1。

五、this指针

特性
(1)this指针的类型:类类型* const。
(2)this指针并不是对象本身的一部分,不影响sizeof的结果。
(3)this是一个指针,它时时刻刻指向对象的实例。
(4)this指针的作用域在类成员函数的内部(不严谨)。
(5)this指针是类成员函数的第一个默认隐含参数,编译器自动维护传递。
(6)只有类的非静态成员函数中才可以使用this指针,其它成员函数都不可以。
_thiscall调用约定
(1)_thiscall只能够在类的成员函数上;
(2)参数从右向左压栈;
(3)如果参数个数确定,this指针通过ecx传给被调用者。如果参数不确定,this指针在所有参数被压栈后压入堆栈;
(4)参数个数不定的,调用者清理堆栈,否则函数自己清理堆栈。
this指针是否为空?

void test(person* const this)
{}

void change(person* const this)
{
    cout<<name<<endl;
}1234567

当将空的this传给test函数时,test没有调用任何函数,因此执行此函数,没有报错;
当把空的this传给change函数时,通过this指针调用this->name,由于this是空的,因此会出错。

总结:
  (1)NULL对象指针可以调用成员函数;
  (2)通过对象调用成员函数,对象的指针会被传入函数中,指针名称为this;
  (3)NULL对象指针调用成员函数时,只要不访问此对象的成员变量,则程序正常运行;
  (4)NULL对象指针调用成员函数时,一旦访问此对象的成员变量,则程序崩溃。

六、编译器如何来识别一个类?

C++之封装     (这篇博文对构造函数有很好的解释)

 

希望暴露public

希望隐藏private

对象实例化有两种方式,从栈实例化,从堆(new出来的)实例化。

以谁做什么作为核心。

public 放前面,private放后面(属性可以定义为private格式)。

只读属性,只有get方法,没有set方法。

#include <iostream>
#include <string>
using namespace std; /**
* 定义类:Student
* 数据成员:m_strName
* 数据成员的封装函数:setName()、getName()
*/
class Student
{
public:
// 定义数据成员封装函数setName()
void setName(string name) {
m_strName = name;
} // 定义数据成员封装函数getName()
string getName() {
return m_strName;
} //定义Student类私有数据成员m_strName
private:
string m_strName; }; int main()
{
// 使用new关键字,实例化对象
Student *str = new Student;
// 设置对象的数据成员
str->setName("cpp");
// 使用cout打印对象str的数据成员
cout << str->getName() << endl;
// 将对象str的内存释放,并将其置空
delete str;
str = NULL;
return 0;
}

栈区,存储变量。

new分配的内存,是堆区。

全局区,存储全局变量和静态变量。

常量区,存储常量。

代码区,存储代码。

对象需要初始化,有的只有一次,有的需要初始化多次。

构造函数,会在对象实例化时被调用。

读书,视频,先看思想,读其骨架。细节次之。

都有默认值的构造函数,称为默认构造函数。

一个类可以没有默认构造函数,有别的构造函数也可以实例化对象。

可以全屏观看,看到关键点可以暂停,记录一下。因为屏幕太小,看着眼疼。或者全屏观看的时候,把文本置顶。

C++中,构造函数与类名相同,析构函数前面加一个波浪线。析构函数,可以进行资源释放。

tips:class 声明类,要小写的c。构造函数,析构函数前面,不需要任何修饰。class结尾还需要分号;

#include <iostream>
#include <string>
using namespace std;
/**
* 定义类:Student
* 数据成员:m_strName
* 无参构造函数:Student()
* 有参构造函数:Student(string _name)
* 拷贝构造函数:Student(const Student& stu)
* 析构函数:~Student()
* 数据成员函数:setName(string _name)、getName()
*/
class Student
{
public:
Student() {
m_strName = "jack";
cout<<"Student()"<<endl;
}
Student(string _name) {
m_strName = _name;
cout<<"Student(string _name)"<<endl;
}
Student(const Student& stu) {
cout<<"Student(const Student& stu)"<<endl;
}
~Student() {
cout<<"~Student()"<<endl;
}
void setName(string _name) {
m_strName = _name;
}
string getName() {
return m_strName;
}
private:
string m_strName;
}; int main(void)
{
// 通过new方式实例化对象*stu
Student *stu = new Student("小李");
// 更改对象的数据成员为“慕课网”
stu->setName("慕课网");
// 打印对象的数据成员
cout<<stu->getName()<<endl;
delete stu;
stu = NULL;
return 0;
}
#include <iostream>
#include <string>
using namespace std;
/**
* 定义类:Student
* 数据成员:m_strName
* 无参构造函数:Student()
* 有参构造函数:Student(string _name)
* 拷贝构造函数:Student(const Student& stu)
* 析构函数:~Student()
* 数据成员函数:setName(string _name)、getName()
*/
class Student
{
public:
Student() {
m_strName = "jack";
cout<<"Student()"<<endl;
}
Student(string _name) {
m_strName = _name;
cout<<"Student(string _name)"<<endl;
}
Student(const Student &stu) {
cout<<"Student(const Student &stu)"<<endl;
}
~Student() {
cout<<"~Student()"<<endl;
}
void setName(string _name) {
m_strName = _name;
}
string getName() {
return m_strName;
}
private:
string m_strName;
}; int main(void)
{
// 通过new方式实例化对象*stu
Student stu;
Student stu2 = stu;
// 更改对象的数据成员为“慕课网”
stu.setName("慕课网");
// 打印对象的数据成员
cout<<stu.getName()<<endl;
return 0;
}
Student()
Student(const Student &stu)
慕课网
~Student()
~Student()

(下面这篇博文用struct结构体c的方式封装)

在C语言中可以使用struct(结构体)将相应的数据封装起来,统一使用,同样地在C++中也可以使用struct将相应的属性及方法封装起来,例如下面的程序:

#include <iostream>
using namespace std;
struct S{
int e1, e2;
}; void f(const S& s){ //以引用的方式传递地址
cout << s.e1 << " " << s.e2 << " ";
} int main(){
S s = {1, 2}; //定义一个S类,并赋值
f(s); //通过向函数f传递s的地址,以输出s中的两个属性
S* z = &s; //定义一个指向s的指针z
z->e1 = 3; //通过指针间接改变s中属性e1的值,注意,针对指针必须使用“->”符号,用来调用结构体中的成员
(*z).e2 = 4; //通过指针指向的地址直接改变s中的属性e2的值,这里则要用"."调用,因为此处是*z
f(s); //再次输出
}

输出结果如下:

上面的程序中main函数中的第一行,初始化(赋值)一个结构体,在C++中称之为构建对象。在上面一个程序中,已经将对象s的所有属性(e1,e2)都放在了同一结构体(S)中,这相当于将所有具有同一性质的物质合并在了一起。例如下面的程序:

#include <cmath>  //在C++中有关复数和根运算的函数都在此头文件中
#include <iostream>
#include <iomanip> //在C++中有关小数点精确的函数都在此头文件中
using namespace std; struct Complex{
double re;
double im; //结构体的属性为实部和虚部——对象为复数
Complex mul(const Complex& z); double amount(){ //该方法用于计算对象复数的模
cout << "amount() "; //用于确认该函数确实被调用了
return sqrt(re * re + im * im); //计算模的方法,sqrt为求平方根
}
}; int main(){
Complex a = {0, 0};
bool valueChange = true;
for(int i = 1; i <= 8; i++){
double amount = a.amount(); //每循环一次就计算一次模,但是这一值确实每两次才改变一次,因为后面有if控制改变的条件
cout << "|a| = " << showpoint << setprecision(2) << amount << " "; //输出模
valueChange = !valueChange;
if(valueChange){
a.re = i;
a.im = -i;
cout << endl;
}
}
}

输出结果:

在上面的程序中,结构体s中的属性re,im即可以在其本身的方法中被读取使用,也可以在main函数中被直接修改,这样结构体中的数据可被任何一个函数调用或者修改,因此就会造成数据泄露,为了阻止这一情况的产生,C++提供了一些关键词用来保护数据的访问权限:

  1. private(私有):在这一关键词之后的数据(属性或者方法)均为对象私有的,只能在结构体内部进行修改或访问,其他函数或结构体没有访问权限。
  2. public(公共):在这一关键词之后的数据(属性或方法)均为公共的,在结构体外的任何函数都可对其访问或修改
  3. protected(保护):在这一关键词之后的数据均为受保护的类型,只有在该类或由该类派生出来的类中才有访问权限。例如:
#include <iostream>
#include <cmath>
#include <iomanip>
using namespace std; struct Complex{
private: //关键词private修饰,表示下面的re和im为结构体Complex的私有成员,只能在结构体中被访问或修改
double re;
double im;
public: //关键词private修饰,表示下面的方法(value,amount,getReal,getImag)为公共成员,不仅能在能在结构体中被访问或修改,也可在main函数中被调用
void value(double r, double i){
re = r;
im = i;
}
double amount(){
return sqrt(re * re + im * im);
}
double getReal(){
return re;
}
double getImag(){
return im;
}
}; int main(){
Complex z;
z.re = 3; //产生编译错误
z.im = 4; //产生编译错误,此处re和im为结构体Complex的私有成员,在main函数中无访问和修改权限
z.value(3, 4); //此处合法,方法value在结构体Complex,其拥有访问和修改im和re的权限,而value被关键词public所修饰,在main函数中可以访问或调用
cout << z.re << " + " << z.im << "j"; //产生编译错误
cout << z.getReal() << " + " << z.getImag() << "j"; //合法
cout << endl;
cout << "the amount of this complex number is: " << sqrt(z.re * z.re + z.im * z.im); //产生编译错误
cout << "the amount of this complex number is: " << z.amount(); //合法
}

在C++中如果没有像上述程序中所示的声明,则结构体中所有的属性和方法均视为public。与此相对,C++还提供了另外一个关键词class(类),在class中,所有的属性和方法,如果没有特别声明,则均视为private。如下面程序:

struct S{
int i;
}; class C{
int j;
} int main(){
S s;
s.i = 4; //合法,结构体中的属性及方法默认为public
C c;
c.j = 4; //产生编译错误,类中的属性及方法默认为private
}

如果要将类中的属性及方法定义成public,则需要先声明。一般地,在一个类中,属性为private型,而访问,调用或修改其的方法为public类。这样在使用数据的同时也做到了对数据的保护。

温故知新——C++--封装的更多相关文章

  1. 深入js的面向对象学习篇(封装是一门技术和艺术)——温故知新(二)

    下面全面介绍封装和信息隐藏. 通过将一个方法或属性声明为私用的,可以让对象的实现细节对其它对象保密以降低对象之间的耦合程度,可以保持数据的完整性并对其修改方式加以约束.在代码有许多人参与设计的情况下, ...

  2. 温故知新----封装(struct)

    上次提到class是最常见的封装,今天发现别人开发的SDK里面有大量的结构体struct 转载: 1. https://blog.csdn.net/a_forever_dream/article/de ...

  3. 【温故知新】c#事件event

    从上一篇文章[温故知新]C#委托delegate可知,委托delegate和事件Event非常的相似,区别就是event关键字,给delegate穿上了个“马甲”. 让我们来看官方定义: 类或对象可以 ...

  4. 【温故知新】C#委托delegate

    在c#的学习过程中,学到委托与事件总会迷糊一段时间,迷糊过后自然而就似懂非懂了~,所以最近我打算把以前所学的迷糊过的知识总结,温故知新,总结记录下来. 首先,我们来看一下msdn对委托的定义: del ...

  5. 00-Unit_Common综述-RecyclerView封装

    自学安卓也有一年的时间了,与代码相伴的日子里,苦乐共存.能坚持到现在确实已见到了"往日所未曾见证的风采".今2018年4月2日,决定用一个案例:Unit_Common,把安卓基础的 ...

  6. [C#] 简单的 Helper 封装 -- RegularExpressionHelper

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  7. iOS开发之App间账号共享与SDK封装

    上篇博客<iOS逆向工程之KeyChain与Snoop-it>中已经提到了,App间的数据共享可以使用KeyChian来实现.本篇博客就实战一下呢.开门见山,本篇博客会封装一个登录用的SD ...

  8. Ajax实现原理,代码封装

    都知道实现页面的异步操作需要使用Ajax,那么Ajax到是怎么实现异步操作的呢? 首先需要认识一个对象 --> XMLHttpRequest 对象 --> Ajax的核心.它有许多的属性和 ...

  9. 用C语言封装OC对象(耐心阅读,非常重要)

    用C语言封装OC对象(耐心阅读,非常重要) 本文的主要内容来自这里 前言 做iOS开发的朋友,对OC肯定非常了解,那么大家有没有想过OC中NSInteger,NSObject,NSString这些对象 ...

随机推荐

  1. Labview学习之路(十一)日常编程技巧

    此文章用于记录自己在学习Labview过程中所用到的编程技巧,会一直更新下去. (一)移动控件 直接鼠标拖动. 按住shift键,鼠标移动,可以水平和竖直移动(取决于鼠标最开始的移动方向). 使用键盘 ...

  2. Transform与Vector3 的API

    Transform.InverseTransformDirection(Vector3 direction) Vector3.ProjectOnPlane(Vector3 vector, Vector ...

  3. Ocelot+Consul实现微服务架构

    API网关 API 网关一般放到微服务的最前端,并且要让API 网关变成由应用所发起的每个请求的入口.这样就可以明显的简化客户端实现和微服务应用程序之间的沟通方式.以前的话,客户端不得不去请求微服务A ...

  4. 跟着兄弟连系统学习Linux-【day09】

    day10-20200609 p29.软件包管理-rpm命令管理-安装升级与卸载 [rpm -ivh 包全名]安装 -i(安装) -v (显示详细信息)-h (显示安装进度) 会一步步依赖,比较麻烦, ...

  5. BIO应用-RPC框架

    为什么要有RPC?  我们最开始开发的时候,一个应用一台机器,将所有功能都写在一起,比如说比较常见的电商场景. 随着我们业务的发展,我们需要提示性能了,我们会怎么做?将不同的业务功能放到线程里来实现异 ...

  6. yum wget rpm

    wget 类似于迅雷,是一种下载工具          下载安装包 yum: 是redhat, centos 系统下的软件安装方式 下载安装包并自动安装 以及一些依赖关系 基于RPM包管理,能够从指定 ...

  7. Promise核心实现

    核心 构造函数核心 维护状态变量,只能由pending变为resolve或者reject 维护一个存储结果的变量 维护一个回调数组,执行到then,如果我们传入的立即执行函数没有立即执行resolve ...

  8. IT人35岁危机:到底是因为爱还是责任?

    互联网蚕食世界,未来属于IT人. 这是属于互联网的时代,每个人都是网络的弄潮儿,由于网络越来越被需要,互联网IT行业被推上了较高的位置,这也导致IT行业的就业环境火热,越来越多的人被IT行业广阔的发展 ...

  9. 修改mysql、sqlserver数据库默认用户,不允许为root、sa等

    1.mysql cmd进入dos命令,输入mysql -u root -P 1202 -h localhost -p敲回车输入密码 use mysql; 修改用户名root为其他用户 update u ...

  10. Linux常用的三种软件安装方式

    一:Linux源码安装    1.解压源码包文件    源码包通常会使用tar工具归档然后使用gunzip或bzip2进行压缩,后缀格式会分别为.tar.gz与.tar.bz2,分别的解压方式:   ...