类和对象(上)
1类的定义:
1.1类定义的格式 :
*class为类定义的格式,后面加一个类名(自己取的),然后{ }为类的主体,最后加个“ ;”,就类似结构体。类体中的内容称为类的成员,类中的变量称为成员变量或类的属性,类中的函数称为类的方法或成员函数。
*为了区分成员变量,一般在成员变量前加上特殊的标识,例如“_",当然这不是强制要求的,只是一种习惯。
*C++中struct被升级也可以定义类吗,但一般情况下还是使用class定义类。
*定义在类面的成员函数默认inline。但如果声明和定义分离就不是内联了。
#include <iostream>
using namespace std;class Stack
{void Push(int x)//成员函数{....}int* a;//成员变量int _top;int capacity;
};
1.2访问限定符:C++一种实现封装的方式,用类将对象的属性和方法结合在一起,让对象更加完整,通过访问权限选择性的将接口提供给外部的用户使用。
*public修饰的成员在类外可以直接被访问,propected和private修饰的成员在类外不能直接被访问。但是propected和private是有区别的,在后面的继承在可以看出。
*访问权限作用域从该访问限定符出现的位置一直到下一个访问权限限定符出现的位置,如果后面没有出现下一个访问权限限定符,就一直作用到类结束。
*class定义的类成员默认为private,struct默认为public。
class Stack
{
public://公有void Init(int year, int month, int day){_year = year;_month = month;_day = day;}
private://私有int _year;//成员变量,用“_”做区分。int _month;int _day;
};int main()
{Stack st;st.Init(1,1,1);//去访问的时候st.只会出现公有的函数。
}
注意,上面private下面的三句话,是声明,不是定义,因为定义是有开辟空间的。只有在下面的main函数里定义了st才会开辟空间。类就像是房子图纸一样,规划了里面的空间和功能,但是还只是图纸还不能住人,只有到main函数里实例化了才可以“住人”。
1.3类域:
*类定义了一个新的作用域,类的所有成员都在类的作用域中,在类体外定义成员时,需要使用::作用域操作符指明成员属于哪个类域。
*类域影响的是编译的查找规则,下面程序中Init如果不指明类域Stack,编译器就会把Init当作全局函数,那么编译时找不到array等成员的声明就会报错。指定类域Stack,就知道Init是类Stack的成员函数,当前域找不到array等成员就会去类域中去查找。
class Stack
{
public://成员函数void Init(int n = 4);private://成员变量int* array;size_t capacity;size_t top;
};
//声明和定义需要指定类域:
void Stack::Init(int n)
{array = (int*)malloc(sizeof(int) * n);if (nullptr == array){perror("malloc空间申请失败");return;}capacity = n;top = 0;
}
int main()
{Stack st;st.Init();return 0;
}
2.实例化:
2.1实例化的概念:
*用类类型在物理内存中创建对象的过程,称为实例化出对象。
*类是对象一种抽象描述,是一个模型一样的东西,限定了类有哪些成员变量,这些变量只是声明,没有分配空间,只有在实例化出对象之后才会分配空间。
*一个类可以实例化出多个对象。
2.2对象的大小:
类实例化出的每个对象都有独立的数据空间,所以对象中肯定包含着成员变量,但是不包含成员函数。首先函数被编译后是一段指令,对象中没办法存储,这些指令被存储在一段单独的区域。然后就是对象被实例化后,虽然各自成员变量不同,但是成员函数的指针是相同的,存储在对象中就太浪费了。其实函数指针是不需要存储的,函数指针是一个地址,调用函数被编译成汇编指令【call 地址】,这个地址在编译链接的时候就被确定了。
下面看一个特殊的例子:
class A
{
public :void Print(){//....}
};int main()
{A a;cout << &a << endl;cout<<sizeof(a)<<endl;return 0;
}
已知类中不存储成员函数的大小,所以用类A实例化之后a的大小应该是0才对,这时候去取a的地址应该是一个空指针,但是结果却是:
a有一个地址且它的大小是1字节。如果按照上述分析的是0的话,就无法证明这个类存在过,所以这里给1,只是为了占位标识对象存在。
3.this指针:
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;Date d2;d1.Init(2024, 9, 21);d2.Init(2024, 10, 22);d1.Print();d2.Print();return 0;
}
有一个问题,既然这两个实例化出来之后的对象它们的函数都是同一个地址,那么是怎么做到打印各自不同的值的?其实是利用了this指针。
*编译器编译后,类的成员函数默认都会在形参的第一个位置,增加一个当前类型的指针,叫做this指针。就比如上述的类Date中,Init的真实原型是void Init(Date* const this, int year, int month, int day),const在*之前。,修饰的是指向对象,在*之后,修饰的是指针本身。在调用的时候,会悄悄地把对应对象的地址传过去,这样就知道调用哪个对象了。
*类的成员函数中访问成员变量,本质是通过this指针访问的,如Init函数中给_year赋值:this->_year = year。
*C++规定不能在实参和形参的位置显示地写this指针(编译时编译器会处理),但是可以在函数体内部显示使用this指针。也就是说你不能void Print(Date*const this) ; d1.Print(&d1) ; d2.Print(&d2) ;
但是可以this->_year = year ;
4.C++相对于C语言的优势:封装、继承和多态
*C++中数据和函数都放到类里面,通过访问限定符进行限制,不能再通过对象随意修改数据,这是C++封装的一种体现,这个是最重要的变化。这里的封装本质是一种更严格规范的管理,避免出现乱访问修改的问题。当然封装不仅仅是这样的,后面还需要学习。
*C++有一些相对方便的语法,比如Init给的缺省参数就会方便很多,成员函数不需要传对象地址,因为this指针隐含地传递了,使用类型不再需要typedef用类名就很方便。
*但是初阶段的学习感受不到太多,还需要不断地学习。