> 技术文档 > C++(面向对象之封装)

C++(面向对象之封装)


前言

封装本身是围绕着类进行的:在类中写各种各样的函数

通过 类 给对象封装各种各样的函数

一、类

在C++中创建类的函数有两个,分别是struct class

struct 和 class 创建的叫,使用 类 定义的变量叫做 对象,类中的变量叫做 成员变量

1、类 中对象的访问权限

piblic:公开权限                                在类 内部,外部均可访问

protected:受保护权限                      在类 内部可以访问,外部不可访问

private:私有权限                              在类 内部可以访问,外部不可访问

目前来说 private 和 protected 这两个权限没有区别

private 和 public 具体使用

根据 c++ 标准委员会的要求(人为规定):没有特殊需求的时候,
所有的成员属性(变量)都放在private里面,所有成员方法(函数) ,都放在public里面

2、内部访问和外部访问

外部访问:
        既不在类的作用域中访问成员属性,并且在访问的时候,前面也没有加上 作用域:: 运算符,都属于外部访问

内部访问:
        和外部访问相反,只要在类的作用域里面,或者访问的时候,前面有 作用域::运算符,都属于内部访问

3、struct 和 class 的区别

创建类:

struct 创建的类,中的参数,默认是piblic(公开权限)

class  创建的类,中的参数,默认是private(私有权限)

初始化类:

class 不允许直接使用 {} 形式进行初始化 (如果想要使用{} 初始化,需要额外操作)

4、类 的初始化方式

类 的初始化要通过 构造函数 初始化

类 中的 静态成员无法通过构造函数初始化,构造函数中只能自动初始化栈区手动初始化堆区

二、构造函数

构造函数是中和类同名的函数,如果类中没有构造函数,编译器会自动添加无意义的构造函数

无论是创建 class对象,还是创建 struct对象,只要是定义类的对象都会自动调用类内的构造函数

1、构造函数的特点

1)构造函数没有返回值类型

这里的没有返回值类型不是指void,而是实实在在的没有返回值类型、

 2)函数名必须和类名一致

3)自动添加空的析构函数

如果类中没有写构造函数编译器会自动补全一个内容为空的、无参无返的 构造函数

4)构造函数允许出现多个(形参类型不完全一致)

形参类型不完全一样:参数、数量、排列组合的顺序,只要有一个不一样

当有多个版本的构造函数的时候,到底调用的是哪个版本的构造函数,取决于调用构造函数的时候,传递了什么排列组合的实参

2、构造函数的调用方式

1)自动调用

格式: 类名 对象(参数1,参数2···) 

2)手动调用

格式: 类名 对象=构造函数名(参数1,参数2···) 

3)隐式调用(两种)

格式: 类名 对象(单个参数)注意:如果填写了多个参数,由于逗号运算符,只获取最后一个变量的值 
格式: 类名 对象={参数1,参数2···} 

3、隐式调用

上述的方法中有两种被称为隐式调用,隐式调用就是我们无法直接看出调用了类的构造函数

可以通过在函数的声明前添加关键字 explicit 用于禁止隐式调用

示例: 

隐式调用

隐式调用赋值

4、初始化列表

构造函数分为两个部分:内核部分和用户部分

        用户部分:{}内的部分,负责执行用户自己编写的代码
        内核部分:()到{}中间的部分,申请该对象所需要的所有栈空间

这部分区域,用户是可以人为干涉的

格式:struct 类名:{private: 成员变量;public: 类名(形参);}类名::类名(): 变量名1(初始值),变量名2(初始值),...,对象1(构造函数参数),对象2(构造函数参数),......{ 函数功能;}

三、对外接口

对外接口:专门用来访问私有成员公开函数就是对外接口

这种形式的好处在于,开发者对私有成员拥有着绝对的控制权

        如果开发者希望使用者能够访问某个私有成员,那么直接提供私有成员的接口即可

关于接口访问私有成员有两个访问形式                           读访问  写访问

所以我们也需要两个接口进行这两种访问         get接口(读访问) set接口(写访问)

四、C++中const关键字

在C++中,不允许普通指针指向常量指针,在C++中常量指针只能被常量指针指向

const的作用:

可以使不希望被修改的变量,作为常量,无法被修改,
C++中不允许普通指针指向常量指针的好处是,可以防止常量被间接访问后修改

封装接口练习:

封装一个mystring类,class mystring{private: //char buf[256]; char* buf;// 指向一个堆空间,该堆空间用来保存外部传入的字符串 int len; // 记录 buf里面保存的字符串有多少public: };要求能够实现以下功能int main(){ mystring str = \"hello\"; // 这段代码要求一定为mystring类写一个可隐式调用的单参构造函数 mystring ptr = \"world\"; // 在写单参构造函数的时候,私有成员len使用列表初始化的形式初始化 str.copy(ptr); str.copy(\"你好\"); str.append(ptr) str.append(\"你好\") str.compare(ptr) str.compare(\"你好\") str.show(); // 最后再写一个 show 函数,用来在终端输出 str 里面保存的字符串 cout << str.at(0) << endl;// 再写一个 at 函数,用来输出 str中第0个字符}

五、C++中的this指针

this指针是什么:

this指针是指针函数所有对象调用成员函数的时候,成员函数的形参中永远存在this指针

        当对象调用成员函数时,编译器会默认,在函数调用传参的最左侧,多传入一个实参,
该实参就是,调用成员函数的对象地址,也就是this指针。在声明部分也是同理。

当函数内部,访问成员变量时,本质上都是通过this指针进行访问的,只是平时this指针可以省略

如何修饰this指针:

可以在列表初始化最前端,写入关键字,就是修饰this指针,

例如:

原本this指针类型为 : XXX* const this想要变成  : const XXX* const this 只需要在函数声明的() 后面,直接加上const 即可void Stu::show()const{ }这里唯一的const 就是用来加在 this 指针前面的

六、析构函数

析构函数:是当对象生命周期结束时,自动调用的函数
        构造函数在创建对象时自动调用;析构函数在销毁对象时自动调用

1、析构函数的特点

1)析构函数没有返回值类型

析构函数和构造函数一样,是实实在在的没有返回值类型

2)函数名必须和类名一致

析构函数的函数名必须和类名一致,但是也为了区分析构函数和构造函数,也表示析构函数和构造函数功能相反,需要在析构函数名前加上( ~ )

3)自动添加空的析构函数

当我们没有写析构函数的时候,编译器会自动补全一个什么都不干的析构函数

4)手动调用析构函数

析构函数可以手动调用,手动调用的作用是,可以在程序异常中断的时候,手动释放资源

例如程序异常中断,我们可以捕获异常中断信号,手动调用析构函数释放资源

2、析构函数的作用

构造函数的作用是:申请成员变量资源并初始化,相对的析构函数的作用就是释放成员变量资源

这里析构函数所谓的释放资源,主要针对  堆区、打开的文件、互斥锁、信号灯集、共享空间  等等这些无法自动释放的资源,这时候就需要手动释放。

七、引用(新的数据类型)

引用时C++中出现的一种新的数据类型,其功能和地位和C语言中的指针类似

1、引用的写法

int a = 10;int& pa = a;所以引用的语法就是: 数据类型& 引用变量名 = 被引用的数据

2、引用的作用

引用成功后,引用的变量,就是被引用的变量本身,或者说是被引用的变量的别名(参考浅拷贝)

int a = 10;int& pa = a;pa 即是 a,a 即是 pa

3、引用的特点

1)普通引用和常量引用

        变量,可以使用 普通引用 或 常量引用 进行绑定
        常量,必须使用 常量引用 进行绑定    

        普通引用 不可以绑定 常量引用

2)引用只有在初始化时,可以绑定对象

        所以引用必须初始化,初始化之后的所有 等号 都是被绑定的数据赋值,而不是更换绑定

4、引用和指针的区别

一个普通指针,既可以指向常量,也可以指向变量
一个常量指针,同样的,既可以指向常量,也可以指向变量
一个普通引用,只能绑定变量,不能绑定常量
一个常量引用,既可以绑定变量,又可以绑定常量

所有引用都不允许更改换绑定,但是指针不同:除了指针常量以外,所有指针都可以更换指向

引用必须初始化,指针的初始化不是必须的

引用是变量的别名,变量本身,指针是一个独立的变量,该变量里面存放了目标的地址

&引用 == &变量&指针 != &变量sizeof(引用) == sizeof(变量) == 变量本身大小sizeof(指针) == 4 或者 8想要改变变量本身的值,直接改变引用的值就可以了但是指针不行,必须先对指针取值(也叫 解引用) 才能改变变量的值

5、数组和函数的引用

在语法格式上,数组和函数的引用 与 数组和函数的指针十分相似int func(int a,char b,double c){cout << \"func\" << endl;}void func3(int(& arr)[5]){}int(& q)(int,char,double) = func;q(1,\'x\',3.14); int arr[5] = {1,2,3,4,5};int(& p4)[5] = arr;数组引用很少用,因为设计函数的时候,如果形参是一个数组的引用,那么就必须明确数组的容量是多少,这就导致该形参只能接收相同容量,相同类型的数组,其他类型的都传不进。

6、使用引用要注意的规则

传参的时候,必须传引用
        如果函数内部不会涉及形参的改变,直接传const &
        如果函数内部会涉及形参的改变,就只能传引用

返回值的时候:返回值类型能够引用就引用,不能引用就返回普通对象
        一般来说,返回值是一个局部对象的时候,返回值类型不允许是 &
        其他时候,返回值类型都可是 &

八、拷贝构造函数

拷贝构造函数就是构造函数多个版本中的一个版本

注意:我们需要保证尽可能的防止拷贝构造函数的发生(遵循引用的使用规则)

构造函数的作用是:
提供一个外部数据(不提供也行), 来构建一个新的对象,并且以外部数据为依据初始化新的对象

拷贝构造函数的作用是:
提供一个已经存在的对象,来构建一个新的对象,并且以这个已经存在的对象为原型,初始化一个一模一样的新的对象。

1、拷贝构造函数的特点

1)构造函数所具有的特点,拷贝构造函数都有

2)自动补全拷贝构造函数

如果没有写拷贝构造函数,编译器就自动补全一个拷贝构造函数,该拷贝函数天生拥有拷贝能力

3)编译器优化

由于拷贝构造函数的特殊性,在特殊情况下,执行拷贝构造函数的步骤会被优化掉

2、拷贝构造函数的调用时机

  1. 当使用一个已经存在的对象去构建一个新的对象时
  2. 当函数的参数是一个对象时
  3. 当函数的返回值时一个对象时

3、编译器默认补全的拷贝构造函数

编译器自动补全的拷贝构造函数是浅拷贝,浅拷贝下两个对象的指针变量是指向同一块地址空间的

// 编译器默认补全的拷贝构造函数,源代码如下Stu(const Stu& r){ memcpy(this,&r,sizeof(r)); // 相当于 // math = r.math // chinese = r.chinese; // eng = r.eng; // XXX = r.XXX }

4、浅拷贝

浅拷贝是将对象的成员变量进行等号( = )赋值,复制普通变量中的值,复制指针变量中的地址,
到新构造的对象中,这也意味着拷贝的对象和被拷贝的对象中的指针变量是指向同一块地址的。

5、深拷贝

深拷贝是将对象的成员变量进行等号( = )赋值,复制普通变量中的值,而指针变量则申请一个新的空间(堆空间)拷贝指针指向的值,并将该指针指向新的地址。

6、分析拷贝构造的执行次数 

class Stu{private:public: Stu(){ cout << \"一般构造\" << endl; } Stu(const Stu& r){ cout << \"拷贝构造\" << endl; }};Stu func(Stu x){ Stu y(x); Stu z = y; return z;}int main(int argc,const char** argv){ Stu a; Stu b = func(func(a)); return 0;}

九、C++的static关键字

1、static的作用

将一个变量,声明在静态存储区中,并且标记其作用域注意: 静态存储区中的变量,要遵循静态存储区的规则, 静态存储区有一个非常重要的规则: 当静态存储区相同作用域中,声明同名变量的时候,并不会报错,也不会重复声明,而是直接引用上次声明的那个变量

2、static的使用方法

注意:如果在类中定义静态成员,该静态成员无法通过构造函数初始化,必须在外部手动初始化

1)修饰类中的成员属性(成员变量)

效果:同一个类中的静态成员属性,对于该类的所有对象,之间是相互共享的。

class Bank{private: static double rate;// 全局::Bank::public:};// 全局::Bank:: 注意这里是声明并初始化,不是访问,所以无关 private 还是 public 的问题double Bank::rate = 0;

2)类中的成员方法(成员函数)

静态成员方法的特点:

  •  静态成员方法也是属于类的方法,可以通过   类名::函数名   直接调用
  •  静态成员方法虽然也是类的方法,但是形参最左侧没有this指针

解析:
这种调用时通过类名直接调用的,而不是对象名,既然没有对象参与,那自然也没有传入对象的地址给this指针,

十、C++中申请堆空间

1、new(申请堆空间)

2、new函数的使用方式(示例)

3、delete(释放对空间)

4、delete函数的使用方式(示例)

十一、单例类,单例模式

1、单例类是什么

单例类是指只能创造唯一的对象

2、懒汉单例类的创造方法

懒汉单例是天生多线程不安全

1)将构造函数私有化

2)写一个构造函数的公开接口

3)将公开接口函数设置为静态成员函数

4)写判断单例的逻辑

到此为止,在单线程下就已经是单例类了,因为懒汉单例是天生多线程不安全,所有要加上互斥锁

5)创建互斥锁

6)示例


3、创建饿汉单例的方法

十二、C++中的函数

1、函数重载

在C++中可以存在多个同名函数

1)完全匹配

2)提升匹配

3)多重匹配

4)函数重载的实现逻辑

2、默认参数

1)设置默认参数的条件

2)默认参数和重载函数同时出现导致的问题

3)如何解决默认参数和重载函数同时出现导致的问题

3、内联函数

1)内联函数的特点:

拥有宏函数不会发送控制转移的优点,以及函数返回逻辑运算结构的优点

2)内联函数的作用

3)内联函数的声明条件

4、友元函数

1)什么是友元函数

  • 友元函数一定是在类外部的全局函数
  • 友元函数一定是与某些类进行绑定的
  • 友元函数可以直接访问与其绑定的类,内部的私有成员

2)友元函数的作用

外部函数是不能访问类的私有成员的,
在要访问的类中将外部函数声明成友元函数,就可以访问内部私有成员

3)如何声明友元函数

4)友元类

5、ope(运算符重载函数)

6、(匿名函数)