目录
一:面向过程与面向对象的区别
二:类的定义
三:类的访问限定符和封装
3.1访问限定符
3.2 封装
四:类的实例化
五:类对象模型
如何计算类的大小
类对象的存储方式
六:this指针
this指针的引出
this指针的特性
一:面向过程与面向对象的区别
-
面向过程
C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
- 面向对象
C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
以洗衣服为例理解:
c语言关注的是洗衣服的过程,通过实现各种函数,最后调用实现
C++关注的是对象,洗衣服这个过程主要的对象就是人、洗衣粉、衣服、洗衣机,人只需要将衣服丢进洗衣机里,加上洗衣粉,不用管洗衣机,对于洗衣机干了什么是洗衣机这个对象的事情
二:类的定义
在C语言中,struct结构体内部只能定义变量,而C++兼容C语言,结构体内部不仅可以定义变量,还可以定义函数,例如:
而在C++中,更喜欢把struct替换为class来定义
格式:
class className { // 类体:由成员函数和成员变量组成 }; // 一定要注意后面的分号
class为定义类的 关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或 成员变量; 类中的函数称为类的方法或者 成员函数。
类由两种定义方法:
1. 声明和定义全部放在类体中,需注意:成员函数如果 在类中定义,编译器可能会将其当成 内 联函数处理。class Student { char name[10]="张三"; int age=10; void speak() { cout << "我叫" << name << ",我今年" << age << "岁了" << endl; } };
2. 类声明放在.h文件中,成员函数定义放在.cpp文件中注意:由于类定义了一个新作用域,成员函数名前需要加 类名: : 指明成员属于哪个类域Student.h文件:#pragma once #include<iostream> using namespace std; void Student::speak() { cout << "我叫" << name << ",我今年" << age << "岁了" << endl; }
Student.c文件:#include"Student.h" class Student { public: char name[10]="张三"; int age=10; void speak(); };
(建议使用第二种方式)
三:类的访问限定符和封装
3.1访问限定符
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用,想要给类外的对象访问,就将成员变量或者函数用 public修饰,否则就用 private或者 protected修饰(根据情况选择)
说明:
1. public修饰的成员在类外可以直接被访问2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止4. 如果后面没有访问限定符,作用域就到 } 即类结束。5. class的默认访问权限为private,struct为public(因为struct要兼容C)
3.2 封装
面向对象的有三大特性: 封装、继承、多态
四:类的实例化
1. 类 是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它2. 类就相当于一个模板,一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
#include<iostream>
using namespace std;
class Student
{
public:
char name[10]="张三";
int age=10;
void speak()
{
cout << "我叫" << name << ",我今年" << age << "岁了" << endl;
}
};
int main()
{
Student stu;
stu.speak();
return 0;
}
五:类对象模型
如何计算类的大小
在C语言中,结构体内只能存在变量,根据结构体内存的对齐规则就可以算出结构体的大小,而在C++中,结构体或者类内不仅可以定义变量还可以定义函数,那类的大小如何计算?
如图所示,这个学生类的大小为16,这个大小是如何求的呢,我们先猜测一下,类对象的存储方式
类对象的存储方式
猜测一: 对象中包含类的所有成员
缺陷: 每个对象中成员变量是不同的,但是调用同一份函数,如果按照此种方式存储,当一个类创建多个对象时,每个对象中都会保存一份代码,相同代码保存多次,浪费空间。那么如何解决呢?
猜测二: 代码只保存一份,在对象中保存存放代码的地址
猜测三:只保存成员变量,成员函数不在对象内存中保存,单独存放在公共的代码段
通过上面的例子来验证类对象的存储方式应该是猜测三:
[解释]:
根据结构体内存的对齐规则,只算成员变量的话,该类的大小正好为16,按猜测二计算的话,大小应该是16+一个指针的大小,不符合答案,所以类对象的存储方式为:
只保存成员变量,成员函数不在对象内存中保存,单独存放在公共的代码段
了解了类对象的存储方式来看如下三个题目:
// 类中既有成员变量,又有成员函数 class A1 { public: void f1() {} private: int _a; char _ch; }; // 类中仅有成员函数 class A2 { public: void f2() {} }; // 类中什么都没有---空类 class A3 {}; int main() { A1 a1; A2 a2; A3 a3; cout << sizeof(a1) << endl; // 8 cout << sizeof(a2) << endl; // 1 cout << sizeof(a3) << endl; // 1 return 0; }
sizeof(A1) : ___ 8_ __ sizeof(A2) : __ 1__ __ sizeof(A3) : ___ 1 _ __[解释]:当一个类不存在成员变量时,编译器会给他开辟一个字节的空间用来占位,用来证明对象存在结论:一个类的大小,实际就是该类中 ” 成员变量 ” 之和,当然要注意内存对齐注意空类的大小,空类比较特殊,编译器给了空类一个字节来唯一标识这个类的对象。
六:this指针
this指针的引出
首先定义一个日期类:
#include<iostream> using namespace std; 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(2022, 1, 11); d2.Init(2022, 1, 12); d1.Print(); d2.Print(); return 0; }
思考:
Date类有:init() print()两个成员函数,当d1和d2调用 init()时,函数都是从公共代码区进行调用的,那是怎样对对象进行区分的?
[解答]:
C++中通过引入 this指针 解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏 的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量” 的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编 译器自动完成。this指针的特性
- this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
- this指针只能在“成员函数”的内部使用
- this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
- this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
class Date { public: void Init(int year, int month, int day) { _year = year; _month = month; _day = day; } // 编译器隐藏的this指针 void Init(const Date* this,int year, int month, int day) { this._year = year; this._month = month; this._day = day; } void Print() { cout << _year << "-" << _month << "-" << _day << endl; } // 编译器隐藏的this指针 void Print(const Date* this) { cout << this._year << "-" << this._month << "-" << this._day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1, d2; d1.Init(2022, 1, 11); d2.Init(2022, 1, 12); d1.Print(); d2.Print(); //传对象地址 d1.Init(&d1,2022, 1, 11); d2.Init(&d2,2022, 1, 12); d1.Print(&d1); d2.Print(&d2); return 0; }
这个可以通过比较this的值与对象的地址是否相同来验证:
可见:
d1的地址与d1调用print函数中this所指地址相同
d2的地址与d2调用print函数中this所指地址相同