【C++】--- 类和对象(上)
类和对象(上)
- 1. 类的定义
- 1.1 类定义格式
- 1.2 访问限定符
- 1.3 类域
- 2. 实例化
- 2.1 实例化概念
- 2.2 对象大小
- 3. this指针
- 4. C和C++中Stack的对比
1. 类的定义
1.1 类定义格式
class是为定义类的关键字,定义类的语法为class 类名 { };,{}中为类的主体,类体中内容称为类的成员:类中的变量称为类的属性或者成员变量;类中的函数称为类的方法或者成员函数。
下面给出一个学生类的简单定义:
classStudent{int_age;int_height;voidprint(){cout<<"这是一个学生类的方法"<<endl;}voidInit(intage,intheight){_age=age;_height=height;}};为了区分形参和实参,我们一般在成员变量的前面或者后面加上_或者m。
我们可以在创建学生类的一个实例
intmain(){Student s1;return0;}值得一提的是,在C语言中创建一个结构体实例,如果不使用typedef对结构体重命名的话,定义一个学生的结构体需要加上struct关键字,但是在C++中,这不是必要的,类名就是类型,当然结构体名也是类型。
structListNode{structListNode*next;intval;};这是C语言中单链表结点定义方式,C++兼容C的语法。
structListNode{ListNode*next;intval;};C++就可以这样定义啦!
定义在类中的成员函数默认为inline,现在知道这一点就可以了,后买我们才会较多的使用。
1.2 访问限定符
可以看到,我们无法访问任何一个成员变量和成员方法,它们都是private私有的成员,这就要引出访问限定符了。
C++提供了三种访问限定符,public修饰的成员在类外是可以访问的,proctect和private修改时的成员在类外是无法被访问到的,这里protect和private是一样的,后面继承章节才能体现出他们的区别。class定义成员的时候默认的访问限定符为private,所以实例无法访问到类的成员。
下面我们做出修改:
classStudent{private:int_age;int_height;public:voidInit(intage,intheight){_age=age;_height=height;}voidprint(){cout<<"这是一个学生类的方法"<<endl;}};访问限定符作用域从该访问限定符出现的位置开始直到下⼀个访问限定符出现时为⽌,如果后面没有访问限定符,作用域就到}即类结束,所以上述代码就是给Init()和print()函数打开了类外的访问权限,这两个函数可以被访问到。
⼀般成员变量都会被限制为private/protected,需要给别人使用的成员函数会放为public。这就要提到类和对象的第一大核心思想:封装,类相比于结构体的优势就是实现了方法和变量的集成以及数据的私有化,在C语言中定义一个Stack的话,变量和方法是分别定义的并且变量是裸露的,可以随意访问到的,这就涉及到安全性的问题。C++中类以及访问限定符解决了这个问题,我们把数据私有,只提供方法给外部的用户使用。
C++中的struct也可以定义类,C++兼容C中struct的用法,同时把struct升级为了类,明显的变化是struct中可以定义函数,但是一般情况下,我们推荐使用class来定义类,使用struct定义类成员的时候,默认的访问限定符是public。
1.3 类域
在类体外定义成员时,需要使⽤ :: 作用域操作符指明成员属于哪个类域。
例如:规范定义一个栈的时候,在Stack.h文件下的代码如下
#include<iostream>#include<assert.h>usingnamespacestd;classStack{public:voidInit(intn=4);private:int*array;inttop;intcapacity;};Stack.c的文件的代码如下:
#include"Stack.h"voidStack::Init(intn){array=(int*)malloc(sizeof(int)*n);if(nullptr==array){perror("malloc fail");return;}capacity=n;top=0;}为什么需要指定作用域呢?类域影响的是编译的查找规则,下⾯程序中Init如果不指定类域Stack,那么编译器就把Init当成全局函数,那么编译时,找不到array等成员的声明/定义在哪⾥,就会报错。指定类域Stack,就是知道Init是成员函数,当前域找不到的array等成员,就会到类域中去查找。
2. 实例化
2.1 实例化概念
- 用类类型在物理内存中创建对象的过程,称为实例化出对象。
- 类是对象进行⼀种抽象描述,是⼀个模型⼀样的东西,限定了类有哪些成员变量,这些成员变量只是声明,没有分配空间,⽤类实例化出对象时,才会分配空间。
我们后面经常用到的是一个日期类,下面给出他的代码。
#include<iostream>usingnamespacestd;classDate{public:voidInit(intyear,intmonth,intday){_year=year;_month=month;_day=day;}voidPrint(){cout<<_year<<"/"<<_month<<"/"<<_day<<endl;}private:int_year;int_month;int_day;};这里是日期类的定义,成员变量仅仅是声明,没有开空间。
- ⼀个类可以实例化出多个对象。
intmain(){Date d1;d1.Init(2020,1,1);d1.Print();Date d2;d2.Init(1,1,1);d2.Print();return0;}类实例化出对象的时候,成员变量随着实例化被定义,才会开空间。
这个时候成员变量还没有初始化,不能根据变量是否被初始化来判断变量是否被定义,当变量被开空间意味者变量被定义了。
2.2 对象大小
我们前面说到类被实例化,成员变量就定义成功了,系统就已经给成员变量开了空间,那么成员函数是否被包含呢?
intmain(){Date d1;cout<<sizeof(d1)<<endl;return0;}类对象大小的确定也要考虑内存对齐,内存对齐规则和结构体的内存对齐一致。
d1的大小为12:这说明d1对象中只存储了三个整形的成员变量,成员函数并没有被存储。其实类的成员函数被单独存储在代码段中,其实成员函数没有必要存储在对象中,因为实例化对象的数据是私有的,每个对象的数据可能不同需要单独存储,但是类的成员函数都是相同的,存储在对象中就太浪费了。
classB{public:voidPrint(){//...}};classC{};intmain(){B b;C c;;cout<<sizeof(b)<<endl;cout<<sizeof(c)<<endl;return0;}
我们看到没有成员变量的B和C类对象的大小是1,为什么没有成员函数还要给1个字节呢?这里给1字节,存粹是为了占位表示对象存在,不存储有效数据。
3. this指针
intmain(){Date d1;Date d2;d1.Init(2024,3,31);d1.Print();d2.Init(2024,3,4);d2.Print();}我们对Date类实例化出了两个对象,并分别调用它们的Init和 Print方法。
函数体中没有关于不同对象的区分,那当d1调用Print函数的时候,函数如何知道该访问d1对象还是d2对象呢?这里其实是C++给了一个隐含的this指针解决了这个问题。
//void Init(Date* const this,int year, int month, int day)voidInit(intyear,intmonth,intday){this->_year=year;//一般不会这样来写,这样写一般都是菜鸟选手this->_month=month;this->_day=day;}//void print(Date* const this)voidprint(){cout<<this->_year<<"/"<<this->_month<<"/"<<this->_day<<endl;cout<<_year<<"/"<<_month<<"/"<<_day<<endl;}//d1.Init(&d1,2929, 1, 2);d1.Init(2929,1,2);其实类的成员函数在编译的时候都会在形参的第一个位置添加一个当前类型的this指针,对象在调用成员函数的时候,在实参的第一个位置也把自己的地址传上去了,这样就做到了不同的对象调用相同的成员函数由不同的效果了。
这里提出一点,C++规定不能在实参和形参的位置显式的给出this指针,但是在成员函数体中可以给出。
4. C和C++中Stack的对比
C语言实现栈的代码
#include<stdio.h>#include<stdlib.h>#include<stdbool.h>#include<assert.h>typedefintSTDataType;typedefstructStack{STDataType*a;inttop;intcapacity;}ST;voidSTInit(ST*ps,intn){assert(ps);ps->a=(STDataType*)malloc(sizeof(STDataType)*n);if(a==NULL){perror("malloc申请空间失败");return;}ps->top=0;ps->capacity=0;}voidSTDestroy(ST*ps){assert(ps);free(ps->a);ps->a=NULL;ps->top=ps->capacity=0;}//...C++实现栈的代码
#include<iostream>usingnamespacestd;typedefintSTDataType;classStack{public:// 成员函数voidInit(intn=4){_a=(STDataType*)malloc(sizeof(STDataType)*n);if(nullptr==_a){perror("malloc申请空间失败");return;}_capacity=n;_top=0;}voidDestroy(){free(_a);_a=nullptr;_top=_capacity=0;}//成员方法privateint*_a;int_capacity;int_top;};- C++中数据和函数都放到了类里面,通过访问限定符进行了限制,不能再随意通过对象直接修改数据,这是C++封装的⼀种体现,这个是最重要的变化。
- C++中有些相对方便的语法,比如Init给的缺省参数会方便很多,成员函数每次不需要传对象地址,因为this指针隐式传递了,使用类型不再需要typedef关键字。
- 这里使用C++实现的栈跟C语言没有本质的差别,但是后面学到STL,用适配器实现Stack的时候就会体现C++的魅力。
