1.class

在 C 语言中,struct 可以用于定义自定义对象,但 C 语言本身并不支持面向对象编程。在来到 C++ 中,为了更严格控制访问权限,引入了 class。与此同时,struct 也被升级为一种特殊的类,与 class 几乎完全等价,但默认的访问权限不同。

如图给出类的简单代码框架:

class是定义类的关键字,tree为类的名字,{}为主体,类定义结束后紧接着;类中的内容成为类的成员,在类中定义的变量我们称之为成员变量,函数则为成员函数;并且定义在类里面的成员函数默认为inline。

图中的public和private是C++中的访问限定符,是C++中的一种独特的实现封装的一种方式,public修饰的成员变量可以直接在类外被访问,protected和private修饰的成员在类外不可以被直接访问,当然若想访问除了将成员放公外,我们可以通过友元函数,类对象提供getxxx函数等,具体后面会细说。

在类中,访问限定符的作用域是从当前限定符开始,在遇到下一个访问限定符或类定义结束截止。struct默认访问与继承权限都为public,class默认访问与继承权限都为private。

在调用对象中的成员函数时,成员函数又是如何区分访问的不同对象呢?

这时就要提到this指针了,用于在类中成员函数隐式的访问对象的成员函数及成员变量,并指向当前对象的地址。当调用类的成员函数时,编译器会隐式自动地传递当前对象的地址作为第一个参数,这个参数就是 this 指针。类的成员函数都会在形参的第一个位置默认添加一个当前类类型的指针比如data类的init真实原型为

void init(data* const this, int year, int month, int day)

但我们一般会把这段data* const this,给省略掉。

当然函数体内可以显式的使用this,实参和形参的位置都不能显式的写this指针。

在类中有六个默认成员函数。

2.构造函数

构造函数是特殊的成员函数在对象创建时自动调用,虽然名字叫构造,但实际上行使的功能却是完成对象的初始化工作(为成员变量赋初值、分配资源等)。

特点:

没有返回类型包括void,并且函数名与类名相同,可以进行重载。

对内置类型不进行初始化。

但是当类中没有显式的构造函数,编译器就会自动生成一个无参无逻辑的默认构造函数并且调用它,但一般来说编译器生成的默认构造函数不符合我们的要求。

简单的来说不用进行传参就可以进行调用。

无参,部分缺省,全缺省都是默认构造函数,但无参函数与全缺省不能同时存在,因为会产生调用歧义。

C++把类型分为内置类型和自定义类型,内置类型通常是int/floar/double/指针等,自定义类型则是我们使用struct/class等关键字自己定义的类型。

对于自定义类型成员变量,会要求调用其的默认构造函数进行初始化;当缺少默认构造函数时,编译器就会报错,这时我们就需要进行初始化列表对成员变量初始化。

对于内置类型成员变量,必须使用手动初始化不然值会被随机,可以使用列表初始化。

3.析构函数

析构函数是C++中与构造函数功能相反的特殊成员函数,负责在对象销毁时自动清理和释放资源。其核心作用是确保对象生命周期结束时,其占用的资源(如动态内存、文件句柄等)能够被正确释放,避免内存泄漏。

C语言中相当于在我们编写的stack栈中的destroy功能,需要手动实现。

而C++同过析构函数自动化调用,不易发生内存泄漏。

特点:

函数名与类名相同,且要在函数名前加上取反符号~,无返回值,参数等,因为没有参数所以不能构成函数重载,所以一个类中只能有一个析构函数。若未显式定义,则编译器会自动生成一个默认析构函数,在对想生命周期结束后,系统就会自动调用来清理释放对象中的资源。

大多数情况下都是需要自己写析构函数来完成资源类的清理释放,若是无资源类的系统生成的默认析构函数就足够使用了,与构造函数相同。

一个局域内,若有多个对象时,C++规定后定义的先进行析构。

4.拷贝构造函数

拷贝构造函数是一个特殊的构造函数,也可以说是构造函数的一个重载。

它的第一个参数是自身类类型的引用,其余参数都必须得有默认值也就是缺省值,才能使得其既支持拷贝语义(单参数调用),又支持扩展功能(多参数调用)。

在 C++ 里,传值传参时会优先调用拷贝构造函数。若拷贝构造函数的第一个参数采用传值方式而非类类型的引用,就会引发无穷递归问题。具体来说,当向函数传入一个参数时,系统会调用拷贝构造函数,由于是传值传参,这个参数会被传递给拷贝构造函数,而传值传参又会触发拷贝构造函数的调用,如此循环往复,不断调用拷贝构造函数,最终形成无穷递归。

解决这一问题的办法是,在拷贝构造函数的第一个参数使用类类型的引用。引用本质上相当于为一个变量取别名,使用引用传参不会触发新的拷贝构造函数调用,避免了无穷递归的发生。

代码展示如下:

 date(const date& d1)
{
    _year = d1._year;
    _month = d1._month;
    _day = d1._day;
}

 在调用时我们可以这样写,但是也要注意与赋值运算符重载区分出来。

date d3(d1);
date d4 = d1;

当然,如果你没有写拷贝构造函数时,编译器默认是浅拷贝,一个字节一个字节的拷贝,跟memcpy很像。

5.运算符重载

当运算符作用于类和对象时,C++允许我们通过运算符重载形式指定新的含义。

由operator和后⾯要定义的运算符共同构成。和其他函数⼀样,它也具有其返回类型和参数列表以及函数体。

重载运算符参数个数与运算对象个数相同,例如一元运算符有一个参数等。

如果一个重载运算符是成员函数时,那么它的第一个运算对象默认传给隐式的this指针,所以它作为成员函数时,参数会比运算对象少一个。

运算符重载后,它的优先级和结合性不变。

当要重载的运算符是自增自减时,为了很好的区分前置与后置,通常会在后置的重载函数中添加一个int形参。

例如:

int operator++(int)//后置++

int operator++()//前置++

int operator--(int)//后置--

int operator--()//前置--

 既然要重载运算符,自然也就包括我们的流提取(>>)与流插入(<<)符号,在C++中是标准输入输出流(cin和cout)交互的关键但仅仅只适用于接口为内置类型如int,double等,如果是要在自定义的类与对象中想要完整的输入与输出对象,就必须要进行重载。

重载<<和>>,需要重载为全局函数,因为重载后是成员函数,隐含的this指针默认的抢占了第一个形参位置,因为第一个形参位置是左侧运算对象,所以调用后会变成 对象<<cout,不符合我们的使用习惯,但这时我们只需要一个小小的改变那就是将iostream/ostream放在第一个形参位置,对象放在第二个形参位置时,就可以正常的输出了,格式也变为了cout<<对象。

代码展示如下:

6.赋值运算符重载

赋值运算符重载是一个默认成员函数,用于两个已经存在的对象进行直接拷贝赋值。

特点:

规定必须重载为成员函数,且参数建议写成const自身类类型的引用。

与构造函数相同,没有显式的实现时,系统会自动生成默认赋值运算符重载,对于内置类型实现浅拷贝(一个字节),对于自定义类型完成调用它的赋值运算符重载。

有返回值,目的是为了能够实现连续的赋值场景使用。

代码展示如下:

 

7.取地址重载

分为普通取地址重载和const取地址重载,一般编译器自动生成的就够用了,但难免也有一些特殊情况需要我们去手动编写。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐