目录
C++ 类与对象(中)
C++ 类与对象(下)
认识类与对象
C语言是面向过程的,关注的是过程(过程中的数据(变量)和方法(函数)),分析出求解问题的步骤,通过函数调用逐步解决问题。
C++是基于面向对象的,关注的是对象(对象的属性(成员变量)与功能(成员函数)),将一件事情拆分成不同的对象,靠对象之间的交互完成。
类的引入
C语言中,结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
struct Student {
void SetStudentInfo(const char* name, const char* gender, int age) {
strcpy(_name, name);
strcpy(_gender, gender);
_age = age;
}
void PrintStudentInfo(){
cout << _name << " " << _gender << " " << _age << endl;
}
char _name[20];
char _gender[3];
int _age;
};
int main() {
Student s;
s.SetStudentInfo("我爱我的祖国", "男", 18);
s.PrintStudentInfo();
system("pause");
return 0;
}
注意 :
1. C中不允许空结构体, C++中可以,还会给空结构体1字节的空间
2. C++中定义结构体变量不需要加struct
3. 在C++中,结构体中可以声明定义函数, C中只能存放函数指针
4. C++中成员函数可以直接访问本结构体成员,变量不需传入, C中函数访问结构体成员必须传入结构体变量或结构体指针
在C++中结构体变得更加方便好用, 但我们在C++中却不常用, 当然是因为有更好用的东西出现 那就是类, 我们把上面声明结构体的关键字struct换成class, 这个结构体类型就变成了类 类型
类的定义
class className{ // 类体:由成员函数和成员变量组成 }; // 和结构体一样, 注意后面的分号
class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号 。
类中的元素称为类的成员:类中的数据称为类的属性或者成员变量; 类中的函数称为类的方法或者成员函数。
类的两种定义方式:
1. 声明和定义全部放在类体中,需要注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。如下:class Student { char _name[20]; char _gender[3]; int _age; public: void SetStudentInfo(const char* name, const char* gender, int age) { strcpy(_name, name); strcpy(_gender, gender); _age = age; } void PrintStudentInfo() { cout << _name << " " << _gender << " " << _age << endl; } };
2 . 声明和定义分开, 声明放在.h文件中,类的定义放在.cpp文件中, 如下 :
一般情况下,更期望采用第二种方式。
类的访问限定符及封装
C++实现封装的方式:用类将对象的属性(成员变量)与方法(成员函数)结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
【访问限定符说明】
1. public修饰的成员在类外可以直接被访问
2. protected和private修饰的成员在类外不能直接被访问(在类外直接访问时protected和private是类似的)
3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4. class的默认访问权限为private,struct的默认访问权限为public(因为struct要兼容C)
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别
问题:C++中struct和class的区别是什么?
C++为了兼容C, C++中的struct 继续和C中一样使用 , 另外C++中的struct也可以定义类, 与struct不同的是, class定义的类成员默认访问是(private)私有的, struct定义的类成员默认访问是(public)公有的
封装
面向对象的三大特性:封装、继承、多态。
这里,我们先只看类的封装特性,那什么是封装呢?
封装 : 将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。封装本质上是一种管理:我们如何管理兵马俑呢?比如如果什么都不管,兵马俑就被随意破坏了。那么我们首先建了一座房子把兵马俑给封装起来。但是我们目的全封装起来,不让别人看。所以我们开放了售票通道,可以买票突破封装在合理的监管机制下进去参观。类也是一样,我们使用类数据和方法都封装到一下。不想给别人看到的,我们使用protected/private把成员封装起来。开放一些共有的成员函数对成员合理的访问。所以封装本质是一种管理。
类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员,需要使用 :: 作用域解析符指明成员属于哪个类域。
在类内定义成员 : 直接定义
在类外定义成员 : 使用" :: "作用域解析符, 如上代码
类实例化成对象后
在类内 , 成员之间可以相互访问
在类外 , 访问成员需要" . "和" -> "来访问 如 : 对象.函数(); 对象指针->函数();
类的实例化
用类 类型 创建对象的过程,称为类的实例化
1. 类只是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有给成员变量分配实际的内存空间来存储它 .
2. 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量打个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出建筑,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间
如下 man就是类Person得实例化
类对象模型
类中成员的存储方式
每个实例化的对象中成员变量是不同的,但是调用的函数是一样滴 , 如果类的每次实例的对象都保存一份函数代码,多个对象就造成了浪费 , 所以成员函数存放在公共的代码段#include<iostream> using namespace std; class T1 { int a; public: void fun() { } }; class T2 { int a; int b; public: void fun() { } }; class T3 { public: void fun() { } }; int main() { cout << sizeof(T1) << endl; cout << sizeof(T2) << endl; cout << sizeof(T3) << endl; system("pause"); return 0; }
类是一种自定义类型, 是一种类型定义 , 和int , double 的区别在于类是自定义类型 , 类型本身没有大小可言 ,就如我们前面打比方类是设计图纸 , sizeof(类型) 是在计算该类型实体的大小。也就是在计算这个类的实体也就是实例化的对象的大小 , 我们可以看到类T1这个类型实例化占4个字节, 这是int a的大小, T2是8, 这是两个int 的大小, T3 是 1 , 这是因为编译器会给空类1个字节的, 可以看出, 成员函数不会占用对象(类实例化)的大小
类和结构体的内存对齐
1. 类和结构体的内存对齐规则是一样的
1). 第一个成员在与结构体偏移量为0的地址处。
2). 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。VS中默认的对齐数为8,gcc中的对齐数为4
3). 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4). 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对 齐数(含嵌套结构体的对齐数)的整数倍。2.. 内存对齐是一种以空间换时间的操作
3. 结构体(或类)中某个成员相对于结构体(或类)起始位置的偏移量 : 宏 offsetof(类型, 成员) , 或将成员和结构体地址取出来, 转成char*,相减
this指针
我们先来看一个日期类Date
.cpp文件#include<iostream> using namespace std; class Date { int m_year; size_t m_month; size_t m_day; public: Date(int y, size_t m, size_t d) { m_year = y; m_month = m; m_day = d; } void PrintDate(); }; void Date::PrintDate() { cout << m_year << "年" << m_month << "月" << m_day << "日\n"; } int main() { Date a(2019, 10, 1); Date b(2020, 12, 1); a.PrintDate(); b.PrintDate(); system("pause"); return 0; }
我们前面刚刚了解到, 在类实例化的对象中, 并不保存成员函数, 成员函数是放在代码段的, 那么当上面代码中的对象a 和对象b 调用DatePrint()函数时, 函数表面上并没有传入和对象a或b有关的信息 , 那么函数如何判断是那个对象调用自己的呢?
C++中通过引入 this指针解决该问题,即:C++编译器给每个“成员函数“增加了一个 隐藏的 指针参数,让该指针 指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透 明的,即用户不需要来传递,编译器自动完成。
this指针的特性
1. this指针的类型:类类型* const , 在上面的代码中就为Date* const
2. 只能在“成员函数”的内部使用
3. this指针本质上其实是一个成员函数的形参(只是隐藏起来了),在对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
4. this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递. 除构造函数外每一个成员函数的第一个形参都为隐藏的this指针#include<iostream> using namespace std; class Date { int m_year; size_t m_month; size_t m_day; public: Date(int y, size_t m, size_t d) { m_year = y; m_month = m; m_day = d; } void PrintDate(); }; void Date::PrintDate() { cout << m_year << "年" << m_month << "月" << m_day << "日\n"; cout << this->m_year << "年" << this->m_month << "月" << this->m_day << "日\n"; } int main() { Date a(2019, 10, 1); Date b(2020, 12, 1); a.PrintDate(); b.PrintDate(); system("pause"); return 0; }
问题 :
1. this指针存放在哪里?
成员函数(被调用时所创建)的栈里, 是一个函数的形参 , 用函数调用结束就释放了
2. this指针什么时候创建的?
在成员函数的开始执行前构造,在成员的执行结束后清除。
2. this指针可以为空吗?
可以 , 空的this也可以调成员函数, 但不能调用操作成员变量的函数.
因为对象的地址不可能为空(为空非法), 所以空的this指针没有指向实例化的对象 , 也就没有实例化的成员变量 ,所以不能 访问成员变量或者操作(访问)成员变量的函数