温故知新——C++--封装 C++之封装

参考:

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类。这样在使用数据的同时也做到了对数据的保护。

希望暴露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类。这样在使用数据的同时也做到了对数据的保护。

猜你喜欢

转载自www.cnblogs.com/MCSFX/p/12677498.html