类的引入
结构体
我们在C语言定义链表时,经常会写如下代码
但是我们在C++里,发现不用加struct也可以定义新变量
这是因为,在C++中结构体已经被优化成了类,而这个ListNode便被称为类名
类
在C++里,我们把所有的结构体优化成了类,在类中不仅可以定义成员变量,还可以定义成员函数,对于结构体的定义,我们更喜欢用class来表示
此刻,我们便开始接触到了C++的灵魂:面向对象
类的定义
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分
号不能省略。
类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者
成员函数。
类的定义规则
类的定义方式一般分为以下两种:
- 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内
联函数处理。 - 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::
第一种:全部放在类体中
第二种:声明在头文件,定义在源文件
一般来说,在工程里我们一般使用第二种方式。
成员定义规则
我们来看以下场景:
class date
{
public:
void print()
{
cout << year << "/" << month << '/' << day << endl;
}
void Date(int year=1900, int month=1, int day=1)
{
year = year;
month = month;
day = day;
}
private:
int year;
int month;
int day;
};
int main()
{
date a;
a.Date(2023, 7, 31);
a.print();
}
我们在类中的Data函数完成了类的初赋值,当我们运行该程序,我们认为的结果应该是赋值了2023年7月31日,但是实际的结果是
为什么?
我们再来仔细读一下程序
我们发现,在类中定义的变量和传入的参数名是相同的,编译器对year=year的赋值是传入的参数名对自身的赋值,而不是对类中的变量进行赋值
(关于为什么不是对类中变量进行赋值,在后面this指针会有详细讲解)
所以,我们要尽量将变量名称区分开,在工程里我们一般将类中名称前加上"_"
class date
{
public:
void print()
{
cout << _year << "/" << _month << '/' << _day << endl;
}
void Date(int year=1900, int month=1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
date a;
a.Date(2023, 7, 31);
a.print();
}
此时,程序的运行便正常了
类的访问限定符
访问限定符由来
我们在C语言里定义栈时,top的初取值不同会导致函数的不同,而其实现的结果是一样的。我们在取栈顶元素的函数里根据个人习惯会取不同的top值,但是,有些人可能会为了方便不使用取栈顶元素函数,而是直接访问a[top],最终取到一个随机值
此刻,为了避免某些自以为是的人导致程序崩溃,C++定义类可以让他们无法取到top的值
访问限定符
在C语言中,有3个访问限定符:
- public(公有)
- protected(保护)
- private(私有)
其中,public表示外部可以访问的成员,而protected和private表示外部不可访问的成员(在此处其作用类似) ,一个访问限定符的作用域在下一个访问限定符或类定义终止时结束
struct和class的区别
C++为了可以兼容C语言,struct便沿袭了C语言的习惯,所有成员都可以直接访问(即默认均为public),而class则是默认均为private
类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 ::
作用域操作符指明成员属于哪个类域。
第一个_a无法识别,第二个_a可以识别
类的实例化
用类类型创建对象的过程,称为类的实例化
类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没
有分配实际的内存空间来存储它;比如:入学时填写的学生信息表,表格就可以看成是一个
类,来描述具体学生信息。
一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量
做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设
计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象
才能实际存储数据,占用物理空间
类的存储方式
比如以下这一个类,与C语言不同的是既有成员函数又有成员变量,我们应该如何计算类的大小呢?
1.普通情况
我们运行后发现,整个类A只有4个字节,即只占用了int的存储空间,我们不难推断出,类中的成员函数是不占用空间的,因为如果每创建一次新变量便创建一次成员函数,其空间消耗会大大提升
就好比类是一个小区,定义一个新变量后相当于为这一个变量搭建了一栋楼房,而成员函数就是小区的公共区域,不会为每一个类二次搭建.
2.内存对齐
类同样和结构体遵循内存对齐的规则
具体规则可以去看结构体内存对齐规则,在此不做过多赘述
3.只含有成员函数
由情况1我们可以知道,在一般情况下,类不会为成员函数开辟空间,而若只有成员函数,我们运行会发现,情况和我们想象并不一样
这是因为,在没有其他成员变量的情况下,如果没有空间为类占位,我们将无法访问到这个类以及其中的函数,所以这一个字节实际上只是为类进行占位
This指针
This指针的引入
我们从类的存储方式可以得知,类不会为每一个类变量开辟一个成员函数的空间,每一个变量调用函数时调用的是相同的函数,那么,我们该如何区分调用函数的对象呢?
在编译器中的解决方案是,每一个成员函数的第一个参数都是this指针,在我们调用成员函数时,会先传入这个变量的地址
这个this指针是透明的,即不需要使用者传入,也不需要编写函数的人写入this指针,编译器自动完成,虽然this指针在参数中并没有声明,仍然可以使用这个this指针
这时,我们便可以解决区分不同成员的问题
class date
{
public:
void print()
{
cout << year << "/" << month << '/' << day << endl;
}
void Date(int year = 1900, int month = 1, int day = 1)
{
//通过this指针来访问变量中的成员
this->year = year;
this->month = month;
this->day = day;
}
private:
int year;
int month;
int day;
};
int main()
{
date a;
a.Date(2023, 7, 31);
a.print();
}
This指针的特性
- this指针的类型:类类型* const,即成员函数中,不能给this指针赋值.
- 只能在“成员函数”的内部使用
- this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给
this形参。所以对象中不存储this指针. - this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传
递,不需要用户传递
This指针可以是空指针吗
class A
{
public:
void print()
{
cout << "print" << endl;
}
void printA()
{
cout << "printA " << _a << endl;
}
private:
int _a = 1;
};
int main()
{
A* a = nullptr;
a->print();
a->printA();
}
我们分别调用print和printA,我们发现,只有printA会运行崩溃,而print程序会正常运行,说明,如果我们不使用this指针中的元素,程序则不会受到影响,而一旦我们访问到了this指针之中的元素,程序便会报错