目录
类的引入
类的定义
类的定义规则
类域问题:
类的访问限定符及封装
访问限定符
封装
类的实例化
类对象的大小问题
this指针
This指针的一些特性:
类的引入
在学习C语言的时候,C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题
自然而然的,C语言就不需要高集成度的结构,每个类型的功能比较单一,比如结构体,在C语言下只能存放各类其他的类型,有函数需要的时候就拿来用就好,这种运作逻辑有点像锤子和钉子。
我们的问题是如何把钉子钉到木板里,我们用锤子把钉子钉入木板的方法就像函数,锤子和钉子就像是各种变量,他们之间的关系较为独立。
而C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完
成。拿钉钉子这个过程举例子,如果C语言是简单的锤子钉钉子,那么C++就是直接拿着钉枪开始钉了。
那么也就意味着,我们不需要关注钉枪是怎么运作的,拿起来就好,也就是我们不再需要一个函数来指导我们怎么钉钉子,钉枪如何运作则是这个对象的事情。那么钉枪内部运作需要很多的电子原件,里面只有锤子可不行,还需要有更多的功能,那么我们该如何包装这个“钉枪”呢?于是,类就出现了。
类的定义
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分
号不能省略。
类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者
成员函数。
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
用C语言方式实现的栈,结构体中只能定义变量;现在以C++方式实现,会发现struct中也可以定义函数,那么我们就可以一股脑的往里头放
typedef int Stackdatatype;
class Mystack
{
public:
Mystack()
:_a(nullptr)
,_top(0)
,_capacity(0)
{}
void Push(int x)
{
if (_top == _capacity)
{
int newcapacity = _capacity == 0 ? 4:_capacity*2;
Stackdatatype* tmp =(Stackdatatype*)realloc(_a, newcapacity * sizeof(Stackdatatype));
_a = tmp;
_capacity = newcapacity;
}
_a[_top] = x;
++_top;
}
void PrintStack()
{
for (int i = 0; i < _top; ++i)
cout << _a[i] << endl;
}
Stackdatatype StackTop()
{
return _a[_top-1];
}
private:
Stackdatatype* _a;
int _top;
int _capacity;
};
然后用起来试试看
那么这就很爽了,我们使用栈的时候不再需要创建一大堆的函数,直接拿过来用就可以了。
当然其中还有不少的细节,我们之后一一来看,现在我们先初步了解类的定义规则。
类的定义规则
第一种:声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
2. 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::
类域问题:
声明于定义分离是我们常玩的东西了,不仅可以增加代码的可读性,也让我们管理变得方便不少,但是有一个问题,当定义与声明分离时,我们怎么知道当前定义的是哪个函数呢?
比如栈和队列都有一个叫Init的函数,我们编写定义的时候怎么区分他俩?
class Stack
{
void Init();
....
};
class Queue
{
void Init();
....
};
这个时候需要使用到上一篇文章中的域作用限定符::来指定当前定义的成员函数属于哪个类
void Stack::Init()
{
...
}
void Queue::Init()
{
...
}
类的访问限定符及封装
最开始接触类的时候,我们肯定会对这两个东西产生疑问
这两个东西叫访问限定符,这两个限定符有什么功能?
访问限定符
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选
择性的将其接口提供给外部的用户使用
【访问限定符说明】
1. public修饰的成员在类外可以直接被访问
2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4. 如果后面没有访问限定符,作用域就到 } 即类结束。
5. class的默认访问权限为private,struct为public(因为struct要兼容C)
那么C++中struct和class的区别是什么?
C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来
定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类默认访问权限是private。
封装
C++之所以使用访问限定符,是为了封装,封装可以让我们的程序可以在更加安全的应用场景下减少出现BUG的几率。
具体的情况举个例子,一台洗衣机的功能使其涉及的按钮并不多,其中内部的电路板以及电子元件单片机什么的都被包装起来了,不去暴力破拆很难看见,毕竟没有必要,而且也能保证用户不会误触某些原件造成短路,封装的本意也是如此,保护成员变量,保证其不被修改并运行于理想状态。
在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来
隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用
类的实例化
用类类型创建对象的过程,称为类的实例化
如上所展示的结构栈的类的实现,其中main函数内部的这串代码创建了名为St的对象,这个对象是以MyStack类实例化出来的
int main()
{
MyStack St;
}
这个过程简而言之类似照着配方做蛋糕的过程,类的创建就像是配方,类的实例化则是就着配方制作出蛋糕。
一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,才存储类成员变量
类对象的大小问题
那么类对象的大小是多少呢?我们还是拿上面的栈试试
啥?为啥是两个12呢?我类里面的函数为什么不计算入大小?
因为类的存储模型只存储类内部的变量,函数是不存储的,我们对函数的存储提出两种猜想:
1.存储的是函数的指针。我们先前已经看过,打印类和类对象的大小都是成员变量的大小,函数指针本身也需要空间存放,所以不太可能
2.所有的类成员函数被存储到了类成员函数表内。
先记录一个问题:
那为什么不把函数和变量放一块呢?这样不是很方便吗?
我们使用某一个类如果将函数和变量都存放在一块的话,假如这个类被多次生成类对象将会产生非常多重复空间的浪费。毕竟某一些对象可能完全用不着其他的成员函数。
成员函数存放在公共代码段
成员函数如果存储在公共代码段将会是非常不错的存储方式,按需索取,不会产生空间上的浪费我们只需要当调用函数的时候去函数表里头找就行了。
那么只存了一个函数的类的对象的大小时多少?0吗?
其实是1,这个1其实是占位符,不存储有效数据,标识对象存在。
this指针
虽然我们知道编译器使用函数的时候用的是公共代码段,那么为什么对象不一样,函数输出也不一样?
隐藏的this指针就是原因,谁调用,谁访问这个函数,谁就把自己对象的成员传进去
this指针本身指向的是当前调用函数的对象,它的指针类型是当前存在的类的类型
This指针的一些特性:
被隐藏的This指针长这样
void Func(class* const this)
特性:
1.this指针自己本身是携带一个const的,所以我们无法直接对this指针进行赋值。(当const在*左边的时候,保护的是指针所指向的对象,而在*右边的时候保护的是指针本身。)
2.this指针是作为传递成员函数的形参而存在的,所以对象中不能存储this指针。
3.this指针只能在成员函数内使用。
4.作为一个隐藏的形参,我们不需要去传递对应的实参,寄存器会帮助我们完成这项工作。
这也解释了为什么我们不能直接访问类内部的函数,因为其内部的函数接收的参数只是类生成对象的指针!如果你想强行使用里面的函数那你连传什么都不知道。
5.this指针的定义和传递都是编译器的活儿,我们不能去抢,但是我们可以用
补充:This指针在所有的成员函数里都有
两道面试题:
1.This指针存在哪?
我们可能会想当然的觉得this指针存放在对象内部,但其实它存放在栈内部,因为它是形参具有栈帧
举例:
如图所示的一个成员函数,它的形参其实有4个,还包含了一个被隐藏的this指针。
2.下面两道题的答案是什么?为什么?
答案是B,C,原因如下:
我们先再次的复习一遍概念:this指针调用的原则是:谁调用,谁访问这个函数,谁就把自己对象的成员传进去
到这里,类和对象还有this指针最最基本的概念就解释完毕了!希望对你有点帮助!感谢阅读!
但这都还没有结束!接下来则是构造函数了!