一、类
什么是类呢?大家都应该知道,在生活中为了方便管理,我们习惯将其具有某种特征的一类物体按其特性进行归类,类就与之类似,与C语言中结构体有相似之处。
类是一种抽象的数据类型。它规定了某种事物的特征(成员变量,可以理解为一种属性,是静态的)和行为(成员函数,可以理解为动作,是动态的),可用class关键字对其定义。
class Student { public: // 此处可定义类的方法或成员函数 private: char _name[20]; int _age; int _gender; //对类的成员变量(属性)加_是为了进行区分,可加可不加 }; //注意: ;不能少
二、封装
我们都明白封装是面向对象程序设计的三大特性之一,那么我们如何实现封装这个事情呢?答案很简单,使用类。我们可以在类中把该函数的函数的功能实现完毕,提供一个公共的接口进行访问。那这时有的人会想?这个函数有的地方成员变量不能改变,万一被人改了程序崩了怎么办?这就要引出访问限定符这个东西来保护我们的代码了,我们可以只提供能改变的变量,不能变的“藏起来”不就好了的。
访问限定符
访问限定符共有三个:public、protected、private。
public
public为公有的,谁都可以访问。
private
private为私有的,在类外不能进行访问。
protected
protected与private类似,在后续学习多态时才能更好明白其中区别。
默认访问限定
访问限定符的范围是该访问限定符到下一个访问限定符的中间的内容。如果下面没有访问限定符, 那么就是之后的所有范围。
那么,我们在没有加访问限定符的时候编译器会给我们默认加什么限定符呢?大家记好了:
class 默认为private;
struct 默认为public。
类域
我现在有一个问题:我定义了一个类,如果我想在这个类的外面对类里面的成员初始化,那该怎么办呢?这个问题就好比,我们在学校得了奖,那这个奖怎么到你手上呢?流程大抵是这样的:先送到学院手里,再通知个人来领取。那经行初始化就与之类似,首先先声明,我在这个类里,然后进行初始化。
class Student { public: void Init(int _age = 18); private: char _name[20]; int _age; int _gender; }; void Student::Init(int age) //此处的类型仍不可少 { _age = age; }
注意:此处必须要指定类域,不然编译器找不到,找不到,就会对你使小绊子,即:报错。所以,我们一定要注意。
三、类的实例化
首先,我们要明白什么是实例化?可这样理解:我们这时就好比是设计师,我们设计出的类就好比是图纸,明确各个地方的功能,但是看不见实物,对吧?实例化就是把它搞出来,具体给出空间。在类中,类中对象仅仅是声明,未开辟空间,实例化的目的就是在栈帧中开辟出空间。
class Data { public: void Init(int year, int month, int day) { _year = year; _month = month; _day = day; } void Print() { cout << _year << "/" << _month << "/" << _day << endl; } private: int _year; // 此处是声明,未开空间 int _month; int _day; }; int main() { Data d1; //Data d1, d2; Data d2; //两种方式实例化均可 d1.Init(2024, 1, 1); d1.Print(); d2.Init(2024, 7, 1); d2.Print(); return 0; }
那么,我又有一个问题:这个Print函数,在内存中会怎么存储?是给每个实例化对象一个?还是专门放到一个地方,要用到时候去取?大家想一想,看看和你想的一样不一样。答案为后者。
函数指针是不需要存储的,函数指针是⼀个地址,调⽤函数被编译成汇编指 令[call 地址], 其实编译器在编译链接时,就要找到函数的地址,不是在运⾏时找,只有动态多态是在 运⾏时找,就需要存储函数地址,
这里我们偷偷打开汇编看一下:
以上汇编指令便能验证我的说法。
那么,类中的成员变量中如何存储呢?是随意存储吗?显然不是。这时我们注意到:class 与struct 有相似那存储是否一样呢?答案是一样的。都要符合内存对齐规则,这里我们不考虑成员函数,因为在类里存的是地址,所以我们不考虑。这里我们简单复习一下对齐规则:
第⼀个成员在与结构体偏移量为0的地址处。
其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
注意:对⻬数 = 编译器默认的⼀个对⻬数 与 该成员⼤⼩的较⼩值。
VS中默认的对⻬数为8
结构体总⼤⼩为:最⼤对⻬数(所有变量类型最⼤者与默认对⻬参数取最⼩)的整数倍。
如果嵌套了结构体的情况,嵌套的结构体对⻬到⾃⼰的最⼤对⻬数的整数倍处,结构体的整体⼤⼩ 就是所有最⼤对⻬数(含嵌套结构体的对⻬数)的整数倍。
来到题来练习一下:
#include<iostream> using namespace std; // 计算⼀下A/B/C实例化的对象是多⼤? class A { public: void Print() { cout << _ch << endl; } private: char _ch; int _i; }; class B { public: void Print() { //... } }; class C {}; int main() { A a; B b; C c; cout << sizeof(a) << endl; cout << sizeof(b) << endl; cout << sizeof(c) << endl; return 0; }
答案如下:8 1 1。
不是,你等会,A为8,我能理解,B与C中不是无对象吗?为啥是1?我知道你很急,但是先别急,听完慢慢道来:因为如果⼀个字节都不给,怎么表⽰对象存在过呢!所以这⾥给1字节,纯粹是为了占位标识对象存在。
四、this指针
大家有没有好奇,上面对日期类进行赋值时,编译器是如何区分d1和d2呢?答案为通过this指针来经行的。编译器编译后,类的成员函数默认都会在形参第⼀个位置,增加⼀个当前类类型的指针,叫做this 指针。这个this指针比较害羞,不会露出真面目,一旦你非要见,你便能明白花儿为什么这样红,即:报错。
但是,她的害羞只是在公共场合中,一旦回家中,还是叫你一睹真容的。
我们明白,函数内的赋值都是通过this指针来完成的,但是,我们要注意这种情况:this指针不能指向空,如果你头铁,非要搞,那就会出现以下情况:
会被爆出:error C2106: “=”: 左操作数必须为左值。所以。我们要谨慎使用。
总结一下便是:C++规定不能在实参和形参的位置显⽰的写this指针(编译时编译器会处理),但是可以在函数体内显 ⽰使⽤this指针。
好了,学了这么久来几道题目来检测一下学习成果吧!
#include<iostream> using namespace std; class A { public: void Print() { cout << "A::Print()" << endl; } private: int _a; }; int main() { A* p = nullptr; p->Print(); return 0; }
程序是否能正常运行?还是编译报错?
答案为正常运行。那p不是指向空指针吗?你先前不是说不能为空吗?难不成是消遣大家?当然不是,它虽然是空,但我们没用它,就好比:你看见一只野狗,你没去挑逗它,它此时不具备攻击性。明白了吧?再来一个巩固一下:
#include<iostream> using namespace std; class A { public: void Print() { cout << "A::Print()" << endl; cout << _a << endl; } private: int _a; }; int main() { A* p = nullptr; p->Print(); return 0; }
那此时结果是什么?是不是能正常运行?对不对?答案很显然是不对的。程序会崩。为啥:大家注意看我们是不是对该空指针进行了使用,这可不得了,你在路上碰见了野狗你非但不躲避,还胆敢向它挑衅!你不入地狱谁入地狱?所以,当对象为空指针,我们万万不可进行使用。切记切记。
好了,我们今天的学习到这里就结束了,下半部分我们再会!
完!