1.类的定义
1.1 类的定义格式
- 类的定义格式是class关键字+类名,以大括号{}为主体,最后结尾必须带分号。大括号里面的内容被叫做类的成员;类里面定义的变量被叫做成员变量或者属性;类中定义的函数被叫做类的方法或者成员函数。
- 为了区分成员变量和参数,我们通常在成员变量后面或前面加上下划线(_)亦或者是m开头,这只是通常的做法,C++里面并没有规定,工作之后也看公司具体如何区别。
- C++里面struct也可以定义类,方法和class一样。而struct也升级成了类,在里面是可以定义函数的,但是通常我们需要定义类的时候还是会用class关键字去定义。
- 类里面的成员函数都是inline类型的。
class Data
{
public:
//成员函数
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
//成员变量
int _year;
int _month;
int _day;
};
1.2 访问修饰符
- C++用类将成员变量和方法封装在一起,访问修饰符的作用就是选择性地让用户去访问那些成员和调用方法。
- 在上面我们可以看到public、private两个访问修饰符,其实还有一个是protected。这几个都可以按照字面意思去理解public就是公共的,可以供外部去访问或者调用;而private和protected就是不给外部去访问或者调用。(现阶段小编知识不够完备,不能讲清private和protected的区别,后面的博客会弄明白的。)
- 修饰符的修饰区域是从自身开始到下一个修饰符之间的部分,如果是最后一个修饰符,则修饰的部分就是自身到类结束的 };
- 在class关键字修饰的类里面:如果一个成员(函数或者变量)不加修饰符,则默认为private;而struct就相反,默认为public。
- 一般成员变量都会被private或protected修饰保护起来,而成员函数则会被public修饰。
class Stack
{
void Push(int x)
{
a[size++] = x;
}
public:
void Init(int n = 4)
{
a = (int*)malloc(sizeof(int) * n);
if (a == nullptr)
{
perror("malloc fail");
return;
}
capacity = n;
size = 0;
}
private:
int* a;
int capacity;
int size;
};
struct _Stack
{
void Init(int n = 4)
{
a = (int*)malloc(sizeof(int) * n);
if (a == nullptr)
{
perror("malloc fail");
return;
}
capacity = n;
size = 0;
}
private:
int* a;
int capacity;
int size;
};
int main()
{
Stack s1;
s1.Init();
//说明class修饰的类里面没有访问修饰符的成员默认是private
s1.Push(4);
_Stack s2;
//说明struct修饰的类里面没有访问修饰符的成员默认是public
s2.Init();
return 0;
}
1.3 类域
- 类的定义形成了一个新的域叫类域,类的所有成员都被定义在这个域里面。如果需要在类域外面定义成员,则需要使用 :: 作用域操作符来指明成员属于哪个类域。
- 如果我们在下面没有给Init指定类域,那么编译器就会认为这是一个全局定义的函数,而里面的capacity和size是类域里面的变量,在全局无法找到定义,会导致报错。
class Stack { public: void Init(int n = 4); private: int* a; int capacity; int size; }; //声明和定义分开,需要指定类域 void Stack::Init(int n) { a = (int*)malloc(sizeof(int) * n); if (a == nullptr) { perror("malloc fail"); return; } capacity = n; size = 0; }
2.类的实例化
2.1 实例化的概念
如果大家在学校学习过Java,那么这一点大家应该不难理解。
- 用类类型在物理内存里创建对象的过程被成为实例化。
- 类是对象的一种抽象描述,就像是一栋房子的图纸,图纸就是对房子的抽象描述,我们通过图纸可以造出许多相同的房子,这就是类。
- 而类里面的成员变量就是对象的各种属性,里面只有声明变量,并没有申请内存,把“房子”造出来才会有内存。有了实物,也就是实例后,才会有物理意义上的内存。
class Data { public: //Init()是全缺省函数 void Init(int year = 2024, int month = 1, int day =1) { _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 d2; //分别调用类里面的两个成员函数 d1.Init(2024, 8, 15); d1.Print(); d2.Init(); d2.Print(); return 0; }
2.2 对象的大小
我们来分析一下对象实例,里面包括了成员变量和成员函数。对象实例化后,成员变量都会占用一定的空间,那成员函数呢。首先,函数在编译过后是一段指令,对象中没办法储存,这些指令储存在一个单独的区域。假如成员函数以函数指针的形式储存,我们创建了100个对象实例,那么我们就需要重复储存100个函数指针,这就浪费了很多空间。所以成员函数是不算在对象实例的大小。其实函数指针是不需要存储的,函数指针是⼀个地址,调⽤函数被编译成汇编指令[call地址],其实编译器在编译链接时,就要找到函数的地址,不是在运行时找,只有动态多态是在运行时找,就需要存储函数地址,这个我们以后会讲解。
对象的具体大小和计算结构体大小的规则一样要内存对齐:
• 第⼀个成员在与结构体偏移量为0的地址处。
• 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
• 注意:对齐数=编译器默认的⼀个对齐数与该成员大小的较⼩值。
• VS中默认的对齐数为8
• 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
• 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
如有需要,可以参考本人写的结构体的文章
那我们来小小练习一下:
class Data
{
public:
void Init(int year = 2024, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
class Bread
{
public:
void Toast()
{
;
}
private:
int price;
int weight;
char* name;
};
class Book
{
};
int main()
{
Data d1;
Bread bread;
Book book;
cout << "d1的大小为:" << sizeof(d1) << endl;
cout << "bread的大小为:" << sizeof(bread) << endl;
cout << "book的大小为:" << sizeof(book) << endl;
return 0;
}
这里可以看到,Book类里面什么都没有,这一个字节的大小就是来占个位,表示这个类存在。所以碰到空的类,不要说它的大小为0哦。
3.this指针
- 在前面的一段代码里面,我们通过Data创建了两个实例,我们都调用了Data类里面的两个函数,那么函数是如何知道是d1在调用还是d2在调用呢?这里C++就创建了this指针来实现。
- 在编译器编译后,类的成员函数都默认在第一个参数前面加上本类类型的this指针,比如Data类里面的Init函数实际的样子是Init(Data* const this,int year,int month,int day)。
- 类的成员函数访问成员变量,本质都是通过this指针来访问的,比如 _year = year等价于this->_year = year;
- • C++规定不能在实参和形参的位置显示的写this指针(编译时编译器会处理),但是可以在函数体内显示使用this指针
class Data { public: //void Init(Data* const this,int year = 2024, int month = 1, int day =1) 不能这样写 void Init(int year = 2024, int month = 1, int day =1) { // this->_year = year; 等价 _year = year; _year = year; _month = month; _day = day; } void Print() { cout << _year << "/" << _month << "/" << _day << endl; } private: int _year; int _month; int _day; };