本章内容相对于之后的类和对象中和下都比较简单,但是整体还是有些难度的。

目录
1.类的定义
1.1类定义格式
(1)class为定义类的关键字,类的定义的格式为:class name{main};其中name是类的名字,main包含了类的主体内容。其次类定义结束时有个";"不能省略,和之前的结构体一样。类体中的内容称为类的成员;类中的变量称为类的属性或成员变量;类中的函数称为类的方法或者成员函数。
类可以存储变量和函数,这比C语言学过的结构体又增加了一个存储的类型。可以说C++中的类替换了C语言的结构体的作用。
(2)为了区分成员变量,一般习惯上成员变量会加一个特殊表示,但不是强制的。
cpp
#include<iostream>
using namespace std;
class Date
{
//普通定义我们可能看不懂,如:
//int year;
//int month;
//int day;
// 变量的初始化
//void Init(int year, int month, int day)
//{
//year = year;
//month = month;
//day = day;
//}
//其实我们也可以把这个形参换一下,换为a,b,c这种简单的字符
//虽然方便了,但是我们怎么区分这个形参到底是year还是month还是day
//所以我们一般写函数的形参都是浅显易懂的形参
//我们可以在类里面定义的变量在原来的变量基础上加个_或者其他的
int _year;
int _month;
int _day;
//设为公有,否则我们会访问不了这个函数
//这个之后也会讲
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//打印变量
//我们一般在类里面实现函数,因为之后在调用时不能随便访问变量(之后讲)
void Print()
{
cout << _year << ' ' << _month << ' ' << _day << endl;
}
};
int main()
{
//类的实例化(之后会讲)
//这和结构体的差不多
Date d;
d.Init(2025, 4, 12);
d.Print();
return 0;
}
由于里面很多没学,但是主要是介绍基本知识以方便之后我们测试函数是否写对。运行结果如下:
(3)C++中struct也可以定义类,C++兼容C语言中struct的用法,同时struct升级成了类,一般情况下我们还是用class定义类。
但是C++在写结构体(类)时就不用struct name a;这样了,而是把struct去掉直接用类名了即name a;即可,当然之前的写法也不会报错。
(4)定义在类里面的成员函数默认为inline。
这个不要多问,规定就是规定!
1.2访问限定符
(1)C++一种实现封装方式,用类将对象的属性与方法(变量与函数)结合在一起,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
我们在类里面的变量如果不加以限制的情况下就会被外部随意修改,这样很难维持数据的稳定,所以我们需要把大部分变量设置为私有(private/protected),把大部分函数设置为公有(public)方便调用,类里面的数据需要修改的情况下需要经过类中的函数来修改,因为我们类中的函数是我们自己写的,所以更安全些,通过这些函数来保证变量的稳定。
(2)public修饰的成员在类外可以直接被访问,protected和private修饰的成员在类外不能直接被访问。在之后讲的继承章节将会体现出protected和private的区别。
(3)访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符为止,如果后面没有访问限定符,作用域就到类结束的位置为止。
(4)class 定义成员没有被访问限定符修饰时,**默认为private,struct默认为public。**可以有多个访问限定符!
这个东西的作用在之后会逐渐体现出来,所以我们现在就没必要管这么多,只要学就可以了,只要讲了就有用处!
1.3类域
类和命名空间一样都定义了一个新的作用域(命名空间知识见:https://blog.csdn.net/2401_86446710/article/details/147100008之前我写的博客:C++入门基础),类的所有成员都在类的作用域中,在类体外定义成员时,需要使用::作用域操作符指明成员属于哪个区域。这也隔开了类和类的命名冲突。
而类的声明如下形式:
cpp
//类的定义
class Date
{
int _year;
int _month;
int _day;
public:
//初始化
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//打印变量
void Print()
{
cout << _year << ' ' << _month << ' ' << _day << endl;
}
};
//类的声明
class Date
{
int _year;
int _month;
int _day;
public:
//初始化
void Init(int year, int month, int day);
//打印变量
void Print();
};
一般类的声明是放在头文件中的,只不过这是为了方便而已。
我们发现:如果是声明,只要把函数定义换成函数声明就可以了。其他的不用变。
如果类的定义和声明分离,我们就需要用::指定类域,这个我们需要靠例子来说:
在Stack.h中包含类的声明:
cpp
//类的声明
class Stack
{
public:
//成员函数
void Init(int n = 4);
private:
//成员变量
int* arr;
size_t capacity;
size_t top;
};
在Test.cpp中包含类的定义:
cpp
#include"Stack.h"
void Stack::Init(int n)
{
arr = (int*)malloc(sizeof(int) * n);
if (arr == nullptr)
{
perror("malloc申请空间失败");
return ;
}
capacity = n;
top = 0;
}
所以这就是指定类域的作用,其次定义中不能包含缺省参数!具体原因不要追究了!
2.实例化
2.1实例化概念
(1)用类类型在物理内存中创建对象的过程,称为类实例化对象;
如:我们创建了一个名字为Date的类,那么在主函数或者其他函数中加Date name;则Date name就是类实例化对象。
(2)类是对象进行的一种抽象描述,是一个模型一样的东西,限定了类有哪些成员变量,这些成员变量只有声明,没有分配空间,用类实例化对象时,才会分配空间。且一个类可以实例化多个对象。
简单理解就是:我们要建一个房子就需要设计图,而设计图还没有在土地上占地等等操作,而设计图可以建造出多个房子,这就相当于分配内存的过程就是国家批准你建设房子和电脑给你分配空间一样。
(3)一个类可以实例化多个对象。占用实际的物理空间,存储类成员变量。
我们在定义类的时候只是定义了还没分配空间,而实例化对象就是分配空间的过程。
2.2对象大小
C++和C语言一样要保持内存对齐的规则(这个自己去搜其他人写的博客,因为讲起来很麻烦而且比较难懂)。若Date d;这个Date就和之前的代码定义的Date一样。如果求sizeof(d);sizeof(Date)的值我们发现最终结果为12,也就是说除了三个成员变量占了空间,成员函数都没占空间。为什么呢?
首先函数在被编译后是一段指令,对象中无法存储,这些指令存储在一个单独的区域,如果对象一定要存储的话,只能为成员函数的指针。但没有必要存储指针,这样太费空间了。函数指针也不需要存储的,它是一个地址,调用时的编译链接阶段,就要找到函数的地址,而不是在运行时找,只有动态多态是在运行时找(之后会讲)。若实例化多个对象后,则调用函数都是一样的,所以就没必要存储起来,也就不需要占用空间了。而每一个实例化对象必然会有每个对象的变量的值改变,所以需要把变量存储起来而非函数。
3.this指针
一个类如果实例化多个对象,那么为何能保证出每一个对象的变量的精准打印和每一个对象的函数的精准调用呢?
这就是C++的比较好的一个点(放在最后讲)。因为有this指针的存在。
(1)编译器在编译后,类的成员函数默认都会在形参第一个位置增加一个当前类类型的指针即this指针。如:
cpp
//类的定义
class Date
{
int _year;
int _month;
int _day;
public:
//初始化
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//Init的真是原型为
//void Init(Date* const this,int year,int month,int day)
//打印变量
void Print()
{
cout << _year << ' ' << _month << ' ' << _day << endl;
}
//Print真实原型为
//void Print(Date* const this)
};
int main()
{
//类的实例化(之后会讲)
//这和结构体的差不多
Date d;
d.Init(2025, 4, 12);
//真实调用传参是:
//d.Init(&d,2025,4,12)
d.Print();
//真实调用传参是:
//d.Print(&d)
return 0;
}
所以就不会出现多个对象时每一个一样了。那为什么还能正常打印结果呢?没有this指针的解引用啊!
this指针不是简单的指针,它相当于类的指针,类比于结构体就是用->来得到里面的成员,而this->是在之后添上去的:
(2)类的成员函数访问成员变量,本质都是通过this指针来访问的。
实例化对象时会有一个地址产生,而this指针就是指向这个地址的,而这个地址刚好就包含了类里面的各个成员,然后传递给了this指针。且在成员函数都加上了this->就不许要我们自己来写了,如:
cpp
//类的实际
class Date
{
int _year;
int _month;
int _day;
public:
//初始化
void Init(Date* const this,int year, int month, int day)
{
this->_year = year;
this->_month = month;
this->_day = day;
}
//Init的真是原型为
//void Init(Date* const this,int year,int month,int day)
//打印变量
void Print(Date* const this)
{
cout << this->_year << ' ' <<this-> _month << ' ' <<this-> _day << endl;
}
//Print真实原型为
//void Print(Date* const this)
};
虽然这代码是报错的,但是我们只是理解这个this指针而已,我们不需要显式写成这样,且我们在函数参数第一个不要改为Date* const this,直接在变量前加->而且不要直接在成员变量定义时就进行this->了,我们是在调用函数时传的参数隐含了this而已。我们不在形参加Date* const this,因为这样会报错,但是我们只在函数调用成员变量时加this->是没有问题的。
为什么是Date* const而不是Date const*?
const有两种形式,第一种*在const前面,第二中*在const后面,前者const修饰this,代码this本身不可以修改,但是指针却可以修改;而后者则相反,我们是不想要this修改所以我们是这样写。
为什么这样就方便了呢?
我们之前用C语言时,总要传递一个指针来改变该点的值等等操作,而现在在这个this指针都隐含了,不需要我们自己去直接传参了,不仅不用创建一个额外的指针了,而且代码也变简单了!
4.练习
4.1选择题1
下面代码的运行结果是()?
A.编译报错 B.运行崩溃 C.正常运行
cpp
#include<iostream>
using namespace std;
class A
{
public:
void Print()
{
cout << "A::Print()" << endl;
}
private:
int _d;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
首先我们分析一下编译错误是语法错误,而里面没有语法错误,故不会报编译错误,所以不选A;
p->Print()实际转化出来的动作是call Print()
那p的作用是什么?
让编译器知道Print()在哪里,去A这个类里面去找。虽然是空指针,但不解引用,故不会报错,但是我们也要注意:p也会作为指针传给this,则会打印出this为00000000。故选C。
那么如果把cout<< "A::Print()"<<endl替换为cout<< _a <<endl呢?
从之前的分析直接把A排除掉,由于有空指针的解引用,则会使运行崩溃。相当于NULL->_a而我们是空指针的解引用。所以这时候选B。
4.2选择题2
this指针存在内存的哪个区域的()?
A、栈 B、堆 C、静态区 D、常量区 E、对象里面
不要想当然哦!
分析一下:E不能选!之前算对象大小时,没有算this指针(因为我们之后才讲的this指针),所以不能讯。
B堆不能选!堆是动态开辟空间才会用到,一般是malloc等动态开辟空间函数才会有,也不能选。
分析D:常量区就是不可修改的区域。我们要注意:const修饰的变量不一定放在常量区,C++中const修饰的是常变量,也可以修改!为什么?
我们在const char*用cout打印时为字符串,而不会打印地址,故要打印地址要么就用printf打印要么就cout<<(void*)str<<endl (str是const char* 类型的),所以是可以修改的(强制转换)。之后会讲具体原因,而只有const char* 才是存在常量区里面的(我不确定哦,是举个例子),而其他的则不是。D不能选。
那么就从A、C里面选。
C静态区是存常量、全局变量的地方,而this指针生命周期不是全局的,所以不能。
故选A堆。this指针相当于一个参数,是一个形参为局部变量,但有些编译器把this放到寄存器,这个就不要纠结了。
5.总结
类和对象上的知识不是很难,但是类和对象中的知识会让你爆炸的,这篇才6000字左右,而类和对象中我觉得至少12000字。但是也没有办法,如果喜欢的可以一键三连哦,下讲再见!
