
个人主页:小则又沐风
个人专栏:<数据结构>
<竞赛专栏>
<C语言>
目录
[一 类的定义](#一 类的定义)
[1. 类定义格式](#1. 类定义格式)
[二 实例化](#二 实例化)
[三 this指针](#三 this指针)
[四 总结](#四 总结)
一 类的定义
1. 类定义格式
就以下面的代码为例子Class是类定义的关键字.Date是类的名称.{}中包含的是类的主体部分,
需要注意的是类最后的分号是不能省略的.
类中的内容我们称为类的对象,在类中我们可以定义变量也可以定义函数,在类中定义的函数我们称为成员函数,在类中定义的变量我们成为成员变量,或者是类的属性.
为了区分成员变量,⼀般习惯上成员变量会加⼀个特殊标识 ,如成员变量前⾯或者后⾯加**_**或m 开头,注意C++中这个并不是强制的,只是⼀些惯例.
就比如在这里我使用的是_
C++中struct也可以定义类,C++兼容C中struct的⽤法,同时struct升级成了类,明显的变化是 struct中可以定义函数,⼀般情况下我们还是推荐⽤class定义类。
需要注意的是我们在类中定义的函数默认的是inline.
cpp
#pragma once
class Date
{
public:
// 获取某年某月的天数
int GetMonthDay(int year, int month);
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1);
// 拷贝构造函数
// d2(d1)
Date(const Date& d);
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& operator=(const Date& d);
// 析构函数
~Date();
// 日期+=天数
Date& operator+=(int day);
// 日期+天数
Date operator+(int day);
// 日期-天数
Date operator-(int day);
// 日期-=天数
Date& operator-=(int day);
// 前置++
Date& operator++();
// 后置++
Date operator++(int);
// 后置--
Date operator--(int);
// 前置--
Date& operator--();
// >运算符重载
bool operator>(const Date& d);
// ==运算符重载
bool operator==(const Date& d);
// >=运算符重载
bool operator >= (const Date& d);
// <运算符重载
bool operator < (const Date& d);
// <=运算符重载
bool operator <= (const Date& d);
// !=运算符重载
bool operator != (const Date& d);
// 日期-日期 返回天数
int operator-(const Date& d);
private:
int _year;
int _month;
int _day;
};
2.访问限定符
在我上面定义的类中我们可以看到这两个关键字**>>private>>public**
我们通过他们的中文的含义就可以大概的猜出他是什么的意思了,下面我来详细的介绍一下:

C++⼀种实现封装的⽅式,⽤类将对象的属性与⽅法结合在⼀块,让对象更加完善,通过访问权限 选择性的将其接⼝提供给外部 的⽤⼾使⽤。
public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访 问,protected和private是⼀样的,以后继承章节才能体现出他们的区别。
访问权限作⽤域从该访问限定符出现的位置开始直到下⼀个访问限定符出现时为⽌,如果后⾯没有 访问限定符,作⽤域就到}即类结束。
class定义成员没有被访问限定符修饰时默认为private,struct默认为public。
⼀般成员变量都会被限制为private/protected,需要给别⼈使⽤的成员函数会放为public。
总的来说就是我们在类中定义的变量和函数一般分为私有和共有的,如果我们使用public来修饰的话这些变量和函数就可以被访问,相反如果是private和protected就是私有的不可以别用户访问.
一般我们的成员的变量都是私有的.
3.类域类
定义了⼀个新的作⽤域,类的所有成员都在类的作⽤域中,在类体外定义成员时,需要使⽤::作⽤域操作符指明成员属于哪个类域。
也就是说如果我们在类的外面要实现类中的函数,如果我们没有指出他的类域的话,那么编译器就会默认我们的函数是全局上的函数,但是我们在函数体中会使用类中的变量,编译器就会找不到这些变量,导致报错.
所以在类外实现函数的话我们需要指明类域
比如我们要实现GetMonthDay的操作我们需要这么写
cpp
int Date:: GetMonthDay(int year, int month)
{
int a[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2)
{
if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
{
return 29;
}
}
else
{
return a[month];
}
}
二 实例化
⽤类类型在物理内存中创建对象的过程,称为类实例化出对象。
也就是所我们先造出了一个模型,之后我们会按照这个模型的样子,创造出一个变量.
类是对象进⾏⼀种抽象描述,是⼀个模型⼀样的东西,限定了类有哪些成员变量,这些成员变量只 是声明,没有分配空间,⽤类实例化出对象时,才会分配空间。
个类可以实例化出多个对象,实例化出的对象占⽤实际的物理空间,存储类成员变量。打个⽐ ⽅:类实例化出对象就像现实中使⽤建筑设计图建造出房⼦,类就像是设计图,设计图规划了有多 少个房间,房间⼤⼩功能等,但是并没有实体的建筑存在,也不能住⼈,⽤设计图修建出房⼦,房 ⼦才能住⼈。同样类就像设计图⼀样,不能存储数据,实例化出的对象分配物理内存存储数据。

cpp
#include<iostream>
using namespace std;
class Date
{
public:
void init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
std::cout << this->_year << ' ' << this->_month << ' ' << this->_day << std::endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.init(2026, 3, 28);
d1.print();
return 0;
}
就以上面的代码为例子我们在没有创建出Date变量的时候我们,这个类所占用的空间是没有的.
当我们创建出变量的时候我们才会为他分配空间.
那么我们应该如何计算类的大小呢???
2.类的大小
在之前的C语言中我们学了结构体的内存对齐的规则.在计算我们类的大小之前,我们来回忆一下,类中我们可不仅仅是包含我们的变量的,他还是包含我们的成员函数的,那么我们的成员函数的大小又该怎么计算呢?
⾸先函数被编译后是⼀段指令,对象中没办法存储,这些指令 存储在⼀个单独的区域(代码段),那么对象中⾮要存储的话,只能是成员函数的指针。再分析⼀下,对 象中是否有存储指针的必要呢,Date实例化d1和d2两个对象,d1和d2都有各⾃独⽴的成员变量 _year/_month/_day存储各⾃的数据,但是d1和d2的成员函数Init/Print指针却是⼀样的,存储在对象 中就浪费了。如果⽤Date实例化100个对象,那么成员函数指针就重复存储100次,太浪费了。这⾥需 要再额外哆嗦⼀下,其实函数指针是不需要存储的,函数指针是⼀个地址,调⽤函数被编译成汇编指 令[call地址],其实编译器在编译链接时,就要找到函数的地址,不是在运⾏时找,只有动态多态是在 运⾏时找,就需要存储函数地址,这个我们以后会讲解
上⾯我们分析了对象中只存储成员变量,C++规定类实例化的对象也要符合内存对⻬的规则。
那么我们是否还记得结构体的内存对齐的规则呢?
我们来复习一遍:
第⼀个成员在与结构体偏移量为0的地址处。
其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
注意:对⻬数=编译器默认的⼀个对⻬数与该成员⼤⼩的较⼩值。
VS中默认的对⻬数为8
结构体总⼤⼩为:最⼤对⻬数(所有变量类型最⼤者与默认对⻬参数取最⼩)的整数倍。
cpp
class a
{
private:
char _ch;
int _x;
};
class b
{
void fun1()
{
}
};
class c
{
};
int main()
{
/*Date d1;
d1.init(2026, 3, 28);
d1.print();*/
a A;
b B;
c C;
cout << sizeof(A) << endl;
cout << sizeof(B) << endl;
cout << sizeof(C) << endl;
return 0;
}
那么我们来计算一下这三个类的大小,\
我们很容易计算的是a类的大小是8,那么B,C呢??
我们运行一下看看是什么结果

我们刚刚才说我们的函数不参与类的内存空间大小的计算的,但是这是什么情况???
我们可以想一想如果不给这些类分配空间的话,我们怎么证明我们的代码中存在着这些类呢?
所以我们会分配给他们一个内存,证明他们存在.
这⾥给1字节,纯粹是为了占位标识 对象存在。
三 this指针
我们来思考一下这样的场景,我们创建两个相同类的变量,我们都调用相同的函数那么编译器怎么知道我们调用的是哪个类的函数呢???
那么这⾥就要看到C++给了 ⼀个隐含的this指针解决这⾥的问题
编译器编译后,类的成员函数默认都会在形参第⼀个位置,增加⼀个当前类类型的指针,叫做this 指针。⽐如Date类的Init的真实原型为,void Init(Date* const this, int year, int month, int day)
类的成员函数中访问成员变量,本质都是通过this指针访问的,如Init函数中给_year赋值
就是这样this->_year=year;
C++规定不能在实参和形参的位置显⽰的写this指针(编译时编译器会处理),但是可以在函数体内显 ⽰使⽤this指针。
下面我们来看几道题来深入理解一下这个this指针.
首先来看这一串代码:
cpp
#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;
}
就以我们的认知,这串代码的运行结果是什么呢???
A、编译报错 B、运⾏崩溃 C、正常运⾏
我们直接来运行看看.


但是我们的p不是空指针吗??不会出现对空指针解引用的错误吗??
虽然我们的p是一个空指针,但是我们来看我们的print的函数,我们并没有对this指针的解引用的过程,也就没有所谓的对空指针的解引用导致的bug
cpp
#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;
}
在来看这道题.他的运行结果是什么呢?
我们通过上一道题我们就可以知道这道题,就会出现我们所说的对空指针解引用的bug
我们直接来看运行结果:

我们的代码在输出a的时候崩溃了,但是并没编译报错.
最后一个问题:
我们的this指针是存储在哪一个存储的空间中?
A.栈 B.堆 C.静态区 D.常量区 E.对象⾥⾯
首先我们的this指针不会存储在我们的对象里,更多的是存储在栈里面,但是现在一般是存在寄存器中.
四 总结
类是对象的抽象模板,不占空间,实例化对象才分配内存,大小仅由成员变量按内存对齐规则决定,成员函数存于代码段不占对象空间。访问限定符实现封装,类外实现成员函数需指定类域。this 指针是成员函数隐藏参数,指向当前调用对象,用于区分不同对象,可在函数体内显式使用。
以上就是我们今天类和对象的全部的知识,希望我的分享能够帮助到你们.
之后我会继续讲解类和对象的知识.