类
什么是类?
C语言结构体不支持成员函数,但C++结构体支持,其class与struct本质没有区别,唯一区别 在于默认时class的访问属性为私有,struct为公有。
class Person { public: void Init(const char* name, const char* gender, int age) { strcpy(_name, name); strcpy(_gender, gender); _age = age; } void Print() { cout << _name << " " << _gender << " " << _age << endl; } //表示私有的 private: char _name[20]; char _gender[3]; //表示受保护的,目前可以理解是和private一样 protected: int _age; };
如上图,class为定义类的关键字,Person为类名,{}内部为类的主体,类中的变量称为成员变量,类中的函数称为成员函数;
但是在类一般默认是私有的(private),因此一般将函数方法设为公开的(public),成员变量设为私有的(private)或受保护的(protected)。
而这三个权限就叫做访问限定符:
- public可以直接被类外访问你,而private和protected不行;
- 访问权限的作用域从该限定符出现的位置到下一个限定符出现的位置;
- 如后面没有其余访问限定符,则访问权限的作用域到 } 结束;
- 默认情况下class是private,struct是public;
类的定义及其作用域
类的定义
定义时可以将声明和定义全部放在类中,但是一般将类的声明放在头文件中,成员函数的实现放在cpp文件中。
如上图,在头文件中,声明stack类,并将函数及变量的声明放在这个类里面,并且还要将变量的设为private,函数设为public。在.cpp文件中实现函数的定义,但是需要注意的是,
在类体外定义成员时,这里需要使用作用域操作符::指明成员属于哪个类域,类名::函数名的方式才能实现函数的定义。但是,如果函数定义在类中,一般编译器会将这个函数认为内联函数。
类的作用域
类定义时就定义了一个新的作用域,类中所有的成员就在这个作用域中,因此当我们在类外定义时,需要使用作用域操作符(::)来指明该成员属于哪个域。
例如上述代码中的Stack::Pop就表示Stack中的Pop函数。
封装与实例化
实际上,封装就类似于管理,在编写代码时,将函数和变量放在一个类中,而只提供接口,这样使用户使用起来更方便,隐藏细节,开放接口,这就是封装。
而类的实例化就是创建一个类的过程,如果将类描述为汽车设计图,那么实例化就是工厂中生产出来的汽车。
类是对对象进行描述的,不占用空间,实例化需要占用空间。
如上图所示的代码中,Stack类本身不会被分配空间,但是当我们Stack s1;时,此时s1是占用空间的。
类的大小计算
如上图所示,A1类中有函数和变量,A2中只有函数,A3是空类;但从结果中有几个疑点
- 类的大小有什么决定?
- 类在内存中怎样存储?
从代码结果推断,类的大小取决于类中成员变量的大小(这里需要内存对齐),对于空类来说,编译器会自动给空类一个字节,代表这个空类存在过。类中的函数被保存在公共区域,当需要调用的时候类对象会根据函数名寻找函数的地址,编译的时候就会call(函数地址)。
this指针
如上图所示,编译器是如何识别d1和d2的呢?
在c++中通过引入this指针来解决这个问题,编译器给非静态成员函数增添了一个隐藏的this指针,this指向的就是当前类对象,函数中所有调用这个类对象时,都是通过this指针来访问的,但是这个this指针是不可见的,编译器不会将它显示出来。
如上图所示,类对象其实传递的是其本身的地址,这里的Init函数的具体实现和Print类似,都是接受类对象的地址。
如上图,通过打印函数中this的地址和main函数中类对象的地址可以发现,main函数确实是将类对象的地址创给类中的函数。
this指针作为临时变量一般存放在栈中,但是也有可能存放在寄存器中。
- 静态成员函数没有this指针,只有非静态成员函数才有,且为隐藏指针;
- 非静态成员函数的第一个参数就是隐藏的this指针;
- this指针在非静态的成员函数里面,对象不存在,
this指针的两道题:
class A{
public:
void Print()
{
cout << "Print()" << endl;
}
private:
int _a;
};
int main(){
A* p = nullptr;
p->Print();
p->_a = 0;
return 0;
}
如上所示代码,p是A类的空指针,对于p->Print()来说,程序不会报错,因为对于类中的函数,函数会放在公共区域,因此不会对p进行解引用;但是当p访问_a时(将_a重设为公开的),此时程序就会崩溃,因为要想访问_a就要对类的对象进行解引用操作(这不同于函数的访问),而p是一个空指针,对空指针进行解引用必然会报错。
class A{
public:
void PrintA() {
cout<<_a<<endl;
}
private:
int _a;
};
int main(){
A* p = nullptr;
p->PrintA();
return 0;
}
如上所示代码,p依然是A类的空指针,而这段程序在cout<<_a<<endl;时就会崩溃,原因就在于PrintA函数中需要对main函数传过来的类对象指针进行解引用,这里的PrintA实际上接受的参数是隐藏的this指针,而这个this指针是空指针,因此在打印时就会崩溃。