类的定义
格式
class为定义类的关键字,后接类名,{}中为类的主体,要注意的是类定义结束时}后面的分号不要忘记
类体中的内容称为类的成员:类中的变量称为类的属性或成员变量;类中的函数称为类的方法或成员函数
为了区别成员变量,一般习惯在成员变量中加一个特殊标记,如在成员变量前或者后面加_或者m开头(成员函数的英文首字母),但这并不是强制的,只是一些惯例,便于区分而已
class Date
{
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
int _year;
int _month;
int _day;
};
类和结构体很相似,更可以说类是结构体的plus版,在类中可以声明定义函数,在结构体可以做到的,在类里都能做到,而结构体不难做到的,类还能做到
又因为C++兼容C语言,所以在C++中也可以使用struct,且struct也可以定义类,C++中struct中和类一样也可以在里面定义函数,而且不需要加typedef就可以直接使用类名
那这么看来,两个看起来没什么区别,那我们在C++中用哪个呢?
我们一般都是推荐使用class定义类
#include<iostream>
using namespace std;
//c中struct
typedef struct ListNodeC
{
struct ListNodeC* next;
int data;
}ListNodeC;
//C++中struct
struct ListNodeCPP
{
void Init()
{
next = NULL;
data = 0;
}
ListNodeC* next;
int data;
};
int main()
{
ListNodeC p1;
ListNodeCPP p2;
p2.Init();
return 0;
}
定义在类里面的成员函数都默认为inline,也就是成员函数前面其实都是有一个inline只是编译器将它省略掉了
访问限定符
前面我们定义了一个日期类,如果我们想在类外访问它成员函数或者成员变量就会出现这样的问题
#include<iostream>
using namespace std;
class Date
{
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1._year = 2024;//error
return 0;
}
我们无法在类外实现对成员变量的赋值,报错内容中出现的private成员便是出错的原因
在 C++中有一种实现封装的方式,用类将对象的属性与方法结合在一起,使对象变得更加完善,通过访问权限选择性的将其接口提供给外部用户使用
访问权限符分为三种
public(公有访问):public修饰的成员在类外可以直接被访问
protected(保护访问)和private(私有访问)修饰的成员在类外不能直接访问
访问权限作用域是从该访问权限符出现的位置开始到下一个访问权限符出现为止,或者类结束
在class类中如果没有访问权限符修饰时,就默认为private,而struct中就默认为public
这就是为什么前面日期类的成员变量在类外定义时报错的原因
在类里面一般成员变量都会被限制为private或者protected,而给外部使用的成员函数会被定义为public
类域
在前面的学习中我们学习了三种域:全局域、局部域、命名空间域,这里我们来学习第四种域——类域
类定义了一个新的作用域,类的所有成员都在类的作用域中,如果要在类外定义成员时,就需要使用到"::"作用域操作符来指明成员属于哪个类域
类域影响的是编译查找的规则,当程序中出现两个函数名相同的函数,一个在全局域中,一个在类域中,如果不指定类域,编译器就会把它当作全局函数;如果指定了类域,那么编译器就知道它是成员函数,就会进入类域中寻找
实例化
概念
用类定义的类型在物理内存中创建对象,就称为类实例化对象
类其实是对象进行的一种抽象描述,就相当于一个模型,我们可以根据类在类外定义具有类属性的对象,定义的对象具有类的成员,而这些成员在类里面只是声明,不是定义
要区分声明和定义也很简单:开辟了空间就是定义,没有开辟空间就是声明
用类实例化出对象时,就会分配空间,这是就是定义,而在类里面的成员变量只是声明,没有分配空间
对象大小
要求实例化出来的对象的大小,它的原则和结构体的内存的对齐规则是相同的,就不在这里具体说明了,又想了解的,请移步
C语言结构体:带你深入了解结构体、学会结构体的使用-****博客
类和结构体区别在于,类里面可以定义函数,而结构体不行,那在求对象大小时要不要加上成员函数的大小呢?
我们要知道函数在被编译后是一段指令,这些指令存储在一个单独区域(代码段),对象无法存储,它只能存储函数指针,但是不同对象中的成员函数都是相同的,也就是成员函数指针都是相同的,相同的函数指针存储在不同对象中就有点浪费了,当一个类实例化了100个对象,那成员函数指针就要重复100次,就太浪费,所以成员函数不包含在对象中
那我们定义的类只有成员函数或者什么都没有的话,它实例化的对象大小为多少呢?
class Date
{
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
int _year;
int _month;
int _day;
};
class a1
{
public:
void test()
{
}
};
class a2
{
};
int main()
{
Date d1;
a1 a;
a2 b;
cout << sizeof(d1) << endl;
cout << sizeof(a) << endl;
cout << sizeof(b) << endl;
return 0;
}
this指针
概念
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, 11, 17);
d1.print();
d2.Init(2024, 11, 16);
d2.print();
return 0;
}
在这个日期类中Init和print两个成员函数,函数体中没有关于不同对象的区分,那它是如何做到应该访问哪个对象的?
这里就引申出来一个新概念:this指针
类的成员函数默认都会在形参第⼀个位置,增加⼀个当前类类型的指针,叫做this指针
如日期类中Init的原型为
void Init(Data* const this, int year, int month, int day)
所以类中成员函数访问成员变量本质上是通过this指针来访问的,C++规定this指针不能在实参和形参中显示出现,编译器在编译时会自动加上,(不需要程序员来操心),但在成员函数体内,可以显示使用this指针
如
void Init(int year, int month, int day)
{
this->_year = year;
this->_month = month;
this->_day = day;
}
要注意的是this指针存储在栈当中,它是可以改变的,根据传入对象地址,而改变指向
this指针的应用
#include<iostream>
using namespace std;
class A
{
public:
void Print()
{
cout << "A::Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
该代码中对象赋值为nullptr,它调用print函数,将相当于将空指针传入函数,但指针并没有解引用,所以程序不会报错,正常运行
#include<iostream>
using namespace std;
class A
{
public:
void Print()
{
cout << "A::Print()" << endl;
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
而这道程序中却不一样,对象在print中解引用了,因为对象为空指针,所以程序就会运行报错