系列文章目录
文章目录
- 系列文章目录
- 👑 前言
- 👑 一、什么是类,什么是对象
- 👑 二、类的引入
- 👑 三、类的定义
- 👑三、1.类的两种定义方式:
- 👑 四、类的内存计算
- 👑五、this指针
- 👑五、1.this指针的特性
- 总结
👑 前言
本文从0开始详解什么是类,什么是对象等问题。
先讲讲什么是面向对象和面向面向过程编程。
面向过程:关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
面向对象:关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
举一个简单例子:在日常洗衣服中,面向过程的思想是:每一步都精分细作,逐步解决问题。
面向对象的思想是:洗衣服这件事情可以分成几个对象,人,洗衣机,洗衣粉,衣服。
整个过程主要是人,洗衣机,衣服,洗衣粉几个对象之间交互完成的。人不需要关心具体的细节如何操作。
👑 一、什么是类,什么是对象
简单来说,类就是一群相同或者相似的东西组成的群体。比如人类,鸟类,水果类等等。
对象就是一个具体的东西,比如,人,洗衣机,电脑等。
每个对象一定是有属于的那一类,比如人,属于人类,电脑属于电子产品类等。
👑 二、类的引入
C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。比如:
之前在数据结构初阶中,用C语言方式实现的栈,结构体中只能定义变量;现在以C++方式实现,会发现结构体中也可以定义函数。
👑 三、类的定义
C++中使用class来定义类
class className
{
// 类体:由成员函数和成员变量组成
};
class是类的关键字, className是类名。
👑三、1.类的两种定义方式:
- 声明和定义全部放在类体中
注意:1.成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
2.在类内部定义成员函数,可能编译器会当成内联函数处理
2.声明和定义分离。
一般推荐使用第二种方法。
注意:使用该方法在定义前需要加上域作用限定符符号。
即成员函数名前需要加类名::
比如:
Tree.h
lass TreeNode
{
public:
//函数成员
void TreePrve(TreeNode& root);
private:
//私有成员
};
Tree.cpp
void TreeNode::TreePrve(TreeNode& root)
{
//
// ...
//
}
在Tree.cpp文件中定义函数前需要加上一个类名::
👑 四、类的内存计算
先看下面的案例:
class A1
{
public:
void f1() {}
private:
int _a;
};
class A2
{
public:
void f1() {}
};
class A3 {};
int main()
{
printf("%d\n", sizeof(A1));
printf("%d\n", sizeof(A2));
printf("%d\n", sizeof(A3));
return 0;
}
输出结果为: 4 1 1
A1中包含一个int类型的成员变量和一个成员函数
A2中只包含个成员函数
A3既没有成员变量也没有成员函数
原因:没有成员变量的类的大小为1byte,这1个byte是用来占位的,表示该对象存在,不存储有效数据。
也就是说:类的成员函数是在公共代码段中存储的。
有成员变量的类依据结构体内存的对齐规则来存储。(不知道什么是结构体内存对齐的小伙伴可以搜一下)
结论:1.类的内存大小是成员变量的大小,不包含成员函数的大小。
2.没有成员变量的类的大小是1byte,这1个byte是用来占位的,表示该对象存在,不存储有效数据。
👑五、this指针
先看下面的代码:
p1
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, d2;
d1.Init(2023, 5, 4);
d2.Init(2023, 5, 5);
d1.Print();
d2.Print();
return 0;
}
在这段代码中,定义了一个日期类,日期类的两个成员函数的功能分别是初始化和打印成员变量。
在main函数中创建两个日期对象d1 和 d2;
d1对象初始化成2023,5,4,
d2初始化成2023,5,5
上面讲过,同一个类的成员函数是存在公共代码段的,每个对象的成员函数都是相同的。
那么为什么调用同一个函数,打印的结果却不相同?
主要原因在于一个隐含的this指针。
实际上在形参部分的传递是这样的:
p2
void Print(Date*this)
{
cout <<this->_year<< "-" <<this->_month << "-"<<this->_day <<endl;
}
d1.Print(&d1)
d2也是如此。
注意:this指针不能在形参和实参部分显示传递。
也就是说,不能像p2这段代码一样写。
因为编译器已经帮我们做好了这样的工作,再次传形参和传this指针会重复。
注意:在类中的成员变量只是一个声明,不能通过下面的方式对类进行访问:
1.Date::_year
2.d1::_year
第一个写法的错误:类的成员变量只是一个声明,不能通过类名::,找到具体的成员变量。就像是建造房子,不能通过图纸找到具体的建的楼的位置在哪。
第二个写法错误:::是域作用限定符,d1是一个类实例化的对象,不是域。
👑五、1.this指针的特性
1.this指针存储在栈区空间中。
因为this是形参,this指针与其他形参一样存储在函数调用的栈桢里面。
在vs下,对this指针的传递进行了优化,对象的地址是放在ecx寄存器中,也就是this的值存储在ecx中。(这种优化方式取决于不同的编译器,不是绝对的)
2.this指针可以为空,但不能对this解引用
看下面的两道题:
// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void Print()
{
cout << "Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
// 2.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
void PrintA()
{
cout<<_a<<endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();
return 0;
}
解析:第一道题,p作为实参传递给this指针,this是空指针,但是在没有对空指针进行解引用,因为Print函数的地址不在对象中,而是在公共代码段,在编译期间就找到了Print函数的地址。
第二道题,p作为实参传递给this指针,this是空指针,调用Print函数没有对空指针进行解引用,因为Print的地址不在对象中,但是在Print内部,却调用了this指针,访问了_a.
本质是对空指针进行解引用。
3.this指针不能在形参和实参显示传递,但是可以在函数内部使用。
总结
以上是类和对象(上)的主要内容,本文讲述了:
1.面向对象和面向过程的区别和联系
2.什么是类,类对象
3.类的定义,使用
4.this指针等等