首页 >> 大全

二、类的六个默认成员函数

2023-12-21 大全 28 作者:考证青年

目录

一、类和对象入门

(一)引入类与对象

(二)成员函数的定义

(三)类对象的大小

(四)隐含的this指针

二、类的六个默认成员函数

(一)构造函数

1.带参构造函数

2.自己实现的无参构造函数

3.自己实现的全缺省构造函数

4.编译器自动生成的构造函数

5.注意

(二)析构函数

1.自己写的析构函数

2.编译器自动生成的析构函数

(三)拷贝构造函数

1.自己写的拷贝构造函数

2.编译器自动生成的拷贝构造函数

(四)赋值运算符重载

1.运算符重载

(1)==的重载

(2)>的重载

2.赋值运算符重载

(1)自己写的赋值运算符重载

(2)编译器自动生成的赋值运算符重载

(3)对比拷贝构造和赋值运算符重载

(五)取地址运算符重载

(六)const取地址运算符重载

三、关于类中的一些细节

(一)关于构造函数的一些细节

1.初始化列表

2.关键字

(二)友元

1.友元函数

2.友元类

(三)const成员

1.const修饰引用(常引用问题)

2.const修饰类的 成员函数

(四)成员

1.成员变量

2.成员函数

四、理解封装

一、类和对象入门

面向对象更关注对象与对象之间的关系;面向对象的三大特性:封装、继承、多态。

(一)引入类与对象

在C语言中,结构体中只能定义变量

在C++中,类(class)和结构体()中可以定义两种:1.成员变量 2.成员函数

注意:一般情况下成员变量都是比较隐私的,都会定义成私有()或保护()

私有和保护就像一把锁:他不让门外面的人进来,只可以是门里面的人进行访问

既然C++中都可以用来定义类,那么类和结构体之间有什么区别呢?

(1)默认的访问限定符不同:class默认是私有的,默认是共有的。

(2)对于C++中的:他不仅可以用来定义类,他还兼容了C中定义结构体的用法。

(二)成员函数的定义

1.在类里面定义:编译器可能将成员函数当作内联函数

2.在类里面声明,类外面定义:需要使用(::)符号

(三)类对象的大小

如何计算一个类实例化出的对象的大小?计算成员变量之和,并且考虑内存对齐规则

问题一:为什么类对象只保存成员变量,而成员函数放在公共代码段?

答:一个类实例化出N个对象,每个对象的成员变量都可以存储不同的值,但是调用的函数却是

同一个。如果每个对象都放成员函数,但是这些函数却是一样的,那么就会存储重复的东西,浪

费空间。所以将成员函数存在公共代码段,类实例化出的对象的大小不包含成员函数,只包含成

员变量。

问题二:没有成员变量的类的大小是1 -> 为什么是1,而不是0?

答:开1个字节不是为了存数据,而是占位。

(四)隐含的this指针

this指针的本质:成员函数的形参(他默认就是成员函数的第一个形参)。

this指针指向谁?哪个类调用成员函数,this指针就指向调用他的类。

如果定义class A,那么一个指向A的指针所指向的空间只存着成员变量,没有存成员函数。

成员函数通过this->_var这种形式就可以访问到成员变量,只是这件事是编译器帮我们完成的。

二、类的六个默认成员函数

六个默认成员函数特点:自己没写编译器自动生成,自己写了编译器就不会生成了

负责初始化和清理的两个函数:构造函数(负责初始化)、析构函数(负责资源清理工作)

负责拷贝复制的两个函数:拷贝构造函数、赋值运算符重载

两个取地址操作符重载函数:取地址运算符重载,const取地址运算符重载

(一)构造函数

完成对象的初始化工作,而不是对象的创建。对象创建时自动调用完成初始化工作。

特征:函数名与类名相同、无返回值、支持重载(有以下四种形式)

下面通过日期类来说明这四种构造函数(关于日期类的定义请关注另一篇博客)

1.带参构造函数

定义

    Date(int year, int month, int day){_year = year;_month = month;_day = day;}

调用方式

Date d(2023,10,30);

2.自己实现的无参构造函数

定义

    Date(){_year = 0;_month = 1;_day = 1;}

调用方式

    Date d2;//调不带参数的构造函数,不加括号//Date d2();//不能加括号,要是调带参数的,那就要加括号了

3.自己实现的全缺省构造函数

使用全缺省的目的:将带参构造函数和无参构造函数合二为一。(最常用)

定义

    Date(int year = 0, int month = 1, int day = 1){_year = year;_month = month;_day = day;}

调用方式

Date d1(2023,11,14);//这种是初始化成自己设置的值
Date d2;//这种是初始化成默认值0,1,1

4.编译器自动生成的构造函数

如果我们没有显示定义(自己写)构造函数,那么C++编译器会自动生成一个无参的默认构造函数

对于 编译器自动生成的无参默认构造函数(双标)

(1)针对内置类型的成员变量 没有做处理

(2)针对自定义类型的成员变量,调用他的构造函数去初始化

一旦用户显示定义了,编译器将不再生成

5.注意

(1)默认构造函数有3种 特点:不用传参数

自己实现的无参的构造函数②、自己实现的全缺省构造函数③、编译器自动生成的构造函数④

(2)三种默认构造函数不能同时存在,他们三个只能同时存在一个。

(3)对比两个概念

默认成员函数:指的是最开始说的那六个,自己不写编译器自动生成。

默认构造函数:说的是(1)中的三种,不传参就能调用。

(4)如果采用声明和定义分离的形式,缺省参数在声明处给就好了,不用在定义处给了。

(5)C++11中支持一种全新的给出缺省参数的方式

class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}//c++11支持的在声明时给缺省值(注意这不是初始化)
private:int _year = 0;int _month = 1;int _day = 1;
};

(二)析构函数

完成对象里面的资源清理工作,不是对象的销毁。对象生命周期到了以后自动调用完成清理工作

特征:函数名(~类名)、无返回值无参数、不支持重载

问题:构造和析构的顺序?先构造的后析构。原因:因为对象是存在栈上的,满足后进先出。

Date d1;
Date d2;

先构造d1,后构造d2;先析构d2,后析构d1。

1.自己写的析构函数

以栈类为例:注意⚠️函数名(~类)

class Stack
{
public://构造函数Stack(int n = 10){_arr = (int*)malloc(sizeof(int)*n);_size = 0;_capacity = n;}//析构函数~Stack(){free(_arr);_arr = nullptr;_size = _capacity = 0;}private:int* _arr;size_t _size;size_t _capacity;
};

2.编译器自动生成的析构函数

如果我们没有显示定义(自己写)析构函数,那么C++编译器会自动生成一个析构函数

对于 编译器自动生成的析构函数(双标)

(1)针对内置类型的成员变量,没有做处理。

(2)针对自定义类型的成员变量,调用他的析构函数。

一旦用户显示定义了,编译器将不再生成

(三)拷贝构造函数 1.自己写的拷贝构造函数

首先说明:1.拷贝构造函数是构造函数的一个重载形式 2.拷贝构造函数只有一个引用参数

解释:为什么拷贝构造函数只有一个引用参数,而且传值方式会引发无穷递归?

Date d(d1);

将d1传给d是一次拷贝构造(函数传参),但这时拷贝构造函数还没有写呢。也就是说在拷贝构

造函数还不存在的情况下就要调用拷贝构造函数了,所以就会引发无穷递归。

拷贝构造函数的定义(以日期类为例)

解释const的作用:保护d为别名的那块空间的内容不被修改。

Date(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}

拷贝构造函数的调用(两种方式)

Date d(d1);    //方式一
Date d = d2;   //方式二

2.编译器自动生成的拷贝构造函数

编译器生成的拷贝构造,会完成按字节的值拷贝(浅拷贝)。

对于Date类这种,完成浅拷贝即可,所以编译器生成的足够用。但是Stack类这种,必须自己去

实现深拷贝。

(四)赋值运算符重载

要了解赋值运算符重载,我们可以先学习运算符重载

1.运算符重载

对于内置类型(int long···)可以直接使用== !=这种运算符。

对于自定义类型,我们不可以直接使用自带的运算符;要想直接使用,就要用到运算符重载。

有关运算符重载的几点说明

①对于运算符重载,有一个关键字。

②.* :: ?: . 这五个运算符不能重载。

下面用日期类来演示具体用法。

(1)==的重载

第一种写法:将运算符重载函数写在类外面(这样写要将成员变量改成)

注意⚠️运算符有几个操作数,重载的函数就有几个参数。

注意⚠️自定义类型是不能用运算符的,要用就得实现重载函数,自定义类型用的时候等价于调

用这个重载函数。

bool operator==(const Date& d1, const Date& d2)
{return d1._year == d2._year&& d1._month == d2._month&& d1._day == d2._day;
}//使用
cout << (d1 == d2) << endl;

第二种写法:将运算符重载函数写在类里面(这样写要将成员变量可以是私有)

这种写法更常用,因为我们一般都需要成员变量是私有的。

//这里实际上有两个参数:一个是this指针,另一个是操作数
bool operator==(const Date& d)//bool operator==(Date* this,const Date& d)
{return _year == d._year && _month == d._month && _day == d._day;
}//使用
cout << (d1 == d2) << endl;

(2)>的重载

bool operator>(const Date& d)
{//年大的日期一定大    if(_year > d._year){return true;}//年一样,月大的一定大else if(_year == d._year && _month > d._month){return true;}//年一样,月一样,日大的一定大else if(_year == d._year && _month == d._month && _day > d._day){return true;}//其他情况都是falsereturn false;
}//使用
cout << (d1 > d2) << endl;

这里只介绍==和>的重载,因为这足以说明运算符重载的写法。

xxx以日期类为例,介绍了各种运算符重载。

2.赋值运算符重载 (1)自己写的赋值运算符重载

//这里返回值类型为Date是为了实现连续赋值
//用引用返回是因为返回值*this出了函数作用域还存在
//引用返回还可以减少拷贝构造,提高效率
Date& operator =(const Date& d)
{if(this != &d)//针对自己给自己赋值的检查判断//如果出现自己给自己赋值,可以不走下面的步骤,提高效率{_year = d._year;_month = d._month;_day = d._day;}return *this;
}

(2)编译器自动生成的赋值运算符重载

编译器生成的=,会完成按字节的值拷贝(浅拷贝)。

对于Date类这种,完成浅拷贝即可,所以编译器生成的足够用。但是Stack类这种,必须自己去

实现深拷贝。

(3)对比拷贝构造和赋值运算符重载

①调用形式上的差别

//拷贝构造
Date d(d1);
Date d = d1;//赋值运算符重载
d2 = d1;

②拷贝构造函数和= 都是默认成员函数,我们不现实时,编译器会帮我们实现一份。

我们不现实时,编译器生成的拷贝构造和=。会完成按字节的值拷贝(浅拷贝)。

也就是说有些类,我们是不需要去实现拷贝构造和=,因为编译器默认生成的就可以用

比如:Date类就是这样,但是Stack类就不可以(因为需要深拷贝)

(五)取地址运算符重载

一般不用自己写,编译器自动生成的足够我们使用。

Date* operator &()
{cout << "Date* operator &()" << endl;//用于测试是否被调用return this;
}//调用
Date d1;
cout << &d1 << endl;

(六)const取地址运算符重载

一般不用自己写,编译器自动生成的足够我们使用。

const Date* operator &() const
{cout << "const Date* operator &() const" << endl;//用于测试是否被调用return this;
}//调用
const Date d2;
cout << &d2 << endl;

三、关于类中的一些细节 (一)关于构造函数的一些细节 1.初始化列表

我们之前一直这样写构造函数,这种构造函数调用之后,对象中就已经有了一个初始值,但是这

个过程不能称作对象的初始化。这样写只能叫做赋初值,而不能叫做初始化。

因为初始化只能初始化一次,而在构造函数内可以多次赋值(显然第一次赋值就是赋初值)

Date(int year = 0, int month = 1, int day = 1)
{//函数体内赋值_year = year;_month = month;_day = day;
}

写成初始化列表就是初始化了,具体语法格式如下

Date(int year = 0, int month = 1, int day = 1):_year(year),_month(month),_day(day)
{}

现在就有疑问了,那我保证在函数体内只赋值一次不就好了?为什么非要搞出一个初始化列表?

解释:①引用成员变量、const成员变量、没有默认构造函数的自定义类型的成员变量

上述三种成员变量必须进行初始化,所以就一定需要初始化列表!!!

②建议优先使用初始化列表,因为初始化的效率比赋值的效率高。

注意⚠️声明和初始化列表的顺序要保持一致,这样可以减少出错!

//最好要顺序一致,如下所示
class Test
{
public:Test(int a1, int a2):_a1(a1),_a2(a2){}private:int _a1;int _a2;
};//尽量不要这样
class Test
{
public:Test(int a1, int a2):_a2(a2),_a1(a1){}private:int _a1;int _a2;
};

2.关键字

要想学习关键字,就要先辨别这三种写法的区别。

(1)单参数隐士类型转换【第三种写法才是】

class Date
{
public:Date(int year):_year(year){}
private:int _year;int _month;int _day;
};//三种写法
Date d1(1);//构造函数
Date d2 = d1;//拷贝构造
Date d3 = 3;//单参数隐士类型的转换 构造出tmp(3)+在用tmp拷贝构造d3(tmp)

(2)C++11支持的多参数隐士类型转换【第三种写法才是】

class Date
{
public:Date(int year ,int month ,int day):_year(year),_month(month),_day(day){}
private:int _year;int _month;int _day;
};//三种写法
Date d1(2023,11,18);//构造函数
Date d2 = d1;//拷贝构造
Date d3 = {2023,11,19};//多参数隐士类型的转换

我们看见第三种写法很奇怪,要是不想出现这种隐士类型转换,就在构造函数前加上

也就是说在构造函数前加上,就不可以出现第三种写法了。

class Date
{
public:explicit Date(int year):_year(year){}
private:int _year;int _month;int _day;
};Date d1(1);//构造函数
Date d2 = d1;//拷贝构造
//Date d3 = 3;//隐士类型转换(加上explicit就报错了)

(二)友元

友元()是一种访问私有和保护的方式:正常情况下我们在类外面访问不了私有和保护,

但是如果我们成为朋友,那我就可以访问你的私有和保护了。

1.友元函数

(1)相关知识

①友元函数可以访问类的私有和保护成员,但不是类的成员函数。

②友元函数不能用const修饰。

③友元函数可以在类的任何地方声明,不受访问限定符的限制。

④一个函数可以是多个类的友元函数。

(2)应用:重载>

不使用友元函数同样可以重载>,那我们为什么要使用友元呢?

不使用友元,在类里面重载>,使用的时候只能这样:d1 非const

class Test
{
public:void f1() const{}void f2(){}
};const Test t2;
t2.f1();//const对象可以访问const成员函数
//t2.f2();//const对象不可以访问非const成员函数

②非const对象可以调用const成员函数。权限缩小:非const->const

class Test
{
public:void f1() const{}void f2(){}
};Test t1;
t1.f1();//非const对象可以调用const成员函数函数
t1.f2();//非const对象可以调用非const成员函数函数

(2)成员函数调用 const成员函数

①const成员函数不可以调用非const成员函数。权限放大:const->非const

//f3 f4属于放大行为 不行
void f3()//void f1 (date* this)
{}void f4()const//void f4(const date* this)
{f3();//this->f3(this)
}

②非const成员函数可以调用const成员函数。权限缩小:非const->const

//f1 f2属于缩小行为 可以
void f1()//void f1 (date* this)
{f2();//this->f2(this)
}
void f2() const
{}

(四)成员 1.成员变量

成员变量 不存在对象中,而是存在静态区。他属于这个类的所有对象,也属于这个类本身。

2.成员函数

(1)成员函数,没有this指针,不使用对象就可以调用。

-> 类名::func() 【⚠️这种方式只能调用静态成员函数】

(2)成员函数,不能访问 类中的非静态成员(成员变量+成员函数)

但是非静态成员函数可以访问静态成员(成员变量+成员函数)

①静态成员函数不能访问非静态成员变量 和 非静态成员函数

总结:静态成员函数 只能访问静态的(成员变量+成员函数)

class Test
{
public:void f1(){}static void f2(){//f1();//静态成员函数不能访问非静态成员函数//_a = 10;//静态成员函数不能访问非静态成员变量}private:int _a;static int _n;
};

②非静态成员函数可以访问静态成员变量 和 静态成员函数

总结:非静态成员函数 静态和非静态(成员变量+成员函数)都能访问

class Test
{
public:static void f3(){}void f4(){}void f5(){f3();//非静态成员函数可以访问静态成员函数_n = 100;//非静态成员函数可以访问静态成员变量f4();//非静态成员函数可以访问非静态成员函数_a = 10;//非静态成员函数可以访问非静态成员变量}private:int _a;static int _n;
};

四、理解封装

1.数据和方法定义到一起。

2.把想给你看到的数据给你看,不想给你看到的封装起来。(通过三种访问限定符实现)

3.一般成员变量为私有,成员函数为公有。我们只能通过接口函数去改变数据。

关于我们

最火推荐

小编推荐

联系我们


版权声明:本站内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 88@qq.com 举报,一经查实,本站将立刻删除。备案号:桂ICP备2021009421号
Powered By Z-BlogPHP.
复制成功
微信号:
我知道了