一、class和struct
1、在其中可以同时定义成员变量和成员函数
struct person { public: void Init() { cout<<"zdl"<<endl; } void fun(int _age, char _name) { age = _age; name = _name; cout<<this->age<<endl; } private: int age; char name; }; class person { public: void Init() { cout<<"zdl"<<endl; } private: int age; char name; };
小函数:想成为inline,直接在类里定义即可。
大函数:应该声明和定义分离。定义时要加作用域,表明属于哪个类域。
2、访问限定符(class默认访问权限为私有,struct默认访问权限为公有)
(1)public(公有):类内类外都可以访问。
(2)protected(保护):类内可以访问,类外不能访问。
(3)private(私有):类内可以访问,类外不能访问。
3、类的实例化
类是对对象进行描述的,是一个模型一样的东西。用类类型创建对象的过程,称为类的实例化。person类是没有空间的,只有实例化出的对象才有具体的空间。比如:一个房子的设计图,就像是一个类。你在上海根据设计图建了房子,在北京根据设计图建了同样的房子,这就叫类的实例化。
person p1;//类的实例化
实例化的每个对象的成员变量存储在类中,且都有自己独立的空间,是不同的变量。但是每个对象,调用类的成员函数都是同一个,编译链接时根据函数名去公共代码区找到函数的地址,call函数地址。且类不计函数大小。
4、this指针(是一个形参)
1、this指针在栈区,成员函数有一个隐式形参 类名*const this 只能在成员函数内部使用。
2、this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给
this形参。所以对象中不存储this指针。
3、 this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传
递,不需要用户传递。
5、构造函数(一般写成全缺省的)
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证
每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任
务并不是开空间创建对象,而是初始化对象。
其特征如下:
1、 函数名与类名相同。
2、无返回值。
3、对象实例化时编译器自动调用对应的构造函数。(即可以不显示地自己调用)
4、构造函数可以重载。
class Date { public: Date(int year=1, int month=1, int day=1) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; }; int main() { Date d1; Date d2(2023, 6, 5); Date d3(2023, 3); return 0; }
注:默认生成构造函数:对内置类型成员不做初始化(因此我们要自己写构造函数)。自定义类型成员(class和struct)可以自动调用默认构造函数(它自己的构造函数)。
无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。
6、析构函数
与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
* 内置类型动态开辟的空间(malloc,new等)——显示写析构函数。
* 内置类型非动态开辟的空间——不需要显示写析构函数。编译器的默认析构函数就可以解决。
* 自定义类型成员会去调用它自己的析构函数,所有不需要显示写。如用两个栈实现队列的题中。队列类中不需要写析构函数,Stack成员会去调用它自己的析构函数。
* 先定义的后析构,后定义的先析构。
class A { //... }; A aa3(3) void f() { Static A aa0(0); A aa1(1); A aa2(2); static A aa4(4); } int main() { f(); f(); }
构造顺序:3、0、1、2、4、1、2。静态变量在程序结束后才会销毁,所以第二次调用了f()后,静态变量就不用再次调用构造函数了。
析构顺序:~2、~1、~2、~1、~4、~0、~3。
7、拷贝构造函数
只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
1、拷贝构造函数是构造函数的一个重载形式。
2、 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,
因为会引发无穷递归调用。
class Data { public: Data(const Data &d) { _year = d.year; _month = d.month; _day = d.day; } private: _year; _month; _day; } Data d2(d1);
若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按
字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。(内置类型和自定义类型均可以)
注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定
义类型是调用其拷贝构造函数完成拷贝的。
8、深拷贝和浅拷贝
浅拷贝:默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
但是,如果在被拷贝的对象中有动态开辟的成员变量,如动态开辟的数组。在浅拷贝后,新的对象的该成员变量会指向和被拷贝对象相同的空间。这样在调用析构函数时,就会被析构两次。
所以我们就需要自己实现深拷贝来解决这个问题。(后面专门讲解)
9、运算符重载(operator)
内置类型可以直接使用运算符运算,编译器知道要如何运算。
自定义类型不可以直接使用运算符运算,编译器不知道要如何运算。
赋值运算符重载格式:
参数类型:const T&,传递引用可以提高传参效率。
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值。
检测是否自己给自己赋值。
返回*this :要复合连续赋值的含义。
//判断两个日期是否相等 class Date { public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } bool operator==(const Date& d) { return _year == d._year && _month == d._month && _day == d._day; } private: int _year; int _month; int _day; };
如:用栈实现队列的题中,MyQueue中的成员变量的类型是自定义类型Stack,默认生成的赋值重载函数就可以用。前提是Stack中有正确的赋值重载函数。这样就可以自动调用。
任何一个类,只需要写一个 > == 或者 < ==重载,剩下的比较运算符重载复用即可。
Data& operator++() //前置,返回++后的 { *this += 1; return *this; } Data operator++(int) //后置,返回++前的 { Data tmp(*this); *this += 1; return tmp; }