引言
C语言是面向过程的,关注的是过程,分析出解决问题的步骤,通过函数掉哦用逐步解决问题。
C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠的是对象之间交互完成。比如说,鸟可以飞,就可以抽象出鸟这个对象,然后呢,它有一个功能是可以飞的。但是鸟并不只有只有一种啊,所以就把所有鸟都有的特点集中到一起,就有了类,也就是说,类是一个模板,可以通过类来创造多个对像。
类的引入
在C语言中,结构体只能定义变量,而在C++中,结构体内不仅可以定义变量,也可以定义函数。
struct Peopel{
void SetPeopleInfo(const char* name, const char* gender, int age){
strcpy(_name, name);
strcpy(_gender, gender);
_age = age;
}
void PrintPeopleInfo(){
cout << _name << " " << _gender << " " << _age << endl;
}
char _name[20];
char _gender[3];
int _age;
};
int main(){
Peopel p;
p.SetPeopleInfo("aiyubb", "nan", "18");
p.PrintPeopleInfo();
return 0;
}
不过在C++中,创建类时更喜欢使用class关键字
类的定义
class ClassName{
//类体,由成员函数和成员变量组成
}; // 此处的分号不能少
在上面的代码中,class为定义类的关键字,ClassName为类的名字,**花括号{}**中为类的主体,一定要注意,最后有一个分号,不能少。
类中的元素称为类的成员:类中的数据称为类的属性或成员变量;类中的函数称为类的方法或成员函数
接下来我们使用class关键字创建一个类:
我们可以看到,在我们用class关键字定义类时,在创建好对象去调用它的成员函数时,编译器报错说函数不可访问。函数不可访问就是说编译器找不到函数,可我们明明就定义了这个函数啊,这是怎么回事呢?这就涉及到一个概念,叫类的封装。
封装:C++实现封装的方式是,用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部用户使用
封装本质上是一种管理:我们我们如何管理兵马俑呢?如果什么都不管,兵马俑就被随意破环了。那么我们首先建了一座房子把兵马俑封装起来。但是我们的目的并不是不让别然看,我们要做的是既要让别人看,又要保护兵马俑不遭受破坏。所以我们开放了售票通道,可以买票突破封装在合法的监管机制下去参观。类也一样,我们把要使用的类数据和方法都封装一下。不想给别人看到的就使用protected或private把成员封装起来。开放一些函数对成员合理的访问。所以封装的本质就是一种管理
访问限定符
在上面的封装中,我们提到了一个新的名字叫访问权限,就是在我们定义类是时有很多的成员变量,可这些成员变量,我们在创建的对象后每个对象的成员变量时不能被别人修改的,别人要使用我的成员变量就只能调用我提供的成员函数来访问,所以就有了访问权限。访问权限修饰符有三个:public protected private 。
访问限定符说明
public 修饰的陈冠在类外可以直接被访问protected 和private 修饰的成员在类外不能直接被访问(此处protected 和private 是类似的)- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现为止
- class的默认访问权限为private,struct为public(因为struct要兼容C)
类的定义
到这里我们就明白了,为什么在使用class关键字声明类时,明明什么都和struct声明时一样,编译却要报错。因为class默认所有成员都是私有的,不允许在类外被访问。所以为了有良好的封装性,又不影响功能,一般像下面这样定义
// class创建类
#include<iostream>
using namespace std;
class Peopel {
// 定义成员函数
public:// public作用域从这里开始
void setPeopleInfo(const char* name, const char* gender, const int age) {
strcpy(_name, name);
strcpy(_gender, gender);
_age = age;
}
void printPeopleInfo() {
cout << _name << " " << _gender << " " << _age << endl;
}
// 定义成员变量
private: // public作用域到这里结束,private作用域从这里开始
char _name[20];
char _gender[3];
int _age;
};
int main() {
// 创建对象
Peopel p;
// 对象调用成员方法
p.setPeopleInfo("aiyubb", "男", 18);
p.printPeopleInfo();
return 0;
}
上面这种方式是将声明和定义全部放在类体中,需要注意的是:成员函数如果在类中定义,编译器可能会将其当成内联函数处理
还有一种方法,是将声明和定义分离,就是把声明放在.h 文件中,类放在.cpp 文件中
person.h
// 类
class Peopel{
// 声明成员函数和成员变量
public:
void SetPeopleInfo(const char* name, const char* gender, const int age);
void PrintPeopleInfo();
private:
char* _name;
char* gender;
int age;
};
person.cpp
// 定义成员函数
#include<person.h>
void Peopel::SetPeopleInfo(const char* name, const char* gender, const int age){
strcpy(_name, name);
srtcpy(_gender, gender);
_age = age;
}
// 显示成员变量
void People::PrintPeopleInfo(){
cout << _name << " " << _gender << " " << age << endl;
}
类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用中。在类体外定义成员,需要使用:: 作用域解析符指明成员属于哪个类域。
class People{
public:
void PrintPeopleInfo();
private:
char _name[20];
char _gender[3];
int age;
};
// 这里需要指定PrintPeopleInfo是属于People这个类
void People::PrintPeopleInfo(){
cout << _name << " " << _gender << " " << age << endl;
}
类的实例化
用类类型创建对象的过程,称为类的实例化
1.类只是一个模型一样的东西,限定了类有哪些成员,定义出一个类,并没有分配实际的内存空间来存储它
2.一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量
2.做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是个设计,实例化的对象才能实际存储数据,占用物理空间
计算类对象的大小
在上面我们定义类的时候,可以看到类中既有函数又有变量。那么怎么去计算一个类的大小呢,是要需不需要计算函数的大小呢?
// 类中既有成员变量,又有成员函数
class A{
public:
void function(){}
private:
int _a;
};
// 类中仅有成员函数
class B{
public:
void function(){}
};
// 类中什么都没有--空类
class C{};
我们看看这三个类的大小会是多少呢?
我们可以看到,空类C和只有成员函数的类B,它们的大小是一样的。而既有成员函数又有成员变量的类A的大小确实它成员变量的大小。所以我们可以得出
结论:一个类的大小,实际就是该类中"成员变量"之和,当然也要进行内存对齐,注意空类的大小,空类比较特殊,编译器给了一个字节来唯一标识这个类。
结构体内存对齐规则
-
第一个成员在与结构体偏移量为0的地址处 -
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处 ps:对齐数 == 编译器默认的一个对齐数 与 该成员大小的较小值 -
结构体总大小为:最大对齐数的整数倍 -
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
this指针
首先我们先来定义一个日期类
// 定义一个日期类
class Date{
public:
void Display(){
cout << _year << " " << _month << " " << _day << endl;
}
void SetDate(int year, int month, int day){
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
}
// 在主函数中实例化日期对象
int main(){
Date d1;
Date d2;
// 对象调用成员函数
d1.SetDate(2022, 2, 4);
d2.SetDate(2021, 1, 3);
d1.Display();
d2.Display();
return 0;
}
在上面的代码中,Date类有SetDate和Display两个成员函数,函数体中没有关于不同对象的区分,那么当d1调用SetDate函数的时候,这个哈桑农户是怎么知道应该设置d1对象,而不是设置d2对象呢?这就是我们即将要讲的this指针
C++编译器给每个"非静态的成员函数"增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户都是透明的,即用户不需要来传递,编译器自动完成。
就拿上面的display() 函数来说,虽然我们看到它是一个无参函数,但是他其实是有一个参数的,也就是const Date* this 参数,也就是说,这个函数的真是面目是这样的
void Display(Date* const this){
cout << this->_yaer << " " << this->_month << " " << this->_day;
}
this指针的特性
1.this指针的类型:类类型* const
2.只能在“成员函数”的内部使用
3.this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对现象地址作为实参传递给this形参。所以对象中不存储this指针
|