Hello,大家好,接下来,我将要为大家带来类和对象部分的精彩内容。
目录
1.类的定义
1.1类定义格式
(1).class为定义类的关键字,后面紧跟这个类的名字,{ }中为类的主体,注意类定义结束时后面的分号不能省略掉。类中的内容称为类的成员:类中的变量称为类的属性或成员变量,类中的函数称为是类的方法或者成员函数。(C++规定:类的名字就是类的类型)
#include<iostream>
using namespace std;
class date//date是类的名字
{
int a;
int b;
int c;//a,b,c是类的成员变量
void print(int x,int y)//print函数是类的成员函数
{ }
};
int main()
{
date d1;//定义一个类型为date类型的类,注:类的名字就是类的类型
return 0;
}
(2).为了区分成员变量,⼀般习惯上成员变量会加⼀个特殊标识,如成员变量前⾯或者后⾯加_ 或者 m开头,注意C++中这个并不是强制的,只是⼀些惯例,具体看公司的要求。
#include<iostream>
using namespace std;
class date
{
int _year;//m_year
int _month;//m_month
int _day;//m_day
//在成员变量前面加上"_"或者"m_"是为了更好的区分
};
int main()
{
date d1;
return 0;
}
(3).C++中struct也可以定义类,C++兼容C中struct的用法,同时struct升级成了类,明显的变化是struct中可以定义函数,⼀般情况下我们还是推荐用class定义类。
#include<iostream>
using namespace std;
struct date//struct定义类
{
int _year;
int _month;
int _day;
void print(int x, int y)
{ }
};
struct node
{
struct node* left;//这种是C语言定义指针的方式
node* right;//在C++中规定,类的名字就是类的类型,因此,在C++中还可以写成这一句代码
};
int main()
{
date d1;
struct node d2;
node d3;
return 0;
}
(4).定义在类⾯的成员函数默认为inline。
1.2访问限定符
(1).C++⼀种实现封装的⽅式,⽤类将对象的属性与⽅法结合在⼀块,让对象更加完善,通过访问权限选择性的将其接⼝提供给外部的⽤⼾使⽤。
(2).public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访问,protected和private是⼀样的,以后继承章节才能体现出他们的区别。
访问限定符:{ public
{ private
{ protected
#include<iostream>
using namespace std;
class date
{
public:
void print()
{
cout << "_year" << " " << "_month" << " " << "_day" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
date d1;
d1.print();//print函数可以在类域外被访问
return 0;
}
#include<iostream>
using namespace std;
class date
{
public:
void print()
{
cout << _year << " " << _month << " " << _day << endl;
}
int _year;
int _month;
int _day;
};
int main()
{
date d1;
d1._year = 2024;
d1._month = 8;
d1._day = 4;//_year,_month,_day类中的这三个变量是被public修饰的,因此,这三个变量可以在类域外被访问到
d1.print();//2024 8 4
return 0;
}
(3).访问权限作⽤域从该访问限定符出现的位置开始直到下⼀个访问限定符出现时为⽌,如果后⾯没有访问限定符,作⽤域就到 }即类结束。
(4).class定义成员没有被访问限定符修饰时默认为private,struct默认为public。
(5).⼀般成员变量都会被限制为private/protected,需要给别⼈使⽤的成员函数会放为public。
1.3类域
(1).类定义了⼀个新的作⽤域,类的所有成员都在类的作⽤域中,在类体外定义成员时,需要使⽤ :: 作⽤域操作符指明成员属于哪个类域。
(2).类域影响的是编译的查找规则,下⾯程序中Init如果不指定类域date,那么编译器就把print函数当成全局函数,那么编译时,找不到_year等成员的声明/定义在哪⾥,就会报错。指定类域date,就是知道print是成员函数,当前域找不到的_year等成员,就会到类域中去查找。
#include<iostream>
using namespace std;
class date
{
int _year;
int _month;
int _day;
//成员函数的声明
int print(int x, int y);
};
int date::print(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}//在定义的前面必须要加上"print::",如果不加上的话,系统就会报错,因为系统不认识_year,_month,和_day这三个变量,系统就会报错,加上之后,就会到date类域中去找这三个变量
int main()
{
date d1;
return 0;
}
2.实例化
2.1实例化概念
(1).⽤类类型在物理内存中创建对象的过程,称为类实例化出对象。
(2).类是对象进⾏⼀种抽象描述,是⼀个模型⼀样的东西,限定了类有哪些成员变量,这些成员变量只是声明,没有分配空间,⽤类实例化出对象时,才会分配空间。
(3).⼀个类可以实例化出多个对象,实例化出的对象 占⽤实际的物理空间,存储类成员变量。打个⽐⽅:类实例化出对象就像现实中使⽤建筑设计图建造出房⼦,类就像是设计图,设计图规划了有多少个房间,房间⼤⼩功能等,但是并没有实体的建筑存在,也不能住⼈,⽤设计图修建出房⼦,房⼦才能住⼈。同样类就像设计图⼀样,不能存储数据,实例化出的对象分配物理内存存储数据。
2.2对象大小
分析⼀下类对象中哪些成员呢?类实例化出的每个对象,都有独⽴的数据空间,所以对象中肯定包含成员变量,那么成员函数是否包含呢?⾸先函数被编译后是⼀段指令,对象中没办法存储,这些指令存储在⼀个单独的区域(代码段),那么对象中⾮要存储的话,只能是成员函数的指针。再分析⼀下,对象中是否有存储指针的必要呢,Date实例化d1和d2两个对象,d1和d2都有各⾃独⽴的成员变量_year/_month/_day存储各⾃的数据,但是d1和d2的成员函数Init/Print指针却是⼀样的,存储在对象中就浪费了。如果⽤Date实例化100个对象,那么成员函数指针就重复存储100次,太浪费了。这⾥需要再额外哆嗦⼀下,其实函数指针是不需要存储的,函数指针是⼀个地址,调⽤函数被编译成汇编指令[call 地址], 其实编译器在编译链接时,就要找到函数的地址,不是在运⾏时找,只有动态多态是在运⾏时找,就需要存储函数地址,这个我们以后会讲解。
总结:对象的大小就是对象中的成员变量所占据的空间。
上⾯我们分析了对象中只存储成员变量,C++规定类实例化的对象也要符合内存对⻬的规则。
内存对齐规则
(1).第⼀个成员在与结构体偏移量为0的地址处。
(2).其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
(3).注意:对⻬数 = 编译器默认的⼀个对⻬数 与 该成员⼤⼩的较⼩值。
(4).VS中默认的对⻬数为8
(5).结构体总⼤⼩为:最⼤对⻬数(所有变量类型最⼤者与默认对⻬参数取最⼩)的整数倍。
(6).如果嵌套了结构体的情况,嵌套的结构体对⻬到⾃⼰的最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体的对⻬数)的整数倍。
#include<iostream>
using namespace std;
class student
{
char _name;
int _age;
char _sex;
};
int main()
{
student s1;
cout << sizeof(s1) << endl;//12
return 0;
}
紧接着,我们来看一下如果我们定义了一个类,这个类中没有成员变量,那么,它的大小会是多少呢?OK,如果按照我们上面的那种方式来算的话,毫无疑问,大小是0,我们来事实求证一下。
#include<iostream>
using namespace std;
class student
{};
int main()
{
student s1;
cout << sizeof(s1) << endl;//1
return 0;
}
通过上面的代码我们可知,当我们的类中没有成员变量的话,编译器给开的空间是1个字节的大小的空间,解析:因为这里如果一个字节都不给的话,那么我们要怎样去表示我们创建的这个对象存在过呢?(对象大小为0,就是所占空间为0,那么编译器就不会给分配空间,那么就无法证明该对象存在过)所以这里给1个字节,纯粹就是为了占位来标识对象存在。
3.this指针
前情提要:先看一下代码:
#include<iostream>
using namespace std;
class date
{
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()
{
date d1;
date d2;
d1.init(2024, 8, 4);
d2.init(2024, 8, 5);
d1.print();//2024 8 4
d2.print();//2024 8 5
return 0;
}
OK,大家看上面的这个代码,date类中有 init 与 print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调⽤init和print函数时,该函数是如何知道应该访问的是d1对象还是d2对象呢?那么这⾥就要看到C++给了⼀个隐含的this指针解决这⾥的问题。
(1).编译器编译后,类的成员函数默认都会在形参第⼀个位置,增加⼀个当前类类型的指针,叫做this指针。⽐如Date类的Init的真实原型为, void Init(Date* const this, int year,int month, int day)
// void init(date* const this, int year, int month, int day);
// void print(date* const this);
// init(date* const this, int year, int month, int day);
d1.inti(&d1,2024, 8, 5);
// void print(date* const this);
void print(&d1);
// init(date* const this, int year, int month, int day);
d2.init(&d,22024,8,4);
// void print(date* const this);
void print(&d2);
(2).类的成员函数中访问成员变量,本质都是通过this指针访问的,如Init函数中给_year赋值, this->_year = year。
void init(int year, int month, int day)
{
this->_year = year;
this->_month = month;
this->_day = day;
}
void print()
{
cout << this->_year << " " << this->_month << " " << this->_day << endl;
}
(3).C++规定不能在实参和形参的位置显⽰的写this指针(编译时编译器会处理),但是可以在函数体内显⽰使⽤this指针。
#include<iostream>
using namespace std;
class date
{
public:
void init(int year, int month, int day)
{
this->_year = year;
this->_month = month;
this->_day = day;
}
void print()
{
cout << this->_year << " " << this->_month << " " << this->_day << endl;
}
private:
int _year;
int _month;
int _day;
};//this指针可有可无
int main()
{
date d1;
d1.init(2024, 8, 5);
d1.print();//2024 8 5
return 0;
}
补充知识
接下来,我们来看一段代码:
#include<iostream>
using namespace std;
class date
{
public:
void print()
{
cout << "print()" << endl;
}
};
int main()
{
date* d1=nullptr;
d1->print();//print()
return 0;
}
这段代码我们看上去是存在一定的问题的,就是有的同学认为d1指针都指向空指针了,那么d1->就是对空指针解引用了,这不是一个明显的错误吗?但是当我们真正去运行起来的时候我们就会发现编译器居然正常的运行下来了,它并没有报出我们所想的那种错误,我们来解析一下。
print函数在编译链接的时候,函数就会被编译成一段指令,这段指令的地址不在对象中,在对象的外面的某一个地方,而之所以要加上 " d1 -> " 有2个原因:
(1).因为print函数它属于是d1的成员函数,只有通过d1才能找到print函数。
(2).因为print函数中的this指针,要传d1的地址。
好了,今天的精彩内容就到这里了,谢谢大家的观看。