C++ 类

类中成员变量叫做属性,类中成员函数叫做方法。

在C++中,通过定义一个类来定义数据结构。一个类定义了一个类型,以及与其关联的一组操作。

对象的概念类似于C语言中的结构体变量,而类类似于结构体。

定义类

定义一个类,本质上是定义一个数据类型的蓝图。这实际上并没有定义任何数据,但它定义了类的名称意味着什么,也就是说,它定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作。

类定义是以关键字 class 开头,后跟类的名称。类的主体是包含在一对花括号中。类定义后必须跟着一个分号或一个声明列表。

对象

定义C++对象

类提供了对象的蓝图,所以基本上,对象是根据类来创建的。声明类的对象,就像声明基本类型的变量一样。

一般格式

类名 对象名;//对象都有他们各自的数据成员

面向对象三大特点

封装、继承、多态

封装和访问控制

struct

当单一变量无法完成描述需求的时候,结构体类型解决了这一问题。可以将多个类型打包成一体,形成新的类型。C语言中封装的概念

对C语言中结构体的操作,都是通过外部函数来实现的。

封装的访问属性

struct 中所有行为和属性都是public的(默认)。C++中的 class 可以指定行为和属性的访问方式。

封装,可以达到,对内开放数据,对外屏蔽数据,对外提供接口。达到了信息隐蔽的功能。

比如我们用 struct 封装的类,即知其接口,又可以直接访问其内部数据,这样却没有达到信息隐蔽的功效。而 class 则提供了这样的功能,屏蔽内部数据,对外开放接口。

封装的2层含义:把属性和方法进行封装,对属性和方法进行访问控制

struct和class关键字的区别

在用 struct 定义类时,所有成员默认属性为 public

在用 class 定义类时,所有成员的默认属性为 private

对象数组

数组有几个元素就调用几次构造函数,调用的构造函数可以是不同的构造函数【构造函数函数重载】。

定义

类名 数组名[n];

类名 数组名[n]={类名(传参),类名(传参),……};//采用无名对象的方法

调用

数组名[i].成员变量;

数组名[i].成员函数;

访问数据成员

类的对象的公共数据成员可以使用直接成员访问运算符 . 来访问。

类成员的访问权限

C++通过 public、protected、private 三个关键字来控制成员变量和成员函数的访问权限,它们分别表示公有的、受保护的、私有的,被称为成员访问限定符。所谓访问权限,就是类外面的代码访问该类中成员权限。

在类的内部,即类的成员函数中,无论成员被声明为 public、protected 还是private,都是可以互相访问的,没有访问权限的限制。

在类的外部(定义类的代码之外),通过对象只能访问 public 的成员,不能访问 private、protected 属性的成员。

private 后面的成员都是私有的,直到有 public 出现才会变成共有的;public 之后再无其他限定符,所以 public 后面的成员都是共有的。

private 关键字的作用在于更好地隐藏类的内部实现,该向外暴露的接口(能通过对象访问的成员)都声明为public,不希望外部知道、或者只在类内部使用的、或者对外部没有影响的成员,都建议声明为 private。

声明为 private 的成员和声明为 public 的成员的次序任意,既可以先出现 private 部分,也可以先出现 public 部分。如果既不写 private 也不写 public,就默认为 private。

在一个类体中,private 和 public 可以分别出现多次。每个部分的有效范围到出现另一个访问限定符或类体结束时(最后一个右花括号)为止。

可能会说,将成员变量全部设置为 public 省事,确实,这样做 99.9%的情况下都不是一种错误,我也不认为这样做有什么不妥;但是,将成员变量设置为 private 是一种软件设计规范,尤其是在大中型项目中,还是尽量遵守这一原则。

成员和类的默认访问修饰符是 private。

公有成员 public

公有成员在程序中类的外部是可访问的。可以不使用任何成员函数来设置和获取公有变量的值。

私有成员 private

私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。

默认情况下,类的所有成员都是私有的。

实际操作中,我们一般会在私有区域定义数据,在公有区域定义相关的函数,以便在类的外部也可以调用这些函数。

受保护成员 protected

受保护成员变量或函数与私有成员十分相似,但有一点不同,protected(受保护)成员在派生类(即子类)中是可访问的。

继承中的特点

有public, protected, private三种继承方式,它们相应地改变了基类成员的访问属性。

  • 1.public 继承:基类 public 成员,protected 成员的访问属性在派生类中分别变成:public, protected,
  • 2.protected 继承:基类 public 成员,protected 成员的访问属性在派生类中分别变成:protected, protected
  • 3.private 继承:public以及protected成员会变为private成员 private成员不可继承
  • private 成员在继承中,无论继承的权限是什么都无法继承给派生类

但无论哪种继承方式,上面两点都没有改变:

  • 1.private 成员只能被本类成员(类内)和友元访问,不能被派生类访问;
  • 2.protected 成员可以被派生类访问。

成员变量

命名

成员变量大都以m_开头,这是约定成俗的写法,不是语法规定的内容。以m_开头既可以一眼看出这是成员变量,又可以和成员函数中的参数名字区分开。

成员函数EnBuffer的函数体如下:

// 启、禁用缓冲区

void CFile::EnBuffer(bool bEnBuffer)

{

m_bEnBuffer=bEnBuffer;

}

const 常成员变量

值不能修改

初始化

声明时直接初始化

相当于给所有的对象都赋同一个值

构造函数使用初始化列表

只能使用初始化列表对其初始化,而不能在函数体内初始化

全局函数与成员函数

1、把全局函数转化成成员函数,通过this指针隐藏左操作数
Test add(Test &t1, Test &t2)===》Test add(Test &t2)
2、把成员函数转换成全局函数,多了一个参数
void printAB()===》void printAB(Test *pthis)
3、函数返回元素和返回引用

#include<iostream>

using namespace std;

class Test {

public:

Test(int a, int b) {

this->a = a;

this->b = b;

}

void printT() {

cout << "a=" << this->a << ",b=" << this->b << endl;

}

int getA() {

return this->a;

}

int getB() {

return this->b;

}

/*BBB 成员函数 相加

Test TestAdd(Test& another) {

Test temp(this->a + another.a, this->b + another.b);

return temp;

}

BBB*/

/*CCC 成员函数 +=

void TestAdd2(Test& another) {

this->a += another.a;

this->b += another.b;

}

CCC*/

//连 +=

Test& TestAdd3(Test& another) {

this->a += another.a;

this->b += another.b;

return *this; //如果想返回一个对象本身,在成员函数中,用 *this 返回

}

private:

int a;

int b;

};

/*AAA 全局函数

Test TestAdd(Test& t1, Test& t2) {

Test temp(t1.getA() + t2.getA(), t1.getB() + t2.getB());

return temp;

}

AAA*/

int main() {

Test t1(10, 20);

Test t2(100, 200);

/*AAA

Test t3 = TestAdd(t1, t2);

t3.printT();

AAA*/

/*BBB

Test t3 = t1.TestAdd(t2);

t3.printT();

BBB*/

/*CCC

t1.TestAdd2(t2);

t1.printT();

CCC*/

t1.TestAdd3(t2).TestAdd3(t2);//t1+=t2+=t2

//如果想对一个对象连续调用成员函数,每次都会改变对象本身,成员函数需要返回引用

t1.printT();

return 0;

}

成员函数

实现

一、所有内容放入类中
二、函数声明放入类中,函数整个放在类外

常成员函数

函数体内不能修改变量的值,只能读取数据

只能访问常函数

语法

返回值类型 函数名(参数列表)const{函数体;}

构造函数

class 类名{

类名(形参){

构造体

}

}

例如:

class A{

A(形参){

......

}

}

声明一个构造函数时,默认构造函数消失,默认拷贝构造函数存在;

声明一个拷贝构造函数时,默认构造函数和默认拷贝构造函数均消失。

类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。

在CFile类的声明中,有一些特殊的成员函数CFile(),它就是构造函数(constructor)。

CFile(); // 类的构造函数

CFile(bool bEnBuffer); // 类的构造函数

构造函数的名字和类名相同,没有返回值,不能被显式的调用,而是在创建对象时自动执行。

功能

1.创建对象时,对对象分配内存

2.为数据成员进行初始化

调用——构造函数是对象初始化的时候调用

自动调用

一般情况下C++编译器会自动调用构造函数

手动调用

在一些情况下则需要手工调用构造函数

规则

1.在对象创建时自动调用,完成初始化相关工作

2.无返回值,与类同名,默认参数,可以重载,可默认参数

3.一经实现,默认不复存在

特点:

1)构造函数必须是 public 属性。

2)构造函数没有返回值,因为没有变量来接收返回值,即使有也毫无用处,不管是声明还是定义,函数名前面都不能出现返回值类型,即使是void 也不允许。

3)构造函数可以有参数,允许重载。一个类可以有多个重载的构造函数,创建对象时根据传递的参数来判断调用哪一个构造函数。

4)构造函数在实际开发中会大量使用,它往往用来做一些初始化工作,对成员变量进行初始化等,注意,不能用memset对整个类进行初始化。

CFile::CFile() // 类的构造函数

{

m_fp=0;

m_bEnBuffer=true;

}

CFile::CFile(bool bEnBuffer) // 类的构造函数

{

m_fp=0;

m_bEnBuffer=bEnBuffer;

}

带参数的构造函数

默认的构造函数没有任何参数,但如果需要,构造函数也可以带有参数。这样在创建对象时就会给对象赋初始值。

构造函数初始化列表

如果我们有一个类成员,它本身是一个类或者是一个结构,而且这个成员它只有一个带参数的构造函数,没有默认构造函数。这时要对这个类成员进行初始化,就必须调用这个类成员的带参数的构造函数。

当类成员中含有一个const对象时,或者是一个引用时,他们也必须要通过成员初始化列表进行初始化,因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的赋值,这样是不被允许的。

初始化列表中的初始化顺序,与声明顺序有关,与前后赋值顺序无关。

构造对象成员的顺序跟初始化列表的顺序无关,而是和成员对象的定义顺序有关。

第一种:在构造函数内部初始化

类对象=形参;

第二种:定义构造函数函数时初始化,初始化列表

:类对象(形参)//调用拷贝构造函数

谁先初始化,谁后析构【即先初始化的后析构】

分类

class Test

{

public:

//无参数构造函数

Test()

{

;

}

//带参数的构造函数

Test(int a,int b)

{

;

}

//赋值构造函数

Test(const Test &obj)

{

;

}

private:

int a;

int b;

}

无参构造函数

有参数构造函数

拷贝构造函数——由已存在的对象,创建新的对象[用对象初始化对象]

浅拷贝和深拷贝——主要区别是针对指针来说的
浅拷贝——默认构造函数

拷贝数据——需要一个拷贝构造函数和默认构造函数,在释放时会出错,因为会释放两次,第一次的时候已经释放了,第二次就没有需要释放的内容了

指针:拷贝它的数据,相当于两个指针指向同一个空间,所指向的内容相同

其它:相当于进行了赋值操作

深拷贝——自定义构造函数

指针:申请一个大小相同的空间,然后再拷贝数据,相当于有两个相同大小的空间,并且空间中的数据相同

需要显示的提供一个拷贝构造函数,来完成深拷贝动作。

手写深拷贝:1.申请一个大小相同的空间 2.拷贝数据

由已存在的对象,创建新的对象。也就是说新对象,不由构造器来构造,而是由拷贝构造器来完成。拷贝构造器的格式是固定的。

class 类名

{

类名(const 类名 & another)

{

拷贝构造体

}

}

例如:

class A

{

A(const A & another)

{}

}

使用拷贝构造函数的场合
  1. 用对象1初始化对象2 Test t2=t1; Testt2(t1); (不可写成:Test t2;t2=t1;,是“=”操作符,不是拷贝构造函数的调用)
  2. 函数的参数是一个对象,并且是值传递方式
  3. 函数的返回值是一个对象,并且是值传递方式

Test t2(t1);等价于Test t2=t1;

不可以进行如下操作·

Test t2;

t2 = t1;//这不是拷贝构造,拷贝构造函数是在变量初始化的时候调用,这是“=”操作符

void operator=(const Test &another_obj){

cout<<"="<<endl;

m_a = another_obj.m_a;

}

默认构造函数

默认无参构造函数

当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空。

当没有任何显示的构造函数(显示的无参构造函数、显示的有参构造函数、显示的拷贝构造函数)的时候,默认无参构造函数就会出现。

默认拷贝构造函数

当类中没有定义拷贝构造函数时,编译器默认提供一个默认拷贝构造函数,简单的进行成员变量的值复制。

当没有显示的拷贝构造函数的时候,默认拷贝构造函数就会出现。

规则

1.系统提供默认的拷贝构造器。一经实现,不复存在。

2.系统提供的时等位拷贝,也就是所谓的浅浅的拷贝【浅拷贝】。

3.要实现深拷贝,必须要自定义。

析构函数

class 类名

{

~类名(){

析构体

}

}

例如:

class A

{

~A()

{}

}

类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。

在 CFile 类的声明中,还有一个特殊的成员函数~CFile(),它就是析构函数(destructor)。

~CFile(); // 类的析构函数

析构函数的名字在类的名字前加~,没有返回值,但可以被显式的调用,在对象销毁时自动执行,用于进行清理工作,例如释放分配的内存、关闭打开的文件等,这个用途非常重要,可以防止程序员犯错。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。

规则

1.对象销毁时,自动调用。完成销毁的善后工作。

2.无返回值,与类同名。无参。不可以重载。

作用

并不是删除对象,而在对象销毁前完成的一些清理工作。

特点:

1)构造函数必须是 public 属性的。

2)构造函数没有返回值,因为没有变量来接收返回值,即使有也毫无用处,不管是声明还是定义,函数名前面都不能出现返回值类型,即使是 void 也不允许。

3)析构函数不允许重载的。一个类只能有一个析构函数。

CFile::~CFile()// 类的析构函数

{

Close();// 调用Close释放资源

}

默认析构函数

当没有显示的析构函数的时候,默认析构函数会出现。

虚析构和纯虚析构

构造函数不能是虚函数。建立一个派生类对象时,必须从类层次的根开始,沿着继承路径逐个调用基类的构造函数。
析构函数可以是虚的。虚析构函数用于指引 delete 运算符正确析构动态对象 。

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。【解决方法:将父类中的析构函数改为虚析构或纯虚析构。】

虚析构和纯虚析构共性

  • 可以解决父类指针释放子类对象
  • 都需要有具体的函数表现

虚析构和纯虚析构区别

  • 如果是纯虚析构,该类属于抽象类,无法实例化对象

语法

虚析构

virtual ~类名(){}

纯虚析构

virtual ~类名() = 0;//类内

类名::~类名(){}//类外

作用

如果父类的析构函数不加virtual关键字

当父类的析构函数不声明成虚析构函数的时候,当子类继承父类,父类的指针指向子类时,delete 掉父类的指针,只调动父类的析构函数,而不调动子类的析构函数。

如果父类的析构函数加virtual关键字

当父类的析构函数声明成虚析构函数的时候,当子类继承父类,父类的指针指向子类时,delete 掉父类的指针,先调动子类的析构函数,再调动父类的析构函数。

总结

  • 虚析构或纯虚析构是用来解决通过父类指针释放子类对象
  • 如果子类中没有堆区数据,可以不写虚析构或纯虚析构
  • 拥有纯虚析构函数的类也属于抽象类

哥哥笔记

文档:第三天.note

链接:有道云笔记

类与类之间的关系

has-A,uses-A和is-A

has-A 包含关系

用以描述一个类由多个“部件类”构成。实现has-A关系用类成员表示,即一个类中的数据成员是另一种已经定义的类。

类本身和成员对象的构造函数的执行顺序:先执行成员对象的构造函数,在执行该类的构造函数。

当有多个成员对象时,构造函数的执行顺序与定义对象的顺序一致,与初始化列表中的顺序无关。

初始化类的成员对象:只能在构造函数的初始化列表进行,不能在构造函数内进行,因为在函数内属于赋值操作而非定义时的初始化,不能直接调用成员对象的构造函数,此时不是实例化类,若对()重载,可以实现在构造函数中初始化成员对象。

uses-A 一个类部分地使用另一个类

通过类之间成员函数的相互联系,定义友员或对象参数传递实现。

is-A 继承

关系具有传递性,不具有对称性。

继承

定义

类的继承,是新的类从已有类那里得到已有的特性。或从已有类产生新类的过程就是类的派生。原有的类称为基类或父类,产生的新类称为派生类或子类。【父类的构造函数不能被子类继承】

创建一个子类的同时会创建一个父类,并且是先创建其父类再创建子类。

继承的好处:可以减少重复的代码。
派生与继承,是同一种意义两种称谓。 isA 的关系。

派生类的组成

派生类中的成员,包含两大部分,一类是从基类继承过来的,一类是自己增加的成员。从基类继承过过来的表现其共性,而新增的成员体现了其个性。

几点说明:

1,全盘接收,除了构造器与析构器。基类有可能会造成派生类的成员冗余,所以说基类是需设计的。
2,派生类有了自己的个性,使派生类有了意义。

方式

语法

class 派生类名:继承方式 基类名{

派生类成员声明;

};
一个派生类可以同时有多个基类,这种情况称为多重继承,派生类只有一个基类,称为单继承。

protected 访问控制

protected对于外界访问属性来说,等同于私有,但可以派生类中可见。

派生类成员的标识和访问

继承方式

共有成员 public

保护成员 protected

私有成员 private

访问属性

内部访问

对象访问

访问属性

内部访问

对象访问

访问属性

内部访问

对象访问

public 公有继承

public

YES

YES

protected

YES

NO

private

NO

NO

protected 保护继承

protected

YES

NO

protected

YES

NO

private

NO

NO

private 私有继承

private

YES

NO

private

YES

NO

private

NO

NO

通过多次私有继承后,对于基类的成员都会成为不可访问

private 成员在子类中依然存在,但是却无法访问。不论何种方式继承基类,派生类都不能直接使用基类的私有成员。

如何恰当的使用 public、protected、private 为成员声明访问级别?

1.需要被外界访问的成员直接设置为public

2.只能在当前类中访问的成员设置为private

3.只能在当前类和子类中访问的成员设置为protected,protected成员的访问权限介于public和private之间。

类型兼容性原则

类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员。这样,公有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决。

类型兼容规则中所指的替代:

子类对象可以当作父类对象使用

子类对象可以直接赋值给父类对象

子类对象可以直接初始化父类对象
父类指针可以直接指向子类对象
父类引用可以直接引用子类对象

在替代之后,派生类对象就可以作为基类的对象使用,但是只能使用从基类继承的成员。

子类就是特殊的父类

赋值兼容规则

派生类的对象可以赋值给基类对象

A a1;// 基类A对象 a1

B b1;// 类A的公有派生类B的对象 b1

a1 = b1;//用派生类B对象 b1 对基类对象 a1 赋值

在赋值时舍弃派生类自己的成员,只进行数据成员的赋值。赋值只是对数据成员赋值,对成员函数不存在赋值的问题,内存中数据成员和成员函数是分开的。

赋值后不能通过基类对象 a1 去访问派生类对象 b1 的成员,因为 b1 的成员与 a1 的成员是不同的,派生类中的一些成员变量是基类没有的。

#include<iostream>
using namespace std;
class A {
public:A(int aa):a(aa){}void Aoutput() {cout << a << endl;}int a;
};
class B :public A {
public:B(int aa, int bb) :A(aa) {b = bb;}void Boutput() {Aoutput();cout << b << endl;}int b;
};
int main() {A a1(2);a1.Aoutput();B b1(3, 4);b1.Boutput();a1 = b1;a1.Aoutput();cout << "----------------" << endl;cout << a1.a << endl;//cout << a1.b << endl; 因为 a1 中不存在成员变量 bcout << b1.a << endl;cout << b1.b << endl;return 0;
}

只能用子类对象对其基类对象赋值,不能用基类对象对其子类对象赋值,因为两种对象的大小是不同的,基类对象不包含派生类的成员变量无法对派生类的成员变量赋值。同理,同一基类的不同派生类之间也不能赋值。

派生类的对象可以初始化基类的引用

A a1;// 基类A对象 a1

B b1;//派生类B对象 b1

A &r = a1;

此时 r 和 a1 共享同一段内容单元

A &r = b1;

此时 r 不是 b1 的别名,也不与 b1 共享同一段内存单元,只是 b1 中基类部分的别名。

r 是A类的引用,其有效范围只有A类那么大,r 与 b1 中基类部分共享同一段存储单元,r 与 b1 具有相同的 起始地址。

如果函数的参数是基类对象或基类对象的引用,相应的实参可以用子类对象。

派生类对象的地址可以赋给指向基类的指针,即指向基类对象的指针可以指向派生类对象

通过指向基类对象的指针,只能访问到派生类中的基类成员变量,不能访问派生类增加的成员变量,即使派生类和基类中存在同名变量,访问的也是基类的成员变量而非派生类的成员变量。

继承中的对象模型

子类是由父类成员叠加子类新成员得到的。

父类中所有非静态成员属性都会被子类继承,父类中私有成员属性被编译器隐藏,因而访问不到,但是是被继承了。

利用开发人员命令提示工具查看对象模型:跳转盘符——>跳转文件路径【cd 具体路径(复制粘贴路径)】——>查看命名【cl /d1 reportSingleClassLayout类名 文件名】

继承中的构造和析构

在子类对象构造时,需要调用父类构造函数对其继承得来的成员进行初始化。

在子类对象析构时,需要调用父类析构函数对其继承得来的成员进行清理。

派生类的构造函数的初始化列表中可以包含:基类的构造函数、派生类中成员对象的初始化、派生类中一般成员变量的初始化

继承中构造析构调用原则

1、子类对象在创建时会首先调用父类的构造函数
2、父类构造函数执行结束后,执行子类的构造函数
3、当父类的构造函数有参数时,需要在子类的初始化列表中显示调用

4、析构函数调用的先后顺序与构造函数相反

继承和组合并存,构造和析构原则

构造顺序:虚基类—>非虚基类—>成员对象—>自己

先构造父类,再构造成员对象,最后构造自己

先析构自己,再析构成员对象,最后析构父类

【一个类,如果不是派生类,那么就先构造成员变量,在构造自己,先析构自己,再析构成员变量;一个类,如果是派生类,那么就先构造基类,再构造成员变量,最后构造自己,先析构自己,再析构成员变量,最后析构基类】

#include<iostream>

using namespace std;

class Object {

public:

Object(const char* s) {

cout << "Object()" << " " << s << endl;

}

~Object() {

cout << "~Object()" << endl;

}

};

class Parent :public Object {

public:

Parent(const char* s) :Object(s) {

cout << "Parent()" << " " << s << endl;

}

~Parent() {

cout << "~Parent()" << endl;

}

};

class Child :public Parent {

public:

Child() :o2("o2"), o1("o1"), Parent("Parameter from Child!") {

cout << "Child()" << endl;

}

~Child() {

cout << "~Child()" << endl;

}

private:

Object o1;

Object o2;

};

void run() {

Child child;

}

int main(int argc, char* argv[]) {

run();

return 0;

}

继承中同名成员的处理方法

同名成员变量和成员函数通过作用域分辨符进行区分

同名成员变量

当子类成员变量与父类成员变量同名时,子类依然从父类继承同名成员,但直接访问到的是子类中的同名成员,在子类中通过作用域分辨符::进行同名成员区分(在派生类中使用基类的同名成员,显式地使用类名限定符),同名成员存储在内存中的不同位置 。

同名成员函数

如果子类中出现和父类同名的成员函数,子类的同名函数会隐藏/覆盖掉父类中所有同名成员函数(包括重载函数)。

访问父类中隐藏的同名成员函数:

  • 定义基类指针,让基类指针指向派生类对象,调用到的是基类中的函数
  • 加作用域

派生类中的static关键字

基类定义的静态成员,将被所有派生类共享。根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质 (遵守派生类的访问控制),派生类中访问静态成员,用以下形式显式说明:类名 :: 成员或通过对象访问对象名.成员

static函数也遵守3个访问原则

static易犯错误(不但要初始化,更重要的显示的告诉编译器分配内存)

同名静态成员

成员变量
  • 通过对象访问

默认访问子类本身,添加作用域访问父类

子类数据:对象.变量

父类数据:对象.父类::变量

  • 通过类名访问

子类数据:子类::变量

父类数据:子类::父类::变量

第一个::代表通过类名方式访问,第二个::代表访问父类作用域下

成员函数

子类出现和父类同名的静态成员函数,会隐藏父类中所有同名成员函数。如果想要访问父类中被隐藏的同名成员函数,需要加作用域。

  • 通过对象访问

子类函数:对象.函数名()

父类函数:对象.父类::函数名()

  • 通过类名访问

子类函数:子类::函数名()

父类函数:子类::父类::函数名()

多继承

父类出现同名成员,需要加作用域区分。

菱形继承/钻石继承

如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性 。

多个父类拥有相同数据,需要加以作用域区分。但是会导致数据有多份,导致资源浪费。

虚继承 virtual

如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性 ;如果在多条继承路径上有一个公共的基类,那么在继承路径的某处汇合点,这个公共基类就会在派生类的对象中产生多个基类子对象,要使这个公共基类在派生类中只产生一个子对象,必须对这个基类声明为虚继承,使这个基类成为虚基类。
虚继承声明使用关键字 virtual 。

在继承方式之前加上关键字 virtual,变为虚继承。使其基类变为虚基类。

底层实现:vbptr(v—virtual,b—base,ptr—pointer):虚基类指针,指向 vbtable,即虚基类表。

多态

分类

  • 静态多态:函数重载和运算符重载属于静态多态,复用函数名
  • 动态多态:派生类和虚函数实现运行时多态

区别

静态多态的函数地址早绑定—编译阶段确定函数地址

动态多态的函数地址晚绑定—运行阶段确定函数地址

多态包括两种多态性:编译时的和运行时的。前者是通过函数和运算符实现的,而后者是通过类继承关系和虚函数实现的

意义

如果有几个相似而不完全相同的对象,有时人们要求在向它们发出同一个消息时,它们的反应各不相同,分别执行不同的操作。这种情况就是多态现象。

C++中所谓的多态是指,由继承而产生的相关的不同的类,其对象对同一消息会作出不同的响应。

多态性是面向对象程序设计的一个重要特征,能增加程序的灵活性。可以减轻系统升级、维护、调试的工作量和复杂度。

封装

突破了C语言函数的概念

继承

代码复用,复用原来写好的代码

多态

多态可以使用未来,80年代写了一个框架,90人写的代码

多态是软件行业追寻的一个目标

优点

  • 代码组织结构清晰
  • 可读性强
  • 利于前期和后期的扩展以及维护

成立条件

  • 有继承关系
  • 子类重写父类中的虚函数【父类中的函数使用关键字 virtual,使之成为虚函数】(重写:函数返回值类型、函数名、参数列表完全相同)
  • 有父类指针(父类引用)指向子类对象(使用多态)

多态是设计模式的基础,多态是框架的基础

实现前提——赋值兼容

赋值兼容规则是指在需要基类对象的任何地方都可以使用公有派生类的对象来替代。
赋值兼容是一种默认行为,不需要任何的显示的转化步骤。
赋值兼容规则中所指的替代包括以下的情况:
派生类的对象可以赋值给基类对象。
派生类的对象可以初始化基类的引用。
派生类对象的地址可以赋给指向基类的指针。
替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。

面向对象新需求

编译器的做法不是我们期望的;

根据实际的对象类型来判断重写函数的调用;

如果父类指针指向的是父类对象则调用父类中定义的函数;

如果父类指针指向的是子类对象则调用子类中定义的重写函数。

C++中通过virtual关键字对多态进行支持

使用virtual声明的函数被重写后即可展现多态性

实现原理

vftable (虚函数表)和 vptr (即vfptr,即虚函数表指针)

当类中声明虚函数时,编译器会在类中生成一个虚函数表,虚函数表是一个存储类成员函数指针的数据结构,是由编译器自动生成与维护的,virtual成员函数会被编译器放入虚函数表中,存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针) 。

类中存在一个 vfptr ,指向 vftable,表内记录一个虚函数地址【&作用域::函数名】。当子类重写父类中的虚函数,子类中的虚函数表内部会替换成子类的虚函数地址。当父类的指针或引用指向子类对象时,发生多态。

说明:

1. 通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多。
2.出于效率考虑,没有必要将所有成员函数都声明为虚函数。
3.C++编译器,执行run函数,不需要区分是子类对象还是父类对象,而是直接通过p的VPTR指针所指向的对象函数执行即可。

证明vptr指针的存在

sizeof A1 = 4是因为存在一个 vfptr 指针,指向虚函数表

sizeof A2 = 1A2 类似于空类,空类占一个字节

构造函数中能否调用虚函数,实现多态?

对象在创建的时,由编译器对VPTR指针进行初始化
只有当对象的构造完全结束后VPTR的指向才最终确定
父类对象的VPTR指向父类虚函数表
子类对象的VPTR指向子类虚函数表

父类指针和子类指针的步长

静态联编和动态联编

1.联编是指一个程序模块、代码之间互相关联的过程。

2.静态联编是程序的匹配、连接在编译阶段实现,也称为早期匹配。重载函数使用静态联编。编译时就确定函数的调用与被调用的关系。

3.动态联编是指程序联编推迟到运行时进行,使用又称为晚期联编(迟绑定)。switch语句和if语句是动态联编的例子。运行时确定函数的调用与被调用的关系。

C++实现运行时运行时多态,必须使用基类指针调用虚函数。

重载、重写、重定义

纯虚函数和抽象类

基本概念

纯虚函数是一个在基类中说明的虚函数,在基类中没有定义,要求任何派生类都定义自己的版本。纯虚函数为个派生类提供一个公共界面(接口的封装和设计、软件的模块功能划分)。一个具有纯虚函数的基类称为抽象类。

语法

virtual 类型 函数名(参数表)=0;

抽象类在多继承中的应用

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

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

相关文章

Matlab/C++源码实现RGB通道与HSV通道的转换(效果对比Halcon)

HSV通道的含义 HSV通道是指图像处理中的一种颜色模型&#xff0c;它由色调&#xff08;Hue&#xff09;、饱和度&#xff08;Saturation&#xff09;和明度&#xff08;Value&#xff09;三个通道组成。色调表示颜色的种类&#xff0c;饱和度表示颜色的纯度或鲜艳程度&#xf…

061:mapboxGL利用fitBounds同时将多个点放在可视范围内

第061个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+mapbox中加载geojson数据,利用fitBounds同时将多个点放在可视范围内。 直接复制下面的 vue+mapbox源代码,操作2分钟即可运行实现效果 文章目录 示例效果配置方式示例源代码(共134行)相关API参考:专栏目标示例…

vscode中快速生成vue3模板

步骤&#xff1a;设置 -> 用户代码片段 -> vue.json&#xff08;没有vue.json,选vue也可&#xff09;-> 定义自己所需的代码段 代码段 如下&#xff0c; {"Print to console": {"prefix": "vue3", //键入该值&#xff0c;按tab…

【linux】Linux 查看内存使用情况的几种方法汇总

文章目录 GUI 查看命令获取命令 free命令 vmstat命令 top命令 htop Linux 查看内存使用情况的几种方法包括使用 free 命令、top 命令、htop 命令、vmstat 命令和/proc/meminfo 文件。这些方法可以帮助用户了解系统内存的使用情况&#xff0c;包括总内存、已用内存、空闲内存、缓…

JavaCV + FFmpeg 播放音视频

JavaCV FFmpeg 播放音视频 1、导入JavaCV库1.1 使用ffmpeg必要库1.2 简单FFmpeg命令 待续~~~~ FFmpeg documentation bytedeco/javacv - GitHub 1、导入JavaCV库 gradle下面这种会导入javacv-platform所有包&#xff0c;非常耗时&#xff1a;https://repo.maven.apache.org/…

(零基础学习)Neo4j+Spring boot 自行定义属性

前置知识 1.Neo4j :属性 节点和关系都可以设置自己的属性。 属性是由Key-Value键值对组成&#xff0c;键名是字符串。属性值是要么是原始值&#xff0c;要么是原始值类型的一个数组。比如String&#xff0c;int和iint[]都是合法的。 注意 null不是一个合法的属性值。 Nulls能…

计算机网络——计算机网络体系结构(3/4)-计算机网络体系结构分层思想举例

目录 发送请求报文 应用层构建HTTP请求报文 运输层添加TCP首部 网络层添加IP首部 数据链路层形成帧 物理层转化为比特流 路由器处理 服务器处理 发回响应报文 计算机网络体系结构分层思想举例 假设网络拓扑如下所示&#xff0c;主机属于网络N1&#xff0c;Web服务器属…

C/C++面试常见问题——static关键字的主要用法

首先我们要明确一下C/C的内存区域划分 在C/C中内存主要被划分为四大块&#xff0c;堆&#xff0c;栈&#xff0c;全局/静态存储区&#xff0c;代码区 而全局/静态存储区又被细分为常量区(静态常量区&#xff0c;const关键字修饰)&#xff0c;全局区(全局变量区)和静态变量区(…

卫星结构。。。

• 下图介绍了现代卫星中常见的组件&#xff0c;它们被分为 卫星有效载荷 和 卫星总线 。 – 卫星有效载荷 包括任务专用设备&#xff0c;例如用于地球观测的高分辨率相机或用于电信的强大无线电硬件。 – 卫星总线 包括操作和维护卫星所需的所有组件。 • 它被设计为独立于有效…

Tomcat部署项目的两种方式

第一种: 将项目放到tomcat的webapps目录下,war包会自动解压 里面有个页面 为什么会默认访问asd.html 可以配置 tomcat--->conf---->web.xml 第二种方式 在Tomcat/conf/Catalina/localhost/目录下随便建个xxx.xml文件 注意字符编码 utf-8 注意aaa就是上下文地址 …

PMP的智慧(2) - 系统性思考及复杂性

PMP的智慧(2) - 系统性思考及复杂性 在2021年推出的第七版《管理专业知识体系指南》中&#xff0c;PMI在传统的过程和ITTO的基础上&#xff0c;重新增加了12大项目管理原则。 管家式管理 stewardship团队 team干系人 stakeholders价值 value系统思考 system thinking领导力 l…

基于人工蜂鸟优化的BP神经网络(分类应用) - 附代码

基于人工蜂鸟优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于人工蜂鸟优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.人工蜂鸟优化BP神经网络3.1 BP神经网络参数设置3.2 人工蜂鸟算法应用 4.测试结果…

进阶课2——语音分类

语音分类主要是对语音从不同的维度进行识别和分类&#xff0c;这些维度可以包括语种、性别、年龄段、情绪、说话人身份等&#xff0c;具体如下&#xff1a; 语种分类&#xff1a;根据发音人的母语或者惯用语言&#xff0c;将语音分为不同的语种&#xff0c;例如中文、英文、法…

中文编程工具开发语言编程案例:会员VIP管理系统软件实例

中文编程工具开发语言编程案例&#xff1a;会员VIP管理系统软件实例 中文编程工具开发语言编程案例&#xff1a;会员VIP管理系统软件实例。 软件功能&#xff1a; 1、系统设置&#xff1a;参数设定&#xff0c;账号及权限设置&#xff0c;系统初始化&#xff0c;卡类型设置&a…

学习SpringMVC,建立连接,请求,响应 SpringBoot初学,如何前后端交互(后端版)?最简单的能通过网址访问的后端服务器代码举例

要想通过SpringBoot写一个简单的处理请求的服务器&#xff08;方法&#xff09;&#xff0c;需要有以下步骤 建立连接请求响应 来复习的话直接在文章末尾看源码就行 1、创建SpringBoot项目 https://blog.csdn.net/dream_ready/article/details/133948253 2、编写Controller建…

音频抓取代码示例

以下是一个使用DefaultsKit库的简单爬虫程序&#xff0c;用于爬取音频。代码中使用了https://www.duoip.cn/get_proxy的API获取代理服务器。 import Foundation import DefaultsKit ​ let url "https://www.douban.com/music" // 目标网站URL let proxyUrl "…

npm ERR! node-sass@6.0.1 postinstall: `node scripts/build.js`

1.遇到的问题 vue npm install提示以下错误 2.首次尝试方法 尝试用下面的方式重新安装弄得-saas&#xff0c;结果不起作用 。 npm config set sass_binary_sitehttps://npm.taobao.org/mirrors/node-sass npm install node-sass 这时考虑降级node版本&#xff0c;node.js从…

从手动操作到自动化管理,如何实现企业身份业务全面自动化?

在数字化时代&#xff0c;身份管理已经成为了企业和组织不可或缺的一部分&#xff0c;企业对于管理员工、客户和合作伙伴的身份信息和访问权限的需求变得愈发复杂。身份管理不仅仅是一项必要的任务&#xff0c;更是确保业务流畅运营和数据安全的关键因素。然而&#xff0c;传统…

系统架构之微服务架构

微服务架构 一.传统的单体架构与微服务架构的区别1.1 单体架构1.1.1 优点1.1.2 缺点 1.2 微服务架构1.2.1 优点1.2.2 面临的问题与挑战 二. 微服务架构模式方案2.1 聚合器微服务2.2 链式微服务2.3 数据共享微服务2.4 异步消息传递微服务 三. SOA与微服务的区别 微服务&#xff…

前端渲染后端返回的HTML格式的数据

在日常开发中&#xff0c;经常有需要前端渲染后端返回页面的需求&#xff0c;对于不同数据结构&#xff0c;前端的渲染方式也不尽相同&#xff0c;本文旨在对各种情况进行总结。 后端返回纯html文件格式 数据包含html标签等元素&#xff0c;数据类型如下图&#xff1a; 前端通…