目录
1.类的定义
在C语言中有结构体struct,它是内置类型的集合。但是他不能存储函数,如果你要对结构体里的数据进行初始化或者任何操作,就得在struct外编写函数。这不仅很不方便,而且C语言对于struct来说自由度太高了,我可以直接访问struct里的数据。高度自由带来的弊端就是不够安全,也很容易出错。所以C++提出了类的概念。
1.1类定义格式
#include<iostream>
using namespace std;
class Date
{
public:
void init()
{
_year = 1;
_month = 1;
_day = 1;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.init();
return 0;
}
如上图代码,class是定义类的关键字,Date是类的名字 ,由用户自己取。{}中是类的主体,注意}后的分号不能省略 。{}中是类的成员:**类中的变量称为成员变量或类的属性,类中的函数称为成员函数或类的方法。**这样一来,就定义出了一个基本的日期类,里面有三个成员变量为年月日,并且可以使用init函数进行初始化。
C++中struct也可以定义类,C++兼容了C语言中struct的用法,同时把它升级成了类,也就是说在C++中struct也可以存储函数,但大多数情况下还是推荐使用class定义类。
类内的成员函数默认是inline(内联函数),这是C++的规定。
1.2访问限定符

访问限定符是C++一种实现封装的方式,它可以通过限制访问权限选择性地让用户使用部分接口。被public修饰的成员在类外可以直接被访问, 被private修饰的成员在类外不能直接被访问 ,protected和private目前可以认为是一样的,在后面更新到继承章节才有区别。
访问权限作⽤域从该访问限定符出现的位置开始直到下⼀个访问限定符出现时为⽌,如果后⾯没有访问限定符,作⽤域就到 }即类结束。
class定义成员没有被访问限定符修饰时(意思就是类中没有访问限定符出现时)默认为private,struct默认为public。
一般情况下,成员变量都用private修饰,成员函数想暴露给外部成员就用public修饰。
注意:访问限定符只对类外有效,类内不受访问限定符限制,也就是说类内的函数可以随便操作被private修饰的成员变量。
下面用一段代码总结上述特性。
#include<iostream>
using namespace std;
//如果把下面的访问限定符全部删掉,class默认整个类内都被private修饰,
// 所以类内的任何成员都不能被外界直接访问
//如果是struct的话默认是被public修饰
class Date
{
public:
void init()
{
_year = 1;
_month = 1;
_day = 1;
}
//下一行出现新的访问限定符,所以这里到public就是public的作用域
private:
void clear()
{
cout << "void clear()" << endl;
}
private:
int _year;
int _month;
int _day;
//遇到},类定义结束从private到}就是private的作用域
};
int main()
{
Date d1;
d1.init();//init函数被public修饰,类外可以直接访问
//d1._day++; 这样写就会报错,因为_day被private修饰,外部不能直接访问。
//d1.clear(); 这样也会报错,clear函数被private修饰,外部不能直接访问。
return 0;
}
有了访问限定符,就限制了使用者的自由,用户要使用你的类,就必须通过你写的函数对成员变量进行操作,不能随便访问成员变量,这样就保证了只要你写的函数没问题,用户的使用就不会出错啦。
1.3类域
类定义了⼀个新的作⽤域,类的所有成员都在类的作⽤域中,在类体外定义成员时,需要使⽤ :: 作⽤域操作符指明成员属于哪个类域。所以不同的类中可以定义相同的函数,类中也支持定义重载函数哦。
#include<iostream>
using namespace std;
class Date
{
public:
void init();
private:
int _year;
int _month;
int _day;
};
//声明和定义分离时,要指明该函数属于哪个类(通过类名+::)
// 这样编译器在查找时才会到该类域中查找,否则编译器会认为该函数是普通的全局函数。
//那编译器就找不到_year这些变量的声明,就会报错
void Date::init()
{
_year = 1;
_month = 1;
_day = 1;
}
int main()
{
Date d1;
d1.init();
return 0;
}
2.实例化
2.1实例化概念
用类类型在物理内存中创建对象的过程,就叫类的实例化,简单来说,上方代码的Date d1就是使用Date类实例化出一个d1对象。
类是⼀个模型⼀样的东西,限定了类有哪些成员变量,这些成员变量只是声明,没有分配空间,⽤类实例化出对象时,才会分配空间。
一个类可以实例化出多个对象,这些对象占用实际的物理内存。打个比方,类就像一个房屋的设计图纸,图纸规定这个房屋有几个房间,房间大小和功能是什么等等,但它并不是实际的建筑。你可以用这个设计图纸创造出很多占用实际空间的房子。所以类并不存储数据 但可以用类实例化出对象存储数据。

2.2类的大小
类可以实例化出对象,那这个对象的大小是多少?首先类实例化的对象要符合内存对齐的规则
内存对⻬规则
-
第⼀个成员在与结构体偏移量为0的地址处。
-
其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
-
注意:对⻬数 = 编译器默认的⼀个对⻬数 与 该成员⼤⼩的较⼩值。
-
VS中默认的对⻬数为8
-
结构体总⼤⼩为:最⼤对⻬数(所有变量类型最⼤者与默认对⻬参数取最⼩)的整数倍。
-
如果嵌套了结构体的情况,嵌套的结构体对⻬到⾃⼰的最⼤对⻬数的整数倍处,结构体的整体大⼩就是所有最⼤对⻬数(含嵌套结构体的对⻬数)的整数倍。
其次,**对象的大小与函数无关,只算成员变量的大小。**类的成员函数都被单独存放在内存的代码区,对象在调用函数时会被编译成汇编指令[call 函数地址]。然后为成员函数建立函数栈帧从而调用该函数,也就是说所有对象调用的都是同一份成员函数。有了这些知识就能计算类的大小啦。#include<iostream>
using namespace std;#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;
}
A的大小:不管函数,只计算成员变量的大小再根据内存对齐规则。第一个_ch放在与结构体偏移量为0的地址处,占第一个字节。第二个_i为int 类型,对齐数为4,所以它在偏移量为4的地址处存储且占4个字节,所以A的大小为8个字节。
那B和C的大小是多少呢?首先不看函数,且B和C都没有成员变量,那就没有什么要存储的啦。答案是1个字节,因为没有成员变量确实没有要存储的东西,但是这个类确实存在,编译器为了表示这个对象存在,所以给了最小的一个字节。
3.this指针

如上图有一个日期类,我实例化出了两个对象并且都调用了init和Print函数。有一个问题:在2.2中我们知道所有对象调用的都是同一份成员函数,那请问Print函数并没有传任何参数,为什么两个对象调用Print函数的结果不同呢?Print函数怎么知道每个对象的成员变量的大小呢?
其实,编译器在传参过程中会多传一个参数叫做this指针也就是该对象的指针,函数体内部也是通过这个this指针来访问对象的成员变量的。只是这些工作都被编译器偷偷完成了我们看不到,这也让我们更加方便。真正的函数调用如下图

注释的调用才是真正的函数调用,每个函数的第一个参数都是this指针,由编译器将对象的指针传过去并且隐藏。所以Print函数能知道是哪个对象在调用自己。函数中的成员变量都是通过this指针访问的。
C++规定不能在实参和形参的位置显⽰的写this指针(编译时编译器会处理),但是可以在函数体内显⽰使⽤this指针。也就是说,写函数声明时你不能写this指针,传参时也不能传this指针。但是在函数体内部可以使用this指针,这是C++的规定。