目录
一.类的定义
1.面向对象的思想
众所周知,C语言是面向过程的语言,对于每一个过程都有一个对应的函数来实现,每个函数都相对独立,但随着计算机发展,问题的场景也越来越复杂,面向过程在大型系统开发中捉襟见肘,面向对象思想运营而生。
其核心思想是:随着系统参与的实体越来越多,过程变得越来越复杂,那么就不必费力去描述每一个可能的过程了,转而描述每一个实体,如果每一个实体都被正确描述了,那么这些实体置于系统中,系统就能正确运行。
2.类定义的格式
C++作为面向对象的编程语言,描述每一个现实世界的实体都可以使用关键字Class(类)来进行定义。Class后面跟类的名字,{}中则为类的主体,且定义结束后不可忘记有个分号。
类中的变量称为类的属性或成员变量,类中的函数称为类的方法或者成员函数。
例如定义一个person类:
class person
{
//类的方法(成员函数)
void say()
{
cout << "i am person!" << endl;
}
//类的属性(成员变量)
char name[20];
int age;
char sex[5];
};
3.访问限定符
访问限定符分为以下三种:public(公共)、private(私有)、protected(保护)
- public修饰的成员在类外可以直接别访问;protected和private修饰的成员在类外不能直接被访问到
- 访问权限作用域从该访问权限限定符出现的位置开始到下一个访问限定符出现为止,若没有,则到 } 定义类结束为止
- class定义的成员默认为private,而struct定义的成员默认为public
4.类域
类定义了一个全新的作用域-类域,类的所有成员都在类的作用域中,在类外定义成员的时候,需要使用域解析运算符::来指明该成员是属于哪个类域。
类域影响的是编译的查找规则,如果成员函数在类外定义而不限定类域,编译器就会将其视为全局函数,同时,全局函数就无法找到类中的成员了,此时就会报错。
二.实例化
1.实例化概念
实例化对象:用类型在物理内存中创建对象的过程。
类是一种抽象的概念,类似于一个模型,实际不存在,类中成员也都只是声明,没有分配空间,只有当实例化的时候,才会真正开出空间。
打个比方,类就像设计建筑的图纸,图纸上设计了房间的样式、大小等等,但空有图纸是没有房间真正存在的,实例化就像参照设计图建造出的房子一样,这时房子才真正存在,同时,根据一张图纸能建造多个房子,那么类也是同理,一个类可以实例化多个对象。
这就是类和对象的关系,就像设计图和房子,人和小明、小王、小周......(具体的人)
上述设计的person类,就可以根据这个类建造多个对象:
#include<iostream>
#include<string.h>
using namespace std;
//以下都还是只是图纸,不分配空间
class person
{
public:
//类的方法(成员函数)
void say();
void Init(const char* name, int age, const char* sex)
{
strcpy(_name, name);
_age = age;
strcpy(_sex,sex);
}
private:
//类的属性(成员变量)
char _name[20];
int _age;
char _sex[5];
};
void person::say()
{
cout << "我是"<<_name << endl;
cout << "我的年龄是"<<_age << endl;
cout << "我是"<<_sex<<"的" << endl;
}
int main()
{
//实例化p1,p2,此时就分配了空间
person p1;
p1.Init("张三", 18, "男");
person p2;
p2.Init("李四", 20, "男");
p1.say();
p2.say();
return 0;
}
2.对象的大小
如上代码所示,在类的定义中,有成员变量和成员函数,这些都存放在对象中吗?
就如上述代码,对于person类,有可能会创建很多具体的对象,不止张三李四王五赵六,如果创建了成千上百个对象,此时如何储存最为节省空间?每个对象的成员变量肯定是要储存的,这是区别每一个对象的特征,这是属性,必须保留,但成员函数呢?每个对象都是调用的同一个成员函数,有必要给每个对象都存一遍吗,没有必要。因此,对象的实际大小就是计算成员变量的总大小,不算入成员函数。
成员变量的总大小,遵循内存对齐原则,这里和之前C语言的结构体对齐是相同的,规则如下:
- 第一个成员在偏移量为0处
- 其他成员在对齐数的整数倍处
- 对齐数:编译器默认的对齐数和该成员大小的较小值(VS默认是8)
- 最大对齐数:所有成员类型中最大的对齐数和默认对齐数中较小值
- 总大小为最大对齐数的整数倍
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
注意:若类中没有声明成员变量,则该对象的大小为1,不是0,若1个字节都没有,就不能表示该对象存在过,需要有1字节来占位标识对象存在。
三.this指针
上述提到,对象大小中不存在成员函数,成员函数都是存在一个公共代码区等待统一调用,那么调用函数时,函数怎么区分不同的对象呢?使用this指针。
实际上,每个成员函数的形参都隐藏了this指针,它默认在成员函数的第一个位置,是person* const this指针,例如用对象p1调用成员函数say时,相当于将p1的地址传给了这个隐藏的this指针,而成员函数访问不同对象中的成员变量,恰恰就是使用this,就是如:this->_name如此访问,只是在函数内部都省略掉了this,看起来是直接使用_name等成员变量而已。
C++规定不能在实参和形参的位置显示的写this指针(编译时编译器会处理),但是可以在函数体内显示使用this指针,例如以下的this->age
//p1.say() ----> say(person* const this)
//void say(person* const this)
void say();
//void Init(person* const this,.......)
void Init(const char* name, int age, const char* sex)
{
strcpy(this->_name, name);
this->_age = age;
strcpy(this->_sex,sex);
}
这里分享一道有意思的题目:如下代码会报错吗?
#include<iostream>
using namespace std;
class A
{
public:
void Print()
{
cout << "A::Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
答案是正常运行,p虽然是空指针,但p->Print()并不意味是解引用,上述已经说了,调用成员函数,相当于把对象的地址传给函数的this指针,也就相当于把空指针传给了this,但函数内部也并没有使用,只是打印了一句话,因此能正常运行。
但如果在函数内部对this指针进行了解引用操作,例如:this->_a,就会报错了。