👑 前言
本文从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文件中定义函数前需要加上一个类名::
👑 四、类的访问限定符
类有三种访问限定符:public,protect,private。
成员函数的声明可以放在类的任何地方,但是如果放在private修饰的区域,在编译阶段成员函数就成为私有的,这样就不能通过类对象访问成员变量了,相当于这个类完全封闭了自己。
所以一般成员变量是放在public区域,类实例化出来的对象可以通过调用类的成员函数来达到访问类对象的成员变量的目的。
【访问限定符说明】
public修饰的成员在类外可以直接被访问
protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
如果后面没有访问限定符,作用域就到 } 即类结束。
class的默认访问权限为private,struct为public(因为struct要兼容C),如果成员函数没有放在public区域,则该成员函数默认是私有的。
注意:1.这三种限定符只有在编译器编译阶段才会起作用。
2.成员函数的声明和定义可以分离,一般在类内声明,类外定义。
👑 五、类的内存计算
先看下面的案例:
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中。(这种优化方式取决于不同的编译器,不是绝对的)
tip:在this的底层实现中,本质上this是被const修饰的。
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指针不能在形参和实参显示传递,但是可以在函数内部使用。
总结 : 类和对象的联系
类可以实例化一个对象,但是不能通过类直接访问类的成员函数,成员变量等。
比如:
class Date { private: int _year; int _month; int _day; } 不能这样访问: Date::_year = 10;
因为类的成员变量仅仅是一个声明,是没有空间的。
类和对象的关系就像是:类是一张造房子的图纸:对象是具体的一栋房子。
不能通过图纸去找到具体的卧室或者厨房。
要想访问成员变量,需要实例化一个对象出来,才能访问。
以上是类和对象(上)的主要内容,本文讲述了:
1.面向对象和面向过程的区别和联系
2.什么是类,类对象
3.类的定义,使用
4.this指针等等