目录
一、类的概念:
类(Class)是面向对象程序设计(OOP, Object-Oriented Programming)的基础概念,它实现了信息的封装。✨✨类是一种用户定义的引用数据类型,也称为类类型。每个类包含数据说明(类的成员变量)和一组操作数据或传递消息的函数(类的方法)。👉👉✨类的实例被称为对象。类是对现实生活中一类具有共同特征的事物的抽象,如果程序中的数据类型与应用中的概念有直接的对应,程序就会更容易理解,也更容易修改。一组经过精心选择的用户定义的类可以使程序更简洁,同时还能使各种形式的代码分析更容易进行,特别是编译器可以检查对象的非法使用 。🐸🐸在面向对象编程中,类是创建对象的蓝图,描述了所创建的对象共同的属性和方法。
二、类的定义:
1、class关键字:
class +类名,这个框架和结构体是一样的,后面也要带分号
✨✨类的成员包括:成员变量,类的方法:一般我们习惯性的把函数写在类里面的上部分,变量写在函数的下部分,但是c++无硬性要求,可以自由书写
格式:
✨class为定义类的关键字,Stack为类的名字,{}中为类的主体,注意类定义结束时后⾯分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量;类中的函数称为类的⽅法或成员函数。
✨为了区分成员变量,⼀般习惯上成员变量会加⼀个特殊标识,如成员变量前⾯或者后⾯加_或者m开头,注意C++中这个并不是强制的,只是⼀些惯例,具体看公司的要求。
✨ C++中struct也可以定义类,C++兼容C中struct的⽤法,同时struct升级成了类,明显的变化是struct中可以定义函数,⼀般情况下我们还是推荐⽤class定义类。struct的名称可以当作类型。
✨ 定义在类里⾯的成员函数默认为inline(内联函数)。
✨在c语言中,定义节点时,结构体指针要写 struct Node* next才行,但是在C++中会struct被当做类,所以Node就是类名,可以不要typedef了
2、访问限定符:
✨✨C++⼀种实现封装的⽅式,⽤类将对象的属性与⽅法结合在⼀块,让对象更加完善,通过访问权限选择性的将其接⼝提供给外部的⽤⼾使⽤。
✨✨public(公有)修饰的成员在类外可以直接被访问;protected和private(私有)修饰的成员在类外不能直接被访问,protected和private是⼀样的,以后学习了继承才能体现出他们的区别。
✨✨ 访问权限作⽤域从该访问限定符出现的位置开始直到下⼀个访问限定符出现时为⽌,如果后⾯没有访问限定符,作⽤域就到 }即类结束。(和函的定义差不多,函数是从开始到遇见第一个return或者直到}结束)
✨✨class定义成员没有被访问限定符修饰时默认为private,struct默认为public(公有)。
✨✨⼀般成员变量都会被限制为private/protected,需要给别⼈使⽤的成员函数会放为public
✨✨✨下面是我写的一个日期类,变量一般我们会进行私有,私有的只能在类里面使用(权限),而不能在类外使用,但是在类外可以调用权限为公有的函数
//日期类
class Date
{
public://(公有)
void Init(int year, int month, int day)
{
//实例化
_year = year;
_month = month;
_day = day;
}
void prin()
{
cout << _year << endl;
cout << _month << endl;
cout << _day << endl;
}
private://(私有)
//这里只是声明,不是定义;(为实列化前,不占内存空间)
int _year;
int _month;
int _day;
};
记住:我们在类外只能访问公有的成员,私有的只有在类里面才能访问/被使用
3、使用:
✨✨和结构体类似 直接就是 . 和 ->操作符;✨✨类名就是类型✨✨
👉就是通过对改类类型的变量使用访问操作符,访问公有的数据,就像结构体访问成员一样类似
4、类域:
类定义了⼀个新的作⽤域,类的所有成员都在类的作⽤域中,在类体外定义成员时,需要使⽤::作⽤域操作符指明成员属于哪个类域。在类域里面写下成员函数的定义,该成员函数默认为内联函数
•类域影响的是编译的查找规则,下⾯程序中Init如果不指定类域Stack,那么编译器就把Init当成全局函数,那么编译时,找不到array等成员的声明/定义在哪⾥,就会报错。指定类域Stack,就是让编译器知道Init是Stack类域的成员函数,当前域找不到的array等成员,就会到类域中去查找
class Stack { public : // 成员函数 void Init(int n = 4); void Push(int x) { // ...扩容 array[top++] = x; } int Top() { assert(top > 0); return array[top - 1]; } void Destroy() { free(array); array = nullptr; top = capacity = 0; } private: // 成员变量 int* array; size_t capacity; size_t top; }; // 分号不能省略 void Stack::Init(int n = 4) { array = (int*)malloc(sizeof(int) * n); if (nullptr == array) { perror("malloc申请空间失败"); return; } capacity = n; top = 0; }
👉👉 当我们在类里面写函数的声明时,类外面写定义,要加一个作用域操作符,但是作用域操作符只能访问到公有的,私有的是无法进行访问的。要注意类中的访问权限。
🤔🤔因为内联函数展开只是给编译器提供的建议,如果函数体中代码过长,编译器不会进行展开的,所以对于代码量大的函数我们仍然要写在类外进行定义,🧑🎓🧑🎓只有代码量少,且需要多次使用的函数才在类域里面定义。
三、类的实例化:
1、概念:
✨⽤类类型在物理内存中创建对象的过程,称为类实例化出对象;
✨类是对对象进⾏⼀种抽象描述,是⼀个模型⼀样的东西,限定了类有哪些成员变量,这些成员变量只是声明,没有分配空间,⽤类实例化出对象时,才会分配空间,占用内存。
✨⼀个类可以实例化出多个对象,实例化出的对象占⽤实际的物理空间,存储类成员变量。
如图:我们通过类创建一个变量(对象),此时类就被实例化了👉这里写了2个实例化对象
👉👉 就像建房子,一张图纸提供了房子的构造、框架等等,工程师通过图纸可以将房子建出来,一张图纸是不是可以建立多个房子,楼盘不就是建很多栋么,外表看上去都是一个样
2、对象大小:
👉👉在这里注意的是不能用sizeof(类名),因为我们类里面的变量只是声明(声明未开辟空间),未实例化的时候是不能计算的,编译器会报错;
👉👉应该是如下:这样就可以计算类的对象大小了
🤔🤔🤔分析⼀下类对象中哪些成员呢?类实例化出的每个对象,都有独⽴的数据空间,所以对象中肯定包含成员变量,那么成员函数是否包含呢?⾸先函数被编译后是⼀段指令,对象中没办法存),这些指令存储在⼀个单独的区域(代码段),那么对象中⾮要存储的话,只能是成员函数的指针。
🤔🤔🤔再分析⼀下,对象中是否有存储指针的必要呢,Date实例化d1和d2两个对象,d1和d2都有各⾃独⽴的成员变量_year/_month/_day存储各⾃的数据,但是d1和d2的成员函数Init/Print指针却是⼀样的,存储在对象中就浪费了(重复存储没必要)。
🧑🎓🧑🎓🧑🎓如果⽤Date实例化100个对象,那么成员函数指针就重复存储100次,太浪费了。这⾥需要再额外哆嗦⼀下,其实函数指针是不需要存储的,函数指针是⼀个地址,调⽤函数被编译成汇编指令[call地址],其实编译器在编译链接时,就要找到函数的地址,不是在运⾏时找,只有动态多态是在运⾏时找,就需要存储函数地址,这个以后涉及了再说。
✨✨换一句话:我们之前说修房子 类就是图纸,那么类的成员函数又叫做类的方法,告诉我们怎么做,🥰那么这么理解,你建房子,是不是要打地基啊,钢架等都是一样的,同样的图纸没必要浪费时间重新画这些建筑方法是不!!!直接用现成的!!然后👉成员变量就相相当于装饰(或者叫房子内搭),根据每个人喜欢的装修风格,我们就需要额外的设计,你想想我们买房子,楼盘的房子外表和内部都是一个样,买的毛坯房,但是可以让我们自己改造,通过装修改成自己喜欢的样子
类实列化的对象:内存对齐
计算大小的方法和结构体的内存对齐一样!!! 规则也是一个,但是值得注意的是,当我们类中没有成员变量的话,✨✨✨✨👉自动给分配 1 字节的内存✨✨✨✨
不会内存对齐的可以看结构体
🤔🤔为啥要内存对齐呢??
-
不是所有的硬件平台都能访问任意地址上的数据;
-
某些硬件平台只能只在某些地址访问某些特定类型的数据,否则抛出硬件异常,及遇到未对齐的边界直接就不进行读取数据了。
-
为了代码的可移植性,和提升CPU访问内存的效率,所以一定要内存对齐。本质:空间换区时间的做法。
可以上网查找一下,这里主要不是讲解这方面内容,就不多赘述了!!
四、this指针:
✨✨Date类中有Init与Print两个成员函数,函数体中没有关于不同对象的区分,那当d1调⽤Init和
Print函数时,该函数是如何知道应该访问的是d1对象还是d2对象呢?那么这⾥就要看到C++给了
⼀个隐含的this指针解决这⾥的问题。调用Init其实还会隐式传一个d1的地址给this指针
✨✨编译器编译后,类的成员函数默认都会在形参第⼀个位置,增加⼀个当前类类型的指针,叫做this指针。⽐如Date类的Init的真实原型为, void Init(Date* const this, int year,int month, int day)
✨✨类的成员函数中访问成员变量,本质都是通过this指针访问的,如Init函数中给_year赋值, this->_year = year;两种写法都行,不写this->成员变量,编译器会自动写,一般我们不写this,没必要
✨✨C++规定不能在实参和形参的位置显⽰的写this指针(编译时编译器会处理),但是可以在函数体内显⽰使⽤this指针。因为我们主动在函数里面加了,反而会发生报错,所以this指针不需要我们管,了解这个东西即可。
2个代码:
✨✨✨正常运行
#include<iostream>
using namespace std;
class a
{
public :
void print()
{
//this = nullptr;//this指针收到的地址
cout << "a::print()" << endl;//正常运行
}
private:
int _a;
};
int main()
{
//不会发生错误
a* p = nullptr;
p->print();//this指针收到的就是同类型的a* p;
return 0;
}
✨✨ 这里指针创建的变量 p ,给了空指针,但是我们传到类里面的函数是没问题的,只要不对空指针进行解引
✨✨✨运行崩溃
#include<iostream>
using namespace std;
class a
{
public :
void print()
{
//this = nullptr;//this指针收到的地址
cout << "a::print()" << endl;
cout << _a << endl;//this->_a 不能对空指针进行解引用
}
private:
int _a;
};
int main()
{
//不会发生错误
a* p = nullptr;
p->print();//this指针收到的就是同类型的a* p;
return 0;
}
✨✨这里对空指针进行了解引用,但是我们要注意的是,不会报错,但是会运行崩溃0!!
五、总结:
继续加油